24.线程概念和控制(一)
一、Linux线程概念
1.概念角度,感性地理解线程
从教材角度:
进程 = 内核数据结构 + 代码和数据(执行流)
线程:进程内部的一个执行分支(执行流)
内核和资源:
进程:承担系统分配资源的基本实体
线程:CPU调度的基本单位
进程模拟线程:
进程访问的大部分数据都是通过进程地址空间访问的!-> 进程地址空间就是窗口,创建一个进程,把窗口共享,分配给多个task_struct,划分地址空间和虚拟地址范围 -> 那么就模拟出线程了(线程是进程内部的一个执行分支)
不同视角下的线程:
Linux操作系统视角:执行流
CPU视角:轻量级进程
初步理解:
结论1:Linux线程可以用进程来模拟
结论2:对资源的划分,本质就是对进程虚拟地址范围划分。虚拟地址,就是资源的代表。
结论3:代码区划分?函数就是虚拟地址(逻辑地址)空间的集合!就是让线程未来执行ELF程序的不同部分即可。
结论4:原理解的进程是进程的一种特殊情况,只有一个task_struct,一般情况有多个。
结论5:Linux的线程,就是轻量级进程或者用轻量级进程模拟实现的。
操作系统学科只提供概念,具体的操作系统提供设计思路和设计方案。
问题1:为什么要这样设计?
复用代码,用轻量级进程来模拟线程,使得兼容性和代码的可维护性比较好。以及调度算法和管理都能沿用,不需要重新设计。
问题2:其他平台,比如windows也是这样设计的吗?
windows给线程设计了单独的结构TCB。
线程和进程的理解和区别:
进程强调独立性,部分共享(进程间通信)
线程强调共享,部分独占
具体例子理解:
家庭是分配社会资源的基本载体,而家庭内有很多成员,成员有各自的目标和资源,家庭内有公共的资源。(家庭->进程,成员->线程)
2.从理性角度,资源划分(虚拟到物理,页表,页表相关概念,部分内存管理的理解)
物理内存在是连续不断的,并没有内存块的概念。(内存块是操作系统的概念)
OS访问磁盘是以块为单位来访问,大小为4KB。可执行程序就是文件,在磁盘中存储。可执行程序,存储的时候,天然就是4KB单位存储的,无论属性还是内容。
理解:4GB/4KB=1,048,576
4KB划分,是操作系统划分的,不管是磁盘(文件系统)还是内存。OS要不要管理页框?
先描述,在组织。
先有struct page结构,再用struct page mem[1048576]; 数组管理。
如何得知具体的page对应内存块的物理地址?(注:内存块不等于page)
每一个page都有下标 -> 假设第一个page对应的内存块起始物理地址从0开始,每一个page对应内存块起始物理地址就知道了(index*4KB)-> 具体内存块物理地址 = 内存块起始物理地址 + 页内(4KB)偏移 -> 因此不需要在page内保存其对应内存块的物理地址
文件结构struct file中有adress_space,里面有radix_tree,管理的是page,因此文件可以通过拿到page的物理地址,从而计算出下标,拿到page对应的内存块的物理地址。
申请物理内存,本质就是在:1.查数据,改page 2.建立内核数据结构的对应关系。
page结构中几个比较重要的参数:
flags :用来存放页的状态。这些状态包括页是不是脏的,是不是被锁定在内存中等。flag的每一位单独表示一种状态,所以它至少可以同时表示出32种不同的状态。这些标志定义在<linux/page-flags.h>中。
_mapcount :表示在页表中有多少项指向概页,也就是这一页被引用了多少次。当计数值变为-1时,就说明当前内核并没有引用这一页,于是在新的分配中就可以使用它。
virtual :是页的虚拟地址。通常情况下,它就是页在虚拟内存中的地址。有些内存(即所谓的高端内存)并不永久地映射到内核地址空间上。在这种情况下,这个域的值为NULL,表示该物理页没有固定的内核虚拟地址,需要的时候,必须动态地映射这些页。
拓展高端内存:
32位系统的地址空间限制:在32位系统中,理论地址空间为4GB。Linux默认将内核空间划分为高1GB(如
0xC0000000~0xFFFFFFFF
),用户空间为低3GB。若物理内存超过内核直接映射区(Direct Mapping Area,通常3GB~3GB+896MB),剩余物理内存(即“高端内存”,约超过896MB的部分)无法被内核空间永久映射。
页表
以32位操作系统举例,虚拟地址是4个字节,32比特位。
从高位到低位,第一组:高10位,第二组:次高10位,第三组:最后12位
取值范围分别是[0,1023],[0,1023],[0,4095]。
页表分为两级:
第一级:页目录,页目录存放的是页表的地址,一共1024个
第二级:页表,页表存放的是物理页的地址,一共1024个
为什么要分级设计?
如果不分级设计,一个页表若要映射4GB空间,4GB/4KB=1,048,576,32位地址大小是4字节,1048576*4B=4MB,页表大小就是4MB。而且页表自身不能做到内存块的分离,页表自身需要的内存页就必须是连续的,总共要4MB/4KB=1024个内存页。页表设计初衷就是为了实现虚拟地址对应的物理页是离散分布的,自身就违背了。此外,根据局部性原理可知,很多时候进程在一段时间内只需要访问某几个页就可以正常运行了。因此也没有必要⼀次让所有的物理页都常驻内存。
结论:
细节1:申请内存->查找数组->找到没有被使用的page->page index->物理页框地址
细节2:写时拷贝,缺页中断,内存申请等等,背后可能都要重新建立新的页表和映射关系的操作。
细节3:进程,一张页目录+n张页表构建的映射体系,虚拟地址是索引,物理页框地址是目标,物理地址 = 虚拟地址低12位(页内偏移) + 页框地址 。
细节4:为什么是低12位数字?ELF格式编址,页框大小都是4KB。
页目录的物理地址被 CR3 寄存器指向,这个寄存器中,保存了当前正在执行任务的页目录地址。虚拟地址到物理地址转化有CPU内MMU自动完成。
3.线程深刻理解
执行流看到的资源本质:在合法的情况下,你有多少虚拟地址(虚拟地址是资源的代表)
虚拟地址空间 mm_struct +vm_area_struct 本质:进行资源的统计数据和整体数据
页表是一张从虚拟到物理的地图。
资源划分:本质是地址空间划分
资源共享:本质是虚拟地址的共享
线程进行资源划分:本质是划分地址空间,获得一定范围的合法虚拟地址,在本质,就是在划分页表(虚拟地址是索引,页表的范围)。
线程进行资源共享:本质是对地址空间的共享,在本质,就是对页表条目的共享。