Linux内核初始化

内核的启动从入口函数start_kernel()(在init/main.c文件中,start_kernel相当于内核的main函数)开始,打开这个函数,里面各种初始化函数

Linux内核初始化

初始化公司职能部分

项目管理部门

操作系统里首先有个创始进程,通过

set_task_stack_end_magic(&init_task)

init_task 定义如下

struct task_struct init_task = INIT_TASK(init_task)

它是系统创建的第一个进程0号进程,这个进程是唯一一个没有通过fork或者kernel_thread产生的进程,是进程列表的第一个。

项目管理工具即进程列表,里面列着我们所有接的项目

办事大厅

对应的函数是trap_init(),里面设置了很多中断门,用于处理各种中断。

系统调用也是通过发送中断的方式进行的。

会议室管理系统

  • mm_init用来初始化内存管理模块
  • sched_init用来初始化调度模块,执行一定的调度策略
  • vfs_cache_init用来初始化基于内存的文件系统rootfs,函数里会调用mnt_init()-init_rootfs(),通过register_filesystem(&rootfs_fs_type)在VFS虚拟文件系统里注册一种类型
  • 为了兼容各种文件系统,将文件的相关数据结构和操作抽象出来,形成一个抽象层对上提供统一的接口,抽象层就是VFS,虚拟文件系统

Linux内核初始化
最后start_kernel()调用的是rest_init(),用来做其他方面的初始化

初始化1号进程

rest_init的第一大工作就是通过kernel_thread(kernel_init, NULL, CLONE_FS) 创建1号进程。因为它将运行一个用户进程。

有了其他人,老板就要对资源做一些区分,哪些是核心资源,哪些是非核心资源

x86分层的权限机制,把区域分成了四个Ring,越往里权限越高,越往外权限越低,
Linux内核初始化
操作系统将能够访问的关键资源的代码放在Ring0,我们称为内核态,将普通的程序代码放在Ring3,我们称为用户态。

如果用户态要访问核心资源,可以通过系统调用,过程如下

Linux内核初始化

用户态-系统调用-保存寄存器- 内核态执行系统调用-恢复寄存器-返回用户态
Linux内核初始化

内核态到用户态

当执行kernel_thread这个函数的时候,还在内核态,现在需要到用户态去执行一个程序。

kernel_thread里的参数是一个函数kernel_init,也就是这个进程会运行这个函数,函数里面会调用kernel_init_freeable(), 里面代码如下

if (!ramdisk_execute_command)
		ramdisk_execute_command = "/init";

kernel_init里面代码块

if (ramdisk_execute_command) {
		ret = run_init_process(ramdisk_execute_command);
......
	}
......
	if (!try_to_run_init_process("/sbin/init") ||
	    !try_to_run_init_process("/etc/init") ||
	    !try_to_run_init_process("/bin/init") ||
	    !try_to_run_init_process("/bin/sh"))
		return 0;
  • run_init_process 里调用的是do_execve,是内核系统调用的是实现
static int run_init_process(const char *init_filename)
{
	argv_init[0] = init_filename;
	return do_execve(getname_kernel(init_filename),
		(const char __user *const __user *)argv_init,
		(const char __user *const __user *)envp_init);
}
  • 1号进程运行的是个文件,尝试运行ramdisk的/init、/sbin/init、/etc/init、/bin/init、/bin/sh

如何利用执行init的机会,从内核态回到用户态呢?

从系统调用的过程(用户态-系统调用-保存寄存器-内核态执行系统调用-恢复寄存器-返回用户态)中得到启发,运行init,是调用do_execve是从内核态执行系统调用开始。

do_execve->do_execveat_common->exec_binprm->search_binary_handler,会调用如下内容

int search_binary_handler(struct linux_binprm *bprm)
{
  ......
  struct linux_binfmt *fmt;
  ......
  retval = fmt->load_binary(bprm);
  ......
}

要运行一个程序,需要加载这个二进制文件,是有一定格式的, Linux的格式是ELF(可执行与可连接格式)

static struct linux_binfmt elf_format = {
.module	= THIS_MODULE,
.load_binary	= load_elf_binary,
.load_shlib	= load_elf_library,
.core_dump	= elf_core_dump,
.min_coredump	= ELF_EXEC_PAGESIZE,
};

先调用load_elf_binary,最后调用start_thread。

void start_thread(struct pt_regs *regs, unsigned long new_ip, unsigned long new_sp)
{
set_user_gs(regs, 0);
regs->fs	= 0;
regs->ds	= __USER_DS;
regs->es	= __USER_DS;
regs->ss	= __USER_DS;
regs->cs	= __USER_CS;
regs->ip	= new_ip;
regs->sp	= new_sp;
regs->flags	= X86_EFLAGS_IF;
force_iret();
}
EXPORT_SYMBOL_GPL(start_thread);

struct pt_regs,是寄存器,这个结构就是在系统调用的时候,内核中保存用户运行上下文的,里面将用户态的代码段CS设置为_USER_CS,将用户态的数据段DS设置为_USER_DS,以及指令指针寄存器IP、栈指针寄存器SP。

iret 是用于从系统调用中返回,上面的设置好了之后,下一条指令,就从用户态开始运行了。

ramdisk的作用

init终于从内核到用户态了。一开始到用户态的是ramdisk的init,后来会启动真正根文件系统上的init,称为所有用户态进程的祖先

内核启动的时候,配置过这个参数

initrd16 /boot/initramfs-3.10.0-862.el7.x86_64.img

是一个基于内存的文件系统,内核访问不需要驱动,这个就是ramdisk,是根文件系统

运行ramdisk上的/init,运行完了就已经在用户态了,/init会先根据存储系统的类型加载驱动,有了驱动就可以设置真正的根文件系统了。

有了真正的根文件系统,ramdisk上的/init会启动文件系统上的init。

接下来就是各种系统的初始化了,启动系统的服务,启动控制台,用户就可以登录进来了。

创建2号进程

rest_init的第二件大事情就是创建2号进程

kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES) 创建进程

为什么名字是thread呢?

  • 从用户态来看,创建进程就是立项,启动一个项目,包含很多资源,会议室、资料库等,多个人并行执行不同的部分,就叫做多线程,如果只有一个人,那它就是项目的主线程
  • 从内核态来看,无论是进程还是线程,统称为任务,使用相同的数据结构,平放在一个链表中

kthreadd负责所有内核态的线程的调度和管理,是内核态所有线程运行的祖先。

总结

  • 各个职能部门的创建
  • 用户态祖先进程的创建
  • 内核态祖先进程的创建

Linux内核初始化

上一篇:深入理解系统调用


下一篇:xenomai内核解析之双核系统调用(一)