Linux试图在硬件无关的源代码与硬件相关的源代码之间保持清晰的界限,为了做到这点,在arch和include目录下包含了很多子目录,以对应Linux所支持的不同硬件平台。
eg:
alpha:HP的Alpha工作站
arm:基于ARM处理器的计算机和嵌入式设备
cris:CISC cpu
frv:基于Fujitsu FR-V系列微处理器的嵌入式系统
……
第二章 内存寻址
在80x86微处理器上,有三种不同的地址:
(1)逻辑地址
包含在机器语言指令中用来指定一个操作数或一条指令的地址,每一个逻辑地址都由一个段和偏移量组成,偏移最指明从段开始的地方到实际地址之间的距离。
(2)线性地址(也称虚拟地址)
是一个32位无符号整数,可以用来表示高达4GB的地址
(3)物理地址
用于内存芯片级内存单元寻址。它们与从微处理器的地址引脚线发送到内存总线上的电信号相对应,物理地址由32位或36位无符号整数表示。
硬件中的分段
从80286模型开始,Intel处理器以两种不同的方式进行地址的转换,分别为实模式和保护模式,实模式存在的主要原因是要维持处理器与早期模型的兼容。
段选择符
是一个16位长的字段,为了快速方便地找到段选择符,处理器提供段寄存器,段寄存器的唯一目的是存放段选择符(cs,ss,ds,es,fs和gs)。
cs:代码段寄存器,指向包含程序指令的段
ss:栈段寄存器,指向包含当前程序栈的段
ds:数据段寄存器,指向包含静态数据或者全局数据段
其他3个段寄存器作为一般用途,可以指向任意的数据段
cs寄存器还有一个很重要的功能:它包含一个两位的字段,用以指明CPU的当前特权级(Current Privilege Level,CPL)。值为0代表最高优先级,而值3代表最低优先级。Linux只用0级和3级,分别称为内核态和用户态。
字段名 | 描述 |
---|---|
index | 指定了放在GDT或LDT中的相应段描述符的入口 |
TI | table indicator:指明段描述符是在GDT中(TI=0)或在LDT中(TI=1) |
RPL | 请求者特权级: |
段描述符
每个段由一个8字节的段描述符表示,它描述了段的特征。段描述符放在全局描述表或局部描述符表中。每个进程除了存放在GDT中的段之外如果还需要创建附加的段,就可以有自己的LDT。GDT在主存中的地址和大小存放在gdtr控制寄存器中,当前正在被使用的LDT地址的大小放在ldtr控制寄存器中。
字段名 | 描述 |
---|---|
Base | 包含段的首字节的线性地址 |
G | 粒度标志:如果该位清0,则段大小以字节为单位,否则以4096字节的倍数计 |
Limit | 存放段中最后一个内存单元的偏移量,从而决定段的长度。如果G被置为0,则 一个段的大小在1个字节到1MB之间变化;否则,将在4KB到4GB之间变化 |
S | 系统标志:如果它被清0,则这是一个系统段,存储诸如LDT这种关键的 数据结构,否则它是一个普通的代码段或数据段 |
Type | 描述了段的类型特征和它的存取权限 |
DPL | 描述符特权级字段:用于限制对这个段的存取。它表示为访问这个字段而要求的 CPU最小的优先级。 |
P | Segment-Present标志:等于0表示段当前不在主存中。Linux总是把这个标志设为1, 因为它从来不把整个段交换到磁盘中 |
D或B | 称为D或B的标志,取决于是代码段还是数据段 |
AVL标志 | 可以由操作系统使用,但是被Linux忽略 |
Linux中广泛采用的段描述符类型
(1)代码段描述符
代表一个代码段,它可以放在GDT或LDT中
(2)数据段描述符
代表一个数据段,它可以放在GDT或LDT中
(3)任务状态段描述符(TSSD)
代表一个任务状态段(Task State Segment, TSS),也就是说这个段用于保存处理寄存器的内容。它只能出现在GDT中。根据相应的进程是否正在CPU上运行,其Type字段的值分别为11或9
(4)局部描述符表描述符(LDTD)
代表一个包含LDT的段,它只出现在GDT中。相应的Type字段的值为2。
快速访问段描述符
为了加快逻辑地址到线性地址的转换,80x86处理器提供了一种附加的非编程的寄存器,供6个可编程寄存器使用。每一个非编程的寄存器含有8个字节的段描述符,由相应的段寄存器中的段选择符来指定。每当一个段选择符被装入段寄存器时,相应的段描述符就由内存装入到对应的非编程CPU寄存器,从那时起,针对那个段的逻辑地址就可以不访问主存中的GDT或LDT,处理器只需直接引用存放段描述符的CPU寄存器即可。仅当段寄存器的内容改变时,才有必城访问GDT或LDT。
逻辑地址转换为线性地址的步骤
(1)先检查段选择符的TI字段,以决定段描述符保存在哪一个描述表中(GDT或LDT)
(2)从段选择符的index字段计算段描述符的地址,index字段的值乘8,这个结果与gdtr或ldtr寄存器中的内容相加
(3)把逻辑地址的偏移量与段描述符Base字段的值相加就得到线性地址
注:有了与段寄存器相关的不可编程寄存器,只有当段寄存器的内容被改变时才需要执行前两个操作
Linux中的分段
实际上,分段和分页在某种程序上有点多余,因为它闪都可以划分进程的物理地址空间:分段可以给每一个进程分配不同的线性地址空间,而分页可以把同一线性地址空间映射到不同的物理空间。与分段相比,Linux更喜欢使用分页方式:
(1)当所有进程使用相同的段寄存器值时,内存管理变得更简单,也就是说它们能共享同样的一组线性地址
(2)RISC体系结构对分段的支持很有限
运行在用户态的所有Linux进程都使用一对相同的段来对指令来数据寻址。这两个段就是所谓的用户代码段和用户数据段。类似地,运行在内核态的所有Linux进程都使用同一对相同的段对指令和数据寻址:内核代码段和内核数据段。对应于宏__USER_CS, __USER_DS, __KERNEL_CS, __KERNEL_DS
Linux GDT
在单处理器系统中只有一个GDT,而在多处理器系统中每个CPU对应一个GDT。所有的GDT都存放在cpu_gdt_table数组中,而所有的GDT的地址和它们的大小被存入在cpu_gdt_descr数组中。
Linux的LDT
大多数用户态下的Linux进程不使用局部描述符表,这样内核就定义了一个缺省的LDT供大多数进程共享。缺省的局部描述符表存放在default_ldt数组中。
常规分页
从80386起,Intel处理器的分页单元处理4KB的页。32位的线性地址分成3个域:
(1)目录 ,最高10位
(2)页表, 中间10位
(3)偏移量, 最低12位
正在使用的页目录的物理地址寄放在控制器cr3中,线性地址内的Directory字段决定页目录中的目录项,而目录项指向适当的页表,地址的Table字段依次又决定页表中的表项,而表项含有页据页框的物理地址。Offset字段决定页框内的相对位置,由于它是12位长,故每一页含4096B的数据。
页目录项和页表项有同样的结构:
字段名 | 说明 |
---|---|
present标志 | 如果被置1,所指的页或页表就在主存中;如果该标志为0,则这页不在主存中。 如果执行一个地址转换所需的页表项或页目录项中present标志被清0,那么分页 单元就把该线性地址存放在控制寄存器cr2,并产生14号异常:缺页异常 |
field包含页框物理 地址最高20位 | 由于每一个页框有4KB的容量,它的物理地址最低12位总是0 |
accessed标志 | 每当分页单元对相应页框进行寻址时就设置这个标志。分页单元从来不重置这个 标志,而是必须由操作系统去做 |
dirty标志 | 只应用于页表项,每当对一个页框进行写操作时就设置这个标志。 由操作系统来设置 |
read/write标志 | 含有页或页表的存取权限 |
user/supervisor标志 | 含有访问或页表所需的特权级 |
PCD和PWT标志 | 控制硬件高速缓存处理页或页表的方式 |
Page Size标志 | 只用于页目录项。如果设置为1,则页目录项指的是2MB或4MB的页框 |
Global标志 | 只应用于页表项。用来防止常用页从TLB高速缓存中刷新出去,只有在cr4寄存器的页 全局启用标志置位时这个标志才起作用 |
扩展分页
从Pentium模型开始,80x86引入了extended paging,它允许页框的大小为4MB而不是4KB,在这种情况下,内核可以不用中间页表进行地址转换,从而节省内存并保留TLB项。
硬件高速缓存
为了缩小CPU和RAM之间的速度不匹配,引入了硬件高速缓存。硬件高速缓存基于著名的局部性原理:由于程序的循环结构及相关数组可以组织成线性数组,最近最常用的相邻地址在最近的将来又被用到的可能性极大。
多处理器系统的每一个处理器都有一个单独的硬件高速缓存,因此它们需要额外的硬件电路用于保持高速缓存内容的同步。
转换后援缓冲器
TLB(Translation Lookaside Buffer)的高速缓存用于加快线性地址的转换。
Linux中的分页
Linux采用了一种同时适用于32位和64位系统的普通分页模型。采用四级分页模型:
(1)页全局目录
(2)页上级目录
(3)页中间目录
(4)页表
函数与宏
PAGE_SHIFT
指定Offset字段的位数;当用于80x86处理器时,它产生的值为12。这个宏由PAGE_SIZE使用以返回页的大小。最后,PAGE_MASK宏产生的值为0xfffff000,用以屏蔽Offset字段的所有位。
PMD_SHIFT
指定线性地址的Offset字段和Table字段的总位数,PMD_SIZE宏用于计算由页中间目录的一个单独表项所映射的区域大小,也就是一个页表的大小。PMD_MASK宏用于屏蔽Offset字段与Table字段的所有位。
当PAE(物理地址扩展)被禁用,PMD_SHIFT产生的值为22(Offset的12位+Table的10位),PMD_SIZE产生的值为4MB,PMD_MASK产生的值为0xffc00000。相反,当PAE被激活时,PMD_SHIFT产生的值为21(Offset4 12位+Table的9位),PMD_SIZE=2MB,PMD_MASK=0xffe00000.
大型页不使用最后一级页表,所以产生的大型页尺寸的LARGE_PAGE_SIZE宏等于PMD_SIZE,而在大型页中的用于屏蔽Offset字段和Table字段的所有位的LARGE_PAGE_MASK宏,就等于PMD_MASK。
PUD_SHIFT
确定页上级目录项所能映射的区域大小的对数。PUD_SIZE宏用于计算页全局目录中的一个单独表项所能影射的区域大小。PUD_MASK宏用于屏蔽Offset字段、Table字段、Middle Dir字段和Upper Dir字段的所有位
在80x86处理器上,PUD_SHIFT总是等价于PMD_SHIFT,而PUD_SIZE则等于2MB或4MB
PGDIR_SHIFT
确定页全局目录项所能映射的区域大小的对数。PGDIR_SIZE宏用于计算页全局目录中一个单独表项所能映射区域的大小。PGDIR_MASK宏用于屏蔽Offset、Table、Middle Dir及Upper Dir字段的所有位。
PTRS_PER_PTE, PTRS_PER_PMD, PTRS_PER_PUD, PTRS_PER_PGD
用于计算页表、页中间目录、页上级目录和页全局目录表中表项的个数。当PAE被禁用时,分别为1024,1,1,1024,当PAE被激活时,产生的值分别为512,512,1,4
物理内存布局
一般来说,Linux内核安装在RAM中从物理地址0x00100000开始的地方,也就是说,从第二个MB开始。所需页框总数依赖于内核的配置方案。为什么内核没有安装在RAM的第一个MB开始的地方?原因如下:
(1)页框0由BIOS使用
(2)物理地址从640KB到1MB(0x000a0000~0x000fffff)之间范围通常留给BIOS例程,并且映射ISA图形卡的同部内存。
(3)第一MB内的其他页框由特定计算机模型保留
进程页表
进程的线性空间分成两部分:
(1)从0x00000000~0xbfffffff的线性地址,无论进程运行在用户态还是内核态都可以寻址
(2)从0xc0000000~0xffffffff的线性地址,只有内核态的进程才能寻址
第3章 进程
从内核的观点看,进程的目的就是担当分配系统资源(CPU时间、内存等)的实体。
进程描述符
进程描述符都是task_struct类型结构,它不仅包含了很多进程属性的字段,而且一些字段还包括了其他数据结构的指针。
第4章 中断和异常
中断通常分为同步中断和异步中断:
(1)同步中断是当指令执行时由CPU控制单元产生的,之所以称为同步,是因为只有在一条指令终止后CPU才会发出中断。
(2)异步中断是由其他硬件设备依照CPU时钟信号随机产生的
在Intel微处理器手册中,把同步和异步中断分别称为异常和中断。
第6章 定时测量
很多计算机化的活动都是由定时测量来驱动的,这常常对用户是不可见的。Linux内核必需完成两种主要的定时测量:
(1)保存当前的时间和日期,以便可以返回给用户程序时间,也可以由内核本身把当前时间代为文件或网络包的时间戳。
(2)维持定时器,这种机制能够告诉内核或用户程序某一时间间隔已经过去了。
实时时钟(RTC)
所有的PC都包含一个叫做实时时钟的时钟,它是独立于CPU和所有其他芯片的。CMOS RAM的RTC被集成在一个芯笥上。
最后欢迎大家访问我的个人网站: 1024s