φ(..)メモメモ ncproc(1)はどうやってcpu数を数えているのか

Linux環境においてcpu数(core数)を数えたいときにgrep processor /proc/cpuinfo | wc -lとやっても良いんですけど、もうちょっとスマートなやり方は無いかな?と思ってたらnproc(1)があったのでそれがどうやって数を調べているのかソースを読んでみました。

その前にnprocコマンドの説明を。nprocコマンドは基本的に引数無しで実行すればOKです。

masami@saga:~$ nproc
8

さて、まずは読むべきソースがどのパッケージにあるのかを調べないといけないので以下を実行して、パッケージを調べる。

masami@saga:~$ yaourt -Qo `which nproc`
/usr/bin/nproc is owned by coreutils 8.23-1

coreutilsはgitで管理されていて、webからも見れるのでそれを使います。あと、coreutilsの他にgnulibのコードも読む必要があってこちらもgitwebで見て行きましょう。

nproc(1)のソースはsrc/nproc.cにあります。引数無しで実行した場合は87行目、123行目だけ覚えてば良いという手軽さ。num_processors()の返り値がcpu数です。

  75 int
  76 main (int argc, char **argv)
  77 {
〜略〜
  87   enum nproc_query mode = NPROC_CURRENT_OVERRIDABLE;
〜略〜
 123   nproc = num_processors (mode);

ということで、cpu数を数えるnum_processors()ですがこれはcoreutilsにはなくてgnulibにあります。なのでここからはgnulibのコードを見て行きましょう。
num_processors()はlib/nproc.cにあります。

まずは引数で渡ってきたmode(query)のチェックで、引数無しでnprocを実行した場合はif文の条件が真になるので中に入りますが、通常はOMP_NUM_THREADSという環境変数は設定されていないと思いますので、ここは単にqueryの値をNPROC_CURRENTに置き換えるだけになります。

 199 unsigned long int
 200 num_processors (enum nproc_query query)
 201 {
 202   if (query == NPROC_CURRENT_OVERRIDABLE)
 203     {
 204       /* Test the environment variable OMP_NUM_THREADS, recognized also by all
 205          programs that are based on OpenMP.  The OpenMP spec says that the
 206          value assigned to the environment variable "may have leading and
 207          trailing white space". */
 208       const char *envvalue = getenv ("OMP_NUM_THREADS");
 209 
 210       if (envvalue != NULL)
 211         {
〜略〜
 228         }
 229 
 230       query = NPROC_CURRENT;
 231     }

そして先に進んで248行目、先の処理でquery変数がNPROC_CURRENTになっているのでここは真となって中に入りnum_processors_via_affinity_mask()を読んでcpu数の取得をします。 ここでcpu数が0以上見つかれば終了です。

 248   if (query == NPROC_CURRENT)
 249     {
 250       /* Try the modern affinity mask system call.  */
 251       {
 252         unsigned long nprocs = num_processors_via_affinity_mask ();
 253 
 254         if (nprocs > 0)
 255           return nprocs;
 256       }
 257 
 258 #if defined _SC_NPROCESSORS_ONLN
 259       { /* This works on glibc, Mac OS X 10.5, FreeBSD, AIX, OSF/1, Solaris,
 260            Cygwin, Haiku.  */
 261         long int nprocs = sysconf (_SC_NPROCESSORS_ONLN);
 262         if (nprocs > 0)
 263           return nprocs;
 264       }
 265 #endif
 266     }

num_processors_via_affinity_mask()はifdefで条件コンパイルをされているのでコードを見ただけだとちょっとどこが使われているのかわかりません(´・ω・`) なのでstraceしてみます。

open("/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=2581856, ...}) = 0
mmap(NULL, 2581856, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f77e19d0000
close(3)                                = 0
sched_getaffinity(0, 128, {ff, 0})      = 16
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 2), ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f77e220a000
write(1, "8\n", 28
)                      = 2
close(1)                                = 0
munmap(0x7f77e220a000, 4096)            = 0
close(2)                                = 0
exit_group(0)                           = ?
+++ exited with 0 +++

sched_getaffinity()が呼ばれています。ソースを見てみるとsched_getaffinity(2)を使っているのはここでした。処理内容としてはsched_getaffinity()を使ってcpu affinity maskを取得して立っているbit数を数え、それを最終的に返してますね。ifdefが多いので見難いですがやってることは単純でした。

 124 #elif HAVE_SCHED_GETAFFINITY_LIKE_GLIBC /* glibc >= 2.3.4 */
 125   {
 126     cpu_set_t set;
 127 
 128     if (sched_getaffinity (0, sizeof (set), &set) == 0)
 129       {
 130         unsigned long count;
 131 
 132 # ifdef CPU_COUNT
 133         /* glibc >= 2.6 has the CPU_COUNT macro.  */
 134         count = CPU_COUNT (&set);
 135 # else
 136         size_t i;
 137 
 138         count = 0;
 139         for (i = 0; i < CPU_SETSIZE; i++)
 140           if (CPU_ISSET (i, &set))
 141             count++;
 142 # endif
 143         if (count > 0)
 144           return count;
 145       }
 146   }
 147 #elif HAVE_SCHED_GETAFFINITY_NP /* NetBSD >= 5 */