💭 写在前面:本文讲解的内容不属于 Pintos 的 Project 项目,而是关于 userprog 如何添加系统调用的,学习如何额外实现一些功能到系统调用中以供用户使用。因为涉及到 src/example 下的Makefile 的修改、lib 目录下 syscall-nr 系统调用号的增添以及定义调用宏等操作,所以需要对 Pintos 项目有一定的了解。本文旨在帮助大家 DIY 自己想实现的系统调用,增加到自己的 Pintos 项目中。为了方便讲解,我们添加两个非常简单的系统调用功能,调用的功能其实并不重要,重要的是关注添加系统调用的操作。我们会将下面两个我们自己实现的函数作为系统调用:
int fibonacci(int n) // 返回斐波那契数列的第n项
int max_of_four_int(int a, int b, int c, int d) // 返回 a b c d 中的最大值
【Pintos】实现自定义 UserProg 系统调用 | 添加 syscall-nr 系统调用号 | 编写新的参数调用宏
🔍 前置文章:
【OS Pintos】Pintos 环境准备
【OS Pintos】用户程序是如何工作的 | Pintos 运行原理 | 虚拟内存 | 页函数 | 系统调用
【OS Pintos】Project1 项目要求说明 | 进程中止信息 | 参数传递 | 用户内存访问
Step1:进入 examples 目录
首先我们要进入 example 目录。examples 目录是在 Pintos 目录下的 src 子目录下的。
$ cd pintos/src/examples
Step2:修改 Makefile 文件
添加系统调用,自然需要修改 Makefile 文件,我们可以照着其他用户程序的编写方式去修改:
$ vim Makefile # 用vim编辑器打开Makefile
(打开后如图所示)
我们来分析一下这个 Makefile 文件,我们先看前面的注释……
emm,这似乎是 Pintos 作者留下的注释哈:
大致意思就是:添加新的系统调用接口,需要先将它名字添加到 列表中,然后按 name_SRC 的格式添加到资源文件中(name 指的是系统调用的名字)。
好,我们乖乖听话,按大哥的要求做!
Vim 下输入 进入插入模式后,在 列表中添加一个文件名,比如取名 additional :
PROGS = cat cmp cp echo halt hex-dump ls mcat mcp mkdir pwd rm shell \
bubsort lineup matmult recursor additional
按照要求在 列表中添加完名字后,还需要将其添加到资源文件中,也在这个 Makefile 里,我们直接往下拉照葫芦画瓢,按格式添加即可:
additional_SEC = additional.c
编辑完成后 !wq 退出 Vim 即可。
Step3: 为新的系统调用添加系统调用编号
记录系统调用编号的文件是 syscall-nr.h,它在 lib 目录下:
$ cd pintos/src/lib # 进入user目录 (在cse目录下输入)
我们用 Vim 打开它,小心点输别输错了,不然自动创建新的文件就麻烦了:
$ vim syscall-nr.h
我们按照要求,在 enum 里添加两个函数的系统调用编号:
SYS_FIBONACCI,
SYS_MAX_OF_FOUR_INT,
Step4:编写新的系统调用的 API 原型
我们需要在 syscall.h 文件夹中修改,它在进入 lib/user 目录下,走起:
$ cd pintos/src/lib/user # 进入user目录 (在cse目录下输入)
我们 Vim 进入 syscall.h:
$ vim syscall.h
我们给它加上新的接口函数的声明:
int fibonacci(int n);
int max_of_four_int(int a, int b, int c, int d);
Step5:为 max_of_four_int 函数定义参数调用宏 syscall4
Pintos 的 syscall 有 4 个参数调用宏,分别是 syscall0, syscall1, syscall2, sycall3(数字几就是几个参数)。
而 max_of_four_int 这个函数有 4 个参数要传,加上调用编号的话就是一共要传 5 个参数:
int max_of_four_int(int a, int b, int c, int d);
因为 Pintos 的自带的最多只能传4个,所以我们就不得不实现一个 syscall4 函数。举这个例子函数正式为教会大家学会如何处理 —— 自己实现的函数需要传的参数大于 syscall3 的情况。
这就需要我们手写添加一个 syscall4 的宏。在 syscall.c 里添加 ,文件位置在 src/lib/user 下:
$ cd pintos/src/lib/user # 进入user目录
老样子,用 Vim 打开进行编辑:
$ vim syscall.c
这个 syscall.c 的源码内容比较长,我们从前往后慢慢看:
这些就是 Pintos 写好的系统调用宏,分别是无参,一个参数,两个参数,三个参数的系统调用宏。而我们新增的 max_of_four_int 函数需要传递 a,b,c,d 四个参数,而 Pintos 并没有实现,所以这需要我们自己去实现!
我们仍然是用 照猫画虎大法, 在实现之前我们仔细观察下 syscall3 宏,有助于我们理解,自己实现 syscall4 也能更轻松。
/* Invokes syscall NUMBER, passing arguments ARG0, ARG1, and
ARG2, and returns the return value as an `int'. */
#define syscall3(NUMBER, ARG0, ARG1, ARG2) \
({ \
int retval; \
asm volatile \
("pushl %[arg2]; pushl %[arg1]; pushl %[arg0]; " \
"pushl %[number]; int $0x30; addl $16, %%esp" \
: "=a" (retval) \
: [number] "i" (NUMBER), \
[arg0] "r" (ARG0), \
[arg1] "r" (ARG1), \
[arg2] "r" (ARG2) \
: "memory"); \
retval; \
})
💡 解读:不要被这一大坨宏吓到,后面的 \ 是代码换行,这是为了代码可读性而加的!
我们可以看到函数参数有 NUMBER,AGE0,AGE1,AGE2。NUMBER 接收的就是我们的系统调用号,AGE 就是 argument 的简写,就是要接收的参数。
这里是 syscall3,接收三个参数所以这里自然有三个 AGE,我们下面要实现 4 个参数时这里就需要再加一个 "AGE3"。随后 asm volatile 进行 pushl 操作,
注意!参数是 "从右往左" 压入的,即先压 AGE2,再压 AGE1……最后再压 NUMBER。
而后面的 addl $16 即需要的空间,每个 int 型参数占 4 个字节,这里加上系统调用号 NUMBER 一共要 pushl 4 个参数,所以需要索要 16 个字节:
通过这里我们就能知道,我们在实现 syscall4 参数调用宏时,会有 5 个参数,那么到时候这里就需要写 addl $16 。好了,开始照猫画虎写 syscall4 宏:
#define syscall4(NUMBER, ARG0, ARG1, ARG2, ARG3) \
({ \
int retval; \
asm volatile \
("pushl %[arg3]; pushl %[arg2]; pushl %[arg1]; pushl %[arg0]; " \
"pushl %[number]; int $0x30; addl $20, %%esp" \
: "=a" (retval) \
: [number] "i" (NUMBER), \
[arg0] "r" (ARG0), \
[arg1] "r" (ARG1), \
[arg2] "r" (ARG2), \
[arg3] "r" (ARG3) \
: "memory"); \
retval; \
})
然后不要急着退出,我们 Step5 还要在这里进行操作。
Step5:编写函数的系统调用 API
我们刚才已经为 max_of_four_int 定义了 syscall4 了,我们还要在 syscall.c 里实现这两个新函数的系统调用接口。我们刚才在 syscall.h 里已经给这两个函数写过函数声明了:
int fibonacci(int n);
int max_of_four_int(int a, int b, int c, int d);
现在也准备好了 syscall4,我们自然要在 syscall.c 里实现一下它们的系统调用接口。
Fibonacci 函数只有一个参数(没算调用号),使用 Pintos 自带的 syscall1 即可。max_of_four_int 有四个参数(没算调用号),就用我们刚才实现的 syscall4 就行。
这也是为什么我们要 Step by Step 地讲。先定义好系统调用号,然后定义 syscall.h 的系统调用接口,再实现 syscall4,最后再实现 syscall.c 的系统调用接口。按这样的顺序去做不会乱,也不至于写着写着怎么参数突然冒出一个系统调用号,搞得人一脸懵。
啊哈哈哈哈哈哈,搞定!
Step6:实现这两个函数的功能
在 userprog 下也是有个 syscall.c 文件的,如果你做过 Pintos Project1 你应该会对它很熟悉。
我们需要在 userprog/syscall.c 这写上系统调用的功能实现:
$ cd pintos/src/userprog
至于 Fibonacci 和 max_of_four_int 函数的实现,和本篇博客主题无关(不是C基础教学),代码我直接给出,这不重要仅供参考。
💬 求第 个斐波那契数列(非递归法)
int fibonacci(int n)
{
/* Non-Rec method */
int a = 1;
int b = 1;
int c = 1;
while (n > 2) {
c = a + b;
a = b;
b = c;
n--;
}
return c;
}
💬 求四数最大值:
int max_of_four_int(int a, int b, int c, int d)
{
int max = a; /* Suppose the first number is bigest */
if (max < b) max = b;
if (max < c) max = c;
if (max < d) max = d;
return max;
}
实现完后保存退出即可,至此我们的任务就大功告成了。
💭 测试: pintos/src/userprog 下输入:
pintos --filesys-size=2 -p ../examples/additional -a additional -- -f -q run 'additional 10 20 62 40'
🚩 效果演示:
📌 [ 笔者 ] 王亦优
📃 [ 更新 ] 2022.12.17
❌ [ 勘误 ] /* 暂无 */
📜 [ 声明 ] 由于作者水平有限,本文有错误和不准确之处在所难免,
本人也很想知道这些错误,恳望读者批评指正!
📜 参考资料:Stanford OS Pintos 官方文档 |