我们首先是作为一个老板,将内存从实模式转换为了保护模式,有了更强的寻址能力

我们需要进行正式的公司搭建了,也就是内核的启动

内核的启动从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

发表评论

邮箱地址不会被公开。 必填项已用*标注