这篇文章聊聊 Linux 中 D 状态的进程与平均负载的关系,通过阅读本文,你会了解到这些东西。

D 状态的进程是什么

如何编写内核模块模拟 D 状态进程

Linus 对 D 状态进程的看法

平均负载的概念

在 top 和 uptime 命令输出中的第一行有一个 load average 字段,由三个数字表示,依次表示过去 1 分钟、5 分钟、15 分钟的平均负载(Load Average),如下图所示。

值得注意的是,平均负载并不是指 CPU 的负载,这也比好理解,毕竟系统资源并不是只有 CPU 这一个。简单来看,平均负载是指单位时间内,系统处于 可运行状态 和 不可中断状态 的平均进程数,也就是平均活跃进程数。实际的计算比较复杂,感兴趣的同学可以查看源码 github.com/torvalds/li… 。

从直观的角度理解,如果平均负载为 2,在 4 核的机器上,表示有 50% 的 CPU 空闲;在 2 核的机器上,表示刚好没有 CPU 空闲,如果是单核的机器,那表明 CPU 竞争激烈,有一半的进程竞争不到 CPU。

进程运行的几种状态如下图所示。

当使用 fork() 等系统调用来创建一个新进程时,新进程的状态是 Ready 状态,在 linux 中,就绪态的进程也属于 TASK_RUNNING 状态,这个时候只是还没有拿到 CPU 的使用权。

图中 Ready 和 Running 状态的进程都属于「可运行状态」的进程,对应 top 命令中 R 标记。

处于 Running 状态的进程在等待某些事件或资源时会进入 Blocked 状态。可中断的进程(TASK_INTERRUPTIBLE)可以被信号和 wakeup 唤醒,重新进入 Ready 就绪状态,对应于 top 中标记为 S 的进程。那不可中断(TASK_UNINTERRUPTIBLE)状态到底是个什么鬼?

D 状态的进程

TASK_UNINTERRUPTIBLE 在 top 命令中显示为 D 标记,也就是大名鼎鼎的 「D 状态」进程。顾名思义,处于 TASK_UNINTERRUPTIBLE 状态的进程不能被信号唤醒,只能由 wakeup 唤醒。既然 TASK_UNINTERRUPTIBLE 不能被信号唤醒,自然也不会响应 kill 命令,就算是必杀 kill -9 也不例外。

“不可中断”指的是当前正处于内核中的关键流程,不可以被打断,比较常见的是读取磁盘文件的过程中被打断去处理信号,读到的内容就是不完整的。

从侧面来看,磁盘的驱动是工作在内核中的,如果磁盘出现了故障,磁盘读不到数据,内核就陷入了很尴尬的两难局面,这个锅只能自己扛着,将进程标记为不可中断,谁让磁盘驱动是跑在内核中呢。

之前有人给大神 Linus 发信希望移除 TASK_UNINTERRUPTIBLE 这个状态,Linus 在 kernel.org 邮件组中专门回答过为什么 D 状态的进程必不可少,链接如下 lore.kernel.org/lkml/Pine.L… ,我截了一个图放在了下面。

如果只是这些问题,倒也平平无奇,不关我们什么事,但是需要注意的是 D 状态的进程会增加系统的平均负载。

下面我们来演示一下,如何通过编写一个系统内核模块,实现一个设备驱动文件,稳定复现展示 D 状态的进程,然后观察系统负载的变化。

内核模块编写

编写一个内核模块非常简单,新建一个 my_char_dev.c 文件,基本的框架如下所示。

int my_module_init(void) {

printk("my module loaded\n");

return 0;

}

void my_module_exit(void) {

printk("my module unloaded\n");

}

module_init(my_module_init);

module_exit(my_module_exit);

MODULE_LICENSE("GPL");

MODULE_AUTHOR("Arthur.Zhang");

MODULE_DESCRIPTION("A simple char device driver");

module_init 和 module_exit 用来定义内核模块的加载和卸载函数入口。 printk 用于打印内核打印,使用 dmesg 可以查看输出的信息。

然后编写一个 Makefile 文件,如下所示。

obj-m += my_char_dev.o

all:

make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:

make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

insmod:

sudo insmod my_char_dev.ko

rmmod:

sudo rmmod my_char_dev.ko

执行 make 编译上面的文件,会生成一个 my_char_dev.ko 文件,随后使用 insmod 加载这个内核模块

sudo insmod my_char_dev.ko

然后使用 dmesg -T 就可以看到调用了 module_init 回调函数,打印了内核模块加载成功语句。

[Wed Apr 22 02:52:07 2020] my module loaded

使用 rmmod 可以卸载这个模块。

sudo rmmod my_char_dev.ko

同样使用 dmesg -T ,可以看到调用了 module_exit 回调函数。

[Wed Apr 22 02:54:46 2020] my module unloaded

接下来实现画马的最后一步,给这个内核模块添加字符设备读取写入的逻辑

也来添加一下其他的细节,代码如下所示。

#define DEVICE_NAME "mychardev"

int major_num;

struct file_operations fops = {

.owner = THIS_MODULE,

.open = my_device_open,

.release = my_device_release,

.read = my_device_read,

.write = my_device_write,

};

/**

* 内核模块初始化

*/

int my_module_init(void) {

printk("my module loaded\n");

// register_chrdev 函数的 major 参数如果等于 0,则表示采用系统动态分配的主设备号

major_num = register_chrdev(0, DEVICE_NAME, &fops);

if (major_num < 0) {

printk("Registering char device failed with %d\n", major_num);

return major_num;

}

// 接下来使用 class_create 和 device_create 自动创建 /dev/mychardev 设备文件

my_class_class = class_create(THIS_MODULE, DEVICE_NAME);

device_create(my_class_class, NULL, MKDEV(major_num, 0), NULL, DEVICE_NAME);

return 0;

}

/**

* 内核模块卸载

*/

void my_module_exit(void) {

device_destroy(my_class_class, MKDEV(major_num, 0));

class_destroy(my_class_class);

unregister_chrdev(major_num, DEVICE_NAME);

printk("my module unloaded\n");

}

这里首先在内核模块初始化回调中使用 register_chrdev 函数注册一个字符设备驱动,随后使用 class_create 和 device_create 函数创建 /dev/mychardev 设备文件。同时定义了这个设备文件的 open、release、read、write 处理函数。

static int my_device_open(struct inode *inode, struct file *file) {

printk("%s\n", __func__);

return 0;

}

static int my_device_release(struct inode *inode, struct file *file) {

printk("%s\n", __func__);

return 0;

}

static ssize_t my_device_read(struct file *file,

char *buffer, size_t length, loff_t *offset) {

printk("%s %u\n", __func__, length);

return 0;

}

static ssize_t my_device_write(struct file *file,

const char *buffer, size_t length, loff_t *offset) {

printk("%s %u\n", __func__, length);

return length;

}

重现编译生成新的 ko 文件,加载运行,会生成一个 /dev/mychardev 设备驱动文件,如下所示。

$ ls -l /dev/mychardev

crw-------. 1 root root 245, 0 Apr 22 20:07 /dev/mychardev

接下来可以使用 cat 和 echo 对这个设备文件进行读写。

sudo cat /dev/mychardev

dmesg 输出

[Wed Apr 22 02:07:31 2020] my_device_open

[Wed Apr 22 02:07:31 2020] my_device_read 65536

[Wed Apr 22 02:07:31 2020] my_device_release

sudo sh -c "echo hello > /dev/mychardev"

[Wed Apr 22 02:09:20 2020] my_device_open

[Wed Apr 22 02:09:20 2020] my_device_write 6

[Wed Apr 22 02:09:20 2020] my_device_release

接下,我们做细微的调整,让 cat 输出 "hello, world!",修改代码如下所示。

static char msg[] = "hello, world!\n";

char *p;

/**

* 设备文件打开的回调

*/

static int my_device_open(struct inode *inode, struct file *file) {

printk("%s\n", __func__);

p = msg;

return 0;

}

/**

* 处理 cat 等读取该设备文件的逻辑,返回 "hello, world!" 字符串到用户终端输出

*/

static ssize_t my_device_read(struct file *file,

char *buffer, size_t length, loff_t *offset) {

printk("%s %u\n", __func__, length);

int bytes_read = 0;

if (*p == 0) return 0;

while (length && *p) {

put_user(*(p++), buffer++);

length--;

bytes_read++;

}

return bytes_read;

}

这时,使用 cat 就可以在终端中看到输出的 "hello, world!" 字符串了,如下所示。

$ sudo cat /dev/mychardev

hello, world!

接下来我们来进入主题,在用户读取 2 次以后将状态设置为 TASK_UNINTERRUPTIBLE,修改 my_device_open 的代码

static int my_device_open(struct inode *inode, struct file *file) {

printk("%s\n", __func__);

// 使用一个静态的局部变量,记录设备文件打开的次数, 每次 cat,这个 counter 加一

static int counter = 0;

if (counter == 2) {

__set_current_state(TASK_UNINTERRUPTIBLE); //改变进程状态为睡眠

schedule();

}

p = msg;

++counter;

return 0;

}

再次编译加载这个文件,执行几次 cat,会发现在第 3 次的时候,cat 阻塞没有输出,如下所示。

使用 top 命令查看 cat 进程的状态。

可以看到 cat 进程的状态为 D,CPU 占用为 0%,但是系统的 load average 在持续升高,运行一段时间会稳定到达 1,如下所示。

如果再启动两个 cat,那么 load average 会升高到 3,如下所示。

到这里我们就非常快速的模拟了 D 状态,以及观察了 D 状态对系统的 load average 的影响。希望能给你提供一些不一样的方式,加深你对平均负载的理解。

linux 负载进程,Linux 中 D 状态的进程与平均负载相关推荐

  1. 操作系统学习笔记7——进程管理中的数据结构:进程控制块

    进程控制块(PCB)--最重要的记录型数据结构 PCB的作用 1.作为独立运行的基本单位的标志: 系统创建一个新进程时,就为它建立一个PCB,进程结束时回收PCB,进程也随之消亡,系统通过感应PCB来 ...

  2. centos 杀死php进程,CentOS中如何杀掉console-kit-daemon进程?

    优化CentOS时,发现有个占用系统资源较多的console-kit-daemon进程, 经google了解到该进程作用是提供桌面端的一个"快速用户切换", 下面我们来看看如何杀掉 ...

  3. 如何进程linux c,在Linux上,在C中,我如何获得进程的所有线程?

    我正在使用的代码,基于读/ proc #include #include #include #include 然后,从一个功能内部: DIR *proc_dir; { char dirname[100 ...

  4. linux基础篇-系统中进程相关概念

    进程概念  内核的功用:进程管理.文件系统.网络功能.内存管理.驱动程序.安全功能等  Process: 运行中的程序的一个副本,是被载入内存的一个指令集合进程ID(Process ID,PID) ...

  5. linux检查是否有D进程,Linux内核调试技术——进程D状态死锁检测

    Linux的进程存在多种状态,如TASK_RUNNING的运行态.EXIT_DEAD的停止态和 TASK_INTERRUPTIBLE的接收信号的等待状态等等(可在include/linux/sched ...

  6. linux在主函数中调用进程,linux 调用进程

    大家好! 请教一个问题:有两个进程(A&B),进程A中通过excel()函数调用进程B.请问进程A如何获取进程B的返回结果? 谢谢! | There is no return from a s ...

  7. linux怎么增加cpu负载,Linux下的CPU平均负载

    linux下的CPU平均负载 一.注销登陆过的用户 先用w命令查看该用户tty号,然后用fuser -k tty号(或显示pts/*)就可以踢出了 先用w命令查看在线用户 然后 pkill -kill ...

  8. 理解Linux和其他UNIX-Like系统上的平均负载

    理解Linux和其他UNIX-Like系统上的平均负载      Linux,Mac以及其他UNIX-like系统都能显示出"load average"信息.这些数字告诉你,你系统 ...

  9. 怎么理解linux的平均负载及平均负载高后的排查工具

    什么是平均负载 平均负载可以对于我们来说及熟悉又陌生,但我们问平均负载是什么,但大部分人都回答说平均负载不就是单位时间内CPU使用率吗?其实并不是这样的,如果可以的话,可以 man uptime 来了 ...

  10. 1 理解Linux系统的“平均负载”

    什么是平均负载 我们知道使用top或uptime可以用来了解系统的负载情况. uptime 2 02:34:03 up 2 days, 20:14, 1 user, load average: 0.6 ...

最新文章

  1. 驱动保护中的ObjectType_Callback探索
  2. python创造者_python 设计模式-建造者模式
  3. legend3---lavarel多对多模型操作实例
  4. optee的异常向量表-(irq,fiq,svc...)
  5. Yii的控制器等名称获取
  6. 【nodejs】安装browser-sync 遇到错误提示
  7. python取两个列表的并集、交集、差集
  8. mysql 数据类型 真假_【转】MySQL数据类型
  9. Pytorch:上采样、下采样
  10. jdbc mysql wait_timeout_MySql wait_timeout问题解决办法。
  11. socket.io 消息发送
  12. mysql判断时间是否在某个区间_如何正确理解 RT 并监控 MySQL 的响应时间
  13. 前端将图片等文件变成二进制流再存入数据库,并实现二进制流显示及下载
  14. 高效办公必备神器-Keychron键盘,值得拥有
  15. 分享两款免费的流程图、原型图工具
  16. aspose.words生成pdf字体乱码为方框
  17. win10家庭版破解lr11遇到的问题
  18. 文件服务器 磁盘配额,文件服务器磁盘配额管理.doc
  19. photoshop实用技巧
  20. MAC直接的剪切快捷键

热门文章

  1. yii2怎样写规则可以隐藏url地址里的控制器名字
  2. Search in Rotated Sorted Array leetcode java
  3. 获取选择的当前天、周、月、年的时间段
  4. HTML学习笔记(七)
  5. !学习笔记:前端测试 、前端调试、console 等
  6. 各种推荐算法的 benchmark
  7. 复旦NLP组:大厂模型的鲁棒性进步了吗?
  8. 首个任务型对话系统中生成模块资源库Awesome-TOD-NLG-Survey开源!
  9. 【代码技巧】21个经典深度学习句间关系模型
  10. 打开你的脑洞:NER如何进行数据增强 ?