Linux进程的过程与状态

程序如何变成进程

程序是个静态的文件,进程是一个动态的实体,进程的状态会在运行过程中改变,那么程序是如何变为一个进程的呢?
通常在 Shell 中输入命令运行就包含了程序到进程转换的过程。整个转换过程主要包含以下 3 个步骤:
(1)查找命令所对应程序文件的位置;
(2)使用 fork()函数为之创建一个新进程;
(3)在新进程中调用 exec 族函数装载程序文件,并执行程序文件的 main()函数。

进程状态

Linux 是一个多用户多任务的操作系统,可以同时运行多个用户的多个程序,就必然会产生多进程,而每个进程都会有不同的状态。 Linux 的进程有以下 6 种状态:

  • R: 运行状态(running)。进程处于运行态或就绪状态,但运行状态的进程并不意味着进程时刻都一定在运行中,它表明进程要么是在运行中要么在运行队列里。只有在该状态的进程才可能在 CPU上运行,而同一时刻可能有多个进程处于可执行状态,这些进程的task_struct结构(进程控制块)被放入对应CPU的可执行队列中(一个进程最多只能出现在一个CPU的可执行队列中)。进程调度器的任务就是从各个CPU的可执行队列中分别选择一个进程在该CPU上运行。

  • S:可中断的睡眠状态,处于这个状态的进程因为等待某种事件的发生而被挂起(比如等待socket连接、等待信号量)。这些进程的task_struct结构被放入对应事件的等待队列中。当这些事件发生时(由外部中断触发、或由其他进程触发),对应的等待队列中的一个或多个进程将被唤醒。

  • D: 不可中断的深度睡眠状态或者称为磁盘休眠状态(Disk sleep),在这个状态的进程通常会等待IO的结束,处于这种状态的进程不能响应异步信号;注意:不可中断,指的并不是CPU不响应外部硬件的中断,而是指进程不响应异步信号。在进程对某些硬件进行操作时(比如进程调用read系统调用对某个设备文件进行读操作,而read系统调用最终执行到对应设备驱动的代码,并与对应的物理设备进行交互),可能需要使用TASK_UNINTERRUPTIBLE状态对进程进行保护,以避免进程与设备交互的过程被打断,造成设备陷入不可控的状态

  • T: 暂停状态或跟踪状态;向进程发送一个SIGSTOP信号,它就会因响应该信号而进入TASK_STOPPED状态(除非该进程本身处于TASK_UNINTERRUPTIBLE状态而不响应信号),向进程发送一个SIGCONT信号,可以让其从TASK_STOPPED状态恢复到TASK_RUNNING状态。可以使用gdb终止进程来实现跟踪终止状态。

  • X: 退出状态(TASK_DEAD - EXIT_DEAD),进程即将被销毁;而进程在退出过程中也可能不会保留它的task_struct。比如这个进程是多线程程序中被detach过的进程 。或者父进程通过设置SIGCHLD信号的handler为SIG_IGN,显式的忽略了SIGCHLD信号。

  • Z: (TASK_DEAD - EXIT_ZOMBIE),退出状态,进程成为僵尸进程。一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符(TCB)仍然保存在系统中,这种进程称之为僵死进程。

  • 僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且子进程(使用wait()系统调用)没有读取到父进程退出的返回代码时就会产生僵死进程。僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码,也就是说在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。 但是仍然为其保留一定的信息(包括进程号the process ID,退出状态the termination status of the process,运行时间the amount of CPU time taken by the process等)。直到父进程通过wait / waitpid来取时才释放。

  • 孤儿进程。一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作(孤儿进程其实并不会产生太大危害,因为init进程可以处理它,回收对应的资源)。

Linux进程的过程与状态