这次实验是让你在linux0.11上增加两个系统调用,并编写两个简单的应用程序测试你写的系统调用。
第一个系统调用是iam(),其原型为:
int iam(const char * name);
完成的功能是将字符串参数name的内容拷贝到内核中保存下来。要求name的长度不能超过23个字符。返回值是拷贝的字符数。如果name的字符个数超过了23,则返回“-1”,并置errno为EINVAL。
在kernel/who.c中实现此系统调用。
第二个系统调用是whoami(),其原型为:
int whoami(char* name, unsigned int size);
它将内核中由iam()保存的名字拷贝到name指向的用户地址空间中,同时确保不会对name越界访存(name的大小由size说明)。返回值是拷贝的字符数。如果size小于需要的空间,则返回“-1”,并置errno为EINVAL。
也是在kernel/who.c中实现。
要注意的是这两个函数是系统调用,是内核态的。
运行添加过新系统调用的Linux 0.11,在其环境下编写两个测试程序iam.c和whoami.c。最终的运行结果是:
$ ./iam lizhijun $ ./whoami lizhijun
这个测试程序是一个普通的用户态的C语言程序,你需要在这个函数中调用你上面你编写的那两个系统调用。
这次实验首先应该大致了解一下linux0.11系统调用的过程,仔细阅读实验指导书,然后看看现成的系统调用(如lib/close.c中的系统调用),之后再研习一下赵烔博士的《linux0.11内核完全注释》相应的章节会对这次实验会对这次实验有很大帮助。
你首先需要修改/include/unistd.h,在里面加上新系统调用的宏定义,代码如下:
#define __NR_sgetmask 68 #define __NR_ssetmask 69 #define __NR_setreuid 70 #define __NR_setregid 71 /*系统原来的系统调用,可见原来linux0.11中有72个系统调用(从0开始)*/ #define __NR_whoami 72 /*新系统调用的宏定义*/ #define __NR_iam 73 /*新系统调用的宏定义*/
然后你要修改/include/linux/sys.h,学着前面的方式在里面加上extern int sys_whoami()和extern int sys_iam(),
extern int sys_sgetmask(); extern int sys_ssetmask(); extern int sys_setreuid(); extern int sys_setregid(); extern int sys_whoami(); /*你添加的已存在声明*/ extern int sys_iam(); /*你添加的已存在声明*/
并在该文件的sys_call_table[]数组中加上两项sys_whoami和sys_iam(这里sys_whoami和sys_iam在该数组的位置一定要和上述宏定义的数字相对应,比如sys_whoami在/include/unistd.h中的宏定义为72,则sys_whoami应该是该数组的第72项,从0开始数),如下所示:
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_whoami, sys_iam }; /*在后面添加两项*/
# offsets within sigaction sa_handler = 0 sa_mask = 4 sa_flags = 8 sa_restorer = 12 nr_system_calls = 74 /*原来是72,你加了两个,变成74*/ /* * Ok, I get parallel printer interrupts while using the floppy for some * strange reason. Urgel. Now I just ignore them. */
最后在/kernel中编写实现新加的系统调用函数的文件who.c,实现你新加的系统调用:
#include <errno.h> #include <fcntl.h> #include <sys/types.h> #include <utime.h> #include <sys/stat.h> #include <linux/sched.h> #include <linux/tty.h> #include <linux/kernel.h> #include <asm/segment.h> #include <string.h> /*以上头文件可能不必全包含,但是我偷懒了,直接全包含算了*/ /*注意:注释时一定不要用双斜线,变量的声明要全写在可执行语句的前面,必须严格按着C语言的语法*/ char myName[23]; /*内核态全局变量,保存从用户态得到的名字*/ int len; /*内核态全局变量,保存从用户态得到的名字的长度*/ /*注意:在本文件中声明以及定义的变量均为内核态变量,而且不能直接访问用户态变量*/ /*2011-11-4 19:23*/ int sys_iam(const char *name) { char temp[23]; int i; for (i=0;(temp[i] = get_fs_byte(&name[i]))!='\0';i++){}/*获取名字的长度*/ if (i>23) { return (-EINVAL);/*直接这样写可以把指导书上的情况全包含*/ } len = i; for (i=0;i<len;i++) { myName[i] = temp[i]; } /*printk("Save complete!\n");*//*内核中向终端输出用printk函数*/ return len; } int sys_whoami(char* name, unsigned int size) { int i; if (len>size) { return (-EINVAL); } for (i=0;i<len;i++) { put_fs_byte(myName[i],&name[i]); } /*printk("%s\n",myName);*/ /*printk("Copy complete!\n");*/ return len; }
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
iam.c样例代码:
#define __LIBRARY__ /* 有它,_syscall1等才有效。详见unistd.h */ #include <unistd.h> /* 有它,编译器才能获知自定义的系统调用的编号 */ #include <stdlib.h> #include <stdio.h> #include <string.h> _syscall1(int, iam, const char*, name); /* iam()在用户空间的接口函数 */ /*2011-11-4 19:24*/ void main(int arg ,char *argv[]) { /*printf("%s\n",argv[0]); printf("%s\n",argv[1]);*/ iam(argv[1]); }
#define __LIBRARY__ /* 有它,_syscall1等才有效。详见unistd.h */ #include <unistd.h> /* 有它,编译器才能获知自定义的系统调用的编号 */ #include <stdlib.h> #include <stdio.h> _syscall2(int, whoami,char*,name,unsigned int,size); /* whoami()在用户空间的接口函数 */ /*2011-11-4 19:25*/ void main(int arg ,char *argv[]) { char *aname; int alen; /*unsigned int n = 0;*/ /*printf("%s\n",argv[0]);*/ /*printf("%s\n",argv[1]);*/ /*scanf("%d",&n);*/ aname = (char *)malloc(sizeof(char)*23); alen = whoami(aname,23); printf("%s\n",aname); /*printf("%d\n",alen);*/ }
实验报告参考:
/*1.从Linux 0.11现在的机制看,它的系统调用最多能传递几个参数?你能想出办法来扩大这个限制吗? 答:当应用程序经过库函数向内核发送一个中断调用int 0x80,开始执行一个系统调用。其中寄存器eax存放着系统调用号,参数可以存放寄存器ebx、ecx、edx中。因此Linux 0.11系统调用最多能向内核传递3个参数。 解决方案是传给函数一个指针值,该指针值指向一个大块数据,这个大块数据可以是一个线性表或者某个特定的数据结构,存放着该函数所有的参数,这样通过寄存器间接寻址,便可向系统调用传递多个参数。 2.用文字简要描述向Linux 0.11添加一个系统调用foo()的步骤。 答:首先修改/include/unistd.h,在里面加上新系统调用的宏定义:#define __NR_foo 72(原来从0开始数有71个系统调用,加了一个,变成72)。然后修改/include/linux/sys.h,在里面加上extern int sys_foo();,并在该文件的fn_ptr sys_call_table[]数组中加上一项sys_foo(这里sys_foo在该数组的位置一定要和上述宏定义的数字相对应,比如上述为72,则sys_foo应该是该数组的第72项,从0开始数)。然后修改/kernel里面的system_call.s文件改变里面的系统调用个数,也即在原来的个数上加1;最后在/kernel中编写实现新加的系统调用函数的文件foo.c,实现你新加的函数,到这里系统调用就添加完成了。最后修改makefile文件,使其在编译linux时能将新加的调用同时编译,全部完成(还可以编写测试程序测试新加的系统调用是否成功)。*/