LoginSignup
7
7

More than 5 years have passed since last update.

Raspberry Pi2(Linux Kernel)のブートシーケンスを読む(その5) cgroupの初期化からページアドレスの初期化まで

Last updated at Posted at 2016-04-09

前回

start_kernel内でスタックにカナリアコードを導入するまでを説明しました。
今回は、ページアドレスの初期化まで読み解きます。

今回の対象範囲

init/main.c
asmlinkage __visible void __init start_kernel(void)
{
    (省略)

    cgroup_init_early();  //対象範囲 ここから

    local_irq_disable();
    early_boot_irqs_disabled = true;

/*
 * Interrupts are still disabled. Do necessary setups, then
 * enable them
 */
    boot_cpu_init();
    page_address_init();
    pr_notice("%s", linux_banner);  //対象範囲 ここまで
    setup_arch(&command_line);      //大物だったので、次回に説明

cgroup_init_early

まず、cgroups(control groups)について説明します
cgroupsはプロセスを特定の基準によってグループ化して、
グループごとのリソース使用量(CPUコア、メモリ、IOなどの使用量)を制限・監視できる機能です。
LXC(Linux Container)やDockerなどのOSレベルの仮想化で使用される技術であり、
インフラ系(サーバ管理者)にとって馴染みのある技術と思われます。
余談ですが、Dockerは書籍が豊富で学習しやすい環境なのに、
LXCはRaspberry Piで試そうとすると、コンフィグを変更してimageを再作成が必要……この差は一体。

以下がcgroup_init_early()の実装です。

kernel/cgroup.c
int __init cgroup_init_early(void)
{
    static struct cgroup_sb_opts __initdata opts;
    struct cgroup_subsys *ss;
    int i;

    init_cgroup_root(&cgrp_dfl_root, &opts);
    cgrp_dfl_root.cgrp.self.flags |= CSS_NO_REF;

    RCU_INIT_POINTER(init_task.cgroups, &init_css_set);

    for_each_subsys(ss, i) {
        WARN(!ss->css_alloc || !ss->css_free || ss->name || ss->id,
             "invalid cgroup_subsys %d:%s css_alloc=%p css_free=%p name:id=%d:%s\n",
             i, cgroup_subsys_name[i], ss->css_alloc, ss->css_free,
             ss->id, ss->name);
        WARN(strlen(cgroup_subsys_name[i]) > MAX_CGROUP_TYPE_NAMELEN,
             "cgroup_subsys_name %s too long\n", cgroup_subsys_name[i]);

        ss->id = i;
        ss->name = cgroup_subsys_name[i];

        if (ss->early_init)
            cgroup_init_subsys(ss, true);
    }
    return 0;
}

cgroup_sb_optsはcgroupのサブシステムのオプションを管理する構造体、
cgroup_subsysはサブシステムIDとコールバック関数を管理する構造体です。
不思議ですが、前者はstaticで静的変数として宣言しているのに、
__initdataで「初期化後にメモリからデータを消去する」とも宣言している。残したいのか消したいのか。

関数init_cgroup_root()では、cgroupのroot(根、親)を設定し、
cgroup_root構造体が持つリストの初期化やrootが持つ情報をcgroup_sb_optsにコピーする作業を行います。
for_each_subsys以下では、cgroup_subsys構造体に対して、
id登録、名前登録を行い、処理が存在すれば早期の初期化処理を行います。
for_each_XXXXはfor文に似たマクロで、今回はサブシステムの数だけループ処理を行います。
このマクロに類似するマクロ(ほぼ同じ機能)は多数存在し、
例えばデバイスをサスペンドさせる際、順番にデバイスを停止させるために類似マクロを使用します。

local_irq_disable()

include/linux/irqflags.h
#define local_irq_disable() \
    do { raw_local_irq_disable(); trace_hardirqs_off(); } while (0)
include/linux/irqflags.h
#define raw_local_irq_disable()     arch_local_irq_disable()
arch/arm/include/asm/irqflags.h
static inline void arch_local_irq_disable(void)
{
    asm volatile(
        "   cpsid i         @ arch_local_irq_disable"
        :
        :
        : "memory", "cc");
}

local_irq_disable()は、CPU(コア)レベルで割り込みを禁止します。
実際には、arch_local_irq_disable()内のインラインアセンブラでCPU割り込みを禁止し、
raw_local_irq_disable()でCPU毎の差異を吸収しています。
ちなみに、disable_irq()の場合は、指定したIRQ(Interrupt ReQuest)に対応する割り込みを禁止します。

また余談ですが、インラインアセンブラはC言語のコンパイラが処理内容を考慮せずに最適化を行うため、
バイナリ段階で記述したコードと変わってしまう場合があります。例えば、勝手にレジスタの内容を破壊します。
その際には、バイナリの逆アセンブルによって、最適化後のコードを読んだり、
キチンとインラインアセンブラならではのお作法に則って、処理を書き直す必要があります。
さらに注意点として、インラインは、アセンブラ命令セットの仕様書に制約が記載されている事もあります。

boot_cpu_init

init/main.c
static void __init boot_cpu_init(void)
{
    int cpu = smp_processor_id();
    /* Mark the boot cpu "present", "online" etc for SMP and UP case */
    set_cpu_online(cpu, true);
    set_cpu_active(cpu, true);
    set_cpu_present(cpu, true);
    set_cpu_possible(cpu, true);
}

boot_cpu_init()は、CPUコアに関する情報を登録します。
まず、smp_processor_id()では、現在動作しているCPU IDを取得します。
その後のset_cpu_XXXという一連の処理は、online、active、present、possibleにビットを立てます。
 online:CPUコアがスケジューリング対象かどうか
 active:CPUコア間でプロセスの移動できるかどうか
 present:現在、CPUコアが使用できるかどうか
 possible:システムブート時にCPUコアが使用できるかどうか

page_address_init

mm/highmem.c
#define PA_HASH_ORDER   7

static struct page_address_slot {
    struct list_head lh;            /* List of page_address_maps */
    spinlock_t lock;            /* Protect this bucket's list */
} ____cacheline_aligned_in_smp page_address_htable[1<<PA_HASH_ORDER];

void __init page_address_init(void)
{
    int i;

    for (i = 0; i < ARRAY_SIZE(page_address_htable); i++) {
        INIT_LIST_HEAD(&page_address_htable[i].lh);
        spin_lock_init(&page_address_htable[i].lock);
    }
}

page_address_htable構造体は、カーネル空間として扱えるようにマッピングしたHigh Memory
ページフレームをハッシュテーブルで管理します。
このページフレーム自体はpage_address_map構造体で管理され、
この構造体がページディスクリプタへのポインタおよびページアドレスに割り当てた仮想アドレスを管理します。

mm/highmem.c
struct page_address_map {
    struct page *page;
    void *virtual;
    struct list_head list;
};

for文は、page_address_htableの配列数が"1<<PA_HASH_ORDER"で定義されているため、
1×2の7乗=128回のループを行います。
INIT_LIST_HEAD()ではpage_address_htable[i].lhのprev/nextを自身へのポインタに設定し、
spin_lock_init()ではスピンロックが使用出来る状態にします。

pr_notice

include/linux/printk.h
#define pr_fmt(fmt) fmt
#define pr_notice(fmt, ...) \
    printk(KERN_NOTICE pr_fmt(fmt), ##__VA_ARGS__)

ログレベルを指定しているだけだが、このマクロを使用する意図が読み切れない。
以下にログレベルを示します。
 KERN_EMERG:システムがダウンする可能性がある状態
 KERN_ALERT:早急に対応が必要な状態
 KERN_CRIT:ハードウェアかソフトウェアに深刻なエラーが発生した状態
 KERN_ERR:エラーが発生した状態。
 KERN_WARNING:警告
 KERN_NOTICE:INFOよりも重要なメッセージ
 KERN_INFO:ログ用メッセージ
 KERN_DEBUG:デバッグ用メッセージ

今回のpr_notice("%s", linux_banner);で出力される内容は、以下のファイルに記述されています。

init/version.c
const char linux_banner[] =
    "Linux version " UTS_RELEASE " (" LINUX_COMPILE_BY "@"
    LINUX_COMPILE_HOST ") (" LINUX_COMPILER ") " UTS_VERSION "\n";

次回

start_kernel内のsetup_arch()を読み解きました。

参考

Docker と LXC
CPU masks

7
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
7