Linux下进程的状态和优先级

一、进程的状态

1.1三种状态:运行、阻塞、挂起

1.2Linux内核关于运行的状态

Linux内核源代码

在Linux内核里,进程有时候也叫做任务)下面的状态在kernel源代码里定义:

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):这个状态只是一个返回状态,你不会在任务列表⾥看到这个状态

1.3进程状态查看

命令:ps aux/ps axj

• a:显示一个终端所有的进程,包括其他用户的进程

• x:显示没有控制终端的进程,例如后台运行的守护进程

• j:显示进程归属的进程组ID、会话ID、父进程ID,以及与作业控制相关的信息

• u:以用户为中心的格式显⽰进程信息,提供进程的详细信息,如用户、CPU和内存使用情况等

以下是Linux进程状态图:

1.4僵尸状态

1.4.1僵尸状态的概念以及验证

• 僵死状态(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;
}

1.4.2僵尸状态的危害:

• 进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我

办的怎么样了。可父进程如果一直不读取,那子进程就一直处于Z状态

• 维护退出状态本身就是要⽤数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话说,Z状态一直不退出,PCB一直都要维护

• 那一个父进程创建了很多⼦进程,就是不回收,是不是就会造成内存资源的浪费?是的!因为数

据结构对象本身就要占用内存,想想C中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空间!

• 内存泄漏

1.5孤儿进程

• 父进程如果提前退出,那么子进程后退出,进入Z之后,那该如何处理呢?

• 父进程先退出,子进程就称之为"孤儿进程"

• 孤儿进程被1号init / systemd进程领养,当然要有init/systemd进程回收喽。

代码验证:

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
	pid_t id = fork();
	if (id < 0) {
		perror("fork");
		return 1;
	}
	else if (id == 0) {//child
		printf("I am child, pid : %d\n", getpid());
		sleep(10);
	}
	else {//parent
		printf("I am parent, pid: %d\n", getpid());
		sleep(3);
		exit(0);
	}
	return 0;
}

二、进程优先级

2.1基本概念

• cpu资源分配的先后顺序,就是指进程的优先权(priority)

• 优先权⾼的进程有优先执行权利。配置进程优先权对多任务环境的linux很有用,可能改善系统性能

• 还可以把进程运行到指定的CPU上,这样一来,把不重要的进程安排到某个CPU,可以大大改善

系统整体性能。

2.2查看系统进程

在linux或者unix系统中,用ps ‒al命令则会类似输出以下几个内容:

我们很容易注意到其中的几个重要信息,有下:

• UID:代表执行者的身份

• PID:代表这个进程的代号

• PPID:代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号

• PRI:代表这个进程可被执行的优先级,其值越⼩越早被执行

• NI:代表这个进程的nice值

2.3PRIandNI

• PRI也还是⽐较好理解的,即进程的优先级,或者通俗点说就是程序被CPU执⾏的先后顺序,此

值越⼩进程的优先级别越高

• NI就是我们所要说的nice值了,其表示进程可被执行的优先级的修正数值

• PRI值越⼩越快被执⾏,那么加⼊nice值后,将会使得PRI变为:PRI(new) = PRI(old) + nice

• 这样,当nice值为负值的时候,那么该程序将会优先级值将变⼩,即其优先级会变⾼,则其越快

被执行

• 所以,调整进程优先级,在Linux下,就是调整进程nice值

• nice其取值范围是 - 20⾄19,⼀共40个级别

2.4 PRI vs NI

• 需要强调一点的是,进程的nice值不是进程的优先级,他们不是一个概念,但是进程nice值会影

响到进程的优先级变化。

• 可以理解nice值是进程优先级的修正数据

2.5查看进程优先级的命令

用top命令更改已存在进程的nice:

• top

• 进入top后按"r"-> 输入进程PID-> 输入nice值

注意:其他调整优先级的命令:nice,renice

2.6竞争、独立、并行、并发

• 竞争性 : 系统进程数⽬众多,而CPU资源只有少量,甚⾄1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级

• 独立性 : 多进程运行,需要独享各种资源,多进程运行期间互不干扰

• 并行 : 多个进程在多个CPU下分别,同时进行运行,这称之为并行

• 并发 : 多个进程在一个CPU下采⽤进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发

2.7进程切换

CPU上下文切换:其实际含义是任务切换, 或者CPU寄存器切换。当多任务内核决定运⾏另外的任务时, 它保存正在运⾏任务的当前状态, 也就是CPU寄存器中的全部内容。这些内容被保存在任务自己的堆栈中入栈工作完成后就把下一个将要运行的任务的当前状况从该任务的栈中重新装入CPU寄存器并开始下一个任务的运行, 这一过程就是context switch。

注意:

时间片:当代计算机都是分时操作系统,没有进程都有它合适的时间片(其实就是一个计数器)。时间片到达,进程就被操作系统从CPU中剥离下来。

2.8Linux2.6内核进程O(1)调度队列

2.8.1一个CPU拥有一个runqueue

• 如果有多个CPU就要考虑进程个数的负载均衡问题

2.8.2优先级

• 普通优先级:100〜139(我们都是普通的优先级,想想nice值的取值范围,可与之对应!)

• 实时优先级:0〜99(不关⼼)

2.8.3活动队列

• 时间片还没有结束的所有进程都按照优先级放在该队列

• nr_active : 总共有多少个运⾏状态的进程

• queue[140]:一个元素就是⼀个进程队列,相同优先级的进程按照FIFO规则进⾏排队调度, 所以,

数组下标就是优先级

• 从该结构中,选择⼀个最合适的进程,过程是怎么的呢?

1.从0下表开始遍历queue[140]

2.找到第一个非空队列,该队列必定为优先级最⾼的队列

3.拿到选中队列的第一个进程,开始运⾏,调度完成

4.遍历queue[140]时间复杂度是常数!但还是太低效了

• bitmap[5]:一共140个优先级,一共140个进程队列,为了提⾼查找非空队列的效率,就可以⽤

5 * 32个比特位表示队列是否为空,这样,便可以大大提⾼查找效率

2.8.4过期队列

• 过期队列和活动队列结构一模一样• 过期队列和活动队列结构模样

• 过期队列上放置的进程,都是时间片耗尽的进程

• 当活动队列上的进程都被处理完毕之后,对过期队列的进程进⾏时间片重新计算

2.8.5active指针和expired指针

• active指针永远指向活动队列

• expired指针永远指向过期队列

• 可是活动队列上的进程会越来越少,过期队列上的进程会越来越多,因为进程时间片到期时一直

都存在的

• 没关系,在合适的时候,只要能够交换active指针和expired指针的内容,就相当于有具有了⼀批

新的活动进程

2.8.6代码

cpp 复制代码
struct rq {
	spinlock_t lock;
	/*
	* nr_running and cpu_load should be in the same cacheline because
	* remote CPUs use both these fields when doing load calculation.
	*/
	unsigned long nr_running;
	unsigned long raw_weighted_load;
#ifdef CONFIG_SMP
	unsigned long cpu_load[3];
#endif
	unsigned long long nr_switches;
	/*
	* This is part of a global counter where only the total sum
	* over all CPUs matters. A task can increase this counter on
	* one CPU and if it got migrated afterwards it may decrease
	* it on another CPU. Always updated under the runqueue lock:
	*/
	unsigned long nr_uninterruptible;
	unsigned long expired_timestamp;
	unsigned long long timestamp_last_tick;
	struct task_struct* curr, * idle;
	struct mm_struct* prev_mm;
	struct prio_array* active, * expired, arrays[2];
	int best_expired_prio;
	atomic_t nr_iowait;
#ifdef CONFIG_SMP
	struct sched_domain* sd;
	/* For active balancing */
	int active_balance;
	int push_cpu;
	struct task_struct* migration_thread;
	struct list_head migration_queue;
#endif
#ifdef CONFIG_SCHEDSTATS
	/* latency stats */
	struct sched_info rq_sched_info;
	/* sys_sched_yield() stats */
	unsigned long yld_exp_empty;
	unsigned long yld_act_empty;
	unsigned long yld_both_empty;
	unsigned long yld_cnt;
	/* schedule() stats */
	unsigned long sched_switch;
	unsigned long sched_cnt;
	unsigned long sched_goidle;
	/* try_to_wake_up() stats */
	unsigned long ttwu_cnt;
	unsigned long ttwu_local;
#endif
	struct lock_class_key rq_lock_key;
};
/*
 * These are the runqueue data structures:
 */
struct prio_array {
	unsigned int nr_active;
	DECLARE_BITMAP(bitmap, MAX_PRIO + 1); /* include 1 bit for delimiter */
	struct list_head queue[MAX_PRIO];
};

三、代码测试和相关概念解说:

相关推荐
稚辉君.MCA_P8_Java2 小时前
Sqoop 实现的功能是什么
java·服务器·架构·kubernetes·sqoop
普通网友2 小时前
单元测试在C++项目中的实践
开发语言·c++·算法
Croa-vo2 小时前
逆袭Akuna Quant!美硕秋招亲历,从网申到拿offer全攻略
数据结构·经验分享·算法·面试·职场和发展
adnyting2 小时前
【Linux日新月异(八)】CentOS 7系统资源监控与排查深度指南:性能调优全景图
linux·运维·centos
IT瑞先生2 小时前
Docker容器使用手册——进阶篇(下)
运维·docker·容器
HIT_Weston2 小时前
32、【Ubuntu】【远程开发】内网穿透:远程主机连接
linux·运维·ubuntu
yolo_guo2 小时前
WSL2 安装 centos6.9 子系统
linux
p66666666682 小时前
【☀Linux驱动开发笔记☀】新字符设备驱动开发_02
linux·嵌入式硬件·学习
從南走到北2 小时前
JAVA同城服务场馆预约门店预约健身房瑜伽馆预约系统支持H5小程序APP源码
java·开发语言·小程序