由于一时疏忽,导致who.c的代码不能通过部分测试数据,现已经改正。
今天整理了一下实验报告
在unistd.h中可以看到现在Linux 0.11 支持3个参数的传递。添加参数的方法大概有3条
1.可以采用ESI,EDI,EBP,ESP这几个寄存器传递参数。
2.可以采用《Linux 0.11注释》中提到的系统调用门的办法。
3.可以开辟一块用户态的空间,允许内核态访问,传递参数时,只需传递此空间的首地址指针即可。
向linux 0.11添加一个系统调用foo()的步骤:
首先。在内核中编写系统调用处理函数。
其次。在include/unistd.h中添加系统调用的功能号(#define __NR_foo **)
并且相应的在include/linux/sys.h中声明新的系统调用处理函数以及添加系统
调用处理程序指针数组表中该项的索引值。在make file中添加新系统调用所在
文件的编译、链接规则(依赖关系)。修改system_call.s中系统调用总数。
最后。在应用程序中提供接口,调用系统调用。
这次试验,需要用c来写,比上次要简单一些。
需要改写unistd.h sys.h system_call.s makefile 4个文件
而且需要自己写出 who.c iam.c whoami.c 3个文件
sys.h在 linux-0.11/include/linux 之中,源文件关键处如下
extern int sys_ssetmask(); extern int sys_setreuid(); extern int sys_setregid(); fn_ptr sys_call_table[] = { sys_setup, sys_exit, sys_fork, sys_read, sys_write, sys_open, sys_close, sys_waitpid, sys_creat, sys_link, sys_unlink, sys_execve, sys_chdir, sys_time, sys_mknod, sys_chmod, sys_chown, sys_break, sys_stat, sys_lseek, sys_getpid, sys_mount, sys_umount, sys_setuid, sys_getuid, sys_stime, sys_ptrace, sys_alarm, sys_fstat, sys_pause, sys_utime, sys_stty, sys_gtty, sys_access, sys_nice, sys_ftime, sys_sync, sys_kill, sys_rename, sys_mkdir, sys_rmdir, sys_dup, sys_pipe, sys_times, sys_prof, sys_brk, sys_setgid, sys_getgid, sys_signal, sys_geteuid, sys_getegid, sys_acct, sys_phys, sys_lock, sys_ioctl, sys_fcntl, sys_mpx, sys_setpgid, sys_ulimit, sys_uname, sys_umask, sys_chroot, sys_ustat, sys_dup2, sys_getppid, sys_getpgrp, sys_setsid, sys_sigaction, sys_sgetmask, sys_ssetmask, sys_setreuid,sys_setregid };
找到相应的函数名,调用其函数。
extern int sys_ssetmask(); extern int sys_setreuid(); extern int sys_setregid(); extern int sys_iam(); extern int sys_whoami(); fn_ptr sys_call_table[] = { sys_setup, sys_exit, sys_fork, sys_read, sys_write, sys_open, sys_close, sys_waitpid, sys_creat, sys_link, sys_unlink, sys_execve, sys_chdir, sys_time, sys_mknod, sys_chmod, sys_chown, sys_break, sys_stat, sys_lseek, sys_getpid, sys_mount, sys_umount, sys_setuid, sys_getuid, sys_stime, sys_ptrace, sys_alarm, sys_fstat, sys_pause, sys_utime, sys_stty, sys_gtty, sys_access, sys_nice, sys_ftime, sys_sync, sys_kill, sys_rename, sys_mkdir, sys_rmdir, sys_dup, sys_pipe, sys_times, sys_prof, sys_brk, sys_setgid, sys_getgid, sys_signal, sys_geteuid, sys_getegid, sys_acct, sys_phys, sys_lock, sys_ioctl, sys_fcntl, sys_mpx, sys_setpgid, sys_ulimit, sys_uname, sys_umask, sys_chroot, sys_ustat, sys_dup2, sys_getppid, sys_getpgrp, sys_setsid, sys_sigaction, sys_sgetmask, sys_ssetmask, sys_setreuid,sys_setregid,sys_iam,sys_whoami };system_call.s 在 linux-0.11/kernel 中
# offsets within sigaction sa_handler = 0 sa_mask = 4 sa_flags = 8 sa_restorer = 12 nr_system_calls = 72只需要把 nr_system_calls 改为 74 就好 ,其代表了中断函数的个数。修改后是这样。
# offsets within sigaction sa_handler = 0 sa_mask = 4 sa_flags = 8 sa_restorer = 12 nr_system_calls = 74
要想让我们添加的kernel/who.c可以和其它Linux代码编译链接到一起,必须要修改Makefile文件。Makefile里记录的是所有源程序文件的编译、链接规则,《注释》3.6节有简略介绍。我们之所以简单地运行make就可以编译整个代码树,是因为make完全按照Makefile里的指示工作。
Makefile在代码树中有很多,分别负责不同模块的编译工作。我们要修改的是kernel/Makefile。需要修改两处。一处是:
OBJS = sched.o system_call.o traps.o asm.o fork.o \ panic.o printk.o vsprintf.o sys.o exit.o \ signal.o mktime.o
改为:
OBJS = sched.o system_call.o traps.o asm.o fork.o \ panic.o printk.o vsprintf.o sys.o exit.o \ signal.o mktime.o who.o
另一处:
### Dependencies: exit.s exit.o: exit.c ../include/errno.h ../include/signal.h \ ../include/sys/types.h ../include/sys/wait.h ../include/linux/sched.h \ ../include/linux/head.h ../include/linux/fs.h ../include/linux/mm.h \ ../include/linux/kernel.h ../include/linux/tty.h ../include/termios.h \ ../include/asm/segment.h
改为:
### Dependencies: who.s who.o: who.c ../include/linux/kernel.h ../include/unistd.h exit.s exit.o: exit.c ../include/errno.h ../include/signal.h \ ../include/sys/types.h ../include/sys/wait.h ../include/linux/sched.h \ ../include/linux/head.h ../include/linux/fs.h ../include/linux/mm.h \ ../include/linux/kernel.h ../include/linux/tty.h ../include/termios.h \ ../include/asm/segment.h
Makefile修改后,和往常一样“make all”就能自动把who.c加入到内核中了。如果编译时提示who.c有错误,就说明修改生效了。所以,有意或无意地制造一两个错误也不完全是坏事,至少能证明Makefile是对的。
unistd.h 不能直接在oslab直接直接修改,而需要在虚拟机中修改,在oslab中有一个mount-hdc脚本
运行sudo ./mount-hdc 可以把虚拟机硬盘挂载在oslab/hdc 目录下。
在hdc/usr/include 目录下修改unistd.h
关键原代码为
#define __NR_ssetmask 69 #define __NR_setreuid 70 #define __NR_setregid 71只需要把两个自定义函数的宏定义在这里就好。
修改后为
#define __NR_ssetmask 69 #define __NR_setreuid 70 #define __NR_setregid 71 #define __NR_iam 72 #define __NR_whoami 73
who.c 的代码如下
#define __LIBRARY__ #include <unistd.h> #include <errno.h> #include <asm/segment.h> char temp[64]={0}; int sys_iam(const char* name) { int i=0; while(get_fs_byte(name+i)!='\0') i++; if(i>23){ return -EINVAL; } printk("%d\n",i); i=0; while((temp[i]=get_fs_byte(name+i))!='\0'){ i++; } return i; } int sys_whoami(char* name,unsigned int size) { int i=0; while (temp[i]!='\0') i++; if (size<i) return -1; i=0; while(temp[i]!='\0'){ put_fs_byte(temp[i],(name+i)); i++; } return i; }写完后吧 who.c 放到linux-0.01/kernel 目录下 执行make all who.c 就会被编译到内核里了
iam.c 与 whoami.c 两个文件是测试用的,可以在本地写好,通过挂载的hdc传到linux下,也可以通过vi在linux下写好(如果你蛋疼的话)
一定要在linux 执行编译 ,然后执行sync确保缓冲区数据写入磁盘就可以了。
代码如下
iam.c
#define __LIBRARY__ #include <unistd.h> #include <string.h> #include <errno.h> #include <stdio.h> _syscall1(int,iam,const char*,name) int main(int argc,char* argv[]) { if(argc>1){ if(iam(argv[1])<0){ printf("SystemCall Exception!\n"); return -1; } } else{ printf("Input Exception!\n"); return -1; } return 0; }
#define __LIBRARY__ #include <unistd.h> #include <string.h> #include <errno.h> #include <stdio.h> _syscall2(int,whoami,char*,name,unsigned int,size) int main() { int counter; char buff[128]={0}; counter=whoami(buff,128); if(counter < 0) { printf("SystemCall Exception!"); return -1; } else{ printf("%s\n",buff); } return 0; }
最后需要用脚本测试一下,程序的正确性,把脚本放到iam.c whoami.c同目录下 运行就好
脚本代码如下
经过分析它不会检查你的的 erron是不是正确,只会检查whoami返回的的字符串正不正确。
最后一个数据很阴险,注意字符串过长的时候,不要覆盖之前保存的字符串,就是说所保存的字符串是之前存的字符串就好。
#/bin/sh string1="Sunner" string2="Richard Stallman" string3="This is a very very long string!" score1=10 score2=10 score3=10 expected1="Sunner" expected2="Richard Stallman" expected3="Richard Stallman" echo Testing string:$string1 ./iam "$string1" result=`./whoami` if [ "$result" = "$expected1" ]; then echo PASS. else score1=0 echo FAILED. fi score=$score1 echo Testing string:$string2 ./iam "$string2" result=`./whoami` if [ "$result" = "$expected2" ]; then echo PASS. else score2=0 echo FAILED. fi score=$score+$score2 echo Testing string:$string3 ./iam "$string3" result=`./whoami` if [ "$result" = "$expected3" ]; then echo PASS. else score3=0 echo FAILED. fi score=$score+$score3 let "totalscore=$score" echo Score: $score = $totalscore%
另外一个测试程序testlab2.c会检查你的返回值是否正确。