一、实验内容
1.在原有的Linux 0.11 上添加两个系统调用;
注意 : 实验2 是重写bootsect.s 和 setup.s;
通过VMware快照功能,恢复到原有的Linux-0.11开始实验3。
(1)iam()
int iam(const char * name);
完成的功能:将字符串参数 name 的内容拷贝到内核中保存下来。
要求:name 的长度不能超过 23 个字符。
返回值:拷贝的字符数。如果 name 的字符个数超过了 23,则返回 “-1”,并置 errno 为 EINVAL(即:return -EINVAL)。
(2)whoami()
int whoami(char* name, unsigned int size);
完成的功能:将内核中由 iam() 保存的名字拷贝到 name 指向的用户地址空间中,同时确保不会对 name 越界访存(name 的大小由 size 说明)。
返回值:拷贝的字符数。如果 size 小于需要的空间,则返回“-1”,并置 errno 为 EINVAL。
2.编写两个简单的应用程序测试它们;
二、实验过程
1. 系统调用的整体逻辑
本实验采用,在用户程序中直接执行对应的系统调用。
(1)从用户级转到内核级的唯一方式:中断。
这是iam.c的部分代码截图,解释:
①_syscall1是宏函数,详细了解见《Linux-0.11内核完全注释》5.5和参考资料;该函数主要实现2点:eax = 系统调用号 + int 0x80
(2)system_call函数
(3)系统调用函数:sys_xxx
本次实验要编写2个系统调用函数,即①sys_iam;②sys_whoami; 见“二、2.”
2. 按照上述逻辑修改相应文件
(1) 修改系统调用总数, 文件:kernel/system_call.s
(2)在系统调用函数表中增加系统调用条目,文件:include/linux/sys.h
千万别异想天开的放在最前面,不然之后添加_NR_xxxxxx会很麻烦,微笑脸。
(3)添加iam和whoami系统调用编号的宏定义(_NR_xxxxxx),文件:include/unistd.h
系统调用函数在 sys_call_table 数组中的位置必须和 __NR_xxxxxx 的值对应上。上文提到的 eax 中放的是系统调用号,即 __NR_xxxxxx。
(4)验证上述修改是否成功,先简单实现sys_iam和sys_whoami,文件:kernel/who.c
(5)修改Makefile 文件:kernel/Makefile
插播:之后调试会用到printk函数,它也是在kernel目录下,而且在Makefile中,有…/include/linux/kernel.h,所以可以使用printk函数。
(6)进入linux-0.11,编写应用程序进行测试
①vi /usr/include/unistd.h
增加iam和whoami的宏定义
别忘了经常执行“sync”以确保内存缓冲区的数据写入磁盘,不然的话重新进入linux-0.11得重新增加宏定义。
②编写测试文件:test.c
#define __LIBRARY__ /* 必须定义这个宏 */
#include <unistd.h>
/* iam 和 whoami 系统调用的用户接口 */
_syscall1(int, iam, const char*, name);
_syscall2(int, whoami, char*, name, unsigned int, size);
int main(void)
{
char buf[24];
iam("test");
whoami(buf, 24);
return 0;
}
(7)总结:至此,说明成功添加系统调用了。
2.实现sys_iam和sys_whoami
(1)代码
#include <string.h> //实现置 errno为EINVAL
#include <errno.h> //调用了strcpy
#include <asm/segment.h> //调用了get_fs_byte, put_fs_byte
#definie maxSize 24
char msg[maxSize]; //这样就可以在内核中保存下来,最后一位是'\0'
int sys_iam(const char *name)
{
char tmp[maxSize];
int i;
for(i = 0; i < maxSize; i++) {
tmp[i] = get_fs_byte(name + i);
//printk("%c\n", tmp[i]);
if(tmp[i] == '\0') break; //'\0'表示字符串结束了
}
if(i == maxSize) {
//printk("too long!\n");
return -EINVAL;
}
else {
strcpy(msg, tmp); //感觉在内核中调用C语言库会不太好
return i;
}
}
int sys_whoami(char *name, unsigned int size)
{
int msg_size = 0;
while(msg[msg_size] != '\0') msg_size++;
//printk("msg_size : %d\n", msg_size);
//printk("msg : %s\n", msg);
if(size < msg_size) return -EINVAL;
else {
int i;
//printk("size : %d\n", size);
for(i = 0; i < size; i++) {
//printk("ok\n");
//printk("name : %c\n", name[i]);
//printk("msg : %c\n", msg[i]);
put_fs_byte(msg[i], name + i);
if(msg[i] == '\0') break;
}
return i;
}
}
适当地向屏幕输出一些程序运行状态的信息,也是一种很高效、便捷的调试方法,有时甚至是唯一的方法,被称为“printf 法”。
for(int i = 0; i < size; i++)这么写会报错,解释
3.编写两个简单的应用程序进行测试
(1)iam.c代码
#include <unistd.h> //有它,编译器才能获知自定义的系统调用的编号
#define __LIBRARY__ //有它,_syscalln才有效
_syscall1(int,iam,const char*,name);
int main(int argc, char **argv) {
iam(argv[1]);
return 0;
}
(2)whoami.c代码
#include <unistd.h> //有它,编译器才能获知自定义的系统调用的编号
#include <stdio.h>
#define __LIBRARY__ //有它,_syscalln才有效
_syscall2(int,whoami,char*,name,unsigned int,size);
int main() {
char s[30];
whoami(s, 30);
printf("%s\n", s);
return 0;
}
这里有些心酸的事儿:上古时代linux-0.11自带的vi编辑器和今天的语法规则不一样吧,或者是键盘的原因。总之,输入很不方便。由此导致了一些莫名其妙的问题,只能重新开始写,认认真真保证一次不错的敲好,才幸运的编译通过…或者,宏函数的参数不能"int, whoami"多了个空格,必须要"int,whoami"?
可以采用Ubuntu和Linux-0.11文件交换的方式,在oslab目录下,输入命令
sudo ./mount-hdc
(3)验证
为了得到正确结果,对who.c调试的时候,也发生了莫名其妙的bug,增加了一些printk,其他也没改,又莫名其妙的好…玄学!总之,可算是看到上图的结果了。
三、其他
实验过程中的一些小积累。
1.对const char *的理解。(以const char * ptr为例子)
ptr是指向字符串变量的指针,可以指向不同的字符串,但不能修改指向字符串的内容。
#include <cstdio>
int main() {
const char * ccptr;
char * cptr;
char name1[] = {"Hello World!"};
char name2[] = {"World Hello!"};
ccptr = name1;
cptr = name1;
//ccptr[0] = 'h';
cptr[0] = 'h';
printf("name1 : %s\n", ccptr);
ccptr = name2;
printf("name2 : %s\n", ccptr);
return 0;
}
总结 : const char * ptr;
①不可以通过ptr[i]来改变指向的字符串;
②但可以指向其他的字符串。
2.C语言级调试遇到的问题及解决办法
在oslab
目录下,通过以下命令进行C语言级调试:
①./dbg-asm
②./rungdb
新开一个终端窗口,进入oslab目录
(1)问题1:缺少libncurses.so.5
解决办法:
①sudo add-apt-repository universe
②sudo apt-get install libncurses5 libncurses5:i386
(2)问题2:缺少libexpat.so.1
解决办法:
解决这个问题,兜了些圈子,不敢保证一定有效。
参考资料
3.sys_iam中,遇到一个疑惑,读取到的name,怎么保存到内核中,使得whoiam去读取。
解决办法:用全局字符数组就行!
唉,傻到了啊