在另一个终端中输入 killall qemu-system-arm(网上的方法,我试着不行,使用ps -A | grep qemu找到进程id,而后kill -9 pid杀死进程)
在 qemu 中 输入ctrl+a 抬起后,再输入’x’。
Xv6 and Unix utilities
vscode格式化头文件排序问题
由于设置了保存时自动格式化,保存时头文件会自动排序
1 2 3 4 5 6 7 8 9 10 11 12 13
#include"kernel/types.h" #include"kernel/stat.h" // 变成了 #include"kernel/types.h" #include"kernel/stat.h" // 使得stat.h中uint未声明 structstat { int dev; // File system's disk device uint ino; // Inode number short type; // Type of file short nlink; // Number of links to file uint64 size; // Size of file in bytes };
解决:将Clang_format_sort Include改成false
以地址空间的视角看待变量
1 2 3 4 5
int number res = read(read_fd, &number, 4); // 等价于 char number[4] res = read(read_fd, number, 4);
intmy_gets(char* buf, int max) { //读取字符串,读到文件末尾时返回-1 int i, cc; char c; int res = 0; for (i = 0; i + 1 < max;) { cc = read(0, &c, 1); if (cc < 1) { res = -1; break; } if (c == '\n') break; buf[i++] = c; } buf[i] = '\0'; return res; }
通过截图
代码参考
sleep
Implement the UNIX program sleep for xv6; your sleep should pause for a user-specified number of ticks. A tick is a notion of time defined by the xv6 kernel, namely the time between two interrupts from the timer chip. Your solution should be in the file user/sleep.c.
int ticks = atoi(argv[1]); // 将字符串转换成整数 int res = sleep(ticks); if (res == -1) { exit(-1); }
exit(0); }
pingpong
Write a program that uses UNIX system calls to ‘’ping-pong’’ a byte between two processes over a pair of pipes, one for each direction. The parent should send a byte to the child; the child should print “: received ping”, where is its process ID, write the byte on the pipe to the parent, and exit; the parent should read the byte from the child, print “: received pong”, and exit. Your solution should be in the file user/pingpong.c.
Write a concurrent version of prime sieve using pipes. This idea is due to Doug McIlroy, inventor of Unix pipes. The picture halfway down this page and the surrounding text explain how to do it. Your solution should be in the file user/primes.c.
voidfunc(int read_fd, int write_fd) { close(write_fd); int first_number; int other_number; // 读取第一个数字,作为素数 int res = read(read_fd, &first_number, 4); if (res == 0) { // 管道关闭,立即退出进程 close(read_fd); exit(-1); } else { int p[2]; pipe(p); if (fork() == 0) { // 创建子进程 func(p[0], p[1]); } else { close(p[0]); while (1) { // 读取其他数字,若不被第一个数字整除,则传给子进程 res = read(read_fd, &other_number, 4); // 管道关闭,输出第一个数字后等待子进程退出,下列语句的顺序很重要 if (res == 0) { fprintf(2, "prime %d\n", first_number); close(p[1]); wait((int *)0); exit(0); } else { if (other_number % first_number != 0) { write(p[1], &other_number, 4); } } } } } } intmain(int argc, char *argv[]) { int p[2]; pipe(p); if (fork() == 0) { func(p[0], p[1]); } else { close(p[0]); for (int i = 2; i <= 35; i++) { // 将数字传给子进程 write(p[1], &i, 4); } close(p[1]); wait((int *)0); } exit(0); }
find
Write a simple version of the UNIX find program: find all the files in a directory tree with a specific name. Your solution should be in the file user/find.c.
Write a simple version of the UNIX xargs program: read lines from the standard input and run a command for each line, supplying the line as arguments to the command. Your solution should be in the file user/xargs.c.
#define MAX_EXTRA_ARG_LEN 100 intmy_gets(char* buf, int max) { //读取字符串,读到文件末尾时返回-1 int i, cc; char c; int res = 0; for (i = 0; i + 1 < max;) { cc = read(0, &c, 1); if (cc < 1) { res = -1; break; } if (c == '\n') break; buf[i++] = c; } buf[i] = '\0'; return res; } intmain(int argc, char* argv[]) { char extra_args[MAX_EXTRA_ARG_LEN]; while (1) { int res = my_gets(extra_args, MAX_EXTRA_ARG_LEN); // 双重判断,避免\n\n的情况 if (extra_args[0] == '\0' && res == -1) { break; } if (fork() == 0) { // 此时argv[0]为xargs,需将参数整体往前移动一位 for (int i = 0; i < argc - 1; i++) { argv[i] = argv[i + 1]; } argv[argc - 1] = extra_args; // 设置额外参数 exec(argv[0], argv); exit(0); } else { wait((int*)0); } } exit(0); }
system calls
trace
In this assignment you will add a system call tracing feature that may help you when debugging later labs. You’ll create a new trace system call that will control tracing. It should take one argument, an integer “mask”, whose bits specify which system calls to trace. For example, to trace the fork system call, a program calls trace(1 << SYS_fork), where SYS_fork is a syscall number from kernel/syscall.h. You have to modify the xv6 kernel to print out a line when each system call is about to return, if the system call’s number is set in the mask. The line should contain the process id, the name of the system call and the return value; you don’t need to print the system call arguments. The trace system call should enable tracing for the process that calls it and any children that it subsequently forks, but should not affect other processes.
// p->lock must be held when using these: enumprocstatestate;// Process state void *chan; // If non-zero, sleeping on chan int killed; // If non-zero, have been killed int xstate; // Exit status to be returned to parent's wait int pid; // Process ID
// wait_lock must be held when using this: structproc *parent;// Parent process
// these are private to the process, so p->lock need not be held. uint64 kstack; // Virtual address of kernel stack uint64 sz; // Size of process memory (bytes) pagetable_t pagetable; // User page table structtrapframe *trapframe;// data page for trampoline.S structcontextcontext;// swtch() here to run process structfile *ofile[NOFILE];// Open files structinode *cwd;// Current directory char name[16]; // Process name (debugging) int trace_mask; // add trace mask };
// An empty user page table. p->pagetable = proc_pagetable(p); if (p->pagetable == 0) { freeproc(p); release(&p->lock); return0; }
// Set up new context to start executing at forkret, // which returns to user space. memset(&p->context, 0, sizeof(p->context)); p->context.ra = (uint64)forkret; p->context.sp = p->kstack + PGSIZE;
// Create a new process, copying the parent. // Sets up child kernel stack to return as if from fork() system call. intfork(void) { int i, pid; structproc *np; structproc *p = myproc();
// Copy user memory from parent to child. if (uvmcopy(p->pagetable, np->pagetable, p->sz) < 0) { freeproc(np); release(&np->lock); return-1; } np->sz = p->sz;
// copy the trace mask from the parent to the child process. np->trace_mask = p->trace_mask;
// copy saved user registers. *(np->trapframe) = *(p->trapframe);
// Cause fork to return 0 in the child. np->trapframe->a0 = 0;
// increment reference counts on open file descriptors. for (i = 0; i < NOFILE; i++) if (p->ofile[i]) np->ofile[i] = filedup(p->ofile[i]); np->cwd = idup(p->cwd);
在测试trace时,总是出现超时的情况,显示MISSING ALL TESTS PASSED 修改gradelib.py中的timeout,将30改成100。测试通过!
1 2 3 4 5 6 7 8 9 10 11 12
defrun_qemu(self, *monitors, **kw): """Run a QEMU-based test. monitors should functions that will be called with this Runner instance once QEMU and GDB are started. Typically, they should register callbacks that throw TerminateTest when stop events occur. The target_base argument gives the make target to run. The make_args argument should be a list of additional arguments to pass to make. The timeout argument bounds how long to run before returning."""
In this assignment you will add a system call, sysinfo, that collects information about the running system. The system call takes one argument: a pointer to a struct sysinfo (see kernel/sysinfo.h). The kernel should fill out the fields of this struct: the freemem field should be set to the number of bytes of free memory, and the nproc field should be set to the number of processes whose state is not UNUSED. We provide a test program sysinfotest; you pass this assignment if it prints “sysinfotest: OK”.
// Copy from kernel to user. // Copy len bytes from src to virtual address dstva in a given page table. // Return 0 on success, -1 on error. int copyout(pagetable_t pagetable, uint64 dstva, char *src, uint64 len) { uint64 n, va0, pa0;
// Return the address of the PTE in page table pagetable // that corresponds to virtual address va. If alloc!=0, // create any required page-table pages. // // The risc-v Sv39 scheme has three levels of page-table // pages. A page-table page contains 512 64-bit PTEs. // A 64-bit virtual address is split into five fields: // 39..63 -- must be zero. // 30..38 -- 9 bits of level-2 index. // 21..29 -- 9 bits of level-1 index. // 12..20 -- 9 bits of level-0 index. // 0..11 -- 12 bits of byte offset within the page. pte_t *walk(pagetable_t pagetable, uint64 va, int alloc){ if (va >= MAXVA) panic("walk");
// extract the three 9-bit page table indices from a virtual address. #define PXMASK 0x1FF // 9 bits // 页表索引掩码 #define PXSHIFT(level) (PGSHIFT+(9*(level))) // 页表索引的位置 #define PX(level, va) ((((uint64) (va)) >> PXSHIFT(level)) & PXMASK) // 取出相应页表的索引并将其移动到低位
// shift a physical address to the right place for a PTE. #define PA2PTE(pa) ((((uint64)pa) >> 12) << 10) #define PTE2PA(pte) (((pte) >> 10) << 12) // 首先去除10位标志位,而后右移12位(页起始地址偏移量为0)
// Create PTEs for virtual addresses starting at va that refer to // physical addresses starting at pa. va and size might not // be page-aligned. Returns 0 on success, -1 if walk() couldn't // allocate a needed page-table page. intmappages(pagetable_t pagetable, uint64 va, uint64 size, uint64 pa, int perm){ uint64 a, last; pte_t *pte;
if (size == 0) panic("mappages: size");
a = PGROUNDDOWN(va); last = PGROUNDDOWN(va + size - 1); for (;;) { if ((pte = walk(pagetable, a, 1)) == 0) return-1; if (*pte & PTE_V) panic("mappages: remap"); *pte = PA2PTE(pa) | perm | PTE_V; if (a == last) break; a += PGSIZE; pa += PGSIZE; } return0; } 简化后即为 intmappages(pagetable_t pagetable, uint64 va, uint64 size, uint64 pa, int perm){ uint64 a, last; pte_t *pte;
a = PGROUNDDOWN(va); // 起始页 last = PGROUNDDOWN(va + size - 1); // 结束页 for (;;) { // 映射是以页的粒度 pte = walk(pagetable, a, 1); // 得到虚拟地址的页表表项指针 *pte = PA2PTE(pa) | perm | PTE_V; // 将该表项设为pa物理地址,建立映射关系,并设置标志位 if (a == last) break; a += PGSIZE; pa += PGSIZE; } return0; } #define PGROUNDDOWN(a) (((a)) & ~(PGSIZE-1)) // 将低12位置为0,即对页大小取整
正式进行实验,Speed up system calls 和 Print a page table是很久之前写的,不太记得了,如有遗漏的地方,望请告知!
Speed up system calls
When each process is created, map one read-only page at USYSCALL (a VA defined in memlayout.h). At the start of this page, store a struct usyscall (also defined in memlayout.h), and initialize it to store the PID of the current process. For this lab, ugetpid() has been provided on the userspace side and will automatically use the USYSCALL mapping. You will receive full credit for this part of the lab if the ugetpid test case passes when running pgtbltest.
// usyscall的实现对标trapframe,创建,注销的方法都与trapframe相似 structproc { structtrapframe *trapframe;// data page for trampoline.S structusyscall* usyscall;// 增加usyscall成员 }; // 比较对称的四个函数allocproc proc_pagetable freeproc proc_freepagetable // Look in the process table for an UNUSED proc. // If found, initialize state required to run in the kernel, // and return with p->lock held. // If there are no free procs, or a memory allocation fails, return 0. staticstruct proc *allocproc(void) { structproc *p;
for (p = proc; p < &proc[NPROC]; p++) { acquire(&p->lock); if (p->state == UNUSED) { goto found; } else { release(&p->lock); } } return0;
found: p->pid = allocpid(); p->state = USED;
// Allocate a trapframe page. if ((p->trapframe = (struct trapframe *)kalloc()) == 0) { freeproc(p); release(&p->lock); return0; }
// Allocate a usyscall page. if ((p->usyscall = (struct usyscall *)kalloc()) == 0) { freeproc(p); release(&p->lock); return0; } // An empty user page table. p->pagetable = proc_pagetable(p); if (p->pagetable == 0) { freeproc(p); release(&p->lock); return0; }
// Set up new context to start executing at forkret, // which returns to user space. memset(&p->context, 0, sizeof(p->context)); p->context.ra = (uint64)forkret; p->context.sp = p->kstack + PGSIZE;
// init usyscall page p->usyscall->pid = p->pid;
return p; }
// Create a user page table for a given process, // with no user memory, but with trampoline pages. pagetable_tproc_pagetable(struct proc *p) { pagetable_t pagetable;
// An empty page table. pagetable = uvmcreate(); if (pagetable == 0) return0;
// map the trampoline code (for system call return) // at the highest user virtual address. // only the supervisor uses it, on the way // to/from user space, so not PTE_U. if (mappages(pagetable, TRAMPOLINE, PGSIZE, (uint64)trampoline, PTE_R | PTE_X) < 0) { uvmfree(pagetable, 0); return0; }
// map the trapframe just below TRAMPOLINE, for trampoline.S. if (mappages(pagetable, TRAPFRAME, PGSIZE, (uint64)(p->trapframe), PTE_R | PTE_W) < 0) { uvmunmap(pagetable, TRAMPOLINE, 1, 0); uvmfree(pagetable, 0); return0; }
// free a proc structure and the data hanging from it, // including user pages. // p->lock must be held. staticvoidfreeproc(struct proc *p) { if (p->trapframe) kfree((void *)p->trapframe); p->trapframe = 0;
// Free a process's page table, and free the // physical memory it refers to. voidproc_freepagetable(pagetable_t pagetable, uint64 sz) { uvmunmap(pagetable, TRAMPOLINE, 1, 0); uvmunmap(pagetable, TRAPFRAME, 1, 0); uvmunmap(pagetable, USYSCALL, 1, 0); uvmfree(pagetable, sz); }
Print a page table
Define a function called vmprint(). It should take a pagetable_t argument, and print that pagetable in the format described below. Insert if(p->pid==1) vmprint(p->pagetable) in exec.c just before the return argc, to print the first process’s page table. You receive full credit for this part of the lab if you pass the pte printout test of make grade.
*pte = PA2PTE(pagetable) | PTE_V; // 表示该表项只有PTE_V置位,这表示指向页表的表项的R W X标志位确实全为0
Detecting which pages have been accessed
Your job is to implement pgaccess(), a system call that reports which pages have been accessed. The system call takes three arguments. First, it takes the starting virtual address of the first user page to check. Second, it takes the number of pages to check. Finally, it takes a user address to a buffer to store the results into a bitmask (a datastructure that uses one bit per page and where the first page corresponds to the least significant bit). You will receive full credit for this part of the lab if the pgaccess test case passes when running pgtbltest.
kernel/sysproc.c:95:15: error: implicit declaration of function ‘walk’ [-Werror=implicit-function-declaration] 95 | pte = walk(pagetable, base + PGSIZE * i, 0); | ^~~~ kernel/sysproc.c:95:13: error: assignment to ‘pte_t *’ {aka ‘long unsigned int *’} from ‘int’ makes pointer from integer without a cast [-Werror=int-conversion] 95 | pte = walk(pagetable, base + PGSIZE * i, 0);
排查后发现没有walk函数声明,在defs.h添加walk函数声明 usertests测试超时
1 2 3 4 5 6 7 8 9 10
Timeout! (300.3s) == Test usertests: all tests == usertests: all tests: FAIL ... test bigfile: OK test dirfile: OK test iref: OK test forktest: OK test bigdir: qemu-system-riscv64: terminating on signal 15 from pid 3652612 (make) MISSING '^ALL TESTS PASSED$'
Implement a backtrace() function in kernel/printf.c. Insert a call to this function in sys_sleep, and then run bttest, which calls sys_sleep. Your output should be as follows: backtrace: 0x0000000080002cda 0x0000000080002bb6 0x0000000080002898 After bttest exit qemu. In your terminal: the addresses may be slightly different but if you run addr2line -e kernel/kernel (or riscv64-unknown-elf-addr2line -e kernel/kernel) and cut-and-paste the above addresses as follows: $ addr2line -e kernel/kernel 0x0000000080002de2 0x0000000080002f4a 0x0000000080002bfc Ctrl-D You should see something like this: kernel/sysproc.c:74 kernel/syscall.c:224 kernel/trap.c:85
Note that the return address lives at a fixed offset (-8) from the frame pointer of a stackframe, and that the saved frame pointer lives at fixed offset (-16) from the frame pointer. 这部分实验主要理清整数,地址,指针等类似概念,最好画一张示意图,便于理解。 参考代码如下
In this exercise you’ll add a feature to xv6 that periodically alerts a process as it uses CPU time. This might be useful for compute-bound processes that want to limit how much CPU time they chew up, or for processes that want to compute but also want to take some periodic action. More generally, you’ll be implementing a primitive form of user-level interrupt/fault handlers; you could use something similar to handle page faults in the application, for example. Your solution is correct if it passes alarmtest and usertests.
// tests whether the kernel calls // the alarm handler even a single time. voidtest0() { int i; printf("test0 start\n"); count = 0; sigalarm(2, periodic); // sigalarm系统调用 for (i = 0; i < 1000 * 500000; i++) { if ((i % 1000000) == 0) write(2, ".", 1); if (count > 0) break; } sigalarm(0, 0); if (count > 0) { printf("test0 passed\n"); } else { printf("\ntest0 failed: the kernel never called the alarm handler\n"); } }