Skip to content

Latest commit

 

History

History
66 lines (45 loc) · 4.24 KB

2014-04-10-the-kernel-thread.md

File metadata and controls

66 lines (45 loc) · 4.24 KB
layout title category description tags
post
内核线程
进程
内核线程...
内核线程 idel swapper init 守护进程

内核线程(kernel thread)是直接由内核本身启动的进程,内核线程实际上是将内核函数委托给独立的进程,与系统中其他进程『并行』执行。内核线程经常被称之为守护进程。它们用于执行下列任务:

  1. 周期性地将修改的内存页与页面来源块设备同步。
  2. 如果内存页很少使用,则写入交换区。
  3. 管理延时动作。
  4. 实现文件系统的事物日志。

基本上,有两种类型的内核线程:

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

这些任务包括刷新磁盘高速缓存,交换出不用的页框,维护网络连接等等。实际上,如果以严格的线性方式执行这些任务效率不高,如果把它们放在后台调度,则会有较好的效率。这些任务委托给内核线程,内核线程不受不必要的用户态上下文的拖累,内核线程和普通进程的区别有:

  1. 内核线程只运行在内核态,而普通进程既可以运行在内核态,也可以运行在用户态。
  2. 因为内核线程只运行在内核态,它们只使用大于PAGE_OFFSET的线性地址空间。普通进程可以使用4GB的线性地址空间。

调用kernel_thread函数可以启动一个内核线程。

<asm-arch/processor.h>

{% highlight c++ %} int kernel_thread(int (*fn)(void *), void * arg, unsigned long flags) {% endhighlight %}

产生的线程将执行fn指针传递的函数,以便内核线程可以根据函数的不同而执行不同的函数,而用arg指定的参数将自动化传递给该函数。

大多数计算机上的系统的全部虚拟地址空间分成两部分:地步可以由用户层程序访问,上部则专供内核使用。在内核代表用户层运行时,虚拟地址空间的用户空间部分有mm指向mm_struct实例描述。每当内核执行上下文切换时,虚拟地址空间的用户层部分都会切换,以便与当前运行的进程匹配。

这为优化提供了一些方法,可遵循所谓的惰性TLB处理。由于内核线程不与任何特定的用户层进程相关,内核不需要倒换虚拟地址空间的用户层部分。由于内核线程之前可能有其他用户层进程在执行,因此用户空间部分的内存本质上时随机的。内核线程绝不能修改其内容。

为强调用户空间部分不能访问,mm设置为空指针。但由于内核必须知道用户空间当前包含了什么,所以在active_mm中保存了指向mm_struct的一个指针来描述它。

内核线程可以用两种方法实现。第一种是将一个函数直接传递给kernel_thread,这个函数接下来负责帮助内核调用daemonize以转换为守护进程。将触发下列操作。

  1. 该函数从内核线程释放其父进程的所有资源,因为守护进程只操作内核地址区域,不需要这些资源。
  2. daemonize阻塞信号的接受。
  3. 将init用作守护进程的父进程。

kthread_create帮助创建内核线程。

<kernel/kthread.c>

{% highlight c++ %} struct task_struct *kthread_create(int (*threadfn)(void *data), void *data, const char namefmt[], ) {% endhighlight %}

另一个被选方案是使用宏指令kthread_run,参数与thread_create相同。它会调用kthread_create创建新线程,但是立即唤醒它。

进程0

所有进程的祖先叫做进程0,idle进程,因为历史原因也可叫做swapper进程,它是在Linux的初始化阶段从无到有创建的一个内核线程。

进程1

由进程0创建的内核线程执行init()函数,init()依次完成内核初始化。init()调用execve()系统调用转入可执行程序initinit内核线程变为一个普通进程,且拥有自己的每进程内核数据结构。在系统关闭之前,init进程一直存活,因为它创建和监控在操作系统外层执行的所有进程的活动。