In the last lab you used system calls to write a few utilities. In this lab you will add some new system calls to xv6, which will help you understand how they work and will expose you to some of the internals of the xv6 kernel. You will add more system calls in later labs.
Before you begin lab system calls, read Chapter 2 of the xv6 book, and Sections 4.3 and 4.4 of Chapter 4, and related source files:
In this lab, there are several questions for you to answer. Questions are in boxes with a light orange background. Write each question and its answer in your notebook. Take photo(s) of your questions/answers and submit the photo(s) on Canvas.
The Linux grep command can be helpful on C programming using Linux with a terminal and on some questions. For example, suppose a question asks you about the struct proc. You can discover the definition and uses of the struct proc by issuing the following Linux grep command in the kernel directory.
$ grep syscall *.c syscall.c:#include "syscall.h" syscall.c:// An array mapping syscall numbers from syscall.h syscall.c:static uint64 (*syscalls[])(void) = { syscall.c:syscall(void) syscall.c: if(num > 0 && num < NELEM(syscalls) && syscalls[num]) { syscall.c: p->trapframe->a0 = syscalls[num](); trap.c: syscall(); trap.c: // send syscalls, interrupts, and exceptions to uservec in trampoline.S % grep syscall *.h defs.h:// syscall.c defs.h:void syscall();
In the directory of your xv6-labs, create two files: answers-syscall.txt and time.txt that I may use if I run your code using your zip file submission. The answers-syscall.txt is a blank file. The time.txt file contains the number of hours you spent on the lab.
$ echo > answers-syscall.txt $ echo 8 > time.txt
Xv6 System Call | Description |
int fork() |
Create a process, return child's PID. |
int exit(int status) |
Terminate the current process; status reported to wait(). No return. Wait for a child to exit; exit status in *status; returns child PID. Terminate process PID. Returns 0, or -1 for error. |
int wait(int *status) |
Wait for a child to exit; exit status in *status; returns child PID. |
int kill(int pid) |
Terminate process PID. Returns 0, or -1 for error. |
int getpid() |
Return the current process's PID. |
int sleep(int n) |
Pause for n clock ticks. |
int exec(char *file, char *argv[]) |
Load a file and execute it with arguments; only returns if error. |
char *sbrk(int n) |
Grow process's memory by n bytes. Returns start of new memory. The C runtime function malloc() calls sbrk() to get space for the heap. |
int open(char *file, int flags) |
Open a file; flags indicate read/write; returns an fd (file descriptor). |
int write(int fd, char *buf, int n) |
Write n bytes from buf to file descriptor fd; returns n. |
int read(int fd, char *buf, int n) |
Read n bytes into buf; returns number read; or 0 if end of file. Release open file fd. |
int close(int fd) |
Release open file fd. |
int dup(int fd) |
Return a new file descriptor referring to the same file as fd. |
int pipe(int p[]) |
Create a pipe, put read/write file descriptors in p[0] and p[1]. Change the current directory. |
int chdir(char *dir) |
Change the current directory. |
int mkdir(char *dir) |
Create a new directory. |
int mknod(char *file, int, int) |
Create a device file. |
int fstat(int fd, struct stat *st) |
Place info about an open file into *st. |
int stat(char *file, struct stat *st) |
Place info about a named file into *st. |
int link(char *file1, char *file2) |
Create another name (file2) for the file file1. |
int unlink(char *file) |
Remove a file. |
int rseed(int seed) |
Seeds the random integer generator rinter . |
int rinter(int max) |
Returns a random integer between 0 and max. |
int trace(int mask) |
Enables tracing of Xv6 system calls which numbers match bits in mask. |
int sysinfo(struct sysinfo *info) |
Return system information - number of free bytes and number of procs. |
>
kernel/syscall.h: Add a syscall number to kernel/syscall.h. syscall.h is a sequence of C macros.
The dosys macro would be the following, where the number 22 is the next number. kernel/syscall.c: Add dosys information to kernel/syscall.c.
syscall.c has the fucntion syscall that is called from trap. The function
syscall determines which system call has been called, retrieves arguments, and makes the call.
syscall.c has function prototypes for each system call and an array (syscalls[]) of function pointers
that point to the various system calls. The array of function pointers uses the SYS_dosys
number to place the address of the sys_dosys function in the array. The system call number
is placed in register a7 by usys.S prior to calling the function syscall.
You can examine the code in syscall.c to see the following statements to call the correct
system call.
user/user.h: Add prototype for the dosys system call to user/user.h
The file user.h provides a seqence of function prototypes that serve as the API for system calls.
User utility programs like ls and cat include user.h to access the API.
The file user.h also has function prototypes for functions that are in
ulib.c. The ulib.c functions can be called directly, and the include
functions such as strcpy and printf.
user/usys.pl: Add a stub to user/usys.pl
The Makefile invokes the perl script user/usys.pl, which produces user/usys.S,
the actual system call stubs, which use the RISC-V ecall instruction to transition from user mode to the
kernel mode. If you examine the code in usys.pl, you will discover the system call number is
placed in register a7 prior to executing the ecall instruction. dosys Implementation: kernel/sysproc.c is the entry point system calls.
For some system calls, the entire implementation is in sysproc.c. For example, you will place
your implementation of rinter in the function sys_rinter() in sysproc.c.
However, other system calls such as fork() the implementation is located in kernel/proc.c, and the
sys_fork() function simply calls fork().
Testing/Makefile: You will have to create a user program to
test your syscall implementation. Add $U/_dosystest to UPROGS in Makefile.
To test your dosys system call,
create your test cases program in user/dosystest.c.
Then add $U/_dosystest to UPROGS in Makefile.
>
A system call is different from a regular function call. A system call must transfer control from the user mode process
to the kernel mode OS. On the RISC-V architecture, this is accomplished with the ecall assembly instruction.
The ecall instruction generates a trap that must be handled by the OS. The following describes the sequence of events for the
rseed() system call. The sequence is the same for all system calls. Some system calls have more or less arguments.
There are some ideas in this explanation of events that will require you to study virtual addresses and page tables, which is immediately after our system call study.
To start the lab, switch to the syscall branch:
In many cases, print statements will be sufficient to debug your
kernel, but sometimes being able to single step through some assembly
code or inspecting the variables on the stack is helpful.
To learn more about how to run GDB and the common issues that can
arise when using GDB, check out the following.
To help you become familiar with gdb, run make qemu-gdb and
then fire up gdb in another window
(see GDB Sample Sessions). Once you have two windows open, type in the gdb window:
The layout command splits the window in two, showing where
gdb is in the source code. The backtrace prints out the
stack backtrace.
1. Looking at the backtrace output, which function
called syscall?
Type n a few times to step pass struct proc *p = myproc();
Once past this statement, type p /x
*p, which prints the current process's proc struct
(see kernel/proc.h>) in hex.
You will notice that a proc struct has a member trapframe which contains the proc's registers when a trap is processed.
The value that p /x *p prints is the address of the trapframe member. To view the contents of trapframe,
you must enter p /x *p->trapframe. This will show you the hex values of the registers saved on the trapframe.
Alternatively, you can view the value of register a7, by entering p /x (*p->trapframe)->a7. Examine the use of parentheses
in the command to disply the value of the register a7 in the trapframe structure.
You may want to try entering p /x *p->trapframe->a7 to help you better understand dereferencing C pointers.
2. What is the value of p->trapframe->a7 and what does that
value represent? (Hint: look user/initcode.S, the first
user program xv6 starts.)
The processor is running in kernel mode, and we can print privileged
registers such as sstatus
(see RISC-V
privileged instructions for a description):
3. What was the previous mode that the CPU was in?
In the subsequent part of this lab (or in following labs), it may
happen that you make a programming error that causes the xv6 kernel
to panic. For example, replace the statement num = p->trapframe->a7;
with
num = * (int *) 0;
at the beginning of syscall, run make
qemu, and you will see:
The privileged register scause contains the cause of the trap. When a trap is taken into S-mode, scause is written with a code indicating the event that caused the trap. Lookup what the value of 0xd (13) in scause means (see RISC-V privileged instructions for a description).
To track down the source of a kernel page-fault panic, search for
the sepc value printed for the panic you just saw
in the
file kernel/kernel.asm, which contains the assembly for the
compiled kernel.
4. Write down the assembly instruction the kernel is panicing at.
Which register corresponds to the varialable num?
To inspect the state of the processor and the kernel at the
faulting instruction, fire up gdb, and set a breakpoint at the
faulting epc, like this. Note, be sure the use the address you saw displayed in the panic: kerneltrap.
5. Confirm that the faulting assembly instruction is the same as the
one you found above.
6. Why does the kernel crash? Hint: look at figure 3-3 in the text;
is address 0 mapped in the kernel address space? What is the
value in scause above? Does this value confirn why the kernel crashed.
(See description of scause
in RISC-V
privileged instructions)
Note that scause was printed by the kernel panic above,
but often you need to look at additional info to track down the
problem that caused the panic. For example, to find out which user
process was running when the kernel paniced, you can print out the
process's name:
7. What is the name of the binary that was running when the kernel
paniced? What is its process id (pid)?
This concludes a brief introduction to tracking down bugs with gdb.
This section and the links at the top of this section contain helpful information on gdb.
In this problem you will add system calls The following are your requirements for rseed and rinter
Your implementation of rinter shall use a
Linear Congruential Generator .
You shall create a static variable to contain the rseedv, which shall be 0 by default.
Calls to rseed(int seedvalue) shall update the value of rseedv to be seedvalue.
One definition of a Linear Congruential Generator is defined by the algorithm
The static variable rseedv shall be used for X.
The number 1103515245 shall be used for a.
The number 12345 shall be used for b.
The C macro for MY_RAND_MAX shall be used for m.
Add $U/_randomtest to UPROGS in Makefile Run make qemu and you will see that the
compiler cannot compile user/randomtest.c, because the
user-space stubs for the system calls don't exist yet: Add prototypes for the system calls to user/user.h, Add stubs to user/usys.pl, and Add syscall numbers to kernel/syscall.h. The Makefile invokes the perl
script user/usys.pl, which produces user/usys.S,
the actual system call stubs, which use the
RISC-V ecall instruction to transition to the
kernel. Once you fix the compilation issues such that make qemu successfully makes,
you can run randomtest 555; but it will fail
because you haven't implemented the system calls in the kernel
yet. Add a sys_rseed() function
in kernel/sysproc.c that implements the new system
call by assigning its argument to the static variable rseedv.
Add a sys_rinter() function
in kernel/sysproc.c that implements the new system
call by using the Linear Congruential General algorithm defined above.
Modify the syscall() function
in kernel/syscall.c to include the information needed to make the system calls.
Follow the pattern of exising system calls. Once you updated kernel/sysproc.c and kernel/syscall.c, perform a make qemu, and run $ randomtest 555 as a utility program in Xv6 to see if you have implemented it correctly.
Use the sample outputs provided above for comparisons.
We provide a randomtest user-level program that calls your
rseed and rinter system calls (see user/randomtest.c).
When you're done, you should see output like this:
8. The system calls for random numbers were a nice introduction to system calls.
You learned how to add OS system calls to the Xv6 OS.
Do they have to be system calls? Examine the files kernel/string.c and user/umalloc.c and
how they fit into Xv6 before answering this question.
In this problem 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.
Let's first consider the int trace(int) function prototype
that is added to user.h. The return code is 0 for success and
-1 for failure. The argument is an integer, which is a 32-bit quantity.
Each bit corresponds to a system call number, which is defined in syscall.h.
For example, bit 1 specifies
to trace fork system calls because fork has number 1
as its system call number. This means if you make a call trace(2),
you are tracing fork system calls because 2 has bit 1 set.
Another example is trace(0x20)
traces read system calls because read has number 5
as its system call number and 0x20 has bit 5 set. The C expression 1<<5
results in the number 0x20, because 1 is shifted 5 bits to the left.
If you want to trace both fork and read system calls,
you would call trace(0x22) which sets bits 1 and 5.
When calling the trace user program from the command line, the
examples show the first parameter as in decimal integer. The samples provided
in the Testing section
discuss translating this decimal integar parameter to specific system calls.
Many of these hints are similar (but maybe briefer) to the hints for the system calls for random numbers.
Add $U/_trace to UPROGS in Makefile. The file user/trace.c contains the test cases for the trace system call.
You could run make qemu and you will see that the
compiler cannot compile user/trace.c, because the
user-space stubs for the system call don't exist yet.
Add a prototype for the system call to user/user.h.
int trace(int);
Add a stub
to user/usys.pl, and a syscall number
to kernel/syscall.h. The Makefile invokes the perl
script user/usys.pl, which produces user/usys.S,
the actual system call stubs, which use the
RISC-V ecall instruction to transition to the
kernel.
entry("trace"); ← ADDED TO user/usys.pl Once you fix the compilation issues, you could
run trace 32 grep hello README; but it will fail
because you haven't implemented the system call in the kernel
yet.
Update the kernel/proc.h file by adding a new member int mask
to the struct proc structure. The following is the code for the updated struct proc structure in kernel/proc.h.
You are only adding one line.
Add a sys_trace() function
in kernel/sysproc.c that implements the new system
call by remembering its argument in a new variable in
the proc structure (see kernel/proc.h). The
functions to retrieve system call arguments from user space are
in kernel/syscall.c, and you can see examples
of their use in kernel/sysproc.c.
This programming is just like the random number system calls,
which contain additional information on this step.
The following is the code for sys_trace(), which is added to kernel/sysproc.c.
Modify fork() (see kernel/proc.c) to copy
the trace mask from the parent to the child process.
fork() creates a child process, which is identical to its
parent. Both the parent and child have struc proc that define
the process attributes. The parent has a trace mask stored in its
struct proc, which is what you added in the previous step.
You must copy this trace mask to the child.
The following is the code of a modified fork(), which is in kerenl/proc.c. You are only adding one line.
Modify the syscall() function
in kernel/syscall.c to print the trace output. You will need to add an array of syscall names to index into.
Make sure that the indices of the array use the SYS_fork names defined in syscall.h. For example
The format for each line printed by your trace in syscall() is
We provide a trace user-level program that runs another
program with tracing enabled (see user/trace.c).
Do not simply run the trace program. Study the code in user/trace.c.
When the trace program is run, it is a child process of the shell.
You will see that the trace user program calls the trace() system call with the mask provided.
This sets the mask member of the struct proc to be the mask. This is for the trace process.
The trace process then calls exec() to execute the the program and arguments.
This program is a child of the trace process, but the fork() function copies its parents mask
to the child. When the program argument executes it has its trace mask set which prints the trace results.
When you're
done, you should see output like this:
In the first example trace runs grep tracing just the
read system call. The 32 is 1<<SYS_read.
In this problem 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 that you can use to test your implementation
Add $U/_sysinfotest to UPROGS in Makefile. The file sysinfotest.c contains test cases for this problem.
You could run make qemu; but user/sysinfotest.c will
fail to compile because it calls the sysinfo system call which is not available yet.
struct sysinfo;
Now you can place the following sysinfo function prototype in the list of system calls in the file user/user.h.
int sysinfo(struct sysinfo *);
Add a stub
to user/usys.pl, and a syscall number
to kernel/syscall.h. The Makefile invokes the perl
script user/usys.pl, which produces user/usys.S,
the actual system call stubs, which use the
RISC-V ecall instruction to transition to the
kernel.
entry("sysinfo"); ← ADDED TO user/usys.pl Once you fix the compilation issues, you could
run trace 32 grep hello README; but it will fail
because you haven't implemented the system call in the kernel
yet.
Your sysinfo will need some helper functions in the kernel.
These functions are not system calls because only the kernel (namely sysinfo) calls them; however,
you must put their function prototypes in defs.h.
Locate the kalloc.c section and add the following prototype. Follow the formatting of the file.
int get_freemem(void);
Locate the proc.c section and add the following prototype. Follow the formatting of the file.
int get_nproc(void);
The following is the code for get_freemem(). It is a basic linked-list traversal algorithm that accumulates the total number of free bytes. You can place this at the bottom of the file kalloc.c.
The following is the code for get_nproc(). You can place this at the bottom of the file proc.c.
sysinfo must copy a struct sysinfo back to user
space; see sys_fstat() (kernel/sysfile.c)
and filestat() (kernel/file.c) for examples of how
to do that using copyout().
9. Why must the kernel call copyout?
We have provided user/sysinfotest.c to test the sysinfo implementationa,
and it is an execellent example of how to test.
The code in user/sysinfotest.c requires some thought to understand.
>
This completes the lab.
Read Lab Submissions for instructions on how
to submit your lab.
Reference: Xv6 System Calls
This section provides a reference of steps to add system calls to Xv6.
You do not have to study these steps prior to performing the lab, becuase
the individual portions of the lab, contain instructions for performing these steps.
In later labs, when you have to add a system call, you can refer to these steps.
There is not a specific order to these steps; however, they all must be completed
in order to run Xv6 with the newly added system call.
In the following steps, a ficticious system call dosys is added.
#define SYS_dosys 22
struct proc *p = myproc(); // proc that made system call
int num = p->trapframe->a7; // retrieve system call num
p->trapframe->a0 = syscalls[num](); // call function, save ret val in reg a0
System Call - Sequence of Events
jalr rseed
rseed:
li a7, SYS_rseed
ecall
ret
Starting the Lab
$ git fetch
$ git checkout syscall
$ make clean
Problem 1: Using gdb
(gdb) b syscall
Breakpoint 1 at 0x80002142: file kernel/syscall.c, line 243.
(gdb) c
Continuing.
[Switching to Thread 1.2]
Thread 2 hit Breakpoint 1, syscall () at kernel/syscall.c:243
243 {
(gdb) layout src
(gdb) backtrace
(gdb) p /x $sstatus
xv6 kernel is booting
hart 2 starting
hart 1 starting
scause 0x000000000000000d
sepc=0x000000008000207e stval=0x0000000000000000
panic: kerneltrap
Quit out of qemu.
(gdb) b *0x000000008000207e
Breakpoint 1 at 0x8000207e: file kernel/syscall.c, line 247.
(gdb) layout asm
(gdb) c
Continuing.
[Switching to Thread 1.3]
Thread 3 hit Breakpoint 1, syscall () at kernel/syscall.c:247
(gdb) p /x $scause
(gdb) p p->name
Problem 2: System calls for Random Integers
rseed
and rinter
that
seed a random integer generator and generate random integers.
This problem introduces you to the Xv6 system call mechanism.
You'll create a
new rseed system call that will seed the random integer generator. It shall
take one argument, an integer "seed", that establishes the starting seed
for the Pseudo Random Number Generator (PRNG).
You'll create a new rinter system call that will return an pseudo random integer.
It shall take one argument, an integer max such that the random number returned is
between 0 and max minus one. Both rseed and rinter shall return -1 on an error.
Xn+1 = (a * Xn + b) bitwiseAnd m
#define MY_RAND_MAX ((1U << 31) - 1)
Notice the expression for MY_RAND_MAX results in 0x7fffffff, which is the largest positive 32-bit number.
The C bitwise and operator is a single &.
When you use a bitwise and in the expression
(1103515245 * rseedv + 12345) & MY_RAND_MAX
you are creating a positive number.
Some Hints
Testing
$ randomtest 555
40
69
74
3
60
1
10
39
96
49
$ randomtest 101
42
79
84
53
94
79
44
9
10
19
$ randomtest 43
24
33
62
63
88
1
26
59
52
89
Problem 3: System call tracing
Some Hints
#define SYS_trace 22 ← ADDED TO kernel/syscall.h
struct proc {
struct spinlock lock;
// p->lock must be held when using these:
enum procstate state; // 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
int mask; // for syscall trace ← add this line
// wait_lock must be held when using this:
struct proc *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
struct trapframe *trapframe; // data page for trampoline.S
struct context context; // swtch() here to run process
struct file *ofile[NOFILE]; // Open files
struct inode *cwd; // Current directory
char name[16]; // Process name (debugging)
};
uint64
sys_trace(void)
{
int n;
argint(0, &n);
myproc()->mask = n;
return 0;
}
int
fork(void)
{
int i, pid;
struct proc *np;
struct proc *p = myproc();
// Allocate process.
if((np = allocproc()) == 0){
return -1;
}
// Copy mask for syscall trace ← add this line
np->mask = p->mask; ← add this line
// 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 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);
safestrcpy(np->name, p->name, sizeof(p->name));
pid = np->pid;
release(&np->lock);
acquire(&wait_lock);
np->parent = p;
release(&wait_lock);
acquire(&np->lock);
np->state = RUNNABLE;
release(&np->lock);
return pid;
}
static char *syscall_name[] = {
[SYS_fork] "fork",
[SYS_exit] "exit",
...
"%d: syscall %s -> %d\n" pid, syscallName, syscallReturnValue"
If you study the code in syscall(), I am sure you can find the variables that
correspond to pid, syscallName, and syscallReturnValue.
These variables may be members of structures.
static char *syscall_name[] = {
[SYS_fork] "fork",
[SYS_exit] "exit",
[SYS_wait] "wait",
[SYS_pipe] "pipe",
[SYS_read] "read",
[SYS_kill] "kill",
[SYS_exec] "exec",
[SYS_fstat] "fstat",
[SYS_chdir] "chdir",
[SYS_dup] "dup",
[SYS_getpid] "getpid",
[SYS_sbrk] "sbrk",
[SYS_sleep] "sleep",
[SYS_uptime] "uptime",
[SYS_open] "open",
[SYS_write] "write",
[SYS_mknod] "mknod",
[SYS_unlink] "unlink",
[SYS_link] "link",
[SYS_mkdir] "mkdir",
[SYS_close] "close",
[SYS_trace] "trace",
[SYS_sysinfo] "sysinfo",
[SYS_rseed] "rseed",
[SYS_rinter] "rinter",
};
void
syscall(void)
{
int num;
struct proc *p = myproc();
//num = * (int *) 0;
num = p->trapframe->a7;
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]();
// core code for syscall trace
if((1<<num) & p->mask){
printf("%d: syscall %s -> %d\n", p->pid, syscall_name[num], p->trapframe->a0);
}
} else {
printf("%d %s: unknown sys call %d\n",
p->pid, p->name, num);
p->trapframe->a0 = -1;
}
}
Testing
$ trace 32 grep hello README
3: syscall read -> 1023
3: syscall read -> 966
3: syscall read -> 70
3: syscall read -> 0
$
$ trace 2147483647 grep hello README
4: syscall trace -> 0
4: syscall exec -> 3
4: syscall open -> 3
4: syscall read -> 1023
4: syscall read -> 966
4: syscall read -> 70
4: syscall read -> 0
4: syscall close -> 0
$
$ grep hello README
$
$ trace 2 usertests forkforkfork
usertests starting
test forkforkfork: 407: syscall fork -> 408
408: syscall fork -> 409
409: syscall fork -> 410
410: syscall fork -> 411
409: syscall fork -> 412
410: syscall fork -> 413
409: syscall fork -> 414
411: syscall fork -> 415
...
$
Problem 4: Sysinfo
Some Hints
#define SYS_sysinfo 23 ← ADDED TO kernel/syscall.h
struct run {
struct run *next;
};
// get the number of bytes of free memory
int
get_freemem()
{
int bytes = 0;
struct run *p = kmem.freelist;
acquire(&kmem.lock);
while(p){
bytes += PGSIZE;
p = p->next;
}
release(&kmem.lock);
return bytes;
}
// get the number of processes whose state is not UNUSED
int
get_nproc()
{
int n = 0;
for(int i=0; i<NPROC; i++){
if(proc[i].state != UNUSED){
n++;
}
}
return n;
}
uint64
sys_sysinfo(void)
{
struct sysinfo info;
info.freemem = get_freemem();
info.nproc = get_nproc();
uint64 addr;
argaddr(0, &addr);
// kernel cannot directly access user space. use copyout()
return copyout(myproc()->pagetable, addr, (char*)&info, sizeof(info));
}
Testing
Running sysinfotest is rather boring. You should be patient for it to terminate.
xv6 kernel is booting
hart 2 starting
hart 1 starting
init: starting sh
$ sysinfotest
sysinfotest: start
sysinfotest: OK
$
Submit the lab