操作系统实验2:系统调用
实验内容请查看实验指导手册
- 实验需要掌握的知识点:
如何使用用户函数调用系统函数、_syscall宏的含义以及作用、管理使用linux系统的基本知识- 实验中添加和修改的文件
(1)添加了:
iam.c(路径:oslab/hdc/usr/root)
whoami.c(路径:oslab/hdc/usr/root)
who.c(路径:oslab/linux-0.11/kernel)
(2)修改了:
unistd.h(路径:oslab/hdc/include/unistd.h)
system_call.s(路径:oslab/linux-0.11/kernel/system_call.s)
sys.h(路径:oslab/linux-0.11/include/linux/sys.h)
Makefile(路径:oslab/linux-0.11/kernel/Makefile)
绪论
我花了很长的时间,才搞清楚这个实验,干了一件什么事情:
- 在用户层面写了两个程序iam.c和whoami.c,通过syscall这个宏开启调用系统函数的窗口,调用sys_iam以及sys_whoami系统函数。
- 编写了两个函数sys_iam以及sys_whoami,以who.c的文件格式保存在了linux0.11系统的内核(kernel)中;
- sys_iam调用了一个名为get_fs_byte()的系统函数实现数据读取输入,sys_whoami则调用了put_fs_byte()这个系统函数完成对数据的打印输出。
- 整个实验就是作了一件这么简单的事情,至于修改了Makefile等文件的复杂操作,可以理解为让上述过程实现的辅助过程。
下面是具体的实验过程:
一、实验内容
1.编写iam以及whoami程序
iam.c的代码
#define __LIBRARY__
#include <unistd.h>
//_syscall1宏展开后是一个调用系统函数的函数
_syscall1(int, iam, const char*, name);
int main(int argc,char ** argv)
{
int wlen = 0;
if(argc<1)
{
printf("not enough arguments!\n");
return -2;
}
wlen = iam(argv[1]);
return wlen;
}
whoami.c的代码
#define __LIBRARY__
#include <unistd.h>
//_syscall2是宏,宏展开后是一个调用系统函数的函数
_syscall2(int, whoami,char*,name,unsigned int,size);
int main()
{
char s[30];
int rlen = 0;
rlen = whoami(s,30);//这里调用了_syscall2写的whoami函数
printf("%s\n",s);
return rlen;
}
两个函数的编写参考了linux0.11中close.c函数。内核中编写完成后,保存在oslab/hdc/usr/root
。通过ubuntu宿主机与虚拟机的文件交换,交换方式如下,才可将编写好的文件放到虚拟机中。
补充:宿主机ubuntu和虚拟机linux0.11的文件交换。
在linux0.11没有运行时,通过cd ~/oslab/,sudo ./mount-hdc。挂载内核的根文件系统镜像文件到ubuntu然后通过cd /oslab/hdc进入目录读写文件。
读写完毕,结束挂载通过cd ~/oslab/,sudo umount hdc。这时才可开启Bochs虚拟机
2.在内核的include/unistd.h添加系统调用号
为了实现自己编写的系统调用,我们需要手动实现 __NR_XXXXX 。就是系统调用的编号,别急着停止挂载,在 /home/shiyanlou/oslab/hdc/include/unistd.h
中操作。操作结束后,可以短暂的停止挂载了。
#define __NR_close 6
/*
所以添加系统调用时需要修改include/unistd.h文件,
使其包含__NR_whoami和__NR_iam。
*/
//我的添加:
#define __NR_whoami 72
#define __NR_iam 73
3.修改系统调用表和调用总数
sys_call_table
,为系统调用表。在linux-0.11/kernel/system_call.s
中,因为我们添加了两个系统调用,所以将nr_system_calls
加2,修改系统调用总数。
# offsets within sigaction
sa_handler = 0
sa_mask = 4
sa_flags = 8
sa_restorer = 12
nr_system_calls = 74
sys_call_table
是一个函数指针数组的起始地址,它定义在 include/linux/sys.h
中。增加实验要求的系统调用,需要在这个函数表中增加两个函数引用 ——sys_iam
和 sys_whoami
。当然该函数在 sys_call_table
数组中的位置必须和__NR_xxxxxx
的值对应上。
同时还要仿照此文件中前面各个系统调用的写法,加上extern int sys_whoami();
以及extern int sys_iam();
4.实现内核函数
添加系统调用的最后一步,是在内核中实现函数 sys_iam()
和 sys_whoami()
who.c代码如下:
#define __LIBRARY__
#include <asm/segment.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
char msg[24]; //23个字符 +'\0' = 24
int sys_iam(const char * name)
/***
function:将name的内容拷贝到msg,name的长度不超过23个字符
return:拷贝的字符数。如果name的字符个数超过了23,则返回“-1”,并置errno为EINVAL。
****/
{
int i;
//临时存储 输入字符串 操作失败时不影响msg
char tmp[30];
for(i=0; i<30; i++)
{
//从用户态内存取得数据
tmp[i] = get_fs_byte(name+i);
if(tmp[i] == '\0') break; //字符串结束
}
//printk(tmp);
i=0;
while(i<30&&tmp[i]!='\0') i++;
int len = i;
// int len = strlen(tmp);
//字符长度大于23个
if(len > 23)
{
// printk("String too long!\n");
return -(EINVAL); //置errno为EINVAL 返回“-1” 具体见_syscalln宏展开
}
strcpy(msg,tmp);
//printk(tmp);
return i;
}
int sys_whoami(char* name, unsigned int size)
/***
function:将msg拷贝到name指向的用户地址空间中,确保不会对name越界访存(name的大小由size说明)
return: 拷贝的字符数。如果size小于需要的空间,则返回“-1”,并置errno为EINVAL。
****/
{
//msg的长度大于 size
int len = 0;
for(;msg[len]!='\0';len++);
if(len > size)
{
return -(EINVAL);
}
int i = 0;
//把msg 输出至 name
for(i=0; i<size; i++)
{
put_fs_byte(msg[i],name+i);
if(msg[i] == '\0') break; //字符串结束
}
return i;
}
who.c文件保存在oslab/linux-0.11/kernel
中。
5.修改Makefile文件
要想让我们添加的 kernel/who.c
可以和其它 Linux 代码编译链接到一起,必须要修改 Makefile 文件。
Makefile 在代码树中有很多,分别负责不同模块的编译工作。我们要修改的是 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
添加了 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
添加了 who.s who.o: who.c ../include/linux/kernel.h ../include/unistd.h
。
修改了Makefile后,在终端中进入oslab/linux0.11
,输入make all
,进行编译。正确的编译最后一行内容为sync
。如果你的编译后出现了错误信息,请根据错误信息进行修改,实在不行的话建议从头开始重做一遍。
6.编译运行程序
在终端中退回到oslab/
,输入命令sudo umount hdc
退出挂载。输入命令./run
启动bochs
。进入bochs
后在输入框中依次输入gcc -o iam iam.c -Wall
、gcc -o whoami whoami.c -Wall
。
接着,在输入框中输入:
./iam f**kinglab2
./whoami
如果一切正常的话,将会得到如下图所示的结果:
至此完成对两个程序的编译。
二、程序测试
Linux 的一大特色是可以编写功能强大的 shell 脚本,提高工作效率。本实验的部分评分工作由testlab2.c
和脚本 testlab2.sh
完成。它的功能是测试 iam.c
和 whoami.c
。
首先将 iam.c
和 whoami.c
在linux0.11下分别编译成 iam
和 whoami
(如果实验1做过就不用了),然后将 testlab2.sh
和testlab2.c
(在 /home/teacher
目录下) 拷贝到虚拟机目录oslab/hdc/usr/root
。
拷贝后需要卸载挂载,启动bochs
cd ~/oslab
//卸载挂载
sudo umount hdc
//启动bochs
./run
在bochs中输入如下命令
gcc -o testlab2 testlab2.c
sync
./testlab2
结果如图:
8个pass,这50分拿到了。
再执行testlab2.sh
在bochs中用下面命令为此脚本增加执行权限:
$ chmod +x testlab2.sh
然后运行之:
$ ./testlab2.sh
结果如图:
30分又到手了
三、回答问题
-
Q1:从 Linux 0.11 现在的机制看,它的系统调用最多能传递几个参数?你能想出办法来扩大这个限制吗?
- 最多传递3个参数。从
_syscall3(type,name,atype,a,btype,b,ctype,c)
可看出。Linux-0.11的系统调用通过寄存器ebx、ecx、edx传递参数,最多能传递3个参数。
- 最多传递3个参数。从
-
Q2:用文字简要描述向 Linux 0.11 添加一个系统调用 foo() 的步骤
- include/unistd.h中添加系统调用号
- 在kernel/system_call.s修改总调用数
- include/linux/sys.h添加调用
- 修改kernel/Makefile
- 在内核文件中实现foo.c
- make all重新编译系统,使用gcc编译用户态程序
四、总结
系统调用的分析
1.初始化:
2.调用流程(以iam()函数为例):
参考文献:
1.(浓缩+精华)哈工大-操作系统-MOOC-李治军教授-实验2-系统调用
2.蓝桥云课-操作系统原理与实践
3.哈工大操作系统实验手册
4.哈工大实验“官方”github仓库
5.哈工大操作系统实验课——系统调用(lab3)
6.操作系统实验报告-系统调用