2017年7月27日
CPUマスク

はじめに
    cpumasks    は、Linuxカーネルが提供する特別な方法で、システム内のCPUに関する情報を格納します。     cpumasks    操作に関して、APIを含む関連のソースコードとヘッダファイルは以下のとおりです。
include/linux/cpumask.h 内のコメントにあるように、CPUマスクは、システム内のCPUセットを表すのに適したビットマップ(CPU番号ごとに1つのビット位置)を提供します。CPUマスクについては、以前に カーネルのエントリポイント を扱ったパートの     boot_cpu_init    関数を説明した部分で少し触れました。この関数は、最初のブートCPUをオンラインやアクティブなどにしたりする操作を行います。
set_cpu_online(cpu, true);
set_cpu_active(cpu, true);
set_cpu_present(cpu, true);
set_cpu_possible(cpu, true);これらの関数の実装を検討する前に、まずはそれぞれのマスクについて見ていきましょう。
    cpu_possible    は、当該のシステム起動中、いつでもプラグインできるCPU IDのセットです。つまり、このCPUのマスクには、システム内で利用可能なCPUの最大数が含まれています。これは、     CONFIG_NR_CPUS    カーネル構成オプションを介して静的に設定される     NR_CPUS    の値と同じです。
    cpu_present    マスクは、現在どのCPUがプラグインされているかを表します。
    cpu_online    は、     cpu_present    のサブセットを表し、どのCPUがスケジューリングできるかを示します。言い換えると、このマスクのビットは、Linuxカーネルがプロセッサを利用できるかどうかをカーネルに伝えます。
最後のマスクは     cpu_active    です。このマスクのビットは、タスクを特定のプロセッサに移していいかどうかをLinuxカーネルに伝えます。
これらのマスクの設定は     CONFIG_HOTPLUG_CPU    設定オプションに依存しており、このオプションがdisalbedな場合は     possible == present    かつ     active == online    というフラグ状態となります。これらの関数の実装については、どれも非常に似ており、いずれも2番目のパラメータをチェックします。     true    であれば     cpumask_set_cpu    が呼び出され、そうでなければ     cpumask_clear_cpu    が呼び出されます。
    cpumask    の作成には2通りの方法があります。1つ目は     cpumask_t    を使う方法で、その定義は以下のとおりです。
typedef struct cpumask { DECLARE_BITMAP(bits, NR_CPUS); } cpumask_t;これが、ビットマスクの1つの     bits    フィールドを含む     cpumask    構造体をラップします。     DECLARE_BITMAP    マクロは、以下の2つのパラメータを取り、
- ビットマップ名
 - ビット数
 
与えられた名前で     unsigned long    の配列を作成します。その実装は、非常に簡単です。
#define DECLARE_BITMAP(name,bits) \
        unsigned long name[BITS_TO_LONGS(bits)]この時     BITS_TO_LONGS    は、以下のようになります。
#define BITS_TO_LONGS(nr)       DIV_ROUND_UP(nr, BITS_PER_BYTE * sizeof(long))
#define DIV_ROUND_UP(n,d) (((n) + (d) - 1) / (d))私たちがベースにしているのは     x86_64    アーキテクチャなので、     unsigned long    は8バイトのサイズで、配列には1つの要素のみが含まれます。
(((8) + (8) - 1) / (8)) = 1    NR_CPUS    は、システム内のCPUの数を表すマクロで、 include/linux/threads.h で定義されている     CONFIG_NR_CPUS    マクロによって設定されます。その表記は以下のとおりです。
#ifndef CONFIG_NR_CPUS
        #define CONFIG_NR_CPUS  1
#endif
#define NR_CPUS         CONFIG_NR_CPUSCPUマスクを作成する2つ目の方法として、     DECLARE_BITMAP    マクロを直接叩いて得られたビットマップを     to_cpumask    マクロに渡して     struct cpumask *    に変換する方法があります。
#define to_cpumask(bitmap)                                              \
        ((struct cpumask *)(1 ? (bitmap)                                \
                            : (void *)sizeof(__check_is_bitmap(bitmap))))ここでは、常に     true    である三項演算子が確認できます。     __check_is_bitmap    インライン関数の定義は以下のとおりです。
static inline int __check_is_bitmap(const unsigned long *bitmap)
{
        return 1;
}このインライン関数は、毎回     1    を返します。ここでこの関数が必要なのは、与えられた     bitmap    がビットマップかどうかをコンパイル時に確認するためです。つまり、     cpu_possible_bits    を     to_cpumask    マクロに渡して、     unsigned long    配列を     struct cpumask *    に変換することで与えられた     bitmap    が、     unsigned long *    型であることをチェックしています。
cpumask API
これまで見てきたようにいくつかの方法で私たちはCPUマスクを作成できるわけですが、一方でLinuxカーネルはCPUマスクを操作するためのAPIを提供します。前述の関数の1つ、     set_cpu_online    を検討してみましょう。この関数は2つのパラメータを取ります。
- CPUの番号
 - CPUの状態
 
その実装は以下のとおりです。
void set_cpu_online(unsigned int cpu, bool online)
{
    if (online) {
        cpumask_set_cpu(cpu, to_cpumask(cpu_online_bits));
        cpumask_set_cpu(cpu, to_cpumask(cpu_active_bits));
    } else {
        cpumask_clear_cpu(cpu, to_cpumask(cpu_online_bits));
    }
}この関数はまず、第2のパラメータ     state    をチェックし、その値に応じて     cpumask_set_cpu    または     cpumask_clear_cpu    を呼び出します。これを見ると、     cpumask_set_cpu    内の第2パラメータが     struct cpumask *    へキャストされていることが分かります。今回の事例では、これは     cpu_online_bits    で、次のとおり定義されたビットマップです。
static DECLARE_BITMAP(cpu_online_bits, CONFIG_NR_CPUS) __read_mostly;    cpumask_set_cpu    関数は     set_bit    関数への呼び出しを1回だけ行います。
static inline void cpumask_set_cpu(unsigned int cpu, struct cpumask *dstp)
{
        set_bit(cpumask_check(cpu), cpumask_bits(dstp));
}    set_bit    関数もまた2つのパラメータを取り、与えられたビット(第1パラメータ)をメモリ(第2パラメータまたは     cpu_online_bits    ビットマップ)内にセットします。ここで、     set_bit    が呼び出される前に、2つのパラメータが次のマクロに渡されることが分かります。
- cpumask_check
 - cpumask_bits
 
この2つのマクロについて考えましょう。まず、今回の事例では、     cpumask_check    は何もしないのであれば、与えられたパラメータを返すのみとなります。第2の     cpumask_bits    は、与えられた     struct cpumask *    構造体の     bits    フィールドのみを返します。
#define cpumask_bits(maskp) ((maskp)->bits)では、     set_bit    の実装を見てみましょう。
 static __always_inline void
 set_bit(long nr, volatile unsigned long *addr)
 {
         if (IS_IMMEDIATE(nr)) {
                asm volatile(LOCK_PREFIX "orb %1,%0"
                        : CONST_MASK_ADDR(nr, addr)
                        : "iq" ((u8)CONST_MASK(nr))
                        : "memory");
        } else {
                asm volatile(LOCK_PREFIX "bts %1,%0"
                        : BITOP_ADDR(addr) : "Ir" (nr) : "memory");
        }
 }この関数は、見た目は難しそうですが、実はそれほどでもありません。まず、この関数は     nr    つまりビット番号を     IS_IMMEDIATE    マクロに渡します。このマクロは、単にGCC内部の関数     __builtin_constant_p    を呼び出します。
define IS_IMMEDIATE(nr)    (__builtin_constant_p(nr))    builtin_constant_p    は与えられたパラメータがコンパイル時に既知の定数であるかチェックします。今回の事例では     cpu    はコンパイル時定数ではないので、     else    の項が実行されます。
asm volatile(LOCK_PREFIX "bts %1,%0" : BITOP_ADDR(addr) : "Ir" (nr) : "memory");これがどのように動くのか、段階を追って考えましょう。
    LOCK_PREFIX    は、x86の     lock    命令です。この命令はCPUに、命令が実行されている間システムを占有するように命じます。これにより、CPUはメモリアクセスを同期させることができ、複数プロセッサ(または、例えばDMAコントローラなどのデバイス)による1つのメモリセルへの同時アクセスが回避されます。
    BITOP_ADDR    は、与えられたパラメータを     (*(volatile long *)    へキャストして、制約条件     +m    を加えます。     +    は、この命令によってこのオペランドの読み出しと書き込みの両方が行われることを意味します。     m    は、これがメモリオペランドであることを示します。     BITOP_ADDR    は、次のとおり定義されます。
#define BITOP_ADDR(x) "+m" (*(volatile long *) (x))次は、     memory    clobberです。これは、アセンブリコードが入力オペランドと出力オペランドに列挙されているもの以外の項目に対してメモリの書き込みまたは読み出しを行う(例えば、入力パラメータの1つによって示されているメモリにアクセスする)ということをコンパイラに伝えます。
    Ir    は、イミディエイト(即値)レジスタコマンドです。
    bts    命令は、与えられたビットをビット列にセットして、与えられたビットの値を     CF    フラグに格納します。したがって、CPUの番号を渡したことになり、この番号は今回の事例ではゼロなので、     set_bit    が実行された後に、     cpu_online_bits    CPUマスク内のゼロ番目のビットをセットします。つまり、この時点では最初のCPUがオンラインになっています。
CPUマスクには、     set_cpu_*    APIの他に、CPUマスク操作のために、もう1つ別のAPIがあります。それについて簡単に触れます。
その他のcpumask API
CPUマスクには、例えば次のような、さまざまな状態のCPUの数を取得するためのマクロもあります。
#define num_online_cpus()    cpumask_weight(cpu_online_mask)このマクロは、     online    CPUの数を返すもので、     cpu_online_mask    ビットマップを渡して     cpumask_weight    関数を呼び出します。     cpumask_weight    関数は、次の2つのパラメータを使って     bitmap_weight    を1回呼び出してから、与えられたビットマップ内のビットの数を計算します。
- cpumask bitmap
 nr_cpumask_bits– このケースではNR_CPUS
static inline unsigned int cpumask_weight(const struct cpumask *srcp)
{
    return bitmap_weight(cpumask_bits(srcp), nr_cpumask_bits);
}    num_online_cpus    の他に、全てのCPU状態についてのマクロもあります。
- num_possible_cpus
 - num_active_cpus
 - cpu_online
 - cpu_possible
 - その他多数
 
上記のマクロに加えて、Linuxカーネルは、     cpumask    の操作のために、次のAPIを提供します。
for_each_cpu– マスク内の全CPUに渡って繰り返すfor_each_cpu_not-相補(ビット反転)マスク内の全CPUに渡って繰り返すfcpumask_clear_cpu– CPUマスク内の1つのCPUをクリアするcpumask_test_cpu– マスク内のつのCPUをテストするcpumask_setall– マスク内の全CPUをセットするcpumask_size– “struct cpumask”のために割り当てるサイズをバイト数で返す- その他多数
 
リンク
株式会社リクルート プロダクト統括本部 プロダクト開発統括室 グループマネジャー 株式会社ニジボックス デベロップメント室 室長 Node.js 日本ユーザーグループ代表
- X: @yosuke_furukawa
 - Github: yosuke-furukawa
 









