current_timeカーネルモジュールから見るカーネル内のタイムゾーン

[NetBSD Advent Calendar 2024](https://qiita.com/advent-calendar/2024/netbsd) 5日目の記事です。今日はNetBSD-10で追加されたカーネルモジュールサンプルを紹介しようと思います。

## 新たに追加されたカーネルモジュールサンプル

NetBSD-10の[/usr/src/sys/modules/examples](https://github.com/NetBSD/src/tree/trunk/sys/modules/examples)を見ると、カーネルモジュールの新しいサンプルがいくつか追加されています。ざっと確認すると、以下のサンプルが追加されているようです。これらのサンプルを(分かる範囲で)見てゆこうと思います。

 * current_time
 * ddbping
 * fopsmapper
 * ping_block
 * pollpal

## current_timeモジュール

さっそくカーネルモジュールをビルドして試してみます。 `make` して[modload(8)](https://man.netbsd.org/modload.8)、[modload(8)](https://man.netbsd.org/modunload.8)を実行します。

```sh
# cd /usr/src/sys/modules/examples/current_time/
# make
/usr/src/tooldir.NetBSD-10.0-amd64/bin/x86_64--netbsd-gcc -O2 -g   -std=gnu99    -Wall -Wstrict-prototypes -Wmissing-prototypes -Wpointer-arith -Wno-sign-compare  -Wsystem-headers   -Wno-traditional   -Wa,--fatal-warnings  -Wreturn-type -Wswitch -Wshadow -Wcast-qual -Wwrite-strings -Wextra -Wno-unused-parameter -Wno-sign-compare -Wold-style-definition -Wsign-compare -Wformat=2  -Wno-format-zero-length  -Werror  -Wno-address-of-packed-member  -Wno-pointer-sign -Wno-attributes -Wno-type-limits  -ffreestanding  -fno-strict-aliasing -mno-red-zone -mno-mmx -mno-sse -mno-avx -msoft-float -mcmodel=kernel -fno-omit-frame-pointer   -I/usr/src/common/include --sysroot=/ -I/usr/src/common/include  -nostdinc -I. -I/usr/src/sys/modules/examples/current_time -isystem /usr/src/sys -isystem /usr/src/sys/arch -isystem /usr/src/sys/../common/include -D_KERNEL -D_MODULE -DSYSCTL_INCLUDE_DESCR  -DKDTRACE_HOOKS  -c    current_time.c -o current_time.o.o
/usr/src/tooldir.NetBSD-10.0-amd64/bin/nbctfconvert -L VERSION -o current_time.o current_time.o.o && rm -f current_time.o.o
#      link  current_time/current_time.kmod
/usr/src/tooldir.NetBSD-10.0-amd64/bin/x86_64--netbsd-gcc  --sysroot=/ -Wl,--warn-shared-textrel -Wl,-z,relro -nostdlib -r -Wl,-T,/usr/src/sys/../sys/modules/xldscripts/kmodule,-d  -Wl,-Map=current_time.kmod.map  -o current_time.kmod current_time.o
/usr/src/tooldir.NetBSD-10.0-amd64/bin/nbctfmerge -t -L VERSION -o current_time.kmod current_time.o
#
# cd /usr/src/sys/modules/examples/current_time/
root@nbsd2024test current_time # ls
CVS/                      current_time.c            current_time.o            x86@
Makefile                  current_time.kmod         i386@                     xen@
amd64@                    current_time.kmod.map     machine@
#
# modload ./current_time.kmod
# modunload current_time
```

コンソールには以下の表示となり、現在時刻がフォーマットされて出力されています。

![img01.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61137/c266250d-06ff-72a2-ffda-b8a6ef9892ba.png)

ソースコードは[/usr/src/sys/modules/examples/current_time/current_time.c](https://github.com/NetBSD/src/blob/trunk/sys/modules/examples/current_time/current_time.c)にあり、 `modload` でカーネルモジュールがロードされた際に `print_current_time()` が呼ばれています。

```c
 66 static int
 67 current_time_modcmd(modcmd_t cmd, void *arg __unused)
 68 {
 69     switch (cmd) {
 70     case MODULE_CMD_INIT:
 71         printf("current_time example module loaded.\n");
 72         print_current_time();
 73         break;
 74     case MODULE_CMD_FINI:
 75         printf("current_time example module unloaded.\n");
 76         break;
 77     default:
 78         return ENOTTY;
 79     }
 80
 81     return 0;
 82 }
```

`print_current_time()` の実装を見てみます。 `getmicrotime()` でUNIXエポック秒を取得し、 `clock_secs_to_ymdhms()` で年・月・日といった値に変換するようです。

```c
 45 static void
 46 print_current_time(void)
 47 {
 48     struct timeval tv;
 49     struct clock_ymdhms dt;
 50     const char *w_day[] = {
 51         "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday",
 52         "Friday", "Saturday"
 53     };
 54
 55     getmicrotime(&tv);
 56     clock_secs_to_ymdhms(tv.tv_sec, &dt);
 57
 58     printf("Current Time: %s, %04" PRIu64 "/%02" PRIu8 "/%02" PRIu8
 59         " %02" PRIu8 ":%02" PRIu8 ":%02" PRIu8 " UTC\n",
 60         w_day[dt.dt_wday], dt.dt_year, dt.dt_mon, dt.dt_day, dt.dt_hour,
 61         dt.dt_min, dt.dt_sec);
 62 }
```

関数のコメントを見てみます。"the kernel doesn't have the notion of local timezones."(カーネルはローカルタイムゾーンの概念を持っていない)とあり、改めて先ほどの実行結果を見るとたしかに `GMT/UTC` な時刻になっています。

```c
 36 /*
 37  * Function print_time() fetches the epoch seconds/unix time from the
 38  * getmicrotime() function and sends its to clock_secs_to_ymdhms(..) in
 39  * dev/clock_subr to parse it into readable date and time format and print it.
 40  *
 41  * Please note that the current time is printed in GMT/UTC because the kernel
 42  * doesn't have the notion of local timezones.
 43  */

訳：
print_time()はgetmicrotime()から取得したUNIXエポック秒を取得し、/usr/src/sys/dev/clock_subr.cに
あるclock_secs_to_ymdhms()でパースし、フォーマットされたものを表示します。
カーネルにはローカルタイムゾーンの概念が無いため、表示される時間はGMT/UTCであることに注意してください。
```

ユーザランド側のタイムゾーンが `JST` であっても、カーネル内では `GMT/UTC` として処理されているようです。

![img02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61137/e34db445-be1d-a82a-cf8c-13c1c6ca17a4.png)

## まとめ

現在時刻を取得するカーネルモジュールサンプルを見てみました。個人的にカーネル内ではローカルタイムゾーンを持たないのは知らなかったので知見が増えました。カーネル内部では `GMT/UTC` であっても、ユーザランド側でローカルタイムゾーンに変更すればよいので特に問題はない(逆にカーネル側でタイムゾーンを意識すると複雑になる)という話なのかもしれません。そう考えると、 `ja_JP.UTF-8` といったロケールなどの概念もカーネル側には存在しないのかもしれません(調べてないので真偽不明…)。