內核線程是直接由內核自己啓動的進程。內核線程其實是將內核函數委託給獨立的進程,它與內核中的其餘進程」並行」執行。內核線程常常被稱之爲內核守護進程。內核線程是被調度的實體,它被加入到某種數據結構中,調度程序根據實際狀況進行線程的調度。 內核線程與用戶態線程的做用相似,一般用於執行某些週期性的計算任務,或者在後臺執行須要大量計算的任務。 node
本文主要介紹一下 內核線程操做相關的API的使用,以及內核線程的實現基本原理,更深刻的內容在後續文章中介紹。內核線程操做涉及的函數(API)主要是建立、調度和中止等函數。操做起來也是比較簡單的。下面分別介紹一下這些接口的定義。 建立線程 建立線程的函數爲kthread_create,以下是函數的原型,該函數其實是函數kthread_create_on_node的一個宏定義。後者則是在某個CPU上建立一個線程。該函數的前兩個參數分別是線程主函數指針和函數的參數,然後面的參數經過變參數的方式爲線程命名。linux
#define kthread_create(threadfn, data, namefmt, arg...) \ kthread_create_on_node(threadfn, data, NUMA_NO_NODE, namefmt, ##arg)
複製代碼
喚醒線程算法
經過該函數建立的線程處於非運行狀態,須要調用wake_up_process函數將其喚醒後才能夠在CPU上運行。緩存
int wake_up_process(struct task_struct *p) 複製代碼
建立並運行線程數據結構
在內核的API中有另一個接口能夠直接建立一個處於運行狀態的線程,其定義以下。這裏其實就是調用了上文描述的兩個函數。socket
#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; \ })
複製代碼
中止線程函數
線程也能夠被中止,此時主函數將會退出,固然須要主函數的實現考慮該問題。以下是中止線程的函數接口。ui
int kthread_stop(struct task_struct *k) 複製代碼
線程的調度spa
內核線程建立完成後將一直運行下去,除非遇到了阻塞事件或者本身將本身調度出去。經過下面函數,線程能夠將本身調度出去。調度出去的含義就是將CPU讓給其它線程。操作系統
asmlinkage __visible void __sched schedule(void) 複製代碼
前面介紹了內核線程基本原理及相關的API,下面咱們將開發一個內核線程的基本實例。 這個實例是在一個內核模塊中啓動一個內核線程。內核線程的做用很簡單,就是定時的向系統日誌中輸出一個字符串。本例的目的主要是介紹如何建立、使用和銷燬一個內核線程。
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/in.h>
#include <linux/inet.h>
#include <linux/socket.h>
#include <net/sock.h>
#include <linux/kthread.h>
#include <linux/sched.h>
#define BUF_SIZE 1024
struct task_struct *main_task;
/* 這個函數用於將內核線程置於休眠狀態,也就是將其調度出 * 隊列。*/
static inline void sleep(unsigned sec) {
__set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout(sec * HZ);
}
/* 線程函數, 這個是線程執行的主體 */
static int multhread_server(void *data) {
int index = 0;
/* 在線程沒有被中止的狀況下,循環向系統日誌輸出 * 內容, 完成後休眠1秒。*/
while (!kthread_should_stop()) {
printk(KERN_NOTICE "thread run %d\n", index);
index ++;
sleep(1);
}
return 0;
}
static int multhread_init(void) {
ssize_t ret = 0;
printk("Hello, thread! \n");
/* 建立並啓動一個內核線程, 這裏參數爲線程函數, * 函數的參數(NULL),和線程名稱。 */
main_task = kthread_run(multhread_server,
NULL,
"multhread_server");
if (IS_ERR(main_task)) {
ret = PTR_ERR(main_task);
goto failed;
}
failed:
return ret;
}
static void multhread_exit(void) {
printk("Bye thread!\n");
/* 中止線程 */
kthread_stop(main_task);
}
module_init(multhread_init);
module_exit(multhread_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("SunnyZhang<shuningzhang@126.com>");
複製代碼
建立線程
不管是用戶態的進程仍是內核線程,在內核態都是線程。在Linux操做系統,建立線程實質是是對父進程(線程)進行克隆的過程。 目前,在3.x之後的版本中,內核線程的建立都有一個名爲kthreadd的後臺線程操做完成。建立線程的接口只是用於建立任務,並加到任務列表中,並等待後臺線程的具體處理。 前文中建立線程的函數kthread_create或者kthread_run調用的函數是__kthread_create_on_node,也就是在某個CPU上建立線程。該函數其實只是建立一個建立線程的請求,以下是裁剪的代碼,核心內容以下:
struct task_struct *__kthread_create_on_node(int (*threadfn)(void *data), void *data, int node, const char namefmt[], va_list args) {
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);
/* 將建立任務添加到鏈表中 */
list_add_tail(&create->list, &kthread_create_list);
spin_unlock(&kthread_create_lock);
wake_up_process(kthreadd_task);
... ...
}
複製代碼
具體建立工做在名爲kthreadd的後臺線程中進行,該線程會從隊列中獲取建立請求,並逐個建立線程。建立線程調用的接口爲kernel_thread,該函數實現從父線程克隆子線程的操做,並創建父子線程的關聯關係。
線程調度
Linux的線程管理和調度是一個很是複雜的話題,很難用一篇文章說清楚,咱們這裏只是介紹一下基本原理。目前Linux操做系統默認使用的是CFS調度算法,該算法是基於優先級和時間片的算法,這個算法包含4部分的內容:
static __always_inline struct rq * context_switch(struct rq *rq, struct task_struct *prev, struct task_struct *next, struct rq_flags *rf) 複製代碼
這樣,經過context_switch函數就能夠將當前進程調度出去,而將新的進程調度進來。context_switch最終會調度到一個平臺相關的函數,而這個函數是彙編語言實現的,主要實現寄存器和堆棧的處理,並最終完成進程的切換。