mysqld_safe启动脚本源码阅读、分析
源码 分析 启动 脚本 阅读 safe Mysqld
2023-06-13 09:15:38 时间
前几天读了下mysqld_safe脚本,个人感觉还是收获蛮大的,其中细致的交代了MySQL数据库的启动流程,包括查找MySQL相关目录,解析配置文件以及最后如何调用mysqld程序来启动实例等,有着不错的参考价值;与此同时,脚本中涉及了很多shell编程中的小技巧,像变量解析,sed替换转义,进程优先级的判断以及无处不在test结构等等,当作Linuxshell的学习素材还是非常合适的,下面是我的环境:
数据库版本:MySQL5.1.45
操作系统版本:RedHatEnterpriseLinuxASrelease4(NahantUpdate3)
MySQL基目录:/usr/local/mysql3306
配置文件目录:/usr/local/mysql3306/etc
数据库是安装好了的,代码如下:
#!/bin/sh #一些状态变量的定义 KILL_MYSQLD=1;#试图kill多余的mysqld_safe程序,1表示需要kill MYSQLD=#mysqld二进制可执行文件的名称 niceness=0#进程的调度优先级标识 #下面的变量主要用于标识不使用错误日志和syslog logging=init#日志记录状态,init代表初始化 want_syslog=0#标识是否要使用syslog syslog_tag= user="mysql"#--user选项值 pid_file=#pid文件的路径 err_log=#错误日志的路径 #这两个都是定义的syslog中标志位,在后面需要写入日志到syslog中时使用 syslog_tag_mysqld=mysqld syslog_tag_mysqld_safe=mysqld_safe trap""12315 #不允许程序在终端上被人打断(包括挂起,中断,退出,系统终止的情形) umask007#默认权限770,其他组用户对该程序创建的文件没有任何权限 #defaults变量记载使用的配置文件的信息 defaults= case"$1"in --no-defaults|--defaults-file=*|--defaults-extra-file=*) defaults="$1";shift ;; esac #usage()函数:使用--help选项时输出的使用帮助信息 usage(){ cat<<EOF Usage:$0[OPTIONS] --no-defaultsDon"treadthesystemdefaultsfile --defaults-file=FILEUsethespecifieddefaultsfile --defaults-extra-file=FILEAlsousedefaultsfromthespecifiedfile --ledir=DIRECTORYLookformysqldinthespecifieddirectory --open-files-limit=LIMITLimitthenumberofopenfiles --core-file-size=LIMITLimitcorefilestothespecifiedsize --timezone=TZSetthesystemtimezone --mysqld=FILEUsethespecifiedfileasmysqld --mysqld-version=VERSIONUse"mysqld-VERSION"asmysqld --nice=NICESettheschedulingpriorityofmysqld --skip-kill-mysqldDon"ttrytokillstraymysqldprocesses --syslogLogmessagestosyslogwith"logger" --skip-syslogLogmessagestoerrorlog(default) --syslog-tag=TAGPass-t"mysqld-TAG"to"logger" Allotheroptionsarepassedtothemysqldprogram. EOF exit1 } #my_which的作用相当于which,通过检索$PATH中的路径,打印出命令的全路径 #这个函数就在后面一个地方用到了,就是my_whichlogger,意思等同于转换logger为/usr/bin/logger my_which() { save_ifs="${IFS-UNSET}"#保存当前的内建分隔符,用于后面重置IFS IFS=:#使用:来分割PATH中的路径 ret=0 forfile#这种写法等同于forfilein&* do fordirin$PATH do if[-f"$dir/$file"] then echo"$dir/$file" continue2#continue第2层,这里就是跳出外层循环了 fi done ret=1#signalanerror break done #将设置过的IFS重置回去 if["$save_ifs"=UNSET] then unsetIFS else IFS="$save_ifs" fi return$ret#Success } #日志输出函数,这是个原型,后面被log_error和log_notice函数引用 log_generic(){ #priority代表日志信息的分类,从后面的两个函数可知有:daemon.error和daemon.notice两种类别 priority="$1" shift #日志中记录的msg前缀格式:时间+mysqld_safe,类似于系统日志的记录格式 msg="`date+"%y%m%d%H:%M:%S"`mysqld_safe$*" echo"$msg" case$loggingin init);;#初始化状态时,只在命令行输出msg信息,不记录日志 file)echo"$msg">>"$err_log";;#记录到err_log中 syslog)logger-t"$syslog_tag_mysqld_safe"-p"$priority""$*";;#使用logger记录到系统日志中 *) echo"Internalprogramerror(non-fatal):"\ "unknownloggingmethod"$logging"">&2 ;; esac } #下面两个函数是对log_generic函数中不同分类的引用 log_error(){ log_genericdaemon.error"$@">&2 } log_notice(){ log_genericdaemon.notice"$@" } #后面就是用它启动的mysqld,通过logging变量区分记录日志的类型,分错误日志和系统日志syslog两种 #最后的eval命令会解析$cmd中的值并执行命令 eval_log_error(){ cmd="$1" case$loggingin file)cmd="$cmd>>"`shell_quote_string"$err_log"`"2>&1";; syslog) cmd="$cmd2>&1|logger-t"$syslog_tag_mysqld"-pdaemon.error" ;; *) echo"Internalprogramerror(non-fatal):"\ "unknownloggingmethod"$logging"">&2 ;; esac #echo"Runningmysqld:[$cmd]" eval"$cmd" } #转义函数,用于在非"a-z","A-Z","09","/","_",".","=","-"的特殊字符前加上一个"\" #sed中的\1代表引用前面\(\)中匹配的值 shell_quote_string(){ echo"$1"|sed-e"s,\([^a-zA-Z0-9/_.=-]\),\\\1,g" } #该函数用于解析配置文件中的选项,并赋值给相应的变量 parse_arguments(){ pick_args= iftest"$1"=PICK-ARGS-FROM-ARGV then pick_args=1 shift fi forargdo #取出参数值,比如--port=3306结果为:val=3306注意这里sed中使用;来分割,等同于/ val=`echo"$arg"|sed-e"s;--[^=]*=;;"` case"$arg"in #将参数值传递给对应的变量 --basedir=*)MY_BASEDIR_VERSION="$val";; --datadir=*)DATADIR="$val";; --pid-file=*)pid_file="$val";; --user=*)user="$val";SET_USER=1;; #有些值可能已经在my.cnf配置文件的[mysqld_safe]组下设置了 #某些值会被命令行上指定的选项值覆盖 --log-error=*)err_log="$val";; --port=*)mysql_tcp_port="$val";; --socket=*)mysql_unix_port="$val";; #接下来这几个特殊的选项在配置文件的[mysqld_safe]组中是必须设置的 #我没配置这个组,所以就用不到了(使用mysqld中的默认) --core-file-size=*)core_file_size="$val";; --ledir=*)ledir="$val";; --mysqld=*)MYSQLD="$val";; --mysqld-version=*) iftest-n"$val" then MYSQLD="mysqld-$val" else MYSQLD="mysqld" fi ;; --nice=*)niceness="$val";; --open-files-limit=*)open_files="$val";; --skip-kill-mysqld*)KILL_MYSQLD=0;; --syslog)want_syslog=1;; --skip-syslog)want_syslog=0;; --syslog-tag=*)syslog_tag="$val";; --timezone=*)TZ="$val";exportTZ;;;#生效了一下时区设置 --help)usage;;#调用了usage函数,输出帮助信息 *) iftest-n"$pick_args" then #将其他命令行参数值附加到$arg的后面 append_arg_to_args"$arg" fi ;; esac done } ######################################## #正式工作开始了!! ######################################## # #下面两段是在寻找基目录和mysqld所在目录 # #找到/usr/local/mysql3306/share/mysql目录,使用relpkgdata来记录相对路径和绝对路径 #这个grep其实应该是想判断一下share/mysql是不是显示的绝对路径,不知道这么写的意义在哪里。 ifecho"/usr/local/mysql3306/share/mysql"|grep"^/usr/local/mysql3306">/dev/null then #一口气用了三个替换,分别为: #第一步:将/usr/local/mysql3306转换为空 #第二步:将/share/mysql开头的/转换为空 #第三步:在share/mysql开头加上./,结果即:./share/mysql relpkgdata=`echo"/usr/local/mysql3306/share/mysql"|sed-e"s,^/usr/local/mysql3306,,"-e"s,^/,,"-e"s,^,./,"` else relpkgdata="/usr/local/mysql3306/share/mysql" fi #这一段都是在找mysqld文件,分别判断了libexec和bin目录 #找不到就使用编译时的默认值 MY_PWD=`pwd` iftest-n"$MY_BASEDIR_VERSION"-a-d"$MY_BASEDIR_VERSION" then iftest-x"$MY_BASEDIR_VERSION/libexec/mysqld" then ledir="$MY_BASEDIR_VERSION/libexec" else ledir="$MY_BASEDIR_VERSION/bin" fi #这里对errmsg.sys文件进行了判断,个人认为这是为了确认当前目录为一个mysql安装基目录 eliftest-f"$relpkgdata"/english/errmsg.sys-a-x"$MY_PWD/bin/mysqld" then MY_BASEDIR_VERSION="$MY_PWD" ledir="$MY_PWD/bin" eliftest-f"$relpkgdata"/english/errmsg.sys-a-x"$MY_PWD/libexec/mysqld" then MY_BASEDIR_VERSION="$MY_PWD" ledir="$MY_PWD/libexec" else MY_BASEDIR_VERSION="/usr/local/mysql3306" ledir="/usr/local/mysql3306/libexec" fi # #接下来是找到配置文件和数据文件目录 # #找到配置文件目录 #我的是放在了etc/目录下,mysqld程序是会读取到的 # #可以从my_print_defaults脚本中获得默认的读取my.cnf顺序,如下 #Defaultoptionsarereadfromthefollowingfilesinthegivenorder: #/etc/my.cnf/etc/mysql/my.cnf/home/mysql/mysql_master/etc/my.cnf~/.my.cnf #或者可以使用strace-eopenlibexec/mysqld2>&1|grepmy.cnf查看 iftest-d$MY_BASEDIR_VERSION/data/mysql then DATADIR=$MY_BASEDIR_VERSION/data iftest-z"$defaults"-a-r"$DATADIR/my.cnf" then defaults="--defaults-extra-file=$DATADIR/my.cnf" fi #接下来找到数据文件的目录 eliftest-d$MY_BASEDIR_VERSION/var/mysql then DATADIR=$MY_BASEDIR_VERSION/var #找不到就用编译时指定的默认值 else DATADIR=/usr/local/mysql3306/var fi #对存在两个配置文件情况进行冲突处理 iftest-z"$MYSQL_HOME" then iftest-r"$MY_BASEDIR_VERSION/my.cnf"&&test-r"$DATADIR/my.cnf" then #优先考虑$MY_BASEDIR_VERSION/my.cnf文件 log_error"WARNING:Foundtwoinstancesofmy.cnf- $MY_BASEDIR_VERSION/my.cnfand $DATADIR/my.cnf IGNORING$DATADIR/my.cnf" MYSQL_HOME=$MY_BASEDIR_VERSION eliftest-r"$DATADIR/my.cnf" then log_error"WARNING:Found$DATADIR/my.cnf Thedatadirectoryisadeprecatedlocationformy.cnf,pleasemoveitto $MY_BASEDIR_VERSION/my.cnf" MYSQL_HOME=$DATADIR else MYSQL_HOME=$MY_BASEDIR_VERSION fi fi exportMYSQL_HOME # #下面是使用bin/my_print_defaults读取my.cnf文件中的配置信息([mysqld]and[mysqld_safe]) #并且和命令行中传入的参数进行合并 #先是找到my_print_defaults执行文件又是各种路径判断 iftest-x"$MY_BASEDIR_VERSION/bin/my_print_defaults" then print_defaults="$MY_BASEDIR_VERSION/bin/my_print_defaults" eliftest-x./bin/my_print_defaults then print_defaults="./bin/my_print_defaults" eliftest-x/usr/local/mysql3306/bin/my_print_defaults then print_defaults="/usr/local/mysql3306/bin/my_print_defaults" eliftest-x/usr/local/mysql3306/bin/mysql_print_defaults then print_defaults="/usr/local/mysql3306/bin/mysql_print_defaults" else print_defaults="my_print_defaults" fi #这个函数可以将一个指定的参数附加到$arg中(在此同时执行了转义操作) append_arg_to_args(){ args="$args"`shell_quote_string"$1"` } args= #这里SET_USER=2是针对下面一条parse_arguments来说的 #因为如果在紧接着的parse_arugments函数中设置了--user的值,那么SET_USER就会变为1,表示--user以被配置 #当然如果没有读取到--user的值,就是说--user没有配置,那么会在后面的if结构中设置SET_USER为0 #这样在后面的判断结构中,SET_USER的值0代表没有配置--user的值,1代表已经配置 SET_USER=2 #解析配置文件中的参数,使用--loose-verbose来过滤[mysqld]和[server]组中的内容 parse_arguments`$print_defaults$defaults--loose-verbosemysqldserver` iftest$SET_USER-eq2 then SET_USER=0 fi #又对[safe_mysqld]和[mysqld_safe]组中的内容进行了过滤读取 #在我的配置文件中已经没有这两个组了,估计是为兼容旧版本的需要 parse_arguments`$print_defaults$defaults--loose-verbosemysqld_safesafe_mysqld` #用命令行输入选项$@来覆盖配置文件中的选项机智 parse_argumentsPICK-ARGS-FROM-ARGV"$@" # #下面是logging工具的使用 # #判断logger工具是否可用 if[$want_syslog-eq1] then my_whichlogger>/dev/null2>&1 if[$?-ne0] then log_error"--syslogrequested,butno"logger"programfound.Pleaseensurethat"logger"isinyourPATH,ordonotspecifythe--syslogoptiontomysqld_safe." exit1 fi fi #给err_log改名字。。。 if[-n"$err_log"-o$want_syslog-eq0] then if[-n"$err_log"] then #下面是为err_log添加一个.err后缀(如果现在名字没有后缀) #如果不设置这个后缀,mysqld_safe和mysqld程序会将日志写入不同的文件中 #因为在mysqld程序中,它将识别带有.的文件名为错误日志(脚本注释上说的) #这里的expr是识别文件名中“.”前面的字符总数量(包括.),如果没有设置后缀,返回就是0了 ifexpr"$err_log":".*\.[^/]*$">/dev/null then : else err_log="$err_log".err fi case"$err_log"in /*);; *)err_log="$DATADIR/$err_log";; esac else err_log=$DATADIR/`/bin/hostname`.err fi #追加错误日志的位置选项 append_arg_to_args"--log-error=$err_log" #发出错误提示:不要使用syslog if[$want_syslog-eq1] then log_error"Can"tlogtoerrorlogandsyslogatthesametime.Removeall--log-errorconfigurationoptionsfor--syslogtotakeeffect." fi #Logtoerr_logfile log_notice"Loggingto"$err_log"." logging=files#正式把logging改成files使用错误日志来记录日志 #这个分支就是使用syslog的方法了 else if[-n"$syslog_tag"] then #设置各个syslog的使用标志位 syslog_tag=`echo"$syslog_tag"|sed-e"s/[^a-zA-Z0-9_-]/_/g"` syslog_tag_mysqld_safe="${syslog_tag_mysqld_safe}-$syslog_tag" syslog_tag_mysqld="${syslog_tag_mysqld}-$syslog_tag" fi log_notice"Loggingtosyslog." logging=syslog fi #设置--user选项 USER_OPTION="" iftest-w/-o"$USER"="root"#根目录是否可写,或者当前用户为root then iftest"$user"!="root"-o$SET_USER=1 then USER_OPTION="--user=$user" fi #创建错误日志,并将日志授权给指定的用户 if[$want_syslog-eq0];then touch"$err_log" chown$user"$err_log" fi #这里它还对当前用户做了ulimit设置,包括可以打开的文件数量--open_files-limit选项 iftest-n"$open_files" then ulimit-n$open_files append_arg_to_args"--open-files-limit=$open_files" fi fi safe_mysql_unix_port={mysql_unix_port:-${MYSQL_UNIX_PORT:-/usr/local/mysql3306/tmp/mysql.sock}} #确保$safe_mysql_unix_port目录是存在的 mysql_unix_port_dir=`dirname$safe_mysql_unix_port` if[!-d$mysql_unix_port_dir] then mkdir$mysql_unix_port_dir chown$user$mysql_unix_port_dir chmod755$mysql_unix_port_dir fi #如果用户没有制定mysqld程序的名称,这里就默认赋值为mysqld iftest-z"$MYSQLD" then MYSQLD=mysqld fi #下面几段分别是对mysqld,pid,port文件选项的检查和设置,省略100个字 iftest!-x"$ledir/$MYSQLD" then log_error"Thefile$ledir/$MYSQLD doesnotexistorisnotexecutable.Pleasecdtothemysqlinstallation directoryandrestartthisscriptfromthereasfollows: ./bin/mysqld_safe& Seehttp://dev.mysql.com/doc/mysql/en/mysqld-safe.htmlformoreinformation" exit1 fi iftest-z"$pid_file" then pid_file="$DATADIR/`/bin/hostname`.pid" else case"$pid_file"in /*);; *)pid_file="$DATADIR/$pid_file";; esac fi append_arg_to_args"--pid-file=$pid_file" iftest-n"$mysql_unix_port" then append_arg_to_args"--socket=$mysql_unix_port" fi iftest-n"$mysql_tcp_port" then append_arg_to_args"--port=$mysql_tcp_port" fi # #接下来是关于优先级的设置 # iftest$niceness-eq0 then NOHUP_NICENESS="nohup" else NOHUP_NICENESS="nohupnice-$niceness" fi #将当前的默认优先级设置为0 ifnohupnice>/dev/null2>&1 then #normal_niceness记载默认的调度优先级 normal_niceness=`nice` #nohup_niceness记载使用nohup执行方式的调度优先级 nohup_niceness=`nohupnice2>/dev/null` numeric_nice_values=1 #这个for是为了检查$normal_niceness$nohup_niceness两个变量值的合法性 forvalin$normal_niceness$nohup_niceness do case"$val"in -[0-9]|-[0-9][0-9]|-[0-9][0-9][0-9]|\ [0-9]|[0-9][0-9]|[0-9][0-9][0-9]) ;; *) numeric_nice_values=0;; esac done #这个判断结构很重要 #它保证了使用nohup执行的mysqld程序在调度优先级上不会低于直接执行mysqld程序的方式 iftest$numeric_nice_values-eq1 then nice_value_diff=`expr$nohup_niceness-$normal_niceness` iftest$?-eq0&&test$nice_value_diff-gt0&&\ nice--$nice_value_diffechotesting>/dev/null2>&1 then #进入分支说明$nohup_niceness的值比$normal_niceness大,即nohup执行方式调度优先级比正常执行方式低 #这是不希望看到的,所以下面就人为的提升了nohup的优先级(降低niceness的值) niceness=`expr$niceness-$nice_value_diff` NOHUP_NICENESS="nice-$nicenessnohup" fi fi else #下面是测试nohup在当前系统中是否可用,不可用的话就置空NOHUP_NICENESS ifnohupechotesting>/dev/null2>&1 then : else NOHUP_NICENESS="" fi fi #指定内核文件大小 iftest-n"$core_file_size" then ulimit-c$core_file_size fi # #如果已经存在一个pid文件,则检查是否有已经启动的mysqld_safe进程 iftest-f"$pid_file" then PID=`cat"$pid_file"` if/bin/kill-0$PID>/dev/null2>/dev/null then if/bin/pswwwp$PID|grep-v"grep"|grep-vmysqld_safe|grep--"$MYSQLD">/dev/null then log_error"Amysqldprocessalreadyexists" exit1 fi fi #下面是处理办法:删除旧的pid文件并报错 rm-f"$pid_file" iftest-f"$pid_file" then log_error"Fatalerror:Can"tremovethepidfile: $pid_file Pleaseremoveitmanuallyandstart$0again; mysqlddaemonnotstarted" exit1 fi fi # #下面便是拼接执行语句运行了。 # cmd="$NOHUP_NICENESS" #检查一下命令并进行转义操作 foriin"$ledir/$MYSQLD""$defaults""--basedir=$MY_BASEDIR_VERSION"\ "--datadir=$DATADIR""$USER_OPTION" do cmd="$cmd"`shell_quote_string"$i"` done cmd="$cmd$args" #Avoid"nohup:ignoringinput"warning test-n"$NOHUP_NICENESS"&&cmd="$cmd</dev/null" log_notice"Starting$MYSQLDdaemonwithdatabasesfrom$DATADIR" #后台循环执行mysqld whiletrue do rm-f$safe_mysql_unix_port"$pid_file" #保险起见,又删除了一次pid文件 #调用eval_log_error函数,传入$cmd参数的值,最后使用eval命令执行了启动mysqld eval_log_error"$cmd" iftest!-f"$pid_file" #没有成功创建pid文件,则退出分支 then break fi #mysqld_safe已经启动的处理方法,保证只有一个mysqld_safe程序启动 iftrue&&test$KILL_MYSQLD-eq1 then #统计启动的mysqld进程的数目 numofproces=`psxaww|grep-v"grep"|grep"$ledir/$MYSQLD\>"|grep-c"pid-file=$pid_file"` log_notice"Numberofprocessesrunningnow:$numofproces" I=1 whiletest"$I"-le"$numofproces" do #这个PROC的数据即是psmysqld_safe程序的输出第一个数字即为进程ID PROC=`psxaww|grep"$ledir/$MYSQLD\>"|grep-v"grep"|grep"pid-file=$pid_file"|sed-n"$p"` #使用T来获取进程ID forTin$PROC do break done #kill掉该个mysqld_safe程序 ifkill-9$T then log_error"$MYSQLDprocesshanging,pid$T-killed" else break fi #每干掉一个mysqld_safe就把I加一,这样没有多余的mysqld_safe时就可以跳出循环了 I=`expr$I+1` done fi log_notice"mysqldrestarted" done #完结撒花 log_notice"mysqldfrompidfile$pid_fileended"
相关文章
- CRMEB V4.X打通版小程序公众号H5 App商城源码
- WebGoat 源码环境的搭建(非jar包搭建)
- 用户管理系统+源码+效果图
- 6.S081/6.828: xv6源码分析--networking
- Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇)「建议收藏」
- TiKV 源码阅读三部曲(一)重要模块
- zookeeper投票选举源码分析
- Nextcloud 源码部署迁移到容器部署
- 干货!源码分析从 import axios from 'axios' 的执行过程(一)
- react源码分析之协调与调度
- golang源码分析:将域名解析代理到自定义域名服务器
- golang源码分析:http代理和https代理
- React源码分析--commit
- selenium源码通读·9 |webdriver/common/desired_capabilities.py-DesiredCapabilities类分析
- 【Netty源码分析】02 Netty Server 启动流程 下
- 【Android 性能优化】应用启动优化 ( 阶段总结 | Trace 文件分析及解决方案 | 源码分析梳理 | 设置主题的方案总结 ) ★
- 【Android 安全】DEX 加密 ( Application 替换 | Android 应用启动原理 | Instrumentation 源码分析 )
- 【Android 插件化】Hook 插件化框架 ( 从 Hook 应用角度分析 Activity 启动流程 一 | Activity 进程相关源码 )
- 【Android UI】Canvas 画布 ⑥ ( Canvas 绘图源码分析 | ViewRootImpl#draw 方法源码 | ViewRootImpl#drawSoftware 方法源码 )
- 深入浅出 Linux 源码阅读指南(阅读linux源码)
- Redis源码分析:深入剖析运行原理(redis源码分析)
- 分析Linux UDP源码实现原理(linuxudp源码)
- 解析深度剖析Redis中链表源码(redis链表源码)
- 深入理解Redis连接池源码分析(redis连接池源码解析)
- jQuery源码分析笔记
- vb简易计算器源码
- des加密解密源码C#key值问题分析
- 纯C语言:分治假币问题源码分享