DragonFly BSD 4.0.1がリリースされたという記事を呼んでリリースノートを見ていたら「New device files /dev/upmap and /dev/kpmap have been added」なんてのがあって、興味が出たのでソースを読んでみました。
この機能はざっくり言ってしまうとLinuxにおけるVDSOみたいなもので、一部のシステムコールに関しては一定回数以上呼ばれたらシステムコールを呼ぶのではなくメモリにマップしたデータを読み書きして返すというものです。このために2個のデバイスファイルが追加されました。
* /dev/upmap
* /dev/kpmap
kpmapはカーネル単位(なので全プロセスに共通)のデータを読むためのもので、Read Onlyとなっています。 upmapはプロセス単位でこちらはR/Wとなります。
ここではkpmapの方を見ていきます。
まずは実際に使っているところから見ていきます。見る関数は__clock_gettime(clock_gettime(2)の実装)でファイルはlib/libc/upmap/ukp_clock.cでlibcの関数です。
最初のif文でfast_clockのチェックをしていますが、これは初期値0の変数です。条件によってい__kpmap_map()内で1が設定されます。次のチェックはfast_countが10>=かの部分でこの変数も初期値は0になります。 このfast_countが10になるとシステムコールの呼び出してはなくて/dev/kmapからデータを読んで返すようになります。
static int fast_clock; static int fast_count; static int *upticksp; static struct timespec *ts_uptime; static struct timespec *ts_realtime; int __clock_gettime(clockid_t clock_id, struct timespec *ts) { int res; int w; if (fast_clock == 0 && fast_count++ >= 10) { __kpmap_map(&upticksp, &fast_clock, KPTYPE_UPTICKS); __kpmap_map(&ts_uptime, &fast_clock, KPTYPE_TS_UPTIME); __kpmap_map(&ts_realtime, &fast_clock, KPTYPE_TS_REALTIME); __kpmap_map(NULL, &fast_clock, 0); }
次はfast_clock変数が0以上の場合で、これは/dev/kmapからデータを読める場合の処理です。switch文のcaseを見るとclock_idの値次第で直接値を読みだして返すかシステムコールを呼ぶかが変わります。fast_clockが0の場合もシステムコールを呼びます。
if (fast_clock > 0) { switch(clock_id) { case CLOCK_UPTIME_FAST: case CLOCK_MONOTONIC_FAST: do { w = *upticksp; cpu_lfence(); *ts = ts_uptime[w & 1]; cpu_lfence(); w = *upticksp - w; } while (w > 1); res = 0; break; case CLOCK_REALTIME_FAST: case CLOCK_SECOND: do { w = *upticksp; cpu_lfence(); *ts = ts_realtime[w & 1]; cpu_lfence(); w = *upticksp - w; } while (w > 1); if (clock_id == CLOCK_SECOND) ts->tv_nsec = 0; res = 0; break; default: res = __sys_clock_gettime(clock_id, ts); break; } } else { res = __sys_clock_gettime(clock_id, ts); } return res; }
では次に__kpmap_map()を見てみます。呼び出しはこのような感じですね。
static int *upticksp; static struct timespec *ts_uptime; static struct timespec *ts_realtime; int __clock_gettime(clockid_t clock_id, struct timespec *ts) { 〜略〜 if (fast_clock == 0 && fast_count++ >= 10) { __kpmap_map(&upticksp, &fast_clock, KPTYPE_UPTICKS); __kpmap_map(&ts_uptime, &fast_clock, KPTYPE_TS_UPTIME); __kpmap_map(&ts_realtime, &fast_clock, KPTYPE_TS_REALTIME); __kpmap_map(NULL, &fast_clock, 0); }
実装はこのようになってます。ファイルはlib/libc/upmap/upmap.cです。 最初はスレッドかどうかの確認で、スレッドだったらロックをとっています。
void __kpmap_map(void *datap, int *state, uint16_t type) { ukpheader_t *head; if (__isthreaded) _pthread_mutex_lock(&ukpmap_lock);
kpmap_okは初期値0の変数です。ということで、if文の中に入ります。そして、最初にまたkpmap_okのチェックがあります。
if (kpmap_ok <= 0) { int fd; if (kpmap_ok < 0) goto failed;
特に問題がなければデバイスファイル(/dev/kpmap)を開いてmmap()してファイルを閉じるという普通の処理です。
fd = _open("/dev/kpmap", O_RDONLY); if (fd < 0) { kpmap_ok = -1; goto failed; } kpmap_headers = mmap(NULL, KPMAP_MAPSIZE, PROT_READ, MAP_SHARED, fd, 0); _close(fd); if ((void *)kpmap_headers == MAP_FAILED) { kpmap_ok = -1; goto failed; } kpmap_ok = 1; }
kpmap_headersはファイルの先頭の方で下記のように宣言されています。
static ukpheader_t *kpmap_headers;
ukpheader_tはsys/sys/upmap.hでこのように定義されています。
typedef struct ukpheader { uint16_t type; /* element type */ uint16_t offset; /* offset from map base, max 65535 */ } ukpheader_t;
次のチェックですが、typeはKPTYPE_UPTICKSとかですが、clock_gettime()ではkpmap_map()の一連の呼び出しの最後にkpmap_map(NULL, &fast_clock, 0);としてkpmap_map()を呼んでいました。この3番目の引数が0だった場合の処理がここになります。そして、stateが0の場合(lib/libc/upmap/ukp_clock.cでfast_clockが0だった場合)に1が設定されます。typeが0だった場合は読みだすデータは特にないのでここで終了になります。
/* * Special case to finalize state */ if (type == 0) { if (*state == 0) *state = 1; if (__isthreaded) _pthread_mutex_unlock(&ukpmap_lock); return; }
ここからはtypeが0以外で何かしら意味のあるデータを取得する場合の処理です。kpmap_headersは先ほどのmmap()の戻り値です。ここからデータを辿って行って、ヘッダの内容が欲しい情報のヘッダだった場合に値が設定されてreturnします。何も見つからなければfailedのラベル以下の行が実行されて終了です。
/* * Look for type. */ for (head = kpmap_headers; head->type; ++head) { if (head->type == type) { *(void **)datap = (char *)kpmap_headers + head->offset; if (__isthreaded) _pthread_mutex_unlock(&ukpmap_lock); return; } } failed: *state = -1; if (__isthreaded) _pthread_mutex_unlock(&ukpmap_lock); }
ここまでが使っているところで、次は初期化部分を見てみます。こちらはlibcではなくてkernelでsys/kern/init_main.cに初期化処理があります。これはまあ見たとおりですね。
static void kpmap_init(const void *udata __unused) { kpmap = kmalloc(roundup2(sizeof(*kpmap), PAGE_SIZE), M_TEMP, M_ZERO | M_WAITOK); kpmap->header[0].type = UKPTYPE_VERSION; kpmap->header[0].offset = offsetof(struct sys_kpmap, version); kpmap->header[1].type = KPTYPE_UPTICKS; kpmap->header[1].offset = offsetof(struct sys_kpmap, upticks); kpmap->header[2].type = KPTYPE_TS_UPTIME; kpmap->header[2].offset = offsetof(struct sys_kpmap, ts_uptime); kpmap->header[3].type = KPTYPE_TS_REALTIME; kpmap->header[3].offset = offsetof(struct sys_kpmap, ts_realtime); kpmap->header[4].type = KPTYPE_TSC_FREQ; kpmap->header[4].offset = offsetof(struct sys_kpmap, tsc_freq); kpmap->header[5].type = KPTYPE_TICK_FREQ; kpmap->header[5].offset = offsetof(struct sys_kpmap, tick_freq); kpmap->version = KPMAP_VERSION; } SYSINIT(kpmapinit, SI_BOOT1_POST, SI_ORDER_FIRST, kpmap_init, NULL)
kpmapはグローバル変数で型はstruct sys_kpmapです。
struct sys_kpmap { ukpheader_t header[64]; int32_t version; int32_t upticks; /* userland reads ts_*[upticks & 1] */ struct timespec ts_uptime[2]; /* mono uptime @ticks (uncompensated) */ struct timespec ts_realtime[2]; /* realtime @ticks resolution */ int64_t tsc_freq; /* (if supported by cpu) */ int32_t tick_freq; /* scheduler tick frequency */ };
kpmap_init()でheaderのtypeとoffsetに設定していた内容が__kpmap_map()の↓で見たところですね。
for (head = kpmap_headers; head->type; ++head) { if (head->type == type) { *(void **)datap = (char *)kpmap_headers + head->offset; if (__isthreaded) _pthread_mutex_unlock(&ukpmap_lock); return; } }
さて、kpmap_init()では構造体に値を設定しただけなので/dev/kpmapにデータを設定する必要があります。kpmap、upmapに限らずmemとかnullみたいなファイルを作っているのはmem_drvinit()です。
static void mem_drvinit(void *unused) { /* Initialise memory range handling */ if (mem_range_softc.mr_op != NULL) mem_range_softc.mr_op->init(&mem_range_softc); make_dev(&mem_ops, 0, UID_ROOT, GID_KMEM, 0640, "mem"); make_dev(&mem_ops, 1, UID_ROOT, GID_KMEM, 0640, "kmem"); make_dev(&mem_ops, 2, UID_ROOT, GID_WHEEL, 0666, "null"); make_dev(&mem_ops, 3, UID_ROOT, GID_WHEEL, 0644, "random"); make_dev(&mem_ops, 4, UID_ROOT, GID_WHEEL, 0644, "urandom"); make_dev(&mem_ops, 5, UID_ROOT, GID_WHEEL, 0666, "upmap"); make_dev(&mem_ops, 6, UID_ROOT, GID_WHEEL, 0444, "kpmap"); zerodev = make_dev(&mem_ops, 12, UID_ROOT, GID_WHEEL, 0666, "zero"); make_dev(&mem_ops, 14, UID_ROOT, GID_WHEEL, 0600, "io"); } SYSINIT(memdev,SI_SUB_DRIVERS,SI_ORDER_MIDDLE+CDEV_MAJOR,mem_drvinit,NULL)
このファイルの中でkpmap/upmapのデータを見ていそうなのはmemuksmap()でした。えーと、memory user kernel space mappingとかそんな意味かな?
case 5: case 6: /* * minor device 5 is /dev/upmap (see sys/upmap.h) * minor device 6 is /dev/kpmap (see sys/upmap.h) */ result = 0; error = user_kernel_mapping(minor(dev), ptoa(fake->pindex), &result); fake->phys_addr = result; break;
で、user_kernel_mapping()ですが、実際にデータ(kpmap)を扱っているのはこの辺です。pmap_kextract()は仮想アドレスから物理アドレスに変換する関数みたいです。
switch(num) { case 5: /* * /dev/upmap - maps RW per-process shared user-kernel area. */ if (p->p_upmap == NULL) proc_usermap(p, invfork); else if (invfork) p->p_upmap->invfork = invfork; if (p->p_upmap && offset < roundup2(sizeof(*p->p_upmap), PAGE_SIZE)) { /* only good for current process */ *resultp = pmap_kextract((vm_offset_t)p->p_upmap + offset); error = 0; } break; case 6: /* * /dev/kpmap - maps RO shared kernel global page */ if (kpmap && offset < roundup2(sizeof(*kpmap), PAGE_SIZE)) { *resultp = pmap_kextract((vm_offset_t)kpmap + offset); error = 0; } break;
/dev/kpmap辺りの初期化はだいたいこれくらいで、あとはシステムコール固有の処理があります。と言いつつシステムコール固有じゃないけど初期化処理としてはkpmap構造体のtsc_freq、tick_freqに値を設定するinitclocks()があります。ファイルはsys/kern/kern_clock.cです。
static void initclocks(void *dummy) { /*psratio = profhz / stathz;*/ initclocks_pcpu(); clocks_running = 1; if (kpmap) { kpmap->tsc_freq = (uint64_t)tsc_frequency; kpmap->tick_freq = hz; } }
このファイルでは他にhardclock()がkpmap構造体にデータを設定します。
/* * Update kpmap on each tick. TS updates are integrated with * fences and upticks allowing userland to read the data * deterministically. */ if (kpmap) { int w; w = (kpmap->upticks + 1) & 1; getnanouptime(&kpmap->ts_uptime[w]); getnanotime(&kpmap->ts_realtime[w]); cpu_sfence(); ++kpmap->upticks; cpu_sfence(); } }
ここで__clock_gettime()の処理を振り返ってみます。clock_idがCLOCK_REALTIME_FASTの場合、ts_realtimeを呼んでますね。ts_realtimeに値を設定しているのが↑ですね。このhardclock()はタイマー割り込みのタイミングで呼ばれるようです。
do { w = *upticksp; cpu_lfence(); *ts = ts_realtime[w & 1]; cpu_lfence(); w = *upticksp - w; } while (w > 1);
ということでDragonFly BSDの/dev/kpmapの処理を読んでみました。
これ一冊で完全理解 Linuxカーネル超入門 (日経BPパソコンベストムック)
- 作者: 日経Linux
- 出版社/メーカー: 日経BP社
- 発売日: 2014/10/16
- メディア: 単行本
- この商品を含むブログ (1件) を見る