- 程序
- 概念
程序是含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,也就是说程序是静态的代码
- 进程
- 概念
进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如 CPU 时间,内存空间,文件,输入输出设备的使用权等等
- PCB
进程控制块PCB (process control block):操作系统中用于描述进程的工具,其中包含的是进程属性的集合;Linux操作系统下的PCB是 task_struct,它是Linux内核的一种数据结构,其内容可以分为如下几类:
- 标示符: 描述本进程的唯一标示符,用来区别其他进程;
- 状态: 任务状态,退出代码,退出信号等;
- 优先级: 相对于其他进程的优先级;
- 程序计数器: 程序中即将被执行的下一条指令的地址;
- 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针;
- 上下文数据: 进程执行时处理器的寄存器中的数据;
- I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表;
- 记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等;
- 其他信息
- 状态
进程状态本质上就是PCB内部的一个整形变量,不同的整形值就对应不同的进程状态。
- 普适的操作系统层面
站在操作系统学科的角度来说,进程状态可能有如下几种:新建、就绪、运行、阻塞、挂起、终止。
- 新建
- 申请一个空白的PCB,并向PCB中填写用于控制和管理进程的信息;为该进程分配运行时必要的资源
- 就绪
- 其余资源都已经获取,PCB在队列中等待获取CPU资源
- 运行
- CPU从就绪队列中取出PCB,获取进程的各种数据和指令,然后执行相应运算
- 阻塞
- 访问某种资源需要进行等待,将PCB放入等待队列中;获取到资源以后,再将该进程PCB放入就绪队列中
- 并不是只有等待硬件资源进程才会处于阻塞状态,一个进程等待另一个进程就绪、一个进程等待某种软件资源就绪等都会处于阻塞状态
- 挂起(静止)
- 由于内存空间不足,操作系统将进程对应的代码数据放到磁盘中以节省内存空间;挂起不会移动进程的PCB,只会移动进程对应的代码和数据
- 挂起进程并不是释放进程,因为该进程对应的PCB仍然处于某硬件的等待队列中,当该进程获得对应的资源以后,操作系统仍然可以将该进程对应的代码和数据从磁盘加载到内存中来继续运行,其本质是对内存数据的换入换出;
- 阻塞不一定挂起,挂起也不一定阻塞,也可能是新建挂起、就绪挂起,甚至是运行挂起
- 终止
等待OS进行善后处理(停止执行、终止子孙进程、归还资源等),然后将其PCB清零,并将PCB空间返还给系统
- Linux操作系统层面
Linux中进程一共有七种状态,分别是运行、睡眠、深度睡眠 (磁盘休眠)、暂停、追踪暂停、死亡、僵尸
- 运行
进程的PCB位于CPU的运行队列中
- 睡眠
进程需要等待某种资源
- 深度睡眠
当内存空间不足的时候,操作系统会将一部分进程挂起来节省资源;但是如果内存空间严重不足,挂起已经解决不了问题的时候,操作系统就会主动杀掉某些进程。
处于深度睡眠状态的进程既不能被用户杀掉,也不能被操作系统杀掉,只能通过断电,或者等待进程自己醒来
深度睡眠一般只会在高IO的情况发生下,且如果操作系统中存在多个深度睡眠状态的程序,那么说明该操作系统也即将崩溃了
- 暂停
暂停状态其实也属于阻塞状态的一种,我们可以使用 kill 命名,指定 -19 选项来让一个进程从运行状态变为暂停状态,可以使用 kill -18 让一个处于暂停状态的进程恢复运行
- 追踪暂停
追踪暂停状态是一种特殊的暂停状态,进程处于此状态表示该进程正在被追踪,比如 gdb 调试进程
- 死亡
死亡状态代表着一个进程结束运行,该进程对应的PCB以及代码和数据全部被操作系统回收
- 僵尸
僵尸状态就是进程在退出时等待父进程或者操作系统来读取退出状态代码,然后释放PCB的一种状态。
具体的Linux操作系统下的进程状态和普适的操作系统学科上进程的状态是不同的,比如Linux操作系统没有阻塞和挂起状态,阻塞状态通过睡眠、深度睡眠、暂停、追踪暂停等状态表现出来,而进程处于这些状态时是否会被调整为挂起状态,用户是不可得知的,因为操作系统没必要将挂起状态暴露给用户,用户也不关心一个进程是否会处于挂起状态
- 特殊进程
僵尸进程
一个进程的资源在被全部释放之前,需要由父进程或者操作系统来读取退出状态代码,而如果父进程不读取子进程的退出状态代码,该进程的PCB就一直得不到释放,此时该进程就会变成僵尸进程,进程对应的 PCB (task_struct) 就将一直存在,不会被释放;会造成内存资源的浪费
孤儿进程
孤儿进程是指父进程提前退出后,子进程被操作系统领养的一种情况,被操作系统领养的进程就被称为孤儿进程
- 优先级
资源是有限的,内存中有很多进程都要占用资源,但是资源是有限的,所以我们需要指定优先级来合理的分配资源
Linux 中优先级的表示与维护通过两个变量 PRI (priority) 和 NI (nice) 来完成,每个进程默认的 PRI 都是 80,NI 都是 0;我们可以通过修改 NI 的值来调整进程的优先级,NI 的改动范围为 [-20, 19];PRI 与 NI 的和越小,进程的优先级就越高
虽然我们可以通过修改 NI 值来调整进程优先级,但是我们一般都不会这样做,因为效果不大
- 进程切换
CPU同一时刻只能运行一个进程,但是CPU的运算速度非常快,所以位于CPU运行队列中的每一个进程都只运行一个时间片,每个进程运行完一个时间片后被都被放到运行队列尾部,等待下次运行;这样使得在一个时间段中多个进程都能被运行
我们在进行进程切换时需要进行进程的上下文保护与上下文恢复,即进程停止运行时将寄存器里面的数据保存起来,进程重新运行时将保存的数据再放入到寄存器中;以便我们能够接着上次运行的地方接着运行
- 线程
- 概念
线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
- 状态
- 普适的操作系统层面
- 新建
一个新的线程被创建,等待该线程被调用执行
- 就绪
表示线程已经被创建,正在等待系统调度分配CPU使用权,或者时间片已用完,此线程被强制暂停,等待下一个属于它的时间片到来
- 运行
表示线程获得了CPU使用权,正在占用时间片,正在进行运算中
- 阻塞
表示线程等待(或者说挂起),等待某一事件(如IO或另一个线程)执行完,让出CPU资源给其他线程使用
- 死亡
一个线程完成任务或者其他终止条件发生,该线程终止进入退出状态,退出状态释放该线程所分配的资源
- Java中线程状态
- 初始状态(NEW)
线程被构建,但是还没有调用start方法
- 运行状态(RUNNABLE)
Java线程把操作系统中就绪和运行两种状态统一称为“运行中”
- 阻塞状态(BLOCKED)
表示线程进入等待状态,也就是线程因为某种原因放弃了CPU的使用权
- 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被其他线程锁占用了,那么JVM会把当前线程放入到锁池中
- I/0阻塞:发出I/O请求,JVM会把当前线程设置为阻塞状态,当I/O处理完毕则线程恢复
- 等待状态后进入阻塞:运行的线程执行了Thread.sleep、wait、join等方法,释放资源,JVM会把当前线程设置为等待状态,当sleep结束,join线程终止或者线程被唤醒后需要重新获取资源,该线程从等待状态进入阻塞状态
- 等待(WAITING)
等待状态,没有超时时间(无限等待),要被其他线程或者有其他的中断操作
执行wait()、join()、LockSupport.park()
- 超时等待(TIME_WAITING)
与等待不同的是,不是无限等待,超时后自动返回
执行 Thread.sleep(long)、wait(long)、join(long)、LockSupport.park(long)、LockSupport.parkNanos(long)、LockSupport.parkUntil(long)
- 终止(Teminated)
线程执行完毕