内核角度谈谈Linux进程和线程

news2025/1/11 14:03:13

目录

    • 前言
    • 内核对进程和线程的表示
    • 创建进程的过程
    • 创建线程的过程
    • 创建进程和线程的异同
      • 揭秘 do_fork 系统调用
      • 结论

前言

昨天面试的时候,面试官问我了个平平淡淡的问题–>“聊聊Linux中进程和线程”;

相比大家不管是在考试还是面试中或多或少都遇到过这个问题,俗话说得好:

进程是资源分配的基本单位,线程是资源调度的基本单位!

这个你我都知道,但是这样解释感觉有点牵强和敷衍,记住八股不是背的,是要理解,这样面试才能有深度,配合上清晰的逻辑表达,凡可百战百胜!

在网上对进程和线程的讨论中,很多都是聚集在这二位有啥不同。但事实在 Linux 上,进程和线程的相同点要远远大于不同点。在 Linux 下的线程甚至都被称为了轻量级进程。(至于说轻量在哪儿,稍后我们最终结论就会浮现)

我今天就给大家从 Linux 内核实现的角度,给大家深度对比下进程和线程。

内核对进程和线程的表示

都用task_struct表示

在这里插入图片描述

对于线程来讲,所有的task_struct字段都是和进程一样的(本来就是一个结构体来表示的)。包括状态、pid、task 树关系、地址空间、文件系统信息、打开的文件信息等等字段,线程也都有。


对于进程来说,这个 pid 就是我们平时常说的进程 pid。

对于线程来说,我们假如一个进程下创建了多个线程出来。那么每个线程的 pid 都是不同的。但是我们一般又需要记录线程是属于哪个进程的。这时候,tgid 就派上用场了,通过 tgid 字段来表示自己所归属的进程 ID

在这里插入图片描述

这样内核通过 tgid 可以知道线程属于哪个进程。

这也就是我前面说的,进程和线程的相同点要远远大于不同点,本质上是同一个东西,都是一个 task_struct !正因为进程线程如此之相像,所以在 Linux 下的线程还有另外一个名字,叫轻量级进程。(至于说轻量在哪儿,稍后我们最终结论就会浮现)


创建进程的过程

Linux下每个进程本质上是一个task_struct(PCB),这点可以参考我的这篇文章Linux虚拟地址空间

事实上,进程线程创建的时候,使用的函数看起来不一样。但实际在底层实现上,最终都是使用同一个函数来实现的:

在这里插入图片描述

通过分析内核源码,创建进程时fork 调用主要就是执行了 do_fork 函数。

注意:fork 函数调用 do_fork 的传的参数分别是SIGCHLD、0,0,NULL,NULL

//file:kernel/fork.c
SYSCALL_DEFINE0(fork)
{
 return do_fork(SIGCHLD, 0, 0, NULL, NULL);

do_fork 函数又调用 copy_process 完成进程的创建。

//file:kernel/fork.c
long do_fork(...)
{
 //复制一个 task_struct 出来
 struct task_struct *p;
 p = copy_process(clone_flags, ...);
 ...
}

创建线程的过程

同样如上图:

在这里插入图片描述

通过分析源码,lib库中的pthread_create创建线程会调用**clone()**系统调用,为其传入了一组 flag。

//file:nptl/sysdeps/pthread/createthread.c
static int
create_thread (struct pthread *pd, ...)
{
 int clone_flags = (CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGNAL
    | CLONE_SETTLS | CLONE_PARENT_SETTID
    | CLONE_CHILD_CLEARTID | CLONE_SYSVSEM
    | 0); //这个flag传入了下面的do_clone()
 
 int res = do_clone (pd, attr, clone_flags, ...);
 ...
}

紧接着查看clone()的实现:

//file:kernel/fork.c
SYSCALL_DEFINE5(clone, ......)
{
 return do_fork(clone_flags, newsp, 0, parent_tidptr, child_tidptr);
}

验证了上图,还是执行到了内核中的do_fork() 函数,并且还是会执行到 copy_process() 来完成实际的创建。


创建进程和线程的异同

可见和创建进程时使用的 fork 系统调用相比,创建线程的 clone 系统调用几乎和 fork 差不多,也一样使用的是内核里的 do_fork 函数,最后走到 copy_process 来完整创建。

不过创建过程的区别是二者在调用 do_fork 时传入的 clone_flags 里的标记不一样!

  • 创建进程时的 flag:仅有一个 SIGCHLD
  • 创建线程时的 flag:包括 CLONE_VM、CLONE_FS、CLONE_FILES、CLONE_SIGNAL、CLONE_SETTLS、CLONE_PARENT_SETTID、CLONE_CHILD_CLEARTID、CLONE_SYSVSEM。

关于线程多传入的这些 flag 的含义,我们选几个关键的做一个简单的介绍:

  • CLONE_VM: 新 task 和父进程共享地址空间
  • CLONE_FS:新 task 和父进程共享文件系统信息
  • CLONE_FILES:新 task 和父进程共享文件描述符表

这些 flag 会对 task_struct 产生啥影响,我们接着看接下来的内容:

揭秘 do_fork 系统调用

前面我们看到,进程和线程创建都是调用内核中的 do_fork 函数来执行的。在 do_fork 的实现中,核心是一个 copy_process 函数,它以拷贝父进程(线程)的方式来生成一个新的 task_struct 出来

//file:kernel/fork.c
long do_fork(unsigned long clone_flags, ...)
{
 //复制一个 task_struct 出来
 struct task_struct *p;
 p = copy_process(clone_flags, stack_start, stack_size,
    child_tidptr, NULL, trace);
 
 //子任务加入到就绪队列中去,等待调度器调度
 wake_up_new_task(p);
 ...
}

创建完毕后,调用 wake_up_new_task 将新创建的任务添加到就绪队列中,等待调度器调度执行。这个代码很长,我对其进行了一定程度的精简:

//file:kernel/fork.c
static struct task_struct *copy_process(...)
{
 //4.1 复制进程 task_struct 结构体
 struct task_struct *p;
 p = dup_task_struct(current);
 ...
 
 //4.2 拷贝 files_struct
 retval = copy_files(clone_flags, p);
 
 //4.3 拷贝 fs_struct
 retval = copy_fs(clone_flags, p);
 
 //4.4 拷贝 mm_struct
 retval = copy_mm(clone_flags, p);
 
 //4.5 拷贝进程的命名空间 nsproxy
 retval = copy_namespaces(clone_flags, p);
 
 //4.6 申请 pid && 设置进程号
 pid = alloc_pid(p->nsproxy->pid_ns);
 p->pid = pid_nr(pid);
 p->tgid = p->pid;
 if (clone_flags & CLONE_THREAD)
  p->tgid = current->tgid;
 
 ......
}

可见,copy_process 先是复制了一个新的 task_struct 出来,然后调用 copy_xxx 系列的函数对 task_struct 中的各种核心字段进行拷贝处理,还申请了 新的pid 。


copy task_struct中各种字段时候的处理方式,就是区分进程和线程的重点:

  • 创建进程中,因为没有传入task_struct中那些共享的字段,则他们都多copy了一份,有自己的内存空间(注意存在写时拷贝)

在这里插入图片描述

通过mm_struct字段访问物理内存的过程:

在这里插入图片描述

  • 创建线程时,因为传入了task_struct中各种字段的共享标志位,则对应task_struct的各字段是共享的

在这里插入图片描述

通过mm_struct字段访问物理内存的过程:

在这里插入图片描述

结论

  1. 对于进程来讲,地址空间 mm_struct、挂载点 fs_struct、打开文件列表 files_struct 等各种task_struct中的核心字段都要是独立拥有的,都需要去申请新的内存并初始化它们。(这也体现了,进程之间字段不共享,资源分配的基本单位)

  2. 对于线程来讲,其地址空间 mm_struct、目录信息 fs_struct、打开文件列表 files_struct 等各种task_struct中的核心字段都是和创建它的任务共享的;(这也体现了线程和创建他的进程,字段等资源共享,但线程有唯一pid,是调度的基本单位)

总之,在 Linux 内核中并没有对线程做特殊处理还是由 task_struct 来管理从内核的角度看,用户态的线程本质上还是一个进程。只不过和普通进程比,稍微“轻量”了那么一些(轻量在:共享了各字段)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/388538.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

python高德地图+58租房网站平台源码

wx供重浩:创享日记 对话框发送:python地图 免费获取完整源码源文件说明文档配置教程等 在PyCharm中运行《高德地图58租房》即可进入如图1所示的高德地图网页。 具体的操作步骤如下: (1)打开地图网页后,在编…

xgboost:防止过拟合的方法收缩和特征列、采样

xgboost除了第2.1节中提到的正则化目标之外,还使用了另外两种技术来进一步防止过拟合1。 目录shrinkage[^1]Column Subsampling[^2]shrinkage2 第一种技术是由弗里德曼提出的收缩。在树提升的每一步之后,收缩率以η因子的比例缩放新添加的权重。与随机优…

Scrapy框架(高效爬虫)

文章目录一、环境配置二、创建项目三、scrapy数据解析四、基于终端指令的持久化存储1、基于终端指令2、基于管道3、数据同时保存至本地及数据库4、基于spider爬取某网站各页面数据5、爬取本页和详情页信息(请求传参)6、图片数据爬取ImagesPipeline五、中…

IP欺骗种类有哪些?

每台计算机都有一个IP地址,发送的任何数据都被分成许多块(“数据包”),每个数据包单独传输,当这些数据包到达链的末端时,就会重新组装并作为一个整体呈现。此外,每个数据包还有其可识别信息&…

4、High-Resolution Image Synthesis with Latent Diffusion Models

简介github地址diffusion model明显的缺点是耗费大量的时间、计算资源,为此,论文将其应用于强大的预训练自编码器的潜在空间 ,这是首次允许在复杂性降低和细节保存之间达到一个近乎最佳的点,极大地提高了视觉保真度。通过在模型架…

操作系统复习题

什么是线程? 线程(Thread):轻量级进程,是操作系统进行调度的最小单位。一个线程是一个任务(一个程序段)的一次执行过程。线程不占有内存空间,它包括在进程的内存空间中。在同一个进程…

自然语言处理历史最全预训练模型(部署)汇集分享

什么是预训练模型?预练模型是其他人为解决类似问题而创建的且已经训练好的模型。代替从头开始建立模型来解决类似的问题,我们可以使用在其他问题上训练过的模型作为起点。预训练的模型在相似的应用程序中可能不是100%准确的。本文整理了自然语…

踩坑:maven打包失败的解决方式总结

Maven打包失败原因总结如下: 失败原因1:无法使用spring-boot-maven-plugin插件 使用spring-boot-maven-plugin插件可以创建一个可执行的JAR应用程序,前提是应用程序的parent为spring-boot-starter-parent。 需要添加parent的包spring-boot…

QML组件

一个QML文件定义了一个独立的、顶级的QML组件。 一个QML组件就是一个模板,被QML运行环境解释来创建一个带有一些预定义行为的对象。 一个独立的QML组件可以运行多次来禅城多个对象,每个对象都可以称为该组件的实例。 例子: 在项目中添加一…

Redis基础入门

文章目录前言一、redis是什么?二、安装步骤1.下载安装包2.安装三、Redis的数据类型redis是一种高级的key-value的存储系统,其中的key是字符串类型,尽可能满足如下几点:字符串(String)列表(List)集合(Set,不允许出现重复…

MySQL面试题-索引篇

1.什么是索引 MySQL的索引是一种数据结构,可以用于加快数据库中数据的查询速度。索引是基于表中一个或多个列的值排序的快速查找数据结构,可以大大提高查询效率。MySQL支持多种类型的索引,如B-tree索引、哈希索引、全文索引等。 索引可以在…

【java基础】异常处理(Exception)

文章目录基本介绍异常分类抛出异常非检查型异常检查型异常捕获异常捕获单个异常捕获多个异常创建自定义异常类finally字句try-with-Resource总结基本介绍 对于一个程序,总是有bug的。如果我们的程序遇到一个错误就终止了,那么肯定是不合理,程…

数据爬取(urllib+BeautifulSoup)

文章目录知识点总结爬虫步骤爬虫三要素爬虫注意事项python爬取技术学习网页抓取库Urllib网页解析库Beautifulsoup案例知识点总结 爬虫是一种按照一定规则,自动抓取互联网上网页中的相应信息的程序或脚本。 爬虫步骤 1.需求分析 2.找到要爬取信息的网站 3.下载reque…

基于halo后台管理+Gblog-wx搭建的微信小程序

先决条件 1、已经通过docker安装了halo后台管理系统(参考:http://43.136.39.20:8090/archives/halo-build) 2、安装的halo版本为1.5.3版本。此版本的halo才能安装小程序主题并启动小程序 3、需要修改小程序文件配置 解决安装的不是1.5.3的halo 1、如果是docker安装的halo…

蓝牙技术|蓝牙5.4标准正式发布,蓝牙ESL电子价签迎来一波利好

蓝牙技术联盟于2023年1月31日批准了蓝牙核心规范v5.4版本(以下简称蓝牙5.4版本),并已正式公布。 蓝牙5.4版本引入了四个新特性,如下: 广播数据加密(Encrypted Advertising Data):对广播数据进行加密以提高广播数据传…

[神经网络]Swin Transformer网络

一、概述 Swin Transformer是一个用了移动窗口的层级式Vision Transformer。 在图像领域,Transformer需要解决如下两个问题: ①尺度问题:同一语义的物体在图像中有不一样的尺度。(大小不同) ②Resolution过大:若以像素点作为单位&…

利用python写一个gui小公举--环境搭建

文章目录背景搭建环境安装必要库添加工具快捷方式检验背景 在实习过程中遇到一个问题,某项目是通过python代码实现的,而且需要一直修改参数实现功能,过程有些繁琐。虽然师兄用PHP study搭了一个网站用于查看结果,但是还是过于繁琐…

分布式新闻项目实战 - 12.热点文章-实时计算(kafkaStream)

死海效应: 公司发展到一定阶段后,工作能力强的员工,就会离职,因为他无法容忍公司的某些行为,即使辞职也很快会找到好工作;工作能力差的员工,却赖着不走,因为辞职以后也不太好找工作&…

JavaScript实现十大排序算法

目录 概览 一、冒泡排序 1、算法描述 2、图示 3、代码 二、选择排序 1、算法描述 2、图示 3、代码 三、插入排序 1、算法描述 2、图示 ​编辑 3、代码 四、希尔排序 1、算法描述 2、图示 3、代码 五、并归排序 1、算法描述 2、图示 ​编辑​编辑3、代码 …

食品与疾病关系预测赛题

和鲸平台数据分析实战 题目:食品与疾病关系预测算法赛道 一、赛题描述 食品与疾病关系预测算法赛道 越来越多的证据表明,食物分子与慢性疾病之间存在关联甚至治疗关系。营养成分可能直接或间接地作用于人类基因组,并调节参与疾病风险和疾病…