在 linux0.11 中添加自定义系统调用
linux 调用 system call 的过程
-
用户态发起系统调用 :
在用户态程序中,调用系统调用的接口,通常是通过一个库函数(如 glibc)封装。这个库函数会触发
int 0x80
软中断。int 0x80
是 x86 平台用于进入内核态的中断指令。 -
进入内核态 :
软中断会使 CPU 切换到内核态,并根据中断向量号(0x80)在中断向量表中查找对应的中断处理程序。对于
int 0x80
,中断处理程序的入口位于内核的system_call
函数中。 -
保存寄存器状态 :
进入
system_call
函数时,CPU 会自动保存一部分寄存器状态(如CS
、EIP
、EFLAGS
等),而内核会通过保存当前任务的寄存器状态(包括用户态的pt_regs
结构),来保存调用前的 CPU 寄存器内容。 -
处理系统调用号和参数 :
system_call
函数会从eax
寄存器中读取系统调用号(用户态程序通过int 0x80
前将系统调用号放入eax
),并根据该系统调用号在系统调用表(sys_call_table
)中找到相应的内核态系统调用函数。用户态传递的参数通常存放在其他寄存器中,如ebx
,ecx
,edx
等。 -
执行系统调用 :
找到对应的系统调用函数后,内核会使用这些寄存器中的参数执行系统调用。系统调用完成后,返回值通常会放入
eax
寄存器中。 -
返回用户态 :
系统调用完成后,内核会恢复用户态的寄存器状态(使用
pt_regs
),并使用iret
指令返回用户态。此时,用户态程序可以从eax
中获取系统调用的返回值。
添加自定义的系统调用
修改系统调用总数:
bash
cd ~/oslab/linux-0.11/kernel
vim system_call.s
向系统调用表中添加自定义的函数:
bash
cd ~/oslab/linux-0.11/include/linux
vim sys.h
我添加了两个自定义的函数:
-
printIntVal(): 打印一个整数值。
-
strToNum(): 将一个数字字符串转换为整形。
添加自定义系统调用的声明和系统调用号定义
bash
cd ~/oslab/linux-0.11/include
vim unistd.h
添加自定义系统调用的实现
bash
cd ~/oslab/linux-0.11/kernel
vim selfdefine.c
c
#include <asm/segment.h> // 包含与内核段寄存器相关的函数,如 get_fs_byte 和 put_fs_long
/**
* 自定义系统调用 printIntVal,用于打印一个整数值。
*
* @param a - 要打印的整数值
* @return 无返回值
*/
int printIntVal(int a)
{
// 使用 printk 输出内核信息,打印传入的整数值
printk("in system print int val: %d\n", a);
}
/**
* 自定义系统调用 strToNum,将字符串转换为整数,并存储结果到内存中。
*
* @param str - 字符串的指针,从用户态传入的字符串。
* @param str_len - 字符串的长度。
* @param ret - 保存转换后结果的指针,返回转换的 long 类型整数。
* @return 无返回值
*/
int strToNum(char *str, int str_len, long *ret)
{
long result = 0; // 存储转换后的结果
char tmp; // 用于存储每次从用户空间读取的字符
int i; // 循环计数器
// 遍历传入的字符串,将其转换为整数
for (i = 0; i < str_len; i++)
{
// 使用 get_fs_byte 从用户空间读取字符串中的每个字符
tmp = get_fs_byte(str + i);
// 将字符转换为对应的数字,累加到 result 中
// '0' 是字符的 ASCII 值,需要减去以获得实际的数字值
result = result * 10 + tmp - '0';
}
// 使用 put_fs_long 将结果写回到用户空间中的 ret 指针指向的地址
put_fs_long(result, ret);
}
写完自定义函数的实现之后记得将这个文件保存哦!
修改 makefile 文件
bash
cd ~/oslab/linux-0.11/kernel
vim Makefile
我们需要修改两个地方:
- 在定义 OBJS 这里添加 selfdefine.o
-
添加依赖
编译 linux0.11 内核
bash
cd ~/oslab/linux-0.11
make clean
make
我不知道怎么通过修改 linux-0.11 的源代码生成用户态的系统调用接口,我就在 linux-0.11 编译号之后的系统中手动添加了!
添加用户态接口
bash
cd ~/oslab
我们运行 mount-hdc 这个可执行文件,可以让我们在 ubantu 系统上编辑 linux0.11 系统中的文件。
bash
./mount-hdc
现在我们需要提供用户态的系统调用接口:
bash
vim ./hdc/usr/include/selfdefine.h
c
#ifndef _LINUX_SELFDEFINE_H
#define _LINUX_SELFDEFINE_H
// 定义系统调用号
#define __NR_printIntVal 72
#define __NR_strToNum 73
// 提供用户调用系统调用的接口
// 系统调用:printval
static inline int printIntVal(int val) {
int res;
asm volatile(
"int $0x80" // 通过中断调用系统调用
: "=a"(res) // 返回值保存在 eax 寄存器
: "0"(__NR_printIntVal), "b"(val) // 系统调用号在 eax,参数在 ebx
: "memory");
return res;
}
// 系统调用:str2num
static inline int strToNum(char *str, int str_len, long *ret) {
int res;
asm volatile(
"int $0x80" // 通过中断调用系统调用
: "=a"(res) // 返回值保存在 eax 寄存器
: "0"(__NR_strToNum), "b"(str), "c"(str_len), "d"(ret) // 参数传递
: "memory");
return res;
}
#endif /* _LINUX_SELFDEFINE_H */
这里有个问题就是用户态的接口为啥要这么写呢?其实就是模仿着 linux-0.11 的源代码来写的:
上面是 open 系统调用的用户态接口,是不是差不多嘛!
编辑完成之后记得要保存哦!
编写测试程序
现在你所在的目录应该还是:
bash
~/oslab
我们来编写测试程序:
bash
vim ./hdc/usr/root/test.c
c
#include <stdio.h>
#include <selfdefine.h>
int main() {
int val = 42;
char str[] = "12345";
long result; // c 语言的早期标准,变量必须定义在函数的最前面
printIntVal(val);
strToNum(str, 5, &result);
printf("Converted number: %ld\n", result);
return 0;
}
编写好了之后保存!然后我们需要取消 hdc 目录的挂载!
umount hdc
运行测试
我们运行 run 程序
bash
./run
编译 test.c
bash
gcc test.c
运行 a.out
bash
./a.out
可以看到成功了!