操作系统中的进程和线程
1.进程和线程的学习中容易混乱的地方
之所以觉得进程和线程难以理解,大概有这么几个原因:
- 进程,线程的概念是不断发展的,不同时期的书籍会有不同的阐述,如一开始内核不支持线程到后来内核开始支持线程
- 经典的书籍如《现代操作系统》多是从原理的角度去说,高屋建瓴,但是难以有切身感受
- 操作系统对进程和线程的实现也是不同的,架构者对进程和线程的理念有所不同,Linux和Windows内核对线程的实现不同。
2.进程模型的演变
最初,进程的内涵分为两个方面:
- 调度,执行的基本单位
最初操作系统不支持线程的概念,内核中维护着进程的各种信息,如运行状态,优先级,寄存器等,也就是说OS可以根据这些信息调度和执行进程。
- 资源所有权
包括程序,数据,文件,socket 等资源。
将两个独立的功能分离,出现了线程的概念:
- 作为执行与调度的基本单位——thread
- 资源所有权: process
这也就是经常说的:"process是资源容器,thread是执行单位"。这样带来的巨大好处是:
- 线程的创建成本远远低于进程,因为线程中包含的数据量少于进程,线程只需要执行相关的数据即可,如,pc,寄存器,栈,执行状态
- 进程中的线程共享地址空间,进程间的通信的代价远大于线程间的通信。
3.三种线程模型
<1>用户级线程
彼时操作系统没有提供对线程的支持,只是在应用层面实现了线程,操作系统并不知道,,内核维护着进程表,仍然以进程为单位进行系统调用。进程自身维护着线程表,维护线程的状态。
-
优势
- 不用切换到内核态,性能极佳,
- 进程自己可以有定制的调度算法
- 在不支持多线程的操作系统实现多线程程序,如DOS
-
劣势
- 对线程的操作,如创建,调度,都是应用程序实现,导致编程困难
- 因为操作系统只知进程不知线程,也就只能调度进程而无法调度线程,所有只要一个线程所在的进程发生了阻塞,那操作系统即阻塞该进程,那么该进程的其他线程也就无法被调度,相当于阻塞了。目前使用用户线程的程序越来越少了。
<2>内核级线程
后来,操作系统内核开始支持线程,就出现了内核级线程模型。这种线程模型中,内核维护着进程表和线程表,也就是说,内核可以对线程直接进行调度,
-
优势
- 内核的调度粒度小至线程,就不会出现用户级进程那种阻塞了进程,进程中所有线程都被迫“阻塞”的情况,内核线程被阻塞,不会导致进程被阻塞,进程中的其他线程依然可以继续执行
-
劣势
- 开销挺大,从用户态到内核态的切换有一定的开销
- 支持的线程总量较小,因为内核线程占据内核的空间,而内核所持有的空间是有限的
一般而言,程序不会直接使用内核线程,而是使用内核线程的高级接口——轻量级进程。轻量级进程和内核线程是1:1的关系。
<3>混合线程
有些操作系统支持,如Solaris平台。比较主流的windows和linux不支持。
4.概括进程和线程的本质
- 进程:当用户启动一个应用,系统将程序的源码和数据从磁盘加载到内存中,并且开始执行他的源码。一个进程即一个正被执行中的程序。与程序不同, 一个进程是一个活跃的实体 ,并且包含了程序作为单个实例在执行期间状态的快照。
- 线程:一个线程的本质是一组寄存器的状态,是操作系统对寄存器状态的抽象。
5.特殊的Linux线程实现
Linux对进程与线程的实现比较特殊,对于Linux内核来说,并不存在线程的说法,但是Linux中进程与进程可以是有区别的。对于linux内核来说,只有进程,创建进使用系统调用clone,可通过设置该方法的参数设置进程是否共享某些数据,比如:
- fork():创建子进程,通过调用0共享的clone,创建的进程是完全复制父进程的数据,二者不共享不可见。
- pthread_create:通过调用最多共享的clone实现,就相当于一般意义上的线程了。
可以看到Linus对进程与线程的观点:
Linus的观点,Re: proc fs and shared pids
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。