进程回顾
在学习线程之前,我们先回顾一下之前讲的进程概念
当我们创建一个进程,操作系统会将磁盘中的代码load到内存中,然后创建当前进程的task_struct(后面可能会用”PCB“或者”进程控制块“代替),创建mm_struct维护当前进程的虚拟地址空间,再通过页表映射到物理内存
![](https://i-blog.csdnimg.cn/blog_migrate/54b28d3e0aa63ec10aaec9db608d5ccc.png)
当我们fork创建一个子进程,为了保证进程的独立性,就要构造新的进程控制块(task_struct),维护新的mm_struct,产生新的页表
![](https://i-blog.csdnimg.cn/blog_migrate/9e0deb6660228d1a59f4b30c0762c4c5.png)
虽然父子进程的代码是共享的,但是可以通过 if(pid > 0) / else
这样的分支结构,让父子进程分别执行不同的代码块,有各自的执行流,同时通过写时拷贝也让它们有自己独立的数据区、堆区、栈区。
线程的理解
当新建一个进程,除了创建新的PCB,还要为每个进程独立分配 mm_struct
和页表,这无疑会消耗大量的空间、时间;
现在,我们新建一个”进程“的时候,只为其创建一个新的PCB,但和原来的进程共享同一个mm_struct
、同一个页表;
![](https://i-blog.csdnimg.cn/blog_migrate/eb6c6c83a24ad1103c359eeba82ae356.png)
先前我们通过if/else
的分支结构拆分代码块,现在我们通过构建函数体的方法,划分代码块,让新的“进程”执行函数体中的那部分代码,同时也为其分配自己的的栈区,从而让它们有各自的执行流
我们就称这种:
- 在父进程的地址空间内部运行执行流
- 比进程粒度更低,调度成本更低
的“进程”为线程
这种进程中创建出的一个个线程块我们成为TCB
但是在Linux中并没有为TCB
创建独立的结构体,而是复用了task_struct
这样的结构体,从CPU的角度看,PCB和TCB是同一个东西
而在Windows这样的真线程操作系统中,专门为线程设计的独立的结构体
重新理解进程
在进程概念一文中我们定义过进程的概念:
进程 = 内核数据结构 + 进程对应的代码数数据
现在,我们从内核的视角重新看待进程:
进程 = 承担分配系统资源的基本实体
如下图,包含了
task_struct
、mm_struct
、页表、维护的地址映射关系……的整个结构才是进程它是向系统申请资源的基本单位
而线程成为了调度的基本单位
![](https://i-blog.csdnimg.cn/blog_migrate/45099fd4c924d5f519ebe6f8c2a1dee2.png)
-
之前我们提到的内部只有一个执行流的进程是单执行流进程
-
现在有了线程的概念,我们知道进程内部可以有多个执行流,这样的进程成为多执行流进程