前引
这个lab真的花了我很久的时间,将近做了5个晚上,可能我效率低吧,但是总算是做完了,我觉得这个实验还是比较有难度的,其实我是先做的内存管理那个实验,但是发现那个章节其中一个实验,是在前面的实验基础上进阶,所以又反过来做系统调用这个实验,还是一个一个按照顺序来吧。下面给出我的完整实验过程。
实验目的和内容
这个实验就是说用一个宏函数,将我们用户态的字符串,储存到内核空间,然后再用一个宏函数,把储存到内核空间的字符串取出来,实现两个系统调用函数。

提示
实验册给出了很多提示流程,这些流程建议反复看,有助于理解,而且要认真读,有些细节就决定了能否完成实验。

比如这里,这个实验的总体流程,
1.API函数的用处是什么:我们实验的困难点在于需要用用户态的函数(这个需要我们手搓 ),去取内核的数据,本身是行不通的。用户态是访问不了内核态的 ,所以需要我们用户态的函数,能实现中断,进入内核,用对应系统调用号调用相对应的内核函数(iam() )去执行(需要我们手搓),这个内核函数负责将储存在用户态的字符串,储存到内核态中去。
所以这个API函数就是我们用户态的函数,它就像用户态进入内核态的一个接口一样。
那么还有一个APi函数,需要进入内核,用系统调用号找到对应的内核函数(手搓whoami()),取出刚刚iam()储存到内核空间的字符串,然后返回给用户态,
在终端使用的效果类似这样

实验步骤
ok那么总结一下实验中哪些事情需要我们去做的
1.首先添加系统调用号
_syscall1(int,close,int,fd)这就是一个我们定义的宏函数,其中close对应的位置参数,就是我们手搓并且调用的内核函数,
这个宏函数的目的在于,我的理解很像一个中间件,使用了这个函数,close的内部就添加了int 0x80中断功能,并且宏函数还会提供内核函数的参数。下面是实验册解释

所以我们需要去内核中维护的系统调用表上加上两个编号,也就是iam和whoami的编号
在unitsd.h中加入对应的系统调用号,注意顺序

2.增加系统调用总数
在文件名 system_call.s
路径 oslab/linux-0.11/kernel/system_call.s
把系统调用总数+2,(原本是72,改后应为74)
文件中这一行,改成这样,nr_system_calls = 74
3.函数调用表
文件名 sys.h
路径 include/linux/sys.h

注意顺序!!,之前调用号哪个在前,哪个在后,这里的顺序不能变!否则调用会乱(我就踩坑了,调用iam,结果跑出来whoami的打印信息)
4.修改Makefile


直接照抄
5.系统调用代码------iam() 和 whoami()
这里其实可以先写个壳子,里面具体的代码实现不用一次写出来,可以检查编译过没过,才能检查之前的编译问题有没有正确修改,这里我直接给出我的最终代码实现
在 oslab/linux-0.11/kernel 中创建 who.c 文件,这里提醒一下我的代码很乱(因为后面真的不想改了),但是是正确的,如果想要更为正确简洁的代码可以看一下这个博主的
love6:哈工大操作系统之基础(Lab2 添加系统调用实验全过程+跳坑解析)
arduino
#include <string.h> //memest
#include <errno.h>
#include <asm/segment.h>
#include <linux/kernel. h> // printk,内核的打印函数
char name_buf[32]={0}; //内核全局变量缓冲区
int flag=0; //标志
int sys_iam(const char * name)
{
char temp;
int count=0;
while((temp=get_fs_byte(name+count))!='\0'){
//注意这里运算顺序,我们想要前面先运算,需要打括号
if (count>=23){
memset(name_buf,0,sizeof(name_buf));
flag=1;
//如果超越最大值了,后标志flag=1,后续whoami,读取后直接返回错误,不会返回上一个测试用例的储存值
return -EINVAL;
}
name_buf[count++]=temp;
flag=0;
}
name_buf[count]='\0';
printk("%d neihe zifu has been uploaded",count);
return count;
}
int sys_whoami(char *buf , unsigned int size)
{
if (flag==1){
return -EINVAL;
}
int count=0;
while (name_buf[count]!='\0'){
if (count+1<=size){
put_fs_byte(name_buf[count],buf+count);
count++;
} else {
return -EINVAL;
//不返回-1,是因为不能返回具体的错误信息,返回值<0代表调用失败
}
}
printk("neihe %d ge zi fu",count);
put_fs_byte('\0',buf+count);
return count;
}
6.内核函数编译
写完后就 cd ~/oslab/linux-0.11
然后 make clean && make all
这里一般会出现很多错误,需要自己完善内核函数的编译问题,多花点时间吧
最后如果没问题了会出现下面的结果

7.用户态函数
注意用户态函数,也就是main函数。需要先挂载
然后写函数
我们就在usr/root中创建

iam.c
arduino
#define __LIBRARY__
#include <unistd.h>
#include <stdio.h>
_syscall1(int,iam,const char *,name);
int main(int argc,char *argv[]){
if (argc<2){
return 0;
}
iam(argv[1]);
return 0;
}
whoami.c
arduino
#define __LIBRARY__
#include <unistd.h>
#include <stdio.h>
_syscall2(int,whoami,char * ,buf,unsigned int, size)
int main(int argc,char *argv[]){
char buf[24]={0};
//用户态缓冲区
whoami(buf,24);
printf("%s",buf);
return 0;
}
最后别忘了卸载 sudo umount hdc
8.测试一下
在 ~/oslab 中 执行 ./run
启动boches
然后编译我们的用户态文件
gcc iam.c -o iam
gcc whoami.c -o whoami
./iam renhan 储存字符串
./whoami 读出字符串

9.测试一下打分文件
在上面我们只是初步用了自己的名字,试了一下程序,如果没问题后,要进一步的测试打分文件,在这个文件中会用多了案例,来看是否能完整实现实验要求
测试文件在home/teacher中
先挂载sudo ./mount-hdc
cp /home/teacher/testlab2.sh /home/teacher/testlab2.c hdc/usr/root/
执行上面的操作
然后文件就会出现在目录中

最后测试,进入boches


上面的是testlab2.c的执行结果,最高 50%
下面是testlab2.sh的执行结果,最高30%
只不过我的testlab2.sh,最后一个案例也失败了,不知道为什么改了很多次,如果需要完整正确的可以看看其他人的
关于挂载和卸载的原因(补充知识)



大概意思就是Ubuntu是我们的宿主机,然后在Ubuntu上启动boches模拟机,然后再启动linux0.11 ,
Image = Linux 0.11 内核本体,
root-0.11.img = Linux0.11 的虚拟硬盘,
Image是linux-0.11操作系统内核代码,需要在Ubuntu上面修改,然后root-0.11.img是硬盘(这里也像u盘),我们在Ubutnu上挂载(插上u盘),然后写我们的用户态测试代码,然后卸载(取出u盘),此时u盘
ai讲解:Ubuntu 是本次实验的宿主机系统,我们在 Ubuntu 中运行 Bochs x86 硬件模拟器,由 Bochs 加载运行 Linux 0.11。
Image:内核镜像文件,由内核源码编译得到,操作系统启动时将它载入内存,提供系统调用、中断、内存管理等内核能力;修改内核代码后要重新编译生成新Image才能生效。root-0.11.img:虚拟硬盘镜像,内部存放 Linux 0.11 的文件系统。可以把它类比成 U 盘:在 Ubuntu 里挂载该镜像后,就能用 cp 命令把用户态可执行程序、测试脚本拷贝进去;操作结束必须卸载镜像,再启动 Bochs,防止宿主机、模拟器并发读写破坏磁盘数据。
后引
到这里就结束了,中途其实遇到了蛮多问题的(甚至想过不做了),但是如果肯花时间就能解决,最后实现的那一刻一切都是值得的,能给你增加不错的自信心。那么接下来的时间,我准备把进程与线程管理系统学一遍,把课后lab做一次。