start_kernel中的rest_init函数到init进程

copy from :https://blog.csdn.net/challen537/article/details/6120130

tart_kernel ,是用来启动内核的主函数,我想大家都知道这个函数啦,而在该函数的最后将调用一个函数叫 rest_init() ,它执行完,内核就起来了,

asmlinkage void __init start_kernel(void)

{

......

/* Do the rest non-__init‘ed, we‘re now alive */

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();

/*

* init can run on any cpu.

*/

set_cpus_allowed_ptr(current, CPU_MASK_ALL_PTR);

/*

* Tell the world that we‘re going to be the grim

* reaper of innocent orphaned children.

* We don‘t want people to have to make incorrect

* assumptions about where in the task array this

* can be found.

*/

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();

/*

* check if there is an early userspace init. If yes, let it do all

* the work

*/

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();

}

/*

* Ok, we have completed the initial bootup, and

* we‘re essentially up and running. Get rid of the

* initmem segments and start the user-mode stuff..

*/

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 呢,那么它的实现就如下喽:

/* Called by boot processor to activate the rest. */

static void __init smp_init(void)

{

unsigned int cpu;

/* FIXME: This should be done in userspace --RR */

for_each_present_cpu(cpu) {

if (num_online_cpus() >= setup_max_cpus)

break;

if (!cpu_online(cpu))

cpu_up(cpu);

}

/* Any cleanup work */

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 /* NR_CPUS == 1 */

#define for_each_cpu_mask(cpu, mask) /

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

#endif /* NR_CPUS */

即对于每个 cpu 都要执行大括号里的语句,如果当前 cpu 没激活就把它激活的,该函数然后打印一些 cpu 信息,如当前激活的 cpu 数目。

原文地址:https://www.cnblogs.com/Oude/p/12588305.html

时间: 2024-10-10 07:58:19

start_kernel中的rest_init函数到init进程的相关文章

[转]go中的main函数和init函数

Go里面有两个保留的函数:init函数(能够应用于所有的package)和main函数(只能应用于package main).这两个函数在定义时不能有任何的参数和返回值.虽然一个package里面可以写任意多个init函数,但这无论是对于可读性还是以后的可维护性来说,我们都强烈建议用户在一个package中每个文件只写一个init函数. Go程序会自动调用init()和main(),所以你不需要在任何地方调用这两个函数.每个package中的init函数都是可选的,但package main就必

Android之init进程分析(1)

本文介绍init进程中的action触发方式 一,什么是action 在android中,使用action来管理并执行命令.action是一个数据结构,里面包含了命令集合command,action的名字等.Android系统通过action来执行一组命令. struct action { /* node in list of all actions */ struct listnode alist; /* node in the queue of pending actions */ stru

Android6.0系统启动流程分析一:init进程

到了Android6.0,Init进程使用c++来写了,不过没有关系,它和c写的init没有太大的区别. Init进程的入口代码是:system\core\init\init.cpp main函数: int main(int argc, char** argv) { if (!strcmp(basename(argv[0]), "ueventd")) { return ueventd_main(argc, argv); } if (!strcmp(basename(argv[0]),

Android Init进程命令的执行和服务的启动

这里开始分析init进程中配置文件的解析,在配置文件中的命令的执行和服务的启动. 首先init是一个可执行文件,它的对应的Makfile是init/Android.mk. Android.mk定义了init程序在编译的时候,使用了哪些源码,以及生成方式.当init程序生成之后,最终会放到/init,即根目录的init文件.通常所说的init进程就是执行这个init程序. 执行这个init程序的代码是在KERNEL/init/main.c文件中的kernel_init()函数里,当kernel把一

(作业3)Linux内核的启动过程(从start_kernel到init进程启动)

作业题目: 详细分析从start_kernel到init进程启动的过程并结合实验截图撰写一篇署名博客,并在博客文章中注明“真实姓名(与最后申请证书的姓名务必一致) + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 ”,博客内容的具体要求如下: 题目自拟,内容围绕Linux内核的启动过程,即从start_kernel到init进程启动: 博客中需要使用实验截图 博客内容中需要仔细分析

Linux内核分析-使用gdb跟踪调试内核从start_kernel到init进程启动

姓名:江军 ID:fuchen1994 实验日期:2016.3.13 实验指导 使用实验楼的虚拟机打开shell cd LinuxKernel/ qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img 内核启动完成后进入menu程序(<软件工程C编码实践篇>的课程项目),支持三个命令help.version和quit,您也可以添加更多的命令,对选修过<软件工程C编码实践篇>的童鞋应该是a piece of

构建一个简单的Linux系统 MenuOs —— start_kernel到init进程(20135304刘世鹏)

构建一个简单的Linux系统 MenuOs —— start_kernel到init进程 作者:刘世鹏20135304 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 Linux内核代码简介 内核源码三个个重要目录 arch占有代码量最大,支持不同cpu的源代码,arch/x86目录下的代码是我们关注的重点 init,内核启动相关的代码基本都在init目录下,init/main.c中start_kernel是整

gdb跟踪调试内核从start_kernel到init进程启动

顾涛原创作品转载请注明出处<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 如果我写的不好或者有误的地方请留言 题目自拟,内容围绕Linux内核的启动过程,即从start_kernel到init进程启动: 博客中需要使用实验截图 博客内容中需要仔细分析start_kernel函数的执行过程 总结部分需要阐明自己对“Linux系统启动过程”的理解,尤其是idle进程.1号进程是怎么来的. 实验报告: 在实验楼里跑了

init进程 &amp;&amp; 解析Android启动脚本init.rc &amp;&amp; 修改它使不启动android &amp;&amp; init.rc中启动一个sh文件

Android启动后,系统执行的第一个进程是一个名称为init 的可执行程序.提供了以下的功能:设备管理.解析启动脚本.执行基本的功能.启动各种服务.代码的路径:system/core/init,编译的结果是一个可执行文件:init.这个init 的可执行文件是系统运行的第一个用户空间的程序,它以守护进程的方式运行.启动脚本则就是下面要讲的Init.rc. ======================================================================