上一节从C语言的源代码层面详细讨论了在Linux中创建进程的过程。其实就是创建进程运行所需的内存空间,填充描述进程的task_struct结构,加载进程的程序。Linux内核没有创建线程的特殊机制。正如我们前面提到的,Linux并没有特殊对待线程。从Linux的角度来看,线程只是一个特殊的进程。那么,Linux是如何创建线程的呢?线程机制是大多数现代编程语言都提供的一种机制,它允许一组“特殊进程(即线程)”运行在同一进程的共享内存地址空间中。这些线程不仅共享相同的内存空间,还共享打开的文件、统计信息和其他资源。线程机制支持程序并发运行。在多处理器核的系统上,这种并发机制可以实现多个线程同时运行。Linux管理线程的方式不同于其他一些经典操作系统。Linux没有线程的概念。它将线程作为进程的一个子集来管理。因此,Linux内核并没有为线程提供额外的调度算法,也没有提供额外的数据结构来描述和存储线程。就像进程一样,Linux使用一个task_struct结构来描述和记录线程,每个线程都有自己独特的task_struct结构。从这个角度来看,线程就是一个普通的进程,只不过线程可能会与其他进程共享一些资源。以Windows为代表的一些操作系统提供了创建线程的机制。在这些系统中,线程通常被称为“轻量级进程”,因为线程消耗的资源比进程少,并且可以更快地创建和投入运行。但是对于Linux来说,线程只是进程间共享资源的一种手段。那么Linux中的线程是不是比Windows中的线程更“重量级”呢?不会,因为进程本身在Linux中是非常轻量级的,Linux创建一个进程所需要的时间不会比Windows创建一个线程所需要的时间多多少。从C语言代码来看,假设一个进程包含4个线程,以Windows为代表的一些操作系统一般都有一个包含指向4个不同线程的指针的进程描述符,负责描述地址空间和打开的文件。等待共享资源,线程自己描述自己的独享资源。相应的,Linux的做法也很优雅,只需要为这4个线程创建4个task_struct结构,然后在task_struct中指定它们共享的资源即可。创建线程看过我最近几篇文章的读者应该已经明白,Linux内核中的线程其实就是进程,所以线程的创建和创建进程的过程类似。从C语言源码来看,基本上是通过fork()函数和exec()函数族。只是在调用clone()函数时,需要传递一个参数来描述共享资源,例如:clone(CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND,0);上面这行C语言代码类似于调用fork()函数的结果。只是入参flags表示子进程与父进程共享一些资源:地址空间、文件系统、打开的文件和信号处理程序。相比之下,fork()基本上等同于clone(SIGCHLD,0),这就是为什么fork()函数创建的子进程不再与父进程共享资源的原因。对于clone()函数的参数flags,可以在Linux下输入man命令查看。Linux内核线程就像用户空间的C语言程序开发一样。Linux内核经常需要在后台处理数据。这时候就需要内核线程了。Linux内核线程一般没有独立的地址空间,只运行在内核空间,不会切换到用户空间。但是调度和普通进程一样,可以被调度和抢占。Linux中内核线程的创建是通过kthread_create()函数实现的。其C语言源代码如下。请看:kthread_create()函数的C语言源码可以看。kthread_create()函数的C语言代码并不长,也可以看出,Linux内核线程是通过kthread_create_info结构体来描述的。其定义C语言代码如下。可以看出,内核线程的描述和存储也包含了task_struct结构:包含task_struct结构的kthread_create()函数创建了一个名为namefmt的线程。但是,线程创建后,处于非运行状态,我们可以通过wake_up_process()函数将其唤醒。当然这个过程也可以通过kthread_run()方法来实现。相关C语言代码如下,请看:相关C语言代码其实是kthread_create()函数和wake_up_process()函数的组合。Linux内核线程启动后,会一直运行到调用do_exit()退出。我们也可以调用kthread_stop()函数提前结束。相关C语言代码如下,请看:kthread_stop()函数kthread_stop()函数接收的参数是kthread_create()函数创建的结构体的task_struct成员。从C语言代码可以看出,kthread_stop()实际上调用了wake_up_process()函数来唤醒线程。唤醒线程后,等待线程函数退出,不调用threadfn()函数。这里要注意,如果创建的线程函数threadfn()调用了do_exit()函数,最好不要调用kthread_stop()函数。kthread_stop()函数通过wait_for_completion()函数等待线程退出。相关的C语言代码如下,请看:wait_for_completion()函数稍微追溯一下C语言代码,发现这个等待过程其实是由do_wait_for_common()函数实现的,它的C语言代码如下,请看:do_wait_for_common()函数比较清楚,这里不再赘述。至此,我们了解了Linux内核如何创建线程并投入运行,以及如何结束内核线程。小结本节主要讨论Linux内核中线程的创建。应该看到,核心其实就是task_struct结构体的管理,和管理流程没有太大区别。因此,说Linux中的线程只是一种特殊的进程也不为过。
