Linux內核線程kthread

轉載於: https://blog.csdn.net/quincyfang/article/details/50426491

      Linux內核可以看作一個服務進程(管理軟硬件資源,響應用戶進程的種種合理以及不合理的請求)。內核需要多個執行流並行,爲了防止可能的阻塞,支持多線程是必要的。內核線程就是內核的分身,一個分身可以處理一件特定事情。內核線程的調度由內核負責,一個內核線程處於阻塞狀態時不影響其他的內核線程,因爲其是調度的基本單位。這與用戶線程是不一樣的。因爲內核線程只運行在內核態。因此,它只能使用大於PAGE_OFFSET(3G)的地址空間。內核線程和普通的進程間的區別在於內核線程沒有獨立的地址空間,mm指針被設置爲NULL;它只在 內核空間運行,從來不切換到用戶空間去;並且和普通進程一樣,可以被調度,也可以被搶佔。

       內核線程(thread)或叫守護進程(daemon),在操作系統中佔據相當大的比例,當Linux操作系統啓動以後,你可以用”ps -ef”命令查看系統中的進程,這時會發現很多以”d”結尾的進程名,確切說名稱顯示裏面加 "[]"的,這些進程就是內核線程。

       內核線程和普通的進程間的區別在於內核線程沒有獨立的地址空間,它只在 內核空間運行,不切換到用戶空間去;並且和普通進程一樣,可以被調度,也可以被搶佔。讓模塊在加載後能一直運行下去的方法——內核線程。要創建一個內核線程有許多種方法,我們這裏要學的是最簡單的一種。打開include/linux/kthread.h,你就看到了它全部的API,一共三個函數:

struct task_struct kthread_run(int (*threadfn)(void *data),

void *data, const char namefmt[],...);

int kthread_stop(struct task_struct *k);

int kthread_should_stop(void);

一、線程的創建

/**
* kthread_run - create and wake a thread.
* @threadfn: the function to run until signal_pending(current).
* @data: data ptr for @threadfn.
* @namefmt: printf-style name for the thread.
*
* Description: Convenient wrapper for kthread_create() followed by
* wake_up_process(). Returns the kthread or ERR_PTR(-ENOMEM).
*/

#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; \

})

​​​​      這個函數的英文註釋裏很明確的說明: 創建並啓動一個內核線程。可見這裏的函數kthread_create()只是創建了內核線程,而後面的這個函數wake_up_process()則是啓動了這個線程,讓它在一開始就一直運行下去。直到遇見kthread_should_stop函數或者kthread_stop()函數。

     kthread_run實際是一個宏定義,它由kthread_create()和wake_up_process()兩部分組成,調用了kthread_create後執行了wake_up_process.這樣的好處是用kthread_run()創建的線程可以直接運行,使用方便。

     kthread_run()負責內核線程的創建,參數包括入口函數threadfn,參數data,線程名稱namefmt。可以看到線程的名字可以是類似sprintf方式組成的字符串。如果線程創建成功,再調用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參數中的入口函數。

struct kthread {

    int should_stop;

    struct completion exited;

};

kthread() (注:原型爲:static int kthread(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()中。

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

    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)) {

                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()

這個函數是幹什麼的呢?在Kernel/Kthread.c裏面我們可以看到:

/**

* kthread_create - create a kthread.

* @threadfn: the function to run until signal_pending(current).

* @data: data ptr for @threadfn.

* @namefmt: printf-style name for the thread.


* Description: This helper function creates and names a kernel

* thread. The thread will be stopped: use wake_up_process() to start

* it. See also kthread_run(), kthread_create_on_cpu().


* When woken, the thread will run @threadfn() with @data as its

* argument. @threadfn can either call do_exit() directly if it is a

* standalone thread for which noone will call kthread_stop(), or

* return when 'kthread_should_stop()' is true (which means

* kthread_stop() has been called). The return value should be zero

* or a negative error number; it will be passed to kthread_stop().


* Returns a task_struct or ERR_PTR(-ENOMEM).

*/

struct task_struct *kthread_create(int (*threadfn)(void *data),

void *data,

const char namefmt[],

...)

{

    struct kthread_create_info create;

    DECLARE_WORK(work, keventd_create_kthread, &create);

    create.threadfn = threadfn;

    create.data = data;

    init_completion(&create.started);

    init_completion(&create.done);


/*

* The workqueue needs to start up first:

*/

    if (!helper_wq)

        work.func(work.data);

    else {

        queue_work(helper_wq, &work);

        wait_for_completion(&create.done);

    }

    if (!IS_ERR(create.result)) {

        va_list args;

        va_start(args, namefmt);

        vsnprintf(create.result->comm, sizeof(create.result->comm),

        namefmt, args);

        va_end(args);

    }

    return create.result;

}

EXPORT_SYMBOL(kthread_create);

      注意到上面的這段英文解釋:說這個函數會創建一個名爲namefmt的內核線程,這個線程剛創建時不會馬上執行,要等到它將kthread_create() 返回的task_struct指針傳給wake_up_process(),然後通過此函數運行線程。我們看到creat結構體,我們將傳入的參數付給了它,而threadfn這個函數就是創建的運行函數。在使用中我們可以在此函數中調用kthread_should_stop()或者kthread_stop()函數來結束線程。這裏我們看到創建線程函數中使用工作隊列DECLARE_WORK,我們跟蹤一下發現這只是將函數

#define DECLARE_WORK(n, f, d) \

struct work_struct n = __WORK_INITIALIZER(n, f, d)

然後再跟進:

#define __WORK_INITIALIZER(n, f, d) { \

    .entry = { &(n).entry, &(n).entry }, \

    .func = (f), \

    .data = (d), \

    .timer = TIMER_INITIALIZER(NULL, 0, 0), \

}

目的是創建一個工作組隊列,而其中keventd_create_kthread()函數主要是起到創建線程的功能

/* We are keventd: create a thread. */

static void keventd_create_kthread(void *_create)

{

    struct kthread_create_info *create = _create;

    int pid;

/* 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);

    } else {

        wait_for_completion(&create->started);

        read_lock(&tasklist_lock);

        create->result = find_task_by_pid(pid);

        read_unlock(&tasklist_lock);

    }

    complete(&create->done);

}

再看看kernel_thread()函數最後調用到了哪裏:

/*

* Create a kernel thread.

*/

pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)

{
    struct pt_regs regs;

    long pid;

    memset(&regs, 0, sizeof(regs));

    regs.ARM_r1 = (unsigned long)arg;

    regs.ARM_r2 = (unsigned long)fn;

    regs.ARM_r3 = (unsigned long)do_exit;

    regs.ARM_pc = (unsigned long)kernel_thread_helper;

    regs.ARM_cpsr = SVC_MODE;

    pid = do_fork(flags|CLONE_VM|CLONE_UNTRACED, 0, &regs, 0, NULL, NULL);

    MARK(kernel_thread_create, "%ld %p", pid, fn);

    return pid;

}

EXPORT_SYMBOL(kernel_thread);

       最後我們看到了線程通過申請進程的pid號來被創建,關鍵是我們要知道如何使用這個宏函數,也就是如何應用它。要注意的是它調用了創建線程函數,同時也激活了線程。所以代碼中調用了它的話就隱含着已經啓動一個線程。

      在非內核線程中調用kernel_thread,必須在調用daemonize(...)來釋放資源,成爲真正的內核線程,kthread_create實際調用kernel_thread但是內部已經做了處理,不需要自己調用daemonize。

二、線程的退出

/**

 * kthread_stop - stop a thread created by kthread_create().
 * @k: thread created by kthread_create().
 *
 * Sets kthread_should_stop() for @k to return true, wakes it, and
 * waits for it to exit. This can also be called after kthread_create()
 * instead of calling wake_up_process(): the thread will exit without
 * calling threadfn().
 *
 * If threadfn() may call do_exit() itself, the caller must ensure

 * task_struct can't go away.
 *
 * Returns the result of threadfn(), or %-EINTR if wake_up_process()
 * was never called.
 */


int kthread_stop(struct task_struct *k)

{

       struct kthread *kthread;

       int ret;

       trace_sched_kthread_stop(k);

       get_task_struct(k);

       kthread = to_kthread(k);

       barrier(); /* it might have exited */

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

       return ret;

}

    kthread_stop:設置線程的退出標記(線程函數內應用int kthread_should_stop(void)函數,當返回真時應退出函數),kthread_stop會一直等待至線程結束,線程結束前會發送完成結束給kthread_stop,如果直接使用do_exit直接退出線程那麼kthread_stop不會收到完成信號將一直等待下去。如果線程已經退出那麼kthread_stop會先設置退出標記再喚醒一下thread,喚醒線程後會判斷退出標記因此設定的處理函數不會被調用。如果線程已經被喚醒並已經退出那麼kthread_stop會一直等待。

    如果處理函數沒用kthread_should_stop判斷退出,那麼 kthread_stop會一直等待處理函數主動退出。 kthread_stop()負責結束創建的線程,參數是創建時返回的task_struct指針。kthread設置標誌should_stop,並等待線程主動結束,返回線程的返回值。線程可能在kthread_stop()調用前就結束。(經過實際驗證,如果線程在kthread_stop()調用之前就結束,之後kthread_stop()再調用會發生可怕地事情—調用kthread_stop()的進程crash!!之所以如此,是由於kthread實現上的弊端)在調用 kthread_stop()結束線程之前一定要檢查該線程是否還在運行(通過 kthread_run 返回的 task_stuct 是否有效),否則會造成災難性的後果。

      kthread_should_stop()返回should_stop標誌(參見 struct kthread )。它用於創建的線程檢查結束標誌,並決定是否退出。線程完全可以在完成自己的工作後主動結束,不需等待should_stop標誌。

      外界調用kthread_stop()刪除線程。kthread_stop首先設置結束標誌should_stop,然後調用wake_for_completion(&kthread->exited)上,這個其實是新線程task_struct上的vfork_done,會在線程結束調用do_exit()時設置。

 

三、源碼分析 (內核版本是2.6.21.5)

3.1 管理調度其它的內核線程kthread

                 使用ps命令可以查看有個名叫kthread的進程,它在內核初始化的時候被創建。

static __init int helper_init(void)

{

    //創建一個單線程的共享列隊

    helper_wq = create_singlethread_workqueue("kthread");

    BUG_ON(!helper_wq);

    return 0;

}

core_initcall(helper_init);

就是這個共享列隊kthread_create會定義一個工作,在工作內創建創建具體的線程。

 

3.2 kthread_create創建線程

                 再看kthread_create前先看下kthread_create_info結構,每個線程創建時使用。

struct kthread_create_info

{

    /* Information passed to kthread() from keventd. */

    int (*threadfn)(void *data); //線程處理函數

    void *data; //線程參數

    struct completion started; //在工作中等待kernel_thread創建線程完成,線程創建完後線程會    通知工作繼續。


    /* Result passed back to kthread_create() from keventd. */

    struct task_struct *result; // started當收到線程創建完信號started後,用來存放創建的任務結構體

    struct completion done; // 工作者線程加入一個工作後會等待工作做完,這個工作只是創建線程。

    struct work_struct work; // 創建線程的工作,具體工作看後面源碼

};
  1.  

/**

* kthread_create - 創建一個線程.

* @threadfn: the function to run until signal_pending(current).

* @data: data ptr for @threadfn.

* @namefmt: printf-style name for the thread.

*

* 描述:這個幫助函數創建並命名一個內核線程,線程創建後並不運行,使用wake_up_process() 函數來運行,參考kthread_run(), kthread_create_on_cpu()

*

*被喚醒後,線程調用threadfn()函數data作爲參數,如果是獨立線程沒有其他線程調用 kthread_stop()那麼可以直接使用do_exit(),或當檢測到kthread_should_stop()返回真時(kthread_stop()已被調用了)返回處理函數 , 應返回0或負數,返回值會傳給 kthread_stop()返回。

*/

struct task_struct *kthread_create(int (*threadfn)(void *data), void *data, const char namefmt[], ...)

{

    struct kthread_create_info create;


    //下面五行初始化kthread_create_info

    create.threadfn = threadfn;

    create.data = data;

    init_completion(&create.started);

    init_completion(&create.done);

    INIT_WORK(&create.work, keventd_create_kthread); //可見創建的工作是在    keventd_create_kthread函數內進行


/*The workqueue needs to start up first:*/

if (!helper_wq) //這個系統啓動後正常是已經初始化了的

    create.work.func(&create.work); //如沒初始化那只有在當前進程下完成工作了而不是在kthread 裏

else {

    queue_work(helper_wq, &create.work); //將工作加入列隊並調度

    wait_for_completion(&create.done); //等待工作執行完,執行完後create.result返回創建的任務結構或錯誤,由於工作是在kthread 裏執行所以必須等待工作做完才能返回

}

if (!IS_ERR(create.result)) {

    va_list args;

    va_start(args, namefmt);

    vsnprintf(create.result->comm, sizeof(create.result->comm),

    namefmt, args);

    va_end(args);

}


return create.result;

}

上面看到創建工作是在keventd_create_kthread函數裏,那麼看下keventd_create_kthread函數

/* We are keventd: create a thread. 這個函數工作在keventd內核線程中*/

static void keventd_create_kthread(struct work_struct *work)

{

    struct kthread_create_info *create =container_of(work, struct kthread_create_info, work);

    int pid;


/* We want our own signal handler (we take no signals by default)*/

/*我們使用自己的信號處理,默認不處理信號*/

    pid = kernel_thread(kthread, create, CLONE_FS | CLONE_FILES | SIGCHLD);//在這裏創建函數,線程處理函數爲kthread函數,參數爲struct kthread_create_info指針create。


if (pid < 0) {

    create->result = ERR_PTR(pid);

} else {

    wait_for_completion(&create->started); //等待創建的線程執行,線程執行後會發送完成信號         create->started

    read_lock(&tasklist_lock);

    create->result = find_task_by_pid(pid);

    read_unlock(&tasklist_lock);

}

complete(&create->done);

}

這時kthread_create在等待create->done信號,內核線程keventd在等待線程創建完create->started。上面創建了線程,處理函數爲kthread

static int kthread(void *_create)

{

struct kthread_create_info *create = _create;

int (*threadfn)(void *data);

void *data;

sigset_t blocked;

int ret = -EINTR;


kthread_exit_files();


/* Copy data: it's on keventd's stack */

threadfn = create->threadfn;

data = create->data;


/* Block and flush all signals (in case we're not from keventd). 阻塞全部信號*/

sigfillset(&blocked);

sigprocmask(SIG_BLOCK, &blocked, NULL);

flush_signals(current);


/* By default we can run anywhere, unlike keventd. 允許線程在任意CPU上運行 keventd值在1個CPU上運行*/

set_cpus_allowed(current, CPU_MASK_ALL);


/* OK, tell user we're spawned, wait for stop or wakeup */

__set_current_state(TASK_INTERRUPTIBLE);

complete(&create->started); //這裏通知keventd完成線程初始化,keventd收到後獲取新線程的任務結構,然後發出工作完成的信號後kthread_create返回。

schedule();


if (!kthread_should_stop()) //判斷先前是否調用過kthread_stop

ret = threadfn(data); //這裏才真正執行定義的線程函數


/* It might have exited on its own, w/o kthread_stop. Check. */

if (kthread_should_stop()) { //判斷是否執行過kthread_stop

kthread_stop_info.err = ret; //ret是線程函數的返回,後面會經過kthread_stop函數返回

complete(&kthread_stop_info.done); //如執行過kthread_stop 還要通知kthread_stop線程完成結束了,如果用戶定義的處理函數使用了do_exit那麼就不會通知kthread_stop,造成kthread_stop一直等待。

}

return 0;

}

至此我們看到kthread_create是如何創建線程,和線程是如何工作的了

        3.3 kthread_stop線程的停止

                先看下停止相關的結構

struct kthread_stop_info

{

struct task_struct *k; //要停止的線程結構

int err; //返回值

struct completion done; //線程完成結束的等待信號

};

/* Thread stopping is done by setthing this var: lock serializes multiple kthread_stop calls. */

/* 線程結束鎖 kthread_stop在整個系統內一次只能被一個線程調用*/

static DEFINE_MUTEX(kthread_stop_lock);

static struct kthread_stop_info kthread_stop_info;
/**

* kthread_should_stop - should this kthread return now?

* When someone calls kthread_stop() on your kthread, it will be woken

* and this will return true. You should then return, and your return

* value will be passed through to kthread_stop().

*/

int kthread_should_stop(void)

{

return (kthread_stop_info.k == current);

}

這個函數在kthread_stop()被調用後返回真,當返回爲真時你的處理函數要返回,返回值會通過kthread_stop()返回。所以你的處理函數應該有判斷kthread_should_stop然後退出的代碼。

/**

* kthread_stop - stop a thread created by kthread_create().

* @k: thread created by kthread_create().

*

* Sets kthread_should_stop() for @k to return true, wakes it, and

* waits for it to exit. Your threadfn() must not call do_exit()

* itself if you use this function! This can also be called after

* kthread_create() instead of calling wake_up_process(): the thread

* will exit without calling threadfn().

*

* Returns the result of threadfn(), or %-EINTR if wake_up_process()

* was never called.

*/

int kthread_stop(struct task_struct *k)

{

int ret;

mutex_lock(&kthread_stop_lock); //系統一次只能處理一個結束線程申請

/* It could exit after stop_info.k set, but before wake_up_process. */

get_task_struct(k); //增加線程引用計數

/* Must init completion *before* thread sees kthread_stop_info.k */

init_completion(&kthread_stop_info.done);

smp_wmb();

/* Now set kthread_should_stop() to true, and wake it up. */

kthread_stop_info.k = k;//設置了這個之後 kthread_should_stop() 會返回真

wake_up_process(k); //不管線程有沒運行 先叫醒再說(如果已經喚醒過並結束了,該線程是喚醒不了的,這樣會造成後面一直等待kthread_stop_info.done信號),即便沒運行叫醒後也不會運行用戶定義的函數。

put_task_struct(k);

/* Once it dies, reset stop ptr, gather result and we're done. */

wait_for_completion(&kthread_stop_info.done);//等待線程結束

kthread_stop_info.k = NULL;

ret = kthread_stop_info.err; //返回值

mutex_unlock(&kthread_stop_lock);

return ret;

}

注意如果調用了kthread_stop你的處理函數不能調用do_exit(),函數返回你處理函數的返回值,如果創建的線程還沒調用過wake_up_process()那麼會返回-EINTR .

 

   四、測試代碼

struct task_struct *mytask;

/*代碼中要有kthread_should_stop()判斷 至於返回值只對kthread_stop纔有意義*/

int func(void* data)

{

while(1 )

{

if( kthread_should_stop()) return -1;

printk(KERN_ALERT "func running\n");

set_current_state(TASK_UNINTERRUPTIBLE);

schedule_timeout(1*HZ);

}

return 0;

}

線程創建和驅動

mytask=kthread_create(func,0,"mykthread");

wake_up_process(mytask);

 

在需要結束的地方調用

kthread_stop(mytask);

通過幾個函數可以很容易的創建內核線程,但線程創建出來之後我們更關注的是有多線程帶來的併發和競爭問題。併發的管理是操作系統編程的核心問題之一,引起的錯誤是一些最易出現又最難發現的問題.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章