线程的应用
来源:互联网 发布:年度网络安全检查报告 编辑:程序博客网 时间:2024/06/05 21:59
上节中,我们成功地编译运行了一个linux模 块。可惜的是,它只有两个函数,hello_init在模块加载时调用,hello_exit 在模块卸载时调用。这样下去,模块纵使有天大的本事,也只能压缩在这两个
函数中。为了避免这种悲剧发生,本节就来学习一种让模块在加载后能一直运行下去的 方法——内核线程。 收藏于 2011-10-10阅读数:
公众公开
好文推荐
要创建一个内核线程有许多种方法,我们这里要学的是最简单的一种。打开 include/linux/kthread.h,你就看到了它全部的API,一共三个函数。节日零食之坚果排名
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实现上的弊端,
之后会专门写文章讲到) 28个非常精美的室内装修风格
女人要精致地活着
kthread_should_stop()返回should_stop标志。它用于创建的线程检查结束标志,并决定是否退出。线程完全可以在完成自己的 工作后主动结束,不需等待should_stop标志。【图】揭
下面就来尝试一下运行不息的内核线程吧。
1、把上节建立的hello子目录,复制为新的kthread子目录。
2、修改hello.c,使其内容如下。
1. #include <linux/init.h>
2. #include <linux/module.h>
3. #include <linux/kthread.h>
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()时设置。
上节中我们已经掌握了创建大量内核线程的能力,可惜线程之间还缺乏配合。要知道学习ITC(inter thread communication),和学习IPC(inter process communication)一样,不是件简单的事情。本节
就暂且解释一种最简单的线程同步手段—completion。
打开include/linux/completion.h,你就会看到 completion使用的全部API。这里简单介绍一下。
1. struct completion{
2. unsigned int done;
3. wait_queue_head_t wait;
4. };
5
6. void init_completion(struct completion *x);
7. void wait_for_completion(struct completion *x);
8. void wait_for_completion_interruptible(struct completion *x);
9. void wait_for_completion_killable(struct completion *x);
10. unsigned long wait_for_completion_timeout(struct completion *x,
11. unsigned long timeout);
12. unsigned long wait_for_completion_interruptible_timeout(struct completion *x,
13. unsigned long timeout);
14. bool try_wait_for_completion(struct completion *x);
15. bool completion_done(struct completion *x);
16. void complete(struct completion *x);
17. void complete_all(struct completion *x);
struct completion{ unsigned int done; wait_queue_head_t wait; }; void init_completion(struct completion *x); void wait_for_completion(struct completion *x); void
wait_for_completion_interruptible(struct completion *x); void wait_for_completion_killable(struct completion *x); unsigned long wait_for_completion_timeout(struct completion *x,
unsigned long timeout); unsigned long wait_for_completion_interruptible_timeout(struct completion *x, unsigned long timeout); bool try_wait_for_completion(struct completion *x);
bool completion_done(struct completion *x); void complete(struct completion *x); void complete_all(struct completion *x);
首先是struct completion的结构,由一个计数值和一个等待队列组成。我们就大致明白,completion是类似于信号量的东西,用 completion.done来表示资源是否可用,获取不到的线程会阻塞
在completion.wait的等待队列上,直到其它线程释放 completion。这样理解在实现上不错,但我认为completion不是与具体的资源绑定,而是单纯作为一种线程间同步的机制,它在概念上要比信 号量
清晰得多。以后会逐渐看到,线程间事件的同步大多靠completion,而资源临界区的保护大多靠信号量。所以说,completion是一种线程 间的约会。
init_completion初始化 completion结构。初此之外,linux当然还有在定义变量时初始化的方法,都在completion.h中。
wait_for_completion等待在completion上。如果加了interruptible,就表示线程等待可被外部发来的信号打断;如 果加了killable,就表示线程只可被kill信号打断;如果加了timeout,表示
等待超出一定时间会自动结束等待,timeout的单位是系统 所用的时间片jiffies(多为1ms)。
try_wait_for_completion则是非阻塞地获取completion。它相当于 wait_for_completion_timeout调用中的timeout值为0。
completion_done检查是否有线程阻塞在completion上。但这个API并不准确,它只是检查completion.done是否为 0,为0则认为有线程阻塞。这个API并不会去检查实际的等待队列,所以用时要注
意。
complete唤醒阻塞在completion上的首个线程。
complete_all唤醒阻塞在completion上的所有线程。它的实现手法很粗糙,把completion.done的值设为 UINT_MAX/2,自然所有等待的线程都醒了。所以如果complete_all之后还要使用这个
completion,就要把它重新初始化。
好,completion介绍完毕,下面就来设计我们的模块吧。
我们模拟5个周期性线程的运行。每个周期性线程period_thread的周期各不相同,但都以秒为单位,有各自的completion变量。period_thread每个周期运行一次,然后等待在 自己的completion
变量上。为了唤醒period_thread,我们使用一个watchdog_thread来模拟时钟,每隔1s watchdog_thread就会检查哪个period_thread下一周期是否到来,并用相应的completion唤醒线程。
下面就动手实现吧。
1、把上节建立的kthread子目录,复制为新的completion子目录。
2、修改hello.c,使其内容如下。
1. #include <linux/init.h>
2. #include <linux/module.h>
3. #include <linux/kthread.h>
4. #include <linux/completion.h>
5
6. MODULE_LICENSE("Dual BSD/GPL");
7
8. #define PERIOD_THREAD_NUM 5
9
10. static int periods[PERIOD_THREAD_NUM] =
11. { 1, 2, 4, 8, 16 };
12
13. static struct task_struct *period_tsks[PERIOD_THREAD_NUM];
14
15. static struct task_struct watchdog_tsk;
16
17. static struct completion wakeups[PERIOD_THREAD_NUM];
18
19
20. static int period_thread(void *data)
21. {
22. int k = (int)data;
23. int count = -1;
24
25. do{
26. printk("thread%d: period=%ds, count=%d\n", k, periods[k], ++count);
27. wait_for_completion(&wakeups[k]);
28. }while(!kthread_should_stop());
29. return count;
30. }
31
32. static int watchdog_thread(void *data)
33. {
34. int k;
35. int count = 0;
36
37. do{
38. msleep(1000);
39. count++;
40. for(k=0; k<PERIOD_THREAD_NUM; k++){
41. if (count%periods[k] == 0)
42. complete(&wakeups[k]);
43. }
44. }while(!kthread_should_stop());
45. return count;
46. }
47
48. static int hello_init(void)
49. {
50. int k;
51
52. printk(KERN_INFO "Hello, world!\n");
53
54. for(k=0; k<PERIOD_THREAD_NUM; k++){
55. init_completion(&wakeups[k]);
56. }
57
58. watchdog_tsk = kthread_run(watchdog_thread, NULL, "watchdog_thread");
59
60. if(IS_ERR(watchdog_tsk)){
61. printk(KERN_INFO "create watchdog_thread failed!\n");
62. return 1;
63. }
64
65. for(k=0; k<PERIOD_THREAD_NUM; k++){
66. period_tsks[k] = kthread_run(period_thread, (void*)k, "period_thread%d", k);
67. if(IS_ERR(period_tsks[k]))
68. printk(KERN_INFO "create period_thread%d failed!\n", k);
69. }
70. return 0;
71. }
72
73. static void hello_exit(void)
74. {
75. int k;
76. int count[5], watchdog_count;
77
78. printk(KERN_INFO "Hello, exit!\n");
79. for(k=0; k<PERIOD_THREAD_NUM]; k++){
80. count[k] = 0;
81. if(!IS_ERR(period_tsks[k]))
82. count[k] = kthread_stop(period_tsks[k]);
83. }
84. watchdog_count = 0;
85. if(!IS_ERR(watchdog_tsk))
86. watchdog_count = kthread_stop(watchdog_tsk);
87
88. printk("running total time: %ds\n", watchdog_count);
89. for(k=0; k<PERIOD_THREAD_NUM; k++)
90. printk("thread%d: period %d, running %d times\n", k, periods[k], count[k]);
91. }
92
93. module_init(hello_init);
94. module_exit(hello_exit);
3、编译运行模块,步骤参照前例。为保持模块的简洁性,我们仍然使用了 kthread_stop结束线程,这种方法虽然简单,但在卸载模块时等待时间太长,而且这个时间会随线程个数和周期的增长而增长
。
4、使用统一的exit_flag标志来表示结束请求,hello_exit发送 completion信号给所有的周期线程,最后调用kthread_stop来回收线程返回值。这样所有的周期线程都是在被唤醒后看到 exit_flag,自
动结束,卸载模块时间大大缩短。下面是改进过后的hello.c,之前的那个姑且叫做hello-v1.c好了。
1. #include <linux/init.h>
2. #include <linux/module.h>
3. #include <linux/kthread.h>
4. #include <linux/completion.h>
5
6. MODULE_LICENSE("Dual BSD/GPL");
7
8. #define PERIOD_THREAD_NUM 5
9
10. static int periods[PERIOD_THREAD_NUM] =
11. { 1, 2, 4, 8, 16 };
12
13. static struct task_struct *period_tsks[PERIOD_THREAD_NUM];
14
15. static struct task_struct watchdog_tsk;
16
17. static struct completion wakeups[PERIOD_THREAD_NUM];
18
19. static int exit_flag = 0;
20
21. static int period_thread(void *data)
22. {
23. int k = (int)data;
24. int count = -1;
25
26. do{
27. printk("thread%d: period=%ds, count=%d\n", k, periods[k], ++count);
28. wait_for_completion(&wakeups[k]);
29. }while(!exit_flag);
30. return count;
31. }
32
33. static int watchdog_thread(void *data)
34. {
35. int k;
36. int count = 0;
37
38. do{
39. msleep(1000);
40. count++;
41. for(k=0; k<PERIOD_THREAD_NUM; k++){
42. if (count%periods[k] == 0)
43. complete(&wakeups[k]);
44. }
45. }while(!exit_flag);
46. return count;
47. }
48
49. static int hello_init(void)
50. {
51. int k;
52
53. printk(KERN_INFO "Hello, world!\n");
54
55. for(k=0; k<PERIOD_THREAD_NUM; k++){
56. init_completion(&wakeups[k]);
57. }
58
59. watchdog_tsk = kthread_run(watchdog_thread, NULL, "watchdog_thread");
60
61. if(IS_ERR(watchdog_tsk)){
62. printk(KERN_INFO "create watchdog_thread failed!\n");
63. return 1;
64. }
65
66. for(k=0; k<PERIOD_THREAD_NUM; k++){
67. period_tsks[k] = kthread_run(period_thread, (void*)k, "period_thread%d", k);
68. if(IS_ERR(period_tsks[k]))
69. printk(KERN_INFO "create period_thread%d failed!\n", k);
70. }
71. return 0;
72. }
73
74. static void hello_exit(void)
75. {
76. int k;
77. int count[5], watchdog_count;
78
79. printk(KERN_INFO "Hello, exit!\n");
80. exit_flag = 1;
81. for(k=0; k<PERIOD_THREAD_NUM]; k++)
82. complete_all(&wakeups[k]);
83
84. for(k=0; k<PERIOD_THREAD_NUM]; k++){
85. count[k] = 0;
86. if(!IS_ERR(period_tsks[k]))
87. count[k] = kthread_stop(period_tsks[k]);
88. }
89. watchdog_count = 0;
90. if(!IS_ERR(watchdog_tsk))
91. watchdog_count = kthread_stop(watchdog_tsk);
92
93. printk("running total time: %ds\n", watchdog_count);
94. for(k=0; k<PERIOD_THREAD_NUM; k++)
95. printk("thread%d: period %d, running %d times\n", k, periods[k], count[k]);
96. }
97
98. module_init(hello_init);
99. module_exit(hello_exit);
5、编译运行改进过后的模块。可以看到模块卸载时间大大减少,不会超过1s。
经过本节,我们学会了一种内核线程间同步的机制—completion。线程们已 经开始注意相互配合,以完成复杂的工作。相信它们会越来越聪明的。
附注:
completion的实现在kernel/sched.c中。这里的每个API 都较短,实现也较为简单。completion背后的实现机制其实是等待队列。等待队列的实现会涉及到较多的调度问题,这里先简单略过。
通过之前几节,我们已经了解了内核线程的创建方法kthread,内核同步的工具completion。现在我们就来学学内核线程传递消息的方法 list。或许大家会说,list不是链表吗。不错,list是链表,但
它可以变成承担消息传递的消息队列。消息的发送者把消息放到链表上,并通过同步工 具(如completion)通知接收线程,接收线程再从链表上取回消息,就这么简单。linux内核或许没有给我们定制
好的东西,但却给了我们可随意变 换的、基础的工具,把这些工具稍加组合就能完成复杂的功能。list又是这些万能工具中最常用的。
前 面两篇文章的惯例是先对新增的功能做出介绍,并解释要用到的API。但我感觉这种既要解释原理,又要分析代码,又要写应用样例的十全文章,写起来实在吃 力,而且漏洞百出。与其如此,我还不
如把两部分分开,这里的模块编程就专心设计模块,编写内核API的组合使用样例;而原理介绍、API代码分析的部分, 会转到linux内核部件分析的部分。这样我一方面能安心设计样例,一方面能把API
介绍地更全面一些。
模块设计
本模块目的是展示list作为消息队列使用时的情况。所以会创建一个全局链表work_list,定义一种消息的结构struct work_event,并创建两个内核线程work_thread和watchdog_thread。work_thread是
消息的接收者,它循环检查 work_list,如果其上有消息,就将其取出并执行,否则阻塞。watchdog_thread是消息的发送者,它周期性地发送消息到 work_list,并唤醒work_thread。
模块实现
1、建立list子目录。
2、编写list.c,使其内容如下。
1. #include <linux/init.h>
2. #include <linux/module.h>
3. #include <linux/list.h>
4. #include <linux/completion.h>
5. #include <linux/kthread.h>
6
7. MODULE_LICENSE("Dual BSD/GPL");
8
9. static struct task_struct *work_tsk;
10
11. static struct task_struct *watchdog_tsk;
12
13. static DECLARE_SPINLOCK(work_list_lock);
14
15. static LIST_HEAD(work_list);
16
17. static DECLARE_COMPLETION(work_wait);
18
19. enum WORK_EVENT_TYPE {
20. EVENT_TIMER,
21. EVENT_EXIT
22. };
23
24. struct work_event {
25. enum WORK_EVENT_TYPE type;
26. int need_free;
27. list_head list;
28. };
29
30. static int work_thread(void *data)
31. {
32. int count = 0;
33
34. while(1){
35. if(list_empty(&work_list))
36. wait_for_completion(&work_wait);
37. spin_lock(&work_list_lock);
38. while(!list_empty(&work_list)){
39. struct work_event *event;
40. event = list_entry(work_list.next, struct work_event, list);
41. list_del(&event->list);
42. spin_unlock(&work_list_lock);
43
44. if(event->type == EVENT_TIMER){
45. printk(KERN_INFO "event timer: count = %d\n", ++count);
46. }
47. else if (event->type == EVENT_EXIT){
48. if(event->need_free)
49. kfree(event);
50. goto exit;
51. }
52
53. if(event->need_free)
54. kfree(event);
55. spin_lock(&work_list_lock);
56. }
57. }
58. exit:
59. return count;
60. }
61
62. static int watchdog_thread(void *data)
63. {
64. int count = 0;
65. while(!kthread_should_stop()){
66. msleep(1000);
67. count++;
68. if(count%5 == 0){
69. struct work_event *event;
70. event = kmalloc(sizeof(struct work_event), GFP_KERNEL);
71. if(event == NULL){
72. printk(KERN_INFO "watchdog_thread: kmalloc failed!\n");
73. break;
74. }
75. event->type = EVENT_TIMER;
76. event->need_free = 1;
77. spin_lock(&work_list_lock);
78. list_add_tail(&event->list, &work_list);
79. spin_unlock(&work_list_lock);
80. complete(&work_wait);
81. }
82. }
83. return count;
84. }
85
86. static int list_init()
87. {
88. printk(KERN_INFO "list_init()\n");
89
90. watchdog_tsk = kthread_run(watchdog_thread, NULL, "watchdog_thread");
91. if(IS_ERR(watchdog_tsk))
92. goto err1;
93
94. work_tsk = kthread_run(work_thread, NULL, "work_thread");
95. if(IS_ERR(work_tsk))
96. goto err2;
97
98. return 0;
99
100. err2:
101. kthread_stop(watchdog_tsk);
102. err1:
103. return 1;
104. }
105
106. static void list_exit()
107. {
108. printk(KERN_INFO "list_exit()\n");
109
110. if(!IS_ERR(watchdog_tsk)){
111. int count = kthread_stop(watchdog_tsk);
112. printk(KERN_INFO "watchdog_thread: running for %ss\n", count);
113. }
114. if(!IS_ERR(work_tsk)){
115. get_task_struct(&work_tsk);
116. struct work_event event;
117. event.type = EVENT_EXIT;
118. event.need_free = 0;
119. spin_lock(&work_list_lock);
120. list_add(&event.list, &work_list);
121. spin_unlock(&work_list_lock);
122. complete(&work_wait);
123
124. int count = kthread_stop(work_tsk);
125. printk(KERN_INFO "work_thread: period 5s, running %d times\n", count);
126. }
127. }
128
129. module_init(list_init);
130. module_exit(list_exit);
整个模块较为简单,work_thread只是接收work_list中的消息并处理,所以在list_exit退出时也要给它发 EVENT_EXIT类型的消息,使其退出。至于在list_exit发消息之前调用的get_task_struct,实
在是无奈之举。因为我们发送 EVENT_EXIT消息后work_thread会在kthread_stop调用前就马上结束,导致之后的kthread_stop错误。所以要先 get_task_struct防止work_thread退出后释放任务结构中的
某些内容,虽然有对应的put_task_struct,但我们并未使 用,因为put_task_struct并未引出到符号表。当然,这种情况只是一时之举,等我们学习了更强大的线程同步机制,或者更灵活的线程管理方
法, 就能把代码改得更流畅。
注意到代码中使用spin_lock/spin_unlock来保护work_list。如果之前不了解spin_lock,很容易认为它不足以保护。实 际上spin_lock不止是使用自旋锁,在此之前还调用了preempt_disable来
禁止本cpu任务调度。只要同步行为只发生在线程之 间,spin_lock就足以保护,如果有中断或者软中断参与进来,就需要用spin_lock_irqsave了。
3、 编译运行模块。
可能本节介绍的list显得过于质朴,但只有简单的东西才能长久保留。不久之后,我们或许不再用kthread,不再用completion,但 list一定会一直用下去。
函数中。为了避免这种悲剧发生,本节就来学习一种让模块在加载后能一直运行下去的 方法——内核线程。 收藏于 2011-10-10阅读数:
公众公开
好文推荐
要创建一个内核线程有许多种方法,我们这里要学的是最简单的一种。打开 include/linux/kthread.h,你就看到了它全部的API,一共三个函数。节日零食之坚果排名
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实现上的弊端,
之后会专门写文章讲到) 28个非常精美的室内装修风格
女人要精致地活着
kthread_should_stop()返回should_stop标志。它用于创建的线程检查结束标志,并决定是否退出。线程完全可以在完成自己的 工作后主动结束,不需等待should_stop标志。【图】揭
下面就来尝试一下运行不息的内核线程吧。
1、把上节建立的hello子目录,复制为新的kthread子目录。
2、修改hello.c,使其内容如下。
1. #include <linux/init.h>
2. #include <linux/module.h>
3. #include <linux/kthread.h>
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()时设置。
上节中我们已经掌握了创建大量内核线程的能力,可惜线程之间还缺乏配合。要知道学习ITC(inter thread communication),和学习IPC(inter process communication)一样,不是件简单的事情。本节
就暂且解释一种最简单的线程同步手段—completion。
打开include/linux/completion.h,你就会看到 completion使用的全部API。这里简单介绍一下。
1. struct completion{
2. unsigned int done;
3. wait_queue_head_t wait;
4. };
5
6. void init_completion(struct completion *x);
7. void wait_for_completion(struct completion *x);
8. void wait_for_completion_interruptible(struct completion *x);
9. void wait_for_completion_killable(struct completion *x);
10. unsigned long wait_for_completion_timeout(struct completion *x,
11. unsigned long timeout);
12. unsigned long wait_for_completion_interruptible_timeout(struct completion *x,
13. unsigned long timeout);
14. bool try_wait_for_completion(struct completion *x);
15. bool completion_done(struct completion *x);
16. void complete(struct completion *x);
17. void complete_all(struct completion *x);
struct completion{ unsigned int done; wait_queue_head_t wait; }; void init_completion(struct completion *x); void wait_for_completion(struct completion *x); void
wait_for_completion_interruptible(struct completion *x); void wait_for_completion_killable(struct completion *x); unsigned long wait_for_completion_timeout(struct completion *x,
unsigned long timeout); unsigned long wait_for_completion_interruptible_timeout(struct completion *x, unsigned long timeout); bool try_wait_for_completion(struct completion *x);
bool completion_done(struct completion *x); void complete(struct completion *x); void complete_all(struct completion *x);
首先是struct completion的结构,由一个计数值和一个等待队列组成。我们就大致明白,completion是类似于信号量的东西,用 completion.done来表示资源是否可用,获取不到的线程会阻塞
在completion.wait的等待队列上,直到其它线程释放 completion。这样理解在实现上不错,但我认为completion不是与具体的资源绑定,而是单纯作为一种线程间同步的机制,它在概念上要比信 号量
清晰得多。以后会逐渐看到,线程间事件的同步大多靠completion,而资源临界区的保护大多靠信号量。所以说,completion是一种线程 间的约会。
init_completion初始化 completion结构。初此之外,linux当然还有在定义变量时初始化的方法,都在completion.h中。
wait_for_completion等待在completion上。如果加了interruptible,就表示线程等待可被外部发来的信号打断;如 果加了killable,就表示线程只可被kill信号打断;如果加了timeout,表示
等待超出一定时间会自动结束等待,timeout的单位是系统 所用的时间片jiffies(多为1ms)。
try_wait_for_completion则是非阻塞地获取completion。它相当于 wait_for_completion_timeout调用中的timeout值为0。
completion_done检查是否有线程阻塞在completion上。但这个API并不准确,它只是检查completion.done是否为 0,为0则认为有线程阻塞。这个API并不会去检查实际的等待队列,所以用时要注
意。
complete唤醒阻塞在completion上的首个线程。
complete_all唤醒阻塞在completion上的所有线程。它的实现手法很粗糙,把completion.done的值设为 UINT_MAX/2,自然所有等待的线程都醒了。所以如果complete_all之后还要使用这个
completion,就要把它重新初始化。
好,completion介绍完毕,下面就来设计我们的模块吧。
我们模拟5个周期性线程的运行。每个周期性线程period_thread的周期各不相同,但都以秒为单位,有各自的completion变量。period_thread每个周期运行一次,然后等待在 自己的completion
变量上。为了唤醒period_thread,我们使用一个watchdog_thread来模拟时钟,每隔1s watchdog_thread就会检查哪个period_thread下一周期是否到来,并用相应的completion唤醒线程。
下面就动手实现吧。
1、把上节建立的kthread子目录,复制为新的completion子目录。
2、修改hello.c,使其内容如下。
1. #include <linux/init.h>
2. #include <linux/module.h>
3. #include <linux/kthread.h>
4. #include <linux/completion.h>
5
6. MODULE_LICENSE("Dual BSD/GPL");
7
8. #define PERIOD_THREAD_NUM 5
9
10. static int periods[PERIOD_THREAD_NUM] =
11. { 1, 2, 4, 8, 16 };
12
13. static struct task_struct *period_tsks[PERIOD_THREAD_NUM];
14
15. static struct task_struct watchdog_tsk;
16
17. static struct completion wakeups[PERIOD_THREAD_NUM];
18
19
20. static int period_thread(void *data)
21. {
22. int k = (int)data;
23. int count = -1;
24
25. do{
26. printk("thread%d: period=%ds, count=%d\n", k, periods[k], ++count);
27. wait_for_completion(&wakeups[k]);
28. }while(!kthread_should_stop());
29. return count;
30. }
31
32. static int watchdog_thread(void *data)
33. {
34. int k;
35. int count = 0;
36
37. do{
38. msleep(1000);
39. count++;
40. for(k=0; k<PERIOD_THREAD_NUM; k++){
41. if (count%periods[k] == 0)
42. complete(&wakeups[k]);
43. }
44. }while(!kthread_should_stop());
45. return count;
46. }
47
48. static int hello_init(void)
49. {
50. int k;
51
52. printk(KERN_INFO "Hello, world!\n");
53
54. for(k=0; k<PERIOD_THREAD_NUM; k++){
55. init_completion(&wakeups[k]);
56. }
57
58. watchdog_tsk = kthread_run(watchdog_thread, NULL, "watchdog_thread");
59
60. if(IS_ERR(watchdog_tsk)){
61. printk(KERN_INFO "create watchdog_thread failed!\n");
62. return 1;
63. }
64
65. for(k=0; k<PERIOD_THREAD_NUM; k++){
66. period_tsks[k] = kthread_run(period_thread, (void*)k, "period_thread%d", k);
67. if(IS_ERR(period_tsks[k]))
68. printk(KERN_INFO "create period_thread%d failed!\n", k);
69. }
70. return 0;
71. }
72
73. static void hello_exit(void)
74. {
75. int k;
76. int count[5], watchdog_count;
77
78. printk(KERN_INFO "Hello, exit!\n");
79. for(k=0; k<PERIOD_THREAD_NUM]; k++){
80. count[k] = 0;
81. if(!IS_ERR(period_tsks[k]))
82. count[k] = kthread_stop(period_tsks[k]);
83. }
84. watchdog_count = 0;
85. if(!IS_ERR(watchdog_tsk))
86. watchdog_count = kthread_stop(watchdog_tsk);
87
88. printk("running total time: %ds\n", watchdog_count);
89. for(k=0; k<PERIOD_THREAD_NUM; k++)
90. printk("thread%d: period %d, running %d times\n", k, periods[k], count[k]);
91. }
92
93. module_init(hello_init);
94. module_exit(hello_exit);
3、编译运行模块,步骤参照前例。为保持模块的简洁性,我们仍然使用了 kthread_stop结束线程,这种方法虽然简单,但在卸载模块时等待时间太长,而且这个时间会随线程个数和周期的增长而增长
。
4、使用统一的exit_flag标志来表示结束请求,hello_exit发送 completion信号给所有的周期线程,最后调用kthread_stop来回收线程返回值。这样所有的周期线程都是在被唤醒后看到 exit_flag,自
动结束,卸载模块时间大大缩短。下面是改进过后的hello.c,之前的那个姑且叫做hello-v1.c好了。
1. #include <linux/init.h>
2. #include <linux/module.h>
3. #include <linux/kthread.h>
4. #include <linux/completion.h>
5
6. MODULE_LICENSE("Dual BSD/GPL");
7
8. #define PERIOD_THREAD_NUM 5
9
10. static int periods[PERIOD_THREAD_NUM] =
11. { 1, 2, 4, 8, 16 };
12
13. static struct task_struct *period_tsks[PERIOD_THREAD_NUM];
14
15. static struct task_struct watchdog_tsk;
16
17. static struct completion wakeups[PERIOD_THREAD_NUM];
18
19. static int exit_flag = 0;
20
21. static int period_thread(void *data)
22. {
23. int k = (int)data;
24. int count = -1;
25
26. do{
27. printk("thread%d: period=%ds, count=%d\n", k, periods[k], ++count);
28. wait_for_completion(&wakeups[k]);
29. }while(!exit_flag);
30. return count;
31. }
32
33. static int watchdog_thread(void *data)
34. {
35. int k;
36. int count = 0;
37
38. do{
39. msleep(1000);
40. count++;
41. for(k=0; k<PERIOD_THREAD_NUM; k++){
42. if (count%periods[k] == 0)
43. complete(&wakeups[k]);
44. }
45. }while(!exit_flag);
46. return count;
47. }
48
49. static int hello_init(void)
50. {
51. int k;
52
53. printk(KERN_INFO "Hello, world!\n");
54
55. for(k=0; k<PERIOD_THREAD_NUM; k++){
56. init_completion(&wakeups[k]);
57. }
58
59. watchdog_tsk = kthread_run(watchdog_thread, NULL, "watchdog_thread");
60
61. if(IS_ERR(watchdog_tsk)){
62. printk(KERN_INFO "create watchdog_thread failed!\n");
63. return 1;
64. }
65
66. for(k=0; k<PERIOD_THREAD_NUM; k++){
67. period_tsks[k] = kthread_run(period_thread, (void*)k, "period_thread%d", k);
68. if(IS_ERR(period_tsks[k]))
69. printk(KERN_INFO "create period_thread%d failed!\n", k);
70. }
71. return 0;
72. }
73
74. static void hello_exit(void)
75. {
76. int k;
77. int count[5], watchdog_count;
78
79. printk(KERN_INFO "Hello, exit!\n");
80. exit_flag = 1;
81. for(k=0; k<PERIOD_THREAD_NUM]; k++)
82. complete_all(&wakeups[k]);
83
84. for(k=0; k<PERIOD_THREAD_NUM]; k++){
85. count[k] = 0;
86. if(!IS_ERR(period_tsks[k]))
87. count[k] = kthread_stop(period_tsks[k]);
88. }
89. watchdog_count = 0;
90. if(!IS_ERR(watchdog_tsk))
91. watchdog_count = kthread_stop(watchdog_tsk);
92
93. printk("running total time: %ds\n", watchdog_count);
94. for(k=0; k<PERIOD_THREAD_NUM; k++)
95. printk("thread%d: period %d, running %d times\n", k, periods[k], count[k]);
96. }
97
98. module_init(hello_init);
99. module_exit(hello_exit);
5、编译运行改进过后的模块。可以看到模块卸载时间大大减少,不会超过1s。
经过本节,我们学会了一种内核线程间同步的机制—completion。线程们已 经开始注意相互配合,以完成复杂的工作。相信它们会越来越聪明的。
附注:
completion的实现在kernel/sched.c中。这里的每个API 都较短,实现也较为简单。completion背后的实现机制其实是等待队列。等待队列的实现会涉及到较多的调度问题,这里先简单略过。
通过之前几节,我们已经了解了内核线程的创建方法kthread,内核同步的工具completion。现在我们就来学学内核线程传递消息的方法 list。或许大家会说,list不是链表吗。不错,list是链表,但
它可以变成承担消息传递的消息队列。消息的发送者把消息放到链表上,并通过同步工 具(如completion)通知接收线程,接收线程再从链表上取回消息,就这么简单。linux内核或许没有给我们定制
好的东西,但却给了我们可随意变 换的、基础的工具,把这些工具稍加组合就能完成复杂的功能。list又是这些万能工具中最常用的。
前 面两篇文章的惯例是先对新增的功能做出介绍,并解释要用到的API。但我感觉这种既要解释原理,又要分析代码,又要写应用样例的十全文章,写起来实在吃 力,而且漏洞百出。与其如此,我还不
如把两部分分开,这里的模块编程就专心设计模块,编写内核API的组合使用样例;而原理介绍、API代码分析的部分, 会转到linux内核部件分析的部分。这样我一方面能安心设计样例,一方面能把API
介绍地更全面一些。
模块设计
本模块目的是展示list作为消息队列使用时的情况。所以会创建一个全局链表work_list,定义一种消息的结构struct work_event,并创建两个内核线程work_thread和watchdog_thread。work_thread是
消息的接收者,它循环检查 work_list,如果其上有消息,就将其取出并执行,否则阻塞。watchdog_thread是消息的发送者,它周期性地发送消息到 work_list,并唤醒work_thread。
模块实现
1、建立list子目录。
2、编写list.c,使其内容如下。
1. #include <linux/init.h>
2. #include <linux/module.h>
3. #include <linux/list.h>
4. #include <linux/completion.h>
5. #include <linux/kthread.h>
6
7. MODULE_LICENSE("Dual BSD/GPL");
8
9. static struct task_struct *work_tsk;
10
11. static struct task_struct *watchdog_tsk;
12
13. static DECLARE_SPINLOCK(work_list_lock);
14
15. static LIST_HEAD(work_list);
16
17. static DECLARE_COMPLETION(work_wait);
18
19. enum WORK_EVENT_TYPE {
20. EVENT_TIMER,
21. EVENT_EXIT
22. };
23
24. struct work_event {
25. enum WORK_EVENT_TYPE type;
26. int need_free;
27. list_head list;
28. };
29
30. static int work_thread(void *data)
31. {
32. int count = 0;
33
34. while(1){
35. if(list_empty(&work_list))
36. wait_for_completion(&work_wait);
37. spin_lock(&work_list_lock);
38. while(!list_empty(&work_list)){
39. struct work_event *event;
40. event = list_entry(work_list.next, struct work_event, list);
41. list_del(&event->list);
42. spin_unlock(&work_list_lock);
43
44. if(event->type == EVENT_TIMER){
45. printk(KERN_INFO "event timer: count = %d\n", ++count);
46. }
47. else if (event->type == EVENT_EXIT){
48. if(event->need_free)
49. kfree(event);
50. goto exit;
51. }
52
53. if(event->need_free)
54. kfree(event);
55. spin_lock(&work_list_lock);
56. }
57. }
58. exit:
59. return count;
60. }
61
62. static int watchdog_thread(void *data)
63. {
64. int count = 0;
65. while(!kthread_should_stop()){
66. msleep(1000);
67. count++;
68. if(count%5 == 0){
69. struct work_event *event;
70. event = kmalloc(sizeof(struct work_event), GFP_KERNEL);
71. if(event == NULL){
72. printk(KERN_INFO "watchdog_thread: kmalloc failed!\n");
73. break;
74. }
75. event->type = EVENT_TIMER;
76. event->need_free = 1;
77. spin_lock(&work_list_lock);
78. list_add_tail(&event->list, &work_list);
79. spin_unlock(&work_list_lock);
80. complete(&work_wait);
81. }
82. }
83. return count;
84. }
85
86. static int list_init()
87. {
88. printk(KERN_INFO "list_init()\n");
89
90. watchdog_tsk = kthread_run(watchdog_thread, NULL, "watchdog_thread");
91. if(IS_ERR(watchdog_tsk))
92. goto err1;
93
94. work_tsk = kthread_run(work_thread, NULL, "work_thread");
95. if(IS_ERR(work_tsk))
96. goto err2;
97
98. return 0;
99
100. err2:
101. kthread_stop(watchdog_tsk);
102. err1:
103. return 1;
104. }
105
106. static void list_exit()
107. {
108. printk(KERN_INFO "list_exit()\n");
109
110. if(!IS_ERR(watchdog_tsk)){
111. int count = kthread_stop(watchdog_tsk);
112. printk(KERN_INFO "watchdog_thread: running for %ss\n", count);
113. }
114. if(!IS_ERR(work_tsk)){
115. get_task_struct(&work_tsk);
116. struct work_event event;
117. event.type = EVENT_EXIT;
118. event.need_free = 0;
119. spin_lock(&work_list_lock);
120. list_add(&event.list, &work_list);
121. spin_unlock(&work_list_lock);
122. complete(&work_wait);
123
124. int count = kthread_stop(work_tsk);
125. printk(KERN_INFO "work_thread: period 5s, running %d times\n", count);
126. }
127. }
128
129. module_init(list_init);
130. module_exit(list_exit);
整个模块较为简单,work_thread只是接收work_list中的消息并处理,所以在list_exit退出时也要给它发 EVENT_EXIT类型的消息,使其退出。至于在list_exit发消息之前调用的get_task_struct,实
在是无奈之举。因为我们发送 EVENT_EXIT消息后work_thread会在kthread_stop调用前就马上结束,导致之后的kthread_stop错误。所以要先 get_task_struct防止work_thread退出后释放任务结构中的
某些内容,虽然有对应的put_task_struct,但我们并未使 用,因为put_task_struct并未引出到符号表。当然,这种情况只是一时之举,等我们学习了更强大的线程同步机制,或者更灵活的线程管理方
法, 就能把代码改得更流畅。
注意到代码中使用spin_lock/spin_unlock来保护work_list。如果之前不了解spin_lock,很容易认为它不足以保护。实 际上spin_lock不止是使用自旋锁,在此之前还调用了preempt_disable来
禁止本cpu任务调度。只要同步行为只发生在线程之 间,spin_lock就足以保护,如果有中断或者软中断参与进来,就需要用spin_lock_irqsave了。
3、 编译运行模块。
可能本节介绍的list显得过于质朴,但只有简单的东西才能长久保留。不久之后,我们或许不再用kthread,不再用completion,但 list一定会一直用下去。
- 线程的应用
- 线程实例的应用
- Java的线程应用
- 线程应用的场景
- GCD 线程的应用
- GCD线程的应用
- 线程池的应用
- Qt线程的应用
- 线程的小应用
- 服务和线程的应用
- Java线程的高级应用
- C# 中线程的应用
- 线程pthread_mutex_init()锁的应用
- java线程的基本应用
- 线程pthread_mutex_init()锁的应用
- 线程pthread_mutex_init()锁的应用
- android 线程池的应用
- 关于线程变量的应用
- 多值依赖,范式
- android代码优化之如何提高Cursor的性能
- 在ubuntu下搭建android开发环境
- Cygwin教程(转至http://cygwincommands.com/)
- Usefunctional programming in Perl to make test automation code more structural
- 线程的应用
- ArrayList列表不同遍历方式性能比较
- Linux下学习动态库的笔记2
- 如何处理大数据量的查询
- UITouches 屏幕绘图线的计算
- ALE/IDOC 中常用 T-CODE
- task_struct结构
- linux中source的用法
- var_dump,var_export,print_r三个函数的区别