mysql 5.7.13 64 mysqld_safe_mysqld_safe启动脚本源码阅读、分析

前几天读了下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表示需要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 <

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"

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值