zoukankan      html  css  js  c++  java
  • 趣谈linux操作系统笔记-内核初始化

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

    第一步:在操作系统里面,先要有个创始进程,有一行指令 set_task_stack_end_magic(&init_task)。这里
    面有一个参数 init_task,它的定义是 struct task_struct init_task = INIT_TASK(init_task)。它是系
    统创建的第一个进程,我们称为0 号进程。这是唯一一个没有通过 fork 或者 kernel_thread 产生
    的进程,是进程列表的第一个

    第二步:对应的函数是 trap_init(),里面设置了很多中断门(Interrupt Gate),用于处理各种中
    断。其中有一个 set_system_intr_gate(IA32_SYSCALL_VECTOR, entry_INT80_32),这是系统调用
    的中断门。系统调用也是通过发送中断的方式进行的

    第三步:初始化的会议室管理系统。对应的,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(Virtual FileSystem),虚拟文件系统。

    第五步:start_kernel() 调用的是 rest_init(),用来做其他方面的初始化,

    在其他初始化中,主要是创建1号进程和2号进程

    初始化 1 号进程

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

    pid=1 :init进程,系统启动的第一个用户级进程,是所有其它进程的父进程,引导用户空间服务。
    pid为 1 的一定是init进程.
    它是内核运行后的第一个进程.

    内核态和用户态

    将能够访问关键资源的代码放在 Ring0,我们称为内核态(Kernel Mode);将普通的程序代码放在 Ring3,我们称为用户态(User Mode)

    用户态的代码想要访问核心资源 ?

    请求系统调用

    比如当一个用户态的程序运行到一半,要访问一个核心资源,例如访问网卡发一个网络包,就需要暂停当前的运行,调用系统调用,接下来就轮到内核中的代码运行了。

    首先,内核将从系统调用传过来的包,在网卡上排队,轮到的时候就发送。发送完了,系统调用就结束了,返回用户态,让暂停运行的程序接着运行

    这个暂停怎么实现呢?其实就是把程序运行到一半的情况保存下来。例如,我们知道,内存是用来保存程运行时候的中间结果的,现在要暂时停下来,这些中间结果不能丢,因为再次运行的时候,还要基于这些中间结果接着来。另外就是,当前运行到代码的哪一行了,当前的栈在哪里,这些都是在寄存器里面的。

    所以,暂停的那一刻,要把当时 CPU 的寄存器的值全部暂存到一个地方,这个地方可以放在进程管理系统很容易获取的地方。在后面讨论进程管理数据结构的时候,我们还会详细讲。当系统调用完毕,返回的时候,再从这个地方将寄存器的值恢复回去,就能接着运行了

    这个过程就是这样的:用户态 - 系统调用 - 保存寄存器 - 内核态执行系统调用 - 恢复寄存器 - 返回
    用户态,然后接着运行

    从内核态到用户态

    1,调用kernel_thread ,kernel_thread 的参数是一个函数kernel_init ,在 kernel_init里面,会调用 kernel_init_freeable(), 然后调用run_init_process 函数,会发现它调用的是 do_execve。

    其中kernel_init_freeable()会去判断是否存在ramdisk_execute_command ,如果ramdisk_execute_command变量指定了要运行的程序,启动它。

    ramdisk_execute_command的取值分为三种情况:

    a.如果命令行参数中指定了“rdinit=...”,则ramdisk_execute_command等于这个参数指定的程序。

    b.否则,如果/init程序存在,ramdisk_execute_command就等于/init

    c.否则,ramdisk_execute_command为空

    execve 是一个系统调用,它的作用是运行一个执行文件。加一个 do_ 的往往是内核系统调用的实现。没错,这就是一个系统调用,它会尝试运行 ramdisk 的“/init”,或者普通文件系的“/sbin/init”“/etc/init”“/bin/init”“/bin/sh”。不同版本的 Linux 会选择不同的文件启动

    后续执行do_execve->do_execveat_common->exec_binprm->search_binary_handler

    ramdisk 的作用
    init 终于从内核到用户态了。一开始到用户态的是 ramdisk 的 init,后来会启动真正根文件系统上的 init,成为所有用户态进程的祖先。
    为什么会有 ramdisk 这个东西呢?还记得上一节咱们内核启动的时候,配置过这个参数:

    init 程序是在文件系统上的,文件系统一定是在一个存储设备上的,例如硬盘。Linux 访问存储设备,要有驱动才能访问。如果存储系统数目很有限,那驱动可以直接放到内核里面,反正前面我们加载过内核到内存里了,现在可以直接对存储系统进行访问。但是存储系统越来越多了,如果所有市面上的存储系统的驱动都默认放进内核,内核就太大了。为了解决这个问题,只好先弄一个基于内存的文件系统。内存访问是不需要驱动的,这个就是ramdisk。这个时候,ramdisk 是根文件系统。然后,我们开始运行 ramdisk 上的 /init。等它运行完了就已经在用户态了。/init 这个程序会先根据存储系统的类型加载驱动,有了驱动就可以设置真正的根文件系统了。有了真正的根文件系统,ramdisk 上的 /init 会启动文件系统上的 init。
    接下来就是各种系统的初始化。启动系统的服务,启动控制台,用户就可以登录进来了。

    2号进程

    rest_init第二大事情就是第三个进程,就是 2 号进程。进程。这里需要指出一点,函数名 thread 可以翻译成“线程”,这也是操作系统很重要的一个概念。它和进程有什么区别呢?为什么这里创建的是进程,函数名却是线程呢?从用户态来看,创建进程其实就是立项,也就是启动一个项目。这个项目包含很多资源,例如会议室、资料库等。这些东西都属于这个项目,但是这个项目需要人去执行。有多个人并行执行不同的部分,这就叫多线程(Multithreading)。如果只有一个人,那它就是这个项目的主线程。但是从内核态来看,无论是进程,还是线程,我们都可以统称为任(Task)都使用相同的数据结构,平放在同一个链表中。这些在进程的那一章节,我会更加详细地讲。这里的kthreadd,负责所有内核态的线程的调度和管理,是内核态所有线程运行的祖先

  • 相关阅读:
    Java中异常的捕获与处理
    vue动态绑定class的最常用几种方式:
    JS常用验证正则表达式
    JAVA面试—JDBC
    spring技术的通俗理解
    @RequestMapping 原理(程序如何找到请求的方法的?)
    Java定时任务的几种实现
    什么是分布式系统?
    Java架构师学习路线
    spring boot和SSM开发中有什么区别?
  • 原文地址:https://www.cnblogs.com/mrwuzs/p/11275623.html
Copyright © 2011-2022 走看看