首先进入实验楼后,解压目标文件,做一下初始化的工作。
cd /home/shiyanlou/oslab
tar -zxvf hit-oslab-linux-20110823.tar.gz -C /home/shiyanlou
1. 在linux-0.11/include/linux/sys.h中添加函数声明extern int sys_iam()和extern int sys_whoami(); 然后在中断向量表fn_ptr sys_call_table[]的最后面填上系统调用sys_iam()和sys_whoami()。
cd ./linux-0.11/include/linux
vim sys.h
打开sys.h后,可以看到如下图的代码,其中红色圈起来的是我们要添加的内容:
即先添加函数声明:
extern int sys_iam();
extern int sys_whoami();
再在中断向量表最后面添加:sys_iam, sys_whoami
2.在linux-0.11/kernel/system_call.s中修改系统调用的个数nr_system_calls,使其增加2。
cd ../../kernel
vim system_call.s
如图所示,将nr_system_calls由72改成74.
**3.新增系统调用号,在虚拟机中修改unistd.h。先通过运行sudo ./mount-hdc 把虚拟机硬盘挂载在oslab/hdc目录下,然后在hdc/usr/include目录下修改unistd.h。
增加#define __NR_iam 72 和 #define __NR_whoami 73
首先运行mount-hdc来把虚拟机硬盘挂载在oslab/hdc目录下,然后切换目录打开unistd.h
cd ../../
sudo ./mount-hdc
cd ./hdc/usr/include
vim unistd.h
如图添加#define __NR_iam 72 和 #define __NR_whoami 73
4.新增who.c文件,实现系统调用的函数
切换目录到linux-0.01/kernel ,在这里创建一个who.c文件。
cd ../../../linux-0.11/kernel
vim
在这里首先要说明题目中的要求文件中iam()函数 “ 如果 name 的字符个数超过了 23,则返回 “-1”,并置 errno 为 EINVAL。”
这里我觉得可能是写错了?我在代码中返回-1后是通不过最后的测试的,只有返回EINVAL才可以通过测试。也就是说,EINVAL是错误代码的一个取值,题目实际上应该是让我们返回这个值EINVAL;同时<errno.h>头文件会自动帮我们把变量errno置为对应的错误码EINVAL,所以我们就不用再写errno = EINVAL 了。在代码中<errno.h>头文件定义了错误代码,关于错误代码error如果不清楚可以参考https://blog.csdn.net/m0_37221216/article/details/101212781
还有一个点需要注意,如果把用户态的数据传入核心态,如这道题中我要把用户态地址name处的字符串传入核心态,name是一个指针,那么是不能直接用 * name代表name指向的字符串的,因为指针传递的是应用程序所在地址空间的逻辑地址,在内核中如果直接访问这个地址,访问到的是内核空间中的数据,不会是用户空间的。所以这里需要通过get_fs_byte()和put_fs_byte()函数来实现用户态与核心态数据的交换。其中get_fs_byte()函数的参数是用户空间的逻辑地址,返回值是该地址处的一个字符的内容;其中put_fs_byte()函数的功能是向用户空间中地址处写一个字节的内容,有两个参数,第一个参数是要写入的数据,第二个参数是用户空间中的逻辑地址。
who.c文件的代码如下:
其中
#include <string.h> //提供复制字符串的函数strcpy
#include <errno.h> //定义了错误代码
#include <asm/segment.h> //提供get_fs_byte等函数
char msg[24];
int sys_iam(const char * name)
{
char tep[26];
int i = 0;
for(; i < 26; i++)
{
tep[i] = get_fs_byte(name+i);
if(tep[i] == '\0') break;
}
if (i > 23) return -(EINVAL);
strcpy(msg, tep);
return i;
}
int sys_whoami(char * name, unsigned int size)
{
int len = 0;
for (;msg[len] != '\0'; len++);
if (len > size)
{
return -(EINVAL);
}
int i = 0;
for(i = 0; i < size; i++)
{
put_fs_byte(msg[i], name+i);
if(msg[i] == '\0') break;
}
return i;
}
5.修改linux-0.01/kernel目录下的Makefile文件,在OBJS中加入who.o,并添加生成who.s、who.o的依赖规则。
vim Makefile
要添加的内容如图所示:即添加who.c 和 who.s who.o: who.c …/include/linux/kernel.h …/include/unistd.h
修改完后,退回linux-0.11目录下,运行make all指令编译内核,who.c会被加入到内核。
cd ../
make all
6.新增iam.c 跟whoami.c文件以测试是否添加系统调用成功
注意这两个文件是要在linux 0.11版本上编译的,所以我们应当先通过运行mount-hdc文件来把虚拟机的硬盘挂载在oslab/hdc 目录下,然后进入hdc/user/root目录中(这个目录就是虚拟机一开机的所在的目录)再创建iam.c和whoami.c。
cd ../
sudo ./mount-hdc
cd ./hdc/usr/root
内核源代码的unistd.h文件中定义了宏函数_syscalln(),其中n代表携带的参数个数,该宏函数展开时会通过int0x80进入内核并找到对应编号的系统调用。若我们要在用户程序中直接执行对应的系统调用,那么该系统调用宏的形式如下(具体参考linux-0.11内核完全注释的5.5章节或课程实验提示):
#define LIBRARY
#include <unistd.h>
_syscalln( …)
因此在iam.c和whoami.c文件代码中需要包含这三行代码,然后我们就可以在main函数中直接使用系统调用了。
main函数的两个参数是argc和argv[],其中argc的值是在命令行运行程序时给的参数的个数;argv是一个指针数组,argv[1]是在命令行执行程序时传递给它的第一个参数的地址, argv[2] 是在命令行执行程序时传递给它的第二个参数的地址…因此在iam.c文件中的main里直接使用系统调用函数iam,参数即为argv[1]。
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(int argc, char ** argv)
{
char t[30];
whoami(t, 30);
printf("%s\n", t);
return 0;
}
7.运行和测试:
首先把/home/teacher 目录下的两个测试文件testlab2.c和testlab2.sh移动到虚拟机的硬盘中的开机目录里
cd ~/oslab
sudo ./mount-hdc
cd ./hdc/usr/root
cp /home/teacher/testlab2.c ./
cp /home/teacher/testlab2.sh ./
然后切换到oslab目录,运行虚拟机
cd ../../../
./run
在弹出的bochs虚拟机窗口中的命令行中,编译几个C语言文件
gcc -o iam iam.c
gcc -o whoami whoami.c
gcc -o testlab2 testlab2.c
编译好了以后,就可以最后的运行测试了:在虚拟机中通过iam将你的名字从用户态传入内核。然后通过whoami将传入内核的名字打印出来
./iam zhaotianhao
whoami
结果如下:
然后我们还可以运行两个测试文件testlab2.c和testlab2.sh
./testlab2
./testlab2.sh
结果如下:第一个满分50%,第二个满分为30%