読者です 読者をやめる 読者になる 読者になる

this_rq()

linux

所用でどんな感じで処理をしているのかを追いかけたのでメもです。

たとえば、kernel/sched.cのnr_iowait_cpu()ですが、this_rq()でcpuごとのランキューのデータを取得できます。

unsigned long nr_iowait_cpu(void)
{
        struct rq *this = this_rq();

このthis_rq()は関数ではなくて、マクロです。kernel/sched.cの659行目で定義してます。

#define this_rq()               (&__get_cpu_var(runqueues))

ここで出てくる__get_cpu_varも関数ではなくてマクロになってます。定義はinclude/asm-generic/percpu.hにあります。

#define __get_cpu_var(var) \
        (*SHIFT_PERCPU_PTR(&per_cpu_var(var), my_cpu_offset))

まずは、順番にper_cpu_varから見ていくと、これもinclude/linux/percpu-defs.hで定義されているマクロで、単純に文字列を連結するだけです。

#define per_cpu_var(var) per_cpu__##var

my_cpu_offsetもこれまたマクロで、include/asm-generic/percpu.hで定義されてます。
こいつは、CONFIG_DEBUG_PREEMPTがセットされているかどうかで処理が変わってます。

#ifdef CONFIG_DEBUG_PREEMPT
#define my_cpu_offset per_cpu_offset(smp_processor_id())
#else
#define my_cpu_offset __my_cpu_offset
#endif

per_cpu_offsetを使うほうから見ていくと、
smp_processor_id()もマクロで、include/linux/smp.hにて以下のように定義されてます。どちらもthis_cpuを返すことには変わりません。

#ifdef CONFIG_DEBUG_PREEMPT
extern unsigned int debug_smp_processor_id(void);
# define smp_processor_id() debug_smp_processor_id()
#else
# define smp_processor_id() raw_smp_processor_id()
#endif

debug_smp_processor_id()はlib/smp_processor_id.cに実体がある関数です。
raw_smp_processor_id()はarch/x86/include/asm/smp.hでマクロが定義されていて、CONFIG_X86_32_SMPやCONFIG_X86_64_SMPが定義されている場合に、以下の定義が有効になってます。

#define raw_smp_processor_id() (percpu_read(cpu_number))

percpu_read()はarch/x86/include/asm/percpu.hとinclude/linux/percpu.hの両方にマクロがあって、どっちが動いているのか、ソース見ただけだとよく分かりませんでした/(^o^)\

include/linux/percpu.hだとこんな感じで、

#ifndef percpu_read
# define percpu_read(var)                                               \
  ({                                                                    \
        typeof(per_cpu_var(var)) __tmp_var__;                           \
        __tmp_var__ = get_cpu_var(var);                                 \
        put_cpu_var(var);                                               \
        __tmp_var__;                                                    \
  })
#endif

arch/x86/include/asm/percpu.hだとこんな感じです。

#define percpu_read(var)        percpu_from_op("mov", per_cpu__##var,   \
                                               "m" (per_cpu__##var))

これでやっと、ここに戻ってきて、

#define __get_cpu_var(var) \
        (*SHIFT_PERCPU_PTR(&per_cpu_var(var), my_cpu_offset))

SHIFT_PERCPU_PTRもinclude/asm-generic/percpu.hにあるマクロで、こんな感じになってます。

#ifndef SHIFT_PERCPU_PTR
#define SHIFT_PERCPU_PTR(__p, __offset) RELOC_HIDE((__p), (__offset))
#endif

悲しいことに、こいつも何個かのファイルに定義がありますorz
include/linux/compiler.hの場合は、

#ifndef RELOC_HIDE
# define RELOC_HIDE(ptr, off)                                   \
  ({ unsigned long __ptr;                                       \
     __ptr = (unsigned long) (ptr);                             \
    (typeof(ptr)) (__ptr + (off)); })
#endif

include/linux/compiler-gcc.hの場合は、

#define RELOC_HIDE(ptr, off)                                    \
  ({ unsigned long __ptr;                                       \
    __asm__ ("" : "=r"(__ptr) : "0"(ptr));              \
    (typeof(ptr)) (__ptr + (off)); })

include/linux/compiler-intel.hの場合は、

#define RELOC_HIDE(ptr, off)                                    \
  ({ unsigned long __ptr;                                       \
     __ptr = (unsigned long) (ptr);                             \
    (typeof(ptr)) (__ptr + (off)); })

マクロ面倒くさいです><
という感じで、またここに戻ったときに、per_cpu_var(var)は文字連結した結果になって、my_cpu_offsetはthis_cpuになります。

#define __get_cpu_var(var) \
        (*SHIFT_PERCPU_PTR(&per_cpu_var(var), my_cpu_offset))

最後に、この2個をRELOC_HIDEマクロで処理した結果が、欲しいランキューになるようです。