🐧

Linuxでsleep(1)に大きな値を指定すると24日ずつnanosleep(3)する

2021/11/14に公開

GNU CoreUtils の sleep は infinity が指定できる を読んで、その後の処理が気になったのでLinux環境での動作を調べてみた。

ソース読解

https://github.com/coreutils/coreutils/blob/2378a531432d21c574830f0e24f7f46fe7daceca/src/sleep.c#L142
GNU CoreUtils の sleep は infinity が指定できるで説明されているように、xstrtod() が HUGE_VAL を返すので、xnanosleep() にはdoubleの値HUGE_VALが渡される

double seconds = 0.0;
    ...
    if (! (xstrtod (argv[i], &p, &s, cl_strtod) || errno == ERANGE)
    ...
    seconds += s;
    ...
if (xnanosleep (seconds))

https://github.com/coreutils/gnulib/blob/dd0af10fa597a95ffe5f4f110ef5edefc2f680bc/lib/xnanosleep.c#L53

xnanosleep()はdtotimespec()でdoubleをstruct timespecに変換して、nanosleep() に渡す

struct timespec ts_sleep = dtotimespec (seconds);
    ...
    if (nanosleep (&ts_sleep, &ts_sleep) == 0)

https://github.com/coreutils/gnulib/blob/dd0af10fa597a95ffe5f4f110ef5edefc2f680bc/lib/dtotimespec.c#L35
dtotimespec()はとても大きな値に対してtimespecの最大値を返す

return make_timespec (TYPE_MAXIMUM (time_t), TIMESPEC_HZ - 1);

動作確認

nsnosleep(2)システムコールに実際に渡されている値を確認するため、strace でシステムコールの引数を見てみると

$ strace sleep infinity
execve("/bin/sleep", ["sleep", "infinity"], [/* 30 vars */]) = 0
...
nanosleep({tv_sec=2073600, tv_nsec=999999999},

tv_nsecの値は予想通りだが、tv_secの値2073600はあまりにも小さく、予想と違う。

読み直し

https://github.com/coreutils/gnulib/blob/dd0af10fa597a95ffe5f4f110ef5edefc2f680bc/lib/nanosleep.c#L46
でnanosleepを置き換えていて、24日(24 * 24 * 60 * 60 = 2073600秒)ずつ分けて nsnosleep を呼び出していた。tv_nsecは最初の呼び出しで吸収される。

24日ずつに分けている理由は、コード中のコメントに書かれている。nanosleepで長時間待てない環境のための対処らしい。

nanosleep mishandles large sleeps due to internal overflow problems.
The worst known case of this is Linux 2.6.9 with glibc 2.3.4, which
can't sleep more than 24.85 days (2^31 milliseconds).  Similarly,
cygwin 1.5.x, which can't sleep more than 49.7 days (2^32 milliseconds).
Solve this by breaking the sleep up into smaller chunks.

まとめ

というわけで、Linuxでsleep(1)に大きな値を指定すると24日ずつnanosleep(3)することがわかった。

strace の出力は24日後に以下のようになっているはず。(後で確認する)

nanosleep({tv_sec=2073600, tv_nsec=999999999}, NULL) = 0
nanosleep({tv_sec=2073600, tv_nsec=0},

(2021.12.09 追記) screen 上で strace sleep infinity して放置していたもの、期待通りの結果になっていました:

Discussion