xv6项目开源---05.md
理论:
1、设备驱动程序在两种环境中执行代码:上半部分在进程的内核线程中运行,下半部分在中断时执行。上半部分通过系统调用 进行调用,如希望设备执行I/O操作的read
和write
。这段代码可能会要求硬件执行操作(例如,要求磁盘读取块);然后代码等待操作完成。最终设备完成操作并引发中断。驱动程序的中断处理程序充当下半部分,计算出已经完成的操作,如果合适的话唤醒等待中的进程,并告诉硬件开始执行下一个正在等待的操作。
2、驱动程序管理的UART硬件是由QEMU仿真的16550芯片。在真正的计算机上,16550将管理连接到终端或其他计算机的RS232串行链路。运行QEMU时,它连接到键盘和显示器。
3、xv6的shell通过*init.c* (*user/init.c* :19)中打开的文件描述符从控制台读取输入。对read
的调用实现了从内核流向consoleread
(*kernel/console.c* :82)的数据通路。consoleread
等待输入到达(通过中断)并在cons.buf
中缓冲,将输入复制到用户空间,然后(在整行到达后)返回给用户进程。如果用户还没有键入整行,任何读取进程都将在sleep
系统调用中等待(kernel/console.c:98)
4、计时器中断可能发生在用户或内核代码正在执行的任何时候;内核无法在临界区操作期间禁用计时器中断。因此,计时器中断处理程序必须保证不干扰中断的内核代码。基本策略是处理程序要求RISC-V发出"软件中断"并立即返回。RISC-V用普通陷阱机制将软件中断传递给内核,并允许内核禁用它们。处理由定时器中断产生的软件中断的代码可以在devintr
(*kernel/trap.c*:204)中看到
5、UART驱动程序首先将传入的数据复制到内核中的缓冲区,然后复制到用户空间。这在低数据速率下是可行的,但是这种双重复制会显著降低快速生成或消耗数据的设备的性能。一些操作系统能够直接在用户空间缓冲区和设备硬件之间移动数据,通常带有DMA。
6、延迟分配用户空间堆内存(lazy allocation of user-space heap memory)
实践:
1、Lazy allocation
修改***trap.c***中的代码以响应来自用户空间的页面错误,方法是新分配一个物理页面并映射到发生错误的地址,然后返回到用户空间,让进程继续执行。您应该在生成"usertrap(): ...
"消息的printf
调用之前添加代码。你可以修改任何其他xv6内核代码,以使echo hi
正常工作。
这个实验很简单,就仅仅改动sys_sbrk()
函数即可,将实际分配内存的函数删除,而仅仅改变进程的sz
属性
c
uint64
sys_sbrk(void)
{
int addr;
int n;
if(argint(0, &n) < 0)
return -1;
addr = myproc()->sz;
// lazy allocation
myproc()->sz += n;
return addr;
}
(1) . 修改usertrap()
(*kernel/trap.c* )函数,使用r_scause()
判断是否为页面错误,在页面错误处理的过程中,先判断发生错误的虚拟地址(r_stval()
读取)是否位于栈空间之上,进程大小(虚拟地址从0开始,进程大小表征了进程的最高虚拟地址)之下,然后分配物理内存并添加映射
c
uint64 cause = r_scause();
if(cause == 8) {
...
} else if((which_dev = devintr()) != 0) {
// ok
} else if(cause == 13 || cause == 15) {
// 处理页面错误
uint64 fault_va = r_stval(); // 产生页面错误的虚拟地址
char* pa; // 分配的物理地址
if(PGROUNDUP(p->trapframe->sp) - 1 < fault_va && fault_va < p->sz &&
(pa = kalloc()) != 0) {
memset(pa, 0, PGSIZE);
if(mappages(p->pagetable, PGROUNDDOWN(fault_va), PGSIZE, (uint64)pa, PTE_R | PTE_W | PTE_X | PTE_U) != 0) {
kfree(pa);
p->killed = 1;
}
} else {
// printf("usertrap(): out of memory!\n");
p->killed = 1;
}
} else {
...
}
(2) . 修改uvmunmap()
(*kernel/vm.c*),之所以修改这部分代码是因为lazy allocation中首先并未实际分配内存,所以当解除映射关系的时候对于这部分内存要略过,而不是使系统崩溃,这部分在课程视频中已经解答。
c
void
uvmunmap(pagetable_t pagetable, uint64 va, uint64 npages, int do_free)
{
...
for(a = va; a < va + npages*PGSIZE; a += PGSIZE){
if((pte = walk(pagetable, a, 0)) == 0)
panic("uvmunmap: walk");
if((*pte & PTE_V) == 0)
continue;
...
}
}
2、Lazytests and Usertests
我们为您提供了lazytests
,这是一个xv6用户程序,它测试一些可能会给您的惰性内存分配器带来压力的特定情况。修改内核代码,使所有lazytests
和usertests
都通过。
- 处理
sbrk()
参数为负的情况。 - 如果某个进程在高于
sbrk()
分配的任何虚拟内存地址上出现页错误,则终止该进程。 - 在
fork()
中正确处理父到子内存拷贝。 - 处理这种情形:进程从
sbrk()
向系统调用(如read
或write
)传递有效地址,但尚未分配该地址的内存。 - 正确处理内存不足:如果在页面错误处理程序中执行
kalloc()
失败,则终止当前进程。 - 处理用户栈下面的无效页面上发生的错误。
(1) . 处理sbrk()
参数为负数的情况,参考之前sbrk()
调用的growproc()
程序,如果为负数,就调用uvmdealloc()
函数,但需要限制缩减后的内存空间不能小于0
c
uint64
sys_sbrk(void)
{
int addr;
int n;
if(argint(0, &n) < 0)
return -1;
struct proc* p = myproc();
addr = p->sz;
uint64 sz = p->sz;
if(n > 0) {
// lazy allocation
p->sz += n;
} else if(sz + n > 0) {
sz = uvmdealloc(p->pagetable, sz, sz + n);
p->sz = sz;
} else {
return -1;
}
return addr;
}
(2) . 正确处理fork
的内存拷贝:fork
调用了uvmcopy
进行内存拷贝,所以修改uvmcopy
如下
c
int
uvmcopy(pagetable_t old, pagetable_t new, uint64 sz)
{
...
for(i = 0; i < sz; i += PGSIZE){
if((pte = walk(old, i, 0)) == 0)
continue;
if((*pte & PTE_V) == 0)
continue;
...
}
...
}
(3) . 还需要继续修改uvmunmap
,否则会运行出错
c
void
uvmunmap(pagetable_t pagetable, uint64 va, uint64 npages, int do_free)
{
...
for(a = va; a < va + npages*PGSIZE; a += PGSIZE){
if((pte = walk(pagetable, a, 0)) == 0)
continue;
if((*pte & PTE_V) == 0)
continue;
...
}
}
(4). 处理通过sbrk申请内存后还未实际分配就传给系统调用使用的情况,系统调用的处理会陷入内核,scause寄存器存储的值是8,如果此时传入的地址还未实际分配,就不能走到上文usertrap中判断scause是13或15后进行内存分配的代码,syscall执行就会失败
- 系统调用流程:
- 陷入内核**>
usertrap
中r_scause()==8
的分支>syscall()
==>**回到用户空间
- 陷入内核**>
- 页面错误流程:
- 陷入内核**>
usertrap
中r_scause()==13||r_scause()==15
的分支>分配内存==>**回到用户空间
- 陷入内核**>
因此就需要找到在何时系统调用会使用这些地址,将地址传入系统调用后,会通过argaddr
函数(*kernel/syscall.c*)从寄存器中读取,因此在这里添加物理内存分配的代码
c
int
argaddr(int n, uint64 *ip)
{
*ip = argraw(n);
struct proc* p = myproc();
// 处理向系统调用传入lazy allocation地址的情况
if(walkaddr(p->pagetable, *ip) == 0) {
if(PGROUNDUP(p->trapframe->sp) - 1 < *ip && *ip < p->sz) {
char* pa = kalloc();
if(pa == 0)
return -1;
memset(pa, 0, PGSIZE);
if(mappages(p->pagetable, PGROUNDDOWN(*ip), PGSIZE, (uint64)pa, PTE_R | PTE_W | PTE_X | PTE_U) != 0) {
kfree(pa);
return -1;
}
} else {
return -1;
}
}
return 0;
}