diff --git a/labs/syscall.html b/labs/syscall.html index 68abad2..d465f35 100644 --- a/labs/syscall.html +++ b/labs/syscall.html @@ -1,58 +1,20 @@
-In this exercise you will modify the xv6 kernel to print out a line -for each system call invocation. It is enough to print the name of the -system call and the return value; you don't need to print the system -call arguments. - -
-When you're done, you should see output like this when booting -xv6: - -
-... -fork -> 2 -exec -> 0 -open -> 3 -close -> 0 -$write -> 1 - write -> 1 -- -
-That's init forking and execing sh, sh making sure only two file descriptors are -open, and sh writing the $ prompt. (Note: the output of the shell and the -system call trace are intermixed, because the shell uses the write syscall to -print its output.) - -
Hint: modify the syscall() function in kernel/syscall.c. - -
Run the programs you wrote in the lab and inspect the system call - trace. Are there many system calls? Which systems calls correspond - to code in the applications you wrote above? - -
Optional: print the system call arguments. +This lab makes you familiar with the implementation of system calls +and switching between threads of execution. In particular, you will +implement new system calls (sigalarm and sigreturn) +and switching between threads of a user-level thread package.
For the alarm system call it will be important to understand RISC-V -assembly. Since in later labs you will also read and write assembly, -it is important that you familiarize yourself with RISC_V assembly. +
For this lab it will be important to understand RISC-V assembly.
Add a file user/call.c with the following content, modify the Makefile to add the program to the user programs, and compile (make @@ -96,8 +58,43 @@ void main(void) { to printf in main? +
In this exercise you will modify the xv6 kernel to print out a line +for each system call invocation. It is enough to print the name of the +system call and the return value; you don't need to print the system +call arguments. + +
+When you're done, you should see output like this when booting +xv6: + +
+... +fork -> 2 +exec -> 0 +open -> 3 +close -> 0 +$write -> 1 + write -> 1 ++ +
+That's init forking and execing sh, sh making sure only two file descriptors are +open, and sh writing the $ prompt. (Note: the output of the shell and the +system call trace are intermixed, because the shell uses the write syscall to +print its output.) + +
Hint: modify the syscall() function in kernel/syscall.c. + +
Run the programs you wrote in the lab and inspect the system call + trace. Are there many system calls? Which systems calls correspond + to code in the applications you wrote above? + +
Optional: print the system call arguments. + -
In this exercise you'll add a feature to xv6 that periodically alerts @@ -227,7 +224,7 @@ alarmtest starting code for the alarmtest program in alarmtest.asm, which will be handy for debugging. -
To get started, the best strategy is to first pass test0, which will force you to handle the main challenge above. Here are some @@ -279,7 +276,7 @@ use only one CPU, which you can do by running -
Test0 doesn't tests whether the handler returns correctly to interrupted instruction in test0. If you didn't get this right, it @@ -311,16 +308,182 @@ use only one CPU, which you can do by running
Once you pass test0 and test1, run usertests to make sure you didn't break any other parts of the kernel. +
Download uthread.c and uthread_switch.S into your xv6 directory. +Make sure uthread_switch.S ends with .S, not +.s. Add the +following rule to the xv6 Makefile after the _forktest rule: + +
+$U/_uthread: $U/uthread.o $U/uthread_switch.o + $(LD) $(LDFLAGS) -N -e main -Ttext 0 -o $U/_uthread $U/uthread.o $U/uthread_switch.o $(ULIB) + $(OBJDUMP) -S $U/_uthread > $U/uthread.asm ++Make sure that the blank space at the start of each line is a tab, +not spaces. + +
+Add _uthread in the Makefile to the list of user programs defined by UPROGS. + +
Run xv6, then run uthread from the xv6 shell. The xv6 kernel will print an error message about uthread encountering a page fault. + +
Your job is to complete uthread_switch.S, so that you see output similar to +this (make sure to run with CPUS=1): +
+~/classes/6828/xv6$ make CPUS=1 qemu +... +$ uthread +my thread running +my thread 0x0000000000002A30 +my thread running +my thread 0x0000000000004A40 +my thread 0x0000000000002A30 +my thread 0x0000000000004A40 +my thread 0x0000000000002A30 +my thread 0x0000000000004A40 +my thread 0x0000000000002A30 +my thread 0x0000000000004A40 +my thread 0x0000000000002A30 +... +my thread 0x0000000000002A88 +my thread 0x0000000000004A98 +my thread: exit +my thread: exit +thread_schedule: no runnable threads +$ ++ +
uthread creates two threads and switches back and forth between +them. Each thread prints "my thread ..." and then yields to give the other +thread a chance to run. + +
To observe the above output, you need to complete uthread_switch.S, but before +jumping into uthread_switch.S, first understand how uthread.c +uses uthread_switch. uthread.c has two global variables +current_thread and next_thread. Each is a pointer to a +thread structure. The thread structure has a stack for a thread and a +saved stack pointer (sp, which points into the thread's stack). The +job of uthread_switch is to save the current thread state into the +structure pointed to by current_thread, restore next_thread's +state, and make current_thread point to where next_thread was +pointing to, so that when uthread_switch returns next_thread +is running and is the current_thread. + +
You should study thread_create, which sets up the initial stack for +a new thread. It provides hints about what uthread_switch should do. +Note that thread_create simulates saving all callee-save registers +on a new thread's stack. + +
To write the assembly in thread_switch, you need to know how the C +compiler lays out struct thread in memory, which is as +follows: + +
+ -------------------- + | 4 bytes for state| + -------------------- + | stack size bytes | + | for stack | + -------------------- + | 8 bytes for sp | + -------------------- <--- current_thread + ...... + + ...... + -------------------- + | 4 bytes for state| + -------------------- + | stack size bytes | + | for stack | + -------------------- + | 8 bytes for sp | + -------------------- <--- next_thread ++ +The variables &next_thread and ¤t_thread each +contain the address of a pointer to struct thread, and are +passed to thread_switch. The following fragment of assembly +will be useful: + +
+ ld t0, 0(a0) + sd sp, 0(t0) ++ +This saves sp in current_thread->sp. This works because +sp is at +offset 0 in the struct. +You can study the assembly the compiler generates for +uthread.c by looking at uthread.asm. + +
To test your code it might be helpful to single step through your +uthread_switch using riscv64-linux-gnu-gdb. You can get started in this way: + +
+(gdb) file user/_uthread +Reading symbols from user/_uthread... +(gdb) b *0x230 + ++0x230 is the address of uthread_switch (see uthread.asm). When you +compile it may be at a different address, so check uthread_asm. +You may also be able to type "b uthread_switch". XXX This doesn't work + for me; why? + +
The breakpoint may (or may not) be triggered before you even run +uthread. How could that happen? + +
Once your xv6 shell runs, type "uthread", and gdb will break at +thread_switch. Now you can type commands like the following to inspect +the state of uthread: + +
+ (gdb) p/x *next_thread + $1 = {sp = 0x4a28, stack = {0x0 (repeats 8088 times), + 0x68, 0x1, 0x0+What address is 0x168, which sits on the bottom of the stack +of next_thread? + +With "x", you can examine the content of a memory location +}, state = 0x1} +
+ (gdb) x/x next_thread->sp + 0x4a28+Why does that print 0x168? + +: 0x00000168 +
The user-level thread package interacts badly with the operating system in +several ways. For example, if one user-level thread blocks in a system call, +another user-level thread won't run, because the user-level threads scheduler +doesn't know that one of its threads has been descheduled by the xv6 scheduler. As +another example, two user-level threads will not run concurrently on different +cores, because the xv6 scheduler isn't aware that there are multiple +threads that could run in parallel. Note that if two user-level threads were to +run truly in parallel, this implementation won't work because of several races +(e.g., two threads on different processors could call thread_schedule +concurrently, select the same runnable thread, and both run it on different +processors.) + +
There are several ways of addressing these problems. One is + using scheduler + activations and another is to use one kernel thread per + user-level thread (as Linux kernels do). Implement one of these ways + in xv6. This is not easy to get right; for example, you will need to + implement TLB shootdown when updating a page table for a + multithreaded user process. + +
Add locks, condition variables, barriers, +etc. to your thread package. + - - - -