Linux内核源码分析 3:Linux线程原理以及相关系统调用


Linux线程原理

1. 进程和线程的概念

进程和线程的概念在操作系统原理这门课上其实已经讲过了,这里为了博客的完整性还是说一下。

A. 操作系统将硬件抽象、提供使用硬件资源的能力

操作系统作作为最底层的软件,直接运行在硬件之上。它将硬件抽象为C语言的结构体,并提供了对应的C语言的函数来使用硬件。

例如对于ide设备,例如早期的磁盘,Linux定义了一个ide_hw结构体来描述。ide_hw结构体中包含了:

  • 设备所使用的io端口
  • 设备的中断号
  • ……

ide_hw结构体

而后定义了ide_input_dataide_output_data这类函数来使用ide设备,即读写磁盘数据

使用ide_input_data、ide_output_data使用ide设备

因此,我们在其他地方最需要调用ide_input_dataide_output_data这类函数就可以使用对应的设备了。

类似于ide设备这样,操作系统将具体得硬件抽象为了一种软件资源,从而为用户程序提供了使用硬件资源的能力

B. 进程是使用操作系统的对象

操作系统通过上面的这种方式,将当前计算机硬件系统中的所有硬件转换为软件资源了。但是此时还没有使用这些资源的对象,在我们直观的认知中,使用这些硬件资源的对象就是我们,即用户。但是这是从我们人类认知的角度出发的。

对于操作系统来说,使用硬件资源的对象是进程。用户通过发起一个一个的进程来使用这些硬件资源

例如,我们在电脑上运行的QQ微信Chrome浏览器QQ音乐……等等应用在操作系统看来都是进程。进程通过调用系统提供的这些使用硬件的函数,最终完成了向耳机输出声音(QQ音乐)、发出消息(QQ/微信)……等等功能

因此,线程是操作系统的使用者,具备使用操作系统抽象出的资源的能力,是完成用户要求的直接对象

下面是我使用htop命令查看了当前系统中我运行了那些进程。

htop

可以看到,我在写这篇文章的时候,正在听歌(QQ音乐)、看文档(wpsoffice)、使用魔法(ClashX Pro)……

我电脑上此刻运行的进程

C. 进程是线程的容器

进程是完成用户要求的对象,也是使用操作系统提供的各种资源的对象。但是使用CPU的直接对象并不是进程,而是线程。进程是线程的容器,即一个进程可能会有很多个线程

进程有多个线程,并且直接使用CPU的对象是线程,这就意味着一个进程可能同时使用多个CPU(或者使用分时技术营造出使用多个CPU的假象)。而一个CPU可以处理一个任务,那么进程是线程的容器就意味着一个进程可能同时处理多个任务

这是非常符合直觉的,例如我们现在使用WPS Office制作PPT,那么WPS Office整体以一个进程的形式运行,用于完成用户制作PPT的需求。而其内部会运行多个线程,以同时处理多个任务:

  • 运行一个线程监听键盘,以得知用户按下了那个快捷键,从而实现对应的功能,例如保存当前PPT
  • 运行一个线程监听鼠标,以得知用户点击了什么按钮,从而实现对应的功能,例如插入图片
  • 运行一个线程在屏幕上实时渲染,例如用户插入图片之后立马就能在屏幕上显示出来
  • ……

还是以我电脑为例,查看一下一个进程包含了那些线程。

htop

这里可以看到,iTerm 2这个进程运行了多个线程。这里其实就是我在命令行里开了多个终端,每个终端都在跑一个命令:

  • 一个终端在跑ChatGPT帮助我写作
  • 一个终端在跑htop
  • 还有终端在运行其他命令

iTerm2命令行本身作为一个进程运行,而因为我在每个终端中都运行了命令,相应的,iTerm2这个进程就使用多个线程来运行我的这些命令。

查看一个进程的所以线程

2. 进程的要素和分类

Linux中,将进程称为任务task)。之所以称为任务是因为很久以前还没有进程和线程的概念,那个时候只有任务。后来随着计算机科学的不断发展,就逐渐出现了线程和进程。

进程本质上就是运行中的程序。进程具有几个要素,这几个要素是进程的本质特征;而后根据一个进程的特征,我们就可以对进程进行分类。

A. 进程的要素

一个进程具有以下四个要素:

  • 有一段可执行的二进制指令序列
  • 有一段专用的、系统为其分配的堆栈空间
  • 内核中有一个task_struct结构体和这个进程对应、存储了进程的一些信息
  • 具有虚拟地址空间(全局页目录表)

1. 可执行的二进制指令序列

源代码是文本形式的文件,本质和.txt文件差不太多,内容上和Word里的文档是一样的。我们利用编译器,将文本格式的程序(此时还是源代码)编译为可执行的二进制程序(此时就是我们熟知的QQ微信等应用)

可执行的二进制程序实际上就是由可执行的二进制指令序列组成的。例如现在使用hexdump命令查看我电脑上的VSCode这个可执行的程序

hexdump -C $(which code)

可以看到,VSCode这个可执行程序实际上就是由可执行的指令序列组成的

而进程实际上就是运行中的程序,所以进程的特征之一就是具有可执行的指令序列

VSCode可执行程序

2. 专用的、系统分配的堆栈空间

通过编译器编译得到的可执行程序是作为静态文件保存在磁盘上的,此时CPU是无法执行程序中的指令的。想要让CPU执行其中的指令,即运行这个程序,就必须要将程序从磁盘中读取到内存中,专业术语称为将程序加载到内存。

因此,操作系统就需要在内存中为程序开辟一段空间,用于存储可执行程序的二进制指令。如下图,程序的二进制指令存储在这段内存空间的开始处。

PS:这段内存空间不仅存储程序的机器指令,还需要存储一些其他的数据,因此

代码段存储在系统分配的内存空间中

运行中的程序就是进程,因此进程需要系统为其分配的一段专用的堆栈空间来存储程序的机器代码、数据……

3. 有一个task_struct结构体和进程对应

内核需要管理所有的进程,因此类似于Linux内核使用结构体抽象所有硬件而后进行管理一样,Linux使用task_struct结构体来描述、管理进程。

关于这个结构体,后面我们会详细进行介绍,这里就不细说了。

4. 具有虚拟地址空间(全局页目录表)

这个略,需要明白Linux的内存管理部分之后才会明白。后面说到内存管理再说。

B. 进程的分类

  • 线程:只具备要素一、二、三,而缺少要素四的任务
  • 进程:具备要素一、二、三、四的任务
  • 用户进程:具备独立的虚拟地址空间的进程
  • 内核进程:共享内核虚拟地址空间的进程

3. 进程的状态


文章作者: Jack Wang
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Jack Wang !
 上一篇
中科院软件所实习日志(LARVa):一切的起源 中科院软件所实习日志(LARVa):一切的起源
本文是中科院软件所实习(LARVa项目)日志的第零篇,主要记录了为什么我要写实习日志以及实习日志未来的形式
下一篇 
Linux内核源码分析 2:Linux内核版本号和源码目录结构 Linux内核源码分析 2:Linux内核版本号和源码目录结构
本文是Linux内核源码分析系列文章的第二篇,介绍了Linux内核的版本号和源码组织结构
  目录