上一节从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等嵌入式开发,喜欢我的文章就关注一波吧,可以看到最新更新和之前的文章哦(最近发现有些帐号搬运我的文章并且标上原创标签,有些可耻,请注明出处!!)。

举报/反馈

windows与linux线程,Linux和Windows两种风格的操作系统,创建线程的方式有何不同?...相关推荐

  1. java创建线程的两种方法是_java创建线程的两种方法

    要产生一个线程,有两种方法: ◆需要从Java.lang.Thread类派生一个新的线程类,重载它的run()方法: ◆实现Runnalbe接口,重载Runnalbe接口中的run()方法. 哪种方法 ...

  2. linux两种方式改变文件权限,Linux更改文件权限的两种方式

    今天小编要跟大家分享一篇关于Linux更改文件权限的方式,喜欢Linux的小伙伴来看一看吧. 我们知道·Linux系统最主要的特点之一就是--Linux系统是多用户.多任务的操作系统. 何为多用户?就 ...

  3. Linux修改主机名的两种方法

    Linux修改主机名的两种方法 文章目录 先展示一下效果 一.通过hostname命令直接更改主机名(不是永久) 1.显示当前的主机名 2.更改主机名 二.通过修改配置文件(永久改) 1.hostna ...

  4. linux 批量删除进程的两种方法

    linux批量删除进程的两种方法 介绍两种方法.要kill的进程都有共同的字串. [plain]  kill -9 `ps -ef |grep xxx|awk '{print $2}' `    ki ...

  5. linux屏蔽海外流量的两种方法

    方法一:使用大神的开源脚本,屏蔽指定国家地区的IP访问 wget https://raw.githubusercontent.com/iiiiiii1/Block-IPs-from-countries ...

  6. 实现Windows下Qt扫描U盘的两种方式

    参考网上资源,总结了如下实现Windows下Qt扫描U盘的两种方式:. 方式1: 检测Windows的事件–WM_DEVICECHANGE 缺点:想要检测时,必须发生过一次U盘插拔动作,需要手动&qu ...

  7. linux启动sh文件命令,Linux 运行 .sh 文件的两种方法

    Linux 运行 .sh 文件的两种方法 文章作者:网友投稿 发布时间:2010-06-15 13:31:16 来源:网络 一个中等水平的Linux用户一定少不了经常执行.sh文件,当然了,你可以在图 ...

  8. Windows Docker 配置国内镜像源的两种方法

    Windows Docker配置国内镜像源的两种方法 更新时间 2022.04.14 通过Docker-Desktop界面操作和修改daemon.json两种方法配置国内镜像源 方法一:通过Docke ...

  9. Linux centos7升级内核(两种方法:内核编译和yum更新)

                Linux centos7升级内核(两种方法:内核编译和yum更新) Linux的内核概念不用说大家也很清楚,正是内核版本的不同,才有Linux发行版本的说法,现在主流的cen ...

  10. 一、查看Linux内核版本命令(两种方法):

    直接使用 cat /etc/redhat-release 便捷快速的方法 LSB Version: :core-4.1-amd64:core-4.1-noarch Distributor ID: Ce ...

最新文章

  1. 3.2亿人都在刷抖音,而优秀的人在看这几个公众号!
  2. iBatis学习第一天
  3. linux-free命令
  4. 前端学习(1653):前端系列实战课程之常见各种窗口
  5. LeetCode 358. K 距离间隔重排字符串(贪心+优先队列)
  6. 高通和英特尔向美国政府施压,要求减轻华为禁令;ATT又裁员1800人,一年解雇2.3万人;Facebook加密货币推出在即……...
  7. Linux系统编程10:进程入门之系统编程中最重要的概念之进程进程的相关操作使用fork创建进程
  8. php用空格分隔的字符串对比,探讨各种PHP字符串函数的总结分析
  9. cJSON各函数实现的功能
  10. 怎样固定计算机画图曲线,如何在电脑上画出固定长度的线段
  11. jdk安装后怎么使用_jdk安装后怎么打开java
  12. 企业数字化转型:构建“感知—思考—响应—反馈优化”闭环
  13. 简洁明了:基于eova平台,对Vue 页面中的自定义Button按钮进行用户权限控制处理
  14. NMS非极大值抑制原理——目标检测
  15. 概念数据模型(E-R模型)
  16. Android垂直方向滚动的跑马灯,带gif
  17. 《天才在左,疯子在右》读书随笔
  18. 所有人体胸部和下半身各部位的英语单词
  19. 多种方式实现字幕滚动效果
  20. python中encoding是什么意思_python中encoding是什么意思

热门文章

  1. mysql 导出中间 数据_mysql导出数据库几种方法
  2. lua反射的一个例子
  3. 关于ugui界面显示隐藏的优化
  4. 关于Lambda表达式的简单语法理解,有参无参,有无返回值的格式的理解,仅限编程新手
  5. python基于udp的网络聊天室再用tkinter显示_Python3:Tkinter gui中的UDP包发送/接收
  6. php修改另一个文件内容,php如何修改文件内容
  7. html javascript 表格id,javascript 获取表格中元素id的实现代码
  8. php元素排序算法,php 4大基础排序算法
  9. mysql compopr_MySql应用的基本操作语句
  10. C# 中用 PadLeft、PadRight 补足位数