- 内存控制单元(MMU)通过分段单元硬件电路将逻辑地址转换为线性地址。通过分页单元硬件电路将线性地址转换为物理地址。
- 逻辑地址由段标志符和段内地址偏移组成。段标志符也成为段选择符。
- 段寄存器用来保存段选择符。cs(代码段寄存器);ss(栈段寄存器);ds(数据段寄存器);其中cs有两位,指明CPL(Current Privilege Level);
- 段描述符有8个字节,保存在GDT或者LDT中,GDT在主存中的地址和大小保存在gdtr控制寄存器中。
- 段描述符8字节长。段选择符高13位左移3位即为其代表的段描述符在GDT或LDT内的地址。eg:gdtr寄存器表明GDT在0x00020000内存地址,且段描述符表明index为2,则代表的段描述符地址即为0x00020000+(2<<3);GDT第一项为0,表示逻辑地址引用无效,引起一个处理器异常。
- 段选择符由index/TI/RPL三个字段组成,其中RPL表明请求这特权。TI表明对应的段描述符在GDT中还是在LDT中,index表明其在GDT或LDT中的索引。
段 |
Base |
G |
Limit |
S |
Type |
DPL |
D/B |
P |
/ |
包含段首地址的线性地址 |
0:段大小字节为单位,否则以4096字节倍数计算 |
段的长度(G为0,则段大小1字节到1MB,否则4KB到4GB |
0表示系统段,否则普通代码段或数据段 |
段类型和存储权限 |
特权描述级 |
|
|
用户代码段 |
0x00000000 |
1 |
0xfffff |
1 |
10 |
3 |
1 |
1 |
用户数据段 |
0x00000000 |
1 |
0xfffff |
1 |
2 |
3 |
1 |
1 |
内核代码段 |
0x00000000 |
1 |
0xfffff |
1 |
10 |
0 |
1 |
1 |
内核数据段 |
0x00000000 |
1 |
0xfffff |
1 |
2 |
0 |
1 |
1 |
- 相应的段选择符由宏__USER_CE, __USER_DS, __KERNEL_CS, __KERNEL_DS定义。例如对内核代码段寻址,则将__KERNEL_CS装入cs寄存器。
- 在Linux下,逻辑地址与线性地址一致。
- 当CPL改变时,ds/ss也都要修改为对应特权级的段选择符。
- 内核态执行的段只有一种,由__KERNEL_CS定义。
- 分页单元负责将线性地址转换为物理地址。如果内存访问无效,则会产生一个缺页异常。线性地址映射到物理地址的数据结构称为页表。
- 分页单元处理 4KB 的页。32 位的线性地址中,其高 10 位作为目录(Directory),中间 10 位作为页表(Table),最低 12位作为页内偏移。
- 每个活动的进程都有分配的页目录。不过不需要为所有页表马上分配 RAM。只在进程实际需要一个页表的时候才给改页表分配 RAM。
- 正在使用的页目录的物理地址存放在控制寄存器 cr3 中。
- 在分页单元与主内存间,插入高速缓存单元。高速缓存单元包含一个硬件高速缓存内存和一个高速缓存控制器。
- PCD标志指明访问包含该页框的数据时,高速缓存功能必须被开启还是禁用。PWT 指明当数据写到页框时,使用回写策略还是通写策略。
- 转换后援缓冲器 TLB(Translation Lookaside Buffer)用以加快线性地址转换。
- Linux 从 2.6.11 开始从三级分页模型替换为四级分页模型。
- 页全局目录(Page Global Directory)
- 页上级目录(Page Upper Directory)
- 页中间目录(Page Middle Directory)
- 页表(Page Table)
- 每个进程都有自己的页全局目录和页表集。当进程切换时,Linux 将 cr3 控制寄存器的内容保存在前一个执行进程的描述符中。随后将下一个要执行的进程的进程描述符装入cr3 寄存器中。于是当新进程在 cpu 执行时,分页单元指向一组正确的页表。
- 不可用的物理地址范围内的页框和含有内核代码与已初始化的数据结构的页框作为保留,不会被动态分配或者交换到磁盘。
- 进程的线性地址从0x00000000 到0xbfffffff 对于用户态和内核态都是可寻址的。从0xc0000000 到0xffffffff 只有内核态才可以寻址。
- 对于内核,进程的目的就是担当分配系统资源(CPU 时间、内存等)的实体。每个线程都代表进程的一个执行流。
- 进程描述符是 task_struct 类型结构,包含了一个进程相关的所有信息。
- task_struct->state 字段。进程状态;TASK_RUNNING/TASK_INTERRUPTIBLE/TASK_UNINTERRUPTIBLE/TASK_STOPPED/TASK_TRACED------EXIT_ZOMBIE/EXIT_DEAD
- 内核对进程的大部分引用都是通过进程描述符指针进行。进程标识符 process ID,即 PID;内核将不同的 PID 与系统中每个进程或轻量级进程相关联。
- Linux 引入线程组。一个线程组所有线程使用与该线程组的领头线程(thread group leader)相同的 PID;被存入进程描述符 tgid 中。getpid( )系统调用返回当前进程 tgid 而非 pid;线程组的领头线程的 tgid 的值与 pid 的值相同。
- Linux 将 thread_info 结构和进程内核栈存放在两个连续的页框中。
union thread_union {
struct thread_info thread_info;
unsigned long stack[1024]; //4KB
}
- 将线程描述符 thread_info 存放在内存的开始,栈则从内存末端开始生长。有指向栈顶的指针 esp,通过屏蔽 esp 的低 12 位(如果 thread_union 栈 8kb 则屏蔽 13 位)就可以得到 thread_info 的指针。通过 thread_info 就可以得到 task_struct。Linux 内核中的 current 宏就是通过屏蔽 esp 的低位得到 thread_info 间接得到当前进程的task_struct 的。
- 内核双向链表 list_head 不保存数据,而做内嵌到数据中。利用的是 offsetof 这个宏
- 进程链表,链接所有进程描述符。task_struct 结构包含 list_head tasks,其 prev 和 next 分别指向前面和后面的 task_struct 中的 list_head tasks。进程表表头是 init_task 描述符,就是 0 进程或者说是 swapper 进程的进程描述符。init_task 中的 tasks->prev 指向链表中插入的最后一个进程描述符的 tasks 字段。于是构成了一个环。
- task_struct 通过字段 list_head run_list 将自己链入该进程优先权对应的可运行进程链表。
- 所有的运行队列(优先权0-139)通过 prio_array_t 数据结构组织
类型 |
字段 |
描述 |
int |
nr_active |
链表中进程描述符的数量 |
unsigned long[5] |
bitmap |
优先权位图。 |
struct list_head[140] |
queue |
140个优先权队列头结点 |
- 为了可以通过进程 PID 得到对应的进程描述符指针。四个散列表地址存入了 pid_hash 数组。同时利用链表来解决冲突。
Hash 表类型 |
字段名 |
说明 |
PIDTYPE_PID |
pid |
进程的 PID |
PIDTYPE_TGID |
tgid |
进程组领头进程的 PID |
PIDTYPE_PGID |
pgrp |
进程组领头进程的 PID |
PIDTYPE_SID |
session |
会话领头进程的 PID |
struct __wait_queue_head{
//自旋锁
spinlock_t lock;
struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;
//队列元素
struct __wait_queue{
unsigned int flags;
struct task_struct* task;
//唤醒方式
wait_queue_func_t func;
struct list_head task_list;
};
typedef struct __wait_queue wait_queue_t;
- 所有的进程共享 CPU 寄存器。于是在恢复一个进程执行之前,内核要确保每个寄存器装入了挂起进程时的值。寄存器要恢复的一组数据成为硬件上下文。Linux 中硬件上下文一部分保存在 TSS 段,剩余部分放在内核态堆栈中。
- Linux 2.6使用软件执行进程切换。
- 任务状态段 TSS。每个 TSS 都有自己的任务状态段描述符(Task State Segment Descriptor,TSSD)。
- Linux创建的 TSSD 存放在 GDT 中。GDT 基址在 gdtr进村器中。每个 CPU 的 tr 寄存器包含对应的 TSS的 TSSD 选择符。包含 2 个隐藏的非编程字段:TSSD的 base 字段和 Limit 字段。
- Linux 中的 TSS 是为 CPU 创建而非进程。于是硬件上下文被保存到到了进程描述符中的 thread_struct thread 字段中。保存了大部分的 CPU 寄存器,而如 eax,ebx 等通用寄存器则是保存在内核堆栈中的。