[HIT操作系统][lab2]添加系统调用

我的ID: 丶爆炸的榴莲,aweffr

实验链接(lab2):

https://www.shiyanlou.com/courses/115/labs/569/document

 

课程链接:

http://mooc.study.163.com/course/HIT-1000002004#/info

 

感谢网友Tradoff,参考了你详尽的实验报告:

http://www.cnblogs.com/tradoff/p/5734582.html

 

-------------------------------实验内容-------------------------------

在Linux 0.11上添加两个系统调用,并编写两个测试程序。

 

两个系统调用:

1. int iam(const char * name);

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

 

测试程序:

编写iam.c,调用iam接口,生成的可执行iam接收一个字符串参数,把名字写进内核。

编写whoami.c,调用whoami接口,把名字从内核中拷贝到程序中并打印。

 

实验报告需要回答两个问题:

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

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

 

实验的一点小感受:

实验中的难题的答案其实都已经在实验提示中了。答案就在眼前,只是一开始看不到,需要多看多想很多遍才get。

 

-------------------------------思路/前言-------------------------------

做这个实验之前首先要明白一个问题,就是用户空间和内核空间的机制。

 

正常情况下,写一个C程序,我们面对的地址空间是系统虚拟化给我们的一个独立的、连续的地址空间,当程序中我们写了”printf”,会触发系统中断,程序会“陷入”内核态,由操作系统大管家接管一些事情,做完之后再把控制权还回来,对我们程序而言除了接收到一个返回值之外,是没有别的的感觉的。我们正常情况下写的程序就像熊孩子在家里,饿了吃饭乏了睡觉精神饱满就可劲造,家长(操作系统)默默烧饭(printf)打扫收拾(回收资源)给零花钱(malloc),给熊孩子一种富二代的错觉。然后小孩子摸了不该摸的或者破坏东西了就吊起来打一顿惩罚(segment fault, kill process)。

 

而在内核态程序中就不一样了,以我的内核中的iam程序为例:

 

当系统执行到14行时,如果我们直接解引用(name + i),得到的东西是未知的,因为此时操作系统正处于内核态,而传进来的name指针所存储的是用户态下地址空间的地址值,直接解引用不知道会解出什么(大概是个segment fault?)。

 

而内核态和用户态的转换机制,需要结合“段页内存管理”的相关知识点。就本实验来说,需要知道的就是:

当我们的用户程序调用了一个系统调用,kernel/system_call.s里的纯汇编代码就会把ds, ex设置成0x10,就是指向内核地址空间。并且把fs这是成17。然后就去调用我们写的系统调用函数了。

而从内核空间拿到正确的用户空间数据的方法,要去看<asm/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));
    return _v;
}

 

这个函数就是临时改变fs寄存器,然后把正确的结果塞到_v寄存器里,然后返回正确的值。

此外就是实际编写代码时,会在#include <unistd.h>之前先预定一个宏__LIBRARY__。目的在于使用这个宏后, _syscall1等展开的宏才有效。

如代码

_syscall1(int, iam, const char *, name)

通过gcc打开-E选项查看预编译后的文件,这句代码被替换成了

int iam( const char * name) {
     long __res;
     __asm__ volatile (
         "int $0x80" : "=a" (__res) : "0" ( 72 ),"b" ((long)( name))
        );
    if (__res >= 0)
        return (int) __res;
    errno = -__res;
    return -1;
}

 (我在unistd.h中把`iam`调用的调用号定义成72了)

 

-------------------------------具体步骤-------------------------------

修改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:

修改调用个数:

nr_system_calls = 74

 

添加kernel/who.c:

#define __LIBRARY__
#include <asm/segment.h>
#include <errno.h>
#include <unistd.h>

#define MAX_SIZE 23

int username_size = 0;
char username[MAX_SIZE + 1];

int sys_iam(const char *name)
{
    /* Get the name length */
    int i = 0;
    while (get_fs_byte(name + i) != '\0') i++;
    printk("sys_iam:\n name size is %d \n", i);
    if (i>MAX_SIZE)
        return -EINVAL;
    else
        username_size = i;
    /* Copy bytes from user space */
    int j;
    for (j = 0; j <= username_size; j++)
        username[j] = get_fs_byte(name + j);
    return username_size;
}

int sys_whoami(char *name, unsigned int size)
{
    if (username_size > size)
        return -EINVAL;
    /* Copy char val to the user space */
    int i;
    for (i = 0; i <= username_size; i++)
        put_fs_byte(username[i], name + i);
    return username_size;
}

 

 

修改kernel/Makefile:

添加编译who的规则:

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

 

测试文件iam.c和whoami.c

挂载hdc,在~/oslab/hdc/usr/root/目录下添加iam.c和whoami.c

/* iam.c */
#include <stdio.h>
#define __LIBRARY__
#include <unistd.h>

_syscall1(int, iam, const char *, name)

int main(int argc, char * argv[])
{
    if (argc <= 1)
    {
         printf("error: input your name, please!\n");
         return -1;
    }

    if (iam(argv[1]) == -1)
    {
         printf("error: length limit is 23. \n");
         return -1;
    }
    return 0;
}

 

 

/* 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\n", name);
    return 0;
}

 

-------------------------------测试结果-------------------------------

将实验提供的测试评分文件:testlab2.c testlab2.sh放到~/oslab/hdc/usr/root/目录下。

在bochs里编译whoami.c, iam.c和testlab2.c后,运行

./testlab2

截图如下:

 

 

sh testlab2.sh

截图如下:

 

 

 

-------------------------------思考题-------------------------------

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

根据sys_call.s可知最多三个参数,分别由ebx, ecx, edx储存。

 

我认为比较好的做法是,由系统调用的地方定义一个用于传参的结构体。用户程序调用该系统调用时,传入结构体的指针。这个思路类似于多线程中pthread_create的传参方法。

此外可以通过位运算的方法,高16位存一个short,低16位存另一个short,合并进一个寄存器,不过这种方式可读性不好,需要操作系统和程序员形成默契,容易出错。

 

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

1. 在include/unistd.h中定义宏__NR_foo。声明给用户调用的函数 int foo()。

2. 修改kernel/system_call.s,使得其中nr_system_calls增加1。

3. 在inlcude/linux/sys.h中添加函数声明 extern int sys_foo(), 在系统调用入口表fn_ptr末端添加元素sys_foo;

4. 添加kernel/foo.c,做foo()的具体实现代码。

5. 修改kernel/Makefile,在OBJS中加入foo.o,在依赖(### Dependencies:)中添加foo.s、foo.o的依赖项。

 

以上就是完整报告,祝大家实验顺利~

 

posted @ 2017-09-15 12:52  往日未尝认真  阅读(738)  评论(0编辑  收藏  举报