内核的启动从入口函数start_kernel()(在init/main.c文件中,start_kernel相当于内核的main函数)开始,打开这个函数,里面各种初始化函数
初始化公司职能部分
项目管理部门
操作系统里首先有个创始进程,通过
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,虚拟文件系统
最后start_kernel()调用的是rest_init(),用来做其他方面的初始化
初始化1号进程
rest_init的第一大工作就是通过kernel_thread(kernel_init, NULL, CLONE_FS) 创建1号进程。因为它将运行一个用户进程。
有了其他人,老板就要对资源做一些区分,哪些是核心资源,哪些是非核心资源
x86分层的权限机制,把区域分成了四个Ring,越往里权限越高,越往外权限越低,
操作系统将能够访问的关键资源的代码放在Ring0,我们称为内核态,将普通的程序代码放在Ring3,我们称为用户态。
如果用户态要访问核心资源,可以通过系统调用,过程如下
用户态-系统调用-保存寄存器- 内核态执行系统调用-恢复寄存器-返回用户态
内核态到用户态
当执行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负责所有内核态的线程的调度和管理,是内核态所有线程运行的祖先。
总结
- 各个职能部门的创建
- 用户态祖先进程的创建
- 内核态祖先进程的创建