🔥个人主页🔥:孤寂大仙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进程回收。