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.
要求在xv6内核中添加一个新的系统调用trace,通过传递一个整数"mask"作为参数来控制需要追踪哪些系统调用。
这个mask的每个位代表一个系统调用号码,例如,如果想要追踪fork系统调用,可以通过调用trace(1 << SYS_fork)来实现,其中SYS_fork是内核/syscall.h中的系统调用号码。
在trace系统调用中,需要打印出每个系统调用返回前的相关信息,包括进程ID、系统调用名称和返回值。不需要打印系统调用的参数。这个trace系统调用应该只对调用它的进程及其后续创建的子进程生效,不影响其他进程。
提示
Add $U/_trace to UPROGS in Makefile
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, 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. Once you fix the compilation issues, run trace 32 grep hello README; it will fail because you haven't implemented the system call in the kernel yet.
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.
Modify fork() (see kernel/proc.c) to copy the trace mask from the parent to the child process.
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.
If a test case passes when you run it inside qemu directly but you get a timeout when running the tests using make grade, try testing your implementation on Athena. Some of tests in this lab can be a bit too computationally intensive for your local machine (especially if you use WSL).
实现目标(例):
$ trace 32 grep hello README
70: syscall read -> 1023
70: syscall read -> 961
70: syscall read -> 321
70: syscall read -> 0
32= 1<
$ trace 2147483647 grep hello README
4: syscall trace -> 0
4: syscall exec -> 3
4: syscall open -> 3
4: syscall read -> 1023
4: syscall read -> 961
4: syscall read -> 321
4: syscall read -> 0
4: syscall close -> 0
2147483647是0111 1111 1111 1111 1111 1111 1111 1111,trace 2147483647 grep hello README 可以查看grep hello README中0~31的所有系统调用。
补充:
strace常用来跟踪进程执行时的系统调用和所接收的信号。 在Linux世界,进程不能直接访问硬件设备,当进程需要访问硬件设备(比如读取磁盘文件,接收网络数据等等)时,必须由用户态模式切换至内核态模式,通 过系统调用访问硬件设备。strace可以跟踪到一个进程产生的系统调用,包括参数,返回值,执行消耗的时间。trace功能和strace类似。
根据提示完成实验:
user/user.h: 用户态程序调用跳板函数(也就是系统调用在用户态的声明)
user/usys.S: 跳板函数使用 CPU 提供的 ecall 指令,调用到内核态
kernel/syscall.c :到达内核态统一系统调用处理函数 syscall(),所有系统调用都会跳到这里来处理。
kernel/syscall.c syscall(): 根据跳板传进来的系统调用编号,查询 syscalls[ ] 表,找到对应的内核函数并调用。
做实验前需要实现user/trace.c
#include "kernel/param.h"
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
int
main(int argc, char *argv[])
{
int i;
char *nargv[MAXARG];
if(argc < 3 || (argv[1][0] < '0' || argv[1][0] > '9')){
fprintf(2, "Usage: %s failed\n", argv[0]);
exit(1);
}
if (trace(atoi(argv[1])) < 0) {
fprintf(2, "%s: trace failed\n", argv[0]);
exit(1);
}
for(i = 2; i < argc && i < MAXARG; i++){
nargv[i-2] = argv[i];
}
exec(nargv[0], nargv);
exit(0);
}
(1) Add $U/_trace to UPROGS in Makefile
vim Makefile
在UPROGS下新添一行$U/_trace
(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.
(3) 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.
kernel/proc.h
kernel/sysproc.c
uint64 sys_trace(void)
{
int mask;
argint(0,&mask);
myproc()->syscall_trace = mask;
return 0;
}
kernel/proc.h
(4)Modify fork() to copy the trace mask from the parent to the child process.
(5)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.
if (p->syscall_trace & (1 << num)) {
// print trace info
printf("%d: syscall %s -> %d\n",p->pid, syscalls_name[num], p->trapframe->a0);
}
// Prototypes for the functions that handle system calls.
...
extern uint64 sys_close(void);
extern uint64 sys_trace(void);
// An array mapping syscall numbers from syscall.h
// to the function that handles the system call.
static uint64 (*syscalls[])(void) = {
...
[SYS_close] sys_close,
[SYS_trace] sys_trace,
};
// 添加识别名
char* syscalls_name[23] = {"", "fork", "exit", "wait", "pipe", "read", "kill", "exec",
"fstat", "chdir", "dup", "getpid", "sbrk", "sleep", "uptime",
"open", "write", "mknod", "unlink", "link", "mkdir", "close", "trace"};
void
syscall(void)
{
...
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]();
if (p->syscall_trace & (1 << num)) {
// print trace info
printf("%d: syscall %s -> %d\n",p->pid, syscalls_name[num], p->trapframe->a0);
}
}
测试:
补充:xv6从用户模式切换到内核模式进行系统调用的全过程 。
待续。。