Linux內(nèi)核線程kernel thread詳解

內(nèi)核線程

為什么需要內(nèi)核線程

Linux內(nèi)核可以看作一個服務(wù)進程(管理軟硬件資源儿奶,響應(yīng)用戶進程的種種合理以及不合理的請求)。

內(nèi)核需要多個執(zhí)行流并行罢坝,為了防止可能的阻塞廓握,支持多線程是必要的。

內(nèi)核線程就是內(nèi)核的分身嘁酿,一個分身可以處理一件特定事情隙券。內(nèi)核線程的調(diào)度由內(nèi)核負責,一個內(nèi)核線程處于阻塞狀態(tài)時不影響其他的內(nèi)核線程闹司,因為其是調(diào)度的基本單位娱仔。

這與用戶線程是不一樣的。因為內(nèi)核線程只運行在內(nèi)核態(tài)

因此游桩,它只能使用大于PAGE_OFFSET(傳統(tǒng)的x86_32上是3G)的地址空間牲迫。

內(nèi)核線程概述

內(nèi)核線程是直接由內(nèi)核本身啟動的進程耐朴。內(nèi)核線程實際上是將內(nèi)核函數(shù)委托給獨立的進程,它與內(nèi)核中的其他進程”并行”執(zhí)行盹憎。內(nèi)核線程經(jīng)常被稱之為內(nèi)核守護進程筛峭。

他們執(zhí)行下列任務(wù)

周期性地將修改的內(nèi)存頁與頁來源塊設(shè)備同步

如果內(nèi)存頁很少使用,則寫入交換區(qū)

管理延時動作, 如2號進程接手內(nèi)核進程的創(chuàng)建

實現(xiàn)文件系統(tǒng)的事務(wù)日志

內(nèi)核線程主要有兩種類型

線程啟動后一直等待陪每,直至內(nèi)核請求線程執(zhí)行某一特定操作影晓。

線程啟動后按周期性間隔運行,檢測特定資源的使用檩禾,在用量超出或低于預(yù)置的限制時采取行動挂签。

內(nèi)核線程由內(nèi)核自身生成,其特點在于

它們在CPU的管態(tài)執(zhí)行盼产,而不是用戶態(tài)饵婆。

它們只可以訪問虛擬地址空間的內(nèi)核部分(高于TASK_SIZE的所有地址),但不能訪問用戶空間

內(nèi)核線程的進程描述符task_struct

task_struct進程描述符中包含兩個跟進程地址空間相關(guān)的字段mm, active_mm戏售,

struct task_struct

{

// ...

struct mm_struct *mm;

struct mm_struct *avtive_mm;

//...

};

大多數(shù)計算機上系統(tǒng)的全部虛擬地址空間分為兩個部分: 供用戶態(tài)程序訪問的虛擬地址空間和供內(nèi)核訪問的內(nèi)核空間侨核。每當內(nèi)核執(zhí)行上下文切換時, 虛擬地址空間的用戶層部分都會切換, 以便當前運行的進程匹配, 而內(nèi)核空間不會放生切換。

對于普通用戶進程來說灌灾,mm指向虛擬地址空間的用戶空間部分芹关,而對于內(nèi)核線程,mm為NULL紧卒。

這位優(yōu)化提供了一些余地, 可遵循所謂的惰性TLB處理(lazy TLB handing)。active_mm主要用于優(yōu)化诗祸,由于內(nèi)核線程不與任何特定的用戶層進程相關(guān)跑芳,內(nèi)核并不需要倒換虛擬地址空間的用戶層部分,保留舊設(shè)置即可直颅。由于內(nèi)核線程之前可能是任何用戶層進程在執(zhí)行博个,故用戶空間部分的內(nèi)容本質(zhì)上是隨機的,內(nèi)核線程決不能修改其內(nèi)容功偿,故將mm設(shè)置為NULL盆佣,同時如果切換出去的是用戶進程,內(nèi)核將原來進程的mm存放在新內(nèi)核線程的active_mm中械荷,因為某些時候內(nèi)核必須知道用戶空間當前包含了什么共耍。

為什么沒有mm指針的進程稱為惰性TLB進程?

假如內(nèi)核線程之后運行的進程與之前是同一個, 在這種情況下, 內(nèi)核并不需要修改用戶空間地址表。地址轉(zhuǎn)換后備緩沖器(即TLB)中的信息仍然有效吨瞎。只有在內(nèi)核線程之后, 執(zhí)行的進程是與此前不同的用戶層進程時, 才需要切換(并對應(yīng)清除TLB數(shù)據(jù))痹兜。

內(nèi)核線程和普通的進程間的區(qū)別在于內(nèi)核線程沒有獨立的地址空間,mm指針被設(shè)置為NULL颤诀;它只在 內(nèi)核空間運行字旭,從來不切換到用戶空間去对湃;并且和普通進程一樣,可以被調(diào)度遗淳,也可以被搶占拍柒。

內(nèi)核線程的創(chuàng)建

創(chuàng)建內(nèi)核線程接口的演變

內(nèi)核線程可以通過兩種方式實現(xiàn):

1、古老的接口 kernel_create和daemonize

將一個函數(shù)傳遞給kernel_thread創(chuàng)建并初始化一個task屈暗,該函數(shù)接下來負責幫助內(nèi)核調(diào)用daemonize已轉(zhuǎn)換為內(nèi)核守護進程拆讯,daemonize隨后完成一些列操作, 如該函數(shù)釋放其父進程的所有資源,不然這些資源會一直鎖定直到線程結(jié)束恐锦。阻塞信號的接收, 將init用作守護進程的父進程

2往果、更加現(xiàn)在的方法kthead_create和kthread_run

創(chuàng)建內(nèi)核更常用的方法是輔助函數(shù)kthread_create,該函數(shù)創(chuàng)建一個新的內(nèi)核線程一铅。最初線程是停止的陕贮,需要使用wake_up_process啟動它。

使用kthread_run潘飘,與kthread_create不同的是肮之,其創(chuàng)建新線程后立即喚醒它,其本質(zhì)就是先用kthread_create創(chuàng)建一個內(nèi)核線程卜录,然后通過wake_up_process喚醒它

2號進程kthreadd的誕生

早期的kernel_create和daemonize接口

在早期的內(nèi)核中, 提供了kernel_create和daemonize接口, 但是這種機制操作復(fù)雜而且將所有的任務(wù)交給內(nèi)核去完成戈擒。

但是這種機制低效而且繁瑣, 將所有的操作塞給內(nèi)核, 我們創(chuàng)建內(nèi)核線程的初衷不本來就是為了內(nèi)核分擔工作, 減少內(nèi)核的開銷的么

Workqueue機制

因此在linux-2.6以后, 提供了更加方便的接口kthead_create和kthread_run, 同時將內(nèi)核線程的創(chuàng)建操作延后, 交給一個工作隊列workqueue。

Linux中的workqueue機制就是為了簡化內(nèi)核線程的創(chuàng)建艰毒。通過kthread_create并不真正創(chuàng)建內(nèi)核線程, 而是將創(chuàng)建工作create work插入到工作隊列helper_wq中, 隨后調(diào)用workqueue的接口就能創(chuàng)建內(nèi)核線程筐高。并且可以根據(jù)當前系統(tǒng)CPU的個數(shù)創(chuàng)建線程的數(shù)量,使得線程處理的事務(wù)能夠并行化丑瞧。workqueue是內(nèi)核中實現(xiàn)簡單而有效的機制柑土,他顯然簡化了內(nèi)核daemon的創(chuàng)建,方便了用戶的編程.

工作隊列(workqueue)是另外一種將工作推后執(zhí)行的形式.工作隊列可以把工作推后绊汹,交由一個內(nèi)核線程去執(zhí)行稽屏,也就是說,這個下半部分可以在進程上下文中執(zhí)行西乖。最重要的就是工作隊列允許被重新調(diào)度甚至是睡眠狐榔。

2號進程kthreadd

但是這種方法依然看起來不夠優(yōu)美, 我們何不把這種創(chuàng)建內(nèi)核線程的工作交給一個特殊的內(nèi)核線程來做呢?

于是linux-2.6.22引入了kthreadd進程, 并隨后演變?yōu)?號進程, 它在系統(tǒng)初始化時同1號進程一起被創(chuàng)建(當然肯定是通過kernel_thread), 并隨后演變?yōu)閯?chuàng)建內(nèi)核線程的真正建造師, 它會循環(huán)的是查詢工作鏈表static LIST_HEAD(kthread_create_list);中是否有需要被創(chuàng)建的內(nèi)核線程, 而我們的通過kthread_create執(zhí)行的操作, 只是在內(nèi)核線程任務(wù)隊列kthread_create_list中增加了一個create任務(wù), 然后會喚醒kthreadd進程來執(zhí)行真正的創(chuàng)建操作

內(nèi)核線程會出現(xiàn)在系統(tǒng)進程列表中, 但是在ps的輸出中進程名command由方括號包圍, 以便與普通進程區(qū)分获雕。

如下圖所示, 我們可以看到系統(tǒng)中, 所有內(nèi)核線程都用[]標識, 而且這些進程父進程id均是2, 而2號進程kthreadd的父進程是0號進程

使用ps -eo pid,ppid,command

kernel_thread

kernel_thread是最基礎(chǔ)的創(chuàng)建內(nèi)核線程的接口, 它通過將一個函數(shù)直接傳遞給內(nèi)核來創(chuàng)建一個進程, 創(chuàng)建的進程運行在內(nèi)核空間, 并且與其他進程線程共享內(nèi)核虛擬地址空間

kernel_thread的實現(xiàn)經(jīng)歷過很多變革

早期的kernel_thread執(zhí)行更底層的操作, 直接創(chuàng)建了task_struct并進行初始化,

引入了kthread_create和kthreadd 2號進程后, kernel_thread的實現(xiàn)也由統(tǒng)一的_do_fork(或者早期的do_fork)托管實現(xiàn)

早期實現(xiàn)

早期的內(nèi)核中, kernel_thread并不是使用統(tǒng)一的do_fork或者_do_fork這一封裝好的接口實現(xiàn)的, 而是使用更底層的細節(jié)薄腻,它內(nèi)部調(diào)用了更加底層的arch_kernel_thread創(chuàng)建了一個線程,但是這種方式創(chuàng)建的線程并不適合運行届案,因此內(nèi)核提供了daemonize函數(shù)被廓。

extern void daemonize(void);

主要執(zhí)行如下操作

該函數(shù)釋放其父進程的所有資源,不然這些資源會一直鎖定直到線程結(jié)束萝玷。

阻塞信號的接收

將init用作守護進程的父進程

我們將了這么多kernel_thread, 但是我們并不提倡我們使用它, 因為這個是底層的創(chuàng)建內(nèi)核線程的操作接口, 使用kernel_thread在內(nèi)核中執(zhí)行大量的操作, 雖然創(chuàng)建的代價已經(jīng)很小了, 但是對于追求性能的linux內(nèi)核來說還不能忍受

因此我們只能說kernel_thread是一個古老的接口, 內(nèi)核中的有些地方仍然在使用該方法, 將一個函數(shù)直接傳遞給內(nèi)核來創(chuàng)建內(nèi)核線程

新版本的實現(xiàn)

于是linux-3.x下之后, 有了更好的實現(xiàn), 那就是

延后內(nèi)核的創(chuàng)建工作, 將內(nèi)核線程的創(chuàng)建工作交給一個內(nèi)核線程來做, 即kthreadd 2號進程

但是在kthreadd還沒創(chuàng)建之前, 我們只能通過kernel_thread這種方式去創(chuàng)建,

同時kernel_thread的實現(xiàn)也改為由_do_fork(早期內(nèi)核中是do_fork)來實現(xiàn)

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

{

return _do_fork(flags|CLONE_VM|CLONE_UNTRACED, (unsigned long)fn,

(unsigned long)arg, NULL, NULL, 0);

}

kthread_create

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

void *data,

int node,

const char namefmt[], ...);

#define kthread_create(threadfn, data, namefmt, arg...) \

kthread_create_on_node(threadfn, data, NUMA_NO_NODE, namefmt, ##arg)

創(chuàng)建內(nèi)核更常用的方法是輔助函數(shù)kthread_create嫁乘,該函數(shù)創(chuàng)建一個新的內(nèi)核線程昆婿。最初線程是停止的,需要使用wake_up_process啟動它蜓斧。

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不同的是,其創(chuàng)建新線程后立即喚醒它挎春,其本質(zhì)就是先用kthread_create創(chuàng)建一個內(nèi)核線程看疙,然后通過wake_up_process喚醒它

內(nèi)核線程的退出

線程一旦啟動起來后,會一直運行直奋,除非該線程主動調(diào)用do_exit函數(shù)能庆,或者其他的進程調(diào)用kthread_stop函數(shù),結(jié)束線程的運行脚线。

int kthread_stop(struct task_struct *thread);

kthread_stop() 通過發(fā)送信號給線程搁胆。

如果線程函數(shù)正在處理一個非常重要的任務(wù),它不會被中斷的邮绿。當然如果線程函數(shù)永遠不返回并且不檢查信號渠旁,它將永遠都不會停止。

在執(zhí)行kthread_stop的時候船逮,目標線程必須沒有退出顾腊,否則會Oops。原因很容易理解挖胃,當目標線程退出的時候杂靶,其對應(yīng)的task結(jié)構(gòu)也變得無效,kthread_stop引用該無效task結(jié)構(gòu)就會出錯酱鸭。

為了避免這種情況伪煤,需要確保線程沒有退出,其方法如代碼中所示:

thread_func()

{

// do your work here

// wait to exit

while(!thread_could_stop())

{

wait();

}

}

exit_code()

{

kthread_stop(_task); //發(fā)信號給task凛辣,通知其可以退出了

}

這種退出機制很溫和,一切盡在thread_func()的掌控之中职烧,線程在退出時可以從容地釋放資源扁誓,而不是莫名其妙地被人“暗殺”。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蚀之,一起剝皮案震驚了整個濱河市蝗敢,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌足删,老刑警劉巖寿谴,帶你破解...
    沈念sama閱讀 212,383評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異失受,居然都是意外死亡讶泰,警方通過查閱死者的電腦和手機咏瑟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來痪署,“玉大人码泞,你說我怎么就攤上這事±欠福” “怎么了余寥?”我有些...
    開封第一講書人閱讀 157,852評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長悯森。 經(jīng)常有香客問我宋舷,道長,這世上最難降的妖魔是什么瓢姻? 我笑而不...
    開封第一講書人閱讀 56,621評論 1 284
  • 正文 為了忘掉前任祝蝠,我火速辦了婚禮,結(jié)果婚禮上汹来,老公的妹妹穿的比我還像新娘续膳。我一直安慰自己,他們只是感情好收班,可當我...
    茶點故事閱讀 65,741評論 6 386
  • 文/花漫 我一把揭開白布坟岔。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上嗅钻,一...
    開封第一講書人閱讀 49,929評論 1 290
  • 那天捻脖,我揣著相機與錄音,去河邊找鬼俊犯。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的啼辣。 我是一名探鬼主播,決...
    沈念sama閱讀 39,076評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼御滩,長吁一口氣:“原來是場噩夢啊……” “哼鸥拧!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起削解,我...
    開封第一講書人閱讀 37,803評論 0 268
  • 序言:老撾萬榮一對情侶失蹤富弦,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后氛驮,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體腕柜,經(jīng)...
    沈念sama閱讀 44,265評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,582評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了盏缤。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片砰蠢。...
    茶點故事閱讀 38,716評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖蛾找,靈堂內(nèi)的尸體忽然破棺而出娩脾,到底是詐尸還是另有隱情,我是刑警寧澤打毛,帶...
    沈念sama閱讀 34,395評論 4 333
  • 正文 年R本政府宣布柿赊,位于F島的核電站,受9級特大地震影響幻枉,放射性物質(zhì)發(fā)生泄漏碰声。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,039評論 3 316
  • 文/蒙蒙 一熬甫、第九天 我趴在偏房一處隱蔽的房頂上張望胰挑。 院中可真熱鬧,春花似錦椿肩、人聲如沸瞻颂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽贡这。三九已至,卻和暖如春厂榛,著一層夾襖步出監(jiān)牢的瞬間盖矫,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評論 1 266
  • 我被黑心中介騙來泰國打工击奶, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留辈双,地道東北人。 一個月前我還...
    沈念sama閱讀 46,488評論 2 361
  • 正文 我出身青樓柜砾,卻偏偏與公主長得像湃望,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子痰驱,可洞房花燭夜當晚...
    茶點故事閱讀 43,612評論 2 350

推薦閱讀更多精彩內(nèi)容