アプリでCPU数(NR_CPUS)を取得する方法

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の値が求められますということです。
はい。


まちがいがあったら誰か指摘してほしい。

*1:正確にはちょっとちがうけど

*2:syscall tracerの影響でcscopeで関数名で検索してもでてこなくなってしまってるのはどうにかならないものか