我的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。然后就去调用我们写的系统调用函数了。
而从内核空间拿到正确的用户空间数据的方法,要去看
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
如代码
_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#include #include #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#define __LIBRARY__ #include _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#define __LIBRARY__ #include _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的依赖项。
以上就是完整报告,祝大家实验顺利~