线程的实现

一、线程的实现

先在内核空间中申请一页内存(4KB),用线程 PCB 结构的指针,指向这块内存。

我们先来看看 PCB 结构都有哪些字段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
struct task_struct {
// 内核线程所用的内核栈
uint32_t* self_kernel_stack;
pid_t pid;
// 状态
enum task_status status;
char name[TASK_NAME_LEN];
// 优先级
uint8_t priority;
// 每次在处理器上执行的时钟滴答数
uint8_t ticks;
// 此任务自从上 CPU 运行后至今占用了多少 cpu 滴答数
// 任务运行了多久
uint32_t elapsed_ticks;
// 用于线程在一般队列中的节点(比如:就绪队列或者其他队列)
struct list_elem general_tag;
// 用于线程队列 thread_all_list 中的节点
struct list_elem all_list_tag;
// 进程自己页表的虚拟地址
// 如果是线程,则此字段为 NULL
uint32_t* pg_dir;
// 用户进程的虚拟地址
struct virtual_addr user_process_vaddr;
// 用户进程内存块描述符
struct mem_block_desc u_block_desc[DESC_CNT];
// 已打开文件数组
int32_t fd_table[MAX_FILES_OPEN_PER_PROC];
// 进程所在的工作目录的 inode 编号
uint32_t cwd_inode_nr;
// 父进程 pid
int16_t parent_pid;
// 栈的边界标记,用于检测栈的溢出
// 这个字段因为要作为边界标记,所以必须放在结构体的末尾
// 我们的 PCB 和 0 级栈是在同一页中,栈位于页的顶端并向下发展
// 因此担心压栈过程中会把 PCB 中的信息给覆盖,所以每次在线程或进程调度时要判断是否触及到了进程信息的边界
// 也就是判断 stack_magic 的值是否为初始化的内容。其实 stack_magic 是一个魔数
uint32_t stack_magic;
};

然后初始化 PCB,包括:

查看更多

用虚拟地址访问页表

虚拟地址和物理地址是乱序映射的。

一、查看分页的结果

在开启分页后,我们可以使用 info tab 命令看到页表中虚拟地址到物理地址的映射关系。

1
2
3
4
5
6
cr3: 0x000000100000
0x00000000-0x000fffff -> 0x000000000000-0x0000000fffff
0xc0000000-0xc00fffff -> 0x000000000000-0x0000000fffff
0xffc00000-0xffc00fff -> 0x000000101000-0x000000101fff
0xfff00000-0xffffefff -> 0x000000101000-0x0000001fffff
0xfffff000-0xffffffff -> 0x000000100000-0x000000100fff

cr3 寄存器显示的是页目录表的物理地址。左边的虚拟地址,右边的是物理地址。

查看更多

实现内存分页

为了计算机安全,用户进程必须运行在低特权级,当用户进程需要访问硬件相关的资源时,需要向操作系统申请,由操作系统去做,之后将结果返回给用户进程。进程可以有无限多个,而操作系统只有一个,所以操作系统必须共享给所有用户进程。

也就是,用户的代码加上所需要的操作系统中的部分代码才算完整的程序。

用户进程要共享操作系统,如何共享呢?只要操作系统属于用户进程的虚拟地址空间即可。

我们学习 Linux 操作系统,在用户进程 4GB 虚拟地址空间中。3GB-4GB 划分给操作系统;0-3GB 是用户进程自己的虚拟空间。因此,为了实现操作系统,让所有用户进程的 3GB-4GB 的虚拟地址空间都指向同一个操作系统,指向同一片物理页地址,这片物理页地址是操作系统的实体代码。

一、分页机制

分页机制有页目录表,页目录表中是页目录项,其中记录的是页表的物理地址以及相关属性。

查看更多

8.内存分页机制

内存分页机制

一、段的换入换出

在保护模式下,CPU 引用一个段时,都要先查看段描述符。很多时候,段描述符存在于描述符表(GDT 或 LDT),但与此对应的段并不在内存中,也就是说,CPU 允许在描述符中已注册的段不在内存中存在。这就是它提供给软件使用的策略,我们利用它实现段式内存管理。

  • 如果该描述符中的 P 位为 1,表示该段在内存中存在。访问过该段后,CPU 将段描述符中的 type 中的 A 位置 1。表示近来刚访问过该段。
  • 相反,如果 P 位为 0,说明内存中并不存在该段,这时候 CPU 抛出个 NP(段不存在)异常,转而去执行中断描述符表中 NP 异常对应的中断处理程序,此中断处理程序是操作系统负责提供的,该程序的工作是将相应的段从外存(比如硬盘)中载入到内存,并将段描述符的 P 位置 1,中断处理函数结束后返回,CPU 重复执行这个检查,继续查看该段描述符的 P 位,此时已经为 1 了,再检查通过后,将段描述符的 A 位置 1
查看更多

13.内存

一、规划内存池

1. 物理内存池规划

内核和用户进程都要运行在物理内存中,我们将物理内存划分成两部分,一部分用来运行内核,一部分用来运行用户进程。操作系统为了能够正常运行,必须给自己预留出足够的内存才行,否则有可能会出现因为物理内存不足,导致内核自己都无法正常运行的情况。

因此我们把物理内存池分为:内核内存池、用户内存池。内存池中管理的是一个个大小为 4K 的内存块,从内存池中获取的内存大小至少为4KB 或者 4KB 的倍数。目前我们将内核内存池和用户内存池的大小设为一致,即各占一半的物理内存。

2. 虚拟内存池规划

我们回顾下分页机制:

  • 分页机制下程序中的地址都是虚拟地址
查看更多

3.操作显示器

操作显示器

一、IO 接口

CPU 通过 “IO接口” 来与外设通信。“IO接口” 是连接 CPU 与外部设备的逻辑控制部件,分为硬件和软件两部分,

  • 硬件部分所做的是一些实质具体的工作,其功能是协调 CPU 和外设之间的种种不匹配,如双方由于速度不匹配,那 IO 接口就实现数据缓冲以减少等待时间;如数据不匹配,IO 接口就在这两种格式间互相转换。
  • 软件部分就是用来控制接口电路工作的驱动程序以及完成内部数据传输所需要的程序。

查看更多

硬盘分区原理

1. 获取安装的磁盘数

在物理地址 0x475 处存储着主机上安装的硬盘的数量,它是由 BIOS 检测并写入的。因此当我们使用 bochs 配置好磁盘,准备调试的时候,可以先看看此处内存存储的磁盘数。如下即可查看

1
xp/b 0x475

2. 创建磁盘分区表

文件系统是运行在操作系统中的软件模块,是操作系统提供的一套管理磁盘文件读写的方法和数据组织、存储形式。他的管理对象是文件,管辖范围是分区,因此他建立在分区的基础上,每个分区都可以有不同的文件系统。

分区:是由多个编号连续的柱面组成,因此分区在物理上的表现是由某段范围内的所有柱面组成的通心环。分区不能垮柱面,一个柱面只属于一个分区,分区的起始和终止都落在完整的柱面上。分区大小等于“每柱面上的扇区数”乘以“柱面数”。

查看更多