1. 进程和线程的概念
进程和线程的概念在操作系统原理这门课上其实已经讲过了,这里为了博客的完整性还是说一下。
A. 操作系统将硬件抽象、提供使用硬件资源的能力
操作系统作作为最底层的软件,直接运行在硬件之上。它将硬件抽象为C语言
的结构体,并提供了对应的C语言
的函数来使用硬件。
例如对于
ide
设备,例如早期的磁盘,Linux
定义了一个ide_hw
结构体来描述。ide_hw
结构体中包含了:
- 设备所使用的
io
端口- 设备的中断号
- ……
而后定义了
ide_input_data
、ide_output_data
这类函数来使用ide
设备,即读写磁盘数据因此,我们在其他地方最需要调用
ide_input_data
、ide_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
这个可执行程序实际上就是由可执行的指令序列组成的
而进程实际上就是运行中的程序,所以进程的特征之一就是具有可执行的指令序列
2. 专用的、系统分配的堆栈空间
通过编译器编译得到的可执行程序是作为静态文件保存在磁盘上的,此时CPU
是无法执行程序中的指令的。想要让CPU
执行其中的指令,即运行这个程序,就必须要将程序从磁盘中读取到内存中,专业术语称为将程序加载到内存。
因此,操作系统就需要在内存中为程序开辟一段空间,用于存储可执行程序的二进制指令。如下图,程序的二进制指令存储在这段内存空间的开始处。
PS:这段内存空间不仅存储程序的机器指令,还需要存储一些其他的数据,因此
运行中的程序就是进程,因此进程需要系统为其分配的一段专用的堆栈空间来存储程序的机器代码、数据……
3. 有一个task_struct
结构体和进程对应
内核需要管理所有的进程,因此类似于Linux
内核使用结构体抽象所有硬件而后进行管理一样,Linux
使用task_struct
结构体来描述、管理进程。
关于这个结构体,后面我们会详细进行介绍,这里就不细说了。
4. 具有虚拟地址空间(全局页目录表)
这个略,需要明白Linux
的内存管理部分之后才会明白。后面说到内存管理再说。
B. 进程的分类
- 线程:只具备要素一、二、三,而缺少要素四的任务
- 进程:具备要素一、二、三、四的任务
- 用户进程:具备独立的虚拟地址空间的进程
- 内核进程:共享内核虚拟地址空间的进程