Bootstrap

【Linux】进程状态

🔥个人主页🔥:孤寂大仙V
🌈收录专栏🌈:Linux
🌹往期回顾🌹:【Linux】进程概念(PCB)与进程创建(fork)
🔖流水不争,争的是滔滔不


一、操作系统进程状态

进程状态是指一个进程在其生命周期中所处的特定阶段或状况。它反映了进程当前正在进行的活动或者等待的事件,并且决定了操作系统如何对该进程进行调度和资源分配。操作系统通过跟踪进程的状态,合理地分配 CPU 时间、内存和其他系统资源,以确保多个进程能够高效地并发执行。
在这里插入图片描述

二、运行、阻塞、挂起、

运行状态

在聊运行状态之前先了解一下进程的调度队列

// 进程结构体 task_struct
struct task_struct {
    volatile long state;  // 进程状态,使用上述 TASK_* 宏表示
    void *stack;  // 进程的堆栈指针
    atomic_t usage;  // 进程的使用计数,用于跟踪该进程被引用的次数
    unsigned int flags;  // 进程的标志位,包含多种属性
    unsigned int ptrace;  // 与进程调试和跟踪相关的标志
    int lock_depth;  // 进程锁深度
    int prio;  // 进程的优先级,数值越小,优先级越高
    int static_prio;  // 进程的静态优先级,通常在进程创建时设置,不会改变
    int normal_prio;  // 进程的正常优先级,考虑了调度策略等因素
    struct sched_entity se;  // 进程的调度实体,用于调度信息
    struct prio_array *array;  // 进程的优先级数组,用于存储不同优先级的进程队列
    struct list_head tasks;  // 进程列表,用于将进程链接到进程链表中
    struct mm_struct *mm;  // 进程的内存管理信息,包括虚拟地址空间等
    struct mm_struct *active_mm;  // 进程活跃的内存管理信息,在某些情况下会使用
    pid_t pid;  // 进程的标识符
    pid_t tgid;  // 进程组的标识符
    struct task_struct *real_parent;  // 进程的真正父进程
    struct task_struct *parent;  // 进程的当前父进程,可能会因为进程的迁移等发生变化
    struct list_head children;  // 进程的子进程列表
    struct list_head sibling;  // 进程的兄弟进程列表,将同一父进程的多个子进程链接在一起
    struct task_struct *group_leader;  // 进程组的领导者进程
    struct fs_struct *fs;  // 进程的文件系统信息,包括文件描述符表等
    struct files_struct *files;  // 进程的打开文件信息,包括文件描述符和打开的文件对象
    struct signal_struct *signal;  // 进程的信号信息,处理进程间的信号传递和处理
    struct sighand_struct *sighand;  // 进程的信号处理函数信息
    sigset_t blocked;  // 进程阻塞的信号集合
    sigset_t real_blocked;  // 进程真正阻塞的信号集合
    sigset_t saved_sigmask;  // 进程保存的信号掩码
    struct thread_info *thread_info;  // 线程信息,包含线程的一些私有信息
    // 还有很多其他成员,以下是一些示例
    // 进程的资源限制,如 CPU 时间、文件大小等
    struct rlimit rlim[RLIM_NLIMITS];
    // 进程的定时器信息
    struct timer_list real_timer;
    // 进程的命名空间,包括用户命名空间、网络命名空间等
    struct nsproxy *nsproxy;
    // 进程的调度类,用于不同的调度算法
    struct sched_class *sched_class;
    // 进程的 CPU 亲和性,指定进程可以运行的 CPU 集合
    cpumask_t cpus_allowed;
    // 进程的上下文切换信息
    unsigned long last_ran;
    // 进程的 I/O 等待信息
    unsigned long nr_iowait;
    // 进程的 CPU 占用统计信息
    u64 utime, stime, gtime, cputime_expires;
    // 进程的唤醒信息
    unsigned long wakee_flips;
    unsigned long wakee_flip_decay_ts;
    // 进程的启动时间
    unsigned long start_time;
    // 进程的公平调度信息
    struct sched_info sched_info;
};

上面代码截取自Linux源码中task_struct中的一部分。
在这里插入图片描述

进程的调度队列并不像我们之前写的链表一样是next和prev指针那么单一的连接的。而是task_struct中有个struct list_head tasks 结构体指针用于将task_struct链接到进程链表中。
在这里插入图片描述
这样链接之后,要想访问整个struct_task中的内容要通过偏移量来访问,现在只能拿到list_head结构体起始地址的值,(struct_head_struct*)(list- &((struct_tast_struct*)0->list_head))。
其实在task_struct中有多个list_head。
在这里插入图片描述
运行状态比较好理解,如上述的调度队列,进程在调度队列中,进程的状态都是running。

阻塞状态

等待某种设备或者资源就绪,如键盘显示器等等外设(操作系统管理各种硬件资源也是先描述在组织)。
下面通过一个例子讲解阻塞状态,注意task_struct为了方便不画细节了。
在这里插入图片描述
操作系统中有struct_dvice结构体管理设备比如一些硬件设备,里面有个等待队列。上图例如输入scanf,scanf也是一个进程但是scanf是从键盘中读数据,此时如果用户没有进行输入。当前的继承就会放在等待队列,等待用户输入数据。这就是阻塞状态。

挂起状态

挂起状态是操作系统中比较极端的状态
阻塞挂起
在这里插入图片描述

阻塞状态中,等待队列可能有多个进程的代码和数据。当内存告急马上就满了的极端状态,操作系统会把阻塞的进程的代码和数据放到磁盘中的swap分区,这叫做唤出。当内存恢复到正常状态,操作系统会把swap交换分区的进程的代码和数据放回到等待队列,这叫做唤入。

运行挂起
与上述原理基本相同,当内存告急处于极端状态的时候,运行队列会唤出到磁盘的swap分区中。恢复正常唤入到原来的运行队列。

三、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): 并不意味着进程⼀定在运行中,它表明进程要么是在运行中要么在进行队列里。
  • S睡眠状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠(interruptible sleep))。
  • D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。
  • T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。
  • X死亡状态(dead):这个状态只是⼀个返回状态,你不会在任务列表里看到这个状态。

R运行状态(running)

#include<stdip.h>
int main()
{
	while(1)
	{
	}
	return 0;
}

上面代码是process.c,让这个代码一只执行。也就是进程一直在调度
在这里插入图片描述
在这里插入图片描述
现在就是R状态,运行状态


S睡眠状态(sleeping)

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
        printf("这是进程 %d ",getpid());
        int x;
        scanf("%d",&x);

        return 0;
}
~

在这里插入图片描述
当我们代码用scanf,等待用户输入。上面我们也讲了这样就会发生阻塞状态。S睡眠状态也就是阻塞状态

s睡眠状态是可中断睡眠,浅睡眠。上面这个例子在s状态下是可以用ctrl+c杀掉的,如下图。

在这里插入图片描述


T/t状态暂停状态

t (tracing stop)
在gdb调试代码的时候,打断点让进程暂停。这就是t暂停状态。从用户角度出发停掉进程
在这里插入图片描述
T (stopped)
一个执行的进程,用ctrl+c停掉就是直接让这个进程停掉。这就是T暂停状态
在这里插入图片描述


D磁盘休眠状态

不可中断休眠,深度睡眠。
举个例子,如果操作系统向磁盘中写入数据,操作系统的任何操作都离不开进程的调度。进程的数据写入了磁盘中,此时内存资源严重不足操作系统可能会自己杀掉进程,但是这时想把磁盘中的数据写入内存找不到对应的进程。此时这段数据就丢失了,上层用户也得不到任何提示。为了解决这一问题就引入了不可中断休眠。
操作系统在任何情况下都不会杀掉D状态的进程。


僵尸状态

僵尸状态(Zombies)是⼀个比较特殊的状态。当进程退出并且父进程(使用wait()系统调用,后面讲)没有读取到子进程退出的返回代码时就会产生僵死(尸)进程
僵死进程会以终止状态保持在进程表中,并且会⼀直在等待父进程读取退出状态代码。
所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态。

#include <stdio.h>//模拟一段僵尸进程
#include <stdlib.h>
int main()
{
	pid_t id = fork();
	if(id < 0){
		perror("fork");
		return 1;
	} 
	else if(id > 0){ 
		//parent
		printf("parent[%d] is sleeping...\n", getpid());
		sleep(30);
	}
	else{
		printf("child[%d] is begin Z...\n", getpid());
		sleep(5);
		exit(EXIT_SUCCESS);
} 
	return 0;
}

在这里插入图片描述

僵尸进程危害

进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果⼀直不读取,那子进程就⼀直处于Z状态。
维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话说,Z状态⼀直不退出,PCB⼀直都要维护
⼀个父进程创建了很多子进程,就是不回收,是不是就会造成内存资源的浪费。因为数据结构对象本身就要占用内存,想想C中定义⼀个结构体变量(对象),是要在内存的某个位置进行开辟空间!

孤儿进程

在这里插入图片描述
父进程如果提前退出,那么子进程后退出,进⼊Z之后,那该如何处理呢?
父进程先退出,子进程就称之为“孤儿进程”
孤儿进程被1号init进程领养,当然要有init进程回收。

;