进程与线程的区别在面试题中非常常见,然而大多数人一般都只是网上找资料,背背下来就完事了(就和我应对epoll面试题一样……)本文将从一些底层角度去剖析进程与线程——主要是从linux内核角度。
首先还是贴一下我对于这种面试题的一些常规回答:
- 线程的划分尺度小于进程,简单说是一个程序至少有一个进程,一个进程至少有一个线程。 线程是cpu调度的
基本单位,进程和线程都能创建和撤销线程;同一个进程内的多个线程之间可以并发执行。- 进程拥有独立的地址空间和其他资源,而同一个进程中的线程共享(当然各自也会有一些用于运行必不可少的
资源)。这就意味着一个进程崩溃,在保护模式下,不会影响到其他进程,而一个线程崩溃,其同一进程内的
所有线程都会崩溃。因此,多进程比多线程更健壮,但一般来说多进程效率较差,因为线程切换代价较小。- 线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出
口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。- 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多
个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。- 进程的一些重要信息存在PCB中,而线程则是TCB,PCB管理这TCB。
其实一般来说能这样回答出来就差不多了,而面试官一般都会打断“背诵”,除非你们双方都会一些底层知识。
进程定义
然而实际上,上述回答甚至没有对进程下一个定义,只是单纯的进行比较。
就我看来,进程是处于执行期的程序以及相关的资源的总称。 另有一种好理解的说法是,进程就是正在执行的程序代码的实时结果。然而处于执行期必然需要一些资源,这是显然的。
进程结构
在内核中,进程调度的列表叫任务列表( task list ),其结构为 task_struct。task_struct略大,成员略多,但考虑到它包含了进程所需的所有信息,也就不足为奇了。其中的成员有
- 进程描述符pid
- 打开的文件
- 进程的地址空间
- 挂起的信号
- 进程的状态
- 其他
在linux中,调度器是直接对任务列表调度的,但众所周知的是,线程才是调度器的调度单元,而任务列表中记录的是进程信息,这显然是矛盾的。所以具体是怎么处理呢?
答案很简单啦,在linux内核中,进程和线程的界限非常模糊,比如说,我只要将几个线程的pid设置相同,将同一个进程的所有线程的task_struct共享某些资源即可。
而在底层实现上,也是如此的,创建进程的操作为 调用 fork() 创建资源 然后调用 exec() 载入程序并运行。而fork函数实际上是调用clone系统调用实现的。而线程也是如此。差别只有它们的参数而已。
- 进程 fork() — clone(SIGCHLD, 0)
- 线程 — clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0 ),其中四个参数的含义分别为,共享地址空间,共享文件系统信息,共享打开文件,共享信号处理函数。
(其中有个问题,进程的pcb和线程的tcb存放在哪里我还尚不清楚,之后会补充)
当然,这是在linux中的线程实现,在其他操作系统中的实现可能会有较大差异,尤其是windows差异非常大。windows提供了一种专门用于支持线程的机制,而linux只是把线程作为共享一些资源的进程。
顺便扯一下其他相关的
线程由来与好处
多任务系统中,在切换任务时必须保存未完成任务的上下文,也就是切换任务的消耗,而同一进程所属的线程共享一些资源,所以它的切换任务的消耗比切换不同进程的消耗要小的多。(有点抢坑的感觉
进程的五状态图
以前腾讯面试问到过,没详细找。这里的五状态有:
- TASK_RUNNING —— 可执行进程,包括执行进程和就绪进程
- TASK_INTERUPTIBLE —— 可中断进程,意为睡眠/阻塞进程
- TASK_UNINTERUPTIBLE —— 不可中断进程,睡眠/阻塞进程,但即使收到信号也不会被唤醒或就绪,这个状态有点难理解,是为了不在等待的时候受到干扰而出现,一般使用较少
- TASK_TRACED —— 被跟踪进程,如ptrace调试
- TASK_STOPPED —— 停止的进程
(实际上我觉得把2,3合并,把1拆开比较合理点。。。
(懒得画图。。。
状态图是
fork() 创建进程 -> TASK_RUNNING(就绪)
TASK_RUNNING(就绪) 被调度 -> TASK_RUNNING(运行)
TASK_RUNNING(运行) 被抢占 -> TASK_RUNNING(就绪)
TASK_RUNNING(运行) do_exit()退出 -> TASK_STOPPED
TASK_RUNNING(运行) 等待特定事件 -> TASK_INTERUPTIBLE / TASK_UNINTERUPTIBLE
TASK_INTERUPTIBLE / TASK_UNINTERUPTIBLE 被唤醒 -> TASK_RUNNING(就绪)
关于协程
简单说就是进程自己来调度“线程”(好饿,以后再说了