【转载】linux 工作队列上睡眠的认识--不要在默认共享队列上睡眠
最近项目组做xen底层,我已经被完爆无数遍了,关键在于对内核、驱动这块不熟悉,导致分析xen代码非常吃力。于是准备细细的将 几本 linux 书籍慢慢啃啃。
正好看到LINUX内核设计与实现,对于内核中中断下半段该如何选择?大牛的原话是这样的:“从根本上来说,你有休眠的需要吗?要是有,工作队列就是你的唯一选择,否则最好用tasklet。……”
书中一直强调 工作队列是可以休眠的,而且翻译的人总是强调”工作队列是运行在进程上下文的”, 对于这个翻译,我不是很理解,进程上下文难道就是指用户态而言吗,完全糊涂了,准备自己做个实验。于是我在网上收了下,并自己写了一个工作队列的例子,基本代码如下:
struct work_struct test_task;
void task_handler(void *data)
{
char c = 'a';
int i = 0;
while (task_doing == 1)
{
c = 'a'+ i%26;
printk(KERN_ALERT "---%c\n", c);
if (i++ > 50)
{
printk(KERN_ALERT "i beyone so quit");
break;
}
//msleep(1000);
wait_event_interruptible(my_dev->test_queue, my_dev->test_task_step !=0);
}
printk(KERN_ALERT "quit task task_doing %d\n",task_doing);
}
static int
test_ioctl(struct inode *inode, struct file *filp,
unsigned int cmd, unsigned long arg)
{
switch(cmd)
{
case IOCTL_INIT_TASK:
task_doing = 1;
INIT_WORK(&test_task, task_handler);
printk(KERN_ALERT "ioctl init task \n");
break;
case IOCTL_DO_TASK:
printk(KERN_ALERT "ioctl do task \n");
schedule_work(&test_task);
break;
default:
printk(KERN_ALERT "unknown ioctl cmd\n");
break;
}
return 0;
}
用户态测试程序通过 ioctl 命令发送 IOCTL_INIT_TASK 和 IOCTL_DO_TASK 命令。通过书中介绍,INIT_WORK 是初始化一个工作队列,其后调用schedule_work(&test_task) 后,才会执行工作队列上注册的回调函数。
在回调函数中,我进行了睡眠,开始用的是 msleep ,这个函数会放弃CPU到指定的时间,没想到我的内核居然挂住了,再也无法响应。看看驱动设计的代码,很少看到有人用msleep的,可能是自己用了不恰当的函数,于是换成如下代码:
wait_event_interruptible(my_dev->test_queue, my_dev->test_task_step !=0);
重新将虚拟机恢复后,执行同样的测试,还是不行,一运行注册的回调函数,内核就立刻挂起,再也无法操作。
更加无法理解了,说好的工作队列是可以睡眠的,但是我调用睡眠,内核居然就永远无法醒来啦。已经没有机会执行一个动作让 my_dev->test_task_step == 1 了,那么书中所说的 工作队列可以睡眠是什么意思呢 ?
同时看了设备驱动详解中阻塞IO的例子,书中说在 linux 中一个等待队列头可以如下动态创建:
wait_queue_head_t my_queue;
init_waitqueue_head(&my_queue);
但是常常更容易的做法是放一个 DEFINE_WAIT 行在循环的顶部, 来实现你的睡眠.
下一步是添加你的等待队列入口到队列, 并且设置进程状态. 2 个任务都由这个函数处理:
void prepare_to_wait(wait_queue_head_t *queue, wait_queue_t *wait, int state);
这里, queue 和 wait 分别地是等待队列头和进程入口. state 是进程的新状态; 它应当或者是 TASK_INTERRUPTIBLE(给可中断的睡眠, 这常常是你所要的)或者 TASK_UNINTERRUPTIBLE(给不可中断睡眠).
在调用 prepare_to_wait 之后, 进程可调用 schedule -- 在它已检查确认它仍然需要等待之后. 一旦 schedule 返回, 就到了清理时间. 这个任务, 也, 被一个特殊的函数处理:
void finish_wait(wait_queue_head_t *queue, wait_queue_t *wait);
同时,书中还有一个例子:
/* Wait for space for writing; caller must hold device semaphore. On
* error the semaphore will be released before returning. */
static int scull_getwritespace(struct scull_pipe *dev, struct file *filp)
{
while (spacefree(dev) == 0)
{ /* full */
DEFINE_WAIT(wait);
up(&dev->sem);
if (filp->f_flags & O_NONBLOCK)
return -EAGAIN;
PDEBUG("\"%s\" writing: going to sleep\n",current->comm);
prepare_to_wait(&dev->outq, &wait, TASK_INTERRUPTIBLE);
if (spacefree(dev) == 0)
schedule();
finish_wait(&dev->outq, &wait);
if (signal_pending(current))
return -ERESTARTSYS; /* signal: tell the fs layer to handle it */
if (down_interruptible(&dev->sem))
return -ERESTARTSYS;
}
return 0;
}
问题在于,手动睡眠的方式和上面调用 wait_event_interruptible 有什么区别呢 ?从代码上看,手动睡眠有一个等待队列头,而且有一个等待队列单个元素 wait, prepare_to_wait 函数会将 该单个等待元素挂到等待队列头里面去。 一直想搞明白调用 prepare_to_wait 后,会不会进入睡眠 ? 做了一个实验,答案是肯定的,调用prepare_to_wait后,内核立刻进入睡眠状态,只有在其他地方调用 wake_up_interruptible 后才会通知它醒来。。。而且 不必再每次 prepare_to_wait醒来后都调用 finish_wait ,只需要最后调用一次就可以了,因为prepare_to_wait 的内部会做检查,发现该元素不在头链表上时,才会添加该元素到头链表。
在看看 wait_event_interruptible 的代码:
#define __wait_event_interruptible(wq, condition, ret) \
do { \
DEFINE_WAIT(__wait); \
\
for (;;) { \
prepare_to_wait(&wq, &__wait, TASK_INTERRUPTIBLE); \
if (condition) \
break; \
if (!signal_pending(current)) { \
schedule(); \
continue; \
} \
ret = -ERESTARTSYS; \
break; \
} \
finish_wait(&wq, &__wait); \
} while (0)
原来这个函数对手动睡眠的过程进行了封装,所以调用的时候只用到工作队列(实际就是等待队列)头,它内部自己封装了一个等待元素。。
现在看来,linux 内核设计与实现中,对工作队列可以睡眠的说法是比较模糊的,工作队列上的回调函数是不能睡眠的。工作队列本身就是一种等待队列,队列是可以睡眠的,但是工作队列的上任务回调函数,看来是不能睡眠的。 今天先睡了,后面还要进一步分析看看。
今天在网上查了下相关的东西,有个家伙写得不错:“使用内核提供的共享列队,列队是保持顺序执行的,做完一个工作才做下一个,如果一个工作内有耗时大的处理如阻塞等待信号或锁,那么后面的工作都不会执行。如果你不喜欢排队或不好意思让别人等太久,那么可以创建自己的工作者线程,所有工作可以加入自己创建的工作列队,列队中的工作运行在创建的工作者线程中。”
问题可能就是出在上面了,如果我使用了内核提供的共享队列,可想而知,如果我进入了睡眠或者阻塞,内核中肯定有其他的工作也在这个共享队列上运行,此时便会阻塞内核的某些工作,当然系统就看起来卡死一样了。这样说,如果我创建自己的工作队列,然后在自己的工作队列上挂起,那样就不会出现卡死现象了。做了下试验,果然是这样。
看来,纸上得来总觉浅,深知此事要恭行。linux 内核设计与实现这本书是比较简洁的,作者只告诉我们,利用工作队列甚至可以睡眠,但是他没有强调:“最好不要在系统提供的共享队列上进行睡眠,如果自己的工作是非阻塞的,可以就近利用默认的共享队列。但是如果自己的工作需要睡眠或者阻塞,此时万万不可使用系统提供的默认共享队列,否则会导致内核中一部分关键工作得不到执行,而陷入系统卡死的状态。
这是一个坑,如果不小心处理,会导致系统挂起。
转自:linux 工作队列上睡眠的认识--不要在默认共享队列上睡眠_枪与玫瑰的专栏-CSDN博客
【转载】linux 工作队列上睡眠的认识--不要在默认共享队列上睡眠的更多相关文章
- linux工作队列 - workqueue总览【转】
转自:https://blog.csdn.net/cc289123557/article/details/52551176 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载 ...
- [转载]Linux进程调度原理
[转载]Linux进程调度原理 Linux进程调度原理 Linux进程调度的目标 1.高效性:高效意味着在相同的时间下要完成更多的任务.调度程序会被频繁的执行,所以调度程序要尽可能的高效: 2.加强交 ...
- 20135327郭皓--Linux内核分析第四周 扒开系统调用的三层皮(上)
Linux内核分析第四周 扒开系统调用的三层皮(上) 郭皓 原创作品转载请注明出处 <Linux内核分析>MOOC课程 http://mooc.study.163.com/course/U ...
- [转载]Linux下非root用户如何安装软件
[转载]Linux下非root用户如何安装软件 来源:https://tlanyan.me/work-with-linux-without-root-permission/ 这是本人遇到的实际问题,之 ...
- [转载]Linux 命令详解:./configure、make、make install 命令
[转载]Linux 命令详解:./configure.make.make install 命令 来源:https://www.cnblogs.com/tinywan/p/7230039.html 这些 ...
- [转载]Linux缓存机制
[转载]Linux缓存机制 来源:https://blog.csdn.net/weixin_38278334/article/details/96478405 linux下的缓存机制及清理buffer ...
- [转载]Linux 线程实现机制分析
本文转自http://www.ibm.com/developerworks/cn/linux/kernel/l-thread/ 支持原创.尊重原创,分享知识! 自从多线程编程的概念出现在 Linux ...
- <转载>linux下内存泄露查找、BUG调试
先收藏着,抽空好好看看:http://www.ibm.com/developerworks/cn/linux/l-pow-debug/ 简介 调试程序有很多方法,例如向屏幕上打印消息,使用调试器,或者 ...
- VM下的linux系统上不了网?? 使用putty远程登录不上linux的解决方法?
背景:昨晚想尝试一下用putty远程登录我的linux系统,悲剧的是,我竟然连接不上,显示 connection refused ,连接被拒绝.于是我就想看看能不能在linux下看看能不能访问百度 ...
随机推荐
- Deepin Pwn环境的配置
要学习Pwn Pwn环境那是必不可少滴! 我是新手,我也弄了好久,这里把经验分享给大家!这里感谢I春秋的"知世"老师的脚本!虽然写的不是很完美.还得我自己动手修改!PS:http ...
- spring-security oauth2.0简单集成
github地址:https://github.com/intfish123/oauth.git 需要2个服务,一个认证授权服务,一个资源服务 认证授权服务为客户端颁发令牌,资源服务用于客户端获取用户 ...
- 006 PCI总线的桥与配置(一)
在PCI体系结构中,含有两类桥片,一个是HOST主桥,另一个是PCI桥.在每一个PCI设备中(包括PCI桥)都含有一个配置空间.这个配置空间由HOST主桥管理,而PCI桥可以转发来自HOST主桥的配置 ...
- 常见的六种容错机制:Fail-Over、Fail-Fast、Fail-Back、Fail-Safe,Forking 和 Broadcast
目录 1.Fail-Over:故障转移 2.Fail-Fast:快速失败 3.Fail-Back:失效自动恢复 4.Fail-Safe:失效安全 5.Forking:并行调用多个服务 6.Broadc ...
- Java入门姿势【面向对象3】构造方法及其重载_方法的调用
上次我为大家写出啦"定义类与创建对象_了解局部变量",上篇文章代码可能较多,如没有了解透彻的话请打开下方文章在进行观看一下哦!! [Java入门姿势[面向对象2]定义类与创建对象_ ...
- noip40
T1 记当前位置 \(i\) 上的颜色,上次出现的位置为 \(last_{1}\) ,上上次出现的位置为 \(last_{2}\) ,则,把当前点的颜色加进来,并且让其产生贡献的话,则会对 \([la ...
- Linux放大缩小字体的快捷键
linux终端窗口字体缩放快捷键 环境:linux, 打开终端, 'ctrl' + '-'字体缩小,一行显示更多的内容 'ctrl' + 'shift' + '+'字体变大 ctl+shift+(+) ...
- Seata–分布式事务
10.1 分布式事务基础 10.1.1 事务 事务指的就是一个操作单元,在这个操作单元中的所有操作最终要保持一致的行为,要么所有操作都成功,要么所有的操作都被撤销.简单地说,事务提供一种"要 ...
- jQuery中的常用方法:empty()、each()、$.each()、$.trim()、focus()(二)
<!DOCTYPE html> <html> <head> <title>02_commonMethod.html</title> < ...
- 12-SpringCloud GateWay
GateWay和Zuul说明 Zuul开发人员窝里斗,实属明日黄花 重点关注Gate Way GateWay是什么 上一代zuul 1.x官网 Gateway官网 概述 Cloud全家桶中有个很重要的 ...