哈工大李治军OS实验3-系统调用

实验3— 系统调用

实验目的

  • 建立对系统调用接口的深入认识;
  • 掌握系统调用的基本过程;
  • 能完成系统调用的全面控制;
  • 为后续实验做准备。

实验内容

此次实验的基本内容是:在 Linux 0.11 上添加两个系统调用,并编写两个简单的应用程序测试它们。

(1)iam()
第一个系统调用是 iam(),其原型为:

int iam(const char * name);
完成的功能是将字符串参数 name 的内容拷贝到内核中保存下来。要求 name 的长度不能超过 23 个字符。返回值是拷贝的字符数。如果 name 的字符个数超过了 23,则返回 “-1”,并置 errnoEINVAL

kernal/who.c 中实现此系统调用。

(2)whoami()
第二个系统调用是 whoami(),其原型为:

int whoami(char* name, unsigned int size);
它将内核中由iam()保存的名字拷贝到 name 指向的用户地址空间中,同时确保不会对 name 越界访存(name 的大小由 size 说明)。返回值是拷贝的字符数。如果 size 小于需要的空间,则返回“-1”,并置 ``errno 为 EINVAL

也是在 kernal/who.c 中实现。

(3)测试程序
运行添加过新系统调用的 Linux 0.11,在其环境下编写两个测试程序iam.c whoami.c。最终的运行结果是:

$ ./iam lizhijun

$ ./whoami

lizhijun

实验过程

操作系统实现系统调用的基本过程:

  • 应用程序调用库函数(API);
  • API 将系统调用号存入 EAX,然后通过中断调用使系统进入内核态;
  • 内核中的中断处理函数根据系统调用号,调用对应的内核函数(系统调用);
  • 系统调用完成相应功能,将返回值存入 EAX,返回到中断处理函数;
  • 中断处理函数返回到 API 中;
  • API 将 EAX 返回给应用程序。

1.在include/unistd.h设置系统调用号

在这里插入图片描述

2.修改了系统调用号之后,系统调用号数增加了2个,总共为74个,因此还需要在kernel/system_call.s中和include/linux/sys.h中修改有关系统调用的代码
在这里插入图片描述

在这里插入图片描述

3.在kernel下建立who.c,并编写who.c,完成whoami和iam的系统调用函数

#include<string.h>
#include<errno.h>
#include<asm/segment.h>

char _name[30]; //_name用来保存iam()中的name

int sys_whoami(char* name,unsigned int size){

int len=strlen(_name);//获得_name长度
printk("%d===>%s\n",len,_name);//打印_name长度及内容

//如果 `size `小于需要的空间,则返回“-1”,并置 ``errno 为 `EINVAL`。
    if(size<len){
        errno=EINVAL;
        return -1;
    }else{
    //否则返回拷贝字符数
    	int i=0;
        while(i<len){

        //逐字节保存_name到name数组中,将内核中由` iam() `保存的名字拷贝到 name 指向的用户地址空间中
            puts_fs_byte(_name[i],name+i);
            i++;
        }
    }
    return len;
}

int sys_iam(const char* name){
	char str[30];//定义一个数组str
	int i=0;
    for(i=0;i<=30&&name[i];i++){
    //逐字节获得用户空间的内容
    	str[i]=get_fs_byte(name+i);
    }
    //如果` name` 的字符个数超过了 23,则返回 “-1”,并置 `errno` 为 `EINVAL`
    if(i>=23){
    	errno=EINVAL;
    	return -1;
    }else{
    
    //调用strcpy 把用户空间的数据拷贝到_name中,供whoami()使用
   	 strcpy(_name,str);
    }
	return i;
}

4.修改kernel/Makefile

在这里插入图片描述

在这里插入图片描述

5.在oslab目录下编写用户程序whoami.c和iam.c来调用刚刚设置的系统调用

iam.c

#define __LIBRARY__
#include<errno.h>
#include<asm/segment.h>
#include<unistd.h>
#include<linux/kernel.h>
_syscall1(int, iam, const char*, name);
int main(int argc,const char*argv[]){
    iam(argv[1]);
    return 0;
}

whoami.c

#define __LIBRARY__
#include<errno.h>
#include<asm/segment.h>
#include<unistd.h>
#include<linux/kernel.h>
#include<stdio.h>
_syscall2(int, whoami,char*,name,unsigned int,size);
int main(int argc,const char*argv[]){
    char name[40];
    whoami(name,24);
    printf("%s\n",name);
    return 0;
}

挂载hdc

在oslab目录下执行sudo ./mount-hdc

执行cp whoami.c iam.c ./hdc/usr/root,实现共享文件

接着卸载这个hdc sudo umount hdc

接着运行./run脚本,调出bochs调试器,执行以下两句代码

gcc -o whoami whoami.c
gcc -o iam iam.c

发现出错

在这里插入图片描述

于是在hdc/usr/include/unistd.h中查看,unistd是否同步

在这里插入图片描述

于是在里面增加这两个系统调用号,在运行

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

小结

​ 系统调用实质就是函数调用,只是调用的函数是系统函数,处于内核态而已。用户在调用系统调用时会向内核传递一个系统调用号,然后系统调用处理程序通过此号从系统调用表中找到相应的内核函数执行(系统调用服务例程),最后返回。

系统调用号

​ 为了唯一的标识每一个系统调用,Linux为每-一个系统调用定义了一个唯一的编号,此编号称为系统调用号。系统调用号的另一个目的是作为系统调用表的下标,当用户空间的进程执行一个系统调用的时候,这个系统调用号就被用来指明到底是要执行哪个系统调用。系统调用号相当关键,一旦分配好就不能再有任何改变

### 系统调用表

​ 为了把系统调用号与相应的服务例程关联起来,内核利用了一个系统调用表,这个表存放在sys_ call_table 数组中,它是一个函数指针数组,每一个函数指针都指向其系统调用的封装例程,有NR_syscalls个表项,第n个表项包含系统调用号为n的服务例程的地址。

​ 每一个系统调用foo()在内核态都有一个对应的内核函数sys_ foo(),这个内核函数就是系统调用foo()的实现,也就是说在用户态调用foo(),最终会由内核函数sys_ foo()为用户服务,这里的sys_foo()就是系统调用服务例程。

提问

  • 从 Linux 0.11 现在的机制看,它的系统调用最多能传递几个参数?你能想出办法来扩大这个限制吗?

    从Linux0.11现在的机制来看,最多只能传递3个参数,因为通过查看include/unistd.h,可以看到

    #define _syscall0(type,name) \
    type name(void) \
    { \
    long __res; \
    __asm__ volatile ("int $0x80" \
    	: "=a" (__res) \
    	: "0" (__NR_##name)); \
    if (__res >= 0) \
    	return (type) __res; \
    errno = -__res; \
    return -1; \
    }
    
    #define _syscall1(type,name,atype,a) \
    type name(atype a) \
    { \
    long __res; \
    __asm__ volatile ("int $0x80" \
    	: "=a" (__res) \
    	: "0" (__NR_##name),"b" ((long)(a))); \
    if (__res >= 0) \
    	return (type) __res; \
    errno = -__res; \
    return -1; \
    }
    
    #define _syscall2(type,name,atype,a,btype,b) \
    type name(atype a,btype b) \
    { \
    long __res; \
    __asm__ volatile ("int $0x80" \
    	: "=a" (__res) \
    	: "0" (__NR_##name),"b" ((long)(a)),"c" ((long)(b))); \
    if (__res >= 0) \
    	return (type) __res; \
    errno = -__res; \
    return -1; \
    }
    
    #define _syscall3(type,name,atype,a,btype,b,ctype,c) \
    type name(atype a,btype b,ctype c) \
    { \
    long __res; \
    __asm__ volatile ("int $0x80" \
    	: "=a" (__res) \
    	: "0" (__NR_##name),"b" ((long)(a)),"c" ((long)(b)),"d" ((long)(c))); \
    if (__res>=0) \
    	return (type) __res; \
    errno=-__res; \
    return -1; \
    }
    
    

    在当前Linux版本中定义了这4个宏,最多只有三个参数,其中EAX用于保存系统调用号,EBX、ECX、EDX用于保存参数。

    解决办法:

    1. 结构体参数:将所有的参数封装到一个结构体中,通过指针来传递结构体的地址作为参数。
    2. 嵌套调用:将一个系统调用作为另一个系统调用的参数,在内部系统调用中获取外部系统调用的参数。
  • 用文字简要描述向 Linux 0.11 添加一个系统调用 foo() 的步骤

​ 1.首先在include/unistd.h中添加系统调用号 #define __NR_foo num

​ 2.在kernel/system_call.s中修改系统调用总个数

​ 3.在include/linux/sys.h中修改系统调用表,把sys_foo添加到表后面

​ 4.实现sys_foo()的系统调用服务例程,其路径为kernel/foo.c

​ 5.重新编译内核

​ 6.编写用户程序,调用foo()。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值