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_CPUS
CPUマスクを作成する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 日本ユーザーグループ代表
- Twitter: @yosuke_furukawa
- Github: yosuke-furukawa