Lab3 【哈工大_操作系统】系统调用

本节将更新哈工大《操作系统》课程第三个 Lab 实验 系统调用。按照实验书要求,介绍了非常详细的实验操作流程,并提供了超级无敌详细的代码注释。文末附完整标准答案代码,包括系统调用实现 who.c 和测试函数 iam.cwhoami.c 以及超详细注释。

实验目的:

  • 建立对系统调用接口的深入认识;
  • 掌握系统调用的基本过程;
  • 能完成系统调用的全面控制;
  • 为后续实验做准备。

实验任务:

在 Linux 0.11 上添加两个系统调用,并编写两个简单的应用程序测试它们。
1、第一个系统调用是 iam(),完成的功能是将字符串参数 name 的内容拷贝到内核中保存下来。要求 name 的长度不能超过 23 个字符。返回值是拷贝的字符数。如果 name 的字符个数超过了 23,则返回 “-1”,并置 errno 为EINVAL。
2、第二个系统调用是 whoami(),它将内核中由 iam() 保存的名字拷贝到 name 指向的用户地址空间中,同时确保不会对 name 越界访存(name 的大小由 size 说明)。返回值是拷贝的字符数。如果 size 小于需要的空间,则返回“-1”,并置 errno 为 EINVAL。
3、运行添加过新系统调用的 Linux 0.11,在其环境下编写两个测试程序 iam.c 和 whoami.c。

实验工具准备:

文件名介绍
hit-操作系统实验指导书.pdf哈工大OS实验指导书
Linux内核完全注释(修正版v3.0).pdf赵博士对Linux v0.11 OS进行了详细全面的注释和说明
file1615.pdfBIOS 涉及的中断数据手册
hit-oslab-linux-20110823.tar.gzhit-oslab 实验环境
gcc-3.4-ubuntu.tar.gzLinux v0.11 所使用的编译器
Bochs 汇编级调试指令bochs 基本调试指令大全
最全ASCII码对照表0-255屏幕输出字符对照的 ASCII 码
x86_64 常用寄存器大全x86_64 常用寄存器大全

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

1、创建实验工程 oslab_Lab2

  • 解压 hit-oslab-linux-20110823.tar.gz ,并命名为 os_lab_Lab2 ,在此源码基础上我们进一步完成实验。
  • 编译及运行:
cd ~/my_space/OS_HIT/oslab_Lab2/linux-0.11/    // 工程文件夹
make all                                       // 编译
../run                                         // 启动 Bochs

2、创建 NR_whoami 和 NR_iam 两个宏

==注意:==此时不是修改内核目录里的,而是修改在 v0.11 的开发环境里的这个文件。(这步很多同学会犯错,导致后续在 linux-v0.11 环境下使用 gcc 编译测试函数时报错找不到这两个宏)

具体原因:可以参照《Linux内核完全注释(修正版v3.0).pdf》

在这里插入图片描述

  • 挂载 linux-v0.11 的磁盘,并在 include/unistd.h 中添加宏
cd oulab_Lab2
sudo ./mount-hdc
cd hdc/usr/include
gedit unistd.h
  • 通过文本编辑器,在 unistd.h 头文件中手动添加宏
#define __NR_iam        72
#define __NR_whoami        73

二、从“int 0x80”进入内核函数

用户程序调用进入内核的步骤

  1. init/main.c 内核初始化时,调用量 sched_init() 初始化函数
  2. kernel/sched.c 中定义sched_init: void sched_init(void) { // …… set_system_gate(0x80,&system_call); }
  3. include/asm/system.h 定义宏 set_system_gate :#define set_system_gate(n,addr) _set_gate(&idt[n],15,3,addr)
  4. include/asm/system.h 定义宏 _ set_gate :填写IDT(中断描述符表),将 system_call 函数地址写到 0x80 对应的中断描述符中,也就是在中断 0x80 发生后,自动调用函数 system_call。

1、修改 kernel/system_call.s 文件中的系统调用总数

// 由原来的 72 修改为 74
nr_system_calls = 74

2、在 include/linux/sys.h 文件中增加 whoami() 和 iam() 两个函数声明

extern int sys_whoami();
extern int sys_iam();
fn_ptr sys_call_table[] = { sys_setup,sys_exit, sys_fork, sys_read,..., sys_iam, sys_whoami }    // 表中添加的函数顺序必须和 _NR_xxxxxx 的值对应

三、实现 sys_iam() 和 sys_whoami()

我需要编写系统调用函数来实现这两个功能。值得注意的是,每个系统调用都有一个 sys_xxxxxx() 与之对应,它们都是我们学习和模仿的好对象。

  • 此处的要点是如何实现:在用户态和核心态之间传递数据。其难点是:指针参数传递的是应用程序所在地址空间的逻辑地址,在内核中如果直接访问这个地址,访问到的是内核空间中的数据,不会是用户空间的。

open 为例:

lib/open.c:
	- 系统调用是用 eax、ebx、ecx、edx 寄存器来传递参数的
	- 获取用户地址空间(用户数据段)中的数据依靠的就是段寄存器 fs,下面该转到 sys_open 执行了
fs/open.c:
	- 将参数传给了 open_namei()
	- open_namei( ) 用 ==get_fs_byte()== 获得一个字节的用户空间中的数据

所以说,在whoami() 中实现用户态和和心态之间的数据传递,即通过:put_fs_xxx() 和 get_fs_xxx() 都是用户空间和内核空间之间的桥梁

  • oslab_Lab2/linux-0.11/kernel/ 文件夹下创建系统调用实现 who.c。具体代码如下:(已有详细注释,此处将不再赘述)
// sys_iam 和 sys_whoami 系统调用函数

#include <string.h>
#include <errno.h>
#include <asm/segment.h>

// 内核中存放name,23字符 + '\0' = 24
char msg[24];

// 将字符串参数 name 拷贝到内核中保存下来
// return:拷贝的字符数。如果name的字符个数超过了23,则返回“­-1”,并置errno为EINVAL。
int sys_iam(const char* name) 
{
    int i;
    char tmp[30];   // 临时存储,操作失败不影响 msg
    
    // 从用户态内存获取数据,存入 tmp
    for(i = 0; i < 30; i++) {
        tmp[i] = get_fs_byte(name+i);
        if(tmp[i] == '\0')
            break;
    }
    // printk(tmp);

    i = 0;
    // 计算 name 的长度
    while(tmp[i] != '\0' && i < 30) {
        i++;
    }
    int len = i;
    // 长度大于 23 返回报错
    if(len > 23) {
        return -(EINVAL);
    }
    strcpy(msg, tmp);

    return i;
}

// 将由 iam() 保存的名字拷贝到 name 指向的用户地址空间,size 指定 name 的大小
// return:如果size小于需要的空间,则返回“­-1”,并置errno为 EINVAL
int sys_whoami(char* name, unsigned int size)
{     
    int len = 0;
    // 计算长度
    for(; msg[len] != '\0'; len++);
    // 长度大于则返回
    if(len > size) {
        return -(EINVAL);
    }
    // 从内核态获取数据,存入 name
    int i = 0;
    for(; i < size; i++) {
        put_fs_byte(msg[i], name+i);
        if(msg[i] == '\0') 
            break;
    }
    return i;
}

值得注意的是,在编写代码过程中,我们可以通过 printk() 函数在屏幕上输出数据来进行调试,适当地向屏幕输出一些程序运行状态的信息,也是一种很高效、便捷的调试方法。

四、修改编译规则 Makefile

我们需要为新建的 who.c 文件添加编译规则来创建编译对象以及添加响应的依赖,后续即可使用 make all 一键编译。

  • 修改 oslab_Lab2/linux-0.11/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 who.o

### Dependencies:
who.s who.o:  who.c ../include/linux/kernel.h ../include/unistd.h

五、编写测试函数

编写两个测试函数,来测试我们编写的系统调用函数 sys_iam() 和 sys_whoami。

  1. iam.c:将字符串参数 name 的内容拷贝到内核中保存下来
  2. whoami.c:将内核中由 iam() 保存的名字拷贝到 name 指向的用户地址空间中并显示。
  • 值得说明的是,我们编写后的两个测试函数需要在 linux-v0.11 环境下进行编译。由于 linux-v0.11 作为一个很小的操作系统,只有编译工具 vi ,使用起来非常不方便。因此,我们可以直接在 ubuntu 环境下进行编写,再通过挂载 hdc 磁盘来将这两个文件拷贝到 linux-v0.11 系统下,然后编译、运行

1、在 oslab_Lab2/linux-0.11/kernel 文件夹下创建两个测试函数

  • iam.c
#include <errno.h>
#define __LIBRARY__
#include <unistd.h>
#include <stdio.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>

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

int main(int argc, char* agrv[])
{
    char s[30];
    whoami(s, 30);
    printf("%s\n", s);
    
    return 0;
}

2、将两个测试函数移动到 linux-v0.11 系统下

通过挂载 hdc 磁盘,将编写的两个测试函数移动到 linux-v0.11 系统下,以便下一步进行编译。由于我们每在 ubuntu 系统下修改一次测试函数都要将其移动到 linux-v0.11 系统下进行编译,为了方便,我们可以编写一个 bash 脚本,存放如下命令。

cd ~/my_space/OS_HIT/oslab_Lab2
touch cp.sh                                     // 创建脚本

脚本内容如下:

sudo ./mount-hdc                                // 挂载磁盘
sudo cp ./linux-0.11/kernel/iam.c ./hdc/iam.c          // 将 iam.c 移动到 hdc 磁盘
sudo cp ./linux-0.11/kernel/whoami.c ./hdc/whoami.c    // 将 whoami.c 移动到 hdc 磁盘
sudo umount hdc                                 // 卸载
./run                                           // 启动 linux-v0.11

3、编译与运行

在编译之前,尤其注意,上述创建的 iam.c 和 whoami.c 两个文件中不能含有任何 // 之类的注释,否则在使用 linux-v0.11 系统进行编译时会报错。

  • 在 linux-v0.11 系统下,我们运行如下命令回到根目录,可以看到移动过来的 iam.cwhoami.c 文件。
cd ../..
ls

  • 编译,运行如下指令,可以生成 iamwhoami 两个可执行文件。
gcc -o iam iam.c -Wall           // 编译 iam.c 文件
gcc -o whoami whoami.c -Wall     // 编译 whoami.c 文件
// -Wall 是 GCC 的一个选项,用来开启所有警告
  • 运行,分别运行两个文件,得到如下所示结果
./iam Joker      // 将 Joker 存入内核
./whoami         // 从内核中取出 ./iam 存入的字符并显示

在这里插入图片描述

至此,Lab3 实验介绍完毕。


也许有人觉得比较实验步骤比较多比较复杂,特此总结一下每个步骤:

  1. hdc/usr/include/unistd.h 中添加系统调用号 __NR_iam__NR_whoami
  2. kernel/system_call.s 修改系统调用总数 nr_system_calls = 74
  3. include/linux/sys.h 添加函数声明 extern int sys_whoami();extern int sys_iam();
  4. 创建两个函数的实现:/kernel/who.c
  5. kernel/Makefile 中添加编译规则
  6. /kernel 中创建测试函数 iam.c 和 whoami.c
  7. 编写 bash 脚本,将测试函数移到 linux-v0.11 下
  8. 在 Linux-v0.11 下编译 iam.c 和 whoami.c
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_小猪沉塘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值