• 一篇很好的关于mysqld_safe脚本源码解读的文章,收藏了!!


    #!/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 '' 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 <<EOF
    Usage: $0 [OPTIONS]
     --no-defaults       Don't read the system defaults file
     --defaults-file=FILE    Use the specified defaults file
     --defaults-extra-file=FILE Also use defaults from the specified file
     --ledir=DIRECTORY     Look for mysqld in the specified directory
     --open-files-limit=LIMIT  Limit the number of open files
     --core-file-size=LIMIT   Limit core files to the specified size
     --timezone=TZ       Set the system timezone
     --mysqld=FILE       Use the specified file as mysqld
     --mysqld-version=VERSION  Use "mysqld-VERSION" as mysqld
     --nice=NICE        Set the scheduling priority of mysqld
     --skip-kill-mysqld     Don't try to kill stray mysqld processes
     --syslog          Log messages to syslog with 'logger'
     --skip-syslog       Log messages to error log (default)
     --syslog-tag=TAG      Pass -t "mysqld-TAG" to 'logger'
     
    All other options are passed to the mysqld program.
     
    EOF
        exit 1
    }
     
    # my_which的作用相当于which,通过检索$PATH中的路径,打印出命令的全路径
    # 这个函数就在后面一个地方用到了,就是my_which logger,意思等同于转换logger为/usr/bin/logger
    my_which ()
    {
     save_ifs="${IFS-UNSET}"   # 保存当前的内建分隔符,用于后面重置IFS
     IFS=:            # 使用 : 来分割PATH中的路径
     ret=0
     for file           # 这种写法等同于for file in &*
     do
      for dir in $PATH
      do
       if [ -f "$dir/$file" ]
       then
        echo "$dir/$file"
        continue 2       # continue 第 2 层, 这里就是跳出外层循环了
       fi
      done
        ret=1 # signal an error
        break
     done
     
    # 将设置过的IFS重置回去
     if [ "$save_ifs" = UNSET ]
     then
      unset IFS
     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 $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/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`
    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/mysql
    then
     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/mysql
    then
     DATADIR=$MY_BASEDIR_VERSION/var
    # 找不到就用编译时指定的默认值
    else
     DATADIR=/usr/local/mysql3306/var
    fi
     
    # 对存在两个配置文件情况进行冲突处理
    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.cnf
    IGNORING $DATADIR/my.cnf"
     
      MYSQL_HOME=$MY_BASEDIR_VERSION
     elif test -r "$DATADIR/my.cnf"
     then
      log_error "WARNING: Found $DATADIR/my.cnf
    The 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
     fi
    fi
    export 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_defaults
    then
     print_defaults="./bin/my_print_defaults"
    elif test -x /usr/local/mysql3306/bin/my_print_defaults
    then
     print_defaults="/usr/local/mysql3306/bin/my_print_defaults"
    elif test -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-verbose mysqld server` 
     
    if test $SET_USER -eq 2
    then
     SET_USER=0
    fi
     
    # 又对[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
     fi
    fi
     
    # 给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=syslog
    fi
     
    # 设置--user选项 
    USER_OPTION=""
    if test -w / -o "$USER" = "root" # 根目录是否可写,或者当前用户为root
    then
     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"
     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
     chmod 755 $mysql_unix_port_dir
    fi
     
    # 如果用户没有制定mysqld程序的名称,这里就默认赋值为mysqld
    if test -z "$MYSQLD"
    then
     MYSQLD=mysqld
    fi
     
    # 下面几段分别是对 mysqld , pid , port文件选项的检查和设置,省略100个字
    if test ! -x "$ledir/$MYSQLD"
    then
     log_error "The file $ledir/$MYSQLD
    does not exist or is not executable. Please cd to the mysql installation
    directory 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 1
    fi
     
    if test -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"
     
    if test -n "$mysql_unix_port"
    then
     append_arg_to_args "--socket=$mysql_unix_port"
    fi
    if test -n "$mysql_tcp_port"
    then
     append_arg_to_args "--port=$mysql_tcp_port"
    fi
     
    #
    # 接下来是关于优先级的设置
    #
     
    if test $niceness -eq 0
    then
     NOHUP_NICENESS="nohup"
    else
     NOHUP_NICENESS="nohup nice -$niceness"
    fi
     
    # 将当前的默认优先级设置为0
    if nohup nice > /dev/null 2>&1
    then
      # 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
      fi
    else
      # 下面是测试nohup在当前系统中是否可用,不可用的话就置空NOHUP_NICENESS
      if nohup echo testing > /dev/null 2>&1
      then
        :
      else
        NOHUP_NICENESS=""
      fi
    fi
     
    # 指定内核文件大小
    if test -n "$core_file_size"
    then
     ulimit -c $core_file_size
    fi
     
    #
    # 如果已经存在一个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_file
    Please remove it manually and start $0 again;
    mysqld daemon not started"
      exit 1
     fi
    fi
     
    #
    # 下面便是拼接执行语句运行了。
    #
     
    cmd="$NOHUP_NICENESS"
     
    # 检查一下命令 并进行转义操作
    for i in "$ledir/$MYSQLD" "$defaults" "--basedir=$MY_BASEDIR_VERSION" 
     "--datadir=$DATADIR" "$USER_OPTION"
    do
     cmd="$cmd "`shell_quote_string "$i"`
    done
    cmd="$cmd $args"
    # Avoid 'nohup: ignoring input' warning
    test -n "$NOHUP_NICENESS" && cmd="$cmd < /dev/null"
     
    log_notice "Starting $MYSQLD daemon with databases from $DATADIR"
     
    # 后台循环 执行mysqld
    while true
    do
     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"
  • 相关阅读:
    grep命令、sed 命令 、awk命令、uniq命令
    正则表达式学习(python之re模块)
    linux目录结构
    shell之find命令详解
    shell之 ps、kill、killall命令详解
    linux用户及权限
    C#中数据类型char*,const char*和string的三者转换
    C#中通过SendARP读取MAC地址
    C#对字典Dictionary 的添加,遍历,移除系列操作
    CMD命令行实现复制一张图片1000份:
  • 原文地址:https://www.cnblogs.com/chaosheng/p/4876719.html
Copyright © 2020-2023  润新知