0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

linux内核线程就这样诞生了么?

嵌入式小生 来源:嵌入式小生 2023-07-10 10:45 次阅读

一、开篇

线程是操作系统的重要组成部件之一,linux内核中,内核线程是如何创建的,在内核启动过程中,诞生了哪些支撑整个系统运转的线程,本文将带着这个疑问瞅一瞅内核源码,分析内核线程的创建机制。本文基于linux内核版本:4.1.15。

与linux内核1号init进程一样,在rest_init()函数中将调用kthread_init()函数创建kthreadd线程,如下代码:

pid=kernel_thread(kthreadd,NULL,CLONE_FS|CLONE_FILES);

下文将看看kthreadd()中完成了哪些事情。

二、kthreadd线程入口分析

kthreadd线程的线程入口为kthreadd(/kernel/kthread.c),如下定义:

intkthreadd(void*unused)
{
structtask_struct*tsk=current;

//该函数会清除当前运行的可执行文件的所有trace,以便启动一个新的trace
set_task_comm(tsk,"kthreadd");
//忽略tsk的信号
ignore_signals(tsk);
//该行代码允许kthreadd在任何CPU上运行
set_cpus_allowed_ptr(tsk,cpu_all_mask);
//设置由alloc_lock保护的内存空间
set_mems_allowed(node_states[N_MEMORY]);
//设置kthreadd线程不应该被冻结
current->flags|=PF_NOFREEZE;

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;
}

上述第3行代码:使用current获取线程控制块。current定义如下(/include/asm-generic/current.h):

#defineget_current()(current_thread_info()->task)
#definecurrentget_current()

上述代码中16~36行代码:for(;;)是kthreadd的核心功能。使用set_current_state(TASK_INTERRUPTIBLE);将当前线程设置为TASK_INTERRUPTIBLE状态,如果当前没有要创建的线程(这一步由kthread_create_list实现),则主动调用schedule()执行调度,让出CPU,这部分由17~19行代码实现。否则,kthreadd将处于唤醒状态,那么就会执行对应的线程创建操作,这部分功能由23~34行代码实现。

上述代码中,出现了kthread_create_list这个待创建线程的链表,定义如下:

staticLIST_HEAD(kthread_create_list);

第26~27行代码,使用:

create=list_entry(kthread_create_list.next,
structkthread_create_info,list);

从链表中取得 kthread_create_info 结构的地址。

第31行代码使用create_kthread()创建create代表的内核线程。定义如下(/kernel/kernel.c):

staticvoidcreate_kthread(structkthread_create_info*create)
{
intpid;

#ifdefCONFIG_NUMA
current->pref_node_fork=create->node;
#endif
/*Wewantourownsignalhandler(wetakenosignalsbydefault).*/
pid=kernel_thread(kthread,create,CLONE_FS|CLONE_FILES|SIGCHLD);
if(pid< 0) {
  /* If user was SIGKILLed, I release the structure. */
  struct completion *done = xchg(&create->done,NULL);

if(!done){
kfree(create);
return;
}
create->result=ERR_PTR(pid);
complete(done);
}
}

从上述代码可知,在create_kthread()中创建线程同样由kernel_thread()函数完成:

pid=kernel_thread(kthread,create,CLONE_FS|CLONE_FILES|SIGCHLD);

可见新创建的线程的入口是kthread,下文将继续分析该线程函数。

三、kthread分析

该函数定义在(/kernel/kthead.c)中:

staticintkthread(void*_create)
{
//拷贝数据
//将_create代表的kthread_create_info赋值给create
structkthread_create_info*create=_create;
//设置线程执行的函数指针
int(*threadfn)(void*data)=create->threadfn;
void*data=create->data;
structcompletion*done;
structkthreadself;
intret;

self.flags=0;
self.data=data;
init_completion(&self.exited);
init_completion(&self.parked);
current->vfork_done=&self.exited;

/*IfuserwasSIGKILLed,Ireleasethestructure.*/
done=xchg(&create->done,NULL);
if(!done){
kfree(create);
do_exit(-EINTR);
}
/*OK,telluserwe'respawned,waitforstoporwakeup*/
/*创建的新的内核线程被置为TASK_UNINTERRUPTIBLE,需要显式的被唤醒才能运行*/
__set_current_state(TASK_UNINTERRUPTIBLE);
create->result=current;
complete(done);

//运行到此处,线程已经创建完毕,调用schedule执行调度,主动让出CPU,唤醒的是调用kthread_create函数的进程。
schedule();

//当本线程(创建的线程)被唤醒后,将继续执行后续代码
ret=-EINTR;

if(!test_bit(KTHREAD_SHOULD_STOP,&self.flags)){
__kthread_parkme(&self);
ret=threadfn(data);
}
/*wecan'tjustreturn,wemustpreserve"self"onstack*/
do_exit(ret);
}

上述函数中,创建新 thread 的进程恢复运行 kthread_create() 并且返回新创建线程的任务描述符,在创建线程的线程中由于执行了 schedule() 调度,此时并没有执行。需使用wake_up_process(p);唤醒新创建的线程,这时候新创建的线程才得以执行。当线程被唤醒后, 会接着执行threadfn(data)(即:对应线程的真正线程函数)(这一点后文会通过实践加以验证!):

if(!test_bit(KTHREAD_SHOULD_STOP,&self.flags)){
__kthread_parkme(&self);
//执行创建线程对应的线程函数,传入的参数为data
ret=threadfn(data);
}

总结一下,在kthreadd线程函数中,将完成两件重要的事情:

1、如果kthread_create_list线程创建链表为空,则调用schedule()执行线程调度。

2、如果kthread_create_list线程创建链表不为空(即需要创建线程),则调用create_kthread()创建内核线程。

(1)动手玩一玩

基于上述分析,本小节对其加以实践。

31fdf228-1eca-11ee-962d-dac502259ad0.pngimage-20230709113909381

重新编译构建内核后启动运行,在启动阶段,会打印出下述信息,这属于正常的预期现象,因为内核在启动阶段会涉及到内核重要线程的创建和运行。例如:

321e1e54-1eca-11ee-962d-dac502259ad0.png

接下来以模块方式设计两份代码:

(1)module_1.c:使用kthread_create()创建一个名为my_thread的线程,在线程执行函数中每隔1000ms打印出一行信息:my_thread running。并暴露出对my_thread线程的唤醒接口

#include
#include
#include
#include
#include
#include

staticstructtask_struct*my_thread;

voidwakeup_mythread(void)
{
//唤醒内核线程
wake_up_process(my_thread);
}
EXPORT_SYMBOL(wakeup_mythread);

intmy_thread_function(void*data){
while(!kthread_should_stop()){
printk("my_threadrunning
");

msleep(1000);
}
return0;
}

staticint__initmodule_1_init(void){
//创建内核线程
my_thread=kthread_create(my_thread_function,NULL,"my_thread");
if(IS_ERR(my_thread)){
printk(KERN_ERR"Failedtocreatekernelthread
");
returnPTR_ERR(my_thread);
}

return0;
}

staticvoid__exitmodule_1_exit(void){
//停止内核线程
kthread_stop(my_thread);
}

module_init(module_1_init);
module_exit(module_1_exit);

MODULE_AUTHOR("iriczhao");
MODULE_LICENSE("GPL");

(2)module_2.c:调用module_1的wakeup_mythread()唤醒my_thread:

#include
#include
#include
#include

externvoidwakeup_mythread(void);

staticintmodule2_init(void)
{
printk("wakeupmythread
");

wakeup_mythread();

return0;
}

staticvoidmodule2_exit(void)
{
printk("module2_exitexiting
");
}

module_init(module2_init);
module_exit(module2_exit);

//定义模块相关信息
MODULE_AUTHOR("iriczhao");
MODULE_LICENSE("GPL");

将上述两份代码以模块方式构建。

3267d4f4-1eca-11ee-962d-dac502259ad0.png

从上图中可见:在加载module_1的时候,打印出了:

>>>>>>>>>>>>>>>>>>>>>>>>>>kthread>>>>>>>>>>>>>>>>>>>>>>>>

则证明执行kthread()函数创建了my_thread线程,但是该线程并没有唤醒执行,而由于在kthread()函数中调用schedule()让出了cpu,故而后面的代码没有执行。

当在加载module_2后,则唤醒了my_thread线程,打印出了:

>>>>>>>>>>>>>>>>>>>>>>>>>>I'mrunning>>>>>>>>>>>>>>>>>>>>>>>>

然后执行my_thread的线程函数,每隔一秒打印出:

my_threadrunning

综上,也验证了,当在kthread()中调用schedule()时执行线程调度,让出了cpu,当唤醒创建的线程时,会继续执行schedule()后面的代码。

四、总结与补充

(1)kthread_create()函数

通过以上代码分析,可见最重要的是kthread_create_list这个全局链表。当使用kthread_create()函数创建线程时,最终都会将线程相关资源添加到kthread_create_list链表中。如下代码(/include/linux/kthread.h):

#definekthread_create(threadfn,data,namefmt,arg...)
kthread_create_on_node(threadfn,data,-1,namefmt,##arg)

由create_kthread()可知,通过kthread_create()入口进来的内核线程创建路径都具有统一的线程函数kthread()。

而linux内核的2号线程kthreadd正好负责内核线程的调度和管理。所以说创建的内核线程都是直接或间接的以kthreadd为父进程。

(2)kthread_create与kernel_thread的区别

在(init/mian.c)中,1号init线程和2号kthreadd线程都是通过kernel_thread()函数创建的,那么kernel_thread()后续会调用do_fork()实现线程相关的创建操作。kernel_thread()函数与kthread_create()创建的内核线程有什么区别呢?

1、kthread_create()创建的内核线程有干净的上下文环境,适合于驱动模块或用户空间的程序创建内核线程使用,不会把某些内核信息暴露给用户空间程序。

2、二者创建的进程的父进程不同: kthread_create()创建的进程的父进程被指定为kthreadd, 而kernel_thread()创建的进程的父进程可以是init或其他内核线程。

(3)kthread_run()函数

kthread_run()函数用于创建并唤醒一个线程,其本质上是调用kthread_create()创建一个线程,并使用wake_up_process()唤醒该线程。定义如下:

#definekthread_run(threadfn,data,namefmt,...)
({
structtask_struct*__k
=kthread_create(threadfn,data,namefmt,##__VA_ARGS__);
if(!IS_ERR(__k))
wake_up_process(__k);
__k;
})

(4)linux内核创建线程的整体过程

综上,linux内核创建线程的整体过程为:

1、创建kthread_create_info结构,为其分配空间,指定线程函数,线程相关描述数据等。

2、将线程的kthread_create_info结构添加到kthread_create_list全局线程创建链表中,并唤醒2号kthreadd线程。

3、2号kthreadd线程将从kthread_create_list全局线程创建链表中取出每一个kthread_create_info结构,然后调用create_kthread()函数创建一个线程函数为kthread的线程。在kthread线程函数中将执行创建线程指定的线程函数。

五、附录

【附录一】

kthread_create_on_cpu()创建一个绑定CPU的线程:

/**
*kthread_create_on_cpu-Createacpuboundkthread
*@threadfn:thefunctiontorununtilsignal_pending(current).
*@data:dataptrfor@threadfn.
*@cpu:Thecpuonwhichthethreadshouldbebound,
*@namefmt:printf-stylenameforthethread.Formatisrestricted
*to"name.*%u".Codefillsincpunumber.
*
*Description:Thishelperfunctioncreatesandnamesakernelthread
*Thethreadwillbewokenandputintoparkmode.
*/
structtask_struct*kthread_create_on_cpu(int(*threadfn)(void*data),
void*data,unsignedintcpu,
constchar*namefmt)
{
structtask_struct*p;

p=kthread_create_on_node(threadfn,data,cpu_to_node(cpu),namefmt,
cpu);
if(IS_ERR(p))
returnp;
set_bit(KTHREAD_IS_PER_CPU,&to_kthread(p)->flags);
to_kthread(p)->cpu=cpu;
/*ParkthethreadtogetitoutofTASK_UNINTERRUPTIBLEstate*/
kthread_park(p);
returnp;
}

【附录二】

kthread_create_on_node()函数将操作kthread_create_list链表。kthread_create_on_node()函数的功能是:创建kthread,并将其添加到 kthread_create_list线程创建链表中,并返回对应的task_struct。

structtask_struct*kthread_create_on_node(int(*threadfn)(void*data),
void*data,intnode,
constcharnamefmt[],
...)
{
DECLARE_COMPLETION_ONSTACK(done);
structtask_struct*task;
structkthread_create_info*create=kmalloc(sizeof(*create),
GFP_KERNEL);

if(!create)
returnERR_PTR(-ENOMEM);
create->threadfn=threadfn;
create->data=data;
create->node=node;
create->done=&done;

spin_lock(&kthread_create_lock);
/*注意这个全局链表kthread_create_list,所有通过kthread_create创建的内核线程都会挂在这*/
list_add_tail(&create->list,&kthread_create_list);
spin_unlock(&kthread_create_lock);
/*这是最重要的地方,从代码看是唤醒了kthreadd_task这个进程,该进程就是内核中的1号进程kthreadd 。因为kthreadd_task在rest_init()中通过find_task_by_pid_ns(pid, &init_pid_ns);进行了linux内核的早期赋值*/
wake_up_process(kthreadd_task);
/*
*Waitforcompletioninkillablestate,forImightbechosenby
*theOOMkillerwhilekthreaddistryingtoallocatememoryfor
*newkernelthread.
*/
if(unlikely(wait_for_completion_killable(&done))){
/*
*IfIwasSIGKILLedbeforekthreadd(ornewkernelthread)
*callscomplete(),leavethecleanupofthisstructureto
*thatthread.
*/
if(xchg(&create->done,NULL))
returnERR_PTR(-EINTR);
/*
*kthreadd(ornewkernelthread)willcallcomplete()
*shortly.
*/
wait_for_completion(&done);
}
task=create->result;
if(!IS_ERR(task)){
staticconststructsched_paramparam={.sched_priority=0};
va_listargs;

va_start(args,namefmt);
vsnprintf(task->comm,sizeof(task->comm),namefmt,args);
va_end(args);
/*
*rootmayhavechangedour(kthreadd's)priorityorCPUmask.
*Thekernelthreadshouldnotinherittheseproperties.
*/
sched_setscheduler_nocheck(task,SCHED_NORMAL,¶m);
set_cpus_allowed_ptr(task,cpu_all_mask);
}
kfree(create);
returntask;
}

kthread_create_on_node()函数的本质则是创建kthread_create_info结构,并将其添加到kthread_create_list全局链表中(/kernel/kthread.h):

structkthread_create_info
{
/*从kthreadd传递给kthread()的信息*/
int(*threadfn)(void*data);
void*data;
intnode;

/*从kthreadd返回给kthread_create()的结果*/
structtask_struct*result;
structcompletion*done;

structlist_headlist;
};





审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • Module
    +关注

    关注

    0

    文章

    58

    浏览量

    12810
  • LINUX内核
    +关注

    关注

    1

    文章

    311

    浏览量

    21389
  • 调度器
    +关注

    关注

    0

    文章

    95

    浏览量

    5161

原文标题:毛毛雨,linux内核线程就这样诞生了么?

文章出处:【微信号:嵌入式小生,微信公众号:嵌入式小生】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    Linux线程编程

    Linux线程编程
    的头像 发表于 08-24 15:42 1716次阅读

    用户级线程内核线程

    线程的实现可以分为两类:用户级线程内核线程,后者又称为内核支持的线程或轻量级进程。在多
    发表于 01-10 15:01

    linux 下如何获取线程ID

    linux线程环境下gettid() pthread_self() 两个函数都获得线程ID,但这2个ID有所不同gettid是内核中的线程
    发表于 07-09 08:36

    请问大佬rt-thread smart用户程序可以创建实时线程?

    RT,rt-smart用户程序可以创建实时线程?现有例子看,感觉smart得内核是实时得,用户层程序是非实时得呢?如果用户可以创建实时线程,一个高优先级得
    发表于 04-15 09:54

    一加5不出意外就这样了,双摄曲面稳稳的

    一加手机5有望采用2K分辨率屏幕,搭配骁龙835处理器,同时配备后置双摄像头。 一加5不出意外就这样了,双摄稳稳的
    发表于 06-14 16:11 480次阅读

    “官宣”——当婚姻遇到了区块链,爱情链就这样诞生了

    “官宣”——当婚姻遇到了区块链,爱情链就这样诞生了赵丽颖与冯绍峰晒婚照、公布喜讯的新闻霸屏不少网站头版头条,粉丝们惊呼、娱乐媒体们更是乐于将婚讯放大。但不少吃瓜群众在为这对新人送上祝福的同时
    发表于 10-17 10:42 129次阅读

    Linux内核线程优先级设置的方法介绍

    内核线程和进程是一样的,前者与POSIX线程(pthread)有很大的区别。因此,内核的进程调度策略和系统调用也适用于内核
    发表于 04-23 14:58 5381次阅读
    <b class='flag-5'>Linux</b><b class='flag-5'>内核</b><b class='flag-5'>线程</b>优先级设置的方法介绍

    嵌入式linux内核开发培训之linux特性

    通常所说的Linux是指Linus Torvald所写的Linux操作系统内核。从诞生开始,Linux就遵循着开源的原则,免费供人们学习和使
    发表于 05-12 10:18 611次阅读

    首款3.0新内核Linux发行版迅速诞生

    内核。Finnix 102操作系统开发代号Algoma,所采用的3.0版内核是由Finnix开发人员制作的,其中部分来源于Debian预发布内核。事实上,就在Linux Kernel
    发表于 04-02 14:32 275次阅读

    linux线程浅析

    上.不过, M:N的线程模型毕竟提供了这样一种手段, 可以让不需要并行执行的线程运行在一个内核线程对应的若干个用户级
    发表于 04-02 14:45 204次阅读

    为什么说内核线程放入SCHED_FIFO的做法毫无意义?

    内核线程的优先级Linux内核会将大量(并且在不断增加中)工作放置在内核线程中,这些
    的头像 发表于 06-09 15:21 4599次阅读

    深入浅析Linux内核内核线程(上)

    本文力求与完整介绍完内核线程的整个生命周期,如内核线程的创建、调度等等,当然本文还是主要从内存管理和进程调度两个维度来解析,且不会涉及到具体的内核
    的头像 发表于 04-28 16:26 1736次阅读
    深入浅析<b class='flag-5'>Linux</b><b class='flag-5'>内核</b>之<b class='flag-5'>内核</b><b class='flag-5'>线程</b>(上)

    管束干燥机轴承位磨损了就这样

    管束干燥机轴承位磨损了就这样
    发表于 03-07 16:38 2次下载

    Linux线程线程与异步编程、协程与异步介绍

    线程之间的切换不需要陷入内核,但部分操作系统中用户态线程的切换需要内核线程的辅助。 协程是编程语言(或者 lib)提供的特性(协程之间的
    的头像 发表于 11-11 11:35 425次阅读
    <b class='flag-5'>Linux</b><b class='flag-5'>线程</b>、<b class='flag-5'>线程</b>与异步编程、协程与异步介绍

    linux线程编程实例

    linux线程
    的头像 发表于 02-15 21:16 73次阅读
    <b class='flag-5'>linux</b>多<b class='flag-5'>线程</b>编程实例