linux模块编程(二)——运行不息的内核线程kthread

来源:互联网 发布:淘宝50元1只狗谁买过 编辑:程序博客网 时间:2024/05/01 07:11
上节中,我们成功地编译运行了一个linux模块。可惜的是,它只有两个函数,hello_init在模块加载时调用,hello_exit 在模块卸载时调用。这样下去,模块纵使有天大的本事,也只能压缩在这两个函数中。为了避免这种悲剧发生,本节就来学习一种让模块在加载后能一直运行下去的方法——内核线程。

      要创建一个内核线程有许多种方法,我们这里要学的是最简单的一种。打开include/linux/kthread.h,你就看到了它全部的API,一共三个函数。

[cpp] view plaincopyprint?
  1. struct task_struct kthread_run(int (*threadfn)(void *data),  
  2.     void *data, const char namefmt[],...);  
  3. int kthread_stop(struct task_struct *k);  
  4. int kthread_should_stop(void);  

      kthread_run()负责内核线程的创建,参数包括入口函数threadfn,参数data,线程名称namefmt。可以看到线程的名字可以是类似sprintf方式组成的字符串。如果实际看到kthread.h文件,就会发现kthread_run实际是一个宏定义,它由kthread_create()和wake_up_process()两部分组成,这样的好处是用kthread_run()创建的线程可以直接运行,使用方便。

      kthread_stop()负责结束创建的线程,参数是创建时返回的task_struct指针。kthread设置标志should_stop,并等待线程主动结束,返回线程的返回值。线程可能在kthread_stop()调用前就结束。(经过实际验证,如果线程在kthread_stop()调用之前就结束,之后kthread_stop()再调用会发生可怕地事情—调用kthread_stop()的进程crash!!之所以如此,是由于kthread实现上的弊端,之后会专门写文章讲到)

      kthread_should_stop()返回should_stop标志。它用于创建的线程检查结束标志,并决定是否退出。线程完全可以在完成自己的工作后主动结束,不需等待should_stop标志。

 

    下面就来尝试一下运行不息的内核线程吧。

1、把上节建立的hello子目录,复制为新的kthread子目录。

 

2、修改hello.c,使其内容如下。

 

[cpp] view plaincopyprint?
  1. #include <linux/init.h>  
  2. #include <linux/module.h>  
  3. #include <linux/kthread.h>  
  4.   
  5. MODULE_LICENSE("Dual BSD/GPL");  
  6.   
  7. static struct task_struct *tsk;  
  8.   
  9. static int thread_function(void *data)  
  10. {  
  11.     int time_count = 0;  
  12.     do {  
  13.         printk(KERN_INFO "thread_function: %d times", ++time_count);  
  14.         msleep(1000);  
  15.     }while(!kthread_should_stop() && time_count<=30);  
  16.     return time_count;  
  17. }  
  18.   
  19. static int hello_init(void)  
  20. {  
  21.     printk(KERN_INFO "Hello, world!\n");  
  22.   
  23.     tsk = kthread_run(thread_function, NULL, "mythread%d", 1);  
  24.     if (IS_ERR(tsk)) {  
  25.         printk(KERN_INFO "create kthread failed!\n");  
  26.     }  
  27.     else {  
  28.         printk(KERN_INFO "create ktrhead ok!\n");  
  29.     }  
  30.     return 0;  
  31. }  
  32.   
  33. static void hello_exit(void)  
  34. {  
  35.     printk(KERN_INFO "Hello, exit!\n");  
  36.     if (!IS_ERR(tsk)){  
  37.         int ret = kthread_stop(tsk);  
  38.         printk(KERN_INFO "thread function has run %ds\n", ret);  
  39.     }  
  40. }  
  41.   
  42. module_init(hello_init);  
  43. module_exit(hello_exit);  


       为了不让创建的内核线程一直运行浪费CPU,代码中采用周期性延迟的方式,每次循环用msleep(1000)延迟1s。为了防止线程一直运行下去,代码中使用了两个结束条件:一个是模块要求线程结束,一个是打印满一定次数,后者是为了防止printk输出信息太多。最后在hello_exit中结束线程,并打印线程运行的时间。

      这里要注意的是kthread_run的返回值tsk。不能用tsk是否为NULL进行检查,而要用IS_ERR()宏定义检查,这是因为返回的是错误码,大致从0xfffff000~0xffffffff。

 

3、编译运行模块,步骤参照前例。在运行过程中使用ps -e命令,可以看到有名字位mythread1的内核线程在运行。

 

 

      经过本节,我们学习了内核线程的创建使用方法,现在要创建一大堆的线程在内核中已经易如反掌。你会逐渐相信,我们模块的拓展空间是无限的。

 

附注:

      我们的重点在模块编程,不断学习内核API的使用。但如果能知其然,而知其所以然就更好了。所以有了文章后的附注部分。在附注部分,我们会尽量解释内核API的实现原理,对相关linux内核代码做简单的分析,以帮助大家学习理解相关的代码。分析的代码包含在linux-2.6.32中,但这些代码在相近版本中都变化不大。作者水平有限,请大家见谅。

      kthread的实现在kernel/kthread.c中,头文件是include/linux/kthread.h。内核中一直运行一个线程kthreadd,它运行kthread.c中的kthreadd函数。在kthreadd()中,不断检查一个kthread_create_list链表。kthread_create_list中的每个节点都是一个创建内核线程的请求,kthreadd()发现链表不为空,就将其第一个节点退出链表,并调用create_kthread()创建相应的线程。create_kthread()则进一步调用更深层的kernel_thread()创建线程,入口函数设在kthread()中。

      外界调用kthread_run创建运行线程。kthread_run是个宏定义,首先调用kthread_create()创建线程,如果创建成功,再调用wake_up_process()唤醒新创建的线程。kthread_create()根据参数向kthread_create_list中发送一个请求,并唤醒kthreadd,之后会调用wait_for_completion(&create.done)等待线程创建完成。新创建的线程开始运行后,入口在kthread(),kthread()调用complete(&create->done)唤醒阻塞的模块进程,并使用schedule()调度出去。kthread_create()被唤醒后,设置新线程的名称,并返回到kthread_run中。kthread_run调用wake_up_process()重新唤醒新创建线程,此时新线程才开始运行kthread_run参数中的入口函数。

      外界调用kthread_stop()删除线程。kthread_stop首先设置结束标志should_stop,然后调用wake_for_completion(&kthread->exited)上,这个其实是新线程task_struct上的vfork_done,会在线程结束调用do_exit()时设置。

0 0
原创粉丝点击