zl程序教程

您现在的位置是:首页 >  工具

当前栏目

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"