【Linux】进程状态

🔥个人主页🔥:孤寂大仙V

🌈收录专栏🌈:Linux

🌹往期回顾🌹:【Linux】进程概念(PCB)与进程创建(fork)

🔖流水不争,争的是滔滔不


一、操作系统进程状态

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

二、运行、阻塞、挂起、

运行状态

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

c 复制代码
// 进程结构体 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内核源码

cpp 复制代码
*
*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)

cpp 复制代码
#include<stdip.h>
int main()
{
	while(1)
	{
	}
	return 0;
}

上面代码是process.c,让这个代码一只执行。也就是进程一直在调度

现在就是R状态,运行状态


S睡眠状态(sleeping)

cpp 复制代码
#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状态。

cpp 复制代码
#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进程回收。

相关推荐
ccnnlxc10 分钟前
日志收集Day001
运维·云原生
黄团团27 分钟前
Redis超详细入门教程(基础篇)
服务器·数据库·redis·缓存
镭速29 分钟前
服务器下发任务镭速利用变量实现高效的大文件传输效率
运维·服务器
kongba0071 小时前
运行fastGPT 第一步 安装Ubuntu Server 20.04 LTS 对系统做初始化配置
linux·运维·ubuntu
深度Linux1 小时前
C++性能优化指南:探索无锁队列设计与实现
linux·c++·性能优化·无锁队列
QotomPC1 小时前
Qotom Q10922H6 N100多网口无风扇迷你电脑2个10G和4个2.5G网口
服务器·边缘计算
Linux运维老纪1 小时前
备份和容灾之区别(The Difference between Backup and Disaster Recovery)
linux·运维·服务器·数据库·安全·云计算·运维开发
久绊A2 小时前
Linux 文件权限详解
linux·运维·服务器
大江东去浪淘尽千古风流人物2 小时前
git系列之revert回滚
服务器·git·github
朝阳392 小时前
windows 极速安装 Linux (Ubuntu)-- 无需虚拟机
linux·windows·ubuntu