内核线程作为内核的“多线程”实现,在设计上没有 mm
等用户程序资源;它并不像内核进程进入内核态转变成的内核进程,在退出内核后还可以变成用户进程。从逻辑上看,内核线程是没有办法转变成用户进程的。
但是这种说法并不是绝对的,比如说著名的 1 号 init
进程,它本身是 0 号进程通过 kernel_thread
复制得到的,因为 0 号进程是一个内核线程,所以 1 号进程最开始也是一个内核线程。但是却在执行中通过 kernel_execve
变成了用户进程,执行像 /sbin/init, systemd
这样的用户程序,同时也成为了基本所有的用户进程的祖先。
kernel_execve
这个函数会调用 sys_exec
所调用的底层程序加载函数,进而获得各种用户资源,也就是说, kernel_execve
具有让内核线程变成用户进程的能力。
有趣的是, kernel_execve
这个函数不止被 init
进程调用了,它同时还被 kernel/umh.c
文件中的函数调用过,这个 umh
就是 User-mode Helper 的缩写,它可以让内核线程变成一个用户进程,甚至可以使用 Pipe 与内核通信。这是一种将部分操作从内核移至用户空间的机制。它提供了一种将复杂的逻辑和资源密集型的处理从内核解耦的方式。通过将这些操作放在用户空间中,可以提高系统的可靠性、安全性和维护性。
Linux Kernel 是一个 Monolithic Kernel,内核线程的隔离性远不如用户进程,在用户空间执行的程序能够受到更严格的隔离和访问控制,同时用户进程也更加好调试,降低了维护的代价。这种模式可以看作既 Kernel Module 之后又一次借鉴了 Microkernel的优点。
除了这些优势外,有的时候是迫不得已,硬件厂商提供的驱动或者驱动补丁有的时候是闭源或者强制用户态的,而 Kernel 作为宏内核,驱动本身应该处于内核态,这就形成了冲突,此时就不得不使用 User-mode Helper 了。
具体的实例可以看这篇关于 bpfilter 的文章,它利用 User-mode Helper 来兼容之前用户程序 netfilter 的代码。不过在这篇文章中指出了 User-mode Helper 使用的人很少。就我个人体验而言,在 Kernel V5.15.36 的启动流程中是存在一些驱动程序利用了这个框架,因为没有注意到这点,导致了我 de 了 3 天 bug 。