Skip to content

Commit

Permalink
proj3 analysis
Browse files Browse the repository at this point in the history
  • Loading branch information
AstatineAi committed May 11, 2024
1 parent 81fba09 commit 0949276
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 10 deletions.
14 changes: 9 additions & 5 deletions lecture_note/docs/pintos/proj2.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
- Task3 : system calls
- Task4 : deny writes to executables

## 调试

为了将可执行文件写入 pintos 的虚拟硬盘, 需要添加参数 `-p tests/userprog/filename -a filename`, 每个文件增加一组上述形式的参数.

## 分析

`tests/userprog/Make.tests` 内有此 project 的自测数据点.
Expand Down Expand Up @@ -59,7 +63,7 @@ process_wait (tid_t child_tid UNUSED)

## 文件系统限制

- 可能并发访问一个文件, 考虑加锁
- 可能并发访问文件, 考虑加锁
- 文件大小固定, 控制文件名长度不超过 14 字符
- 没有虚拟内存, 文件必须占用连续的物理内存, 合理分配和回收

Expand Down Expand Up @@ -147,13 +151,13 @@ system call 属于内部中断, 和之前的 timer interrupt 等 CPU 外设备
一个参数, 为 user program 返回值.
## 回到 Task 1
重新审视进程退出的行为, 在内核层面-用户层面分别考虑.
问题: 进程在被 wait 之前调用 exit
### 用户层面
需要适当保存一个进程所有子进程的 exit status. 如果将 exit status 保存在 `struct thread` 里面, 在 `thread_exit()` 时, 会释放这个结构, 无法保存 exit status.
考虑使用 `thread/malloc` 内实现的 `malloc()`, 这个函数在 kernel pool 内分配内存, 保证了父进程, 子进程都可以在内核态访问到, 且在进程终结时不会被立即释放, 可以等待父进程去释放.
在分配内容较少时, 避免使用 `palloc_get_page(0)`
## Task 4
Expand Down
98 changes: 93 additions & 5 deletions lecture_note/docs/pintos/proj3.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,11 @@
- Task 1 : paging
- Task 2 : stack growth
- Task 3 : memory mapping files
- Task 4 : swapping
- Task 5 : accessing user memory
- Task 4 : accessing user memory

## 文件系统限制依然存在

- 可能并发访问一个文件, 考虑加锁
- 可能并发访问文件, 考虑加锁
- 文件大小固定, 控制文件名长度不超过 14 字符

## 分析
Expand All @@ -26,15 +25,104 @@

新增的 `.c` 文件需要在 `Makefile.build` 里面添加到 `vm_SRC`.

### 什么是 virtual address

Pintos 的虚拟内存分为 user virtual memory 和 kernel virtual memory, 内核内存全局共享, 在 project 2 使用的 `thread/malloc` 里面实现的 `malloc/free``palloc_get_page (0)` 都是从此部分内存中分配.

用户的 virtual address 范围为 `0``PHYS_BASE` (0xc0000000, 3 GB), 内核的 vritual address 范围为 `PHYS_BASE` 到 4 GB, 且内核 virtual memory 和 physical memory 映射关系固定为 `vaddr = paddr + PHYS_BASE`

`userprog/pagedir.c pagedir_activate()` 通过内联汇编修改 `CR3` 寄存器 (若该进程未设置 page directory, 则使用只映射了 kernel pool 的 `init_page_dir`), 更改 CPU 查找虚拟内存时使用的 page directory, 来实现切换线程时的内存切换.

page directory 保证了通过 page table 编号可以找到对应 page table.

在一个 page 被切换出去后, 使用这个 page 的进程可能又要用到这个 page, 需要把切出的 page 放到 disk 上, 以便在需要时再次载入到内存.

user virtual memory 布局和 C 程序内存布局相似, 栈区从高地址向低地址增长, 但是不支持动态分配堆区, 全局区在低地址, 加载可执行文件时被初始化.

由于每个 page 的大小都相同, 且每个 page 都是对齐的, 即第 $k$ 个页的虚拟内存范围为 $[k \times pageSize, (k+1) \times pageSize)$.

即用一个地址的低位表示这个地址在对应页相对页开头的偏移量 offset, 高位表示页编号, 恰好可以完成地址到页和页内地址的映射.

Pintos 页大小为 4096 字节, 低位 12 位用于描述 offset, 则前 20 位可以用于页编号.

```
31 12 11 0
+-------------------+-----------+
| Page Number | Offset |
+-------------------+-----------+
Virtual Address
```

页编号用于找到对应的 page table, page table 找 frame, frame 加上 virtual address 中的 offset 部分映射到物理内存.

但是如果需要通过页编号找到对应的 page table, 就需要保证所有的 page table 都在内存中, 但是这样会导致 page table 占用大量内存. 引入 page directory, 划分地址的高 10 位为 page directory index, 低 10 位为 page table index, 末尾 12 位为 offset.

```
31 22 21 12 11 0
+-------------------+------------+------------+
| Page Directory Idx| Page Table | Offset |
+-------------------+------------+------------+
Virtual Address
```

除此之外, 还需要 swap slot, 用于明确如何存储被换出的 page.

### virtual address -> physical address

回顾目前的结构

1. 每个 process 有一个 page directory, 隔离了不同进程的内存
2. page directory 包含许多 page table
3. page table 包含许多 page
4. page 表示一段虚拟内存
5. page 的情况
- 有的 page 对应了 frame
- 有的 page 对应了 swap slot
- 有的 page 地址合法但是没有被分配

现在, 我们有一个 32-bit 的 virtual address, 需要找到对应的 physical address.

1. 通过 `struct thread``pagedir` 指针找到 page directory
2. 通过 virtual address 的高 10 位在 page directory 中找到指向对应的页目录项 (page table 的物理地址) 的指针 `lookup_page() pde = pd + pd_no (vaddr);`
3. 通过 kernel pool 里面 virtual address 和 physical address 的映射关系找到对应的 page table 的虚拟地址 `lookup_page() pt = pde_get_pt (*pde);`
4. 通过 virtual address 的中间 10 位在 page table 中找到指向对应的页表项 (page 的地址) 的指针 `lookup_page() return &pt[pt_no (vaddr)];`

### page fault

在有虚拟内存时, page fault 有更多种情况

- 访问的虚拟地址合法
- 没有对应的页
- 对应的页没有载入内存
- 访问的虚拟地址非法
- 中止, 防止操作系统崩溃
- 中止进程, 防止操作系统崩溃

### 总结

Pintos 的内存结构决定了我们需要管理的部分为加载可执行文件时的静态/全局区内存, 以及栈内存 (在访问到时实现 stack growth 而不是结束进程), 这些部分是会被 swap in/out 的, 在 `PHYS_BASE` 之上的内核内存不需要这方面考虑.

用户内存行为包括

1. 静态区内存: 在低地址, 大小在加载可执行文件时可以确定.
2. 栈区: 在高地址, 需要确定一个增长策略.
3. 非法访问: 导致 exit -1

静态区需要实现 lazy loading, 即加载可执行文件时不载入内存, 在访问时出现 page fault 时载入.

栈区需要实现 stack growth, 先判断当前行为是可能导致堆栈还是非法访问, 然后根据情况处理是否要为栈区分配新的 page.

除此之外, 需要实现 swap in/out, 在无法分配新的 frame 时, 将不常用的 page 换出到 disk 上. 有被修改的 page 需要写回 disk (有 dirty bit 的情况), 而没有被修改的 page 可以直接丢弃, 在需要时可以从原来的文件载入.

## Task 1: Paging

首先需要建立 frame 结构, frame 需要从 user pool 取得 (`palloc_get_page (PAL_USER)`).

文档提到:

> Synchronization is also a concern: how do you deal with it if process A faults on a page whose frame process B is in the process of evicting?
## Task 1
可知不能并发访问一个 frame, 需要对每个 frame 加锁.



Expand Down

0 comments on commit 0949276

Please sign in to comment.