tasksetコマンドと言うのがLinux(Fedora 13)にはあるのだが、
その中でCPU数(NR_CPUS)を求めるコード(ここではF13でのコード)が面白かったのでメモ。
アプリからNR_CPUSを求めるときの参考になります。はい。
まず、CPU数を求める関数は次のmax_number_of_cpus()。
static int max_number_of_cpus(void) { int n; int cpus = 2048; for (;;) { unsigned long buffer[longsperbits(cpus)]; memset(buffer, 0, sizeof(buffer)); /* the library version does not return size of cpumask_t */ n = syscall(SYS_sched_getaffinity, 0, bytesperbits(cpus), &buffer); if (n < 0 && errno == EINVAL && cpus < 1024*1024) { cpus *= 2; continue; } return n*8; } fprintf (stderr, "cannot determine NR_CPUS; aborting"); exit(1); return 0; }
で、肝はここ。
/* the library version does not return size of cpumask_t */ n = syscall(SYS_sched_getaffinity, 0, bytesperbits(cpus), &buffer); <snip> return n*8;
sched_getaffinity()システムコールをsyscall()を使って直接読んでいる。
これは、カーネルのシステムコールハンドラはNR_CPUSのバイト数を返すけど*1、(コメントにもあるように)libraryを使ってシステムコールを呼び出すとライブラリ内部で
返り値を変更して、成功時には0を返すようにしており、NR_CPUSの大きさを返さないからというのが理由。
次にカーネルの中でやっていることを確認してみる(2.6.35-rc6)。
カーネル内のシステムコールハンドラは下記の関数*2。
SYSCALL_DEFINE3(sched_getaffinity, pid_t, pid, unsigned int, len, unsigned long __user *, user_mask_ptr) { <snip> ret = sched_getaffinity(pid, mask); if (ret == 0) { size_t retlen = min_t(size_t, len, cpumask_size()); if (copy_to_user(user_mask_ptr, mask, retlen)) ret = -EFAULT; else ret = retlen; } <snip> return ret; }
ということで、retlenが返り値。
retlenに何が入っているかというのは、ここ。
size_t retlen = min_t(size_t, len, cpumask_size());
なので、retlenはlenとcpumask_size()の小さい方になりますと。
lenはユーザが指定するcpusetの長さ(システムコールに第2引数)。
cpumask_size()はなんでしょということで見てみると
static inline size_t cpumask_size(void) { /* FIXME: Once all cpumask assignments are eliminated, this * can be nr_cpumask_bits */ return BITS_TO_LONGS(NR_CPUS) * sizeof(long); }
NR_CPUSのバイト数と思われ。
max_number_of_cpus()では、sched_getaffinity()システムコールが成功するまで
cpusetsizeを大きくして、何度もリトライするので、結果としてNR_CPUの値が求められますということです。
はい。
まちがいがあったら誰か指摘してほしい。