适合零基础入门,也可用于期末、考研复盘,全文整合实操代码、命令实验与底层原理,知识点完整无遗漏。
前言
当初学操作系统线程和虚拟内存分页时,很容易混淆进程与线程、搞不懂 32 位地址分段、CR3 寄存器作用这些高频考点。本文结合代码实操、服务器命令验证,把两大块内容完整梳理,逻辑循序渐进,没有晦涩的跳跃知识点,同时预留截图插入位置,方便自己复习查阅。
第一部分 Linux 线程:轻量级进程 LWP
1 进程、线程基础概念
1.1 什么是进程
我们把正在运行的程序称作进程,它由内核维护的管理结构、程序代码、全局数据共同构成,操作系统是以进程为单位分配硬件资源。 当系统创建新进程时,会一次性分配全套独立资源:专属虚拟内存空间、独立页目录、私有的文件描述符表、单独的信号处理规则,还有专属工作目录与权限掩码。 进程切换的成本很高,切换时需要替换页目录,刷新 CPU 内 TLB 缓存,消耗不少算力。
进程是运行起来的程序,内核数据结构+代码和数据=进程—>承担分配相同资源的的基本实体
1.2 什么是线程
线程是进程内部独立的执行分支,一个进程最少有一条主线程,也能创建多条子线程。平时直接运行的程序,本质就是单线程进程。 这里是 Linux 和其他系统最大的区别:内核没有专门存放线程信息的 TCB 结构体,Linux 里线程就是轻量级进程 LWP。不管是进程还是线程,内核都用task_struct统一管理,CPU 调度程序不会区分二者,只依靠 LWP 识别不同执行流。
线程是进程内部的执行分支
必须学习具体的操作通信的线程实现

线程资源分为共享、私有两类
1.3 进程与线程简单对比
| 资源隔离 | 全部资源独立 | 绝大多数资源共享 |
| 切换时修改 CR3 | 需要修改 | 不用修改 |
| 唯一标识 | PID | LWP,同进程 PID 完全相同 |


原理
Linux的线程原理


2 创建线程的 pthread_create 函数详解
2.1 头文件与完整函数原型
pthread_create

参数
pthread_t *restrict thread 输出型参数,传入pthread_t变量地址,函数执行成功后,会把新建线程的 TID(线程 ID,TCB 标识)写入该变量。 restrict是 C 关键字,标记该指针是访问对应内存的唯一途径,编译器优化用,写代码时直接传普通指针即可。
const pthread_attr_t *restrict attr 线程属性结构体,用来设置栈大小、分离属性、调度优先级等; 传nullptr/NULL代表使用系统默认线程属性。
void *(*start_routine)(void *) — 线程入口函数(重点) 函数指针,规定线程主函数固定格式:
- 参数:void* 通用指针
- 返回值:void* 你之前代码报错根源就是线程函数返回值写成void,不匹配该签名。
void *restrict arg 传给线程入口函数的唯一参数,无参数时填nullptr; 可传递数字、结构体指针等任意数据,在线程函数内强转类型使用。
返回值
- 返回 0:线程创建成功
- 返回非 0 数字:创建失败,对应错误码(如内存不足、属性非法)


我们发现这两个死循环居然同时在运行,也就是说明这是两个执行流,我们不妨可以进一步验证
我们让这两个循环都打印自己的pid,发现pid都是一致的,说明这两个执行流属于同一个进程


查看线程(LWP 轻量级进程)
ps -aL
PID LWP TTY TIME CMD
2664911 2664911 pts/0 00:00:00 testThread
2664911 2664912 pts/0 00:00:00 testThread
字段解释:
- LWP=2664911:主线程 main(主线程 LWP 号和进程 PID 相等)
- LWP=2664912:pthread_create 创建的子线程 Linux 内核不区分进程与用户态线程,全部统一用 task_struct 管理,用户线程在内核层面就是轻量级进程 LWP。
区分执行流严格意义上来说,并不是依靠pid而是lwp
那么如何杀掉这个呢—->直接选择杀掉进程kill -9 pid
虚拟地址空间第四讲–页表,虚拟地址 物理地址
1 物理内存基础管理规则
1.1 物理内存最小单元:4KB 页框
内核会把整块物理内存切割成大小统一的页框,单块页框固定 4KB,换算公式4KB=2^12。内存和磁盘交换数据时,读写的最小单位同样是 4KB。

1.2 页框管理结构体 struct page
机器上每一块物理页框,都会对应一个 struct page 结构体。结构体内部 flags 采用位图存储页面状态,一位标记一种状态,包含是否空闲、是否脏页、是否锁定、文件缓存、交换分区标记等。使用位图能极大节省内核内存开销。

1.3 全局数组 mem [] 统一管理所有页框
系统全部物理页框由全局数组 mem [] 统一索引。32 位 4GB 内存设备,总页框数量:4GB / 4KB = 1048576。
物理地址与数组下标换算公式
整体逻辑流程

2 32 位程序虚拟地址空间布局
32 位系统完整虚拟地址空间共 4GB,分为用户空间、内核空间两大区域:
- 用户空间(0 ~ 0xC0000000,合计 3GB) 地址由低到高依次存放:程序代码段、初始化全局数据、未初始化数据、堆(地址向上增长)、共享库映射区域、栈(地址向下增长)。
- 内核空间(0xC0000000 ~ 4GB,合计 1GB) 所有进程共用同一份内核空间,存放内核代码、硬件驱动、全局页表、内核栈等核心资源。
3 32 位二级分页:虚拟地址 10+10+12 分段规则
32 位虚拟地址一共 32 个 bit,硬件 MMU 会自动拆分三段:

1)蓝色段:高 10 位,页目录索引
- 长度:10bit,取值范围 \\(0 \\sim 2^{10}-1=1023\\),共 1024 个下标;
- 对应结构:页目录(每个进程独立一张,固定 4KB 物理内存);
- 作用:以 CR3 存放的页目录物理地址为基址,用这 10 位找到对应页目录项;
- 页目录项内容:保存一张页表的物理起始地址。
2)绿色段:中间 10 位,页表索引
- 长度:10bit,同样 0~1023,单张页表 1024 个页表项;
- 对应结构:页表(按需分配,进程没用到的虚拟区间不会创建页表);
- 作用:拿到页表物理地址后,用这 10 位找到页表项 PTE;
- 页表项核心:存放物理页框的起始地址,同时附带权限位(读 / 写 / 执行)、存在位、脏位。
3)红色段:低 12 位,页内偏移
- 长度:12bit,\\(2^{12}=4096\\),正好对应 4KB 一页;
- 关键特性:不参与页表查找;
- 作用:找到物理页框首地址后,直接加上这 12 位偏移,定位页内具体的字节;
- 举例:物理页框起始地址 0x1000,偏移 0x0FF → 最终物理地址 0x10FF。
每张页表同样包含 1024 个页表项 (PTE),单张页表 4KB; 一个页目录项对应一张独立页表,图里画出 3 张页表代表不同虚拟区间。

一条虚拟地址完整翻译流程
页表大小:4MB+4kb
单个进程不可能拥有全部内存,当前进程页表总数远远小于4MB
懒加载,写实拷贝,缺页中断—>页表少–>页表会更少
写时拷贝会讲整个4kb进行拷贝—–>局部性原理(磁盘上的数据可以提前加载到内存)
任何一个页框的地址,用多少位就够了
页框的起始地址低12位都是0—->用20比特位表示页框的起始地址,对地址进行相应的右移12位,左移12位来完成—>20位表示页表的地址剩下来12位—>标志位,
以上工作由硬件完成mmu,因为效率高
4 CR3 寄存器核心知识点
4.1 CR3 寄存器存储内容
CR3 是 CPU 专属硬件寄存器,里面存放的是当前进程页目录的物理起始地址,绝对不能存放虚拟地址。 原因很容易理解:翻译虚拟地址需要依赖页目录,如果 CR3 存虚拟地址,想要读取页目录又要翻译地址,会形成无限循环。
4.2 CR3 和进程、线程切换的关联
5 MMU 完成虚实地址转换完整流程
- 标记有效:提取物理页框基地址,拼接低 12 位偏移,访问物理内存;
- 标记无效:触发缺页异常,内核分配物理页、从磁盘加载对应数据、更新页表项,之后重新执行出错指令。
拥有更多的虚拟地址—>拥有更多的内存,只要我们将虚拟地址进行划分,本质就是划分物理内存
页表的本质:是进程看到内存资源的窗口,拥有更多的虚拟地址,拥有更多的内存,划分区域,划分虚拟地址,在编译阶段进行划分.

linux在物理上,存在真正意义上的线程吗??—>不存在,没有TCB,只存在轻量级进程

进程拥有独立性大部分资源独立
线程拥有共享性,大部分资源共享
网硕互联帮助中心





评论前必须登录!
注册