视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
mysqld_safe启动脚本源码阅读、分析_MySQL
2020-11-09 19:36:07 责编:小采
文档


前几天读了下mysqld_safe脚本,个人感觉还是收获蛮大的,其中细致的交代了MySQL数据库的启动流程,包括查找MySQL相关目录,解析配置文件以及最后如何调用mysqld程序来启动实例等,有着不错的参考价值;与此同时,脚本中涉及了很多shell编程中的小技巧,像变量解析,sed替换转义,进程优先级的判断以及无处不在test结构等等,当作Linux shell的学习素材还是非常合适的,下面是我的环境:

数据库版本: MySQL 5.1.45
操作系统版本: Red Hat Enterprise Linux AS release 4 (Nahant Update 3)
MySQL基目录: /usr/local/mysql3306
配置文件目录: /usr/local/mysql3306/etc

数据库是安装好了的,代码如下:

#!/bin/sh# 一些状态变量的定义KILL_MYSQLD=1; # 试图kill多余的mysqld_safe程序,1表示需要killMYSQLD= # mysqld二进制可执行文件的名称niceness=0 # 进程的调度优先级标识# 下面的变量主要用于标识不使用错误日志和sysloglogging=init # 日志记录状态,init代表初始化want_syslog=0 # 标识是否要使用syslogsyslog_tag=user='mysql' # --user选项值pid_file= # pid文件的路径err_log= # 错误日志的路径# 这两个都是定义的syslog中标志位,在后面需要写入日志到syslog中时使用syslog_tag_mysqld=mysqldsyslog_tag_mysqld_safe=mysqld_safetrap '' 1 2 3 15	# 不允许程序在终端上被人打断(包括挂起,中断,退出,系统终止的情形)umask 007 # 默认权限770,其他组用户对该程序创建的文件没有任何权限# defaults变量记载使用的配置文件的信息defaults=case "$1" in --no-defaults|--defaults-file=*|--defaults-extra-file=*) defaults="$1"; shift ;;esac# usage()函数:使用--help选项时
输出的使用帮助信息usage () { cat <输出函数,这是个原型,后面被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 $logging in init) ;; # 初始化状态时,只在命令行输出msg信息,不记录日志 file) echo "$msg" >> "$err_log" ;; # 记录到err_log中 syslog) logger -t "$syslog_tag_mysqld_safe" -p "$priority" "$*" ;; # 使用logger记录到系统日志中 *) echo "Internal program error (non-fatal):" / " unknown logging method '$logging'" >&2 ;; esac}# 下面两个函数是对log_generic函数中不同分类的引用log_error () { log_generic daemon.error "$@" >&2}log_notice () { log_generic daemon.notice "$@"}# 后面就是用它启动的mysqld,通过logging变量区分记录日志的类型,分错误日志和系统日志syslog两种# 最后的eval命令会解析 $cmd 中的值并执行命令eval_log_error () { cmd="$1" case $logging in file) cmd="$cmd >> "`shell_quote_string "$err_log"`" 2>&1" ;; syslog) cmd="$cmd 2>&1 | logger -t '$syslog_tag_mysqld' -p daemon.error" ;; *) echo "Internal program error (non-fatal):" / " unknown logging method '$logging'" >&2 ;; esac #echo "Running mysqld: [$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= if test "$1" = PICK-ARGS-FROM-ARGV then pick_args=1 shift fi for arg do # 取出参数值,比如 --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=*) if test -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"; export TZ; ;; # 生效了一下时区设置 --help) usage ;; # 调用了usage函数,输出帮助信息 *) if test -n "$pick_args" then # 将其他命令行参数值附加到$arg的后面 append_arg_to_args "$arg" fi ;; esac done}######################################### 正式工作开始了!!########################################## 下面两段是在寻找基目录和mysqld所在目录## 找到/usr/local/mysql3306/share/mysql目录,使用relpkgdata来记录相对路径和绝对路径# 这个grep其实应该是想判断一下share/mysql是不是显示的绝对路径,不知道这么写的意义在哪里。if echo '/usr/local/mysql3306/share/mysql' | grep '^/usr/local/mysql3306' > /dev/nullthen # 一口气用了三个替换,分别为: # 第一步:将/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`if test -n "$MY_BASEDIR_VERSION" -a -d "$MY_BASEDIR_VERSION"then if test -x "$MY_BASEDIR_VERSION/libexec/mysqld" then ledir="$MY_BASEDIR_VERSION/libexec" else ledir="$MY_BASEDIR_VERSION/bin" fi# 这里对errmsg.sys文件进行了判断,个人认为这是为了确认当前目录为一个mysql安装基目录elif test -f "$relpkgdata"/english/errmsg.sys -a -x "$MY_PWD/bin/mysqld"then MY_BASEDIR_VERSION="$MY_PWD" ledir="$MY_PWD/bin"elif test -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顺序,如下# Default options are read from the following files in the given order:# /etc/my.cnf /etc/mysql/my.cnf /home/mysql/mysql_master/etc/my.cnf ~/.my.cnf# 或者可以使用strace -e open libexec/mysqld 2>&1 | grep my.cnf查看if test -d $MY_BASEDIR_VERSION/data/mysqlthen DATADIR=$MY_BASEDIR_VERSION/data if test -z "$defaults" -a -r "$DATADIR/my.cnf" then defaults="--defaults-extra-file=$DATADIR/my.cnf" fi# 接下来找到数据文件的目录elif test -d $MY_BASEDIR_VERSION/var/mysqlthen DATADIR=$MY_BASEDIR_VERSION/var# 找不到就用编译时指定的默认值else DATADIR=/usr/local/mysql3306/varfi# 对存在两个配置文件情况进行冲突处理if test -z "$MYSQL_HOME"then if test -r "$MY_BASEDIR_VERSION/my.cnf" && test -r "$DATADIR/my.cnf" then # 优先考虑 $MY_BASEDIR_VERSION/my.cnf 文件 log_error "WARNING: Found two instances of my.cnf -$MY_BASEDIR_VERSION/my.cnf and$DATADIR/my.cnfIGNORING $DATADIR/my.cnf" MYSQL_HOME=$MY_BASEDIR_VERSION elif test -r "$DATADIR/my.cnf" then log_error "WARNING: Found $DATADIR/my.cnfThe data directory is a deprecated location for my.cnf, please move it to$MY_BASEDIR_VERSION/my.cnf" MYSQL_HOME=$DATADIR else MYSQL_HOME=$MY_BASEDIR_VERSION fifiexport MYSQL_HOME## 下面是使用bin/my_print_defaults读取my.cnf文件中的配置信息([mysqld] and [mysqld_safe])# 并且和命令行中传入的参数进行合并# 先是找到my_print_defaults执行文件 又是各种路径判断if test -x "$MY_BASEDIR_VERSION/bin/my_print_defaults"then print_defaults="$MY_BASEDIR_VERSION/bin/my_print_defaults"elif test -x ./bin/my_print_defaultsthen print_defaults="./bin/my_print_defaults"elif test -x /usr/local/mysql3306/bin/my_print_defaultsthen print_defaults="/usr/local/mysql3306/bin/my_print_defaults"elif test -x /usr/local/mysql3306/bin/mysql_print_defaultsthen 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-verbose mysqld server` if test $SET_USER -eq 2then SET_USER=0fi# 又对[safe_mysqld]和[mysqld_safe]组中的内容进行了过滤读取# 在我的配置文件中已经没有这两个组了,估计是为兼容旧版本的需要parse_arguments `$print_defaults $defaults --loose-verbose mysqld_safe safe_mysqld`# 用命令行输入选项 $@ 来覆盖配置文件中的选项 机智parse_arguments PICK-ARGS-FROM-ARGV "$@"## 下面是logging工具的使用## 判断logger工具是否可用if [ $want_syslog -eq 1 ]then my_which logger > /dev/null 2>&1 if [ $? -ne 0 ] then log_error "--syslog requested, but no 'logger' program found. Please ensure that 'logger' is in your PATH, or do not specify the --syslog option to mysqld_safe." exit 1 fifi# 给err_log改名字。。if [ -n "$err_log" -o $want_syslog -eq 0 ]then if [ -n "$err_log" ] then # 下面是为err_log添加一个.err后缀(如果现在名字没有后缀) # 如果不设置这个后缀,mysqld_safe和mysqld程序会将日志写入不同的文件中 # 因为在 mysqld 程序中,它将识别带有.的文件名为错误日志(脚本注释上说的) # 这里的expr是识别文件名中“.”前面的字符总数量(包括.),如果没有设置后缀,返回就是0了 if expr "$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 -eq 1 ] then log_error "Can't log to error log and syslog at the same time. Remove all --log-error configuration options for --syslog to take effect." fi # Log to err_log file log_notice "Logging to '$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 "Logging to syslog." logging=syslogfi# 设置--user选项 USER_OPTION=""if test -w / -o "$USER" = "root" # 根目录是否可写,或者当前用户为rootthen if test "$user" != "root" -o $SET_USER = 1 then USER_OPTION="--user=$user" fi # 创建错误日志,并将日志授权给指定的用户 if [ $want_syslog -eq 0 ]; then touch "$err_log" chown $user "$err_log" fi # 这里它还对当前用户做了ulimit设置,包括可以打开的文件数量--open_files-limit选项 if test -n "$open_files" then ulimit -n $open_files append_arg_to_args "--open-files-limit=$open_files" fifisafe_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 chmod 755 $mysql_unix_port_dirfi# 如果用户没有制定mysqld程序的名称,这里就默认赋值为mysqldif test -z "$MYSQLD"then MYSQLD=mysqldfi# 下面几段分别是对 mysqld , pid , port文件选项的检查和设置,省略100个字if test ! -x "$ledir/$MYSQLD"then log_error "The file $ledir/$MYSQLDdoes not exist or is not executable. Please cd to the mysql installationdirectory and restart this script from there as follows:./bin/mysqld_safe&See http://dev.mysql.com/doc/mysql/en/mysqld-safe.html for more information" exit 1fiif test -z "$pid_file"then pid_file="$DATADIR/`/bin/hostname`.pid"else case "$pid_file" in /* ) ;; * ) pid_file="$DATADIR/$pid_file" ;; esacfiappend_arg_to_args "--pid-file=$pid_file"if test -n "$mysql_unix_port"then append_arg_to_args "--socket=$mysql_unix_port"fiif test -n "$mysql_tcp_port"then append_arg_to_args "--port=$mysql_tcp_port"fi## 接下来是关于优先级的设置#if test $niceness -eq 0then NOHUP_NICENESS="nohup"else NOHUP_NICENESS="nohup nice -$niceness"fi# 将当前的默认优先级设置为0if nohup nice > /dev/null 2>&1then # normal_niceness记载默认的调度优先级 normal_niceness=`nice` # nohup_niceness记载使用nohup执行方式的调度优先级 nohup_niceness=`nohup nice 2>/dev/null` numeric_nice_values=1 # 这个for是为了检查$normal_niceness $nohup_niceness两个变量值的合法性 for val in $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程序的方式 if test $numeric_nice_values -eq 1 then nice_value_diff=`expr $nohup_niceness - $normal_niceness` if test $? -eq 0 && test $nice_value_diff -gt 0 && / nice --$nice_value_diff echo testing > /dev/null 2>&1 then # 进入分支说明$nohup_niceness的值比$normal_niceness大,即nohup执行方式调度优先级比正常执行方式低 # 这是不希望看到的,所以下面就人为的提升了nohup的优先级(降低niceness的值) niceness=`expr $niceness - $nice_value_diff` NOHUP_NICENESS="nice -$niceness nohup" fi fielse # 下面是测试nohup在当前系统中是否可用,不可用的话就置空NOHUP_NICENESS if nohup echo testing > /dev/null 2>&1 then : else NOHUP_NICENESS="" fifi# 指定内核文件大小if test -n "$core_file_size"then ulimit -c $core_file_sizefi## 如果已经存在一个pid文件,则检查是否有已经启动的mysqld_safe进程if test -f "$pid_file"then PID=`cat "$pid_file"` if /bin/kill -0 $PID > /dev/null 2> /dev/null then if /bin/ps wwwp $PID | grep -v " grep" | grep -v mysqld_safe | grep -- "$MYSQLD" > /dev/null then log_error "A mysqld process already exists" exit 1 fi fi # 下面是处理办法:删除旧的pid文件并报错 rm -f "$pid_file" if test -f "$pid_file" then log_error "Fatal error: Can't remove the pid file:$pid_filePlease remove it manually and start $0 again;mysqld daemon not started" exit 1 fifi## 下面便是拼接执行语句运行了。#cmd="$NOHUP_NICENESS"# 检查一下命令 并进行转义操作for i in "$ledir/$MYSQLD" "$defaults" "--basedir=$MY_BASEDIR_VERSION" / "--datadir=$DATADIR" "$USER_OPTION"do cmd="$cmd "`shell_quote_string "$i"`donecmd="$cmd $args"# Avoid 'nohup: ignoring input' warningtest -n "$NOHUP_NICENESS" && cmd="$cmd < /dev/null"log_notice "Starting $MYSQLD daemon with databases from $DATADIR"# 后台循环 执行mysqldwhile truedo rm -f $safe_mysql_unix_port "$pid_file" # 保险起见,又删除了一次pid文件 # 调用eval_log_error函数,传入$cmd参数的值,最后使用eval命令执行了启动mysqld eval_log_error "$cmd" if test ! -f "$pid_file" # 没有成功创建pid文件,则退出分支 then break fi # mysqld_safe已经启动的处理方法,保证只有一个mysqld_safe程序启动 if true && test $KILL_MYSQLD -eq 1 then # 统计启动的mysqld进程的数目 numofproces=`ps xaww | grep -v "grep" | grep "$ledir/$MYSQLD/>" | grep -c "pid-file=$pid_file"` log_notice "Number of processes running now: $numofproces" I=1 while test "$I" -le "$numofproces" do # 这个PROC的数据即是ps mysqld_safe程序的输出 第一个数字即为进程ID PROC=`ps xaww | grep "$ledir/$MYSQLD/>" | grep -v "grep" | grep "pid-file=$pid_file" | sed -n '$p'` # 使用T来获取进程ID for T in $PROC do break done # kill掉该个mysqld_safe程序 if kill -9 $T then log_error "$MYSQLD process hanging, pid $T - killed" else break fi # 每干掉一个mysqld_safe就把I加一,这样没有多余的mysqld_safe时就可以跳出循环了 I=`expr $I + 1` done fi log_notice "mysqld restarted"done# 完结撒花log_notice "mysqld from pid file $pid_file ended"

下载本文
显示全文
专题