内核线程是直接由内核自己启动的进程。内核线程其实是将内核函数委托给独立的进程,它与内核中的其余进程”并行”执行。内核线程常常被称之为内核守护进程。内核线程是被调度的实体,它被加入到某种数据结构中,调度程序根据实际状况进行线程的调度。 内核线程与用户态线程的做用相似,一般用于执行某些周期性的计算任务,或者在后台执行须要大量计算的任务。 node
内核线程操做涉及的函数(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最终会调度到一个平台相关的函数,而这个函数是汇编语言实现的,主要实现寄存器和堆栈的处理,并最终完成进程的切换。