日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長(zhǎng)提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請(qǐng)做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747

內(nèi)核線程

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

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

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

內(nèi)核線程就是內(nèi)核的分身,一個(gè)分身可以處理一件特定事情。內(nèi)核線程的調(diào)度由內(nèi)核負(fù)責(zé),一個(gè)內(nèi)核線程處于阻塞狀態(tài)時(shí)不影響其他的內(nèi)核線程,因?yàn)槠涫钦{(diào)度的基本單位。

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

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

內(nèi)核線程概述

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

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

  1. 周期性地將修改的內(nèi)存頁與頁來源塊設(shè)備同步
  2. 如果內(nèi)存頁很少使用,則寫入交換區(qū)
  3. 管理延時(shí)動(dòng)作, 如2號(hào)進(jìn)程接手內(nèi)核進(jìn)程的創(chuàng)建
  4. 實(shí)現(xiàn)文件系統(tǒng)的事務(wù)日志

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

  1. 線程啟動(dòng)后一直等待,直至內(nèi)核請(qǐng)求線程執(zhí)行某一特定操作。
  2. 線程啟動(dòng)后按周期性間隔運(yùn)行,檢測(cè)特定資源的使用,在用量超出或低于預(yù)置的限制時(shí)采取行動(dòng)。

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

  1. 它們?cè)贑PU的管態(tài)執(zhí)行,而不是用戶態(tài)。
  2. 它們只可以訪問虛擬地址空間的內(nèi)核部分(高于TASK_SIZE的所有地址),但不能訪問用戶空間

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

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

struct task_struct

{

// ...

struct mm_struct *mm;

struct mm_struct *avtive_mm;

//...

};

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

對(duì)于普通用戶進(jìn)程來說,mm指向虛擬地址空間的用戶空間部分,而對(duì)于內(nèi)核線程,mm為NULL。

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

為什么沒有mm指針的進(jìn)程稱為惰性TLB進(jìn)程?

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

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

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

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

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

1、古老的接口 kernel_create和daemonize

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

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

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

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

2號(hào)進(jìn)程kthreadd的誕生

早期的kernel_create和daemonize接口

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

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

Workqueue機(jī)制

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

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

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

2號(hào)進(jìn)程kthreadd

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

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

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

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

使用ps -eo pid,ppid,command

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

 

kernel_thread

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

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

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

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

早期實(shí)現(xiàn)

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

extern void daemonize(void);

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

  1. 該函數(shù)釋放其父進(jìn)程的所有資源,不然這些資源會(huì)一直鎖定直到線程結(jié)束。
  2. 阻塞信號(hào)的接收
  3. 將init用作守護(hù)進(jìn)程的父進(jìn)程

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

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

新版本的實(shí)現(xiàn)

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

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

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

同時(shí)kernel_thread的實(shí)現(xiàn)也改為由_do_fork(早期內(nèi)核中是do_fork)來實(shí)現(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)建一個(gè)新的內(nèi)核線程。最初線程是停止的,需要使用wake_up_process啟動(dòng)它。

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)建一個(gè)內(nèi)核線程,然后通過wake_up_process喚醒它

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

線程一旦啟動(dòng)起來后,會(huì)一直運(yùn)行,除非該線程主動(dòng)調(diào)用do_exit函數(shù),或者其他的進(jìn)程調(diào)用kthread_stop函數(shù),結(jié)束線程的運(yùn)行。

int kthread_stop(struct task_struct *thread);

kthread_stop() 通過發(fā)送信號(hào)給線程。

如果線程函數(shù)正在處理一個(gè)非常重要的任務(wù),它不會(huì)被中斷的。當(dāng)然如果線程函數(shù)永遠(yuǎn)不返回并且不檢查信號(hào),它將永遠(yuǎn)都不會(huì)停止。

在執(zhí)行kthread_stop的時(shí)候,目標(biāo)線程必須沒有退出,否則會(huì)Oops。原因很容易理解,當(dāng)目標(biāo)線程退出的時(shí)候,其對(duì)應(yīng)的task結(jié)構(gòu)也變得無效,kthread_stop引用該無效task結(jié)構(gòu)就會(huì)出錯(cuò)。

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

thread_func()

{

// do your work here

// wait to exit

while(!thread_could_stop())

{

wait();

}

}

exit_code()

{

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

}

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

分享到:
標(biāo)簽:內(nèi)核 Linux
用戶無頭像

網(wǎng)友整理

注冊(cè)時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊(cè)賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績(jī)?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績(jī)?cè)u(píng)定