一.进程的概念及为何引入进程
现代操作系统要运行各种程序,为了管理程序的运行,操作系统提出了进程的抽象。每个进程都对应于一个运行的程序。有了进程的抽象应用程序在运行时仿佛独占了整个cpu,不用考虑何时将cpu让给其他程序。其实进程就是正在执行的程序,它是一个动态的概念。
二.进程的状态:
为了对进程进行管理,操作系统定义了进程的状态。共有五种状态
1. 新生状态:该状态表示一个进程刚刚被创建出来,还没有完成初始化,不能被调度执行。初始化之后,进程进入预备状态。
2.预备状态:该状态表示可以被调度执行,但还没有被调度器选择。此时,系统中其他可被调度的进程都处于预备状态。被调度器选择执行后,进入运行状态。
3.运行状态:该状态表示进程正在CPU上运行。当一个进程执行一段时间后,调度器可以选择终端它的执行并重新将其放回调度队列,它就迁移至预备状态。当进程运行结束,它会迁移至终止状态。如果一个进程需要等待某些外部事件,它可以放弃CPU并迁移至阻塞状态。
4.阻塞状态:表示进程需要等待外部事件,暂时无法被调度。当进程等待的外部事件完成后,它会迁移至预备状态。
5.终止状态:表示进程已经完成了执行,并且无法再被调度
三.进程的内存空间布局
进程具有独立的虚拟内存空间。从上至下依次为,内核部分,内核栈,用户栈(存放临时变量),代码库,用户堆(管理动态分配内存),数据和代码段。
四.进程控制块和上下文切换
1.在内核中,每个进程都通过一个数据结构来保存它的相关状态,如它的进程标识符(PID),进程状态,虚拟内存状态,打开的文件等。这个数据结构称为进程控制块(PCB).要注意的是,不同的操作系统的进程控制块的内容可能不同。
2.进程的上下文包括进程运行时的寄存器状态,其能够用于保存和恢复一个进程在处理器上的运行状态。当操作系统要切换当前执行的进程时,就会使用上下文切换机制。该机制会将前一个进程的寄存器状态保存到PCB中,然后将下一个进程先前保存的状态写入寄存器,从而切换到该进程执行。
五.Linux进程的操作
1.Linux的进程创建一般是通过调用fork接口来从以用进程中分裂出新的进程来达到创建进程的目的。
2.fork接口不接受任何参数,返回值是当前进程的PID。当一个进程调用fork时,操作系统会为该进程创建一个几乎一模一样的新进程。我们一般将调用fork的进程称为父进程,将新创建的进程称为子进程。当fork刚刚完成时,两个进程的内存,寄存器,程序计数器等状态都完全一致,但它们是完全独立的两个进程,拥有不同的PID与虚拟内存空间,在fork完成之后他们会各自独立执行,互不干扰。fork创建完一个新的进程之后,会在父进程和子进程中分别返回,它是一个调用一次,返回两次的系统调用。对于父进程,fork返回子进程的PID,对于子进程fork返回0.
3.进程的执行:exec。
在很多时候子进程要执行和父进程完全不同的任务,为了实现这个目标,通常通过exec接口来完成
六.进程的管理
1.进程间的关系和进程树
每个进程都会记录自己的父进程和子进程,进程之间因此构成了进程树的结构。内核正是通过这种进程树结构来对进程进行管理的。处于进程树根部的是init进程,它是操作系统创建的第一个进程。其他的是所有进程都是通过他直接或者间接创建出来的。
2.进程间的监控:wait
在Linux中进程可以通过wait操作来进行对子进程状态的监控。wait不仅起到监控作用,还起到回收已经运行结束的子进程和释放资源的作用。如果父进程没有调用wait操作,或者还没来得及调用wait操作,就算子进程已经终止了,他所占用的资源也不会完全释放,我们将这种进程称为僵尸进程。内核会为僵尸进程保留其进程描述符(PID)和终止时的信息,以便父进程在调用wait的时候可以监控其状态。但是由于内核的PID是有限制的,如果一个进程大量创建了子进程却不调用wait,那么僵尸进程会迅速占据可用的PID,使得后续fork因为资源不足而失败。不过如果父进程退出了,那么所有由父进程创建的僵尸进程都会被内核的第一个进程init通过调用wait来回收。