深入理解进程和线程

时间:2022-01-05 03:58:39

在之前的博客里面(进程通信),我简单的区分了一下进程和线程,然后过了一个月我发现之前的理解远远不够。

先把之前的简单理解贴一下,

进程是装入内存运行的程序段,是许多的系统对象拥有权的集合,换句大家经常引用的话说进程是资源分配的基本单位

线程是CPU运行调度的基本单位,线程必须被包含在进程中,一个进程可以有很多线程(至少有一个),这些线程有自己的资源(如栈,寄存器)也共享进程的许多资源。

 

我把我的理解过程一步步的给大家贴出来,希望这样能让各位更好的理解进程与线程。

在理解下面的内容之前,需要先理解虚拟地址空间与虚拟内存的概念。

(简单来说,我们代码中的指针地址都是cpu的逻辑地址,而不是实际上的物理地址(内存地址),这就是虚拟地址,虚拟地址很好的解决了地址空间隔离问题。

我们很多时候不需要将整个程序都放入内存中(内存的大小是有限的),部分程序可以先放在磁盘上等需要时再调入内存(可能几乎不调用),这样就利用有限的内存放入更多的进程。所以这个磁盘上的伪内存就是虚拟内存。)

 

其实我的理解过程来源于一个很常见又比较难理解的问题:我们知道main函数可以理解为一个主线程,我们在main中创建其他线程,而其他线程可以用malloc来动态申请内存。那么问题来了,我们都说线程并不拥有资源分配的权限,他只有寄存器,栈等少数资源,可是他malloc出的不就是从堆上分配的资源么?(这里不要把线程理解成一个函数)

答:我要给出几个观点,而这里可能并不是完全正确,但是绝对有参考意义。

首先我参考了这篇博客,进程控制块概述        梳理出了第一个观点。

1.  进程就是一个代码树,我们的线程就是其中的一个分支,没有了线程,进程并不能执行任何操作。换句话就是说,我们进程的具体操作最后还是分配给每一个线程来执行。相对于线程,我们甚至可以把进程理解为线程的一个容器,它代表线程来接受分配到的资源,为线程提供执行代码,所以我们常说的进程是资源分配的基本单位不能说线程就没办法分配资源了。

其次,又参考了这篇博客,进程的虚拟地址空间综合出了第二个观点

2.  这里我们需要了解,进程在创建时就被分配了虚拟的地址空间, 可执行代码, 数据, 对象句柄集, 环境变量, 基础优先级, 以及最大最小工作集等等。虚拟的地址空间(上面已简单阐述)就可以理解为我们给进程分配的伪内存的大小(理论上这个进程可以占的空间大小)。而系统寻址能力决定了地址空间的大小. 主流的Windows系统是32位系统, 寻址能力是32位(这里的说法并不是十分准确,寻址能力不是简单的由字长决定。我在文章的最后会贴出一个相关的博客,大家暂时这样理解), 所以地址空间的大小是4GB. 地址空间的基地址为0x00000000, 最大地址是0xFFFFFFFF. 4GB的地址空间被划分为多个大的内存区域, 包括用户区(我们代码用到的基本上就是这块区域),系统区(内核区),可能还有NULL指针区, 系统越界保护区等等。

所以呢,这里我们的线程申请的就是这个所谓虚拟内存的堆空间,那么就可以理解为线程其实是在进程已经申请好的内存堆上再申请内存,其实说到底还是线程依赖于进程来获取的内存资源

想到这里,不禁又要问,那线程所谓拥有的寄存器,栈与其他动态申请的资源又有什么区别?

3. 线程创建的时候,便与栈绑定。因为每一个线程都要存储自己的局部变量,这些变量会在线程里的函数运行完自动的释放,这样对于每一个线程都会拥有一个自己的栈,这个栈其实也是在进程栈上分配下来的,而且是自动分配的。所以,一般在不越界的情况下,各个线程维护的是自己栈,互不干扰。但是如果线程动态申请一个内存就不同了,因为这块区域不是你自己的,其他的线程也可以使用与修改,因为这个堆是进程的,也是各个线程共用的。

 

最后呢,对这3点我们总结一下,我们所说的线程只拥有寄存器与栈等资源是相对于其他线程而言的。你可以依赖进程来动态申请内存,不过这个内存其实就是进程所分给你的。

 

 

 

再补充一个疑问,进程虚拟地址的内核区放的是什么东西?

按照我的理解,内核区放的就是内核(线程调度,内存管理,进程控制块PCB),也就是我们所说的操作系统。系统实际上被存放在进程的地址空间的一块区域中,各个进程共享这块区域,只不过操作系统一开机就被当做进程运行到内存当中去。(这里是我暂时查找资料的理解,如果有高人进一步指点或纠错不胜感激!)

 

链接:不要再被误导了,64位X86 CPU是没有64位寻址能力的!