哈工大操作系统实验2—系统接口

实验步骤

(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时能将新加的调用同时编译。
 



你可能感兴趣的:(操作系统,内核)