/var/lock/subsys/ ってなに?

RedHat のプロセスの停止処理がおかしいというので init スクリプト周りを眺めていたら、どういうわけか /var/lock/subsys/ ディレクトリ以下を操作していた。
/var/lock/subsys/ 以下のファイルを何に使っているのかわからなかったので、ちょっと調べてみた。

結果的に言えば↓の RedHat Magazine の記事にかいてあることを確認しただけ。

RedHat Magazines Issue #8 June 2005
Why do init scripts require lock files?
by Bradford Hinson
http://www.redhat.com/magazine/008jun05/departments/tips_tricks/

/var/lock/subsys/ が作成されるタイミング

CentOS(RedHat) の /etc/init.d/crond を例に取るとだいたい以下のようになっており、プロセスの起動が成功すると /var/lock/subsys/crond を作成し、プロセスの終了が成功すると /var/lock/subsys/crond を削除し、一種のロックファイルとして存在している。

# excerpt from /etc/init.d/crond
start() {
    echo -n $"Starting $prog: "
        if [ -e /var/lock/subsys/crond ]; then
        if [ -e /var/run/crond.pid ] && [ -e /proc/`cat /var/run/crond.pid` ]; then
        echo -n $"cannot start crond: crond is already running.";
        failure $"cannot start crond: crond already running.";
        echo
        return 1
        fi
    fi
    daemon crond $CRONDARGS
    RETVAL=$?
    echo
    [ $RETVAL -eq 0 ] && touch /var/lock/subsys/crond;
    return $RETVAL
}

stop() {
    echo -n $"Stopping $prog: "
        if [ ! -e /var/lock/subsys/crond ]; then
        echo -n $"cannot stop crond: crond is not running."
        failure $"cannot stop crond: crond is not running."
        echo
        return 1;
    fi
    killproc crond
    RETVAL=$?
    echo
        [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/crond;
    return $RETVAL
}

rhstatus() {
    status crond
}

...

case "$1" in
  start)
    start
    ;;
  stop)
    stop
    ;;
  ...
  status)
    rhstatus
    ;;
  condrestart)
    [ -f /var/lock/subsys/crond ] && restart || :
    ;;
  *)
    echo $"Usage: $0 {start|stop|status|reload|restart|condrestart}"
    exit 1
esac

/var/lock/subsys/ の用途

気になるのは作成された /var/lock/subsys/ 以下のファイルがいつ何のために使われるのか?

ざっと、以下の様な用途で使われている

  1. プログラムのステータスチェック
  2. ランレベル変更時のプログラム再起動
  3. サーバ再起動/シャットダウン時のプログラム終了

順に確認

#1:プログラムのステータスチェック

$ sudo /sbin/service initスクリプト名 status

で、プログラムのステータスを確認できる。

この時、以下の順でプログラムのステータスを確認している。

1 プログラム名から PID を取得し、成功すれば running 扱い

$ sudo /sbin/service crond status
crond (pid 11672) is running...

PID の取得には

pid=`pidof -o $$ -o $PPID -o %PPID -x $1 || \
     pidof -o $$ -o $PPID -o %PPID -x ${base}`

というようなことをやっている

2 pid ファイルが存在すれば、異常終了扱い

$ sudo /sbin/service crond status
crond dead but pid file exists

3 subsys ファイルが存在すれば、異常終了扱い

$ sudo /sbin/service crond status
crond dead but subsys locked

4 #1-#3 いずれも見たなされけば、停止状態

$ sudo /etc/init.d/crond status
crond is stopped

#3 で subsys 以下のファイルを利用している。

#2:ランレベル変更時のプログラム再起動

ランレベルを変更した時、新しいランレベルに合わせてプログラムを停止/起動する。この動処理は "/etc/rc.d/rc" で行なっている。

起動されるのは /etc/rc{変更後のランレベル}/S で始まるファイルのうち、subsys ディレクトリにファイルが存在しないプログラム。停止されるのは /etc/rc{変更後のランレベル}/K で始まるファイルのうち、 subsys ディレクトリにファイルが存在するプログラム。

実際にコードを見てみると

# excerpt from /etc/rc.d/rc
...
# First, run the KILL scripts.
for i in /etc/rc$runlevel.d/K* ; do
    check_runlevel "$i" || continue

    # Check if the subsystem is already up.
    subsys=${i#/etc/rc$runlevel.d/K??}
    [ -f /var/lock/subsys/$subsys -o -f /var/lock/subsys/$subsys.init ] \
        || continue

    # Bring the subsystem down.
    if egrep -q "(killproc |action )" $i ; then
        $i stop
    else
        action $"Stopping $subsys: " $i stop
    fi
done

# Now run the START scripts.
for i in /etc/rc$runlevel.d/S* ; do
    check_runlevel "$i" || continue

    # Check if the subsystem is already up.
    subsys=${i#/etc/rc$runlevel.d/S??}
    [ -f /var/lock/subsys/$subsys -o -f /var/lock/subsys/$subsys.init ] \
        && continue

    # If we're in confirmation mode, get user confirmation
    if [ -f /var/run/confirm ]; then
        confirm $subsys
        test $? = 1 && continue
    fi

    update_boot_stage "$subsys"
    # Bring the subsystem up.
    if [ "$subsys" = "halt" -o "$subsys" = "reboot" ]; then
        export LC_ALL=C
        exec $i start
    fi
    if egrep -q "(daemon |action |success |failure )" $i 2>/dev/null \
            || [ "$subsys" = "single" -o "$subsys" = "local" ]; then
        $i start
    else
        action $"Starting $subsys: " $i start
    fi
done
...

■実際の動作を確認

・kill/start対象のプログラムを確認
ランレベル5で kill/start 対象のプログラムを確認

$ ls /etc/rc.d/rc5.d/ | egrep '(cron|ntp)'
K74ntpd
S90crond
S95anacron

ntpd は kill 対象、crond は start 対象。
ランレベルを 5 に変更した時、ntpd に subsys ファイルがあれば kill され、subsys ファイルがなければ crond が start されるはず。

・プログラムの起動を確認

$ sudo /etc/init.d/ntpd status
ntpd (pid 26633) is running...
$ sudo /etc/init.d/crond status
crond is stopped

・subsys ファイルを操作
subsys ファイルを操作して /etc/rc の管理対象外にする

$ sudo rm /var/lock/subsys/ntpd
$ sudo touch /var/lock/subsys/crond

・ランレベルを変更

$ sudo /sbin/init 5
$ who -r # /sbin/runlevel でも OK
         run-level 5  May 29 22:26                   last=3

ランレベルが3から5に変わっている。

・プロセスを確認

$ ps aux | egrep '(cron|ntp)'
ntp      26633  0.0  0.2  5764 5760 ?        SLs  13:42   0:00 ntpd -u ntp:ntp -p /var/run/ntpd.pid -g
abcd     26935  0.0  0.0  4740  636 pts/2    R+   13:45   0:00 egrep (cron|ntp)

ntpd は起動したままで、crond も起動していない。

・subsys ファイルを操作
subsys ファイルを再度操作して、 /etc/rc の管理対象にする

$ sudo touch /var/lock/subsys/ntpd
$ sudo unlink /var/lock/subsys/crond

・ランレベルを変更

$ sudo /sbin/init 3

ランレベルが5から3に変更。

・プロセスを確認

$ ps aux | egrep '(cron|ntp)'
root     27431  0.0  0.0  5660 1036 ?        Ss   13:48   0:00 crond
abcd     27951  0.0  0.0  4212  636 pts/2    R+   13:49   0:00 egrep (cron|ntp)

$ tail /var/log/messages
May 29 22:52:20 hogehoge init: Switching to runlevel: 5
...
May 29 22:52:20 hogehoge ntpd[26633  ]: ntpd exiting on signal 15
May 29 22:52:20 hogehoge ntpd: ntpd shutdown succeeded
...
May 29 22:52:20 hogehoge crond: crond startup succeeded

ntpd は終了し、crond は起動している

#3:サーバ再起動・シャットダウン時のプログラム終了

シャットダウンがかかると、次の順で成功するまでエスカレートしてプログラムを終了させる(参照)

  1. 各サービスに対して service stop を実行
  2. kill -SIGTERM を実行
  3. 5秒まつ
  4. kill -SIGKILL を実行

#1 の箇所では killall が呼ばれる。このプログラムは /var/lock/subsys 以下にあるファイルをチェックし、対応する init スクリプトの stop を呼び出す(プログラムの先頭コメントにあるように、各 daemon プログラムを終了させる sanity check として実行)

# excerpt from /etc/init.d/killall
#! /bin/bash

# Bring down all unneeded services that are still running (there shouldn't
# be any, so this is just a sanity check)

case "$1" in
   *start)
    ;;
   *)
    echo $"Usage: $0 {start}"
    exit 1
    ;;
esac

for i in /var/lock/subsys/* ; do
    # Check if the script is there.
    [ -f "$i" ] || continue

    # Get the subsystem name.
    subsys=${i#/var/lock/subsys/}

    # Networking could be needed for NFS root.
    [ $subsys = network ] && continue

    # Bring the subsystem down.
    if [ -f /etc/init.d/$subsys.init ]; then
        /etc/init.d/$subsys.init stop
    elif [ -f /etc/init.d/$subsys ]; then
        /etc/init.d/$subsys stop
    else
        rm -f "$i"
    fi
done

References

  1. RedHat Magazines Issue #8 June 2005 Why do init scripts require lock files?
    http://www.redhat.com/magazine/008jun05/departments/tips_tricks/
  2. DeveloperWorks : Learn Linux, 101: Runlevels, shutdown, and reboot  “Clean Shutdown”
    http://www.ibm.com/developerworks/library/l-lpic1-v3-101-3/

Leave a comment