TASK_UNINTERRUPTIBLE状态与CPU load 负载

1010阅读 0评论2014-11-12 jonas_mao
分类:LINUX

作者:gfree.wind@gmail.com
博客:blog.focus-linux.net   linuxfocus.blog.chinaunix.net
 
 
本文的copyleft归gfree.wind@gmail.com所有,使用GPL发布,可以自由拷贝,转载。但转载请保持文档的完整性,注明原作者及原链接,严禁用于任何商业用途。
======================================================================================================
今天群中的LYJ同学遇到一个问题,经过一番讨论研究,我们得到了结果,因此产生了此文。

LYJ写了一个内核模块,功能很简单。但是加载该模块后,通过top命令,发现CPU的负载持续增加,但是CPU的利用率却并不高。为了更突出问题,我只保留了最少的示例代码:

  1. #include <linux/init.h>
  2. #include <linux/types.h>
  3. #include <linux/module.h>
  4. #include <linux/kthread.h>


  5. static struct task_struct *sleep_task = NULL;

  6. static int thread_func(void *data)
  7. {
  8.     while (1) {
  9.         if (kthread_should_stop()) {
  10.             break;
  11.         }

  12.         ssleep(1);
  13.     }

  14.     return 0;
  15. }

  16. static int __init sleep_task_init(void)
  17. {
  18.     sleep_task = kthread_create(thread_func, NULL, "sleep_task");
  19.     if (IS_ERR(sleep_task)) {
  20.         printk(KERN_ERR "sleep_task: kthread_create failed\n");
  21.         sleep_task = NULL;
  22.         return -1;
  23.     }

  24.     wake_up_process(sleep_task);

  25.     printk(KERN_INFO "sleep_task: init successfully\n");

  26.     return 0;
  27. }

  28. static void __exit sleep_task_exit(void)
  29. {
  30.     if (sleep_task) {
  31.         kthread_stop(sleep_task);
  32.         sleep_task = NULL;
  33.     }

  34.     printk(KERN_INFO "sleep_task: exit successfully\n");
  35. }

  36. MODULE_LICENSE("GPL");

  37. module_init(sleep_task_init);
  38. module_exit(sleep_task_exit);
这段代码很简单。在加载模块的时候,创建了一个内核线程。该内核线程每次sleep 1秒,然后检查是否需要退出。

在得到上面的代码之前,我们不断的猜测是哪些操作导致了CPU负载的持续增加。然后不断的裁剪代码,最后得到上面的代码后,问题依然存在。
加载模块前:

  1. top - 10:50:12 up 1:20, 11 users, load average: 0.08, 0.03, 0.01
加载模块后,运行一段时间:

  1. top - 10:54:21 up 1:24, 11 users, load average: 0.97, 0.51, 0.20
但是CPU占有率确实仍然非常低。


那么剩下的代码中,最有可能导致这个问题的就是ssleep。查看ssleep的代码:

  1. static inline void ssleep(unsigned int seconds)
  2. {
  3.     msleep(seconds * 1000);
  4. }

  5. void msleep(unsigned int msecs)
  6. {
  7.     unsigned long timeout = msecs_to_jiffies(msecs) + 1;

  8.     while (timeout)
  9.         timeout = schedule_timeout_uninterruptible(timeout);
  10. }

  11. signed long __sched schedule_timeout_uninterruptible(signed long timeout)
  12. {
  13.     __set_current_state(TASK_UNINTERRUPTIBLE);
  14.     return schedule_timeout(timeout);
  15. }
从上面的代码也没有看出问题来。于是我怀疑是否ssleep并没有真正的sleep。再添加了log确认sleep确实如期望工作后,这个念头也打消了。最后,我秉着试试看的态度,使用了schedule_timeout_interruptible代替了ssleep。
代码如下:

  1. static int thread_func(void *data)
  2. {
  3.     while (1) {
  4.         if (kthread_should_stop()) {
  5.             break;
  6.         }

  7.         schedule_timeout_interruptible(HZ);
  8.     }

  9.     return 0;
  10. }
结果发现CPU的负载不再增加了。那么造成CPU负载增加的根本原因就在于ssleep时,是将task的状态设置为TASK_UNINTERRUPTIBLE。可这真的让我有些难以接受。因为就我以往的了解,TASK_UNINTERRUPTIBLE与TASK_INTERRUPTIBLE就在于当进程处于前者状态时,是无法被信号中断的。而现在怎么会有这么大的区别呢?!

因为CPU的占有率非常低,所以可以肯定,该线程大部分时间确实不是运行。可是这个结论又与CPU 负载的结果相冲突,并且这个原因还是由于TASK_UNINTERRUPTIBLE造成的。

这样的话,造成这些种种矛盾的原因可能就在于linux的CPU 负载的计算方法。在google搜索了一番,找到了下面的信息:
The system load data is collected by the calc_load( ) function, which is invoked by update_times( ). This activity is therefore performed in the TIMER_BH bottom half. calc_load( ) counts the number of processes in the TASK_RUNNING or TASK_UNINTERRUPTIBLE state and uses this number to update the CPU usage statistics.
上面清楚的说明,Linux在计算CPU负载的时候,TASK_RUNNING和TASK_UNINTERRUPTIBLE两种状态的进程都会被统计。对于本例来说,虽然该thread大部分时间是在sleep,但是其状态不是TASK_RUNNING就是TASK_UNINTERRUPTIBLE,因此CPU的负载会持续增加。

此处留下了一个问题:为什么Linux统计CPU负载的时候,要包括TASK_UNINTERRUPTIBLE呢?今天时间不够了,以后我会继续跟踪这个问题。

上一篇:scanf用法总结 及此分类博文的内容
下一篇:AIX kernel extension 开发中的timer