文章目录
- 一、计算机上电最源头(还没操作系统)
- 二、Linux 内核初始化阶段
- 三、Linux 后续所有进程统一创建规则
- 四、加载器理解
- 五、完整全局流程串联
**一、**计算机上电最源头(还没操作系统)
1. 电脑通电,硬件固化的 BIOS/UEFI 先运行
2. BIOS/UEFI 本质就是硬件级加载器
3. 它把硬盘上的 Linux 内核镜像 读取、加载到内存
4. CPU 跳转执行内核代码,操作系统正式开始启动
二、Linux 内核初始化阶段
1. 内核自己初始化:内存管理、进程调度、驱动、文件系统、网络等
2. 内核不依靠 fork,手动在内核态创建出系统第一个用户态进程: init / systemd
3. 内核自己调用内核内部的加载器逻辑,给这个进程载入程序、环境、栈结构
4. 这个进程是所有用户进程的祖先,整个系统进程树从它开始衍生
唯一特例:第一个用户进程 是内核直接创建+加载,没有经历 fork
三、Linux 后续所有进程统一创建规则
除了第一个始祖进程,系统进程、后台服务、终端、命令、自己写的程序、子进程......
全世界所有后续进程,套路永远固定:先 fork ,再 exec
1. 第一步:调用 fork
核心作用:单纯创建一个全新的子进程
分配全新 PID
复制父进程的 PCB 进程描述符
复制/共享虚拟地址空间、页表
生成一个独立的进程结构体,操作系统调度队列里多了一个进程
⚠️ 关键:
fork 结束这一刻:
子进程只是父进程的复刻品,代码、数据、程序和父进程一模一样,还没有新程序。
只是多了一个「进程壳子」,没有换新内容。
2. 第二步:子进程立刻调用exec系列函数然后该函数内部进行execve 系统调用
(1).execve 是内核自带的一段加载器代码,它绝对不创建新进程
(2). 就在当前这个刚刚 fork 出来的子进程内部原地操作:
释放、清空自己原来从父进程复制来的:代码段、数据段、BSS、堆、共享库
从磁盘读取新的 ELF 可执行文件
解析程序头部,把代码段、数据段映射到当前进程虚拟地址空间
清零 BSS 段
重新构建用户态栈,把命令行参数、环境变量拷贝进去
找到程序入口 _start
跳转过去,开始执行新程序
fork:只负责「生孩子」,造出独立进程、PID、壳子
exec内核加载器:只负责「换脑子」,原地把这个进程里面的旧程序扔掉,重装新程序,不
生新进程
四、加载器理解
1.内核加载器(exec)负责范围
(1).销毁进程旧的整片地址空间:旧代码、数据、堆、共享库、旧ld全部清空。
(2).解析ELF文件,映射程序自身代码段、数据段、BSS段到内存。
(3). 重建全新用户栈,完成命令行参数、环境变量的拷贝与传递,完成程序代码和数据的加载
(由内核完成,和ld无关)。
(4). 区分静态、动态链接程序,动态链接场景下负责加载ld-linux.so,并把执行权转交ld。
2.动态链接:内核加载器 + ld 分工
(1). ld-linux.so 是独立用户态程序,不属于操作系统内核。
(2).ld 只负责两件事:加载所有依赖的.so动态库、完成符号解析与运行时重定位。
(3). 动态链接完整流程:
exec内核加载器布置程序主体+参数环境+拉起ld → ld完成库加载和重定位 → ld跳转至程
序 _start ,用户代码开始运行。
3.静态链接执行流程
(1). 编译阶段已将库代码和程序代码合并,无需依赖外部动态库,不需要ld参与。
(2). 内核加载器完成内存映射、栈构建、参数环境传递后,直接跳转 _start 执行用户代码。
4.程序替换的本质:
调用exec做程序替换时,会彻底清空进程原有全部内存环境,包括旧程序、数据、动态库、
旧ld,再根据新程序类型,重新搭建全新的地址空间与运行环境。
5.为什么说每个进程最开始都是加载器
(1).进程fork诞生后,在执行业务代码前,必须先走加载流程。
(2).动态链接进程:内核加载器 → ld动态链接器 → 用户代码
(3).静态链接进程:内核加载器 → 用户代码
(4).本质:进程启动最先执行的是加载逻辑,而非自身业务代码,因此概括为每个进程最开始
都是加载器。
6.三种加载器最终区别
(1).BIOS/UEFI:硬件固件级小程序,负责加载操作系统内核。
(2).内核加载器(exec内部):内核代码一部分,负责程序替换、程序主体加载、参数环境
传 递、拉起ld。
(3).ld动态链接器:独立用户态程序,只负责依赖库加载和符号重定位。
7.总结
fork负责创建新进程容器,exec内核加载器负责替换程序与搭建运行基础环境,ld只负责动
态库与重定位,所有进程启动都必先经过加载阶段。
五、完整全局流程串联
上电 → 硬件BIOS/UEFI加载器 → 加载Linux内核 →
内核初始化 → 内核直接创建第一个用户进程(无fork) →
此后所有进程:
父进程fork生成子进程容器 → 子进程exec触发内核加载器 → 原地替换为新程序运行