## 实验步骤
(1)了解应用程序如何调用系统调用
在通常情况下,调用系统调用和调用一个普通的自定义函数在代码上并没有什么区别,但调用后发生的事情有很大不同。调用自定义函数是通过call指令直接跳转到该函数的地址,继续运行。而调用系统调用,是调用系统库中为该系统调用编写的一个接口函数,叫API(Application Programming Interface)(它对应一个宏_syscallx,在unistd.h中)。API并不能完成系统调用的真正功能,它要做的是去调用真正的系统调用,过程是:
- 把系统调用的编号存入EAX
- 把函数参数存入其它通用寄存器
- 触发0x80号中断(int 0x80)
- 接下来就是内核的中断处理了,自动调用函数system_call(在kernel/system_call.s中),到sys_call_table找到系统调用号对用的系统调用sys_xxx,执行它。
(2)阅读文件lib/close.c、fs/open.c、kernel/system_call.s、include/unistd.h、include/linux/sys.h,找出系统调用close与这些文件之间的关系,清晰close系统调用的过程;
(3)参照系统调用close,在上面一系列文件中添加或修改系统调用iam和whoami相关的内容(系统调用号、系统调用表、系统调用总数等);
(4)创建who.c文件,在其中分别编写包含具体实现细节的sys_iam()和sys_whoami()函数;
(5)修改Makefile,以便在执行make命令时可以编译who.c文件;
(6)编译linux内核,运行bochs;
(7)编写测试程序iam.c和whoiam.c;
(8)将测试程序、unistd.h及测试脚本复制到 hdc目录,以便bochs启动后能够共享这些文件;
(9)运行bochs;
(10)在虚拟机上编译测试程序iam.c和whoiam.c,并运行测试;
(11)运行测试脚本,测试得分情况。
此次实验需要修改unistd.h sys.h system_call.s makefile,并编写who.c iam.c whoami.c
(1)修改linux-0.11/include/linux/sys.h
根据Linux调用系统调用的过程,需要把 iam()与whoami()两个函数加到全局变量,和中断函数表中就可以了,中断被调用的时候,先查找中断向量表,找到相应的函数名,调用其函数。
分别添加声明到最下面和数组中
~~~
extern int sys_setup();
extern int sys_exit();
extern int sys_fork();
extern int sys_read();
extern int sys_write();
extern int sys_open();
extern int sys_close();
extern int sys_waitpid();
extern int sys_creat();
extern int sys_link();
extern int sys_unlink();
extern int sys_execve();
extern int sys_chdir();
extern int sys_time();
extern int sys_mknod();
extern int sys_chmod();
extern int sys_chown();
extern int sys_break();
extern int sys_stat();
extern int sys_lseek();
extern int sys_getpid();
extern int sys_mount();
extern int sys_umount();
extern int sys_setuid();
extern int sys_getuid();
extern int sys_stime();
extern int sys_ptrace();
extern int sys_alarm();
extern int sys_fstat();
extern int sys_pause();
extern int sys_utime();
extern int sys_stty();
extern int sys_gtty();
extern int sys_access();
extern int sys_nice();
extern int sys_ftime();
extern int sys_sync();
extern int sys_kill();
extern int sys_rename();
extern int sys_mkdir();
extern int sys_rmdir();
extern int sys_dup();
extern int sys_pipe();
extern int sys_times();
extern int sys_prof();
extern int sys_brk();
extern int sys_setgid();
extern int sys_getgid();
extern int sys_signal();
extern int sys_geteuid();
extern int sys_getegid();
extern int sys_acct();
extern int sys_phys();
extern int sys_lock();
extern int sys_ioctl();
extern int sys_fcntl();
extern int sys_mpx();
extern int sys_setpgid();
extern int sys_ulimit();
extern int sys_uname();
extern int sys_umask();
extern int sys_chroot();
extern int sys_ustat();
extern int sys_dup2();
extern int sys_getppid();
extern int sys_getpgrp();
extern int sys_setsid();
extern int sys_sigaction();
extern int sys_sgetmask();
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 };</span>
~~~
(2)linux-0.11/kernel/system_call.s
需要把nr_system_calls 由72改为 74 表示了中断函数的个数。
~~~
# offsets within sigaction
sa_handler = 0
sa_mask = 4
sa_flags = 8
sa_restorer = 12
nr_system_calls = 74</span>
~~~
(3)修改linux-0.11/include/unistd.h
需要把两个自定义函数的宏定义在这里,修改后为
~~~
#define __NR_setup 0 /* used only by init, to get system going */
#define __NR_exit 1
#define __NR_fork 2
#define __NR_read 3
#define __NR_write 4
#define __NR_open 5
#define __NR_close 6
#define __NR_waitpid 7
#define __NR_creat 8
#define __NR_link 9
#define __NR_unlink 10
#define __NR_execve 11
#define __NR_chdir 12
#define __NR_time 13
#define __NR_mknod 14
#define __NR_chmod 15
#define __NR_chown 16
#define __NR_break 17
#define __NR_stat 18
#define __NR_lseek 19
#define __NR_getpid 20
#define __NR_mount 21
#define __NR_umount 22
#define __NR_setuid 23
#define __NR_getuid 24
#define __NR_stime 25
#define __NR_ptrace 26
#define __NR_alarm 27
#define __NR_fstat 28
#define __NR_pause 29
#define __NR_utime 30
#define __NR_stty 31
#define __NR_gtty 32
#define __NR_access 33
#define __NR_nice 34
#define __NR_ftime 35
#define __NR_sync 36
#define __NR_kill 37
#define __NR_rename 38
#define __NR_mkdir 39
#define __NR_rmdir 40
#define __NR_dup 41
#define __NR_pipe 42
#define __NR_times 43
#define __NR_prof 44
#define __NR_brk 45
#define __NR_setgid 46
#define __NR_getgid 47
#define __NR_signal 48
#define __NR_geteuid 49
#define __NR_getegid 50
#define __NR_acct 51
#define __NR_phys 52
#define __NR_lock 53
#define __NR_ioctl 54
#define __NR_fcntl 55
#define __NR_mpx 56
#define __NR_setpgid 57
#define __NR_ulimit 58
#define __NR_uname 59
#define __NR_umask 60
#define __NR_chroot 61
#define __NR_ustat 62
#define __NR_dup2 63
#define __NR_getppid 64
#define __NR_getpgrp 65
#define __NR_setsid 66
#define __NR_sigaction 67
#define __NR_sgetmask 68
#define __NR_ssetmask 69
#define __NR_setreuid 70
#define __NR_setregid 71 /*Linux system_call total 72*/
#define __NR_iam 72 /*new system_call 72 and 73*/
#define __NR_whoami 73
~~~
### 修改Makefile
要想**让我们添加的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
~~~
**此处实验指导书上很详细**
下面进入编写环节
(1)who.c
who.c 放到linux-0.01/kernel 目录下 执行make all who.c 被编译到内核中
~~~
#define __LIBRARY__
#include <unistd.h>
#include <errno.h>
#include <asm/segment.h>
#include <string.h>
char username[64]={0};
int sys_iam(const char* name)
{
int i = 0;
while(get_fs_byte(&name[i]) != '\0')
i++;
if(i > 23)
return -EINVAL;
i=0;
while(1)
{
username[i] = get_fs_byte(&name[i]);
if(username[i] == '\0')
break;
i++;
}
return i;
}
int sys_whoami(char* name,unsigned int size)
{
int i,len;
len = strlen(username);
if (size < len)
return -1;
i=0;
while(i < len)
{
put_fs_byte(username[i],&name[i]);
i++;
}
return i;
}
~~~
说明:此处get_fs_byte()与put_fs_byte()均是按地址位读取,即指针操作,且-EINVAL的含义需要掌握,此处不再赘述
(2)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[])
{
iam(argv[1]);
return 0;
}</span>
~~~
此处即是读取你传进去的参数,不明白的复习一下int main()函数两个参数的含义
(3)whoami.c
~~~
#define __LIBRARY__
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
_syscall2(int, whoami,char*,name,unsigned int,size);
int main(int argc ,char *argv[])
{
char *username;
int counter;
username = (char *)malloc(sizeof(char)*128);
counter = whoami(username,128);
printf("%s\n",username);
free(username);
return 0;
}
~~~
此处是打印回显你的输入(在iam.c中传入的) 最后一个数据是一个超长数据(坑的一比),注意字符串过长的时候,不要覆盖之前保存的字符串,保存的字符串是之前存的字符串,不然python脚本的分数不会满。
附report
1.从Linux 0.11现在的机制看,它的系统调用最多能传递几个参数?你能想出办法来扩大这个限制吗?
答:当应用程序经过库函数向内核发出一个中断调用int 0x80时,就开始执行一个系统调用。其中寄存器eax中存放着系统调用号,而携带的参数可依次存放在寄存器ebx,ecx和edx中。因此Linux 0.11内核中用户程序能够向内核最多直接传递三个参数,当然也可以不带参数。为了方便执行,内核源代码在include/unistd文件中定义了宏函数_syscalln(),其中n代表携带的参数个数,可以分别0至3。因此最多可以直接传递3个参数。
若需要传递多个参数,大块数据给内核,则可以传递这块数据的指针值。例如系统调用int read(int fd,char *buf,int n)在其宏形式_syscall3(int, read, int, fd, char*, buf, int, n),对于include/unistd中给出的每个系统调用宏,都有2+2*n个参数。其中第一个参数对应系统调用返回值的类型;第2个参数是系统调用的名称;随后是系统调用所携带参数的类型和名称。
2.用文字简要描述向Linux 0.11添加一个系统调用foo()的步骤。
答:1)修改/include/unistd.h,在里面加上新系统调用的宏定义:#define __NR_foo 72为原来从0开始数有72个系统调用,变成73。
2)修改/include/linux/sys.h,在里面加上extern int sys_foo(),并在该文件的fn_ptr sys_call_table[]数组中加上一项sys_foo
3)修改kernel里面的system_call.s文件改变里面的系统调用个数,也即在原来的个数上加1;即nr_system_calls = 73
4)在kernel文件中编写实现新加的系统调用函数的文件foo.c
5)修改makefile文件,修改OBJS和Dependencies添加新的系统调用,使其在编译linux时能将新加的调用同时编译。