【Linux】线程

[复制链接]
发表于 2026-10-5 20:21:32 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×

线程为什么存在?先得讲清楚地址空间和页表。物理内存不是一整块,被切成一份份 4KB 的空间,OS 管理内存以内存块为单位而不是字节。这篇文章从 4KB 页框讲起,推到二级页表和 MMU,再落到"进程是分配系统资源的基本实体、线程是 CPU 调度的基本单位",最后解释 Linux 为什么用 PCB 复用模拟线程。

一、重谈地址空间:一切从 4KB 开始

1. 为什么是 4KB

物理内存被划分为一份份 4KB 空间,OS 以内存块为单位做管理,默认 4KB 是主流 Linux 用的块大小。实际上系统与磁盘做 IO 的基本单位也是 4KB(8 个扇区),整个计算机里的巧合都被精心设计过。程序编译完成后是有地址的,而且以 4KB 数据块存好;所谓加载,就是把磁盘上的 4KB 数据块搬到 4KB 的内存块上。

  • 4KB 的内存块叫页框;
  • 4KB 的数据块叫页帧。

父子进程写时拷贝的基本单位也是 4KB——改一点点数据也要拷 4KB,其实是用空间换时间:一个数据被改,它周围的数据大概率也要被改。

2. 用 struct page 管页框

4GB 空间里有一百多万个页框,OS 要"先描述再组织":用 struct page 描述每一个页框,再用一个结构体数组把它们组织起来,形如 struct page memory[1048576]。每个 page 有了下标,第一个页框起始地址是 0,下一个是 1 乘 4KB,于是"page 下标换算成页框起始地址"变得平凡,对内存的管理就变成对一个数组的增删查改。

二、二级页表:32 位地址怎么拆

1. 朴素映射的荒谬

之前谈页表时没细说:如果 4GB 地址空间在页表里一一对应虚拟与物理,每组对应关系再加一个状态标记位(按 1 字节算),一组就要 9 字节,4GB 就是 36GB——太大了,肯定不能这么映射。

2. 二级页表的拆法

虚拟地址在 OS 看来不是整体,而是把 32 位拆成 10 位、10 位、12 位三段:

  1. 前 10 位作索引指向第一张表,共 1024 项,这张表叫页目录;
  2. 页目录每一项存的是第二张表(页表)的地址,所以页目录可以指向很多张页表;
  3. 每张页表也是 1024 项,用第二个 10 位索引到其中某一项;
  4. 页表中那一项存的是页框的起始地址;
  5. 最后拿低 12 位(范围 0 到 4095)作页内偏移。

总结成一句:前 20 位用来搜索页框,后 12 位在页框内偏移,合起来就能索引页框里的任意字节。这种方案叫二级页表。

算一笔账:页表每项 2 字节,一个页表 2KB,共 1024 张页表,加起来 2MB,再加一级页目录 4KB。相比 36GB,省了三个数量级。

3. cr3 与 MMU

CPU 读的是虚拟地址,要按页表换成物理地址。CPU 怎么找到页表?CPU 里有 cr3 寄存器,每个进程把自己页表的页目录起始地址放在这个寄存器里。虚拟地址进 CPU 后,靠芯片里的 MMU 电路直接完成地址转换。

三、从地址空间到线程

1. 一个函数就是一批连续虚拟地址

在地址空间里,正文代码限定了一批虚拟地址范围,而且必须靠页表才能看到对应的物理空间。假设正文由 20 个函数构成,给每个函数分配一个执行流,代码就从并行变串行——技术上是可行的。函数地址其实是一批代码的入口地址,同一函数内的地址视为连续,所以一个函数对应一批连续的虚拟地址。划分这 20 个函数,本质上是在划分页表——虚拟地址本质上是一种资源。

2. 线程的定义

官方说法:线程是在进程内部运行、是 CPU 调度的基本单位。回顾进程的定义"进程 = 内核数据结构 + 进程代码和数据",现在换个角度——站在内核视角,进程是承担分配系统资源的基本实体。

所以创建"线程"时不再新建地址空间和页表,只新建一个 task_struct;把正文代码分成几份,各由一个执行流跑。这就是"线程在进程内部运行"这句话的由来。对比一下之前学的进程:之前进程内部只有一个执行流,现在进程内有多个执行流,所以只有单执行流的进程是多执行流进程的特殊情况。

3. 家庭比喻

把 OS 想成国家,进程就是一个个家庭。家里有爸爸妈妈爷爷奶奶:你在上课(执行上课的代码)、爸妈上班(执行上班的代码)、爷爷奶奶遛弯(执行养老的代码),每个人做自己的事,一家就过好了。家庭的使命是把日子过好——这个基本实体就是进程,家庭里的每一个人就是线程。

四、Linux 为什么没有单独的 TCB

如果 OS 单独设计线程,就要设计新建、暂停、销毁、调度,线程还得和进程关联。先描述再构造:struct tcb 里存线程 id、优先级、状态、上下文、链接属性;而描述进程的结构体是 struct pcb,一个进程有多个线程。

上图那种方案其实是 Windows 里真实存在的线程控制块设计:CPU 调度时先选进程,再从进程里选一个线程。

但设计 Linux 的时候发现:线程那些属性(id、优先级、状态、上下文、链接属性等)进程也都有,为什么还要单独搞一套 TCB?更麻烦的是,进程和线程设计成两套,调度算法是不是也要写两套?于是Linux 的设计者想能不能复用 PCB,用 PCB 统一表示执行流——这样就不需要为线程单独设计数据结构和调度算法。这就是 Linux 的方案:用进程模拟的线程。显然 Linux 的方案更简洁,也正是它今天能撑起容器生态的原因之一:容器本质上就是把一批 task_struct 和一组资源限制打包在一起。

qidao123.com企服评测-应用市场 编辑提示

本文按 4KB 页框与页帧的界定、写时拷贝以 4KB 为单位的"空间换时间"解释、struct page 数组管理一百多万个页框的先描述再组织思路、32 位虚拟地址拆 10/10/12 的二级页表推演与 2MB+4KB 的容量账、cr3 寄存器与 MMU 的地址转换路径、函数与连续虚拟地址区间与"虚拟地址是一种资源"的推论、线程在进程内运行且是 CPU 调度基本单位与进程作为资源分配基本实体的定义、家庭/线程比喻、以及 Linux 复用 PCB 用进程模拟线程的设计取舍(并延伸到容器理念),重排成"内存—地址转换—执行流—调度单位"的四段式。文中数值(4KB、1024、2MB、1048576)为典型配置下的教学示意,随架构与内核配置可能变化;内容仅用于操作系统原理学习与自有环境实验。

回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表