一 实验内容
在Linux 0.11下添加两个系统调用,并编写两个应用程序测试他们
iam()
原型:
int iam(const char* name);
功能为将字符串参数name的内容拷贝到内核中保存下来,要求name的长度不能超过23个字符,返回拷贝的字符数。若name的字符超过了23,返回“-1”,并置errno为EINVAL。
whoami()
原型:
int whoami(char* name,unsigned int size);
功能为将内核中由iam()保存的名字拷贝到name指向的用户地址空间中。返回值是拷贝的字符数。若size小于需要的空间,则返回“-1”,并置errno为EINVAL。
二.系统调用的过程
1. 应用程序调用API
2. API将系统调用号存入EAX,然后启用中断进入内核态
3. 中断处理函数根据系统调用号,调用对应的系统调用
4. 系统调用完成后,将返回值存入EAX,返回到中断处理函数,再返回到API
5. API将返回值返回给应用程序
三.API
3.1 API的调用过程
将系统调用的编号存入EAX,将函数参数存入其他通用寄存器,触发0x80号中断
3.2 研究close()的API
#define __LIBRARY__
#include<unistd.h>
_syscall1(int,close,int,fd)
_syscall1是一个宏,在include/unistd.h中定义
另外还有_syscall0,_syscall2,_syscall3;分别可以传递0个参数,两个参数,三个参数。所以从linux0.11 的机制来看,它的系统调用最多能传递三个参数。
其中__NR_##name会被宏替换为__NR_close,放入EAX中,这个__NR_close就是系统调用的编号,在/oslab/linux-0.11/include/unistd.h中定义
所以我们需要在include/unistd.h中添加__NR_iam()和__NR_whoami()
3.3 添加iam()的API
//iam.c
#define __LIBRARY__ //有这个_syscall1等才有效
#include<unistd.h> //帮助编译器获得系统调用的编号
_syscall1(int,iam,const char*,name);
int main(int argc,char* argv[])
{
int len=0;
len = iam(argv[1])//传入第一个参数
return len;
}
3.4 添加whoami()的API
//whoami.c
#define __LIBRARY__
#include<unistd.h>
syscall2(int,whoami,char*,name,unsigned int,size);
int main()
{
char arr[30];
int len=0;
len = whoami(arr,30);
printf("%s\n",arr);
return len;
}
3.5 保存文件
通过挂载来实现文件交换:
在linux 0.11未在bochs下运行时,在oslab目录下,在终端输入 sudo ./mount-hdc 。挂载内核的根文件目录
系统镜像文件到ubuntu,然后在hdc目录下进行文件交换。
注意:linux 0.11的文件系统是minix,实测cenos7不支持,建议使用ubuntu
将iam.c和whoami.c保存在oslab/hdc/usr/root目录下。
四.添加iam()和whoami()的系统调用
4.1 添加系统调用号
在oslab/hdc/include/unistd.h中添加
//这里的系统调用号,需要和后面系统调用表system_call.s中的位置相对应
#define __NR_iam 72
#define __NR_whoami 73
4.2 修改系统调用总数
在添加系统调用后,需要在kernel/system_call.s中修改nr_system_calls
我们添加了iam和whoami两个系统调用,所以需要将72改为74
五 在系统调用表中添加函数
在/kernel/system_call.s中,有一句代码:call sys_call_table(,%eax,4)
根据汇编寻址方法,实际上为:call sys_call_table+4*%eax
其中eax放的是__NR_xxname,也就是系统调用号
sys_call_table是一个函数指针数组,定义在include/linux/sys.h中
我们需要在这个数组的末尾,添加72号sys_iam和73号sys_whoami,与他们的系统调用号__NR_xxname相对应。同时还要在sys.h中添加:
extern int sys_iam();
extern int sys_whoami();
六.实现sys_iam()和sys_whoami()
最后,我们需要在内核中实现系统调用的函数,并且将修改后的内核重新编译。
get_fs_byte()可以从用户态内存处取得一个字节的内容。
put_fs_byte()把一个字节的内容放到用户态内存空间中
//who.c
#include<unistd.h>
#include<string.h>
#include<errno.h>
#define __LIBRARY__
char msg[24]; //作为存放name的容器,在这里创建,处于内核态空间
int len =0;
int sys_iam(const char* name)
{
int i =0;
char tmp[30];
for(i=0;i<30;i++)
{
tmp[i] = get_fs_byte(name+i);
if(tmp[i]=='\0')
{
break;
}
len++;
}
if(len>23)
{
return -(EINVAL);
}
else
{
strcpy(msg,tmp);
}
return i;
}
sys_whoami(char* name,unsigned int size)
{
int i =0;
int len =0;
if(len>size)
{
return -(EINVAL);
}
else
{
while(msg[i]!='\0')
{
put_fs_byte(msg[i],name+i);
i++;
}
}
return i;
}
我们将who.c放在oslab/linux-0.11/kernel下
七.修改Makefile文件
为了让上面的who.c可以和其他的Linux代码编译链接到一起,必须要修改Makefile文件,这个文件里记录了
所有源程序文件的编译、链接规则,make指令按照Makefile的规则编译整个代码树
添加who.o
添加who.s who.o: who.c …/include/linux/kenel.h …/include/unistd.h
修改完毕后,在linux-0.11目录下,在终端输入make all编译内核,在显示sync时,表明编译正确完成。
八.编译运行程序
8.1编译
1 返回到oslab目录,输入 sudo umount hdc 取消挂载,
2 输入 ./run 在bochs中启动linux-0.11
3 编译程序:在bochs中输入:gcc -o iam iam.c -Wall 和 gcc -o whoami whoami.c -Wall
8.2 运行程序
输入:
./iam yunhun
./whoami
输出结果为:
至此实验内容完结。