zoukankan      html  css  js  c++  java
  • 操作系统实验报告-系统调用

    实验内容

    在Linux 0.11上添加两个系统调用,并编写两个简单的应用程序测试它们。

    iam()

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

    int iam(const char * name);

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

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

    whoami()

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

    int whoami(char* name, unsigned int size);

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

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

    实验步骤

    代码的修改和补充

    include/linux/sys.h:

    添加声明:

    extern int sys_iam();
    extern int sys_whoam();

    在sys_call_table数组的最后增加两个元素:

    ..., sys_iam, sys_whoami}

    include/unistd.h:

    定义调用号宏:

    #define __NR_iam    72
    #define __NR_whoami    73

    声明供用户调用的函数:

    int iam(const char *name);
    int whoami(char *name, unsigned int size);

    kernel/system_call.s:

    修改调用个数(从72改为74,调用号从0开始计数):

    nr_system_calls = 74

    +kernel/who.c:

    添加此文件,实现系统调用:

    #define __LIBRARY__
    #include <asm/segment.h>
    #include <errno.h>
    #include <unistd.h>
    #define MAX_SIZE 23
    
    char username[MAX_SIZE+1];
    
    int sys_iam(const char *name)
    {
        int i;
        for(i=0; get_fs_byte(name+i)!=''; i++);      // 注解1:get_fs_byte()
        printk("sys_iam:
    	 name size is:%d 
    ", i);
        if(i>MAX_SIZE)
            return -EINVAL;
        for(i=0; (username[i]=get_fs_byte(name+i))!=''; i++);
        return i;
    }
    
    int sys_whoami(char *name, unsigned int size)
    {
        int i;
        for(i=0; put_fs_byte(username[i], name+i), username[i]!=''; i++);
        printk("sys_whoami:
    	 username size is:%d 
    	 given size is:%d
    ", i, size);
        if(i>size)
            return -EINVAL;
        return i;
    }

    注解1:

    // 由于sys_iam()的参数name指针来自用户空间,sys_iam()运行处于内核态,默认使用的是ds、es段寄存器,  // 注解1.1:发生系统调用时段寄存器的设置
    // 要取得用户空间的数据需要临时改变为fs段寄存器(用于用户空间) // segment.h中的get_fs_byte() static inline unsigned char get_fs_byte(const char * addr) { unsigned register char _v; __asm__ ("movb %%fs:%1,%0":"=r" (_v):"m" (*addr));  # => movb *fs:addr, _v 将用户空间addr处的数据存入_v寄存器 return _v; }
    注解1.1:
    system_call:
        cmpl $nr_system_calls-1,%eax  # 系统调用时会将调用号存入eax,将其与最大的系统调用号进行比较
        ja bad_sys_call          # 如果超过,则跳转到bad_sys_call(将-1存入eax并返回)
        push %ds      # 压栈
        push %es
        push %fs
        pushl %edx      # 系统调用通过这3个寄存器传参,压栈作为参数
        pushl %ecx        
        pushl %ebx
        movl $0x10,%edx        # 这3行将ds、es设置为内核口空间段地址0x10
        mov %dx,%ds
        mov %dx,%es
        movl $0x17,%edx        # 这2行将fs设置为用户空间段地址0x17
        mov %dx,%fs
        call sys_call_table(,%eax,4)  # 调用sys_call_table+eax*4处的函数
                          # sys_call_table是include/linux/sys.h中的函数入口数组,
                          # 它的类型是fn_ptr,定义在include/linux/sched.h中,
                          # typedef int (*fn_ptr)(); 实际是将一个int类型定义为函数指针
                          # 所以这里根据系统调用号计算调用入口地址时需要*4(int为4个byte) pushl %eax movl current,%eax cmpl $0,state(%eax)        # state和下面的counter分别被初始化为0和4,作为current所属结构体相应数据偏移 jne reschedule            # 根据比较结果判断是否重新调度 cmpl $0,counter(%eax) je reschedule

    kernel/Makefile:

    修改Makefile,补充与who.c相关规则:

    OBJS  = ... who.o
    ......
    ### Dependencies:
    who.s who.o: who.c ../include/unistd.h 
      ../include/asm/segment.h ../include/errno.h
    ......

    测试

    添加测试用例

    运行oslab目录下的mount-hdc,挂载虚拟硬盘:

    cd ~/workspace/oslab
    sudo ./mount-hdc
    cd hdc/usr/root

    添加测试文件:

    sudo vim iam.c

    iam.c具体内容:

    #include <stdio.h>
    #define __LIBRARY__  # 在unistd.h中,调用号与_syscall*宏函数都是在定义了__LIBRARY__的前提下才定义的
    #include <unistd.h>
    _syscall1(int,iam,const char *,name)  # 注解2:_syscall*()
    
    int main(int argc, char *argv[])
    {
        if(argc<=1)
        {
            printf("error: input your name, please! 
     ");
            return -1;
        }
        if(iam(argv[1])==-1)
        {
            printf("error: length limit is 23. 
    ");
            return -1;
        }
        return 0;
    }
    sudo vim whoami.c
    #include <stdio.h>
    #define __LIBRARY__
    #include <unistd.h>
    _syscall2(int,whoami,char *,name,unsigned int,size)
    
    int main(int argc, char *argv[])
    {
        char name[24];
        if(whoami(name, 23)==-1)
        {
            printf("error while reading name...");
            return -1;
        }
        printf("%s
    ", name);
        return 0;
    }

    实验也给我们提供了测试评分文件:testlab2.c testlab2.sh ,也将它们下载放到当前目录下。

    替换部分库文件:

    由于挂载的虚拟硬盘为oslab目录下的hdc-0.11.img,里面的文件不会随着内核的编译而改变,我们手动替换改动的库文件:

    sudo cp ~/workspace/oslab/linux-0.11/include/linux/sys.h ~/workspace/oslab/hdc/usr/include/linux/sys.h 
    sudo cp ~/workspace/oslab/linux-0.11/include/unistd.h ~/workspace/oslab/hdc/usr/include/unistd.h

    OK,我们现在可以卸载hdc了:

    cd ~/workspace/oslab
    sudo umount hdc

    编译与运行

    cd linux-0.11
    make
    ../run

    开启时默认的目录即为/usr/root。

    编译C文件:

    gcc -o iam iam.c
    gcc -o whoami whoami.c
    gcc -o testlab2 testlab2.c
    sync

    在bochs上的linux-0.11中产生或修改的文件需要手动写入硬盘,否则关闭虚拟机后不会保存。

    运行自己的测试用例:

    ./iam "lg's student"
    ./whoami

    运行实验给的评分用例(满分分别为50%和30%,还有20%是写实验报告。。。):

    ./testlab2

    ./testlab2.sh

    系统调用的分析

    初始化

    调用流程

    以iam()函数为例:

    思考

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

    Linux-0.11的系统调用通过寄存器ebx、ecx、edx传递参数,最多能传递3个参数。

    扩大传递参数的数量的方法:

    1. 增加传参所用的寄存器,但考虑到寄存器的成本以及它们还需要用于其他地方,这个方法不太可取;
    2. 通过定义结构体,在结构体中存入很多参数,然而只把结构体入口地址作为参数进行传递;
    3. 用这3个寄存器循环传值;
    4. 将寄存器拆分为高位和低位传递一直比较小的参数;
    5. 利用堆栈传递参数。

    用文字简要描述向Linux 0.11添加一个系统调用foo()的步骤。

    我们假设foo()的返回值类型为void。

    1. 在include/unistd.h中定义宏__NR_foo,并添加供用户调用的函数声明void foo();
    2. 修改kernel/system_call.s中的nr_system_calls,使其增加1;
    3. 在include/linux/sys.h中添加函数声明extern void sys_foo(),在系统调用入口表fn_ptr末端添加元素sys_foo;
    4. 添加kernel/foo.c文件,实现sys_foo()函数;
    5. 修改kernel/Makefile,在OBJS中加入foo.o,并添加生成foo.s、foo.o的依赖规则。
  • 相关阅读:
    bash中常见环境变量env、set、export 、declare与bash漏洞原理
    Java-获取时间方法比较
    Java-数据类型转换大全
    Java-后端图片处理(图片拼接)
    Java-AOP切面
    Java-异常状态码
    Spring Data JPA-日常使用
    Http-请求头各种参数设置
    Java-java命令运行jar编译问题
    SpringBoot-文件上传
  • 原文地址:https://www.cnblogs.com/tradoff/p/5734582.html
Copyright © 2011-2022 走看看