OS20.【Linux】进程状态(2) 僵尸进程、孤儿进程和进程优先级

目录

1.知识回顾

2.僵尸进程

使用exit函数来产生僵尸进程

僵尸进程无法使用kill命令杀死

僵尸进程的危害

3.孤儿进程

结论

4.特殊的孤儿进程: 守护进程和精灵进程

5.进程优先级

前置知识

linux内核获取PCB中所有属性的原理

如何获取成员变量的偏移量?

进一步思考: 如果给出结构体的指针,怎么访问这些成员变量呢?

结论

进程优先级

为什么有优先级?

查看进程优先级的命令

PRI和NI

修改进程的NI值


1.知识回顾

复习OS19.【Linux】进程状态(上)文章

2.僵尸进程

首先明确:一个进程退出时,它的资源不会立刻被操作系统释放

僵尸进程(又称僵死进程): 操作系统需要暂时维持该进程的信息等待父进程的回收(可以形象理解为“收尸”),那么处于此状态下的死亡的子进程称为僵尸进程,Linux上标识为Z状态(Zombie)

-->子进程比父进程先死亡,该子进程为僵尸进程 

使用exit函数来产生僵尸进程

exit函数可以让进程正常退出,需要包含stdlib.h头文件

进一步解释:一般情况下,进程调用exit时,它的绝大多数内存和相关的资源已经被内核释放掉,但在进程表这个进程项中还保留着,等待父进程获取它的退出状态

例如以下代码:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
	pid_t ret_id = fork();
	if (ret_id == 0)
	{//子进程
		int cnt = 5;
		while (cnt--)
		{
			printf("the child process is running... PPID:%d,PID:%d,cnt:%d\n", getppid(), getpid(), cnt);
			sleep(1);
		}
		exit(0);
	}
	else
	{//父进程,不让其读取子进程的信息
		while (1)
		{
			printf("the parent process is running... PPID:%d,PID:%d\n", getppid(), getpid());
			sleep(1);
		}
	}
	return 0;
}

注: 父进程没有调用wait或waitpid获取子进程的状态信息

运行结果:

显然子进程比父进程先死亡,看看子进程的状态:

 ps ajx | head -1 && ps ajx | grep "a.out"

从PPID和PID来看:

从处于僵尸状态的子进程来看,其处于Z状态,而且有标注defunct n.失效的

僵尸进程无法使用kill命令杀死

僵尸进程是已经退出运行的进程,无法被kill命令杀死

僵尸进程的危害

本身子进程死亡是处于僵尸状态是正常的,因为会有父进程来获取信息,之后子进程处于X状态

但如果操作系统中有大量僵尸进程持续存在,尤其是进程的PCB无法释放,需要一直维护,占用内存资源,导致内存泄漏(如何避免后面会讲),而且操作系统容易负载甚至有崩溃的风险

注意:内存泄漏不一定是没有free()或者delete导致的,还有可能是僵尸进程未被父进程回收

例如该文章提到的:randomascii zombie-processes-are-eating-your-memory

详细解释也可以参见linux后台开发中避免僵尸进程的方法总结文章

3.孤儿进程

孤儿进程: 如果父进程比子进程先死亡,处于此状态的下的子进程被称为孤儿进程

修改上方代码,使得父进程先退出:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
	pid_t ret_id = fork();
	if (ret_id == 0)
	{//子进程
		while (1)
		{
			printf("the child process is running... PPID:%d,PID:%d\n", getppid(), getpid());
			sleep(1);
		}
		
	}
	else
	{//父进程
		int cnt = 5;
		while (cnt--)
		{
			printf("the parent process is running... PPID:%d,PID:%d,cnt:%d\n", getppid(), getpid(), cnt);
			sleep(1);
		}
		exit(0);
	}
	return 0;
}

运行结果:

一开始两个进程正常运行:

父进程死亡后:

 会发现子进程的PPID=1,下面查找PID=1 的进程:

ps ajx

1号进程为system,是操作系统本身,也被称为init进程,负责系统的初始化和进程管理

结论

1.父进程比子进程先死亡,子进程为孤儿进程,孤儿进程会被1号进程(新的父进程init)领养,父进程需要对子进程负责

2.领养的原因:因为孤儿进程未来也会退出,也要被释放

3.孤儿进程运行在系统后台(孤儿进程的产生一般都会带有目的性,比如需要一个程序运行在后台或者不想一个进程退出后成为僵尸进程之类的需要)

4.特殊的孤儿进程: 守护进程和精灵进程

这两种是同一种进程的不同翻译,是特殊的孤儿进程,父进程是1号进程.

守护进程和精灵进程不但运行在后台,最主要的是脱离了与终端和登录会话的所有联系,也就是默默的运行在后台不想受到任何影响

5.进程优先级

前置知识

1.一个父进程可以有多个子进程

2.进程可以用多种数据结构来维护:可以是在树中的节点,也可以是在双向链表中的节点-->一个进程既可以属于多叉树,也可以属于双向链表

例如在双向链表中的进程:

Linux内核有双向链表的源代码,头文件位于/include/linux/list.h中,在线查看:https://github.com/torvalds/linux/blob/master/include/linux/list.h

 调试文件位于lib/list_debug.c中,在线查看:https://github.com/torvalds/linux/blob/master/lib/list_debug.c

linux内核获取PCB中所有属性的原理

对于一个多进程的操作系统而言,运行队列的所有进程的PCB都是串在一起的,可以通过指针来遍历PCB

而对于linux,PCB是task_struct,其简化版为:

struct task_struct
{
	//......(省略属性的详细内容)
	struct task_struct* next;
};

假设task_struct有这样一些属性(不全,也不规范):

struct task_struct
{
	char* process_state;
	struct task_struct* next;
	int Process_ID;
	struct cpu* ptr_cpu;
	char* memory_management;
};

如何获取成员变量的偏移量?

查看结构体各个成员变量的偏移量可以用VS的内存布局:

x86:

思路:如果task_struct结构体存储在地址为0的地方(实际上不可能,下面只是利用这个特性获取偏移量),那么成员变量的地址就等于成员变量在结构体在的偏移量

即0号地址存不存在task_struct是不重要的,重要的是取得每个成员变量的偏移

如果直接使用printf打印成员变量会发生段错误:

printf("next's disp==%d", ((struct  task_struct*)(NULL))->next);

运行结果:虽然报错,但VS给出的是正确的偏移量  

解决方法:->访问成员变量,如果再前面加一个&,就能打印出成员变量的地址而并没有访问成员变量

(->是访问,而&只要地址)

printf("next's disp==%d", &(((struct  task_struct*)(NULL))->next));

运行结果:给出的偏移量是正确的

打印所有成员变量的偏移:

int main()
{
	printf("process_state's disp==%d\n", &(((struct  task_struct*)(NULL))->process_state));
	printf("next's disp==%d\n", &(((struct  task_struct*)(NULL))->next));
	printf("Process_ID's disp==%d\n", &(((struct  task_struct*)(NULL))->Process_ID));
	printf("ptr_cpu's disp==%d\n", &(((struct  task_struct*)(NULL))->ptr_cpu));
	printf("memory_management's disp==%d\n", &(((struct  task_struct*)(NULL))->memory_management));
	return 0;
}

运行结果:

进一步思考: 如果给出结构体的指针,怎么访问这些成员变量呢?

要想访问结构体其他的成员变量,需要取得结构体地址

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
struct node
{
	struct node* prev;
	struct node* next;
};
struct task_struct
{
	char* process_state;
	struct node* link;//用于串联task_struct
	int Process_ID;
	struct cpu* ptr_cpu;
	char* memory_management;
};

int main()
{
	struct task_struct* s_ptr = (struct task_struct*)malloc(sizeof(struct task_struct));//假设s_ptr无法直接取得
	printf("s_ptr=%p\n", s_ptr);
	s_ptr->Process_ID = 123;
	//假设只知道link的地址link_address,任务是访问task_struct结构体的其他成员变量,例如Process_ID
	struct task_struct* link_address = (struct task_struct*) & (s_ptr->link);
	//取得task_struct的地址
	int link_disp = (int) & (((struct  task_struct*)(NULL))->link);
	printf("link_address=%p\n", link_address);
	struct task_struct* task_struct_address = (struct task_struct*)((unsigned int)link_address - link_disp);//一定要视为纯数字相减,否则+1意味着跳过一个结构体
	printf("task_struct address==%p\n", task_struct_address);
	if (task_struct_address == s_ptr)
		printf("计算出的task_struct地址正确\n");
	else
		printf("计算出的task_struct地址错误\n");
	//借助task_struct_address访问Process_ID
	printf("Process_ID==%d\n", task_struct_address->Process_ID);
	return 0;
}

运行结果:

 

结论

只需要结构体某个成员变量的地址就能访问该结构体其他的成员变量,因为结构体的每个成员变量的偏移量都能算出来

进程优先级

为什么有优先级?

CPU的资源是有限的,而多任务的操作系统是有多个进程的,那么进程相互之间必定会竞争CPU的资源,那就需要优先级来控制哪个进程先访问资源,哪个进程后访问资源,确保进程之间是良性竞争的

如果进程长时间得不到CPU资源,那么该进程的代码长时间无法得到推进,产生该进程饥饿的问题,在用户层面上来看: 进程”卡”了

简单理解优先级:

1.控制进程对于资源的访问

2.给出让某个进程先访问,其他进程后访问的理由

3.分配CPU资源的先后顺序就是进程的优先级

4.如果是多核CPU,可以把重要进程运行到指定的核心上,把不重要的进程安排到其他核心,这样可以改善系统整体性能

注:非特殊情况不建议更改优先级,优先级是为了保证进程之间公平地调度

查看进程优先级的命令

1.ps

ps l或者ps al都可以

2.top

UID代表执行者的身份(User ID),PRI是priority的缩写,而NI是nice值的缩写,是进程优先级的修正数据

PRI和NI

1.PRI表示进程优先级,值越小,进程的优先级别越高,越早被执行

2.NI表示进程可被执行的优先级的修正数值nice,取值范围为[-20,19],一共40个级别

3.加入nice值后,将会使得PRI变为: PRI(new)=PRI(old)+nice,结论: 调整进程的优先级就是调整nice值,因此更改优先级只会修改NI,不会改PRI

特别强调:进程的nice值不是进程的优先级!

修改进程的NI值

可以修改进程的NI值来达到修改进程的优先级

1.nice命令

使用-n选项来为启动的程序设置初始的优先级,不是设置正在运行的进程!

例如运行./a.out程序,其初始的NI值为8:

sudo nice -n 8 ./a.out  #建议加上sudo,如果不加,可能NI值是改不掉的

运行结果:

可知:PRI(old)=20,nice=8,则PRI(new)=PRI(old)+nice=28

2.renice命令

作用:修改正在运行的进程的优先级

可以使用-n修改

sudo renice -n 新的NI值 -p 进程的PID

3.top命令

步骤:

1.打开 top

2.按下 r 键(代表 renice)

3.输入要调整的进程 PID

4.输入新的 nice 值

演示动图: 

4.Linux桌面版进程优先级的设置

以Linux mint为例.在系统监视器中右击进程选择"改变优先级(C)"

### Linux 常见的进程状态及其含义 在 Linux 系统中,进程状态可以通过 `top` 或者 `ps` 命令查看。以下是常见的进程状态以及它们的具体含义: #### 1. 运行状态 (Running, R) 当一个进程正在运行或者等待 CPU 时间片以便执行时,它处于 **运行状态**。这种状态下分为两种情况:实际运行中的进程准备运行但尚未获得 CPU 的进程[^4]。 #### 2. 可中断睡眠状态 (Sleeping, S) 如果一个进程暂时无法继续执行而进入休眠模式,则其状态标记为 **可中断睡眠状态**。在这种情况下,进程正等待某些事件的发生(例如 I/O 操作完成)。一旦条件满足,进程会被唤醒并恢复到运行状态。 #### 3. 不可中断睡眠状态 (Disk Sleep, D)状态表示进程正处于深度睡眠之中,并且不能被信号打断。通常是因为该进程正在进行重要的硬件操作(比如磁盘读写),直到设备响应为止才会返回正常流程。 #### 4. 停止状态 (Stopped, T/t) 当某个进程接收到 SIGSTOP、SIGTSTP 等暂停信号后就会转变为 **停止状态**。此时不会消耗任何 CPU 资源,除非再次受到启动指令才能重新激活成为活动实体;另外还有一种特殊情况即调试过程中设置断点所引发的 t 标记形式。 ```bash kill -SIGSTOP <pid> # 发送停止信号给指定 PID 的进程 ``` #### 5. 死亡状态 (Dead, X) 这是短暂过渡阶段,在内核清理完毕之后很快消失不见。一般无需特别关注此类状况下的实例存在与否即可。 #### 6. 僵死状态 (Zombie, Z) 所谓僵尸进程是指那些已经结束了自己的生命周期但却未完全释放资源的对象——尽管它们实际上不再占用计算能力却仍然保留着唯一的标识符(PID),直至父程序调用 wait() 函数回收相关信息为止之前一直保持这样的形态存在于系统当中。 #### 7. 孤儿进程 (Orphan Process) 孤儿进程是指它的父进程先于自己终止的情况下的子代单元,默认由 init 进程接管负责后续处理工作以防丢失必要的管理权限[^2]。 #### 8. 特殊标志组合解释 除了上述基础分类之外还有一些复合型标签用于提供更多细节描述: - **Ss+**: 表明这是一个前台进程中属于会话领导者角色的同时也具备较高优先级属性。 - **D+**: 类似常规不可中断态只是额外标注出了当前对象位于前台分组内部位置而已。 - **S<**: 显示出此刻目标拥有高于平均水平调度权值设定情形下呈现出来的外观特征表现出来样子模样如何等等诸如此类现象发生时候应该怎样应对办法措施有哪些可供选择考虑采用实施应用实践探索研究学习交流分享讨论共同进步成长发展成就辉煌未来梦想成真愿望达成目的实现目标圆满成功胜利告捷喜讯传来佳音频传好消息不断涌现层出不穷层出不 ### 示例代码展示 下面提供一段简单的 Python 脚本用来模拟创建新进程并通过 ps 查看其状态变化过程: ```python import os import time def child_process(): print(f"Child process started with pid {os.getpid()}") while True: pass # Simulate a busy loop to keep the process running. if __name__ == "__main__": pid = os.fork() if pid == 0: # Child process logic. child_process() else: print(f"Forked new process with pid {pid}.") try: input("Press Enter key after checking 'ps' command output...") finally: os.wait() # Wait for child termination before exiting parent. print("Parent process ended.") ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhangcoder

赠人玫瑰手有余香,感谢支持~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值