天天看點

linux kthread

前言

Linux下有3個特殊的程序,idle程序(PID = 0), init程序(PID = 1)和kthreadd(PID = 2):

a.  PID=0  系統自動建立、運作在核心态;

b.  PID=1  由0程序建立,完成系統的初始化. 是系統中所有其它使用者程序的祖先程序Linux中的所有程序都是有init程序建立并運作的。首先Linux核心啟動,然後在使用者空間中啟動init程序,再啟動其他系統程序。在系統啟動完成完成後,init将變為守護程序監視系統其他程序;

c. PID=2 它的任務就是管理和排程其他核心線程kernel_thread, 會循環執行一個kthreadd的函數,該函數的作用就是運作kthread_create_list全局連結清單中維護的kthread, 當我們調用kernel_thread建立的核心線程會被加入到此連結清單中,是以所有的核心線程都是直接或者間接的以kthreadd為父程序。

1. kthreadd線程

路徑:\linux-3.10.x\init\main.c  start_kernel()-->rest_init();

static noinline void __init_refok rest_init(void)
{
  int pid;

  rcu_scheduler_starting();
  /*
   * We need to spawn init first so that it obtains pid 1, however
   * the init task will end up wanting to create kthreads, which, if
   * we schedule it before we create kthreadd, will OOPS.
   */
  kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
  numa_default_policy();
  pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
  rcu_read_lock();
  kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
  rcu_read_unlock();
  complete(&kthreadd_done); //喚醒完成量

  /*
   * The boot idle thread must execute schedule()
   * at least once to get things moving:
   */
  init_idle_bootup_task(current);
  schedule_preempt_disabled();
  /* Call into cpu_idle with preempt disabled */
  cpu_startup_entry(CPUHP_ONLINE);
}      

pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES); kthread線程建立,源碼如下:

int kthreadd(void *unused)
{
  struct task_struct *tsk = current;

  /* Setup a clean context for our children to inherit. */
  //為我們的子程序設定一個幹淨的上下文
  set_task_comm(tsk, "kthreadd");
  ignore_signals(tsk);
  set_cpus_allowed_ptr(tsk, cpu_all_mask); //運作kthread在任意cpu上運作
  set_mems_allowed(node_states[N_MEMORY]);

  current->flags |= PF_NOFREEZE;

  for (;;) {
    set_current_state(TASK_INTERRUPTIBLE); //設定目前程序為中斷模式,即可被cpu打斷
    if (list_empty(&kthread_create_list)) //kthread_create_list 連結清單是否為空,為空就調用schedule()讓出cpu進入休眠
      schedule();
    __set_current_state(TASK_RUNNING); //到這裡表示此程序已從休眠狀态恢複,即kthread_create_list連結清單不為空,設定目前程序為運作态

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

這裡要看下是在哪裡對kthread_create_list連結清單進行添加的, 在連結清單裡主要是線程執行函數、線程參數進行綁定:

struct task_struct *kthread_create_on_node(int (*threadfn)(void *data),
             void *data, int node,
             const char namefmt[],
             ...)
{
  struct kthread_create_info create;

  create.threadfn = threadfn; //線程執行函數
  create.data = data; //線程執行函數參數
  create.node = node;
  init_completion(&create.done); //初始化線程完成量

  spin_lock(&kthread_create_lock);
  list_add_tail(&create.list, &kthread_create_list); //加入到kthread_create_list連結清單中
  spin_unlock(&kthread_create_lock);

  wake_up_process(kthreadd_task); //喚醒kthreadd_task核心任務線程
  wait_for_completion(&create.done); //等待threadfn線程完成//至此線程建立完畢 , 會喚醒kthread_create_on_node()函數内的完成量wait_for_completion(&create.done);

  if (!IS_ERR(create.result)) {
    static const struct sched_param param = { .sched_priority = 0 };
    va_list args;

    va_start(args, namefmt);
    vsnprintf(create.result->comm, sizeof(create.result->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(create.result, SCHED_NORMAL, ¶m);
    set_cpus_allowed_ptr(create.result, cpu_all_mask);
  }
  return create.result;
}
EXPORT_SYMBOL(kthread_create_on_node);      

通過create_kthread(create),完成真正的程序建立:

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) {
    create->result = ERR_PTR(pid);
    complete(&create->done);
  }
}      

調用kernel_thread(kthread, create, CLONE_FS | CLONE_FILES | SIGCHLD)完成create線程的建立:

static int kthread(void *_create)
{
  /* Copy data: it's on kthread's stack */
  struct kthread_create_info *create = _create;
  int (*threadfn)(void *data) = create->threadfn; //新的程序建立完後執行的函數
  void *data = create->data; //建立程序的參數
  struct kthread self;
  int ret;

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

  /* OK, tell user we're spawned, wait for stop or wakeup */
  __set_current_state(TASK_UNINTERRUPTIBLE);
  create->result = current; //current 表示目前新建立的 thread 的 task_struct 結構
  complete(&create->done); //至此線程建立完畢 , 會喚醒kthread_create_on_node()函數内的等待完成量wait_for_completion(&create.done);

  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);
}//至此線程建立完畢 , 會喚醒kthread_create_on_node()函數内的等待完成量wait_for_completion(&create.done);

  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)

總結:

     kthreadd程序由idle通過kernel_thread建立,并始終運作在核心空間, 負責所有核心線程的排程和管理,它的任務就是管理和排程其他核心線程kernel_thread, 會循環執行一個kthreadd的函數,該函數的作用就是運作kthread_create_list全局連結清單中維護的kthread, 當我們調用kernel_thread建立的核心線程會被加入到此連結清單中,是以所有的核心線程都是直接或者間接的以kthreadd為父程序我們在核心中通過kernel_create或者其他方式建立一個核心線程, 然後kthreadd核心線程被喚醒, 來執行核心線程建立的真正工作,新的線程将執行kthread函數, 完成建立工作,建立完畢後讓出CPU,是以新的核心線程不會立刻運作.需要手工 wake up, 被喚醒後将執行自己的真正工作函數。

任何一個核心線程入口都是 kthread()

通過 kthread_create() 建立的核心線程不會立刻運作.需要手工 wake up.

通過 kthread_create() 建立的核心線程有可能不會執行相應線程函數threadfn而直接退出

繼續閱讀