Help us understand the problem. What is going on with this article?

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;
                }
            }
        }
Gin
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away