linux 负载进程,Linux 中 D 状态的进程与平均负载
这篇文章聊聊 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 状态的进程与平均负载相关推荐
- 操作系统学习笔记7——进程管理中的数据结构:进程控制块
进程控制块(PCB)--最重要的记录型数据结构 PCB的作用 1.作为独立运行的基本单位的标志: 系统创建一个新进程时,就为它建立一个PCB,进程结束时回收PCB,进程也随之消亡,系统通过感应PCB来 ...
- centos 杀死php进程,CentOS中如何杀掉console-kit-daemon进程?
优化CentOS时,发现有个占用系统资源较多的console-kit-daemon进程, 经google了解到该进程作用是提供桌面端的一个"快速用户切换", 下面我们来看看如何杀掉 ...
- 如何进程linux c,在Linux上,在C中,我如何获得进程的所有线程?
我正在使用的代码,基于读/ proc #include #include #include #include 然后,从一个功能内部: DIR *proc_dir; { char dirname[100 ...
- linux基础篇-系统中进程相关概念
进程概念 内核的功用:进程管理.文件系统.网络功能.内存管理.驱动程序.安全功能等 Process: 运行中的程序的一个副本,是被载入内存的一个指令集合进程ID(Process ID,PID) ...
- linux检查是否有D进程,Linux内核调试技术——进程D状态死锁检测
Linux的进程存在多种状态,如TASK_RUNNING的运行态.EXIT_DEAD的停止态和 TASK_INTERRUPTIBLE的接收信号的等待状态等等(可在include/linux/sched ...
- linux在主函数中调用进程,linux 调用进程
大家好! 请教一个问题:有两个进程(A&B),进程A中通过excel()函数调用进程B.请问进程A如何获取进程B的返回结果? 谢谢! | There is no return from a s ...
- linux怎么增加cpu负载,Linux下的CPU平均负载
linux下的CPU平均负载 一.注销登陆过的用户 先用w命令查看该用户tty号,然后用fuser -k tty号(或显示pts/*)就可以踢出了 先用w命令查看在线用户 然后 pkill -kill ...
- 理解Linux和其他UNIX-Like系统上的平均负载
理解Linux和其他UNIX-Like系统上的平均负载 Linux,Mac以及其他UNIX-like系统都能显示出"load average"信息.这些数字告诉你,你系统 ...
- 怎么理解linux的平均负载及平均负载高后的排查工具
什么是平均负载 平均负载可以对于我们来说及熟悉又陌生,但我们问平均负载是什么,但大部分人都回答说平均负载不就是单位时间内CPU使用率吗?其实并不是这样的,如果可以的话,可以 man uptime 来了 ...
- 1 理解Linux系统的“平均负载”
什么是平均负载 我们知道使用top或uptime可以用来了解系统的负载情况. uptime 2 02:34:03 up 2 days, 20:14, 1 user, load average: 0.6 ...
最新文章
- 驱动保护中的ObjectType_Callback探索
- python创造者_python 设计模式-建造者模式
- legend3---lavarel多对多模型操作实例
- optee的异常向量表-(irq,fiq,svc...)
- Yii的控制器等名称获取
- 【nodejs】安装browser-sync 遇到错误提示
- python取两个列表的并集、交集、差集
- mysql 数据类型 真假_【转】MySQL数据类型
- Pytorch:上采样、下采样
- jdbc mysql wait_timeout_MySql wait_timeout问题解决办法。
- socket.io 消息发送
- mysql判断时间是否在某个区间_如何正确理解 RT 并监控 MySQL 的响应时间
- 前端将图片等文件变成二进制流再存入数据库,并实现二进制流显示及下载
- 高效办公必备神器-Keychron键盘,值得拥有
- 分享两款免费的流程图、原型图工具
- aspose.words生成pdf字体乱码为方框
- win10家庭版破解lr11遇到的问题
- 文件服务器 磁盘配额,文件服务器磁盘配额管理.doc
- photoshop实用技巧
- MAC直接的剪切快捷键