当前位置: 代码迷 >> 综合 >> 哈工大操作系统之基础(Lab2 添加系统调用实验全过程+跳坑解析)
  详细解决方案

哈工大操作系统之基础(Lab2 添加系统调用实验全过程+跳坑解析)

热度:57   发布时间:2023-11-17 18:11:36.0

文章目录

    • 专栏博客链接
    • 前引
    • Phase总概括
    • 预先了解
      • 实验内容
      • 评分标准
      • 实验提示
      • 应用程序如何调用系统调用
    • 预处理(正式开始实验)
      • 解压压缩包
      • 增加系统调用号
      • 增加系统调用数
      • 函数调用表修改
      • 修改MakeFile声明
    • 开始编写 系统调用代码
    • 编译内核
    • 编写应用程序
      • 进入虚拟机文件系统
      • 编写iam.c 和 whoami.c
    • 测试文件复制 关闭文件系统
    • 运行linux虚拟机 测试数据


专栏博客链接

哈工大操作系统原理与实践 Lab全实验博客链接


前引


说真的 写到现在 我不知道已经重复了多少遍多少遍
重启环境 因为死机而重启的过程了
做这个Lab 我差不多整整花了一天多的时间 而且一直卡在一些奇奇怪怪的地方
啊 各种问题 下午本来终于快弄完了
结果环境卡住了 直接死机了
我去找客服 客服去找了研发人员 最后解决了问题 还给了我三天的vip使用时间
我真的麻了 各种小细节让我头疼
到最后我还是坚持下来了 好像在我写这个博客的时候好像我测试
testlab2.sh testlab2 一个是20% 一个是50% 就是一个超级长的string fail掉了
但是我也找到了原因 只是刚刚一不小心又把环境删除了
所以之后三天vip过期了 我还是重新冲个一个月会员吧


Phase总概括


这个实验目的 我们最后要达成什么目的 我们需要先搞清楚
我们需要设计一个小的应用程序 并且达到在内核中存放我们的字符串
最后在用户区读取内核区的我们的字符串 复制到 用户区并打印出来

这就是我们最终的目的
那我们可以一步一步来 慢慢来进行 分析


预先了解


实验内容


这个实验 我觉得很多地方坑真的很多
就是关于系统细节的一些地方 一定一定要注意
因为这些地方 我就是编译不了程序 或者 文件系统破坏此类的
真的很令人头疼 我还是一步一步慢慢来弄

我们根据实验册 他先提前把我们的要写的函数交代了出来

int iam(const char * name);
这个函数我们是 把目标输入字符串放到内核区域的

int whoami(char* name, unsigned int size);
这个函数我们是 需要把内核区的字符串放到用户区域的 并且printf出来


评分标准


最后我们需要 把 testlab2.c testlab2.sh
放到我们的虚拟机文件系统中 我们所编译完成的iam.c whoiam.c旁边
并且运行之后 这两个脚本就会自动通过一些phase来检测我们的函数正确性


实验提示


删除原来的文件指令
$ cd ~/oslab
$ sudo rm -rf ./

重新拷贝
$ cp -r /home/teacher/oslab/* ./

上面两步骤是为了 如果你的 虚拟机文件系统被破坏 或者你的一些文件确实得不到恢复 就干脆重新来过吧 删除所有文件 重新解压linux源码包

我这里再来回顾一下 应用程序怎么实现系统调用的

首先需要应用程序发出系统调用的请求 调用库函数

然后我们的库函数 把系统调用号放到eax寄存器中 通过中断进入内核态
中断也是分很多种中断的 但是linux只提供了我们intx 80
是唯一我们应用程序与系统调用的接口中断

进入内核态之后 中断处理函数通过我们在eax存放的调用号
通过我们的系统调用表 找到我们的相对应的系统调用函数

完成系统调用之后 返回值存入eax 返回中断处理函数

中断处理函数返回我们的API接口 把eax返回给应用程序

我理了一遍觉得还是很有必要的 对过程更清晰了


应用程序如何调用系统调用


这里我就不详细说了
因为我觉得实验册讲的已经够好了
但是还是需要花时间去看那些代码 是怎么一回事

稍微需要注意的地方我后面会说
接口函数需要写成像这个样子

_syscall1(int, iam, const char*, name);
_syscall2(int, whoami,char*,name,unsigned int,size);


预处理(正式开始实验)


解压压缩包


这里我们就正式开始实验了 对于上面老师的讲解部分
我就不多重复了 我们就按照我们的思路一部分一部分推进

我们首先先解压 这里我习惯把那个一大长串的名字的压缩包给重命名
方便我们解压 例如我们改成a.gz

cd oslab
tar -zxvf a.gz

即得到了我们的压缩包

在这里插入图片描述


增加系统调用号


首先我们要想一下 我们是需要先写一个系统调用的函数
之后我们再在 虚拟机linux中写一个应用小程序来调用

那我们先来写系统调用的函数

系统调用函数 首先是有系统调用表的 我们先去把系统调用表中调用号给 增加了
表项中的调用号是在一个是虚拟机中的 /usr/include/unistd.h
另一个就是在源码树 oslab/linux-0.11/include/unistd.h

注意注意注意注意!这里两个都要改 都要改
我不知道为什么 我做实验的时候 只改了虚拟机中的
我的系统调用函数就是导入不进去 而且之后我再更改的时候也导入不进去
因为这里我浪费一下午的美好心情和一晚上的时间(还好rng msi夺冠了)

我们点进去 虚拟机的 依葫芦画瓢的给他加上

这里不会进入虚拟机的 需要重新去看我们的第一个 实验预先手册

安装虚拟机文件系统指令 sudo ./mount-hdc
卸载文件系统指令 sudo umount hdc
卸载主要不能 打开虚拟机会起冲突 文件系统会被破坏
这里还有一个地方需要注意

就是当你卸载或者打开虚拟机时 你当前的文件目录一定不要在
hdc目录中 不然的话 你再打开 你之前在的地方就会被破坏
切记切记
处理办法还是有的 就是需要你把你之前放在虚拟机中的iam.c whoami.c给放到其他没有被破坏的地方 重新敲一次代码 继续完成你的实验就ok了

在这里插入图片描述
在这里插入图片描述


再点源码树的

在这里插入图片描述

在这里插入图片描述


增加系统调用数


文件名 system_call.s
路径 oslab/linux-0.11/kernel/system_call.s

之前默认的系统调用数是 72 现在我们想有两个
sys_iam sys_whoami调用 就需要增加两个

在这里插入图片描述


函数调用表修改


文件名 sys.h
路径 include/linux/sys.h
依葫芦画瓢 增加说明最后两条
在这里插入图片描述
最后的指针表也需要加上这两个
在这里插入图片描述


修改MakeFile声明


这里就不详细解释了
因为我们需要把我们 最后想加入的系统调用文件和那些源文件一起编译链接到一起
那MakeFile是按照上面的指令和文件来执行的 我们就需要把MakeFile修改

怕自己打错的同学 去使用右边的剪切板功能 需要把你复制的东西
通过右边剪切板当过渡 保存后即可粘贴到环境

MakeFile 路径 linux-0.11/kernel/who.c

第一处原处

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

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

第二处原处

### 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

第二处修改后 第一排添加了 who.s who.o: who.c ../include/linux/kernel.h ../include/unistd.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

开始编写 系统调用代码


其实开始编写代码的时候
我们可以不用一来就把
真的我们需要的系统调用函数写进去
我们可以只用写两个这样子的函数进去试一下
但是我们在虚拟机中编写的函数就必须 变量的类型 个数要与这里保持一致
我们就可以试一下是否写入了系统中 然后看看是否调用成功嘛

但这里我就不重复这个过程了 因为之前修改 unistd.h没有两边都修改
导致我浪费了一晚上的时间 所以就不重复了

int sys_iam(int name)
{
    printk("lets go rng\n");//内核区 不再是printfreturn 0;
}int sys_whoami(int name)
{
    printk("lets go 3:2 rng:dk\n");return 0;
}

这里就写一下我的代码分析
具体的实现希望大家自己仔细看看实验册到最后
再看看调用 明白为什么这样写了再来

这部分代码可以不用急 可以把实验册慢慢看了来
或者把后面的实验步骤做了 再返回来写 也是ok的
可以做思路点拨吧

我再说一些地方 需要注意的 需要注意的是 这里是内核区了

#include <string.h> //memset 
#include <errno.h> //errno 实验册上面说要用//get_fs_byte put_fs_byte要用 这两个函数是穿越内核区和外核区数据访问关键
#include <asm/segment.h> char msg[24];//内核区保存我们的字符串的全局变量 int sys_iam(const char* name)
{
    int count = 0;errno = 0;//初始化char temp;memset(msg,'\0',sizeof(msg));//置0 while((temp = get_fs_byte(name+count)) != '\0'){
    if(count >= 23){
    errno = EINVAL;break;}//修改EINVAL状态msg[count++] = temp;}
//注意这里为什么return -(EINVAL) 呢
//因为linux 系统返回整数及0的时候意味着正常访问
//而返回负数表示错误访问 此时我们返回错误值是正的错误码加个符号
//他就会再进一步检测错误类型return (errno == EINVAL) ? -(EINVAL) : count;
}int sys_whoami(char* pos,unsigned int size)
{
    errno = 0;int len = strlen(msg),i;if(len > size){
    errno = EINVAL;return -(EINVAL);}for(i=0;i<len;++i)	put_fs_byte(msg[i],pos+i);return len;
}

最后我们把代码写到 linux-0.11\kernel中的一个新文件中
新文件名字叫 who.c即可

在这里插入图片描述


编译内核


做完以上的那些(可以暂时先不写完who.c 但要建立 可以尝试写几个弱智函数hhhh)

在这里插入图片描述
路径 /oslab/linux-0.11
指令make all
可以得到内核已经得到编译了 如果上面那些步骤都老老实实做了
并且注意事项该注意的也注意了 可以发现编译最后结果只有这几行
没有报错即编译成功

如果编译错误了也没有关系
自己多花点时间排一下错误
自己再重新输入命令make clean && make all
重新编译编译直到编译成功即可

在这里插入图片描述


编写应用程序


进入虚拟机文件系统


这个时候如果你没有卸载 hdc挂载程序的话
应该

oslab/hdc 是可以打开的 并且是这样的

在这里插入图片描述


我们刚开始在的位置 读入位置就是在 usr/root
所以干脆我们把应用程序就在 usr/root中建立就好了

此时进入并且先建立
在这里插入图片描述


编写iam.c 和 whoami.c


这里如果你之前的who.c里面的调用程序是printk的话
你这里的程序就可以写一个很简单的
之后会讲运行执行的 不急

我这里还是直接写我的最终代码吧
做思路启发 里面含有注释 记得复制粘贴进去的时候
不要加注释 注释是没办法通过编译的

// iam.c的
#define __LIBRARY__ //必须加
#include <unistd.h> //必须加 __NR_iam 在里面72调用号_syscall1(int,iam,const char*,name);//声明iam函数 call后面的数表示有几个参数//argc是读入参数个数
//argv指针数组是 argv[0]是文件名字符串 之后的1 2 3就是你依次输入
//依次输入的字符串了 详情解析可以去百度或者csdn 搜一下
//因为我们运行的时候是需要把字符串读进去的
// ./iam lizhijun 相等于把后面的字符串作为参数给进去了 argv[1]表示的就是那个字符串的地址
int main(int argc,char* argv[])
{
    iam(argv[1]);return 0;
}
#define __LIBRARY__ //必须有
#include <unistd.h> //必须有
#include <stdio.h> //printf
#include <string.h> //memset_syscall2(int,whoami,char*,pos,unsigned int,size);//声明whoami函数int main(int argc,char* argv[])
{
    char tempstr[24];//用户区字符地址memset(tempstr,'\0',sizeof(tempstr));//初始化whoami(tempstr,24);//调用函数printf("%s\n",tempstr);//输出 此时位于用户层return 0;
}


测试文件复制 关闭文件系统


测试文件在/home/teacher

我们只需要把testlab2.shtestlab2给复制进来即可
在这里插入图片描述


关闭文件系统请注意 我们需要把当前文件目录从hdc中移开
就是随便指向到其他的目录都行 不然会破坏文件系统的 记得每次卸载都需要切换目录

sudo umount hdc 后 我们要进入虚拟机linux进行检验了


运行linux虚拟机 测试数据


路径 ./run 记得在运行前卸载挂载hdc文件系统
之后我们就需要编译程序了

这里我之后测试了一下 确实有一个This is very very long
这个Phase没过
我找了很久的问题 还是没找到 可能是因为一个或字符空间设置小了
导致字符没有读入的问题
但是百分之99的Phase都是通过了
如果大家想去看百分百过的代码 可以跳转

MOOC哈工大操作系统实验2:添加系统调用 博主: Zhao tianhao

现在也是晚上8:30了 我从开写博客到现在
6点开写 还没吃饭 我就最后再介绍介绍怎么运行 和 测试用例怎么用

gcc -o iam iam.c -Wall 是编译 -Wall是显示错误信息
同理 gcc -o whoami whoami.c -Wall 也是一样的

编译之后你就可以自己试试 ./iam rng ./whoami
看看是否能输出 rng

测试的话就是先编译testlab2.cgcc -o testlab2 testlab2.c 在运行
还有个testlab2.sh也是个测试用例

下面是我测试的结果
但是我也打算就这样结束这个Lab了
可能有的时候 不完美也是一种完美
这个Lab让我耗费一天多的时间
我认为还是非常有意义的
继续Keep going on
希望这个博客可以帮助到你做这个Lab 我也要去吃饭了
Good luck

在这里插入图片描述

  相关解决方案