内核线程是直接由内核来启动的进程,通常也称为守护进程,用于执行下列任务
- 内存页很少使用时,换出。
- 管理延时的动作。
- 实现文件系统的事务日志。
- 周期性的将修改的内存页与页来源块设备同步。
- 其它。
内核线程按照工作方式可以分为两种类型:
- 线程启动后一直在等待,直到内核请求线程执行某一特定操作。
- 线程启动后按周期性间隔运行,检测特定资源的使用等操作,在必要时采取一些行动。
一,内核线程创建
内核线程采用kernel_thread函数创建,接收的参数有:线程执行的函数指针,传递给执行函数的参数,以及用于创建进程的标志。
int kernel_thread(int (*fn)(void *), void * arg, unsigned long flags)
{
struct pt_regs regs;
memset(®s, 0, sizeof(regs));
regs.ebx = (unsigned long) fn;
regs.edx = (unsigned long) arg;
regs.xds = __USER_DS;
regs.xes = __USER_DS;
regs.xfs = __KERNEL_PERCPU;
regs.orig_eax = -1;
regs.eip = (unsigned long) kernel_thread_helper;
regs.xcs = __KERNEL_CS | get_kernel_rpl();
regs.eflags = X86_EFLAGS_IF | X86_EFLAGS_SF | X86_EFLAGS_PF | 0x2;
/* Ok, create the new process.. */
return do_fork(flags | CLONE_VM | CLONE_UNTRACED, 0, ®s, 0, NULL, NULL);
}
EXPORT_SYMBOL(kernel_thread);
这个函数最后的调用,还是创建一个进程,即调用我们熟知的:do_fork(flags | CLONE_VM | CLONE_UNTRACED, 0, ®s, 0, NULL, NULL);
CLONE_VM:表示不用复制进程的页表,共享地址空间,其实进程所使用的内存空间是随机的,一般借用上一个被调度的进程内存空间,在执行完调度后再”归还“。
其实内核线程是由内核生成的,并且也是在CPU的管态执行。
CLONE_UNTRACED:表示内核不会被其它进程追踪。
三,寄存器参数
pt_regs参数在本质上就是一些寄存器,这个参数设置完成以下一些工作。
- 把ebx和edx寄存器设置为fn和arg,为什么要这样?这和下面的设置相关,先往下看。
- 把eip寄存器设置为kernel_thread_helper。在体系结构相关的entry_32.S 文件中定义的:
为样就明白为什么把ebx和edx设置为fn和arg了。在调用call *%ebx之后,就会开始执行fn(arg)这个函数。直到结束,并返回。ENTRY(kernel_thread_helper) pushl $0 # fake return address for unwinder CFI_STARTPROC movl %edx,%eax push %edx CFI_ADJUST_CFA_OFFSET 4 call *%ebx push %eax CFI_ADJUST_CFA_OFFSET 4 call do_exit CFI_ENDPROC ENDPROC(kernel_thread_helper)
四,内核线程的地址空间
在Linux上,地址空间被分成两个部分,这已经不是秘密了,并把它分割成两块。空间底部的部分由用户程序访问,空间上层的部分由内核使用。内核在调度用户进程时,其task_struct指向一个mm_struct的实例,就是mm,以描述虚拟地址空间。在进程切换时,虚拟地址空间也会切换。
但内核线程不是用户进程,也不与用户进程相关联,所以在内核进程调度时,不要需切换其地址空间,因此内核线程的地址空间是随机的,为使得内核线程不访问用户空间部分,将内核线程的mm置为空指针,另设一个active_mm指针来保存前一个进程的mm_struct实例。
但当内核调度完成时,需要将active_mm也断开与上一个进程的联系。