下面给出内核映像完整的启动过程:

arch/x86/boot/header.S:

--->header第一部分(以前的bootsector.S):  载入bootloader到0x7c00处,设置内核属性

--->_start()  bzImage映像的入口点(实模式),header的第二部分(以前的setup.S)

--->code32_start=0x100000  0x100000为解压后的内核的载入地址(1M高端地址)

--->设置大量的bootloader参数、创建栈空间、检查签名、清空BSS

--->arch/x86/boot/main.c:main()  实模式内核的主函数

--->copy_boot_params()  把位于第一个扇区的参数复制到boot_params变量中,boot_params位于setup的数据段

--->检查内存布局、设置键盘击键重复频率、查询Intel SpeedStep(IST)信息

--->设置视频控制器模式、解析命令行参数以便传递给decompressor

--->arch/x86/boot/pm.c:go_to_protected_mode()  进入保护模式

--->屏蔽PIC中的所有中断、设置GDT和IDT

--->arch/x86/boot/pmjump.S:protected_mode_jump(boot_params.hdr.code32_start,...)  跳转到保护模式

--->in_pm32()  跳转到32位保护模式的入口处(即0x100000处)

--->jmpl *%eax 跳转到arch/i386/boot/compressed/head_32.S:startup_32()处执行

arch/i386/boot/compressed/head_32.S:startup_32()  保护模式下的入口函数

--->leal boot_stack_end(%ebx), %esp  设置堆栈

--->拷贝压缩的内核到缓冲区尾部

--->清空BSS

--->compressed/misc.c:decompress_kernel()  解压内核

--->lib/decompress_bunzip2.c:decompress()

--->lib/decompress_bunzip2.c:bunzip2()

--->lib/decompress_bunzip2.c:start_bunzip()  解压动作

--->parse_elf()  将解压后的内核ELF文件(.o文件)解析到内存中

--->计算vmlinux编译时的运行地址与实际装载地址的距离

--->jmp *%ebp  跳转到解压后的内核的arch/x86/kernel/head_32.S:startup_32()处运行

arch/x86/kernel/head_32.S:startup_32()  32位内核的入口函数,即进程0(也称为清除进程)

--->拷贝boot_params以及boot_command_line

--->初始化页表:这会创建PDE和页表集

--->开启内存分页功能

--->为可选的浮点单元(FPU)检测CPU类型

--->head32.c:i386_start_kernel()

--->init/main.c:start_kernel()  Linux内核的启动函数,包含创建rootfs,加载内核模块和cpio-initrd

--->很多初始化操作

--->setup_command_line()  把内核启动参数复制到boot_command_line数组中

--->parse_early_param()  体系结构代码会先调用这个函数,做时期的参数检查

--->parse_early_options()

--->do_early_param()  检查早期的参数

--->parse_args()  解析模块的参数

--->fs/dcache.c:vfs_caches_init()  创建基于内存的rootfs(一个VFS)

--->fs/namespace.c:mnt_init()

--->fs/ramfs/inode.c:init_rootfs()

--->fs/filesystems.c:register_filesystem()  注册rootfs

--->fs/namespace.c:init_mount_tree()

--->fs/super.c:do_kern_mount()  在内核中挂载rootfs

--->fs/fs_struct.c:set_fs_root() 将rootfs配置为当前内存中的根文件系统

--->rest_init()

--->arch/x86/kernel/process.c:kernel_thread(kernel_init,...)  启动一个内核线程来运行kernel_init函数,进行内核初始化

--->cpu_idle()                            进入空闲循环

--->调度器周期性的接管控制权,提供多任务处理

init/main.c:kernel_init() 内核初始化过程入口函数,加载initramfs或cpio-initrd,或传统的image-initrd,把工作交给它

--->sys_open("/dev/console",...)  启动控制台设备

--->do_basic_setup()

--->do_initcalls()  启动所有静态编译进内核的模块

--->init/initramfs.c:populate_rootfs()  初始化rootfs

--->unpack_to_rootfs()  把initramfs或cpio-initrd解压释放到rootfs

--->如果是image-initrd则拷贝到/initrd.image

####################################### 传统的image-initrd情形 ###########################################

--->rootfs中没有/init文件

--->do_mounts.c:prepare_namespace() 加载image-initrd,并运行它的/linuxrc文件,以挂载实际的文件系统

--->do_mounts_initrd.c:initrd_load()  把image-initrd数据加载到默认设备/dev/ram0中

--->do_mounts_rd.c:rd_load_image()  加载image-initrd映像

--->identify_ramdisk_image() 识别initrd,确定是romfs、squashfs、minix,还是ext2

--->crd_load()  解压并为ramdisk分配空间,计算循环冗余校验码

--->lib/inflate.c:gunzip()  对gzip格式的ramdisk进行解压

--->do_mounts_initrd.c:handle_initrd() 指定的根设备不是/dev/ram0,由initrd来挂载真正的根文件系统

--->mount_block_root("/dev/root.old",...)  将initrd挂载到rootfs的/root下

--->arch/x86/kernel/process.c:kernel_thread(do_linuxrc, "/linuxrc",...)  启动一个内核线程来运行do_linuxrc函数

--->do_mounts_initrd.c:do_linuxrc()

--->arch/x86/kernel/sys_i386_32.c:kernel_execve() 运行image-initrd中的/linuxrc

--->将initrd移动到rootfs的/old下

--->若在linuxrc中根设备重新设成Root_RAM0,则返回,说明image-initrd直接作为最终的根文件系统

--->do_mounts.c:mount_root() 否则将真正的根文件系统挂载到rootfs的/root下,并切换到这个目录下

--->mount_block_root()

--->do_mount_root()

--->fs/namespace.c:sys_mount()  挂载到"/root"

--->卸载initrd,并释放它的内存

--->do_mounts.c:mount_root() 没有指定另外的根设备,则initrd直接作为真正的根文件系统而被挂载

--->fs/namespace.c:sys_mount(".", "/",...)  根文件挂载成功,移动到根目录"/"

########################################################################################################

--->init/main.c:init_post()  启动用户空间的init进程

--->run_init_process(ramdisk_execute_command)   若加载了initramfs或cpio-initrd,则运行它的/init

--->run_init_process("/sbin/init")  否则直接运行用户空间的/sbin/init

--->arch/x86/kernel/sys_i386_32.c:kernel_execve()  运行用户空间的/sbin/init程序,并分配pid为1

--->run_init_process("/bin/sh")  当运行init没成功时,可用此Shell来代替,以便恢复机器

/init  cpio-initrd(或initramfs)中的初始化脚本,挂载真正的根文件系统,启动用户空间的init进程

--->export PATH=/sbin:/bin:/usr/sbin:/usr/bin  设置cpio-initrd的环境变量$PATH

--->挂载procfs、sysfs

--->解析命令行参数

--->udevd --daemon --resolve-names=never  启动udev

--->/initqueue/*.sh  执行/initqueue下的脚本完成对应初始化工作(现在该目录下为空)

--->/initqueue-settled/*.sh  执行/initqueue-settled下的脚本(现在该目录下为空)

--->/mount/*.sh  挂载真正的根文件系统

--->/mount/99mount-root.sh  根据/etc/fstab中的选项挂载根文件系统

--->/lib/dracut-lib.sh  一系列通用函数

--->把根文件系统挂载到$NEWROOT下

--->寻找真正的根文件系统中的init程序并存放在$INIT中 /sbin/init, /etc/init, /bin/init, 或/bin/sh

--->从/proc/cmdline中获取启动init的参数并存放在$initargs中

--->switch_root "$NEWROOT" "$INIT" $initargs  切换到根分区,并启动其中的init进程

注意kernel_evecve调用的是与具体体系平台相关的实现,但它是一个通用的系统调用,在linux/syscalls.h中声明,这个头文件中声明了与体系结构无关的所有系统调用接口。只不过kernel_evecve在实现时是与体系结构相关的,每种体系结构都要提供它的实现。

从以上分析可以看出,如果使用新的cpio-initrd(或initramfs),kernel_init只负责内核初始化(包括加载内核模块、创建基于内存的rootfs以及加载cpio-initrd)。后续根文件系统的挂载、init进程的启动工作都交给cpio-initrd来完成。cpio-initrd相对于image-initrd承担了更多的初始化责任,这种变化也可以看作是内核代码的用户层化的一种体现,实际上精简内核代码,将部分功能移植到用户层必然是linux内核发展的一个趋势。如果是使用传统的image-initrd的话,根文件系统的挂载也会放在kernel_init()中,其中prepare_namespace完成挂载根文件系统,init_post()完成运行/sbin/init,显然这样内核的代码不够精简。

5、init进程

init是第一个调用的使用标准C库编译的程序。在此之前,还没有执行任何标准的C应用程序。在桌面Linux系统上,第一个启动的程序通常是/sbin/init,它的进程号为1。init进程是所有进程的发起者和控制者,它有两个作用:

(1)扮演终结父进程的角色:所有的孤儿进程都会被init进程接管。

(2)系统初始化工作:如设置键盘、字体,装载模块,设置网络等。

在完成系统初始化工作之后,init进程将在控制台上运行getty(登录程序)等任务,我们熟悉的登录界面就出现了!

init程序的运行流程需要分专门的一节来讨论,因为它有不同的实现方式。传统的实现是基于UNIX System V init进程的,程序包为sysvinit(以前的RedHat/Fedora用的就是这个)。目前已经有多种sysvinit的替代产品了,这其中包括initng,它已经可以用于Debian了,并且在Ubuntu上也能工作。在同一位置上,Solaris使用SMF(Service Management Facility),而Mac OS则使用 launchd。现在广泛使用的是upstart init初始化进程,目前在Ubuntu和Fedora,还有其他系统中已经取代了sysvinit。

传统的Sysvinit daemon是一个基于运行级别的初始化程序,它使用了运行级别(如单用户、多用户等)并通过从/etc/rcX.d目录到/etc/init.d目录的初始化脚本的链接来启动与终止系统服务。Sysvinit无法很好地处理现代硬件,如热插拔设备、USB硬盘、网络文件系统等。upstart系统则是事件驱动的,事件可能被硬件改动触发,也可被启动或关机或任务所触发,或者也可能被系统上的任何其他进程所触发。事件用于触发任务或服务,统称为作业。比如连接到一个USB驱动器可能导致udev服务发送一个block-device-added事件,这可能引起一个预定任务检查/etc/fstab和挂载驱动器(如果需要的话)。再如,一个Apache web服务器可能只有当网络和所需的文件系统都可用时才能启动。

Upstart作业在/etc/init目录及其子目录下被定义。upstart系统兼容sysvinit,它也会处理/etc/inittab和System V init脚本(如果有的话)。在诸如近来的Fedora版本的系统上,/etc/inittab可能只含有initdefault操作的id项。目前Ubuntu系统默认没有/etc/inittab,如果您想要指定一个默认运行级别的话,您可以创建一个。Upstart也使用initctl命令来支持与upstart init守护进程的交互。这时您可以启动或终止作业、列表作业、以及获取作业的状态、发出事件、重启init进程,等等。

总的来说,x86架构的Linux内核启动过程分为6大步,分别为:

(1)实模式的入口函数_start():在header.S中,这里会进入众所周知的main函数,它拷贝bootloader的各个参数,执行基本硬件设置,解析命令行参数。

(2)保护模式的入口函数startup_32():在compressed/header_32.S中,这里会解压bzImage内核映像,加载vmlinux内核文件。

(3)内核入口函数startup_32():在kernel/header_32.S中,这就是所谓的进程0,它会进入体系结构无关的start_kernel()函数,即众所周知的Linux内核启动函数。start_kernel()会做大量的内核初始化操作,解析内核启动的命令行参数,并启动一个内核线程来完成内核模块初始化的过程,然后进入空闲循环。

(4)内核模块初始化的入口函数kernel_init():在init/main.c中,这里会启动内核模块、创建基于内存的rootfs、加载initramfs文件或cpio-initrd,并启动一个内核线程来运行其中的/init脚本,完成真正根文件系统的挂载。

(5)根文件系统挂载脚本/init:这里会挂载根文件系统、运行/sbin/init,从而启动众所周知的进程1。

(6)init进程的系统初始化过程:执行相关脚本,以完成系统初始化,如设置键盘、字体,装载模块,设置网络等,最后运行登录程序,出现登录界面。

如果从体系结构无关的视角来看,start_kernel()可以看作时体系结构无关的Linux main函数,它是体系结构无关的代码的统一入口函数,这也是为什么文件会命名为init/main.c的原因。这个main.c粘合剂把各种体系结构的代码“粘合”到一个统一的入口处。

整个内核启动过程如下图:

图1 Linux内核启动过程

linux内核中启动页面,Linux内核启动过程分析相关推荐

  1. mint linux更新内核,如何在Ubuntu/Linux Mint中安装最新Linux 5.2.5内核

    原标题:如何在Ubuntu/Linux Mint中安装最新Linux 5.2.5内核 Linux 5.2的Ubuntu主线内核包最终可以在32位和64位操作系统中下载和安装. 由于构建失败,Linux ...

  2. 在linux环境中利用efibootmgr管理efi启动项[添加、删除、改变顺序]

    在linux环境中利用efibootmgr管理efi启动项[添加.删除.改变顺序] 本例可以解决uefi双系统中启动顺序的问题,首先确保自己的机器为uefi固件 在Terminel中输入efiboot ...

  3. linux内核中的jiffies,Linux内核中的jiffies及其作用介绍及jiffies等相关函数详解

    在LINUX的时钟中断中涉及至二个全局变量一个是xtime,它是timeval数据结构变量,另一个则是jiffies,首先看timeval结构 struct timeval { time_t tv_s ...

  4. 【Linux 内核】进程管理 ( 进程特殊形式 | 内核线程 | 用户线程 | C 标准库与 Linux 内核中进程相关概念 | Linux 查看进程命令及输出字段解析 )

    文章目录 一.进程特殊形式 ( 内核线程 | 用户线程 ) 二.C 标准库与 Linux 内核中进程相关概念 三.Linux 查看进程命令及输出字段解析 一.进程特殊形式 ( 内核线程 | 用户线程 ...

  5. firefox linux脚本启动,在Linux终端中使用后台运行模式启动程序的方法

    这是一个篇幅不长但是十分有用的教程,可以帮助你在终端启动一个Linux应用程序,并且使终端窗口不会丢失焦点. 我们有很多可以在Linux系统中打开一个终端窗口的方法,这取决于你的选择以及你的桌面环境. ...

  6. linux efi启动,在linux环境中利用efibootmgr管理efi启动项

    UEFI用来替代传统BIOS引导操作系统,学会修改UEFI启动项也变得十分重要,UEFI全称为:"统一的可扩展固件接口"(Unified Extensible Firmware I ...

  7. linux命令中选项分为,Linux 考试试题

    Linux 考试试题 一.选择题 (每小题2分,共50分) 1.在创建Linux分区时,一定要创建( D )两个分区 A. FAT/NTFS B. FAT/SWAP C. NTFS/SWAP D.SW ...

  8. linux系统中文件编程,Linux当中的文件系统

    1. 设备专用文件(设备文件) 设备专用文件与系统的某个设备相对应.在内核中,每种设备类型都有阈值向对应的设备驱动程序,用来处理设备的所有I/O请求.可以将设备划分为字符设备和块设备两种. 每个设备文 ...

  9. linux系统中mywho命令,linux查看在线用户 who命令参数及用法

    linux who 命令 详解 Linux最常用命令之一 功能说明:显示目前登入系统的用户信息. 语 法:who [-Himqsw][--help][--version][am i][记录文件] 补充 ...

最新文章

  1. html 的基本结构、标签(分类、关系)、文档类型、页面语言、字符集、语义化
  2. 设计模式模式游客(Visitor)摘录
  3. docker应用到生产环境的前提
  4. java归还线程_再谈java线程
  5. Spring原始注解开发-02
  6. 如何创建一个数据科学项目? 1
  7. BZOJ1856[Scoi2010]字符串——组合数学+容斥
  8. JS_理解函数参数按值传递
  9. MySQL 8.0.22 源码编译安装全过程
  10. powershell一行代码批量修改文件名(附命令详解)
  11. 2019ug最新版本是多少_宝塔Linux面板7.4.2版本/Windows面板6.8版本请尽快升级到最新版本...
  12. Linux下autoconf与automake
  13. CMMI3 和 CMMI 4
  14. 苹果计算机怎么显示汉字,苹果的safari浏览器怎样设定成中文显示
  15. 2022年:企业绩效管理蓝图
  16. 诺基亚N81手机宝典!由浅入深玩转手机
  17. 共享计算机后无法访问磁盘,Win10系统下无法访问共享硬盘怎么办?
  18. 爬虫之-bilibili视频下载-下载链接获取
  19. python 方法加强@ pytho中@ python@
  20. laravel 多语言切换

热门文章

  1. 领扣(LeetCode)最长公共前缀 个人题解
  2. 第二阶段个人冲刺03
  3. 计算机网络总结之计算机概述
  4. Memcached安装和基本使用
  5. 定义系统消息 Specify system messages
  6. 实现IHttpModule接口获取Session来实现页面访问日志功能。
  7. [导入]连连看.NET 1.41全部源码
  8. 英特尔AIDC秀肌肉:展示AI软硬件+生态全景图
  9. getchar()到底怎么用_脱霉剂到底该怎么用?
  10. raptor五个数排序流程图_数据结构与算法(一):排序(上)