linux内核多线程.docx
- 文档编号:5659779
- 上传时间:2022-12-30
- 格式:DOCX
- 页数:14
- 大小:22.15KB
linux内核多线程.docx
《linux内核多线程.docx》由会员分享,可在线阅读,更多相关《linux内核多线程.docx(14页珍藏版)》请在冰豆网上搜索。
linux内核多线程
Linux内核多线程(转)
Linux内核可以看作一个服务进程(管理软硬件资源,响应用户进程的种种合理以及不合
理的请求)。
内核需要多个执行流并行,为了防止可能的阻塞,支持多线程是必要的。
内核线程就是内核的分身,一个分身可以处理一件特定事情。
内核线程的调度
由内核负责,一个内核线程处于阻塞状态时不影响其他的内核线程,因为其是调度的基本单位。
这与用户线程是不一样的。
因为内核线程只运行在内核态,因此,它
只能使用大于PAGE_OFFSET(3G)的地址空间。
内核线程和普通的进程间的区别在于内核线程没有独立的地址空间,mm指针被设置为NULL;它只
在内核空间运行,从来不切换到用户空间去;并且和普通进程一样,可以被调度,也可以被抢占。
内核线程(thread)或叫守护进程(daemon),在操作系统中占据相当大的比例,当Linux操作系统启动以后,你可以用”ps-ef”命令查看系统中的进程,这时会发现很多以”d”结尾的进程名,确切说名称显示里面加"[]"的,这些进程就是内核线程。
创建内核线程最基本的两个接口函数是:
kthread_run(threadfn,data,namefmt,...)
和
kernel_thread(int(*fn)(void*),void*arg,unsignedlongflags)
这里我们主要介绍kthread_run,后面会专门分析这两个函数的异同。
kthread_run事实上是一个宏定义:
/***kthread_run-createandwakeathread.*@threadfn:
thefunctiontorununtilsignal_pending(current).*@data:
dataptrfor@threadfn.*@namefmt:
printf-stylenameforthethread.**Description:
Convenientwrapperforkthread_create()followedby*wake_up_process().ReturnsthekthreadorERR_PTR(-ENOMEM).*/#definekthread_run(threadfn,data,namefmt,...)\
({
structtask_struct*__k
=kthread_create(threadfn,data,namefmt,##__VA_ARGS__);
if(!
IS_ERR(__k))
wake_up_process(__k);
__k;
})
kthread_run()负责内核线程的创建,它由kthread_create()和wake_up_process()两部分组成,这样的
好处是用kthread_run()创建的线程可以直接运行。
外界调用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参数中的入口函数。
在介绍完如何创建线程之后,下面来介绍另外两个基本的函数:
intkthread_stop(structtask_struct*k);
intkthread_should_stop(void);
kthread_stop()负责结束创建的线程,参数是创建时返回的task_struct指针。
kthread设置标志
should_stop,并等待线程主动结束,返回线程的返回值。
在调用kthread_stop()结束线程之前一定要检查该线程是否还在运行(通过
kthread_run返回的task_stuct
是否有效),否则会造成灾难性的后果。
kthread_run的返回值tsk。
不能用tsk是否为NULL进行检查,而要用IS_ERR()宏定义检查,
这是因为返回的是错误码,大致从0xfffff000~0xffffffff。
kthread_should_stop()返回should_stop标志(参见structkthread)。
它用于创建的线程检查结束标志,并决定是否退出。
kthread()(注:
原型为:
staticintkthread(void
*_create))的实现在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_stop()删除线程。
kthread_stop首先设置结束标志should_stop,然后调用
wake_for_completion(&kthread->exited)上,这个其实是新线程task_struct上的
vfork_done,会在线程结束调用do_exit()时设置。
附:
structkthread{intshould_stop;structcompletionexited;};intkthreadd(void*unused)
{structtask_struct*tsk=current;/*Setupacleancontextforourchildrentoinherit.*/set_task_comm(tsk,"kthreadd");ignore_signals(tsk);set_cpus_allowed_ptr(tsk,cpu_all_mask);set_mems_allowed(node_states[N_HIGH_MEMORY]);current->flags|=PF_NOFREEZE|PF_FREEZER_NOSIG;
for(;;){set_current_state(TASK_INTERRUPTIBLE);if(list_empty(&kthread_create_list))schedule();__set_current_state(TASK_RUNNING);
spin_lock(&kthread_create_lock);while(!
list_empty(&kthread_create_list)){structkthread_create_info*create;create=list_entry(kthread_create_list.next,structkthread_create_info,list);list_del_init(&create->list);spin_unlock(&kthread_create_lock);create_kthread(create);spin_lock(&kthread_create_lock);}spin_unlock(&kthread_create_lock);}return0;
}/***kthread_stop-stopathreadcreatedbykthread_create().*@k:
threadcreatedbykthread_create().**Setskthread_should_stop()for@ktoreturntrue,wakesit,and*waitsforittoexit.Thiscanalsobecalledafterkthread_create()*insteadofcallingwake_up_process():
thethreadwillexitwithout*callingthreadfn().**Ifthreadfn()maycalldo_exit()itself,thecallermustensure*task_structcan'tgoaway.**Returnstheresultofthreadfn(),or%-EINTRifwake_up_process()*wasnevercalled.*/intkthread_stop(structtask_struct*k)
{structkthread*kthread;intret;
trace_sched_kthread_stop(k);get_task_struct(k);
kthread=to_kthread(k);barrier();/*itmighthaveexited*/if(k->vfork_done!
=NULL){kthread->should_stop=1;wake_up_process(k);wait_for_completion(&kthread->exited);}ret=k->exit_code;put_task_struct(k);trace_sched_kthread_stop_ret(ret);returnret;}Linux内核多线程
(二)
内核多线程是在项目中使用到,自己也不熟悉,遇到一个很囧的问题,导致cpu运行100%。
这是写的第一个内核线程程序,通过全局变量来实现两个内核线程之间的通信。
但是这里遇到致命错误,就是:
每当
wait_event_interruptible()被wake_up_interruptible
唤醒之后线程就进入死循环。
后面发现是线程不会主动的自己调度,需要显式的通过schedule或者
schedule_timeout()来调度。
如果不加tc=0
这一行,wait_event_intrruptible()就一直不会睡眠(参见前面的文章“等待队列”),不会被调度放弃CPU,因此进入死循环。
这个过程可以通过分析wait_event_intrruptible()的源代码来看出。
#include<linux/init.h>#include<linux/module.h>#include<linux/kthread.h>#include<linux/wait.h>
MODULE_LICENSE("DualBSD/GPL");
staticstructtask_struct*_tsk;staticstructtask_struct*_tsk1;staticinttc=0;staticwait_queue_head_tlog_wait_queue;staticintthread_function(void*data)
{do{ printk(KERN_INFO"INthread_functionthread_function:
%dtimes\n",tc);
wait_event_interruptible(log_wait_queue,tc==10);tc=0;///必须加这一行,内核才会进行调度。
内核线程不像应用程序会主动调度,我们需要显式的使用调度函数,
想要在thread_function_1中去重置tc的值是不可能的,因为线程不会被调度,该线程会一直占用CPU
printk(KERN_INFO"hasbeenwokeup!
\n");}while(!
kthread_should_stop());returntc;
}staticintthread_function_1(void*data)
{do{ printk(KERN_INFO"INthread_function_1thread_function:
%dtimes\n",++tc);if(tc==10&&waitqueue_active(&log_wait_queue)){wake_up_interruptible(&log_wait_queue);}msleep_interruptible(1000);
}while(!
kthread_should_stop());returntc;}
staticinthello_init(void){printk(KERN_INFO"Hello,world!
\n");init_waitqueue_head(&log_wait_queue);_tsk=kthread_run(thread_function,NULL,"mythread");if(IS_ERR(_tsk)){//需要使用IS_ERR()来判断线程是否有效,后面会有文章介绍IS_ERR()printk(KERN_INFO"firstcreatekthreadfailed!
\n");}else{printk(KERN_INFO"firstcreatektrheadok!
\n");}_tsk1=kthread_run(thread_function_1,NULL,"mythread2");if(IS_ERR(_tsk1)){printk(KERN_INFO"secondcreatekthreadfailed!
\n");}else{printk(KERN_INFO"secondcreatektrheadok!
\n");}return0;}
staticvoidhello_exit(void)
{printk(KERN_INFO"Hello,exit!
\n");if(!
IS_ERR(_tsk)){intret=kthread_stop(_tsk);printk(KERN_INFO"Firstthreadfunctionhasstopped,return%d\n",ret);}if(!
IS_ERR(_tsk1)){intret=kthread_stop(_tsk1);printk(KERN_INFO"Secondthreadfunction_1hasstopped,return%d\n",ret);}}
module_init(hello_init);module_exit(hello_exit);
说明:
这个程序的目的就是,使用一个线程(thread_function_1)通知另外一个线程(thread_function)某个条件(tc==10)满足(比如接收线程收到10帧然后通知处理线程处理接收到的数据)
运行结果:
程序加载并运行(tc的值等于10之后就会唤醒另外一个线程,之后tc又从10开始计数):
程序卸载(程序卸载其实还是要很注意的,很多程序在卸载的时候回出现各种问题后面文章会提到):
这里介绍另一种线程间通信的方式:
completion机制。
Completion机制是线程间通信的一种轻量级机制:
允许一个线程告诉另一个线程工作已经完成。
为使用completion,需要包含头文件<linux/completion.h>。
可以通过以下方式来创建一个completion:
DECLARE_COMPLETION(my_completion);
或者,动态创建和初始化:
structcompletionmy_completion;
init_completion(&my_completion);
等待completion是一个简单事来调用:
voidwait_for_completion(structcompletion*c);
注意:
这个函数进行一个不可打断的等待.如果你的代码调用wait_for_completion并且
没有人完成这个任务,结果会是一个不可杀死的进程。
completion事件可能通过调用下列之一来发出:
voidcomplete(structcompletion*c);
voidcomplete_all(structcompletion*c);
如果多于一个线程在等待同一个completion事件,这2个函数做法不同.complete只
唤醒一个等待的线程,而complete_all允许它们所有都继续。
下面来看使用completion机制的实现代码:
#include<linux/init.h>#include<linux/module.h>#include<linux/kthread.h>#include<linux/wait.h>#include<linux/completion.h>
MODULE_LICENSE("DualBSD/GPL");
staticstructcompletioncomp;staticstructtask_struct*_tsk;staticstructtask_struct*_tsk1;staticinttc=0;
staticintthread_function(void*data)
{do{ printk(KERN_INFO"INthread_functionthread_function:
%dtimes\n",tc);
wait_for_completion(&comp);//tc=0;///在哪里都行
printk(KERN_INFO"hasbeenwokeup!
\n");}while(!
kthread_should_stop());returntc;}staticintthread_function_1(void*data)
{do{ printk(KERN_INFO"INthread_function_1thread_function:
%dtimes\n",++tc);if(tc==10)
{complete(&comp);tc=0;}msleep_interruptible(1000);
}while(!
kthread_should_stop());returntc;}staticinthello_init(void)
{printk(KERN_INFO"Hello,world!
\n");init_completion(&comp);_tsk=kthread_run(thread_function,NULL,"mythread");if(IS_ERR(_tsk)){printk(KERN_INFO"firstcreatekthreadfailed!
\n");}else{printk(KERN_INFO"firstcreatektrheadok!
\n");}_tsk1=kthread_run(thread_function_1,NULL,"mythread2");if(IS_ERR(_tsk1)){printk(KERN_INFO"secondcreatekthreadfailed!
\n");}else{printk(KERN_INFO"secondcreatektrheadok!
\n");}return0;}staticvoidhello_exit(void)
{printk(KERN_INFO"Hello,exit!
\n");if(!
IS_ERR(_tsk)){intret=kthread_stop(_tsk);printk(KERN_INFO"Firstthreadfunctionhasstopped,return%d\n",ret);}if(!
IS_ERR(_tsk1)){intret=kthread_stop(_tsk1);printk(KERN_INFO"Secondthreadfunction_1hasstopped,return%d\n",ret);}}module_init(hello_init);module_exit(hello_exit);
运行结果:
自己创建的内核线程,当把模块加载到内核之后,可以通过:
ps–ef
命令来查看线程运行的情况。
通过该命令可以看到该线程的pid和ppid等。
也可以通过使用kill–s9pid
来杀死对应pid的线程。
如果要支持kill命令自己创建的线程里面需要能接受kill信号。
这里我们就来举一个例,支持kill命令,同时rmmod的
时候也能杀死线程。
#include<linux/kernel.h>#include<linux/module.h>#include<linux/init.h>#include<linux/param.h>#include<linux/jiffies.h>#include<asm/system.h>#include<asm/processor.h>#include<asm/signal.h>#include<linux/completion.h>//forDECLARE_COMPLETION()#include<linux/sched.h>#include<linux
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- linux 内核 多线程