判断Linux 进程运行在哪个 CPUlinux 内核进程通信上的 4 个方法

博客访问: 879012
博文数量: 46
博客积分: 710
博客等级: 中校
技术积分: 7280
注册时间:
提升自已,分享别人
APP发帖 享双倍积分
IT168企业级官微
微信号:IT168qiye
系统架构师大会
微信号:SACC2013
分类: LINUX
linux设备驱动归纳总结(四):4.单处理器下的竞态和并发
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
经过上面几节的铺垫,终于要来重点了,由于内核的进程调度和中断(中断还没讲,不过这里会大概的说说),它们都会进入内核共用内核的资源。所以,只要一不留神,自己进程的资源就会在不经意的情况下被别的进程修改了。这节将介绍并讨论如何解决。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
一、什么是并发
所谓的并发,就是多个进程同时、并行执行。在单处理器的情况下,并发只是宏观上的,用户会感觉多个程序共同执行,其实只不过是多个进程轮流占用处理器运行,只有在多处理器的情况下才会实现真正的同时执行。
但是,不管单处理器还是多处理器,内核中的并发都会引起共享资源的并发访问。举个简单的例子,有两个相同代码的进程并发执行,它们都是要修改存在与内核中个一个数据data。
情况一:没有出错:
上面的举例是在单处理器的情况下,内核调度AB进程分别在处理器上运行,并且运行完A再运行B,情况相当理想,并没有出错。
情况二:出错了。
同样是单处理器的情况下,但是却出现问题了,进程A执行到一半,内核调度进程B执行,等进程B执行完后再回来执行A。细心一想就会发现,不对劲了,进程B等于白干了!我最后保存的只是执行进程B前的data。
上面只是想说明,在并发执行的情况下,我们根本不能预料到进程什么时候会被调度,一些内核中的共享资源就要有一些相应的保护。白干还没什么大不了,如果搞到系统崩溃就糟糕了。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
二、什么是临界区和竞争条件
临界区就是访问和操作共享数据的代码段。之前说过,进程并发访问共享资源是不安全的,那是因为它访问临界区的数据。如果两个进程同时出去临界区,那就会发生资源的抢夺,这个情况就叫做竞争条件。避免并发和防止竞争条件被成为同步,这将是接下来要重点讨论的内容。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
三、什么会造成内核中的并发
注意,进程不管是在用户空间还是内核空间都是并发执行的,但是这里主要的是讨论内核中的并发,用户空间中的并发应该是在系统编程时了解的内容,如多进程共享文件等。
在以下情况下内核会造成并发执行:
1)中断:中断是随时可以产生,内核一旦接收到中断,就会放下手头上的工作,优先执行中断。如果中断代码中修改了之前运行进程的共享资源,这样就会出现bug。
2)内核抢占:前一节已经介绍,支持内核抢占的情况下,正在执行的进程随时都有可能被抢占。
3)睡眠:当在内核中执行的进程睡眠,此时就唤醒调度程序,调度新的进程执行。
4)多处理器:多个处理器就能同时执行多个进程。这是真正的同时执行,、。
既然知道了在什么情况下会造成并发,在编写代码时,就要考虑到临界区的保护,在临界区中,避免上述情况的发生会可以避免并发,从而保护共享资源,这就是内核同步。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
四、单处理器不支持内核抢占的情况下
我现在使用的内核版本2.6.29就是不支持内核抢占。分析两种情况。
情况一,两个进程之间:
在单处理器不支持抢占的情况下,运行在内核的两个内核线程,并不会产生并发。
情况二,中断与进程之间:
中断上下文与普通内核线程之间,会产生并发。在内核线程正在执行时,随时会有可能被中断打断。所以,在临界区的代码,可以通过关闭中断来避免并发。
先写个程序看看在没关中断的情况下,中断可以打断正在执行的内核线程。
例子源代码在:4th_mutex_4/1st
首先看一下驱动的代码怎么写:
/*1st/test.c*/
ssize_t test_read(struct file *filp, char __user *buf, size_t count,
loff_t *offset)
unsigned long flag = 0;
printk("[%s]task pid[%d], context [%s]\n",
__FUNCTION__, current->pid, current->comm);
mdelay(2000);
return count -
先声明一下,这个驱动程序实现的功能跟我说的完全不一样,,这里只是想实现内核线程在内核中死循环,除了中断能够打断test_read函数的执行,应用程序想冒个泡都不可以。
驱动函数出来了,接下来要看看应用代码怎么实现:
/*app/app.c*/
int main(void)
printf(" runing\n");
这个应用程序并不需要进入内核操作,他只是每隔两秒就打印一句话。
再看一下另外一个应用程序:
/*app/app_read.c*/
int main(void)
char buf[20];
fd = open("/dev/test", O_RDWR);
if(fd < 0)
perror("open");
return -1;
printf(" pid[%d]\n", getpid());
read(fd, buf, 10);
这个当这个程序调用read系统调用时,内核会调用test_read,此时,进程就会在内核中陷入循环。
另外,还需要注册一个中断,当我按下开发板上的按键时,就会打印出”key
down”。因为还没有介绍中断的实现,这里先不讲解代码。(这里的中断是按照我的开发板来写得,按键对应EINT1,所以可以你们加载后没效果)
看看实验效果:
1st]# cd irq/
irq]# insmod irq.ko
//注册中断,其实也是记载模块
irq]# cd ../
1st]# insmod test.ko
//加载模块
major[253], minor[0]
1st]# mknod /dev/test c 253 0
1st]# cd app/
app]# ./app&
//后台运行app
//app在欢快地运行
app]# ./app_read
//运行app_read
[test_open]
[test_read]task
pid[404], context [app_read]
//进程在内核中陷入循环,在不支持内核
[test_read]task
pid[404], context [app_read]
//抢占情况下,即使进程睡眠,应用空间
[test_read]task
pid[404], context [app_read]
//进程app也不能获得调度。因为进程
[test_read]task
pid[404], context [app_read]
//没有返回用户空间。
[test_read]task
pid[404], context [app_read]
[test_read]task
pid[404], context [app_read]
//但是,当我按下按键,中断产生,内核
[test_read]task
pid[404], context [app_read]
//执行中断处理函数。
[test_read]task
pid[404], context [app_read]
//用户空间的进程不能打印。
[test_read]task
pid[404], context [app_read]
[test_read]task
pid[404], context [app_read]
上面的例子说明了两个情况:
1)只要进程运行在内核上下文,就不会被内核抢占去执行&别的进程。
2)但是,中断会打断正在内核运行的进程,出现并发。
当然,我的test_read函数只是打印一句话,如果我的函数正在修改共享资源,这时是不能允许中断产生并且修改正在被使用的共享资源。所以,为了避免并发和防止竞争条件,只需要把中断关闭就可以了。
接下来讲一下关闭中断的方法:
local_irq_disable(); //关中断
/*执行临界区代码*/
local_irq_enable(); //开中断
但是,这种方法有缺陷,如果内核本来就是关闭中断的,上面的代码却在最后把中断打开了,这是多不合理的做法。所以有了下面的函数:
local_irq_save(flag); //在关中断前,先报存原来的中断状态
/*执行临界区代码*/
local_irq_restore(flag); //开启中断,然后还原原来的中断状态
通过上面的代码,就可以解决上面所说的缺陷。还要注意的是,关中断的时间不能太长。
现在改进一下原来的代码,在访问临界资源时关闭中断:
/*4th_mutex_4/2nd/test.c*/
ssize_t test_read(struct file *filp, char __user *buf, size_t count,
loff_t *offset)
unsigned long flag = 0;
local_irq_save(flag);
//假设这是临界区
printk("[%s]task pid[%d], context [%s]\n",
__FUNCTION__, current->pid, current->comm);
local_irq_restore(flag);
mdelay(2000);
return count -
添加了这两句代码,在访问临界区时就不会被中断打断了。上面的代码我就不验证了,也验证不出效果,因为临界区太小了,通过我按键产生的中断进入临界区的概率自然就小,只要大家知道通过关中断就能防止中断处理函数打断原来的进程就行了。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
五、单处理器又支持内核抢占的情况下
同样分析上面的两种情况:
情况一,两个进程之间:
在单处理器支持抢占的情况下,运行在内核的两个内核线程,会产生并发。访问临界区时需要关抢占。
情况二,中断与进程之间:
中断上下文与普通内核线程之间,会产生并发。在内核线程正在执行时,随时会有可能被中断打断。所以,在临界区的代码,可以通过关闭中断来避免并发。
注意:在我的开发板2.6.29的内核是不支持内核抢占的,为了能够支持内核抢占,需要打开以下选项并重新编译内核。
1、General
for development and/or incomplete code/drivers
/*选择使用开发中的驱动代码*/
Preemptible
/*选择开启抢占式内核*/
重现编译并运行1st目录的代码,你会发现跟原来不一样的地方:
1st]# insmod test.ko
//加载模块
major[253], minor[0]
1st]# mknod /dev/test c 253 0
1st]# cd irq/
irq]# insmod irq.ko
//加载中断
irq]# cd ../app/
app]# ./app&
//后天运行app
//app欢快地独自运行
app]# ./app_read
//再运行app_read
[test_open]
[test_read]task
pid[401], context [app_read]
//在支持内核抢占下,app和app_read交替运行
[test_read]task
pid[401], context [app_read]
[test_read]task
pid[401], context [app_read]
//当我按下按键后,中断马上处理中断函数
[test_read]task
pid[401], context [app_read]
可能你在前一节我介绍内核抢占的时候还是不明白内核抢占是怎么一回事,但看到同样的程序(目录1st),在支持和不支持内核抢占的内核下运行的不同结果,想该明白了吧。不支持内核抢占的内核是霸道的,只要进程还运行在内核上下文,除了中断就没其他人能够打断。
言归正传,为了避免并发,在单处理器支持抢占的情况下,需要防两个情况:
情况一:内核线程之间并发访问临界区。
这个解决办法很简单,既然这种情况是因为内核支持内核抢占引起的,那我访问临界区时把内核抢占关掉就好了!包含头文件
preempt_disable(); 关抢占
临界区代码
preempt_enable(); 开抢占
这两个函数的实现原理也很简单,有这样一个计数器,当执行preempt_disable()时计数器加一,当执行preempt_enable()时计数器减一,只有当计数器的值为0时,内核才可以抢占。
只要把代码稍作修改,运行的结果就和非抢占时运行1st的代码一样:
/*4th_mutex_4/3th/test.c*/
ssize_t test_read(struct file *filp, char __user *buf, size_t count,
loff_t *offset)
unsigned long flag = 0;
preempt_disable();
//在死循环前关掉抢占。
printk("[%s]task pid[%d], context [%s]\n",
__FUNCTION__, current->pid
, current->comm);
mdelay(2000);
preempt_enable();
return count -
注意:上面的代码是完全不合理的。我只是想说明内核抢占的模式下怎么把抢占关掉。关掉抢占是为了保护临界区的共享数据,而上面的代码会导致系统陷入死循环。
情况二:中断程序打断正在运行的内核线程。
同样的,我把中断关掉就可以了。结果代码编程这样子:
preempt_disable();
local_irq_save(flag);
临近区代码
local_irq_restore(flag);
preempt_enable();
所以,为了保护临界区的共享数据,代码应该改成这样。
/*4th_mutex_4/4th/test.c*/
ssize_t test_read(struct file *filp, char __user *buf, size_t count,
loff_t *offset)
unsigned long flag = 0;
preempt_disable();
local_irq_save(flag);
printk("[%s]task pid[%d], context [%s]\n",
__FUNCTION__, current->pid
, current->comm);
//假设这是临界区的代码。。。
local_irq_restore(flag);
preempt_enable();
mdelay(2000);
return count -
大功告成!这个也不验证了。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
p { margin-bottom: 0.21 }code.cjk { font-family: "DejaVu Sans", }code.ctl { font-family: "DejaVu Sans Mono", }
这节介绍了内核中并发产生的原因,并介绍了在单处理器的情况下如何保护内核中的共享资源同时实现内核同步。
有一个不足,就是写出来的代码很难去验证,这是因为,即使并发是存在,但是很难保证一定会在临界区发生,毕竟临界区代码不长。
还有一个地方我觉得没有讲清楚的,上面介绍了的是防止并发的方法,不一定需要全用。
如单处理器非抢占内核,如果你知道中断代码中根本没有访问另一个进程临界区的资源,你的进程完全可以不关中断。
同样的,单处理器抢占内核的情况下。如果只有中断会访问到临界区的资源,那你完全可以不关抢占(但是这样的情况好像很难说,同时打开两个相同的程序就会有可能发生临界区并发访问)。
所以说,代码用在需要它的地方,不要随便加。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
阅读(7357) | 评论(1) | 转发(31) |
相关热门文章
给主人留下些什么吧!~~
说的太TM的好了,我自己实践一遍。。。。
请登录后评论。Linux系统入门学习:如何知道进程运行在哪个 CPU 内核上?_Linux教程_Linux公社-Linux系统门户网站
你好,游客
Linux系统入门学习:如何知道进程运行在哪个 CPU 内核上?
来源:Linux中国&
作者:Linux
问题:我有个 Linux 进程运行在多核处理器系统上。怎样才能找出哪个 CPU 内核正在运行该进程?
当你在 运行需要较高性能的 HPC(高性能计算)程序或非常消耗网络资源的程序时,CPU/memory 的亲和力是限度其发挥最大性能的重要因素之一。在同一 NUMA 节点上调度最相关的进程可以减少缓慢的远程内存访问。像英特尔 Sandy Bridge 处理器,该处理器有一个集成的 PCIe 控制器,你可以在同一 NUMA 节点上调度网络 I/O 负载(如网卡)来突破 PCI 到 CPU 亲和力限制。
作为性能优化和故障排除的一部分,你可能想知道特定的进程被调度到哪个 CPU 内核(或 NUMA 节点)上运行。
这里有几种方法可以 找出哪个 CPU 内核被调度来运行给定的 Linux 进程或线程。
如果一个进程使用
命令明确的被固定(pinned)到 CPU 的特定内核上,你可以使用 taskset 命令找出被固定的 CPU 内核:
$ taskset -c -p &pid&
例如, 如果你对 PID 5357 这个进程有兴趣:
$ taskset -c -p 5357pid 5357's current affinity list: 5
输出显示这个过程被固定在 CPU 内核 5上。
但是,如果你没有明确固定进程到任何 CPU 内核,你会得到类似下面的亲和力列表。
pid 5357's current affinity list: 0-11
输出表明该进程可能会被安排在从0到11中的任何一个 CPU 内核。在这种情况下,taskset 不能识别该进程当前被分配给哪个 CPU 内核,你应该使用如下所述的方法。
ps 命令可以告诉你每个进程/线程目前分配到的 (在&PSR&列)CPU ID。
$ ps-o pid,psr,comm -p &pid&PID PSR COMMAND535710 prog
输出表示进程的 PID 为 5357(名为"prog")目前在CPU 内核 10 上运行着。如果该过程没有被固定,PSR 列会根据内核可能调度该进程到不同内核而改变显示。
top 命令也可以显示 CPU 被分配给哪个进程。首先,在top 命令中使用&P&选项。然后按&f&键,显示中会出现 "Last used CPU" 列。目前使用的 CPU 内核将出现在 &P&(或&PSR&)列下。
$ top-p 5357
相比于 ps 命令,使用 top 命令的好处是,你可以连续监视随着时间的改变, CPU 是如何分配的。
另一种来检查一个进程/线程当前使用的是哪个 CPU 内核的方法是使用 。
从命令行启动 htop。按 键,进入"Columns",在"Available Columns"下会添加 PROCESSOR。
每个进程当前使用的 CPU ID 将出现在&CPU&列中。
请注意,所有以前使用的命令 taskset,ps 和 top 分配CPU 内核的 IDs 为 0,1,2,...,N-1。然而,htop 分配 CPU 内核 IDs 从 1开始(直到 N)。
作者: 译者: 校对:
原创编译, 荣誉推出
本文永久更新链接地址:
相关资讯 & & &
& (09/17/:24)
& (08/17/:55)
& (06/07/:42)
& (08/17/:44)
& (06/14/:06)
& (06/05/:55)
   同意评论声明
   发表
尊重网上道德,遵守中华人民共和国的各项有关法律法规
承担一切因您的行为而直接或间接导致的民事或刑事法律责任
本站管理人员有权保留或删除其管辖留言中的任意内容
本站有权在网站内转载或引用您的评论
参与本评论即表明您已经阅读并接受上述条款如何知道进程运行在哪个 CPU 内核上? - 简书
<div class="fixed-btn note-fixed-download" data-toggle="popover" data-placement="left" data-html="true" data-trigger="hover" data-content=''>
写了77422字,被493人关注,获得了608个喜欢
如何知道进程运行在哪个 CPU 内核上?
问题:我有个 Linux 进程运行在多核处理器系统上。怎样才能找出哪个 CPU 内核正在运行该进程?
当你在 运行需要较高性能的 HPC(高性能计算)程序或非常消耗网络资源的程序时,CPU/memory 的亲和力是限度其发挥最大性能的重要因素之一。在同一 NUMA
节点上调度最相关的进程可以减少缓慢的远程内存访问。像英特尔 Sandy Bridge 处理器,该处理器有一个集成的 PCIe 控制器,你可以在同一 NUMA 节点上调度网络 I/O 负载(如网卡)来突破 PCI 到 CPU 亲和力限制。
作为性能优化和故障排除的一部分,你可能想知道特定的进程被调度到哪个 CPU 内核(或 NUMA 节点)上运行。
这里有几种方法可以 找出哪个 CPU 内核被调度来运行给定的 Linux 进程或线程。
如果一个进程使用
命令明确的被固定(pinned)到 CPU 的特定内核上,你可以使用 taskset 命令找出被固定的 CPU 内核:
$ taskset -c -p &pid&
例如, 如果你对 PID 5357 这个进程有兴趣:
$ taskset -c -p 5357
pid 5357's current affinity list: 5
输出显示这个过程被固定在 CPU 内核 5上。
但是,如果你没有明确固定进程到任何 CPU 内核,你会得到类似下面的亲和力列表。
pid 5357's current affinity list: 0-11
输出表明该进程可能会被安排在从0到11中的任何一个 CPU 内核。在这种情况下,taskset 不能识别该进程当前被分配给哪个 CPU 内核,你应该使用如下所述的方法。
ps 命令可以告诉你每个进程/线程目前分配到的 (在“PSR”列)CPU ID。
$ ps -o pid,psr,comm -p &pid&
PID PSR COMMAND
输出表示进程的 PID 为 5357(名为"prog")目前在CPU 内核 10 上运行着。如果该过程没有被固定,PSR 列会根据内核可能调度该进程到不同内核而改变显示。
top 命令也可以显示 CPU 被分配给哪个进程。首先,在top 命令中使用“P”选项。然后按“f”键,显示中会出现 "Last used CPU" 列。目前使用的 CPU 内核将出现在 “P”(或“PSR”)列下。
$ top -p 5357
相比于 ps 命令,使用 top 命令的好处是,你可以连续监视随着时间的改变, CPU 是如何分配的。
另一种来检查一个进程/线程当前使用的是哪个 CPU 内核的方法是使用 。
从命令行启动 htop。按 &F2& 键,进入"Columns",在"Available Columns"下会添加 PROCESSOR。
每个进程当前使用的 CPU ID 将出现在“CPU”列中。
请注意,所有以前使用的命令 taskset,ps 和 top 分配CPU 内核的 IDs 为 0,1,2,...,N-1。然而,htop 分配 CPU 内核 IDs 从 1开始(直到 N)。
作者:译者:校对:
原创编译, 荣誉推出
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
打开微信“扫一扫”,打开网页后点击屏幕右上角分享按钮
被以下专题收入,发现更多相似内容:
如果你是程序员,或者有一颗喜欢写程序的心,喜欢分享技术干货、项目经验、程序员日常囧事等等,欢迎投稿《程序员》专题。
专题主编:小...
· 253183人关注
玩转简书的第一步,从这个专题开始。
想上首页热门榜么?好内容想被更多人看到么?来投稿吧!如果被拒也不要灰心哦~入选文章会进一个队...
· 145183人关注
Android,Java,前端,Node.js,React,React Native
· 2780人关注
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
选择支付方式:1962人阅读
linux(68)
前几天碰到一个问题:一个进程运行过程中挂死了,把gdb挂上去之后bt打印的内容为空,后来通过查看 /proc 文件系统,查看程的调用栈,才发现是发消息给内核态程序时,内核态一直没有响应,导致用户态进程挂死。刚好在网上看到一篇描述通过 /proc 文件系统来定位问题的文章,这篇文章讲解得比较清楚,因此尝试翻译出来。原文地址:
这篇博客是基于现代Linux的。换句话说,是RHEL6所对应的2.6.3x内核版本,而不是古老的RHEL5所对应的2.6.18内核版本(神马玩意儿?!),很不幸是后者才是企业中最常见的版本。并且,在这里我不打算使用内核调试器或者SystemTap脚本,只使用平凡而古老的cat /proc/PID/xyz,而不是那些便捷的/proc文件系统工具。
定位一个“运行慢”的进程
我打算介绍一个系统性定位问题的例子,我在手提电脑上重现了这个例子。一个DBA想知道为什么他的find命令运行起来&非常慢&,并且很长时间都没有返回任何结果。了解环境之后,我对这个问题的起因有一个直觉的答案,但是他问我,对于这种正在发生中的问题,有没有系统性的方法立刻进行定位。
幸运的是,这个系统运行的是OEL6,因此刚好有一个新内核。确切的说2.6.39 UEK2。
那么,让我们试着定位一下。首先,看看find进程是否还活着:
[root@oel6 ~]# ps -ef | grep find
4 11:57 pts/0 00:00:01 find . -type f
0 11:57 pts/1 00:00:00 grep find
是的,他还在 —— PID 27288 (在整个定位问题的过程中我将会一直使用这个pid)。
让我们从最基本的开始,先看下这个进程的瓶颈在什么地方 —— 如果不是被什么操作阻塞的话(例如从缓存中读取需要的数据),CPU占用率应该是100%。如果瓶颈是IO或者连接问题,CPU占用率应该很低,或者就是0%。
[root@oel6 ~]# top -cbp 27288
top - 11:58:15 up 7 days,
load average: 1.21, 0.65, 0.47
0 running,
1 sleeping,
0 stopped,
0.0%ni, 99.8%id,
2026460k total,
1935780k used,
90680k free,
64416k buffers
4128764k total,
251004k used,
3877760k free,
662280k cached
SHR S %CPU %MEM
27288 root
0:01.11 find . -type f
top的结果显示这个进程的CPU占用率是0%,或者非常接近0%(因此输出被四舍五入为0%)。这两种情况实际上有着重要的差别,一种情况是进程完全挂死,根本没有机会获得CPU,另一种情况是进程不时的退出等待状态(例如,某些轮询操作不时的超时,而进程选择继续sleep)。因此,Linux上的top并不是一个适合显示这种差别的工具
—— 但是至少我们知道了进程并不是占用了大量的CPU。
让我们用其他命令试试。通常当一个进程看起来好像挂死时(0%的CPU占用率通常意味着进程挂在某些阻塞性的系统调用上 —— 这会导致内核让进程进入休眠状态),我会在这个进程上运行strace来跟踪进程挂在哪个系统调用上。同样的,如果进程并没有完全挂死,而是不时的从系统调用中返回并且被短暂的唤醒,这种情况也会呈现在strace中(阻塞性的系统调用将会完成并很快的再次进入):
[root@oel6 ~]# strace -cp 27288
Process 27288 attached - interrupt to quit
strace -cp 27288
[root@oel6 ~]# kill -9 %%
strace -cp 27288
[root@oel6 ~]#
strace -cp 27288
天啊,strace命令也挂住了!strace很长时间都没有打印任何东西,并也不能响应CTRL&#43;C,因此我不得不用CTRL&#43;Z,并杀死它。简单的诊断手段就这些了。
让我们再试试pstack(在Linux上,pstack就是GDB调试器的一个shell包装)。尽管pstack并不能查看内核态信息,它仍然能够告诉我们是哪个系统调用被执行了(通常,有一个相应的libc库调用显示在用户态堆栈的顶端上):
[root@oel6 ~]# pstack 27288
pstack 27288
[root@oel6 ~]# kill %%
pstack 27288
[root@oel6 ~]#
Terminated
pstack 27288
pstatck也挂死了,什么都没返回!
因此,我们还是不知道我们的进程是100%(无可救药的)挂死了还是99.99%的挂住了(进程还在运行只是在睡眠) —— 以及在哪儿挂住了。
好了,还有别的可以看吗?还有一个更普通的东西可以坚持 —— 进程状态和WCHAN字段,可以通过古老而美好的ps(也许我早就应该运行这个命令,以确认进程到底是不是僵死了):
[root@oel6 ~]# ps -flp 27288
NI ADDR SZ **WCHAN**
0 - 28070 **rpc_wa** 11:57 pts/0
00:00:01 find . -type f
你应该多运行几次ps命令,以确保进程一直是同一个状态(你肯定不想被一个偶然的单独采样所误导),为了简洁一点这里只显示一次结果。
进程状态是D(不可中断睡眠状态,也就是不会被任何外部信号唤醒),这个状态通常与磁盘IO相关(ps帮助上也这样说)。并且WCHAN字段(表示导致进程睡眠或者等待的函数)被截断了一点。我可以用ps选项(参考帮助)把这个字段打印得跟宽一点,但是既然这个信息是来自proc文件系统,就让我们直接到源头去查询吧(再强调一次,既然我们不确定我们的进程到底是完全挂死了还是仅仅只是经常处于睡眠状态,那么最好把这个命令多执行几次以获取多次采样结果):
[root@oel6 ~]# cat /proc/27288/wchan
rpc_wait_bit_killable
嗯,进程是在等待某个RPC调用。RPC通常意味着进程是在和其它进程通信(可能是本地服务进程或者远程服务进程)。但是我们还是不知道为什么挂住。
进程有什么活动或者完全挂死了?
在我们进入这篇文章中真正有营养的部分之前,让我们先弄清楚进程到底有没有完全挂死。在最新的系统内核上/proc/PID/status 可以告诉我们答案:
[root@oel6 ~]# cat /proc/27288/status
D (disk sleep)
TracerPid:
FDSize: 256
Groups: 0 1 2 3 4 6 10
SigPnd: 0000
ShdPnd: 0000
SigBlk: 0000
SigIgn: 0000
SigCgt: 0000
CapInh: 0000
CapPrm: ffffffffffffffff
CapEff: ffffffffffffffff
CapBnd: ffffffffffffffff
Cpus_allowed:
ffffffff,ffffffff
Cpus_allowed_list:
Mems_allowed:
Mems_allowed_list:
voluntary_ctxt_switches:
nonvoluntary_ctxt_switches: 17104
进程状态是D —— Disk Sleep(不可中断睡眠)。然后看看voluntaryctxtswitches 和nonvoluntaryctxtswitches的数&#20540; —— 它可以告诉你进程占用(或者释放)了多少次CPU。等几秒钟之后,再次执行该命令,看看这些数&#20540;有没有增加。在我这个案例中,这些数&#20540;没有增加,据此我可以得出结论,这个进程是完全挂死了(额,至少在执行命令的这几秒钟内是完全挂死的)。所以,现在我更有信心认为这个进程是完全挂死了(而不是在飞行在雷达探测不到地带
—— 在0.04%以下的低CPU占用率下运行)。
顺便说一句,有两个地方可以获得上下文切换次数(并且第二种方法还可以在老的系统内核上工作):
[root@oel6 ~]# cat /proc/27288/sched
find (27288, #threads: 1)
---------------------------------------------------------
se.exec_start
se.vruntime
se.sum_exec_runtime
se.statistics.wait_start
se.statistics.sleep_start
se.statistics.block_start
se.statistics.sleep_max
se.statistics.block_max
se.statistics.exec_max
se.statistics.slice_max
se.statistics.wait_max
se.statistics.wait_sum
724.745506
se.statistics.wait_count
se.statistics.iowait_sum
se.statistics.iowait_count
se.nr_migrations
se.statistics.nr_migrations_cold
se.statistics.nr_failed_migrations_affine:
se.statistics.nr_failed_migrations_running:
se.statistics.nr_failed_migrations_hot:
se.statistics.nr_forced_migrations :
se.statistics.nr_wakeups
se.statistics.nr_wakeups_sync
se.statistics.nr_wakeups_migrate
se.statistics.nr_wakeups_local
se.statistics.nr_wakeups_remote
se.statistics.nr_wakeups_affine
se.statistics.nr_wakeups_affine_attempts:
se.statistics.nr_wakeups_passive
se.statistics.nr_wakeups_idle
avg_per_cpu
nr_switches
nr_voluntary_switches
nr_involuntary_switches
se.load.weight
clock-delta
你需要看看nr_switchs的数&#20540;(等于nrvoluntaryswitches &#43;nrinvoluntaryswitches)。
在上面的输出中,总的nr_switches次数是27054,这个&#20540;同时也是/proc/PID/schedstat的结果中的第3个字段。
[root@oel6 ~]# cat /proc/27288/schedstat
并且它不会增加...
用/proc文件系统查看内核态信息
那么,看起来我们的进程很漂亮的挂死了:)strace和pstatck都没有用武之地。它们使用ptrace()系统调用来附着到进程上,并查看进程的内存,但是由于进程绝望的挂死了,很可能挂在某个系统调用上,因此我猜测ptrace()调调本身也被挂住了。(顺便说一句,我试过strace那个附着到目标进程的strace进程,结果目标进程崩溃了。记着我警告过你:)。)
那么,怎么看到底挂在哪个系统调用上呢 —— 没法用strace或者pstack?幸运的是我运行的是现代的操作系统内核 —— 跟/proc/PID/syscall打个招呼吧!
[root@oel6 ~]# cat /proc/27288/syscall
262 0xffffffffffffff9c 0x20cf6c8 0x7fff97c 0x100 0x676e776f645f616d 0x7fff97ce2da8ea
好了,我可以拿他干嘛呢? 嗯,这些数字代表某些东西。如果它是一个&0x很大的数&,它通常表示一个内存地址(并且,pmap之类的工具可以用来查看它指向那里);但是如果是一个很小的数字,那么很可能是一个数组索引 —— 例如打开的文件描述符数组(可以从/prco/PID/fd读取到),或者是当前进程正在执行的系统调用号 —— 既然在这个例子中,我们正在处理系统调用。那么,这个进程是挂死在#262号系统调用上吗?
注意在不同的OS类型、版本或者平台之间,系统调用号可能不同,因此你需要看看对应的OS上的.h文件。通常应该在/usr/include中搜索&syscall*&。在我的Linux上,系统调用定义在/usr/include/asm/unistd_64.h中:
[root@oel6 ~]# grep 262 /usr/include/asm/unistd_64.h
#define __NR_newfstatat
找到了!系统调用262是某个叫做newfstatat的东西。打开手册看看它到底是什么。关于系统调用名称有一个小小的技巧 —— 如果在手册中找不到这个系统调用,试试去掉后缀或者前缀(例如,用man pread代替man pread64)—— 在这个例子中,查找时去掉&new& ——man fstata。或者直接google。
无论如何,系统调用&new-fstat-at&允许你读取文件属性,非常像通常的&stat&系统调用。那么我们挂在这个文件元数据读取操作上。我们前进了一步,但是仍然不知道为什么会挂在这儿?
好了,跟我的小朋友/proc/PID/statck打个招呼吧,使用它可以读取进程的内核堆栈的调试信息:
[root@oel6 ~]# cat /proc/27288/stack
[] rpc_wait_bit_killable&#43;0x24/0x40 [sunrpc]
[] __rpc_execute&#43;0xf5/0x1d0 [sunrpc]
[] rpc_execute&#43;0x43/0x50 [sunrpc]
[] rpc_run_task&#43;0x75/0x90 [sunrpc]
[] rpc_call_sync&#43;0x42/0x70 [sunrpc]
[] nfs3_rpc_wrapper.clone.0&#43;0x35/0x80 [nfs]
[] nfs3_proc_getattr&#43;0x47/0x90 [nfs]
[] __nfs_revalidate_inode&#43;0xcc/0x1f0 [nfs]
[] nfs_revalidate_inode&#43;0x36/0x60 [nfs]
[] nfs_getattr&#43;0x5f/0x110 [nfs]
[] vfs_getattr&#43;0x4e/0x80
[] vfs_fstatat&#43;0x70/0x90
[] sys_newfstatat&#43;0x24/0x50
[] system_call_fastpath&#43;0x16/0x1b
[] 0xffffffffffffffff
最上面的函数就是在内核代码中挂住的地方 —— 它跟WCHAN输出完全吻合(注意,实际上有更多的函数在调用栈上,例如内核scheduler()函数,它使进程休眠或者唤醒进程,但是这些函数没有显示出来,很可能是因为它们是等待条件的结果而不是原因)。
感谢它打印出了完整的内核态堆栈,我们可以从下而上的看一下函数调用,从而理解是怎么最终调用到rpc_wait_bit_killable的,这个函数结束了对调度器的调用并使进程进入睡眠模式。
底端的system_call_fastpath是一个通用的内核调用处理函数,它为我们处理过的newfstatat系统调用执行内核代码。然后继续向上,我们可以看到好几个NFS函数。这是100%无可抵赖的证据,证明我们处在某些NFS代码路径下(under NFS codepath)。我没有说在NFS代码路径中(in NFS codepath),当你继续向上看的时候,你会看到最上面的NFS函数接着调用了某些RPC函数(rpc_call_sync)以便跟其它进程通信
—— 在这个例子中可能是[kworker/N:N]、 [nfsiod]、 [lockd] 或者 [rpciod]内核IO线程。并且因为某些原因一直没有从这些线程收到应答(通常的怀疑点是网络连接丢失、数据包丢失或者仅仅是网络连通性问题)。
要想看看到底是哪个辅助线程挂在网络相关的代码上,你同样可以收集内核堆栈信息,尽管kworkers做的事情远不止NFS RPC通信。在另外一个单独的试验中(只是通过NFS拷贝一个大文件),我抓取到了一个kworkder在网络代码中等待的信息:
[root@oel6 proc]# for i in `pgrep worker` ; do ps -fp $ cat /proc/$i/ done
C STIME TTY
00:04:34 [kworker/1:1]
[] __cond_resched&#43;0x2a/0x40
[] lock_sock_nested&#43;0x35/0x70
[] tcp_sendmsg&#43;0x29/0xbe0
[] inet_sendmsg&#43;0x48/0xb0
[] sock_sendmsg&#43;0xef/0x120
[] kernel_sendmsg&#43;0x41/0x60
[] xs_send_kvec&#43;0x8e/0xa0 [sunrpc]
[] xs_sendpages&#43;0x173/0x220 [sunrpc]
[] xs_tcp_send_request&#43;0x5d/0x160 [sunrpc]
[] xprt_transmit&#43;0x83/0x2e0 [sunrpc]
[] call_transmit&#43;0xa8/0x130 [sunrpc]
[] __rpc_execute&#43;0x66/0x1d0 [sunrpc]
[] rpc_async_schedule&#43;0x15/0x20 [sunrpc]
[] process_one_work&#43;0x13e/0x460
[] worker_thread&#43;0x17c/0x3b0
[] kthread&#43;0x96/0xa0
[] kernel_thread_helper&#43;0x4/0x10
如果准确的知道哪个内核线程在和其它内核线程通信,就有可能打开内核跟踪,但是在这篇文章中我不想走到那一步& —— 这篇文章的描述的是一个实践性的、简单的问题定位练习!
诊断和&修复&
无论如何,感谢新Linux内核提供的内核堆栈信息收集方法(我不知道到底是在哪个具体版本引入的),使我们得以系统性的找出find命令到底挂在哪儿 —— 在Linux内核的NFS代码里。并且当你雏形NFS相关的挂起时,最通常的怀疑点是网络问题。如果你想知道我是怎么重现出这个问题的,我从一个虚拟机里挂载了一个NFS卷,然后启动find命令,接着挂起虚拟机。这种操作导致了与网络(配置、防火墙)问题相同的症状,例如使一个网络连接默默的断开,而不通知TCP端点,或者因某种原因使数据包无法送达。
既然在堆栈最顶端的函数是一个可杀死的、可安全杀死的函数(rpc_wait_bit_killable),我们可以用kill -9杀死它:
[root@oel6 ~]# ps -fp 27288
C STIME TTY
0 11:57 pts/0 00:00:01 find . -type f
[root@oel6 ~]# kill -9 27288
[root@oel6 ~]# ls -l /proc/27288/stack
ls: cannot access /proc/27288/stack: No such file or directory
[root@oel6 ~]# ps -fp 27288
C STIME TTY
[root@oel6 ~]#
进程不见了。
穷人的内核线程分析
/proc/PID/stack看起来就像一个简单的文本proc文件,你一样可以在内核线程上进行穷人的堆栈分析!下面这个例子演示了如何收集当前系统调用和内核堆栈信息,以及如何以穷人的方式集成进一个半层次化的分析器:
[root@oel6 ~]# export LC_ALL=C ; for i in {1..100} ; do cat /proc/29797/syscall | awk '{ print $1 }' ;
cat /proc/29797/stack | /home/oracle/os_explain - usleep 100000 ; done | sort -r | uniq -c
69 running
1 ffffff81534c83
0xffffffffffffffff
thread_group_cputime
sysenter_dispatch
ia32_sysret
task_sched_runtime
sys32_pread
compat_sys_io_submit
compat_sys_io_getevents
sys_pread64
sys_io_getevents
do_io_submit
read_events
io_submit_one
do_sync_read
aio_run_iocb
generic_file_aio_read
aio_rw_vect_retry
generic_file_read_iter
generic_file_aio_read
mapping_direct_IO
generic_file_read_iter
blkdev_direct_IO
__blockdev_direct_IO
do_blockdev_direct_IO
dio_post_submission
dio_await_completion
blk_flush_plug_list
它给出关于进程在内核中的什么地方耗费时间的粗略信息。上面的一段单独列出了系统调用号的信息 —— “running”表示进程处于用户态(而不是在系统调用中)。因此,在收集信息期间,69%的时间进程跑在用户态。25%的时间花在#180号系统调用上(在我的系统上是nfsservctl),而6%的时间花在#247号系统调用上(waitid)。
在这个输出里还可以看到更多的“函数” —— 但是由于某些原因它们没有被恰当的翻译城函数名称。嗯,这个地址应该代表某些东西,因此我们手工碰碰运气:
[root@oel6 ~]# cat /proc/kallsyms | grep -i ffffff81534c83
ffffffff81534c83 t ia32_sysret
看起来这些信息是一个32位架构兼容的系统调用的返回函数 —— 但是这个函数本身不是一个系统调用(只是一个内部的辅助函数),也许这就是为什么/proc/stack没有翻译它。也许显示地址是因为在/proc视图上没有“读一致性”,当时属主线程修改了这些内存结构和入口,读线程可能读取了不稳定的数据。
让我们也检查一下其它地址:
[root@oel6 ~]# cat /proc/kallsyms | grep -i ffffff
[root@oel6 ~]#
什么都没有?嗯,然而问题定位并不是一定得终止 —— 让我们看看这个地址附近有没有其它有趣得信息。我仅仅移走了地址尾部的两个字符:
[root@oel6 ~]# cat /proc/kallsyms | grep -i ffffff815348
ffffffff8153480d t sysenter_do_call
ffffffff t sysenter_dispatch
ffffffff t sysexit_from_sys_call
ffffffff8153487a t sysenter_auditsys
ffffffff t sysexit_audit
&#20284;乎sysenter_dispatch函数是在/proc/PID/stack输出的原始地址前1个字节开始的。因此我们很可能已经执行了一个字节(可能是一个为了动态跟踪探针陷阱而留下的NOP操作)。但是,&#20284;乎这些堆栈信息都是在system_dispatch函数内,它本身不是一个系统调用,而是一个系统调用辅助函数。
更多关于堆栈分析器的信息
注意有不同类型的堆栈采集器 —— Linux Perf、Oprofile和Solaris DTrace用于采集当前正在运行的线程的指令指针寄存器(32位Intel CPU上的EIP,或者x64上的RIP)和堆栈指针寄存器(32位CPU上的ESP,和64位CPU上的RSP)。因此,这些工具只显示了在采集信息时恰好在CPU上运行的线程的信息!当定位高CPU占用率问题时,这是很完美的,但是对于定位挂死的进程或者长时间睡眠或者等待的进程,却一点用也没有,
Linux、Solaris、HP-UX上的pstack工具,AIX上的procstack工具,ORADEBUG SHORT_STACK工具,以及直接读取/proc/PID/stack文件,为CPU分析工具提供了一个很好的附加(而不是替代)工具。如果进程正在睡眠,不是在CPU上运行,可以从存储的上下文信息中读取堆栈的起始点 —— 在上下文切换时OS调度器把上下文信息存储到了内核内存中。
当然,CPU事件分析工具通常可以做得比pstack更多,OProfile、Perf甚至DTrace可以设置和采集CPU内部的性能计数器来统计类&#20284;等待主存的CPU周期数、L1/L2缓存命中率等等。仔细看看Kevin Closson关于这些主题的论述:(,)
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:80632次
积分:1197
积分:1197
排名:千里之外
原创:30篇
转载:71篇
评论:30条
(1)(1)(4)(2)(10)(13)(3)(1)(1)(7)(1)(1)(2)(2)(1)(1)(3)(7)(8)(25)(7)

我要回帖

更多关于 linux 内核进程状态 的文章

 

随机推荐