一步一步学linux操作系统: 05 内核初始化

内核初始化中几个重要的 init

内核下载 http://ftp.sjtu.edu.cn/sites/ftp.kernel.org/pub/linux/kernel/v4.x/

内核的启动从入口函数 start_kernel() 开始,在 init/main.c 中
在这里插入图片描述
start_kernel 相当于内核的 main 函数,里面是各种各样初始化函数 XXXX_init。
图片来自极客时间趣谈linux操作系统

1、0号进程创建

set_task_stack_end_magic(&init_task)函数 ,设置操作系统的第一个进程init。系统创建的第一个进程,唯一一个没有通过 fork 或者 kernel_thread 产生的进程,是进程列表的第一个。内核态下执行的0号进程,0号进程是所有进程的祖先。

init_task变量,从init/init_task.c中初始化的,它的定义是 struct task_struct init_task = INIT_TASK(init_task)。
在这里插入图片描述

2、 初始化中断

trap_init()里面设置了很多中断门(Interrupt Gate),用于处理各种中断。

在这里插入图片描述
其中有一个 set_system_intr_gate(IA32_SYSCALL_VECTOR, entry_INT80_32),这是32位系统的系统调用中断门,64 位的有另外的系统调用方法。

3、初始化内存管理模块

mm_init() 用来初始化内存管理模块

在这里插入图片描述

4、初始化调度模块

sched_init() 用于初始化调度模块

在这里插入图片描述

5、初始化基于内存的文件系统

vfs_caches_init() 初始化基于内存的文件系统 rootfs

在这里插入图片描述
在这里插入图片描述
其中 mnt_init()->init_rootfs()
在这里插入图片描述
会调用 register_filesystem(&rootfs_fs_type); 在 VFS 虚拟文件系统里面注册了一种类型,定义为 struct file_system_type rootfs_fs_type。VFS(Virtual File System),虚拟文件系统,是一个抽象层对上提供统一的接口,来支持兼容各种各样的文件系统(抽象 文件的相关数据结构和操作)

在这里插入图片描述

6、其他方面的初始化

rest_init(),用来做其他方面的初始化

在这里插入图片描述

初始化 1 号进程

rest_init 的第一大工作是,用 kernel_thread(kernel_init, NULL, CLONE_FS) 创建第二个进程,这个是 1 号进程,用户进程。

在这里插入图片描述

x86 的分层权限机制

图片来自极客时间趣谈linux操作系统
分成了四个 Ring,越往里权限越高,越往外权限越低。能够访问关键资源的代码放在Ring0,称为内核态(Kernel Mode);普通的程序代码放在 Ring3,称为用户态(User Mode)

用户态的代码想要访问核心资源,通过系统调用运行内核中的相关代码。这就需要进行用户态和内核态的切换,就需要把用户态程序运行到一半的情况保存下来。当系统调用完毕,返回的时候将保存的值恢复回去,就能接着运行了。

图片来自极客时间趣谈linux操作系统
流程:用户态 - 系统调用 - 保存寄存器 - 内核态执行系统调用 - 恢复寄存器 - 返回用户态,然后接着运行。
图片来自极客时间趣谈linux操作系统

从内核态到用户态,1 号进程的启动

当前执行 kernel_thread 这个函数的时候,还在内核态,如何在“先内核态再用户态”情况下到用户态去运行一个程序?

函数 pid = kernel_thread(kernel_init, NULL, CLONE_FS); 参数是一个函数 kernel_init,

在这里插入图片描述

其中的 kernel_init_freeable函数有如图代码

在这里插入图片描述
回到 kernel_init 中

	if (ramdisk_execute_command) {
		ret = run_init_process(ramdisk_execute_command);
		if (!ret)
			return 0;
		pr_err("Failed to execute %s (error %d)\n",
		       ramdisk_execute_command, ret);
	}
	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;

进一步看函数 try_to_run_init_process 函数run_init_process,调用的是 do_execve
在这里插入图片描述
execve 是一个系统调用,它的作用是运行一个执行文件。加一个 do_ 的往往是内核系统调用的实现。说明1 号进程运行的是一个文件。

do_execve,它会尝试运行** ramdisk 的“/init”**,或者普通文件系统上的“/sbin/init”“/etc/init”“/bin/init”“/bin/sh”。不同版本的 Linux 会选择不同的文件启动,但是只要有一个起来了就可以。

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

系统调用的过程,“用户态 - 系统调用 - 保存寄存器 - 内核态执行系统调用 - 恢复寄存器 - 返回用户态”
刚才运行 init,是调用 do_execve,正是上面的过程的后半部分,从内核态执行系统调用开始。

do_execve->do_execveat_common->exec_binprm->search_binary_handler,这里面会调用这段内容:
在这里插入图片描述
要运行一个程序,需要加载这个程序的二进制文件

二进制文件,Linux 下一个常用的格式是 ELF(Executable and Linkable Format,可执行与可链接格式)。就有了下面这个定义:


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,看名字里的 register,是寄存器。这个结构就是在系统调用的时候,内核中保存用户态运行上下文的,里面将用户态的代码段 CS 设置为 __USER_CS,将用户态的数据段 DS 设置为 __USER_DS,以及指令指针寄存器 IP、栈指针寄存器 SP。这里相当于补上了原来系统调用里,保存寄存器的一个步骤。

iret用于从系统调用中返回,这个时候会恢复寄存器,从进入系统调用的时候,保存的寄存器里面拿出,也就是上面函数补上的寄存器。

CS 和指令指针寄存器 IP 恢复了,指向用户态下一个要执行的语句。DS 和函数栈指针 SP 也被恢复了,指向用户态函数栈的栈顶。所以,下一条指令,就从用户态开始运行了。

ramdisk 的作用

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

基于内存的文件系统,内存访问是不需要驱动的,这个就是 ramdisk。这个时候,ramdisk 是根文件系统。没有真正的根文件系统了,使用基于内存的文件系统,开始运行 ramdisk 上的 /init。等它运行完了就已经在用户态了。/init 这个程序会先根据存储系统的类型加载驱动,有了驱动就可以设置真正的根文件系统了。有了真正的根文件系统,ramdisk 上的 /init 会启动文件系统上的 init。

创建 2 号进程

rest_init 第二大事情就是第三个进程,就是 2 号进程
在这里插入图片描述

通过 kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES)创建 2号进程(工作在内核态)

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

int kthreadd(void *unused)
{
	struct task_struct *tsk = current;

	/* Setup a clean context for our children to inherit. */
	set_task_comm(tsk, "kthreadd");
	ignore_signals(tsk);
	set_cpus_allowed_ptr(tsk, cpu_all_mask);
	set_mems_allowed(node_states[N_MEMORY]);

	current->flags |= PF_NOFREEZE;
	cgroup_init_kthreadd();

	for (;;) {
		set_current_state(TASK_INTERRUPTIBLE);
		if (list_empty(&kthread_create_list))
			schedule();
		__set_current_state(TASK_RUNNING);

		spin_lock(&kthread_create_lock);
		while (!list_empty(&kthread_create_list)) {
			struct kthread_create_info *create;

			create = list_entry(kthread_create_list.next,
					    struct kthread_create_info, list);
			list_del_init(&create->list);
			spin_unlock(&kthread_create_lock);

			create_kthread(create);

			spin_lock(&kthread_create_lock);
		}
		spin_unlock(&kthread_create_lock);
	}

	return 0;
}

参考资料:

趣谈Linux操作系统(极客时间)链接:
http://gk.link/a/10iXZ
欢迎大家来一起交流学习

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

墨1024

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值