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

描述

 

一、开篇

线程是操作系统的重要组成部件之一,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),如下定义:

 

int kthreadd(void *unused)
{
 struct task_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)) {
   struct kthread_create_info *create;

   create = list_entry(kthread_create_list.next,
         struct kthread_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);
 }

 return 0;
}

 

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

 

#define get_current() (current_thread_info()->task)
#define current get_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这个待创建线程的链表,定义如下:

 

static LIST_HEAD(kthread_create_list);

 

第26~27行代码,使用:

 

 create = list_entry(kthread_create_list.next,
         struct kthread_create_info, list);

 

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

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

 

static void create_kthread(struct kthread_create_info *create)
{
 int pid;

#ifdef CONFIG_NUMA
 current->pref_node_fork = create->node;
#endif
 /* We want our own signal handler (we take no signals by default). */
 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)中:

 

static int kthread(void *_create)
{
    //拷贝数据
    //将_create代表的kthread_create_info赋值给create
 struct kthread_create_info *create = _create;
    //设置线程执行的函数指针
 int (*threadfn)(void *data) = create->threadfn;
 void *data = create->data;
 struct completion *done;
 struct kthread self;
 int ret;

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

 /* If user was SIGKILLed, I release the structure. */
 done = xchg(&create->done, NULL);
 if (!done) {
  kfree(create);
  do_exit(-EINTR);
 }
 /* OK, tell user we're spawned, wait for stop or wakeup */
    /* 创建的新的内核线程被置为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);
 }
 /* we can't just return, we must preserve "self" on stack */
 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)动手玩一玩

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

Moduleimage-20230709113909381

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

Module

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

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

 

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

static struct task_struct *my_thread;

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

int  my_thread_function(void *data) {
    while (!kthread_should_stop()) {
      printk("my_thread running
");

      msleep(1000);
    }
    return 0;
}

static int __init module_1_init(void) {
    // 创建内核线程
    my_thread = kthread_create(my_thread_function, NULL, "my_thread");
    if (IS_ERR(my_thread)) {
        printk(KERN_ERR "Failed to create kernel thread
");
        return PTR_ERR(my_thread);
    }

    return 0;
}

static void __exit module_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 

extern void wakeup_mythread(void);

static int module2_init(void)
{
    printk("wake up mythread
");

    wakeup_mythread();

    return 0;
}

static void module2_exit(void)
{
    printk("module2_exit exiting
");
}

module_init(module2_init);
module_exit(module2_exit);

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

 

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

Module

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

 

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

 

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

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

 

>>>>>>>>>>>>>>>>>>>>>>>>>>I'm running>>>>>>>>>>>>>>>>>>>>>>>>

 

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

 

my_thread running

 

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

四、总结与补充

(1)kthread_create()函数

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

 

#define kthread_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()唤醒该线程。定义如下:

 

#define kthread_run(threadfn, data, namefmt, ...)      
({            
 struct task_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 - Create a cpu bound kthread
 * @threadfn: the function to run until signal_pending(current).
 * @data: data ptr for @threadfn.
 * @cpu: The cpu on which the thread should be bound,
 * @namefmt: printf-style name for the thread. Format is restricted
 *      to "name.*%u". Code fills in cpu number.
 *
 * Description: This helper function creates and names a kernel thread
 * The thread will be woken and put into park mode.
 */
struct task_struct *kthread_create_on_cpu(int (*threadfn)(void *data),
       void *data, unsigned int cpu,
       const char *namefmt)
{
 struct task_struct *p;

 p = kthread_create_on_node(threadfn, data, cpu_to_node(cpu), namefmt,
       cpu);
 if (IS_ERR(p))
  return p;
 set_bit(KTHREAD_IS_PER_CPU, &to_kthread(p)->flags);
 to_kthread(p)->cpu = cpu;
 /* Park the thread to get it out of TASK_UNINTERRUPTIBLE state */
 kthread_park(p);
 return p;
}

 

【附录二】

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

 

struct task_struct *kthread_create_on_node(int (*threadfn)(void *data),
        void *data, int node,
        const char namefmt[],
        ...)
{
 DECLARE_COMPLETION_ONSTACK(done);
 struct task_struct *task;
 struct kthread_create_info *create = kmalloc(sizeof(*create),
           GFP_KERNEL);

 if (!create)
  return ERR_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);
 /*
  * Wait for completion in killable state, for I might be chosen by
  * the OOM killer while kthreadd is trying to allocate memory for
  * new kernel thread.
  */
 if (unlikely(wait_for_completion_killable(&done))) {
  /*
   * If I was SIGKILLed before kthreadd (or new kernel thread)
   * calls complete(), leave the cleanup of this structure to
   * that thread.
   */
  if (xchg(&create->done, NULL))
   return ERR_PTR(-EINTR);
  /*
   * kthreadd (or new kernel thread) will call complete()
   * shortly.
   */
  wait_for_completion(&done);
 }
 task = create->result;
 if (!IS_ERR(task)) {
  static const struct sched_param param = { .sched_priority = 0 };
  va_list args;

  va_start(args, namefmt);
  vsnprintf(task->comm, sizeof(task->comm), namefmt, args);
  va_end(args);
  /*
   * root may have changed our (kthreadd's) priority or CPU mask.
   * The kernel thread should not inherit these properties.
   */
  sched_setscheduler_nocheck(task, SCHED_NORMAL, ¶m);
  set_cpus_allowed_ptr(task, cpu_all_mask);
 }
 kfree(create);
 return task;
}

 

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

 

struct kthread_create_info
{
        /* 从kthreadd传递给kthread()的信息 */
        int (*threadfn)(void *data);
        void *data;
        int node;

        /* 从kthreadd返回给kthread_create()的结果 */
        struct task_struct *result;
        struct completion *done;

        struct list_head list;
};

 





审核编辑:刘清

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

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分