linux有內(nèi)核級線程,linux支持內(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)核線程。
本教程操作環(huán)境:linux7.3系統(tǒng)、Dell G3電腦。
線程通常被定義為一個進程中代碼的不同執(zhí)行路線。從實現(xiàn)方式上劃分,線程有兩種類型:“用戶級線程”和“內(nèi)核級線程”。
用戶線程指不需要內(nèi)核支持而在用戶程序中實現(xiàn)的線程,其不依賴于操作系統(tǒng)核心,應(yīng)用進程利用線程庫提供創(chuàng)建、同步、調(diào)度和管理線程的函數(shù)來控制用戶線程。這種線程甚至在象 DOS 這樣的操作系統(tǒng)中也可實現(xiàn),但線程的調(diào)度需要用戶程序完成,這有些類似 Windows 3.x 的協(xié)作式多任務(wù)。
另外一種則需要內(nèi)核的參與,由內(nèi)核完成線程的調(diào)度。其依賴于操作系統(tǒng)核心,由內(nèi)核的內(nèi)部需求進行創(chuàng)建和撤銷,這兩種模型各有其好處和缺點。
用戶線程不需要額外的內(nèi)核開支,并且用戶態(tài)線程的實現(xiàn)方式可以被定制或修改以適應(yīng)特殊應(yīng)用的要求,但是當一個線程因 I/O 而處于等待狀態(tài)時,整個進程就會被調(diào)度程序切換為等待狀態(tài),其他線程得不到運行的機會;而內(nèi)核線程則沒有各個限制,有利于發(fā)揮多處理器的并發(fā)優(yōu)勢,但卻占用了更多的系統(tǒng)開支。
Windows NT和OS/2支持內(nèi)核線程。Linux 支持內(nèi)核級的多線程。
linux中的內(nèi)核級線
1.內(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)核線程。
內(nèi)核線程是直接由內(nèi)核本身啟動的進程。內(nèi)核線程實際上是將內(nèi)核函數(shù)委托給獨立的進程執(zhí)行,它與內(nèi)核中的其他“進程”并行執(zhí)行。內(nèi)核線程經(jīng)常被稱之為內(nèi)核守護進程。當前的內(nèi)核中,內(nèi)核線程就負責下面的工作:
- 周期性地將修改的內(nèi)存頁與頁來源塊設(shè)備同步
- 實現(xiàn)文件系統(tǒng)的事務(wù)日志
內(nèi)核線程由內(nèi)核創(chuàng)建,所以內(nèi)核線程在內(nèi)核態(tài)執(zhí)行,只能訪問內(nèi)核虛擬地址空間,不能訪問用戶空間。
在linux所有的線程都當作進程來實現(xiàn),也沒有單獨為線程定義調(diào)度算法以及數(shù)據(jù)結(jié)構(gòu),一個進程相當于包含一個線程,就是自身,多線程,原本的線程稱為主線程,他們一起構(gòu)成線程組。
進程擁有自己的地址空間,所以每個進程都有自己的頁表,而線程卻沒有,只能和其它線程共享主線程的地址空間和頁表
2.三個數(shù)據(jù)結(jié)構(gòu)
每個進程或線程由三個重要的數(shù)據(jù)結(jié)構(gòu),分別是struct thread_info, struct task_struct 和內(nèi)核棧。
thread_info對象存放的進程/線程的基本信息,它和進程/線程的內(nèi)核棧存放在內(nèi)核空間里的一段2倍頁長空間中。其中thread_info結(jié)構(gòu)存放在地址段的末尾,其余空間作為內(nèi)核棧。內(nèi)核使用伙伴系統(tǒng)分配這段空間。

struct thread_info { int preempt_count; /* 0 => preemptable, <0 => bug */ struct task_struct *task; /* main task structure */ __u32 cpu; /* cpu */};
thread_info結(jié)構(gòu)體中有一個struct task_struct *task,task指向該線程或者進程的task_struct對象,task_struct也叫做任務(wù)描述符:
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:對象用來管理該進程/線程的頁表以及虛擬內(nèi)存區(qū)
- active_mm:主要用于內(nèi)核線程訪問主內(nèi)核頁全局目錄
- pid:每個task_struct都會有一個不同的id,就是pid
- tgid:線程組領(lǐng)頭線程的PID,就是主線程的pid
linux系統(tǒng)上虛擬地址空間分為兩個部分:供用戶態(tài)程序訪問的虛擬地址空間和供內(nèi)核訪問的內(nèi)核空間。每當內(nèi)核執(zhí)行上下文切換時,虛擬地址空間的用戶層部分都會切換,以便匹配運行的進程,內(nèi)核空間的部分是不會切換的。
3.內(nèi)核線程創(chuàng)建
在內(nèi)核版本linux-3.x以后,內(nèi)核線程的創(chuàng)建被延后執(zhí)行,并且交給名為kthreadd 2號線程執(zhí)行創(chuàng)建過程,但是kthreadd本身是怎么創(chuàng)建的呢?過程如下:
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實現(xiàn)的,do_fork通過傳入不同的參數(shù),可以分別用于創(chuàng)建用戶態(tài)進程/線程,內(nèi)核線程等。當kthreadadd被創(chuàng)建以后,內(nèi)核線程的創(chuàng)建交給它實現(xiàn)。
內(nèi)核線程的創(chuàng)建分為創(chuàng)建和啟動兩個部分,kthread_run作為統(tǒng)一的接口,可以同時實現(xiàn),這兩個功能:
#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中斷線程去創(chuàng)建改線程*/ 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函數(shù)中:
- 首先利用kmalloc分配kthread_create_info變量create,利用函數(shù)參數(shù)初始化create
- 將create加入kthread_create_list鏈表中,然后喚醒kthreadd內(nèi)核線程創(chuàng)建當前線程
- 喚醒kthreadd后,利用completion等待內(nèi)核線程創(chuàng)建完成,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(;;)一直駐留在內(nèi)存中運行:主要過程如下:
- 檢查kthread_create_list為空時,kthreadd讓出cpu的執(zhí)行權(quán)
- kthread_create_list不為空時,利用while循環(huán)遍歷kthread_create_list鏈表
- 每取下一個鏈表節(jié)點后調(diào)用create_kthread,創(chuàng)建內(nèi)核線程
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); } }
可以看到內(nèi)核線程的創(chuàng)建最終還是和kthreadd一樣,調(diào)用kernel_thread實現(xiàn)。
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為參數(shù),create中帶有創(chuàng)建內(nèi)核線程的回調(diào)函數(shù),以及函數(shù)的參數(shù)。kthread中,完成completion信號量的處理,然后schedule讓出cpu的執(zhí)行權(quán),等待下次返回 時,執(zhí)行回調(diào)函數(shù)threadfn(data)。
4.內(nèi)核線程的退出
線程一旦啟動起來后,會一直運行,除非該線程主動調(diào)用do_exit函數(shù),或者其他的進程調(diào)用kthread_stop函數(shù),結(jié)束線程的運行。
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; }
如果線程函數(shù)正在處理一個非常重要的任務(wù),它不會被中斷的。當然如果線程函數(shù)永遠不返回并且不檢查信號,它將永遠都不會停止。在執(zhí)行kthread_stop的時候,目標線程必須沒有退出,否則會Oops。所以在創(chuàng)建thread_func時,可以采用以下形式:
thread_func() { // do your work here // wait to exit while(!thread_could_stop()) { wait(); } } exit_code() { kthread_stop(_task); //發(fā)信號給task,通知其可以退出了 }
如果線程中在等待某個條件滿足才能繼續(xù)運行,所以只有滿足了條件以后,才能調(diào)用kthread_stop殺掉內(nèi)核線程。
5.內(nèi)核線程使用
#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 %urn", times); times++; msleep_interruptible(time_conut*1000); } printk("n test_thread_fun exit successrnn"); 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, };
相關(guān)推薦:《Linux視頻教程》