趣文网 > 作文大全

Linux和Windows两种风格的操作系统 创建线程的方式有何不同?

2020-12-02 09:55:01
相关推荐

上一节从C语言源代码层面较为详细的讨论了Linux创建进程的过程,其实就是创建进程运行所需的内存空间,填充描述进程的 task_struct 结构体,以及加载进程的程序而已。

Linux是如何创建线程的呢?

Linux 内核并无专门创建线程的机制

我们之前提到,Linux并不特殊对待线程,在Linux看来,线程不过就是一种特殊的进程而已。那么,Linux是如何创建线程的呢?

线程机制是大多数现代编程语言都会提供的机制,该机制允许在同一进程的共享内存地址空间运行一组“特殊的进程(即线程)”。这些线程不仅共享同一段内存空间,还可以共享已经打开的文件,统计量等其他资源。线程机制支持程序并发运行,在多处理器核心的系统上,该并发机制能够实现多条线程同时运行。

Linux 管理线程的方式不同于其他一些经典操作系统,Linux 并没有线程的概念,它把线程当作进程的一个子集来管理。因此,Linux 内核并未为线程提供额外调度算法,也没有提供额外的数据结构用于描述和存储线程。

Linux 并没有线程的概念

就像进程一样,Linux 使用 task_struct 结构体描述和记录线程,每个线程都有唯一属于自己的 task_struct 结构。从这个角度来看,线程就是一个普通的进程,只不过线程可能和其他进程共享一些资源而已。

以 Windows 为代表的一些操作系统提供了专门用于创建线程的机制,在这些系统中,线程常常被称作“轻量级进程”,因为相对于进程而言,线程耗费的资源较少,能够较为迅速的创建和投入运行。

但是对于 Linux 而言,线程不过是进程之间共享资源的一种手段罢了。那么是不是 Linux 中的线程比 Windows 中的线程更加“重量级”呢?也不是,因为 Linux 中的进程本身就很轻量级,Linux 创建进程所需时间,并不比 Windows 创建线程所需时间多多少。

从C语言代码层面来看,假设某个进程包含 4 个线程,以 Windows 为代表的一些操作系统一般会有一个包含指向 4 个不同线程的指针的进程描述符,负责描述地址空间、打开的文件等共享资源,而线程本身再去描述自己独占的资源。

Linux 的做法很高雅

与之对应的,Linux 的做法很高雅,它仅需为这 4 个线程创建 4 个 task_struct 结构体,然后在 task_struct 中指定它们共享的资源就可以了。

创建线程

看了我最近几篇文章的读者应该已经明白,Linux 内核中的线程其实就是进程,因此线程的创建与进程的创建过程是类似的,从C语言源代码层面看,基本上也是通过 fork() 函数和 exec() 函数族实现的。只不过在调用 clone() 函数时需要传递一个参数用于描述共享资源,例如:

clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0);上面这行C语言代码和调用 fork() 函数的结果差不多,只不过输入的几个参数标志位说明了子进程与父进程共享一些资源:地址空间、文件系统、打开的文件、信号处理程序。

Linux 内核线程

对比一下,fork() 基本上就相当于 clone(SIGCHLD, 0),这也是 fork() 函数创建的子进程之后不再与父进程共享资源的原因。

关于 clone() 函数的参数标志位,可以在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语言代码如下,请看:

C语言代码

还是比较清晰的,这里就不再赘述了。至此,我们就了解了Linux内核是如何创建线程并投入运行,以及如何结束内核线程的了。

小结

本节主要讨论了 Linux 内核中的线程的创建,应该能够看出,其实核心还是围绕对 task_struct 结构的管理,这与管理进程并无过多区别。因此,说Linux中的线程只是一种特殊的进程,一点也不为过。

点个赞再走吧

欢迎在评论区一起讨论,质疑。文章都是手打原创,每天最浅显的介绍C语言、linux等嵌入式开发,喜欢我的文章就关注一波吧,可以看到最新更新和之前的文章哦(最近发现有些帐号搬运我的文章并且标上原创标签,有些可耻,请注明出处!!)。

阅读剩余内容
网友评论
相关内容
小编推荐

大家都在看

严格要求自己 作文 赞扬中国的作文 描写晚秋的作文 朋友的事作文 怎么教孩子写作文 职业选择作文 一分耕耘一份收获作文 渴望为题的作文600字 打女朋友的屁股作文 关于鸟类的作文 高考英语作文题目大全 雨的作文开头 我最好的朋友300字作文 初中优秀叙事作文 人与自然800字作文 作文美丽的校园四年级 善于发现身边的美作文 暑假趣事作文提纲 今天真冷啊 作文 我的班主任英语作文 作文乐在其中600字 英语口头作文 种土豆的作文 高二开学作文800字 二年级关于植物的作文 love作文 从今天开始作文600字 暑假记事英语作文 关于端午的作文开头 成人大专考试作文