深入Linux内核架构-进程管理和调度(六)

本文深入探讨了内核线程的概念,包括它们如何由内核启动并在管态下执行,以及与用户空间进程的区别。详细介绍了内核线程的两种类型,启动方式,以及在系统中的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、内核线程

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

周期性地将修改的内存页与页来源块设备同步(例如,使用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, &regs, 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的编号在斜线后给出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值