一、内核线程
内核线程是直接由内核本身启动的进程。内核线程实际上是将内核函数委托给独立的进程,与系统中其他进程并行执行(实际上,也并行于内核自身的执行)。内核线程称之为(内核) 守护进程。它们用于执行下列任务。
周期性地将修改的内存页与页来源块设备同步(例如,使用mmap的文件映射)。
如果内存页很少使用,则写入交换区。
管理延时动作。
实现文件系统的事务日志。
基本上,有两种类型的内核线程。
类型1:线程启动后一直等待,直至内核请求线程执行某一特定操作。
类型2:线程启动后按周期性间隔运行,检测特定资源的使用,在用量超出或低于预置的限制值时采取行动。内核使用这类线程用于连续监测任务。
调用kernel_thread函数可启动一个内核线程。其定义是特定于体系结构的,但原型总是相同的。
<asm-arch/processor.h>
int kernel_thread(int (*fn)(void *), void *arg, unsigned long flags);
产生的线程将执行用fn指针传递的函数,而用arg指定的参数将自动传递给该函数。flags中可指定CLONE标志。
kernel_thread的第一个任务是构建一个pt_regs实例,对其中的寄存器指定适当的值,这与普通的fork系统调用类似。接下来调用熟悉的do_fork函数。
arch/arm/kernel/process.c
do_fork(flags|CLONE_VM|CLONE_UNTRACED, 0, ®s, 0, NULL, NULL);
因为内核线程是由内核自身生成的,应该注意下面两个特别之处。
(1) 它们在CPU的管态执行,而不是用户状态。
(2)它们只可以访问虚拟地址空间的内核部分(高于TASK_SIZE的所有地址),但不能访问用户空间。
task_struct中包含了指向mm_structs的两个指针:
<sched.h>
大多数计算机上系统的全部虚拟地址空间分成两个部分:底部可以由用户层程序访问,上部则专供内核使用。在内核代表用户层程序运行时(例如,执行系统调用),虚拟地址空间的用户空间部分由mm指向的mm_struct实例描述。每当内核执行上下文切换时,虚拟地址空间的用户层部分都会切换,以便与当前运行的进程匹配。
这为优化提供了一些余地,可遵循所谓的惰性TLB(理解为页表缓冲,地址变换高速缓存)处理。由于内核线程不与任何特定的用户层进程相关,内核并不需要倒换虚拟地址空间的用户层部分,保留旧设置即可。由于内核线程之前可能是任何用户层进程在执行,因此用户空间部分的内容本质上是随机的,内核线程绝不能修改其内容。为强调用户空间部分不能访问, mm设置为空指针。但由于内核必须知道用户空间当前包含了什么,所以在active_mm中保存了指向mm_struct的一个指针来描述它。
为什么没有mm指针的进程称作惰性TLB进程?假如内核线程之后运行的进程与之前是同一个。在这种情况下,内核并不需要修改用户空间地址表,地址转换后备缓冲器(即TLB)中的信息仍然有效。只有在内核线程之后执行的进程是与此前不同的用户层进程时,才需要切换(并对应清除TLB数据)。
请注意,当内核在进程上下文下运转时,mm和active_mm的值相同。
内核线程可以用两种方法实现。古老的方法:内核中一些地方仍然在使用该方法,将一个函数直接传递给kernel_thread。该函数接下来负责帮助内核调用daemonize以转换为守护进程。这依次引发下列操作:
(1) 该函数从内核线程释放其父进程(用户进程)的所有资源(例如,内存上下文、文件描述符,等等),不然这些资源会一直锁定到线程结束,这是不可取的,因为守护进程通常运行到系统关机为止。因为守护进程只操作内核地址区域,它甚至不需要这些资源。
(2) daemonize阻塞信号的接收。
(3) 将init用作守护进程的父进程。
创建内核线程更现代的方法是辅助函数kthread_create。
kernel/kthread.c
struct task_struct *kthread_create(int (*threadfn)(void *data), void *data, const char namefmt[], ...)
该函数创建一个新的内核线程,其名称由namefmt给出。最初该线程是停止的,需要使用wake_up_process启动它。此后,会调用通过threadfn给出的线程函数,而data则作为参数。
另一个备选方案是宏kthread_run(参数与kthread_create相同),它会调用kthread_create创建新线程,但立即唤醒它。
还可以使用kthread_create_cpu代替kthread_create创建内核线程,使之绑定到特定的CPU。
内核线程会出现在系统进程列表中,但在ps的输出中由方括号包围,以便与普通进程区分。
如果内核线程绑定到特定的CPU, CPU的编号在斜线后给出。