Edited at

RHEL6/RHEL7ごとのanacronの比較をしてみた

More than 3 years have passed since last update.

Linuxの設計にあたり、こんな記事を見つけてしまった。

細かすぎて伝わらない anacron の挙動

内容をかいつまんで説明すると、


CentOS 6 に付属の cronie-anacron-1.4.4 の動きが特徴的だということと、RHELの公式リファレンスに書かれた「最短の遅延時間は、デフォルトで 6 分に」がソースにのってないじゃないか!


という話


環境

今回自分が構築する環境は以下の2つ

OS
rpmバージョン

RHEL6.7
cronie-anacron-1.4.4-15.el6.x86_64

RHEL7.2
cronie-anacron-1.4.11-14.el7.x86_64

それぞれ見て行きたいと思う。


ソースの取得方法


ダウンロード

探すのに、苦労した。

最終的には以下のサイトで取得できた。

今回はソースコードを取得するため、RPMではなくSRPMを取得する。

※SRPM=RPMを作成可能なソースコード xxxx.src.rpm という名前

※anacronのSRPMはcronのものと一緒くたになっているらしい


ソースゲット!

ここを参考にした。

第10回「ソースコード閲覧ノススメ」

実際の手順はこちら

[root@rhel6 ~]# wget http://vault.centos.org/6.7/os/Source/SPackages/cronie-1.4.4-15.el6.src.rpm

[root@rhel6 ~]# yum -y install yum-utils rpm-build
[root@rhel6 ~]# yum -y libselinux-devel pam-devel audit-libs-devel
[root@rhel6 ~]# yum-builddep -y --nogpgcheck cronie-1.4.4-15.el6.src.rpm
[root@rhel6 ~]# rpm -ivh cronie-1.4.4-15.el6.src.rpm
[root@rhel6 ~]# rpmbuild -bp ~/rpmbuild/SPECS/cronie.spec
[root@rhel6 ~]# cd /root/rpmbuild/BUILD/cronie-1.4.4/anacron
[root@rhel6 anacron]# ls
Makefile.am global.h gregor.h log.c main.c.624043 matchrx.c.null-deref readtab.c runjob.c
Makefile.in gregor.c lock.c main.c matchrx.c matchrx.h readtab.c.null-deref runjob.c.commoncriteria


動作の確認


最短の遅延時間は、デフォルトで6分?

以下が正しいか確認する。


RANDOM_DELAY — ジョブごとに指定されている delay in minutes 変数に追加される最大の時間分数。最短の遅延時間は、デフォルトで 6 分に設定されています。

引用元:RedHatEnterpriseLinux6 公式リファレンス



結論

RHEL6/7いずれとも、anacronの遅延時間のアルゴリズムは/etc/anacrontabRANDOM_DELAYと各ジョブのdelay in minutesに準拠しており、最短6分のコードは見つからなかった。


調査


RHEL6のケース

value(RANDOM_DELAY)をunbiased_rand関数に渡してランダム値を生成し、random_numberに格納しています。


readtab.c

        else if (strncmp(env_var, "RANDOM_DELAY", 12) == 0) {

r = match_rx("^([[:digit:]]+)$", value, 0);
if (r == -1) goto reg_err;
if (r == 0) goto reg_invalid;

random_number = (int)unbiased_rand(atoi(value));
Debug(("Randomized delay set: %d", random_number));


ランダム値を生成するunbiased_rand関数を見ても特に6分の記載はありません。


readtab.c

static long int

unbiased_rand(long int max)
{
long int rn;
long int divisor;

divisor = RAND_MAX / (max + 1);

do {
rn = random() / divisor;
} while (rn > max);

return rn;
}


delay(anacrontabのjobで定義されたdelay in minutes)にrandom_numberを追加しています。


readtab.c

    if (period < 0 || delay < 0)

{
complain("%s: number out of range on line %d, skipping",
anacrontab, line_num);
return;
}
jr = obstack_alloc(&tab_o, sizeof(job_rec));
jr->period = period;
jr->named_period = 0;
delay += random_number;


そしてmain.cではdelayを使用してジョブの実行タイミングが取得されるため、+6分されていません。


RHEL7のケース

value(RANDOM_DELAY)とRAND_MAXを使用してランダム値を生成し、random_numberに入れています。RHEL6との違いは関数をローカルにベタ書きしたということと、double型を使用していることぐらいでしょうか。

6分に関する記載はありません。


readtab.c

        if (strncmp(env_var, "RANDOM_DELAY", 12) == 0) {

r = match_rx("^([[:digit:]]+)$", value, 0);
if (r == -1) goto reg_err;
if (r == 0) goto reg_invalid;
if (r != -1) {
int i = random();
double x = 0;
x = (double) i / (double) RAND_MAX * (double) (atoi(value));
random_number = (int)x;
Debug(("Randomized delay set: %d", random_number));
}
}

delayにrandom_numberを追加している処理は、RHEL6とソースが同一のためこちらも6分に関する記載はありません。

つまりRHEL6/7いずれともソースコードを見る限りは最短6分は無いと考えられます。


おまけ


anacron -s について

[root@docker ~]# more /etc/cron.hourly/0anacron

#!/bin/sh
# Check whether 0anacron was run today already
if test -r /var/spool/anacron/cron.daily; then
day=`cat /var/spool/anacron/cron.daily`
fi
if [ `date +%Y%m%d` = "$day" ]; then
exit 0;
fi

# Do not run jobs when on battery power
if test -x /usr/bin/on_ac_power; then
/usr/bin/on_ac_power >/dev/null 2>&1
if test $? -eq 1; then
exit 0
fi
fi
/usr/sbin/anacron -s

anacro -sで呼び出されているため、anacronは「ジョブの実行をシリアル化し、前の処理が終了する前に、 anacronのは、新しいジョブを開始しません。

    for(j = 0; j < njobs; ++j)

{
xsleep(time_till(job_array[j]));
if (serialize) wait_jobs(); ←
launch_job(job_array[j]);
}


RHEL 6と7の挙動の違い


(job起動時間+遅延時間) が現在時刻より1日以上先の場合


OS
挙動

RHEL6
jobのsleepの待機値が (job起動時間+遅延時間) - 現在時刻分

RHEL7
jobのsleepの待機値が0

下記の記載はRHEL6にはありません。


RHEL7のmain.c

    if (now) return 0;

tn = time(NULL);
tj = start_sec + jr->delay * 60;
if (tj < tn) return 0;

#ここからRHEL7にしかない

if (tj - tn > 3600*24)
{
explain("System time manipulation detected, job `%s' will run immediately",
jr->ident);
return 0;
}


(Job起動時間+遅延時間) - 現在時刻 に1日以上(3600秒×24時間)差がある場合には遅延時間なし(returnが0)になっています。

start_secはanacronを起動したときにtime(tnと同じ)が割り当てられますので、ほとんど無いとは思うのですが、start_secにtimeを割り当てた時とtnに割り当てた時で差が大きすぎる場合(NTPがおかしくなった場合、時間が巻き戻った場合)や、遅延時間(delay)が大きすぎて1日以上間が開く場合の対応なんでしょうか。


ファイルロックの違い



しかし、anacron の動作として


  1. /var/spool/anacron/ 以下にあるジョブ名と同じ名前のファイルを読み、その中に書かれている前回実行日付から、ジョブを実行対象とするかどうかを判定する。たとえば週次ジョブなら、 /var/spool/anacron/cron.weekly ファイルに記載の前回実行日から7日経過しているかを判定する。



  2. 所定の日数が経過していて実行対象と判定されたジョブについて、この前回実行日ファイルをロックする。



  3. この後で、前述の START_HOURS_RANGE のチェックを行う。実行対象外と判定されたジョブについても、anacron 自体の完了まで前回実行日ファイルはアンロックされない。


    という順序になっているため、前述のように 2:01 の回の anacron が 3:01 以降まで cron.weekly の起動待ちになっている場合、実際には起動されない cron.daily の前回実行日ファイルも一緒にロックされたままになっている。

    引用:細かすぎて伝わらない anacron の挙動



RHEL6だと上記の通りで、weeklyのせいでdailyのジョブが実行されません。


messages

anacron -s を実行

Jan 20 09:50:19 rhel6 anacron[2627]: Anacron started on 2016-01-20
Jan 20 09:50:19 rhel6 anacron[2627]: Will run job `cron.weekly' in 65 min.
Jan 20 09:50:19 rhel6 anacron[2627]: Jobs will be executed sequentially

anacron -s を実行

Jan 20 09:50:36 rhel6 anacron[2630]: Anacron started on 2016-01-20
Jan 20 09:50:36 rhel6 anacron[2630]: Job `cron.weekly' locked by another anacron - skipping
Jan 20 09:50:36 rhel6 anacron[2630]: Normal exit (0 jobs run)


RHEL7だと、lockの制御が修正されておりweeklyがlockしていてもdailyには影響が出ない作りになっています。


messages

anacron -s を実行

Jan 20 18:42:24 master anacron[374]: Anacron started on 2016-01-20
Jan 20 18:42:24 master anacron[374]: Will run job `cron.daily' in 0 min.
Jan 20 18:42:24 master anacron[374]: Will run job `cron.weekly'
in 65 min.
Jan 20 18:42:24 master anacron[374]: Jobs will be executed sequentially
Jan 20 18:42:24 master anacron[374]: Job `cron.daily' started
Jan 20 18:42:24 master run-parts(/etc/cron.daily)[375]: starting 0yum-daily.cron
Jan 20 18:42:24 master run-parts(/etc/cron.daily)[382]: finished 0yum-daily.cron
Jan 20 18:42:24 master run-parts(/etc/cron.daily)[375]: starting logrotate
Jan 20 18:42:24 master run-parts(/etc/cron.daily)[389]: finished logrotate
Jan 20 18:42:24 master run-parts(/etc/cron.daily)[375]: starting man-db.cron
Jan 20 18:42:26 master run-parts(/etc/cron.daily)[400]: finished man-db.cron
Jan 20 18:42:26 master run-parts(/etc/cron.daily)[375]: starting messages
Jan 20 18:42:26 master run-parts(/etc/cron.daily)[407]: finished messages
Jan 20 18:42:26 master run-parts(/etc/cron.daily)[375]: starting mlocate
Jan 20 18:42:26 master run-parts(/etc/cron.daily)[420]: finished mlocate
Jan 20 18:42:26 master anacron[374]: Job `cron.daily'
terminated
※weeklyは終了していないのでNormal exitは出力されない

anacron -s を実行

Jan 20 18:42:41 master anacron[422]: Anacron started on 2016-01-20
Jan 20 18:42:41 master anacron[422]: Job `cron.weekly' locked by another anacron - skipping
Jan 20 18:42:41 master anacron[422]: Will run job `cron.daily'
in 0 min.
Jan 20 18:42:41 master anacron[422]: Jobs will be executed sequentially
Jan 20 18:42:41 master anacron[422]: Job `cron.daily' started
Jan 20 18:42:41 master run-parts(/etc/cron.daily)[423]: starting 0yum-daily.cron
Jan 20 18:42:41 master run-parts(/etc/cron.daily)[432]: finished 0yum-daily.cron
Jan 20 18:42:41 master run-parts(/etc/cron.daily)[423]: starting logrotate
Jan 20 18:42:41 master run-parts(/etc/cron.daily)[439]: finished logrotate
Jan 20 18:42:41 master run-parts(/etc/cron.daily)[423]: starting man-db.cron
Jan 20 18:42:43 master run-parts(/etc/cron.daily)[460]: finished man-db.cron
Jan 20 18:42:43 master run-parts(/etc/cron.daily)[423]: starting messages
Jan 20 18:42:43 master run-parts(/etc/cron.daily)[468]: finished messages
Jan 20 18:42:43 master run-parts(/etc/cron.daily)[423]: starting mlocate
Jan 20 18:42:43 master run-parts(/etc/cron.daily)[479]: finished mlocate
Jan 20 18:42:43 master anacron[422]: Job `cron.daily'
terminated
Jan 20 18:42:43 master anacron[422]: Normal exit (1 job run)
※weeklyは実行されず、dailyは終了しているのでNormal exitが出力される



PREFERRED_HOUR


RHEL7に追加されてました。/etc/anacrontabのオプションのようです。

manには載っていない模様。検索しても全然でてこない…。


RHEL7のreadtab.c

        if (strncmp(env_var, "PREFERRED_HOUR", 14) == 0) {

r = match_rx("^([[:digit:]]+)$", value, 1, &pref_hour);
if ((r != -1) || (pref_hour != NULL)) {
preferred_hour = atoi(pref_hour);
if ((preferred_hour < 0) || (preferred_hour > 24)) {
preferred_hour = -1;
goto reg_invalid;
}
}
}