我们首先是作为一个老板,将内存从实模式转换为了保护模式,有了更强的寻址能力
我们需要进行正式的公司搭建了,也就是内核的启动
内核的启动从start_kernel入口,在上文启动的init/main.c文件中,其中start_kernel相当于main函数,其中有着各种初始化函数XXXX_init
我们接下来说一下这些init
1.首先是项目管理部门,项目管理初始化的时候,需要有一个样本,也就是之前说的,我们根据一个父子进程进行拷贝启动项目,这个样本进程,就是系统创建的第一个进程,称为0号进程,利用指令
set_task_stack_end_magic(&init_task)
2.进行办事大厅的初始化,我们的办事大厅的函数就是trap_init(),设置了很多的中断门,处理各种中断事件,其中包含系统调用需要的中断门事件,系统调用也是通过发送中断进行的
3.会议室管理系统,内存模块初始化,利用函数 mm_init(),项目需要项目管理进行调度,需要对不同的项目进行调度,利用sched_init()进行的调度管理
vfs_caches_init()用于初始化内存文件系统 rootfs,其中调用 mnt_init() -> init_rootfs(),里面有一行代码,register_filesystem(&rootfs_fs_type),在VFS中注册了一种类型,定义为struct file_system_type rootfs_fs_type
文件系统是项目资料库,我们将文件系统的实际实现和上层操作解耦,形成一个抽象的接口层,就是VFS Virutal File System,虚拟文件系统
我们的内存文件系统加载为VFS
4.项目调度器的初始化
5.其他初始化 rest_init
rest_init会进行调用kernel_thread(kernel_init,NULL,CLONE_FS)创建第二个进程,也就是1号进程
1号进程负责运行用户进程,这个用户进程是创建其他用户进程的样本,相当于一个大徒弟,有了第一个,就有第二个,直到形成一个进程树
有了这个用户进程树,就需要考虑用户进程的执行调度
而且由于Unix提供了分层的机制,将系统资源分为了核心资源和非核心资源,整体分为了四个Ring,越往里权限越高,越往外权限越低
操作系统将关键资源放在Ring0,即为内核态,普通用户代码放在Ring3,用户态
从用户态访问内核态,需要找办事大厅,进行统一的入口,用户态进行请求处理,办事大厅就是内核态,用户态就好比调用一个接口,不关心内部真正的实现
比如一个网络发包的操作
就需要调用对应的内核代码,在内核队列中排队发送,发送完成,返回用户态,让暂停的程序接着运行
在调用期间呢?用户态的程序需要停下来,暂停在寄存器中的执行,这一步呢?需要我们将用户态的程序保存起来,保存这一个状态,等待调用完成后的运行
暂停的时候,将CPU的寄存器状态暂存,方便进程管理系统获取,基本如下图
整体的过程就是用户态 – 系统调用 -保存寄存器 – 内核态执行 – 恢复寄存器 -返回用户态
那么继续,我们看一下从内核态到用户态的过程
我们1号进程启动的时候,需要从内核态跨越到用户态运行一个程序,如何做到的呢?
整体如下,kernel_thread是1号进程启动的时候调用的,内部有一个函数kernel_init
进程会运行这个函数,内部会调用对应的do_execve函数,execve是一个系统调用
尝试运行一个可执行文件
内部会尝试调用ramdisk的init,以及普通文件系统上的 /sbin/init etc/init /bin/init /bin/sh
不同Linux版本运行不同的位置
如何利用init执行文件的机会,进行内核到用户的切换呢?
系统调用的过程中,我们会经历 用户态 – 系统调用 -保存寄存器 – 内核态执行系统调用 – 恢复寄存器 – 返回用户态
然后do_execve对应的就是内核态执行系统调用
然后按照调用栈,我们会调用如下的内容
do_execve->do_execveat_common->exec_binprm->search_binary_handler
在最后的search_binary_handler的中间,我们需要加载一个二进制文件,也就是项目执行计划书,内部有一定的格式,就是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, }; |
最后加载完成这些二进制文件,就可以掉用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); |
内部传入了一个register,寄存器,其结构就是保存在用户态执行的寄存器状态
最后的iret,从系统调用中返回时,恢复寄存器,将保存的寄存器从里面拿出来,继续执行用户态进程
init到了用户态的1号进程完成,然后进行用户态相关的init,首先启动用户态的ramdisk的init,然后启动真正的根文件系统的init,
ramdisk是一个基于内存的文件系统,因为文件系统是在存储设备上的,操作系统访问必须要驱动,所以我们在一开始,先访问不需要驱动的ramdisk,作为根系统,等到加载了驱动,有了真正的根系统,ramdisk的init就会启动/init就会启动文件系统的init
rest_init完成了1号进程的启动
然后是内核态进程的管理,也就是2号进程
kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES)创建2号进程
这里面使用了kernel_thread函数创建进程,thread可以翻译为线程,这是因为,在创建进程是启动一个项目,但是一个项目有一个管理人,这个线程就是主线程,不过下面有多个人去执行,执行的不同部分叫做多线程
kthreadd之后负责所有内核态线程的调度和管理,是所有内核态线程的祖先
本次的内核初始化如下
6中别忘了,还有ramdisk和根文件系统的init