owned this note
owned this note
Published
Linked with GitHub
# CS333 – Introduction to Operating Systems
## Project 02 - Xv6 System calls
### Member
* 21125020 - Dang Trung Nghia
* 21125029 - Tran Tuan Viet
### Detail Explanation
**a) Using gdb (easy)**
**Question 1:** Looking at the backtrace output, which function called syscall?
``` python
(gdb) b syscall
Breakpoint 1 at 0x8000203e: file kernel/syscall.c, line 133.
(gdb) c
Continuing.
[Switching to Thread 1.2]
Thread 2 hit Breakpoint 1, syscall () at kernel/syscall.c:133
133 {
(gdb) layout src
(gdb) backtrace
```
Result:
``` cmd
#0 syscall () at kernel/syscall.c:133
#1 0x0000000080001d72 in usertrap () at kernel/trap.c:67
#2 0x0505050505050505 in ?? ()
```
By the output of backtrace, we know that `syscall` was called by `usertrap`.
**Question 2:** What is the value of `p->trapframe->a7` and what does that value represent?
``` python
(gdb) p p->trapframe->a7
$1 = 7
```
It can be inferred from the code in `user/initcode.S` that the system call number to be executed is stored in register `a7`:
``` asm
start:
la a0, init
la a1, argv
li a7, SYS_exec
ecall
```
From the output, we can look up the value in `kernel/syscall.h` to get the value of the constant `SYS_exec`:
``` C
...
line 8: #define SYS_exec 7
...
```
So the value of `p->trapframe->a7` is 7 and this value represent the system call `exec`.
**Question 3:** What was the previous mode that the CPU was in?
```
(gdb) p /t $sstatus
$2 = 100010
```
From the `RISC-V privileged instructions`, the SPP bit indicates the privilege level which was executing before entering supervisor mode. When a trap is taken, SPP is set to 0 if the trap originated from user mode, or 1 otherwise. When an SRET instruction is executed to return from the trap handler, the privilege level is set to user mode if the SPP bit is 0, or supervisor mode if the SPP bit is 1; SPP is then set to 0.
From the output, SPP bit is '0' so we know that the privilege level before entering the kernel was user mode.
**Question 4:** Write down the assembly instruction the kernel is panicing at. Which register corresponds to the variable `num`?
Run 'make qemu' after replacing `num = p->trapframe->a7` with `num = * (int *) 0`.
``` python
xv6 kernel is booting
hart 2 starting
hart 1 starting
scause 0x000000000000000d
sepc=0x0000000080002052 stval=0x0000000000000000
panic: kerneltrap
```
Look at `kernel/kernel.asm` to see the assembly instruction that the kernel is panicing at:
``` asm
80002052: 00002683 lw a3,0(zero) # 0 <_entry-0x80000000>
```
So the register corresponds to the variable `num` is resgister `a3`.
**Question 5:** Why does the kernel crash? Hint: look at figure 3-3 in the text; is address 0 mapped in the kernel address space? Is that confirmed by the value in scause above?
Rerun `gdb`, and set a breakpoint at the location `0x0000000080002052` (where the panic occurs):
``` python
(gdb) b *0x0000000080002052
Breakpoint 1 at 0x80002052: file kernel/syscall.c, line 138.
(gdb) c
Continuing.
Thread 1 hit Breakpoint 1, syscall () at kernel/syscall.c:138
138 num = * (int *) 0;
```
Entering `n` to make the panic occur. At this point, we can terminate the process and check the value of the `scase` register:
``` python
(gdb) n
^C
Thread 3 received signal SIGINT, Interrupt.
panic (s=s@entry=0x800083c0 "kerneltrap") at kernel/printf.c:126
126 for(;;)
(gdb) p $scause
$2 = 13
```
According to `RISC-V privileged instructions`, the value `13` here represents a load page fault. In particular, an error occurred while loading data from memory address `0` into `a3`. This is because address `0` does not map to the kernel space; rather, it begins at virtual address `0x80000000`.
**Question 6:** What is the name of the binary that was running when the kernel paniced? What is its process id (pid)?
This information can be obtained by printing the `name` field of the `proc` structure:
``` python
(gdb) b syscall
Breakpoint 1 at 0x8000203e: file kernel/syscall.c, line 133.
(gdb) c
Continuing.
[Switching to Thread 1.2]
Thread 2 hit Breakpoint 1, syscall () at kernel/syscall.c:133
133 {
(gdb) n
135 struct proc *p = myproc();
(gdb) n
138 num = * (int *) 0;
(gdb) p p->name
$1 = "initcode\000\000\000\000\000\000\000"
```
So `initcode` is the name of the binary that was running when the kernel paniced.
Printing `p->pid` to see the process id:
```(gdb) p p->pid
$3 = 1
```
The `pid` of the binary is `1`.
**b) System call tracing (moderate)**
First step, we add `$U/_trace` to `UPROGS` in `Makefile` as the following source code:
```
UPROGS=\
...
$U/_trace\
$U/_sysinfotest\
```
Second step, we add a prototype for the system call to `user/user.h`, an `trace` entry to `user/usys.pl`, and a syscall number to `kernel/syscall.h`:
``` C
// user/user.h
...
int uptime(void);
int trace(int);
```
``` C
// user/usys.pl
...
entry("uptime");
entry("trace");
```
``` C
// kernel/syscall.h
...
#define SYS_close 21
#define SYS_trace 22
```
Third step, we implement `sys_trace` function in `kernel/sysproc.c`. This function is aim to illustrate the system call `sys_trace`, then add its argument by a new variable in `proc` structure in `kernel/proc.h`:
``` C
// kernel/sysproc.c
uint64
sys_trace(void)
{
int mask;
argint(0, &mask);
myproc()->syscall_trace = mask;
return 0;
}
```
``` C
// kernel/proc.h
struct proc {
...
char name[16];
uint64 syscall_trace;
};
```
In next step, we need to change the code of `fork` in `kernel/proc.c` to copy the trace mask from the parent to its child process:
``` C
np->syscall_trace = p->syscall_trace;
```
Finally, in `kernel/syscall.c`, we need to add prototype for system trace: `extern uint64 sys_trace(void)`, get new array mapping syscall numbers from `syscall.h` to the function that handles the system call, then append list of system call names to print out to console, and print the result when the system call is ready:
``` C
extern uint64 sys_trace(void);
```
``` C
static uint64 (*syscalls[])(void) = {
...
[SYS_close] sys_close,
[SYS_trace] sys_trace,
};
```
``` C
const char *syscall_names[] = {
...
[SYS_close] "close",
[SYS_trace] "trace",
};
```
``` C
void
syscall(void)
{
int num;
struct proc *p = myproc();
num = p->trapframe->a7;
// num = * (int *) 0;
if(num > 0 && num < NELEM(syscalls) && syscalls[num]) {
// Use num to lookup the system call function for num, call it,
// and store its return value in p->trapframe->a0
p->trapframe->a0 = syscalls[num]();
if (p->syscall_trace & (1 << num)) {
printf("%d: syscall %s -> %d\n", p->pid, syscall_names[num], p->trapframe->a0);
}
} else {
printf("%d %s: unknown sys call %d\n",
p->pid, p->name, num);
p->trapframe->a0 = -1;
}
}
```
**c) Sysinfo (moderate)**
1. Add `$U/_sysinfotest` to `UPROGS` in `Makefile`
``` cmd
UPROGS=\
...
$U/_sysinfotest\
```
2. Add a prototype for the system call to `user/user.h`, a stub to `user/usys.pl`, and a syscall number to `kernel/syscall.h`.
``` C
// user/user.h
...
struct sysinfo;
int sysinfo(struct sysinfo *);
```
``` C
// user/usys.pl
...
entry("sysinfo");
```
``` C
// kernel/syscall.h
...
#define SYS_sysinfo 23
```
3. To collect the amount of free memory, write function `freemem_size` to `kernel/kalloc.c`. This function count the number of free memory blocks to get the result.
``` C
uint64
freemem_size(void)
{
acquire(&kmem.lock); // prevent race condition
uint64 size = 0;
struct run *r = kmem.freelist;
while (r) {
size++;
r = r->next;
}
release(&kmem.lock);
return size * PGSIZE;
}
```
4. To collect the number of processes, write funtion `count_proc` to `kernel/proc.c`. This function consider all possible process and count if the process's state is not `UNUSED`.
``` C
uint64
count_proc(void)
{
uint64 cnt = 0;
for (int i = 0; i < NPROC; ++i) {
if (proc[i].state != UNUSED) {
cnt++;
}
}
return cnt;
}
```
5. Add a `sys_sysinfo` function in `kernel/sysproc.c` that copies a struct `sysinfo` back to user space. Use `copyout()` to copy struct `sysinfo` back to user space.
```C
uint64
sys_info(void)
{
uint64 addr;
argaddr(0, &addr);
struct sysinfo sinfo;
sinfo.freemem = freemem_size();
sinfo.nproc = count_proc();
if (copyout(myproc()->pagetable, addr, (char *)&sinfo, sizeof(sinfo)) < 0)
return -1;
return 0;
}
```
6. Add the prototype to `kernel/defs.h`.
```C
// kalloc.c
...
void kinit(void);
uint64 freemem_size(void);
...
// proc.c
...
uint64 count_proc(void);
```
**Test:** use the test program `sysinfotest` to check the correctness of the function `sysinfo`.
``` cmd
xv6 kernel is booting
hart 1 starting
hart 2 starting
init: starting sh
$ sysinfotest
sysinfotest: start
sysinfotest: OK
```