Linux线程与进程:探秘共享地址空间的并发实现与内

Linux系列


文章目录


前言

在Linux操作系统中,线程作为CPU调度的基本单位,起着至关重要的作用。深入理解线程控制机制,是学习Linux系统不可或缺的重要环节。

本篇文章我主要介绍,线程的概念、线程与进程的关系及线程在Linux下的实现。


一、线程的概念

线程(Thread)是操作系统能够进行运算调度的最小单位 ,是进程中的一个独立执行流 。线程在进程的上下文中运行,共享进程的资源(如:内存、文件、内核数据结构等),但拥有自己的独立的执行栈。线程的存在使得程序可以更高效的并发执行。

Linux系统中,并未为线程单独构建管理结构,而是通过模拟进程的方式实施线程管理,复用进程的数据结构与管理算法。因此,Linux不存在真正意义上的线程,从操作系统层面来看,所有执行实体本质上均为轻量级进程。

下图表示的线程与进程的关系:

  1. 进程是资源分配的单位:图中红色框内的「进程地址空间」(包含用户空间的正文代码、数据段、共享区、栈等,以及内核空间)是进程的资源集合。进程拥有独立的地址空间,是系统分配资源(如内存、文件描述符)的基本单位。

  2. 线程是调度的基本单位 :图中多个 task_struct(任务控制块)都表示一个线程,均指向同一进程地址空间,它们属于同一个进程的不同线程。每个 task_struct 代表一个线程,线程是 CPU 调度的基本单位,由 CPU 分配时间片执行。

  3. 资源共享关系 :同一进程内的线程共享进程地址空间的资源(如用户空间的共享区、数据段、正文代码等),但每个线程有独立的栈空间(图中未详细区分每个线程的栈)和 task_struct(记录线程状态、寄存器信息等)。

  4. 内核视角与用户视角 :在 Linux 中,线程本质是「轻量级进程」,内核通过 task_struct 管理线程,与管理进程的方式类似。但从用户视角看,多个线程属于同一进程,协作完成任务,共享进程资源,提升执行效率。

总结:进程是资源载体,线程是执行实体。在创建线程时不需要重新加载数据(代码、页表等),进程内的线程共用同一份地址空间,最开始创建的task_struct我们称为主线程,其他线程称为新线程。

我们之前介绍过的进程,都是只有一个执行流的特殊情况

二、线程与地址空间

2.1 线程资源的分配

在学习进程时我们了解到,CPU 访问进程资源需借助 task_struct 结构,通过进程地址空间获取虚拟内存地址,再经页表转换得到物理内存,进而实现对进程数据的访问。由此可见,进程地址空间是进程的资源载体 。而线程并不单独分配地址空间资源,其本质是对所属进程地址空间特定范围的使用与操作,通过共享进程地址空间来实现资源利用。

2.2 虚拟地址到物理地址的转换

以32为计算机为例

磁盘文件文件加载到内存时,一般以4KB为单位(介绍文件管理时讲过)

在32位及其下,虚拟内存使用32位表示,当CPU进行虚拟内存到物理内存的转化时,会将这32位划分为:32=10+10+12,分别进行映射:

当 CPU 将虚拟地址转换为物理地址时,首先依据虚拟地址的前 10 位确定页目录表中的下标,利用该下标获取二级页表的基地址。接着,通过虚拟地址中间的 10 位确定二级页表中的下标,二级页表项中包含权限位,CPU 借助这些权限位判断是否存在越界访问或触发缺页中断。若访问合法,从二级页表项中获取物理内存页框的起始地址,最后结合虚拟地址的末 12 位,精准定位到实际物理内存地址。

三 、线程VS进程

Cache(高速缓存):

Cache 是介于 CPU 与主存(DRAM)之间的高速缓冲存储器,通常由 SRAM(静态存储器)构成,虽容量较小但访问速度极快。其核心功能是基于局部性原理,临时存储 CPU 近期可能频繁访问的数据,从而大幅提升数据访问效率。当 CPU 需读取数据时,会优先在 Cache 中查找:若命中目标数据,则直接从 Cache 读取,避免了对低速主存的访问;若未命中,则需从主存加载数据,并将其存入 Cache 以备后续使用。

从进程与线程的调度特性看:

  • 线程切换的缓存友好性:同一进程内的线程共享代码段、数据段和堆内存。当线程切换时,由于新线程所需访问的代码和数据仍属于原进程的地址空间范围,其数据可能依然驻留在 Cache 中,因此无需重新加载基础资源,有效降低了 Cache 失效的概率。
  • 进程切换的缓存冷启动 :进程拥有独立的地址空间,当发生进程切换时,新进程的地址空间与原进程无重叠,导致 Cache 中缓存的旧进程数据无法被新进程复用。此时,CPU 需重新从主存加载新进程的代码和数据到 Cache,这一过程被称为「缓存冷启动」,会引入较高的访问延迟。

这种差异使得线程切换在性能上通常优于进程切换,尤其在需要频繁调度的场景中,线程的缓存利用率优势更为显著。

在创建进程时,操作系统需要为其分配独立的资源载体:

  • 构建专属的 task_struct 结构体(记录进程状态、优先级等核心信息);
  • 分配独立的页表与进程地址空间(包含代码段、数据段等内存区域);
  • 从磁盘加载进程的代码和数据到内存,并初始化进程上下文(如程序计数器、通用寄存器值等)。

而在进程中创建新线程时,由于线程共享所属进程的地址空间、页表及已加载的代码数据,操作系统仅需:

  • 为新线程创建独立的 task_struct 结构体;
  • 初始化线程独有的上下文数据(如线程栈空间、寄存器现场等轻量级资源)。

这种「资源共享 + 轻量上下文创建」的机制,使得线程创建的开销远低于进程,成为操作系统实现高效并发的关键技术路径。

总的来说线程相较于进程来说更加轻量化:

  • 切换更加轻量化
  • 创建和释放更加轻量化

总结

有些性质我会放在下篇介绍,本篇主要介绍概念
线程的优点

  • 创建一个新线程的代价要比创建一个新进程小得多
  • 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多线程占用的资源要比进程少很多
  • 能充分利用多处理器的可并行数量在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
  • 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
  • I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作

线程的缺点
性能损失

  • 一个很少被外部事件阻塞的计算密集型线程往往无法与其它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。
    健壮性降低
  • 编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。
    缺乏访问控制
  • 进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。
    编程难度提高
  • 编写与调试一个多线程程序比单线程程序困难得多
    线程异常
  • 单个线程如果出现除零,野指针问题导致线程崩溃,
相关推荐
想睡hhh16 分钟前
c++STL——stack、queue、priority_queue的模拟实现
开发语言·c++·stl
北冥有鱼被烹24 分钟前
【微知】/proc中如何查看Linux内核是否允许加载内核模块?(/proc/sys/kernel/modules_disabled)
linux·服务器
cloues break.27 分钟前
C++初阶----模板初阶
java·开发语言·c++
wwww.wwww1 小时前
Qt软件开发-摄像头检测使用软件V1.1
开发语言·c++·qt
qq_273900231 小时前
CentOS系统防火墙服务介绍
linux·运维·centos
共享家95271 小时前
栈相关算法题解题思路与代码实现分享
c++·leetcode
Wendy_robot1 小时前
【前缀和计算和+哈希表查找次数】Leetcode 560. 和为 K 的子数组
c++·算法·leetcode
小余吃大鱼1 小时前
CentOS中在线安装Docker(超详细)
linux·docker·centos
一只鱼^_1 小时前
第十六届蓝桥杯大赛软件赛省赛 C/C++ 大学B组 [京津冀]
c语言·数据结构·c++·算法·贪心算法·蓝桥杯·动态规划
程序员JerrySUN1 小时前
驱动开发硬核特训 · Day 19:字符设备驱动实战(控制 LED)
linux·驱动开发