当前位置:   article > 正文

一文搞懂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 停止内核线程

 

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
推荐阅读
相关标签
  

闽ICP备14008679号