Linux:进程:进程状态
进程是一个负责分配系统资源(CPU时间,内存)的实体。
进程=内核数据结构(用于描述和组织进程)+代码数据(实际内容)
描述进程-PCB
进程信息被放在⼀个叫做进程控制块的数据结构中,简称PCB(processcontrolblock),
Linux操作系统下的PCB是:task_struct
task_struct是Linux内核的⼀种数据结构,它会被装载到RAM(内存)⾥并且包含着进程的信息。
task_ struct
内容分类:
标⽰符:描述本进程的唯⼀标⽰符,⽤来区别其他进程。
状态:任务状态,退出代码,退出信号等。
优先级:相对于其他进程的优先级。
程序计数器:程序中即将被执⾏的下⼀条指令的地址。
内存指针:包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
上下⽂数据:进程执⾏时处理器的寄存器中的数据。
I/O状态信息:包括显⽰的I/O请求,分配给进程的I/O设备和被进程使⽤的⽂件列表。
记账信息:可能包括处理器时间总和,使⽤的时钟数总和,时间限制,记账号等。
其他信息
所有运⾏在系统⾥的进程都以task_struct链表的形式存在内核⾥:
查看进程
使用ps指令可快速查看当前正在运行的进程:
使用top命令可以更全面的看到当前运行的进程:
也通过系统调⽤获取进程标⽰符:
#include <stdio.h>#include <sys/types.h>#include <unistd.h>int main(){while(1){sleep(1);}return 0;}
运行结果:
其中PID就是进程标识 (process identification value),用于区分不同进程,
而PPID则是父进程的PID
父进程
在 Linux 系统中,所有的进程都是由父进程创建的,但有一个例外:第一个进程。第一个进程是由内核直接创建的,它没有父进程。这个第一个进程通常被称为 init(传统Linux系统)
或 systemd
(现代 Linux 系统)。
而对于直接接受用户指令的shell外壳(命令行解释器),它的进程则是bash,在命令行下执行指令或启动程序所产生的进程都是bash的子进程
进程状态
理论层面
进程的状态决定了进程要做的工作,理论上进程主要有以下几个状态:
新建状态,就绪状态,运行状态,阻塞状态,挂起状态,结束状态
1. 就绪状态(Ready)
-
进程的程序和数据已经加载到内存中,等待 CPU 调度。
-
一旦 CPU 空闲,操作系统会从就绪队列中选择一个进程分配给 CPU 运行。
2. 运行状态(Running)
-
进程正在 CPU 上执行。
-
一个 CPU 同一时间只能有一个进程处于运行状态(单核 CPU)。
3. 阻塞状态(Waiting 或 Blocked)
-
进程因为某种原因(如等待 I/O 操作完成、等待锁、等待事件发生等)而暂时无法运行。
-
进程需要等待某个事件完成后才能继续运行。
-
在阻塞状态下,进程不会占用 CPU 资源。
4. 挂起状态(Suspended 或 Blocked)
-
进程被暂时移出内存,存储到外存(如磁盘)中,以释放内存资源。
-
挂起状态的进程无法直接运行,需要操作系统将其重新调入内存后才能恢复到就绪或运行状态。
-
挂起状态通常用于内存不足或系统负载过高的情况。
5. 终止状态(Terminated 或 Exit)
-
进程已经完成任务或因异常情况(如错误、信号中断等)而结束。
-
操作系统会清理该进程占用的资源,并从进程表中移除该进程。
进程状态转换
进程状态之间可以相互转换,常见的转换路径如下:
-
就绪 → 运行:CPU 调度器选择一个就绪状态的进程分配 CPU。
-
运行 → 就绪:进程的 CPU 时间片用完,或者被更高优先级的进程抢占。
-
运行 → 阻塞:进程主动发起 I/O 操作或等待某个事件。
-
阻塞 → 就绪:等待的事件完成(如 I/O 操作完成)。
-
运行 → 终止:进程完成任务或因错误终止。
-
就绪/阻塞 → 挂起:系统内存不足,进程被移出内存。
-
挂起 → 就绪:系统将挂起的进程重新调入内存。
Linux中进程状态的实现
Linux对于进程状态有自己的划分,当然也在一定程度上对应了上述状态
Linux内核源码对进程状态的定义:
/**The task state array is a strange "bitmap" of*reasons to sleep. Thus "running" is zero, and*you can test for combinations of others with*simple bit tests.*/static const char *const task_state_array[] = {"R (running)", /*0 */"S (sleeping)", /*1 */"D (disk sleep)", /*2 */"T (stopped)", /*4 */"t (tracing stop)", /*8 */"X (dead)", /*16 */"Z (zombie)", /*32 */};
R运⾏状态(running):并不意味着进程⼀定在运⾏中,它表明进程要么是在运⾏中要么在运⾏队列⾥。R对应理论层面的运行状态和就绪状态
S睡眠状态(sleeping):意味着进程在等待事件完成(这⾥的睡眠有时候也叫做可中断睡眠 (interruptible sleep))。
D磁盘休眠状态(Disksleep)有时候也叫不可中断睡眠状态(uninterruptiblesleep),在这个 状态的进程通常会等待IO的结束。
S和D加起来对于理论层面的阻塞状态
T停⽌状态(stopped):可以通过发送SIGSTOP信号给进程来停⽌(T)进程。这个被暂停的进程可以通过发送SIGCONT信号让进程继续运⾏。
X死亡状态(dead):这个状态只是⼀个返回状态,你不会在任务列表⾥看到这个状态。X对应理论层面的结束状态
而挂起状态并没有直接标识,是因为挂起状态并不是一个独立的进程状态,而是通过其他状态和机制间接实现的。对于用户而言并不用特别在意挂起状态。
Z(zombie)-僵⼫进程
僵尸状态(Zombies)是⼀个⽐较特殊的状态。当进程退出并且⽗进程没有读取到⼦进程退出的返回代码(即父进程没有调用 wait()
或 waitpid()
来清理子进程的状态)时,就会产⽣僵尸进程
僵尸进程会以终⽌状态保持在进程表中,并且会⼀直在等待⽗进程读取退出状态代码。
所以,只要⼦进程退出,⽗进程还在运⾏,但⽗进程没有读取⼦进程状态,⼦进程就会进⼊Z状态
创建一个僵尸进程:
#include <unistd.h>#include <stdio.h>#include <stdlib.h>int main(){pid_t id = fork();if(id < 0){perror("fork");return 1;}else if(id > 0){ //parentprintf("parent[%d] is sleeping...\n", getpid());sleep(30);}else{printf("child[%d] is begin Z...\n", getpid());sleep(5);exit(EXIT_SUCCESS);}return 0;}
运行结果:
父进程30816处于s状态,而子进程30817处于z状态
僵⼫进程危害
如果父进程一直不读取子进程状态,那么⼦进程就⼀直处于Z状态 ,这会导致子进程的PCB一直存在占用系统资源
孤儿进程
如果父进程先退出,而⼦进程后退出并进入Z状态,这就使得子进程成为了“孤⼉进程”
孤⼉进程会被1号进程init领养,并且由init进程回收。