• 一文搞懂Linux内核之内核线程


    1.内核线程概述

    Linux内核可以看作服务进程(管理软硬件资源,响应用户进程的各种进程)。

    内核需要多个执行流并行,为了防止可能的阻塞,支持多线程。

    内核线程就是内核的一个分身,可以用以处理一件特定事情,内核线程的调度由内核负责,一个内核线程的处于阻塞状态时不影响其他的内核线程。

    内核线程是直接由内核本身启动的进程。内核线程实际上是将内核函数委托给独立的进程执行,它与内核中的其他“进程”并行执行。内核线程经常被称之为内核守护进程。当前的内核中,内核线程就负责下面的工作:

    • 周期性地将修改的内存页与页来源块设备同步
    • 如果内存页很少使用,则写入交换区
    • 管理延时动作, 如2号进程接手内核进程的创建
    • 实现文件系统的事务日志

    内核线程主要有两种类型

    1. 线程启动后一直等待,直至内核请求线程执行某一特定操作。
    2. 线程启动后按周期性间隔运行,检测特定资源的使用,在用量超出或低于预置的限制时采取行动。

    内核线程由内核自身生成,其特点在于

    1. 它们在CPU的管态执行,而不是用户态。
    2. 它们只可以访问虚拟地址空间的内核部分(高于TASK_SIZE的所有地址),但不能访问用户空间

    在linux所有的线程都当作进程来实现,也没有单独为线程定义调度算法以及数据结构,一个进程相当于包含一个线程,就是自身,多线程,原本的线程称为主线程,他们一起构成线程组。

    进程拥有自己的地址空间,所以每个进程都有自己的页表,而线程却没有,只能和其它线程共享主线程的地址空间和页表

    2.三个数据结构

    每个进程或线程由三个重要的数据结构,分别是struct thread_info, struct task_struct 和内核栈。

    thread_info对象存放的进程/线程的基本信息,它和进程/线程的内核栈存放在内核空间里的一段2倍页长空间中。其中thread_info结构存放在地址段的末尾,其余空间作为内核栈。内核使用伙伴系统分配这段空间。

    1. struct thread_info {
    2. int preempt_count; /* 0 => preemptable, <0 => bug */
    3. struct task_struct *task; /* main task structure */
    4. __u32 cpu; /* cpu */
    5. };

    thread_info结构体中有一个struct task_struct *task,task指向该线程或者进程的task_struct对象,task_struct也叫做任务描述符:

    1. struct task_struct {
    2. pid_t pid;
    3. pid_t tgid;
    4. void *stack;
    5. struct mm_struct *mm, *active_mm;
    6. /* filesystem information */
    7. struct fs_struct *fs;
    8. /* open file information */
    9. struct files_struct *files;
    10. };
    11. #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内核技术交流群: 【977878001】整理一些个人觉得比较好得学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!前100进群领取,额外赠送一份 价值699的内核资料包(含视频教程、电子书、实战项目及代码)

      内核资料直通车:Linux内核源码技术学习路线+视频教程代码资料

      学习直通车:Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈

    linux系统上虚拟地址空间分为两个部分:供用户态程序访问的虚拟地址空间和供内核访问的内核空间。每当内核执行上下文切换时,虚拟地址空间的用户层部分都会切换,以便匹配运行的进程,内核空间的部分是不会切换的。

    3.内核线程创建

    在内核版本linux-3.x以后,内核线程的创建被延后执行,并且交给名为kthreadd 2号线程执行创建过程,但是kthreadd本身是怎么创建的呢?过程如下:

    1. pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)
    2. {
    3. return do_fork(flags|CLONE_VM|CLONE_UNTRACED, (unsigned long)fn,
    4. (unsigned long)arg, NULL, NULL);
    5. }
    6. pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);

    kthreadadd本身最终是通过do_fork实现的,do_fork通过传入不同的参数,可以分别用于创建用户态进程/线程,内核线程等。当kthreadadd被创建以后,内核线程的创建交给它实现。

    内核线程的创建分为创建和启动两个部分,kthread_run作为统一的接口,可以同时实现,这两个功能:

    1. #define kthread_run(threadfn, data, namefmt, ...) \
    2. ({ \
    3. struct task_struct *__k \
    4. = kthread_create(threadfn, data, namefmt, ## __VA_ARGS__); \
    5. if (!IS_ERR(__k)) \
    6. wake_up_process(__k); \
    7. __k; \
    8. })
    9. #define kthread_create(threadfn, data, namefmt, arg...) \
    10. kthread_create_on_node(threadfn, data, -1, namefmt, ##arg)
    11. struct task_struct *kthread_create_on_node(int (*threadfn)(void *data),
    12. void *data, int node,
    13. const char namefmt[],
    14. ...)
    15. {
    16. DECLARE_COMPLETION_ONSTACK(done);
    17. struct task_struct *task;
    18. /*分配kthread_create_info空间*/
    19. struct kthread_create_info *create = kmalloc(sizeof(*create),
    20. GFP_KERNEL);
    21. if (!create)
    22. return ERR_PTR(-ENOMEM);
    23. create->threadfn = threadfn;
    24. create->data = data;
    25. create->node = node;
    26. create->done = &done;
    27. /*加入到kthread_creta_list列表中,等待ktherad_add中断线程去创建改线程*/
    28. spin_lock(&kthread_create_lock);
    29. list_add_tail(&create->list, &kthread_create_list);
    30. spin_unlock(&kthread_create_lock);
    31. wake_up_process(kthreadd_task);
    32. /*
    33. * Wait for completion in killable state, for I might be chosen by
    34. * the OOM killer while kthreadd is trying to allocate memory for
    35. * new kernel thread.
    36. */
    37. if (unlikely(wait_for_completion_killable(&done))) {
    38. /*
    39. * If I was SIGKILLed before kthreadd (or new kernel thread)
    40. * calls complete(), leave the cleanup of this structure to
    41. * that thread.
    42. */
    43. if (xchg(&create->done, NULL))
    44. return ERR_PTR(-EINTR);
    45. /*
    46. * kthreadd (or new kernel thread) will call complete()
    47. * shortly.
    48. */
    49. wait_for_completion(&done);
    50. }
    51. task = create->result;
    52. .
    53. .
    54. .
    55. kfree(create);
    56. return task;
    57. }

    kthread_create_on_node函数中:

    • 首先利用kmalloc分配kthread_create_info变量create,利用函数参数初始化create
    • 将create加入kthread_create_list链表中,然后唤醒kthreadd内核线程创建当前线程
    • 唤醒kthreadd后,利用completion等待内核线程创建完成,completion完成后,释放create空间

    下面来看下kthreadd的处理过程:

    1. int kthreadd(void *unused)
    2. {
    3. struct task_struct *tsk = current;
    4. /* Setup a clean context for our children to inherit. */
    5. set_task_comm(tsk, "kthreadd");
    6. ignore_signals(tsk);
    7. set_cpus_allowed_ptr(tsk, cpu_all_mask);
    8. set_mems_allowed(node_states[N_MEMORY]);
    9. current->flags |= PF_NOFREEZE;
    10. for (;;) {
    11. set_current_state(TASK_INTERRUPTIBLE);
    12. if (list_empty(&kthread_create_list))
    13. schedule();
    14. __set_current_state(TASK_RUNNING);
    15. spin_lock(&kthread_create_lock);
    16. while (!list_empty(&kthread_create_list)) {
    17. struct kthread_create_info *create;
    18. create = list_entry(kthread_create_list.next,
    19. struct kthread_create_info, list);
    20. list_del_init(&create->list);
    21. spin_unlock(&kthread_create_lock);
    22. create_kthread(create);
    23. spin_lock(&kthread_create_lock);
    24. }
    25. spin_unlock(&kthread_create_lock);
    26. }
    27. return 0;
    28. }

    kthreadd利用for(;;)一直驻留在内存中运行:主要过程如下:

    • 检查kthread_create_list为空时,kthreadd让出cpu的执行权
    • kthread_create_list不为空时,利用while循环遍历kthread_create_list链表
    • 每取下一个链表节点后调用create_kthread,创建内核线程
    1. static void create_kthread(struct kthread_create_info *create)
    2. {
    3. int pid;
    4. /* We want our own signal handler (we take no signals by default). */
    5. pid = kernel_thread(kthread, create, CLONE_FS | CLONE_FILES | SIGCHLD);
    6. if (pid < 0) {
    7. /* If user was SIGKILLed, I release the structure. */
    8. struct completion *done = xchg(&create->done, NULL);
    9. if (!done) {
    10. kfree(create);
    11. return;
    12. }
    13. create->result = ERR_PTR(pid);
    14. complete(done);
    15. }
    16. }

    可以看到内核线程的创建最终还是和kthreadd一样,调用kernel_thread实现。

    1. static int kthread(void *_create)
    2. {
    3. .
    4. .
    5. .
    6. .
    7. /* If user was SIGKILLed, I release the structure. */
    8. done = xchg(&create->done, NULL);
    9. if (!done) {
    10. kfree(create);
    11. do_exit(-EINTR);
    12. }
    13. /* OK, tell user we're spawned, wait for stop or wakeup */
    14. __set_current_state(TASK_UNINTERRUPTIBLE);
    15. create->result = current;
    16. complete(done);
    17. schedule();
    18. ret = -EINTR;
    19. if (!test_bit(KTHREAD_SHOULD_STOP, &self.flags)) {
    20. __kthread_parkme(&self);
    21. ret = threadfn(data);
    22. }
    23. /* we can't just return, we must preserve "self" on stack */
    24. do_exit(ret);
    25. }

    kthread以struct kthread_create_info 类型的create为参数,create中带有创建内核线程的回调函数,以及函数的参数。kthread中,完成completion信号量的处理,然后schedule让出cpu的执行权,等待下次返回 时,执行回调函数threadfn(data)。

    4.内核线程的退出

    线程一旦启动起来后,会一直运行,除非该线程主动调用do_exit函数,或者其他的进程调用kthread_stop函数,结束线程的运行。

    1. int kthread_stop(struct task_struct *k)
    2. {
    3. struct kthread *kthread;
    4. int ret;
    5. trace_sched_kthread_stop(k);
    6. get_task_struct(k);
    7. kthread = to_live_kthread(k);
    8. if (kthread) {
    9. set_bit(KTHREAD_SHOULD_STOP, &kthread->flags);
    10. __kthread_unpark(k, kthread);
    11. wake_up_process(k);
    12. wait_for_completion(&kthread->exited);
    13. }
    14. ret = k->exit_code;
    15. put_task_struct(k);
    16. trace_sched_kthread_stop_ret(ret);
    17. return ret;
    18. }

    如果线程函数正在处理一个非常重要的任务,它不会被中断的。当然如果线程函数永远不返回并且不检查信号,它将永远都不会停止。在执行kthread_stop的时候,目标线程必须没有退出,否则会Oops。所以在创建thread_func时,可以采用以下形式:

    1. thread_func()
    2. {
    3. // do your work here
    4. // wait to exit
    5. while(!thread_could_stop())
    6. {
    7. wait();
    8. }
    9. }
    10. exit_code()
    11. {
    12. kthread_stop(_task); //发信号给task,通知其可以退出了
    13. }

    如果线程中在等待某个条件满足才能继续运行,所以只有满足了条件以后,才能调用kthread_stop杀掉内核线程。

    5.内核线程使用

    1. #include "test_kthread.h"
    2. #include <linux/delay.h>
    3. #include <linux/timer.h>
    4. #include <linux/platform_device.h>
    5. #include <linux/fs.h>
    6. #include <linux/module.h>
    7. static struct task_struct *test_thread = NULL;
    8. unsigned int time_conut = 5;
    9. int test_thread_fun(void *data)
    10. {
    11. int times = 0;
    12. while(!kthread_should_stop())
    13. {
    14. printk("\n printk %u\r\n", times);
    15. times++;
    16. msleep_interruptible(time_conut*1000);
    17. }
    18. printk("\n test_thread_fun exit success\r\n\n");
    19. return 0;
    20. }
    21. void register_test_thread(void)
    22. {
    23. test_thread = kthread_run(test_thread_fun , NULL, "test_kthread" );
    24. if (IS_ERR(test_thread)){
    25. printk(KERN_INFO "create test_thread failed!\n");
    26. }
    27. else {
    28. printk(KERN_INFO "create test_thread ok!\n");
    29. }
    30. }
    31. static ssize_t kthread_debug_start(struct device *dev, struct device_attribute *attr, char *buf)
    32. {
    33. register_test_thread();
    34. return 0;
    35. }
    36. static ssize_t kthread_debug_stop(struct device *dev, struct device_attribute *attr, char *buf)
    37. {
    38. kthread_stop(test_thread);
    39. return 0;
    40. }
    41. static DEVICE_ATTR(kthread_start, S_IRUSR, kthread_debug_start,NULL);
    42. static DEVICE_ATTR(kthread_stop, S_IRUSR, kthread_debug_stop,NULL);
    43. struct attribute * kthread_group_info_attrs[] =
    44. {
    45. &dev_attr_kthread_start.attr,
    46. &dev_attr_kthread_stop.attr,
    47. NULL,
    48. };
    49. struct attribute_group kthread_group =
    50. {
    51. .name = "kthread",
    52. .attrs = kthread_group_info_attrs,
    53. };
    cat kthread_start 启动内核线程
    cat kthread_stop 停止内核线程

     

  • 相关阅读:
    关联容器(字典)map
    jni-02、lib路径、数组、对象、引用、extern修饰函数
    02 【常用类型(上)】
    Spring MVC应该怎么学?这份教程带你快速入门,深入剖析源码!
    selenium爬取图片
    Maven 如何打包可运行jar包
    【java】Java中的异步实现方式
    从一次性销售到持续收益:低代码服务商的转型之路
    Ubuntu: 系统使用, 系统源更新, Vi基本操作, 磁盘拓展
    [附源码]java毕业设计基于Vue智能化许愿墙
  • 原文地址:https://blog.csdn.net/m0_74282605/article/details/127864928