🔥个人主页🔥:孤寂大仙V
🌈收录专栏🌈:Linux
🌹往期回顾🌹:【Linux】进程概念(PCB)与进程创建(fork)
🔖流水不争,争的是滔滔不
- 一、操作系统进程状态
- 二、运行、阻塞、挂起、
- 运行状态
- 阻塞状态
- 挂起状态
- 三、Linux进程状态
- R运行状态(running)
- S睡眠状态(sleeping)
- T/t状态暂停状态
- D磁盘休眠状态
- 僵尸状态
- 僵尸进程危害
- 孤儿进程
一、操作系统进程状态
进程状态是指一个进程在其生命周期中所处的特定阶段或状况。它反映了进程当前正在进行的活动或者等待的事件,并且决定了操作系统如何对该进程进行调度和资源分配。操作系统通过跟踪进程的状态,合理地分配 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){ //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;
}
僵尸进程危害
进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果⼀直不读取,那子进程就⼀直处于Z状态。
维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话说,Z状态⼀直不退出,PCB⼀直都要维护
⼀个父进程创建了很多子进程,就是不回收,是不是就会造成内存资源的浪费。因为数据结构对象本身就要占用内存,想想C中定义⼀个结构体变量(对象),是要在内存的某个位置进行开辟空间!
孤儿进程
父进程如果提前退出,那么子进程后退出,进⼊Z之后,那该如何处理呢?
父进程先退出,子进程就称之为“孤儿进程”
孤儿进程被1号init进程领养,当然要有init进程回收。