内核启动:阶段三

本文详细剖析了操作系统内核启动的第三个关键阶段,涵盖了这一阶段的主要任务和流程,深入理解这一过程对于操作系统开发者和爱好者至关重要。
接上一篇博文内容,进入内核启动阶段三:
主要任务一览:
(1)创建第一个内核线程kernel_init
(2)初始化设备驱动
(3)挂接根文件系统
(4)执行应用程序

init/main.c中定义了下面几个主要函数:
/*
 * We need to finalize in a non-__init function or else race conditions
 * between the root thread and the init thread may cause start_kernel to
 * be reaped by free_initmem before the root thread has proceeded to
 * cpu_idle.
 * gcc-3.4 accidentally inlines this function, so use noinline.
 * 我们需要在非__init 函数或者其他函数中完成。root 线程和 init 线程之间存在条件竞争。
 * 在 root 线程执行到 cpu_idle 之前, start_kernel 可能被 free_initmem 扼杀。
 * gcc-3. 4 偶然会内联这个函数, 所以使用 noinline 的函数类型
 */

static noinline void __init_refok rest_init(void)
__releases(kernel_lock)
{
int pid;
kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
/*
启动内核1号进程kernel_init,看kernel_init()函数的实现
kernel_init:函数指针
NULL:表示传递给该函数的参数为空
CLONE_FS | CLONE_SIGHAND:为do_fork产生进程时的标志,表示进程间的fs信息共享,信号处理和块信号共享
*/
numa_default_policy();
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
unlock_kernel();

/*
* The boot idle thread must execute schedule()
* at least once to get things moving:
*/
init_idle_bootup_task(current);
rcu_scheduler_starting();
preempt_enable_no_resched();
schedule(); //在进入idle空闲状态之前,主动调用schedule函数看有没有可执行的进程
preempt_disable();

/* Call into cpu_idle with preempt disabled */
cpu_idle();//启动0号进程进入空闲状态
}
===========================================================================================
static int __init kernel_init(void * unused)
{
lock_kernel();//大内核锁(即1号进程的锁),空函数什么也不做
/*
* init can run on any cpu.
*/
set_cpus_allowed_ptr(current, cpu_all_mask);
/*
* 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();
start_boot_trace();
smp_init();
sched_init_smp();
do_basic_setup();  //初始化内核设备:就是那些编译进内核的模块的初始化
/*
 * check if there is an early userspace init.  If yes, let it do all
 * the work
 * 检查是否有早期用户空间的init程序。如果有,让其执行
 */
if (!ramdisk_execute_command) 
ramdisk_execute_command = "/init";
/* ramdisk 中没有 init 的用户空间的话, 挂接根文件系统 */
if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0)
{
ramdisk_execute_command = NULL;
prepare_namespace();//里面调用mount_root()挂接根文件系统
}
/*
 * 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.
 * 现在我们已经完成了 初始化启动过程, 并且彻底地启动运行起来了
 * 去掉 initmem 段并且启动用户模式的部分
 */
init_post();//执行应用程序:真正启动了用户进程init
return 0;
}
===========================================================================================
初始化内核设备驱动程序:
static void __init do_basic_setup(void)
{
rcu_init_sched(); /* needed by module_init stage. */
init_workqueues(); //初始化工作队列
cpuset_init_smp();
usermodehelper_init();
driver_init();  //设备模型中一些结构体初始化和设备的注册
init_irq_proc();
do_initcalls(); 
/*
* 执行所有的设备初始化程序:内核镜像中把设备初始化函数指针链接成数组,
* 从__initcall_start到__initcall_end 之间的数据
* do_initcalls()函数就是通过调用数组中的函数指针,完成驱动程序的初始化
*/
}
===========================================================================================
 /*
  * This is a non __init function. Force it to be noinline otherwise gcc
  * 这是一个非__init函数。强制让它为非内联函数,以防 gcc
  * makes it inline to init() and it becomes part of init.text section
  * 让它内联到init()中并成为init.text段的一部分
  */
static noinline int init_post(void)//此时内核已经挂接了根文件系统
__releases(kernel_lock)
{
/* need to finish all async __init code before freeing the memory */
async_synchronize_full();
free_initmem();
/*
到此,内核初始化已经接近尾声了,所有的初始化函数都已经被调用,因此free_initmem函数可以
舍弃内存的__init_start至__init_end(包括.init.setup、.initcall.init等节)之间的数据.
所有使用__init标记过的函数和使用__initdata标记过的数据,在free_initmem函数执行后,都
不能使用,它们曾经获得的内存现在可以重新用于其他目的
*/
unlock_kernel();
mark_rodata_ro();
system_state = SYSTEM_RUNNING;//设置系统状态为运行状态
numa_default_policy();
/* 下面sys_open()函数打开根文件系统中的 /dev/console , 此处不可失败 */
if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
printk(KERN_WARNING "Warning: unable to open an initial console.\n");
/*
* 这是kernel_init(以后的init进程)打开的第一个文件,它也就成为了标准输入。
* 这里需要打开设备文件/dev/console,如果没有这个节点,系统就出错。
* 这个错误信息也是经常碰到。可能的原因是:
* 1、制作文件系统的时候忘记创建/dev/console节点
* 2、文件系统挂载问题,挂载上的文件系统不是什么都没有,就是挂错了节点。
*/
(void) sys_dup(0);
(void) sys_dup(0);
/*
* 复制两次标准输入(0)的文件描述符(它是上面打开的/dev/console,也就是系统控制台):
* 一个作为标准输出(1)
* 一个作为标准出错(2)
* 现在标准输入、标准输出、标准出错都是/dev/console了。
* 这个console在内核启动参数中可以配置为某个串口(ttySn、ttyOn等等),也可以是虚拟控制台(tty0)。
* 所以我们就在串口或者显示器上看到了之后的系统登录提示。
*/
current->signal->flags |= SIGNAL_UNKILLABLE;//设置当前进程(init)为不可以杀进程

if (ramdisk_execute_command)//如果ramdisk_execute_command有指定的init程序,就执行它
{
run_init_process(ramdisk_execute_command);//执行应用程序
printk(KERN_WARNING "Failed to execute %s\n",
ramdisk_execute_command);
}
/*
* We try each of these until one succeeds.
* 我们尝试以下的每个函数,直到函数成功执行
* The Bourne shell can be used instead of init if we are
*  如果我们试图修复一个真正有问题的设备
* trying to recover a really broken machine.
* 可以直接执行 shell 替代 init, 可以用于恢复系统
*/
/*
下面调用run_init_process()函数执行应用程序,这是一个尝试的过程, 如果 execute_command 存在,
则执行 execute_command;如果不存在,则顺序执行/sbin/init、/etc/init、 /bin/init、 /bin/sh, 
直到有一个执行成功为止。如果都不存在,那就是执行panic()函数了
*/
if (execute_command)//如果execute_command有指定的init程序,就执行它
{
run_init_process(execute_command);//该函数调用kernel_execve()执行应用程序
printk(KERN_WARNING "Failed to execute %s.  Attempting "
"defaults...\n", execute_command);
}
/*
在检查完ramdisk_execute_command和execute_command为空或者执行完对应任务的情况下,顺序执行以下初始化程序:如果都没有找到就打印错误信息。这也是我们做系统移植的时候经常碰到的错误信息,出现这个信息很有可能是:
1、你的启动参数配置有问题,通过 指定了init程序,但是没有找到,且默认的那四个程序也不在文件系统中。
2、文件系统挂载有问题,文件不存在
3、init程序没有执行权限
*/
run_init_process("/sbin/init");
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");

panic("No init found.  Try passing init= option to kernel.");

}

static void run_init_process(char *init_filename)
{
argv_init[0] = init_filename;
kernel_execve (init_filename, argv_init, envp_init);//函数成功执行目标程序时并不返回
//通过kernel_execue()调用do_execuve()执行应用程序

}

到此,内核启动分析完毕微笑


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值