带您进入内核开发的大门 | 内核中的线程

内核线程是直接由内核本身启动的进程。内核线程实际上是将内核函数委托给独立的进程,它与内核中的其他进程”并行”执行。内核线程经常被称之为内核守护进程。内核线程是被调度的实体,它被加入到某种数据结构中,调度程序根据实际情况进行线程的调度。
内核线程与用户态线程的作用类似,通常用于执行某些周期性的计算任务,或者在后台执行需要大量计算的任务。

linux kernel

本文主要介绍一下内核线程操作相关的API的使用,以及内核线程的实现基本原理,更深入的内容在后续文章中介绍。

内核线程操作函数

内核线程操作涉及的函数(API)主要是创建、调度和停止等函数。操作起来也是比较简单的。下面分别介绍一下这些接口的定义。
创建线程
创建线程的函数为kthread_create,如下是函数的原型,该函数实际上是函数kthread_create_on_node的一个宏定义。后者则是在某个CPU上创建一个线程。该函数的前两个参数分别是线程主函数指针和函数的参数,而后面的参数通过变参数的方式为线程命名。

#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中有另外一个接口可以直接创建一个处于运行状态的线程,其定义如下。这里其实就是调用了上文描述的两个函数。

#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;                                                               \
})

停止线程
线程也可以被停止,此时主函数将会退出,当然需要主函数的实现考虑该问题。如下是停止线程的函数接口。

int kthread_stop(struct task_struct *k) 

线程的调度
内核线程创建完成后将一直运行下去,除非遇到了阻塞事件或者自己将自己调度出去。通过下面函数,线程可以将自己调度出去。调度出去的含义就是将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部分的内容:

  • 时间记账
  • 进程选择
  • 调度器入口
  • 睡眠和唤醒
    时间记账用于记录进程运行的虚拟时间,而进程选择则是根据策略选择应该将那个进程调度到CPU上运行。进程选择使用的数据结构是红黑树,红黑树是一个自平衡二叉树,也就是其中的数据是有序的,这样可以很容易的找到目的数据。Linux内核在具体实现的时候又使用了一个技巧,也就是将下一个要调度的进程放入缓存中,这样就可以直接找到该进程进行调度,降低了检索时间。
    Linux内核的调度入口是schedule函数,当线程调用该函数时将触发线程调度。这个函数实现本身很简单,但其内部调用context_switch函数实现真正的调度,在调用该函数之前会通过调度类获取目的进程。
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最终会调度到一个平台相关的函数,而这个函数是汇编语言实现的,主要实现寄存器和堆栈的处理,并最终完成进程的切换。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容