Part1: System call tracing
第一个任务是修改xv6内核,为每个系统调用调用打印输出。( 输出系统调用的名称和返回值就足够了; 不需要打印系统调用参数)
样例:
1
2
3
4
5
6
7
...
fork -> 2
exec -> 0
open -> 3
close -> 0
$write -> 1
write -> 1
这是初始化和执行sh
,确保只打开两个文件描述符,并写入$prompt
。 (注意:shell
的输出和系统调用跟踪是混合的,因为shell
使用write syscall
来打印它的输出。)
提示:修改syscall.c
中的 syscall()
可选挑战:打印系统调用参数
通过观察 xv6-public/syscall.c
可知,系统调用使用了方法指针来实现不同的系统调用入口,因此添加了一个数组保存方法名:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static char syscalls_name [][ 7 ] = {
[ 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"
};
观察 syscall()
函数,在保存返回值后进行打印系统调用名称及返回值:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void
syscall ( void )
{
int num ;
struct proc * curproc = myproc ();
num = curproc -> tf -> eax ;
if ( num > 0 && num < NELEM ( syscalls ) && syscalls [ num ]) {
curproc -> tf -> eax = syscalls [ num ](); // save the return value
// My code Here
cprintf ( "%s -> %d \n " , syscalls_name [ num ], curproc -> tf -> eax );
} else {
cprintf ( "%d %s: unknown sys call %d \n " ,
curproc -> pid , curproc -> name , num );
curproc -> tf -> eax = - 1 ;
}
}
其中 fetchint
和 fetchstr
函数为根据 arg*
函数计算的地址,打印对应堆栈数据的函数,在其中添加输出即可:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// Fetch the int at addr from the current process.
int
fetchint ( uint addr , int * ip )
{
struct proc * curproc = myproc ();
if ( addr >= curproc -> sz || addr + 4 > curproc -> sz )
return - 1 ;
* ip = * ( int * )( addr );
cprintf ( "<int>: %d \n " , * ip );
return 0 ;
}
// Fetch the nul-terminated string at addr from the current process.
// Doesn't actually copy the string - just sets *pp to point at it.
// Returns length of string, not including nul.
int
fetchstr ( uint addr , char ** pp )
{
char * s , * ep ;
struct proc * curproc = myproc ();
if ( addr >= curproc -> sz )
return - 1 ;
cprintf ( "<str>: %s \n " , ** pp );
* pp = ( char * ) addr ;
ep = ( char * ) curproc -> sz ;
for ( s = * pp ; s < ep ; s ++ ){
if ( * s == 0 )
return s - * pp ;
}
return - 1 ;
}
// Fetch the nth 32-bit system call argument.
int
argint ( int n , int * ip )
{
cprintf ( "> args%d " , n );
return fetchint (( myproc () -> tf -> esp ) + 4 + 4 * n , ip );
}
int
argstr ( int n , char ** pp )
{
int addr ;
if ( argint ( n , & addr ) < 0 )
return - 1 ;
cprintf ( "> args%d " , n );
return fetchstr ( addr , pp );
}
运行 make qemu-nox
重新编译运行:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
...
fork -> 2
> args0 <int>: 2070
> args0 <str>: uZgf
.gf
> args1 <int>: 2792
<int>: 2070
<str>: fC
.gf
<int>: 0
exec -> 0
> args0 <int>: 4697
> args0 <str>: x7
> args1 <int>: 2
open -> 3
> args0 <int>: 3
close -> 0
> args0 <int>: 2
> args2 <int>: 1
> args1 <int>: 16250
$write -> 1
> args0 <int>: 2
> args2 <int>: 1
> args1 <int>: 16250
write -> 1
> args0 <int>: 0
> args2 <int>: 1
> args1 <int>: 16255
Part 2: Date system call
第二个任务是向xv6添加新的系统调用。主要是了解系统调用装置的不同部分。 新的系统调用将获得当前的UTC时间并将其返回给用户程序。 您可能希望使用辅助函数cmostime()
(在lapic.c
中定义)来读取实时时钟。 date.h
包含struct rtcdate
结构的定义,您将作为指针提供cmostime()
的参数。
您应该创建一个用户级程序来调用您的新日期系统调用; 这里有一些你应该放在date.c
中的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include "types.h"
#include "user.h"
#include "date.h"
int
main ( int argc , char * argv [])
{
struct rtcdate r ;
if ( date ( & r )) {
printf ( 2 , "date failed \n " );
exit ();
}
// your code to print the time in any format you like...
exit ();
}
为了使您的新日期程序可以从xv6
shell
运行,请将_date
添加到Makefile
中的UPROGS
定义中。
您进行日期系统调用的策略应该是克隆特定于某些现有系统调用的所有代码段,例如“uptime”系统调用。 您应该使用grep -n uptime *.[chS]
来查看所有源文件的正常运行时间。
When you're done, typing date
to an xv6 shell prompt should print the current UTC time.
Write down a few words of explanation for each of the files you had to modify in the process of creating your date system call.
Optional challenge: add a dup2() system call and modify the shell to use it.
根据提示可得:
1
2
3
4
5
6
7
$ grep -n uptime *.[ c,h]
syscall.c:110:extern int sys_uptime( void) ;
syscall.c:126:[ SYS_uptime] sys_uptime,
syscall.c:151:// [ SYS_uptime] "uptime" ,
syscall.h:15:#define SYS_uptime 14
sysproc.c:83:sys_uptime( void)
user.h:25:int uptime( void) ;
模仿 uptime
添加对 date
系统调用的支持
在 syscall.h
中添加 syscall.h:15:#define SYS_date 22
在 syscall.c
中添加 extern int sys_date(void);
在 syscall.c
中添加 [SYS_uptime] sys_date,
【和上边的编号22对应】
在 sysproc.c
中添加函数 int sys_date(struct )
1
2
3
4
5
6
int sys_date ( struct rtcdate * r ) {
if ( argptr ( 0 , ( void * ) & r , sizeof ( * r )) < 0 )
return - 1 ;
cmostime ( r );
return 0 ;
}
新建文件 date.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include "types.h"
#include "user.h"
#include "date.h"
static char month_name [ 12 ][ 7 ] = {
"Jan" ,
"Feb" ,
"Mar" ,
"Apr" ,
"May" ,
"June" ,
"July" ,
"Aug" ,
"Sept" ,
"Oct" ,
"Nov" ,
"Dec"
};
int
main ( int argc , char * argv [])
{
struct rtcdate r ;
if ( date ( & r )) {
printf ( 2 , "date failed \n " );
}
printf ( 1 , "%s %d %d:%d:%d UTC %d \n " , & month_name [ r . month ], r . day , r . hour , r . minute , r . second , r . year );
exit ();
}
将_date
添加到Makefile
中的UPROGS
定义:vim Makefile
UPROGS=\
_cat\
_echo\
_forktest\
_grep\
_init\
_kill\
_ln\
_ls\
_mkdir\
_rm\
_sh\
_stressfs\
_usertests\
_wc\
_zombie\
_date\
编译运行 make qemu-nox
1
2
$ date
July 28 14:35:8 UTC 2019
实现 dup2
在之前的文章中,提到了 dup
和 dup2
的使用,可以理解 dup2
借助 dup
自动实现了IO重定向中文件描述符的重定向与释放。
译 | pipe, fork & dup: 理解命令执行和输入输出流
使用图表清晰的展示了管道的工作方式 原文链接 [http://www.rozmichelle.com/pipes-forks-dups/] i> 注意:理解这篇文章需要基本熟悉Unix命令和C / C ++。 我的目标是在运行命令时解释进程之间的数据流。 如果您想直接跳转到管道内容,请单击此处\n[https://www.zhangjc.tech/archives/548.html#%E7%AE%A1%E9%81%93-Pipe]。 在这篇文章中,我们将讨论Unix命令如何通过管道和输入/输出重定向将数据相互传递,并且我将说明执行命令时数据流实际发生的情况。 文件描述符 - File D…