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/anacrontab**のRANDOM_DELAY
と各ジョブのdelay in minutes
に準拠しており、最短6分のコードは見つからなかった。
調査
RHEL6のケース
value(RANDOM_DELAY)をunbiased_rand関数に渡してランダム値を生成し、random_numberに格納しています。
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分の記載はありません。
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を追加しています。
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分に関する記載はありません。
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にはありません。
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 の動作として
- /var/spool/anacron/ 以下にあるジョブ名と同じ名前のファイルを読み、その中に書かれている前回実行日付から、ジョブを実行対象とするかどうかを判定する。たとえば週次ジョブなら、 /var/spool/anacron/cron.weekly ファイルに記載の前回実行日から7日経過しているかを判定する。
- 所定の日数が経過していて実行対象と判定されたジョブについて、この前回実行日ファイルをロックする。
3. この後で、前述の START_HOURS_RANGE のチェックを行う。実行対象外と判定されたジョブについても、anacron 自体の完了まで前回実行日ファイルはアンロックされない。
という順序になっているため、前述のように 2:01 の回の anacron が 3:01 以降まで cron.weekly の起動待ちになっている場合、実際には起動されない cron.daily の前回実行日ファイルも一緒にロックされたままになっている。 引用:[細かすぎて伝わらない anacron の挙動](http://qiita.com/ohtsuka1317/items/b89ea66f3d227c172477)
RHEL6だと上記の通りで、weekly
のせいでdaily
のジョブが実行されません。
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
には影響が出ない作りになっています。
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には載っていない模様。検索しても全然でてこない…。
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;
}
}
}