文章目录
线程概念理解
本篇文章,将重点了解在Linux系统下关于线程的相关概念。
Linux线程的理解——宏观
首先,线程是我们学习Linux系统的又一个非常重要的知识点。首先我们需要做的,就是先对Linux下的线程有一个感性的理解,也就是能明白线程究竟是什么。
然后,我们在将线程的概念推广到其他操作系统,也就是宏观理解上!
线程、进程定义
在操作系统学科上:
进程 :内核数据结构(PCB) + 代码数据、CPU资源
线程 :是进程内部的一个执行分支
本质上,都是代码的一个执行流!
我们直接看这个操作系统学科上(所有操作系统的共性)的概念,是很难看明白的。
这里,我们需要给进程和线程一个新的解释,并且针对于这个解释的概念进行讲解:
进程 : 承担分配系统资源的实体
线程 : CPU调度的基本单位
对于概念,先不做解释,我们将一步步地引导理解这个概念。
回顾进程定义、执行过程
在学习进程的时候,我们曾经说过:
1.我们写的代码程序,在系统中跑起来就是进程,执行代码的就是进程!
2.进程需要受到操作系统管理,所以会有内核数据结构PCB,进程 = PCB + 代码和数据
3.进程在运行的时候,是被分配了进程地址空间的,这个进程地址空间是虚拟的,即操作系统 骗了 每个进程,让每个进程认为自己独自占用了所有的内存空间!
通过上面的回忆,我们可以简单得出一个结论:
进程访问的大部分资源,都是通过虚拟的进程地址空间访问,也就是说,进程地址空间,就是进程看到访问资源的一个窗口。

如上图所示,进程地址空间就类似于一个个的窥探窗口,能够让CPU调度进程的时候,找到该进程对应的相关代码和数据!
而每一个进程,就是分配资源的实体!因为在系统中,运行起来做任务的,是进程!
引出Linux下线程概念
可以试想一下,如果这个窗口是可以被共享的呢?也就是多个PCB通过一个窗口来找数据?

在Linux系统下,线程其实就是这么实现的!
也就是说,在Linux系统下,当CPU分配资源的时候,如果能够让多个task_struct共享同一个进程地址空间,并且再通过划分虚拟地址空间,划分每个线程的范围,就可以让不同的线程通过划分出来的地址找到对应的物流内存上的数据!
线程概念初步理解
1.在Linux系统下,线程是通过进程来模拟实现的!
但是这里需要说明:其他操作系统会有所区别!
2.对于线程划分资源应该怎么理解?
我们已经知道,无论是进程还是线程,都是通过虚拟进程地址空间这个窗口来看到物理内存上的资源的!也就是说,虚拟进程地址空间,就是真实资源的代表!
所以,本质上而言,划分资源,就是在划分虚拟地址。
3.划分资源即划分地址空间,那么需要划分代码区吗?
答案是,完全不需要,也不应该!
因为本身,代码区.text就是只读保护的。 可执行程序,也就是CPU执行的代码,就是一个ELF可执行文件,所有的代码,执行的时候都需要地址,也都编址在虚拟地址空间的代码区。
所有的线程,执行代码的时候,也可以看做成是以前的进程一样,也是独立的!执行代码,无非就是让CPU在不同的地址上跳转。
所以,所谓的对线程的代码区划分,只不过是我们认为的,希望不同的线程做不同的事情,而在代码逻辑上进行特定操作,让不同的线程根据不同的地址进入执行代码!(具体怎么执行的需要学习到线程控制才能搞清楚,这里先不做了解)
4.线程既然是分享同一个窗口,那么会有属于线程独有的吗?
答案是有的,但是我们在这里先不做了解,知道即可。
5.既然线程会共享数据,那么需要数据保护吗?
答案是,非常需要。我们在学习进程间通信的时候就知道,如果通信双方不同步(如单纯使用共享内存),那么会导致一个很严重的问题:数据不同步!
所以,当多个线程使用共享数据的时候,就需要对数据做同步保护!也就是加锁!
如何理解过往学习的进程概念
既然说,Linux线程也是通过进程来模拟的,那么我们又应该如何理解过往学习进程的概念呢?
难道说,以往学习的进程概念都是不对的吗?
其实并不是这样,其实概念是可以融合的。我们可以发现:
1.Linux下的进程,也就是只有一个task_struct看到进程地址空间
2.Linux下的线程,有多个task_struct可以看到同一个进程地址空间
而前面又说到:
进程 : 承担分配系统资源的实体
线程 : CPU调度的基本单位
分配资源,本质就是划分进程地址空间!线程又是让CPU调度的基本单位。
-> 进程是分配资源的实体,但是每个进程里面可能会有多个线程。
至此,我们可以知道:
过往的进程,其实是只有一个线程的进程!而多线程,就是有多线程的进程!

而我们的代码,也就是在线程上跑!只不过以往的进程中,只有一个线程。所以,Linux的线程,也叫做轻量级线程,或者说是轻量级进程来模拟实现的。
因为线程和进程最终都会被CPU执行,所以,本质上都是一个执行流。但是,在Linux系统视角下,进程和线程还是有很大的区别的(进程之间不共享进程地址空间,通信需要使用IPC)。
但是,如今在CPU看来,其实无论是进程还是线程,都无所谓。因为,最后执行流执行的时候,都是拿着task_struct + 代码和数据结构执行。线程也是一样执行。所以,CPU硬件而言,执行流的概念 <= 进程的。
与其他操作系统的对比
上面也是简单地从多个方面理解了线程的概念,现在需要提出两个问题:
1.其他操作系统也是这么做的吗?如果不是又会怎么做?
2.Linux系统为什么要这么做?有什么好处?
首先,对于进程的实现,其实每个系统都差不了太多。都是操作系统管理者一大堆的PCB,然后接收到CPU的调度。但是对于线程来说,不同系统在实现上差距还是比较大的。
就拿我们常用的Windows系统举例:
Windows系统底层实现线程,虽然线程也是和进程共享所谓的资源,但Windows系统下,线程有自己的独立的数据结构——TCB(Thtead Control Block)!
我们可以想象得到,Windows系统下会存在着很多的冗余代码!因为不管是线程也好、进程也好,最终受到CPU执行的时候逻辑肯定是类似的。但是Windows系统又都实现了独立的进程和线程内核数据结构。这个是必然的。
而Linux系统下,拿进程模拟线程,能够最大限度的复用代码逻辑!因为这样子可以减少冗余代码,Linux程序员们秉持着能复用就复用的态度,所以就这么设计了。
可以见得,Linux系统在代码的健壮性上是更好的!而Windows设计上会复杂一点。但是这并不是说说Windows系统不好,而是说Linux系统的优点!
对资源划分的理解——重谈页表
了解完线程的基本概念之后,我们现在来了解Linux系统下关于资源划分的概念。
可能会涉及到一些内存管理的知识、还需要重点重谈页表。
内存管理基本认识
过往学习Ext文件系统的时候,我们就说过:
操作系统真正操作磁盘空间的时候,是以基本单位4KB的数据块来整体操作的!这个是正好对应操作系统对于内存操作的基本单位,也是4KB。
当然,4KB的前提是32位系统下,如果是64位系统一般就是8KB。在本文的后序讲解中,将以32位系统进行讲解,因为讲解起来方便。64位就是扩大后,再增加一些新的特性!
所以,操作系统在管理内存和操作内存的时候,都是面对着基本单位4KB来操作的。而可执行程序,本质就是ELF格式的二进制文件。所以,CPU调度的时候,数据都是整体的以4KB的基本单位来在磁盘和内存间做数据的交换操作的!
而这4KB的基本单位,会被称为页框/页帧!
所以,之前说的写时拷贝,其实是:
如果有一个全局变量被父子进程都看到,但是其中一个进程要修改它,就会触发写时拷贝。也就是将这个变量拷贝出来,在内存空间上找另外一个地方存储。然后让修改它的进程指向的是这个被拷贝的的部分。
但其实,操作系统操作内存的时候,是以基本单位4KB来进行操作的!也就是说,一旦发生写时拷贝,其实拷贝的是该变量所在的一整个页框!
操作系统对内存的管理
在32位系统下,地址线有32条,也就是2^32个地址,大约是4GB的内存空间!而内存中每个页框的大小是4KB。经过计算得知,32位系统下,内存中是有4GB/4KB = 1048576个页框!
这么多页框,有些已经被使用,有些没有被使用。页框被哪些进程使用,总而言之,我们能够知道,这么多的页框也是需要被操作系统管理的——先描述、后组织!
我们来看一下在系统是如何描述这一个个页框的:
/* include/linux/mm_types.h */
struct page {
/* 原⼦标志,有些情况下会异步更新 */
unsigned long flags;
union {
struct {
/* 换出页表表,例如由zone->lru_lock保护的active_list */
struct list_head lru;
/* 如果最低为为0,则指向inode
* address_space,或为NULL
* 如果页映射为匿名内存,最低为置位
* ⽽且该指针指向anon_vma对象
*/
struct address_space* mapping;
/* 在映射内的偏移量 */
pgoff_t index;
/*
* 由映射私有,不透明数据
* 如果设置了PagePrivate,通常⽤于buffer_heads
* 如果设置了PageSwapCache,则⽤于swp_entry_t
* 如果设置了PG_buddy,则⽤于表⽰伙伴系统中的阶
*/
unsigned long private;
};
struct { /* slab, slob and slub */
union {
struct list_head slab_list; /* uses lru */
struct { /* Partial pages */
struct page* next;
#ifdef CONFIG_64BIT
int pages; /* Nr of pages left */
int pobjects; /* Approximate count */
#else
short int pages;
short int pobjects;
#endif
};
};
struct kmem_cache* slab_cache; /* not slob */
/* Double-word boundary */
void* freelist; /* first free object */
union {
void* s_mem; /* slab: first object */
unsigned long counters; /* SLUB */
struct { /* SLUB */
unsigned inuse : 16; /* ⽤于SLUB分配器:对象的数⽬ */
unsigned objects : 15;
unsigned frozen : 1;
};
};
};
...
};
union {
/* 内存管理⼦系统中映射的页表项计数,用于表⽰页是否已经映射,还用于限制逆向映射搜索*/
atomic_t _mapcount;
unsigned int page_type;
unsigned int active; /* SLAB */
int units; /* SLOB */
};
...
#if defined(WANT_PAGE_VIRTUAL)
/* 内核虚拟地址(如果没有映射则为NULL,即高端内存) */
void* virtual;
#endif /* WANT_PAGE_VIRTUAL */
...
}
那么,操作系统是如何组织这一个个的page的呢?
——答案非常简单粗暴,直接用数组:struct page mem[1048576]
所以,最后对内存块的管理,经过结构描述变成了对其数据结构的增删查改。因为最后这些数据结构都被放在了一个数组中,最后的操作也就是对数组进行操作。当然,在数组之上还会有很多的对于内存数据结构操作的算法,这里我们不考虑。
我们还会发现,为什么struct page中没有对应数据块在物理内存上的地址呢?
答案是,没有必要!可以通过某种方式计算出来!
首先,数组的大小 = 数组长度 * 结构体大小。而且系统启动的时候,就存储在内存前面部分:

(不一定是从物理地址0开始的)。
其次,我们学进程的时候就已经知道,某些数据结构是可以并存的!所以,Linux系统下,这个struct page不仅仅是存在于数组,还会存在于一个radix_tree上,这个树是基数树,可以用来查找到struct page的地址。
struct radix_tree_node{
unsigned int count; // 内存块被使用的引用计数
void* slot[RADIX_TREE_MAX_SIZE]; //指向内存块,内存块的虚拟地址
unsigned int tags[RADIX_TREE_MAX_TAGS][RADIX_TREE_TAG_LONG];
};
最后,我们来理解我为什么根本不需要存储物理地址/虚拟地址,因为可以计算出来。
1.我们是可以通过一定方式,得到struct page的虚拟地址(在进程地址空间上)。
2.又因为数组struct page mem[1048576]是全局的!就是一开始就初始化在了物理内存的开始位置,并连续存放。
3.page存储在数组上,就会有下标。如果知道了下标,那么就可以知道每个页框的起始物理地址:下标index * 4KB(页框基本单位)。每个页内的内容地址 = 页框起始物理地址 + 页内偏移。
4.得到了虚拟地址,是可以一定程度上找到其对应的下标的!我们简单点理解就是,直接通过得到的地址 / 每个page的大小,就得到了page在数组中的下标。然后就可以得到物理地址了。但实际上,这个是理想化状态,真正的情况远远复杂的多,所以操作系统内是提供了相关的宏函数来进行虚地址到物理地址的转换的!
所以,所谓的内存申请和管理:
本质是修改查找struct page数组,修改对应page,然建立对应的内核数据结构之间的关系!
重谈页表
目前来说,我们是基本知道了操作系统是如何管理内存、调度进程/线程的时候如何使用内存:

大致如上图所示,但是目前,我们对于页表的理解是不足的!
过往理解页表
我们在刚开始学习进程调度等相关知识的时候,我们就简单地认为:
每个进程都会带有一个页表,这个页表就是一个虚拟地址到物理地址一一映射的表!当然,也可能不止是地址的映射关系,还会有相关数据的权限标志。
但是,我们来分析一下这是否可行:(以32位系统理解)
在32位系统下,内存大小位4GB,也就是232个地址。所以,虚拟内存空间也一定是4GB,即232个地址,这是很容易理解的。
在这种情况下,如果说页表就是一个简单的从虚拟地址到物理地址的映射表,那么也就是说,这个表中会存在232对虚拟地址到物理地址的映射关系。在32位系统下,地址的大小为4个字节!而且,一个进程到底用了哪些地址是不清楚的,所以,需要把所有的地址映射关系都存储下来,这样就能得知,一个进程的一个页表就需要4 * 232 = 8GB,光是一个页表就这么大,这怎么可能?内存总共也就4GB。
所以,过往对于页表的理解,是简单粗暴的,甚至可以说,是不对的!
页表真实结构——分页式存储管理
为了满足内存能够容纳下每个进程的页表,所以需要一种新的设计方法,使得每个进程的页表占用空间 << 内存大小。
所以我们需要了解在Linux系统下页表的真实结构(还是以32位系统介绍,64位系统扩展即可)。
首先,32位系统下,一个虚拟地址占用空间位4个字节,也就是32个bit位!
Linux系统是这么做的,这里用一张图来解释:

所以,操作系统中,其实是通过分页式存储管理的思想来实现页表的!
所以,当一个进程/线程想要通过页表来找到物理内存中的数据,主要就是两步:
1.根据前10位,在页目录中找到页表,然后根据中间10位,找到页表中指向的页框。
2.找到页框后,最后12位正好是对应4KB,所以,最后12位就是页内偏移!
通过上面的两个基本步骤,就能够完成从虚拟地址到物理地址的映射了!
分页式存储管理的占用空间
我们已经知道了Linux系统下是如何实现页表的了,但是,这样做到了节省空间吗?
我们假设每个进程能够完完全全用完整个物理内存(当然这是不可能的,这里只是理论分析):
最重要的就是1024个页表的部分,每个页表有1024个指向页框的地址,这里虽然只用10个bit位,但是还是以4字节(32bit位)进行计算:
一个页表的大小 = 4byte * 1024 = 4KB
1024个页表的大小= 4KB * 1024 = 4MB
在加上一个页目录,页目录也是一样的,每个位置四个字节,最后整个页表占用的空间大小 ≈ 4MB,是4GB的1024分之1,远远小于内存大小!
所以,在每个进程(这里的进程是广义的,可能是多线程的结合),能够把所有的空间用满,也都只是大约4MB的大小。但是还有一个更加重要的点:
一个进程不可能把所有的内存用到!在分页式存储管理的思想下,并不会存在所有的页表!也就是说,页目录中可能只有几个位置指向了页表!所以,占用的内存是非常小的!
页表中的其他字段
页表中每个位置都是4个字节,但是查找页框只需要用到10个bit位,所以,在页表中,其他字段是用来表示一些关于当前进程下,相关数据的相关信息!
如权限(rwx)、是否命中,属于内核还是用户等…
所以,通过分页式存储管理,是能够很轻松的把相关信息的存储在页表中的同时,还能保存住数据的相关信息,方便CPU调度的时候使用!
理解CPU调度过程
CPU调度的基本单位是线程。在今天过后的CPU看来,是分不清楚线程还是进程的。因为本质都是PCB + 代码数据 + 页表!
我们也还知道,学习malloc / new等操作开辟堆内存的时候,其实只是在虚拟地址空间上划分出需要的地址而已,直到真正使用了才会关联到物理内存上!
所以,当CPU进行调度的时候:
1.根据进程的情况申请内存(本质就是修改page,建立数据结构关系,如radix_tree)。建立出虚拟地址到物理地址的映射关系。
2.CPU内有一个寄存器CR3,其存储的是每个页表的起始地址,也就是页框的。然后通过MMU硬件,动态查找页表,然后让CPU拿到数据的物理地址。
3.真正调度的时候,CPU就拿着索引到的物理地址查询内容。如果发现一些异常就会主动触发中断,此时操作系统需要先执行处理中断(如缺页中断、权限问题、写时拷贝)…
4.通过调度算法,不断地切换调度的进程/线程,最终完成整个操作系统的运行!
页表使用中出现的问题
效率问题:
因为页表是使用了分页式存储的管理思想,所以,索引物理地址的时候,是需要先索引一遍页目录,再索引一次页表!这必然会使得虚拟地址转化物理地址的时候效率变低!
所以,分页思想是一个双刃剑!分级越多,索引效率变低!
在计算机的世界中,所有问题都可以加一层中间层!这里的方法是:
在CPU转化地址的时候,不会先直接检索页表,而是在中间层TLB,俗称快表中先查找。如果在这里查找到了就不会去页表进行索引!
这其实就是一个在CPU和页表之间的缓存机制!

如果不存在于TLB中,那才去页表中查找,然后再刷新缓存。
不过一旦调度的时候切换了进程地址空间,那么TLB内部的内容会被刷新,即失效!所以,快表在CPU调度线程的时候是可以起到明显作用的!
缺页异常:
这种情况就是:当CPU拿到一个虚拟地址的时候,找不到对应的物理地址,即缺页了!
此时,操作系统就会主动的触发缺页异常 Page Fault,然后处理该中断信号!
处理缺页异常中断的方式是:PageFaultHandler。其中,缺页有三种可能:
- Hard Page Fault 也被称为 Major Page Fault,翻译为硬缺页错误/主要缺页错误。这种情况是,在物理内存中没有对应的页。这个时候就需要让CPU打开磁盘等外设存储设备,将要使用的内容重新加载到物理内存对应的页中,然后通过MMU来建立虚拟地址和物理地址的映射!
- Soft Page Fault 也被称为 Minor Page Fault,翻译为软缺页错误/次要缺页错误。这种情况是,此时物理内存中其实是有对应的页的,只不过这个页可能是被别的线程/进程导入进来的,这个时候只需要通过MMU建立一下连接即可!(一般出现在共享区,如动态库)
- Invalid Page Fault,翻译为无效缺页错误。比如野指针的解引用,导致segment fault段错误,直接让进程挂掉。也有可能是越界访问(权限不对)。
但是,这里还是需要说明的一点,越界访问,很可能是检测不出来的:
int i = 0;
int array[10];
for(int i = 0; i <= 10; ++i){
array[i] = 0;
}
这种情况,非常不巧的时候,可能会导致死循环!
如果栈向下增长,朝着地址小的地方生长,那么变量i和数组array的关系如下:

当使用的时候,一旦使用到array[10],即*(array + 10),就会把array[10]也就是变量i改成0。这样子就导致了i的值始终小于等于10。
这个必然是越界访问了,但是因为这个地址是合法的!操作系统也不知道用户层是人为就要这么做,还是出错了。操作系统也识别不到的,这种情况需要自己debug!
但是,这是一种巧合,很有可能也出现不了这种情况!
页表结构设计的巧妙之处
我们再来仔细观察分页存储结构的页表:
我们会发现,页目录有1024个,也就是1024个页表。每个页表又对应1024个页框,我们可以发现,正好是1024 * 1024 = 1048576个页框,这正好对应数组struct page mem[]。
而且,找到页框后,就是根据虚拟地址的低12位来查找内容。因为12个比特位正好能够表示212个地址,即4096个,这不正好对应4KB吗?
所以,低12位对应的一定是一整个页框,即内存块!这些都是设计好的。
所以,找到页框 = 找到页框起始地址
虚拟地址低12位 = 页框内地址的偏移量
我们也还知道,可执行文件是ELF格式,是采用平坦编址模式的。最后放在内存中也都是基地址 + 偏移量来找到相关的数据的!相同类型的数据又会天然的放在一起,ELF格式中将数据节合并成代码段,本质就是合并成一个个的内存块大小的数据块!
所以,要找到一个段内的数据,一般来说都是比较靠近的。所以,虚拟地址只要前20位保证是一样的,那么就一定在同一个页框(4KB)内。
最后,我们就会惊奇的发现,操作系统的所有操作,最终都是为了这个4KB内存块(32位系统)来服务的!这是Linux系统设计的巧妙之处!
线程优缺点
线程优点
- 创建一个新线程的代价要比创建一个新进程小得多
- 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多:
2.1. 最主要的区别是线程的切换虚拟内存空间依然是相同的,但是进程切换是不同的。这两种上下文切换的处理都是通过操作系统内核来完成的。内核的这种切换过程伴随的最显著的性能损耗是将寄存器中的内容切换出。
2.2. 另外一个隐藏的损耗是上下文的切换会扰乱处理器的缓存机制。简单的说,一旦去切换上下文,处理器中所有已经缓存的内存地址一瞬间都作废了。还有⼀个显著的区别是当你改变虚拟内存空间的时候,处理的页表缓冲 TLB (快表)会被全部刷新,这将导致内存的访问在一时间内相当的低效。但是在线程的切换中,不会出现这个问题,当然还有硬件cache。 - 线程占用的资源要比进程少的很
- 能充分利用多处理器的可并行数量
- 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
- 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
- I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。
线程缺点
- 性能损失
一个很少被外部事件阻塞的计算密集型线程往往无法与其它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。 - 健壮性降低
编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。 - 缺乏访问控制
进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。 - 编程难度提高
编写与调试一个多线程程序比单线程程序困难得多
但也正是因为线程不好控制,才使得线程好用!因为线程间通信不需要做前置操作,只要是再一个进程里面的线程,就能够天然地看见同一份资源!
线程异常
- 单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃。
- 线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止,该进程内的所有线程也就随即退出。
Linux进程 vs 线程
本质区别
进程是分配系统资源的基本实体
线程是CPU调度的基本单位
进程,其实可以看作单线程的进程!主要是强调进程独占自己的代码和数据。进程间通信需要使用较为繁琐的IPC通信机制!
线程,在Linux系统使用进程模拟出来的,只不过更加强调的是数据的共享:
• 文件描述符表
• 每种信号的处理方式(SIG_ IGN、SIG_ DFL或者自定义的信号处理函数)
• 当前工作目录
• 用户id和组id
用一张图就可与很轻松的理解线程和进程的区别:

更深层次的区别——CPU调度切换的成本差异:
这个内容在线程的优点部分讲过:

最直观的差距就是:进程切换的时候,CPU需要保存当前进程地上下文,还需要切换页表,进程地址空间等。而线程是共享同一份的,不需要这些操作。
但是上述差距听着还是不够大。
主要是最大的差距就是缓存的切换!即快表TLB和缓存cache。
快表是CPU和页表之间地中间层,如果是切换进程,那么TLB内的缓存数据也就没用了,会被直接刷新。然后需要重新导入新的进程的相关缓存。但是切换线程就不用怕,因为线程公用的是一份,所以缓存还是有效的。但是,如果是切换到不同进程的线程那也是做废了!
cache这也是一个缓存器件,即CPU执行代码的时候,为了提高执行效率,会在CPU内部集成一个器件,即cache。当执行到一句代码的时候,就把该代码周边的数据都导入到缓存cache中。然后不断地进行缓存。用法和快表类似,这是方便CPU查找数据用的。
所以在进程切换的时候,cache也是面临和TLB一样的问题!而线程(同一进程下)不需担心!
资源占用区别
前面说到:
进程更加注重资源的独占,通信需要IPC机制
线程资源都是共享的,小部分独占
线程其实也是有独占的资源的:
线程ID
一组寄存器
栈
errno
信号屏蔽字
调度优先级
没有看错,栈区其实每个线程是会有独占的情况的。但是这里只是做了解,等到学习进程控制的时候再来详谈!

2743

被折叠的 条评论
为什么被折叠?



