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,unsigned long flags)

這裏我們主要介紹kthread_run,後面會專門分析這兩個函數的異同。

 

kthread_run 事實上是一個宏定義:

複製代碼
/**

 * 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_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參數中的入口函數。

在介紹完如何創建線程之後,下面來介紹另外兩個基本的函數:

int kthread_stop(struct task_struct *k);

 

int kthread_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標誌(參見 struct kthread )。它用於創建的線程檢查結束標誌,並決定是否退出。

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

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

附:

複製代碼
struct kthread {

       int should_stop;

       struct completion exited;

};

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

}


複製代碼

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("Dual BSD/GPL");  

  
static struct task_struct * _tsk;  

static struct task_struct * _tsk1;

static int tc = 0;

static wait_queue_head_t log_wait_queue;

  

static int thread_function(void *data)  
{  

    do {  

                printk(KERN_INFO "IN thread_function thread_function: %d times \n", tc);    

        
                   wait_event_interruptible(log_wait_queue,tc == 10);

                   tc = 0;  ///必須加這一行,內核纔會進行調度。內核線程不像應用程序會主動調度,我們需要顯式的使用調度函數,想要在thread_function_1中去重置tc的值是不可能的,因爲線程不會被調度,該線程會一直佔用CPU

                           
                   printk(KERN_INFO "has been woke up !\n");

    }while(!kthread_should_stop());  

    return tc;  
}  


static int thread_function_1(void *data)  
{  

    do {  

              printk(KERN_INFO "IN thread_function_1 thread_function: %d times\n", ++tc);  

       

                   if(tc == 10 && waitqueue_active(&log_wait_queue))

                   {

                            wake_up_interruptible(&log_wait_queue);

                   }

                   msleep_interruptible(1000);

                  
    }while(!kthread_should_stop());  

    return tc;  

}  

  
static int hello_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 "first create kthread failed!\n");  

    }  

    else {  

        printk(KERN_INFO "first create ktrhead ok!\n");  

    }  

          _tsk1 = kthread_run(thread_function_1,NULL, "mythread2");

    if (IS_ERR(_tsk1)) {  

        printk(KERN_INFO "second create kthread failed!\n");  

    }  

    else {  

        printk(KERN_INFO "second create ktrhead ok!\n");  

    }  

    return 0;  

}  

  
static void hello_exit(void)  
{  

    printk(KERN_INFO "Hello, exit!\n");  

    if (!IS_ERR(_tsk)){  

        int ret = kthread_stop(_tsk);  

        printk(KERN_INFO "First thread function has stopped ,return %d\n", ret);  

    }  

    if(!IS_ERR(_tsk1))

         {

                   int ret = kthread_stop(_tsk1);

                   printk(KERN_INFO "Second thread function_1 has stopped ,return %d\n",ret);

         }

}  

  
module_init(hello_init);  

module_exit(hello_exit);  
複製代碼

說明:這個程序的目的就是,使用一個線程(thread_function_1)通知另外一個線程(thread_function)某個條件(tc == 10)滿足(比如接收線程收到10幀然後通知處理線程處理接收到的數據)

運行結果:

程序加載並運行(tc 的值等於10 之後 就會喚醒另外一個線程,之後tc又從10開始計數):

程序卸載(程序卸載其實還是要很注意的,很多程序在卸載的時候回出現各種問題後面文章會提到):

Linux內核多線程(三)

接上 一篇文章 ,這裏介紹另一種線程間通信的方式:completion機制。Completion機制是線程間通信的一種輕量級機制:允許一個線程告訴另一個線程工作已經完成。爲使用 completion, 需要包含頭文件 <linux/completion.h>。

可以通過以下方式來創建一個 completion :

DECLARE_COMPLETION(my_completion);

或者, 動態創建和初始化:

struct completion my_completion;

init_completion(&my_completion);

等待 completion 是一個簡單事來調用: void wait_for_completion(struct completion *c); 

注意:這個函數進行一個不可打斷的等待. 如果你的代碼調用 wait_for_completion 並且

沒有人完成這個任務, 結果會是一個不可殺死的進程。

completion 事件可能通過調用下列之一來發出:

void complete(struct completion *c);

void complete_all(struct completion *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("Dual BSD/GPL");  
  

static struct completion  comp;  

static struct task_struct * _tsk;  

static struct task_struct * _tsk1;

static int tc = 0;
 

static int thread_function(void *data)  
{  

    do {  

              printk(KERN_INFO "IN thread_function thread_function: %d times \n", tc);    

   
                   wait_for_completion(&comp);

                   //tc = 0;  ///在哪裏都行
                   

                   printk(KERN_INFO "has been woke up !\n");

    }while(!kthread_should_stop());  

    return tc;  

}   

 

static int thread_function_1(void *data)  
{  

    do {  

              printk(KERN_INFO "IN thread_function_1 thread_function: %d times\n", ++tc);  

       

                   if(tc == 10)
                   {

                            complete(&comp);

                            tc = 0;

                   }

                   msleep_interruptible(1000);
                  

    }while(!kthread_should_stop());  

    return tc;  

}  

  

static int hello_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 "first create kthread failed!\n");  

    }  

    else {  

        printk(KERN_INFO "first create ktrhead ok!\n");  

    }  

          _tsk1 = kthread_run(thread_function_1,NULL, "mythread2");

    if (IS_ERR(_tsk1)) {  

        printk(KERN_INFO "second create kthread failed!\n");  

    }  

    else {  

        printk(KERN_INFO "second create ktrhead ok!\n");  

    }  

    return 0;  

}  

  

static void hello_exit(void)  
{  

    printk(KERN_INFO "Hello, exit!\n");  

    if (!IS_ERR(_tsk)){  

        int ret = kthread_stop(_tsk);  

        printk(KERN_INFO "First thread function has stopped ,return %d\n", ret);  

    }  

    if(!IS_ERR(_tsk1))

         {

                   int ret = kthread_stop(_tsk1);

                   printk(KERN_INFO "Second thread function_1 has stopped ,return %d\n",ret);

         }

}   

module_init(hello_init);  

module_exit(hello_exit);  
複製代碼

 

運行結果:

    

轉自:http://www.cnblogs.com/zhuyp1015/archive/2012/06/13/2548458.html

發佈了2 篇原創文章 · 獲贊 8 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章