共計 7931 個字符,預計需要花費 20 分鐘才能閱讀完成。
本文丸趣 TV 小編為大家詳細介紹“linux 有沒有內核級線程”,內容詳細,步驟清晰,細節處理妥當,希望這篇“linux 有沒有內核級線程”文章能幫助大家解決疑惑,下面跟著丸趣 TV 小編的思路慢慢深入,一起來學習新知識吧。
linux 有內核級線程,linux 支持內核級的多線程。Linux 內核可以看作服務進程(管理軟硬件資源,響應用戶進程的各種進程);內核需要多個執行流并行,為了防止可能的阻塞,支持多線程。內核線程就是內核的一個分身,可以用以處理一件特定事情,內核線程的調度由內核負責,一個內核線程的處于阻塞狀態時不影響其他的內核線程。
線程通常被定義為一個進程中代碼的不同執行路線。從實現方式上劃分,線程有兩種類型:“用戶級線程”和“內核級線程”。
用戶線程指不需要內核支持而在用戶程序中實現的線程,其不依賴于操作系統核心,應用進程利用線程庫提供創建、同步、調度和管理線程的函數來控制用戶線程。這種線程甚至在象 DOS 這樣的操作系統中也可實現,但線程的調度需要用戶程序完成,這有些類似 Windows 3.x 的協作式多任務。
另外一種則需要內核的參與,由內核完成線程的調度。其依賴于操作系統核心,由內核的內部需求進行創建和撤銷,這兩種模型各有其好處和缺點。
用戶線程不需要額外的內核開支,并且用戶態線程的實現方式可以被定制或修改以適應特殊應用的要求,但是當一個線程因 I/O 而處于等待狀態時,整個進程就會被調度程序切換為等待狀態,其他線程得不到運行的機會;而內核線程則沒有各個限制,有利于發揮多處理器的并發優勢,但卻占用了更多的系統開支。
Windows NT 和 OS/ 2 支持內核線程。Linux 支持內核級的多線程。
linux 中的內核級線
1. 內核線程概述
Linux 內核可以看作服務進程(管理軟硬件資源,響應用戶進程的各種進程)
內核需要多個執行流并行,為了防止可能的阻塞,支持多線程。
內核線程就是內核的一個分身,可以用以處理一件特定事情,內核線程的調度由內核負責,一個內核線程的處于阻塞狀態時不影響其他的內核線程。
內核線程是直接由內核本身啟動的進程。內核線程實際上是將內核函數委托給獨立的進程執行,它與內核中的其他“進程”并行執行。內核線程經常被稱之為內核守護進程。當前的內核中,內核線程就負責下面的工作:
周期性地將修改的內存頁與頁來源塊設備同步
實現文件系統的事務日志
內核線程由內核創建,所以內核線程在內核態執行,只能訪問內核虛擬地址空間,不能訪問用戶空間。
在 linux 所有的線程都當作進程來實現,也沒有單獨為線程定義調度算法以及數據結構,一個進程相當于包含一個線程,就是自身,多線程,原本的線程稱為主線程,他們一起構成線程組。
進程擁有自己的地址空間,所以每個進程都有自己的頁表,而線程卻沒有,只能和其它線程共享主線程的地址空間和頁表
2. 三個數據結構
每個進程或線程由三個重要的數據結構,分別是 struct thread_info, struct task_struct 和內核棧。
thread_info 對象存放的進程 / 線程的基本信息,它和進程 / 線程的內核棧存放在內核空間里的一段 2 倍頁長空間中。其中 thread_info 結構存放在地址段的末尾,其余空間作為內核棧。內核使用伙伴系統分配這段空間。
struct thread_info {
int preempt_count; /* 0 = preemptable, 0 = bug */
struct task_struct *task; /* main task structure */
__u32 cpu; /* cpu */};
thread_info 結構體中有一個 struct task_struct *task,task 指向該線程或者進程的 task_struct 對象,task_struct 也叫做任務描述符:
struct task_struct {
pid_t pid;
pid_t tgid;
void *stack;
struct mm_struct *mm, *active_mm;
/* filesystem information */
struct fs_struct *fs;
/* open file information */
struct files_struct *files;};#define task_thread_info(task) ((struct thread_info *)(task)- stack)
stack:是指向進程或者線程的 thread_info
mm:對象用來管理該進程 / 線程的頁表以及虛擬內存區
active_mm:主要用于內核線程訪問主內核頁全局目錄
pid:每個 task_struct 都會有一個不同的 id,就是 pid
tgid:線程組領頭線程的 PID,就是主線程的 pid
linux 系統上虛擬地址空間分為兩個部分:供用戶態程序訪問的虛擬地址空間和供內核訪問的內核空間。每當內核執行上下文切換時,虛擬地址空間的用戶層部分都會切換,以便匹配運行的進程,內核空間的部分是不會切換的。
3. 內核線程創建
在內核版本 linux-3.x 以后,內核線程的創建被延后執行,并且交給名為 kthreadd 2 號線程執行創建過程,但是 kthreadd 本身是怎么創建的呢?過程如下:
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);
}
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
kthreadadd 本身最終是通過 do_fork 實現的,do_fork 通過傳入不同的參數,可以分別用于創建用戶態進程 / 線程,內核線程等。當 kthreadadd 被創建以后,內核線程的創建交給它實現。
內核線程的創建分為創建和啟動兩個部分,kthread_run 作為統一的接口,可以同時實現,這兩個功能:
#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; \
})
#define kthread_create(threadfn, data, namefmt, arg...) \
kthread_create_on_node(threadfn, data, -1, namefmt, ##arg)
struct task_struct *kthread_create_on_node(int (*threadfn)(void *data),
void *data, int node,
const char namefmt[],
...)
{
DECLARE_COMPLETION_ONSTACK(done);
struct task_struct *task;
/* 分配 kthread_create_info 空間 */
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;
/* 加入到 kthread_creta_list 列表中,等待 ktherad_add 中斷線程去創建改線程 */
spin_lock(kthread_create_lock);
list_add_tail(create- list, kthread_create_list);
spin_unlock(kthread_create_lock);
wake_up_process(kthreadd_task);
/*
* Wait for completion in killable state, for I might be chosen by
* the OOM killer while kthreadd is trying to allocate memory for
* new kernel thread.
*/
if (unlikely(wait_for_completion_killable( done))) {
/*
* If I was SIGKILLed before kthreadd (or new kernel thread)
* calls complete(), leave the cleanup of this structure to
* that thread.
*/
if (xchg( create- done, NULL))
return ERR_PTR(-EINTR);
/*
* kthreadd (or new kernel thread) will call complete()
* shortly.
*/
wait_for_completion(done);
}
task = create- result;
.
.
.
kfree(create);
return task;
}
kthread_create_on_node 函數中:
首先利用 kmalloc 分配 kthread_create_info 變量 create,利用函數參數初始化 create
將 create 加入 kthread_create_list 鏈表中,然后喚醒 kthreadd 內核線程創建當前線程
喚醒 kthreadd 后,利用 completion 等待內核線程創建完成,completion 完成后,釋放 create 空間
下面來看下 kthreadd 的處理過程:
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_MEMORY]);
current- flags |= PF_NOFREEZE;
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;
}
kthreadd 利用 for(;;) 一直駐留在內存中運行:主要過程如下:
檢查 kthread_create_list 為空時,kthreadd 讓出 cpu 的執行權
kthread_create_list 不為空時,利用 while 循環遍歷 kthread_create_list 鏈表
每取下一個鏈表節點后調用 create_kthread,創建內核線程
static void create_kthread(struct kthread_create_info *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) {
/* If user was SIGKILLed, I release the structure. */
struct completion *done = xchg(create- done, NULL);
if (!done) {
kfree(create);
return;
}
create- result = ERR_PTR(pid);
complete(done);
}
}
可以看到內核線程的創建最終還是和 kthreadd 一樣,調用 kernel_thread 實現。
static int kthread(void *_create)
{
.
.
.
.
/* If user was SIGKILLed, I release the structure. */
done = xchg(create- done, NULL);
if (!done) {
kfree(create);
do_exit(-EINTR);
}
/* OK, tell user we re spawned, wait for stop or wakeup */
__set_current_state(TASK_UNINTERRUPTIBLE);
create- result = current;
complete(done);
schedule();
ret = -EINTR;
if (!test_bit(KTHREAD_SHOULD_STOP, self.flags)) {
__kthread_parkme(self);
ret = threadfn(data);
}
/* we can t just return, we must preserve self on stack */
do_exit(ret);
}
kthread 以 struct kthread_create_info 類型的 create 為參數,create 中帶有創建內核線程的回調函數,以及函數的參數。kthread 中,完成 completion 信號量的處理,然后 schedule 讓出 cpu 的執行權,等待下次返回 時,執行回調函數 threadfn(data)。
4. 內核線程的退出
線程一旦啟動起來后,會一直運行,除非該線程主動調用 do_exit 函數,或者其他的進程調用 kthread_stop 函數,結束線程的運行。
int kthread_stop(struct task_struct *k)
{
struct kthread *kthread;
int ret;
trace_sched_kthread_stop(k);
get_task_struct(k);
kthread = to_live_kthread(k);
if (kthread) {
set_bit(KTHREAD_SHOULD_STOP, kthread- flags);
__kthread_unpark(k, kthread);
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 的時候,目標線程必須沒有退出,否則會 Oops。所以在創建 thread_func 時,可以采用以下形式:
thread_func()
{
// do your work here
// wait to exit
while(!thread_could_stop())
{
wait();
}
}
exit_code()
{
kthread_stop(_task); // 發信號給 task,通知其可以退出了
}
如果線程中在等待某個條件滿足才能繼續運行,所以只有滿足了條件以后,才能調用 kthread_stop 殺掉內核線程。
5. 內核線程使用
#include test_kthread.h
#include linux/delay.h
#include linux/timer.h
#include linux/platform_device.h
#include linux/fs.h
#include linux/module.h
static struct task_struct *test_thread = NULL;
unsigned int time_conut = 5;
int test_thread_fun(void *data)
{
int times = 0;
while(!kthread_should_stop())
{
printk(\n printk %u\r\n , times);
times++;
msleep_interruptible(time_conut*1000);
}
printk(\n test_thread_fun exit success\r\n\n
return 0;
}
void register_test_thread(void)
{
test_thread = kthread_run(test_thread_fun , NULL, test_kthread);
if (IS_ERR(test_thread)){
printk(KERN_INFO create test_thread failed!\n
}
else {
printk(KERN_INFO create test_thread ok!\n
}
}
static ssize_t kthread_debug_start(struct device *dev, struct device_attribute *attr, char *buf)
{
register_test_thread();
return 0;
}
static ssize_t kthread_debug_stop(struct device *dev, struct device_attribute *attr, char *buf)
{
kthread_stop(test_thread);
return 0;
}
static DEVICE_ATTR(kthread_start, S_IRUSR, kthread_debug_start,NULL);
static DEVICE_ATTR(kthread_stop, S_IRUSR, kthread_debug_stop,NULL);
struct attribute * kthread_group_info_attrs[] =
{
dev_attr_kthread_start.attr,
dev_attr_kthread_stop.attr,
NULL,
};
struct attribute_group kthread_group =
{
.name = kthread ,
.attrs = kthread_group_info_attrs,
};
讀到這里,這篇“linux 有沒有內核級線程”文章已經介紹完畢,想要掌握這篇文章的知識點還需要大家自己動手實踐使用過才能領會,如果想了解更多相關內容的文章,歡迎關注丸趣 TV 行業資訊頻道。