Linux内核进程是否支持多线程?

8
对于Linux用户空间进程,确定哪些进程是多线程似乎很容易。您可以使用 ps -eLf 并查看 NLWP 值以获取线程数,该值还对应于 /proc/$pid/status 中的“ Threads:”值。显然,在LinuxThreads时代,实现不符合POSIX标准。但是此stackoverflow答案说“ POSIX.1要求线程共享相同的进程ID ”,这在NPTL中已经得到纠正。因此,使用NPTL允许使用像 ps -eLf 这样的命令漂亮地显示线程,因为所有线程都共享相同的PID,并且您可以在 /proc/$pid/task/ 下验证它,并查看所有属于该“父”进程的线程子文件夹。

我找不到类似的线程分组,用于由kthreadd生成的内核进程的“父”进程,我怀疑实现差异,因为此答案下的评论说“您无法在内核空间中使用POSIX线程”,而漂亮的线程分组是POSIX功能。因此,使用 ps -eLf ,我从未看到由kthreadd创建的内核进程的多个线程列出,它们具有方括号,例如[ksoftirqd / 0]或[nfsd],不像由init创建的用户空间进程。

来自用于用户空间的pthread的手册页面:

       A single process can contain multiple threads, all of which are
       executing the same program.  These threads share the same global
       memory (data and heap segments), but each thread has its own stack
       (automatic variables).

然而,我没有看到内核“线程”在一个包含多个线程的进程中。简而言之,我从未看到'ps'列出的任何kthreadd子进程具有比一个更高的NLWP(线程)值,这使我想知道任何内核进程是否像用户空间程序(使用pthread)那样分叉/并行和多线程。这些实现的区别在哪里?

实际例子:NFS进程的ps auxf输出。

root         2  0.0  0.0      0     0 ?        S    Jan12   0:00 [kthreadd]
root      1546  0.0  0.0      0     0 ?        S    Jan12   0:00  \_ [lockd]
root      1547  0.0  0.0      0     0 ?        S    Jan12   0:00  \_ [nfsd4]
root      1548  0.0  0.0      0     0 ?        S    Jan12   0:00  \_ [nfsd4_callbacks]
root      1549  0.0  0.0      0     0 ?        S    Jan12   2:40  \_ [nfsd]
root      1550  0.0  0.0      0     0 ?        S    Jan12   2:39  \_ [nfsd]
root      1551  0.0  0.0      0     0 ?        S    Jan12   2:40  \_ [nfsd]
root      1552  0.0  0.0      0     0 ?        S    Jan12   2:47  \_ [nfsd]
root      1553  0.0  0.0      0     0 ?        S    Jan12   2:34  \_ [nfsd]
root      1554  0.0  0.0      0     0 ?        S    Jan12   2:39  \_ [nfsd]
root      1555  0.0  0.0      0     0 ?        S    Jan12   2:57  \_ [nfsd]
root      1556  0.0  0.0      0     0 ?        S    Jan12   2:41  \_ [nfsd]

默认情况下,当您启动rpc.nfsd服务(至少使用init.d服务脚本)时,它会生成8个进程(或者至少它们有PIDs)。如果我想编写一个多线程版本的NFS,它作为一个内核模块实现,那么为什么不能将默认的8个不同的nfsd进程分组到一个PID下,并在其中运行8个线程,而不是(如所示 - 与用户空间进程不同)八个不同的PID?
相比之下,NSLCD是一个使用多线程的用户空间程序的例子:
UID        PID  PPID   LWP  C NLWP STIME TTY          TIME CMD
nslcd     1424     1  1424  0    6 Jan12 ?        00:00:00 /usr/sbin/nslcd
nslcd     1424     1  1425  0    6 Jan12 ?        00:00:28 /usr/sbin/nslcd
nslcd     1424     1  1426  0    6 Jan12 ?        00:00:28 /usr/sbin/nslcd
nslcd     1424     1  1427  0    6 Jan12 ?        00:00:27 /usr/sbin/nslcd
nslcd     1424     1  1428  0    6 Jan12 ?        00:00:28 /usr/sbin/nslcd
nslcd     1424     1  1429  0    6 Jan12 ?        00:00:28 /usr/sbin/nslcd

PID相同,但每个线程的LWP唯一。

kthreadd功能更新

这个stack overflow答案中得知:

kthreadd是在内核空间运行的守护线程。原因是内核有时需要创建线程,但在内核中创建线程非常棘手。因此,kthreadd是内核用于从那里生成新线程(如果需要)的线程。该线程也可以访问用户空间地址空间,但不应该这样做。 它由内核管理...

这个答案

kthreadd()是守护进程kthreadd的主要函数(和主循环),是所有其他内核线程的父进程。

因此,在引用的代码中,有一个对kthreadd守护程序的请求创建。为了满足此请求,kthreadd将读取它并启动内核线程。


3
多线程共享与地址空间类似的资源。内核线程仅存在于内核空间,并且所有内核线程自然地共享内核空间。因此,根本不需要内核多线程。 - Chris Tsui
我曾经读到过fork()和pthread_create()都使用clone(),但传递给它的位掩码设置略有不同 - 所以这很有道理。当你说内核空间任务“似乎分组”时,你能举个例子吗?这是Linux中可以通过检查特定内容来显示分组(在/proc或其他地方)吗?还是需要有人编写一个C程序来收集指示它们分组的相关数据?欢迎提供额外的阅读建议。谢谢。 - SeligkeitIstInGott
@SeligkeitIstInGott,您所说的“线程不仅共享内存”的意思是什么?它们还共享文件描述符,但内核线程没有文件描述符。当您问“如何跟踪”时,不清楚您在问什么。您认为需要跟踪的“you”是什么? - David Schwartz
1
@SeligkeitIstInGott,在Linux中,每个用户线程(无论是多线程进程中的线程还是单线程进程中的线程)都有一个任务(也称为进程或线程,完全没有关系)。内核线程也是任务。从进程调度的角度来看,内核线程和用户线程没有区别,因为它们都是基本调度单元的任务。因此,当您说并行运行时,内核线程与用户空间多线程相同。 - Chris Tsui
@SeligkeitIstInGott,我无法立即给您一个示例。当您调用clone时,您会为其设置标志以设置gid、pgid和其他一些内容。我想ps、top和其他类似的工具在某种程度上使用它们。我相信线程分组来自/proc/<pid>/task/<tid>结构。 - BitWhistler
显示剩余7条评论
1个回答

13

内核中没有进程的概念,所以你的问题并没有真正意义。Linux内核可以创建完全在内核上下文中运行的线程,但这些线程都在同一个地址空间中运行。没有通过PID将相似的线程进行分组,尽管相关的线程通常具有相关的名称。

如果多个内核线程在处理相同的任务或共享数据,则需要通过锁定或其他并发算法协调对该数据的访问。当然,在内核中不可用pthread API,但可以使用内核互斥、等待队列等来获得与pthread互斥、条件变量等相同的功能。

将这些执行上下文称为“内核线程”是一个合理的命名,因为它们与用户空间进程中的多个线程密切类比。它们都共享(内核的)地址空间,但具有自己的执行上下文(堆栈、程序计数器等),并且每个线程独立地被调度并并行运行。另一方面,内核实际上实现了所有美好的POSIX API抽象(在用户空间的C库的帮助下),所以在实现之内我们没有完整的抽象。


非常有帮助的答案。我的一个困难是术语,而说内核“任务”而不是“进程”似乎是正确的指定方式。感谢您理解我试图问什么,因为我有些困难知道如何构思问题,而您的第二段似乎准确描述了互斥、等待队列和锁定方面的内容。是否有任何印刷品或在线材料以介绍性的方式阐述您所说的内容,因为我在这个主题上很难找到任何简单明了的东西。 - SeligkeitIstInGott
嗯...在《Solaris Internals: Core Kernel Components》(第283页)中,“内核线程(kthread)”页面似乎涉及到了一些这方面的内容,但我仍然无法理解:http://tinyurl.com/jl7elu5。也许我只需要多读一些关于UNIX/Linux内核内部的资料。 - SeligkeitIstInGott
1
它们实际上没有PID。在Linux上,内核只有一个命名空间和进程ID、线程ID以及内核任务分配的ID都来自同一命名空间。各种工具以不同的方式区分来自同一命名空间的不同类型的ID。仅仅因为某些东西称其为PID,并不能假设它实际上是用户空间进程。它们可能只是指从与PID相同的命名空间分配的数字,并且如果这个内核调度实体是用户空间进程,则出现在PID所在的插槽中。 - David Schwartz
顺便说一句,如果有阅读资源可以避免我纠缠你问这些问题,我很乐意被推荐。 - SeligkeitIstInGott
1
init 是第一个具有用户空间的任务,它的所有子进程都是从它派生出来的(然后执行)。kthreadd 只是一个内核线程,用于简化创建其他内核线程,因为还有其他方法可以创建内核线程,例如 kernel_thread()。所有这些都是通过不同参数的 do_fork() 完成的。 - Chris Tsui
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接