天天看點

kernel 啟動過程之四,start_kernel中的rest_init函數到init程序

      tart_kernel ,是用來啟動核心的主函數,我想大家都知道這個函數啦,而在該函數的最後将調用一個函數叫 rest_init() ,它執行完,核心就起來了,

      asmlinkage void __init start_kernel(void)

      {

      ......

      rest_init();

      }

      現在我們來看一下 rest_init() 函數,它也在檔案 init/main.c 中,它的前面幾行是:

      static void noinline __init_refok rest_init(void) __releases(kernel_lock)

      {

      int pid;

      kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);

      其中函數 kernel_thread 定義在檔案 arch/ia64/kernel/process.c 中,用來啟動一個核心線程,這裡的 kernel_init 是要執行的函數的指針, NULL 表示傳遞給該函數的參數為空, CLONE_FS | CLONE_SIGHAND 為 do_fork 産生線程時的标志,表示程序間的 fs 資訊共享,信号處理和塊信号共享,然後我就屁颠屁颠地追随到 kernel_init 函數了,現在來瞧瞧它都做了什麼好事,它的完整代碼如下:

      static int __init kernel_init(void * unused)

      {

      lock_kernel();

      set_cpus_allowed_ptr(current, CPU_MASK_ALL_PTR);

      init_pid_ns.child_reaper = current;

      cad_pid = task_pid(current);

      smp_prepare_cpus(setup_max_cpus);

      do_pre_smp_initcalls();

      smp_init();

      sched_init_smp();

      cpuset_init_smp();

      do_basic_setup();

      if (!ramdisk_execute_command)

      ramdisk_execute_command = "/init";

      if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {

      ramdisk_execute_command = NULL;

      prepare_namespace();

      }

      init_post();

      return 0;

      }

      在 kernel_init 函數的一開始就調用了 lock_kernel() 函數,當編譯時選上了 CONFIG_LOCK_KERNEL ,就加上大核心鎖,否則啥也不做,緊接着就調用了函數 set_cpus_allowed_ptr ,由于這些函數對 init 程序的調起還是有影響的,我們還是一個一個來瞧瞧吧,不要忘了啥東東最好,

      static inline int set_cpus_allowed_ptr(struct task_struct *p,

      const cpumask_t *new_mask)

      {

      if (!cpu_isset(0, *new_mask))

      return -EINVAL;

      return 0;

      }

      這函數其實就調用了 cpu_isset 宏,定義在檔案 "include/linux/cpumask.h 中,如下:

      #define cpu_isset(cpu, cpumask) test_bit((cpu), (cpumask).bits)

      再來看看 set_cpus_allowed_ptr 的第二個參數類型吧,也定義在檔案 include/linux/cpumask.h 中,具體如下:

      typedef struct { DECLARE_BITMAP(bits, NR_CPUS); } cpumask_t;

      接着尾随着 DECLAR_BITMAP 宏到檔案 include/linux/types.h 中,定義如下:

      #define DECLARE_BITMAP(name,bits) /

      unsigned long name[BITS_TO_LONGS(bits)]

      而宏 BITS_TO_LONGS 定義在檔案 include/linux/bitops.h 中,實作如下:

      #define BITS_TO_LONGS(nr) DIV_ROUND_UP(nr, BITS_PER_BYTE * sizeof(long))

      DIV_ROUND_UP 宏定義在檔案 include/linux/kernel.h 中, BITS_PER_BYTE 宏定義在檔案 include/linux/bitops.h 中,實作如下:

      #define DIV_ROUND_UP(n,d) (((n) + (d) - 1) / (d))

      #define BITS_PER_BYTE 8

      即當 NR_CPUS 為 1 ~ 32 時, cpumask_t 類型為

      struct {

}

然後來看看在 set_cpus_allowed_ptr(current, CPU_MASK_ALL_PTR); 中的 CPU_MASK_ALL_PTR 宏,定義在 include/linux/cpumask.h 中:

#define CPU_MASK_ALL_PTR (&CPU_MASK_ALL)

而 CPU_MASK_ALL 宏也定義在檔案 include/linux/cpumask.h 中:

#define CPU_MASK_ALL /

(cpumask_t) { { /

[BITS_TO_LONGS(NR_CPUS)-1] = CPU_MASK_LAST_WORD /

} }

NR_CPUS 宏定義在檔案 include/linux/threads.h 中,實作如下:

#ifdef CONFIG_SMP

#define NR_CPUS CONFIG_NR_CPUS

#else

#define NR_CPUS 1

#endif

CPU_MASK_LAST_WORD 宏定義在檔案 include/linux/cpumask.h 中,實作如下:

#define CPU_MASK_LAST_WORD BITMAP_LAST_WORD_MASK(NR_CPUS)

BITMAP_LAST_WORD_MASK(NR_CPUS) 宏定義在檔案 include/linux/bitmap.h 中,實作如下:

#define BITMAP_LAST_WORD_MASK(nbits) /

( /

((nbits) % BITS_PER_LONG) ? /

(1UL<<((nbits) % BITS_PER_LONG))-1 : ~0UL /

)

當 NR_CPUS 為 1 時, CPU_MASK_LAST_WORD 為 1

當 NR_CPUS 為 2 時, CPU_MASK_LAST_WORD 為 2

當 NR_CPUS 為 n 時, CPU_MASK_LAST_WORD 為 2 的 n-1 次方

有點暈了,我們現在把參數帶入,即 set_cpus_allowed_ptr(current, CPU_MASK_ALL_PTR)

-- >cpu_isset(0,CPU_MASK_ALL_PTR) -- >test_bit(0,CPU_MASK_ALL_PTR.bits)

即當 NR_CPUS 為 n 時,就把 usigned long bits[0] 的第 n 位置 1 ,應該就如注釋所說的, init 能運作在任何 CPU 上吧。

現在 kernel_init 中的 set_cpus_allowed_ptr(current, CPU_MASK_ALL_PTR); 分析完了,我們接着往下看,首先 init_pid_ns.child_reaper = current; init_pid_ns 定義在 kernel/pid.c 檔案中

struct pid_namespace init_pid_ns = {

.kref = {

.refcount = ATOMIC_INIT(2),

},

.pidmap = {

[ 0 ... PIDMAP_ENTRIES-1] = { ATOMIC_INIT(BITS_PER_PAGE), NULL }

},

.last_pid = 0,

.level = 0,

.child_reaper = &init_task,

};

它是一個 pid_namespace 結構的變量,先來看看 pid_namespace 的結構,它定義在檔案

include/linux/pid_namespace.h 中,具體定義如下:

struct pid_namespace {

struct kref kref;

struct pidmap pidmap[PIDMAP_ENTRIES];

int last_pid;

struct task_struct *child_reaper;

struct kmem_cache *pid_cachep;

unsigned int level;

struct pid_namespace *parent;

#ifdef CONFIG_PROC_FS

struct vfsmount *proc_mnt;

#endif

};

即把目前程序設為接受其它孤兒程序的程序,然後取得該程序的程序 ID ,如:

cad_pid = task_pid(current);

然後調用 smp_prepare_cpus(setup_max_cpus); 如果編譯時沒有指定 CONFIG_SMP ,它什麼也不做,接着往下看,調用 do_pre_smp_initcalls() 函數,它定義在 init/main.c 檔案中,實作如下:

static void __init do_pre_smp_initcalls(void)

{

extern int spawn_ksoftirqd(void);

migration_init();

spawn_ksoftirqd();

if (!nosoftlockup)

spawn_softlockup_task();

}

其中 migration_init() 定義在檔案 include/linux/sched.h 中,具體實作如下 :

#ifdef CONFIG_SMP

void migration_init(void);

#else

static inline void migration_init(void)

{

}

#endif

好像什麼也沒有做,然後是調用 spawn_ksoftirqd() 函數,定義在檔案 kernel/softirq.c 中,代碼如下:

__init int spawn_ksoftirqd(void)

{

void *cpu = (void *)(long)smp_processor_id();

int err = cpu_callback(&cpu_nfb, CPU_UP_PREPARE, cpu);

BUG_ON(err == NOTIFY_BAD);

cpu_callback(&cpu_nfb, CPU_ONLINE, cpu);

register_cpu_notifier(&cpu_nfb);

return 0;

}

在該函數中,首先調用 smp_processor_id 函數獲得目前 CPU 的 ID 并把它指派給變量 cpu ,然後把 cpu 連同 &cpu_nfb , CPU_UP_PREPARE 傳遞給函數 cpu_callback ,我們先看 cpu_callback 的前幾行:

static int __cpuinit cpu_callback(struct notifier_block *nfb,

unsigned long action,

void *hcpu)

{

int hotcpu = (unsigned long)hcpu;

struct task_struct *p;

switch (action) {

case CPU_UP_PREPARE:

case CPU_UP_PREPARE_FROZEN:

p = kthread_create(ksoftirqd, hcpu, "ksoftirqd/%d", hotcpu);

if (IS_ERR(p)) {

printk("ksoftirqd for %i failed/n", hotcpu);

return NOTIFY_BAD;

}

kthread_bind(p, hotcpu);

per_cpu(ksoftirqd, hotcpu) = p;

break;

從上述代碼可以看出當 action 為 CPU_PREPARE 時,将建立一個核心線程并把它指派給 p ,該程序所要運作的函數為 ksoftirqd ,傳遞給該函數的參數為 hcpu ,而緊跟其後的” ksoftirqd/%d”,hotcpu 為該程序的名字參數,這就是我們在終端用指令 ps -ef | grep ksoftirqd 所看到的線程;如果程序建立失敗,列印出錯資訊,否則把建立的線程 p 綁定到目前 CPU 的 ID 上,這就是 kthread_bind(p,hotcpu) 所做的,接下來的幾行為:

case CPU_ONLINE:

case CPU_ONLINE_FROZEN:

wake_up_process(per_cpu(ksoftirqd, hotcpu));

break;

即在 spawn_ksoftirqd 函數中 cpu_callback(&cpu_nfb, CPU_ONLINE, cpu); 的 action 為 CPU_ONLINE 時,将調用 wake_up_process 函數來喚醒目前 CPU 上的 ksoftirqd 程序。最後調用 register_cpu_notifier(&cpu_nfb) ;其實也沒做什麼,隻是簡單的傳回 0 。傳回到 do_pre_smp_initcalls 函數中,接着往下看:

if (!nosoftlockup)

spawn_softlockup_task();

spawn_softlockup_task() 函數定義在檔案 include/linux/sched.h 中,是個空函數。

到現在為止, do_pre_smp_initcalls 分析完了,它主要就是建立程序 ksoftirqd ,把它綁定到目前 CPU 上,然後再把該程序拷貝給每個 CPU ,并喚醒所有 CPU 上的程序 ksoftirqd ,就是當我們執行 ps -ef | grep ksoftirqd 的時候所看到的:

root 4 2 0 08:30 ? 00:00:03 [ksoftirqd/0]

root 7 2 0 08:30 ? 00:00:02 [ksoftirqd/1]

革命尚未成功,同志仍需努力!接着享受吧,呵呵!

現在到了 kernel_init 函數中的 smp_init(); 了

如果在編譯時沒有選擇 CONFIG_SMP ,若定義 CONFIG_X86_LOCAL_APIC 則去調用 APIC_init_uniprocessor() 函數,否則什麼也不做,具體代碼定義在檔案 init/main.c 中:

#ifndef CONFIG_SMP

#ifdef CONFIG_X86_LOCAL_APIC

static void __init smp_init(void)

{

APIC_init_uniprocessor();

}

#else

#define smp_init() do { } while (0)

#endif

如果在編譯時選擇了 CONFIG_SMP 呢,那麼它的實作就如下喽:

static void __init smp_init(void)

{

unsigned int cpu;

for_each_present_cpu(cpu) {

if (num_online_cpus() >= setup_max_cpus)

break;

if (!cpu_online(cpu))

cpu_up(cpu);

}

printk(KERN_INFO "Brought up %ld CPUs/n", (long)num_online_cpus());

smp_cpus_done(setup_max_cpus);

}

來看看這個函數的, for_each_present_cpu(cpu) 宏在檔案 include/linux/cpumask.h 中實作:

#define for_each_present_cpu(cpu) for_each_cpu_mask((cpu), cpu_present_map)

而 for_each_cpu_mask(cpu,mask) 宏也在檔案 include/linux/cpumask.h 中實作:

#if NR_CPUS > 1

#define for_each_cpu_mask(cpu, mask) /

for ((cpu) = first_cpu(mask); /

(cpu) < NR_CPUS; /

(cpu) = next_cpu((cpu), (mask)))

#else

#define for_each_cpu_mask(cpu, mask) /

for ((cpu) = 0; (cpu) < 1; (cpu)++, (void)mask)

#endif

即對于每個 cpu 都要執行大括号裡的語句,如果目前 cpu 沒激活就把它激活的,該函數然後列印一些 cpu 資訊,如目前激活的 cpu 數目。

繼續閱讀