向操作系统中添加系统调用

一, 实验内容

  在Linux 0.11上添加两个系统调用, 在kernel/who.c中实现.

  第一个系统调用是iam(),其原型为:int iam(const char * name);

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

  第二个系统调用是whoami(),其原型为:int whoami(char* name, unsigned int size);

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

  运行结果应该是这样的:

  \$ ./iam aixiangfei

  \$ ./whoami

  aixiangfei

 

二, 实验步骤

1, 根据printf()函数的实现原理, 首先是调用库函数printf(), 然后在库函数中调用write()函数, 把系统调用号__NR_write传递给0x80号中断. 由于这次实验只要求实现系统调用, 而不是实现库函数, 所以就不需要第一步了. 所以现在应该先添加一个类似于__NR_write的系统调用号, 可以在include/unistd.h中添加两个宏:

#define __NR_whoami    72
#define __NR_iam       73

2, 当发生0x80号中断时, 就会调用中断处理函数system_call, 这个函数在在system_call.s中, 注意到system_call函数中有一个判断语句:

......
nr_system_calls = 72
......
system_call: cmpl $nr_system_calls
-1,%eax ja bad_sys_call
......
call sys_call_table(,%eax,4)
  ......

  system_call函数首先会判断nr_system_calls-1和%eax的大小, 如果%eax更大, 那么就会跳到bad_sys_call 发生系统调用错误! 如果调用whoami, 那么传递给eax的值就是72, 如果调用iam, 那么传递给eax的值就是73, 所以, 如果不修改这个值, 系统调用肯定会失败的! 所以nr_system_calls应该改为74

3, 中断处理函数system_call会从sys_call_table函数表中查找系统调用的入口地址, 所以还要修改include/linux/sys.h文件. 

  首先在数组中的末尾添加两个函数名:

fn_ptr sys_call_table[] = { sys_setup, sys_exit, sys_fork, ...... , sys_whoami, sys_iam };

  由于sys_whoami和sys_iam是函数名, 编译器必须要知道函数名的声明才能编译通过, 所以在前面必须要加上这两个函数的声明:

    ......

extern
int sys_whoami(); extern int sys_iam();

4, 接下来就应该来实现sys_whoami()和sys_iam()函数了. 根据实验的要求, 这两个系统调用必须在kernel/who.c中实现.

    首先应该考虑一个问题: int iam(const char * name)和int whoami(char* name, unsigned int size)中的指针参数传递的是应用程序所在地址空间的逻辑地址,而现在是在内核中, 在内核中如果直接访问这个地址,访问到的是内核空间中的数据,而不会是原来用户空间的数据. 所以必须借助于内核提供的get_fs_byte()和put_fs_byte(), get_fs_byte()的功能是从用户地址空间中获得一个字节的的数据, put_fs_byte()的功能是从核心态拷贝数据到用核心态. 

  这两个函数都是在头文件include/asm/segment.h中声明的, 所以who.c文件必须先包含asm/segment.h

    然后考虑到出错处理的问题, 实验要求函出错之后应该返回-1,并置errno为EINVAL, 但是在进入0x80中断之前, 其实已经做了一些错误处理的工作的, 比如说write的定义:

int write(int fd, const char * buf, off_t count)
{
    long __res;
    __asm__ volatile (
        "int $0x80"     // 汇编语句
        : "=a" (__res)  // 输出寄存器
        : "0" (__NR_write), "b" (long fd), "c" (long buf) "d" (long count)    // 输入寄存器
        if (__res >= 0)
            return (int)__res;
        errno = -__res;
        return -1;
    )
}

  可以看到如果返回值__res>=0, 函数才正确返回, 否则就要先设置errno为 -__res, 并返回-1, 所以在系统调用处理出错情况时, 只需要返回-EINVAL即可. 

  也就是说, 如果系统调用没有出错, 那么返回值肯定是一个正数, 如果系统调用出错了, 就返回-EINVAL, EINVAL是在errno.h中定义的是, 是一个正数, 当返回到用户态的时候, errno的值就是EINVAL了, 最后会返回-1表示出错.

  所以最终的who.c代码如下所示:

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

char buf[24];

int sys_iam(const char *name)
{
    int count;
    int i;
    char temp[24];

    for (count = 0; count < 24; count++)
    {
        temp[count] = get_fs_byte(name + count);
        if (temp[count] == '\0')
            break;
    }
    if (count == 24)
        return -EINVAL;
    for (i = 0; i <= count; i++)
        buf[i] = temp[i];
    return count;
}

int sys_whoami(char *name, unsigned int size)
{
    int count = 0;
    int i = 0;
    while (buf[count] != '\0')
        count++;
    if (size < count)
        return -EINVAL;
    while (i < count)
    {
        put_fs_byte(buf[i], &name[i]);
        i++;
    }
    return count;

}

5, 由于修改了内核文件, 所以必须要修改Makefile文件, 把添加的文件编译进操作系统镜像中. 因为对于整个代码树来说, 仅仅添加了who.c文件, 所以只需要修改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

  即可. 

  接下来就可以make了

 

三, 验证结果

  上面仅仅是修改了linux内核相关文件, 还没有修改linux的文件系统, 而实验文件给的是一个编译好的hdc-0.11.img镜像文件, 不能直接修改, 所以只好挂载到Ubuntu系统上再来修改了.

  先挂载linux 0.11文件系统, 然后在/usr/include/unistd.h中添加两个两个系统调用号, 跟实验步骤1是一样的. 

  然后就可以./run了!

  最后可以编写测试函数了. 需要注意的是, 由于不能修改直接修改文件系统的源码, 库里面还没有iam()和whoami()这两个库函数, 可以向库里面增加这两个函数, 但是为了方便, 就直接在测试程序中添加函数的定义了. 

// iam.c
#define
__LIBRARY__ #include <unistd.h> _syscall1(int, iam, const char*, name) int main(int argc, char *argv[]) { iam(argv[1]); return 0; }
// whoami.c
#define
__LIBRARY__ #include <unistd.h> #include <stdio.h> _syscall2(int, whoami, char *, name, unsigned int, size) int main() { char arr[24]; whoami(arr, 24); printf("%s", arr); return 0; }

  用gcc编译之后, 运行结果如图所示:

 

posted on 2015-08-20 16:19  艾翔飞  阅读(1125)  评论(0)    收藏  举报

导航