实验要求与实验指导见实验楼。
实验环境为本地实验环境。
首先在 kernel/
下创建 who.c
,实现两个系统调用的处理函数:
#include
#include
#include
#include
char myname[24];
int mylen;
int sys_iam(const char * name)
{
int len = 0, i;
char str[30] = "";
for(i=0;;i++) {
str[i] = get_fs_byte(&name[i]);
if(str[i]=='\0' || i>23)
break;
len++;
}
if(len <= 23) {
strcpy(myname, str);
mylen = len;
return mylen;
}
return -EINVAL;
}
int sys_whoami(char * name, unsigned int size)
{
if(mylen >= size) {
return -EINVAL;
}
int i;
for(i=0; i
name指针的字符串是用户空间的数据,在内核态无法直接访问,只能通过
include/asm/segment.h
中的get_fs_byte()
、get_fs_word()
等函数进行访问。同样地,内核要将数据传输到用户空间需要使用put_fs_byte()
等函数。
include/unistd.h
头文件中定义了系统调用嵌入式汇编宏函数,如果处理函数的返回值小于0则把 errno 置为返回值的相反数并返回 -1。因此在上面的处理函数中设置errno = EINVAL
是无法通过测试的,只能把处理函数的返回值设为 -EINVAL,这样系统调用的返回值就是 EINVAL 了。
接着修改 kernel/Makefile
,从而能够将新增的 who.c
编译进内核:
# 在27行OBJS末尾添加 who.o
OBJS = ... \
signal.o mktime.o who.o
# 在末尾添加:
who.s who.o: who.c ../inlude/asm/segment.h ../include/linux/kernel.h ../include/errno.h \
../include/string.h
然后在 include/unistd.h
中添加新系统调用的功能号和函数原型定义:
//在原程序第132行添加:
#define __NR_iam 72
#define __NR_whoami 73
//在源程序第252行添加:
int iam(const char * name);
int whoami(char * name, unsigned int size);
接着在 include/linux/sys.h
中添加外部函数声明和并修改函数指针表:
//在原程序第73行添加:
extern int iam();
extern int whoami();
//在 sys_call_table[] 表尾添加:
fn_ptr sys_call_table[] = { sys_setup, ..., sys_setregid, sys_iam, sys_whoami };
最后,修改 kernel/system_call.s
程序中的第 61 行的数值:
nr_system_calls = 74
至此新的系统调用就添加完了,使用 make all
命令编译,就可以运行了。
分别创建 iam.c
和 whoami.c
程序,代码如下:
//iam.c
#define __LIBRARY__
#include
#include
_syscall1(int, iam, const char *, name);
int main(int argc, char* argv[])
{
int n;
if(argc != 2) {
printf("Please enter your name!\n");
return -1;
}
n = iam(argv[1]);
if(n == -1) {
printf("Your name is too long!\n");
}
return 0;
}
//whoami.c
#define __LIBRARY__
#include
#include
_syscall2(int, whoami, char *, name, unsigned int, size);
int main()
{
char name[24];
int n = whoami(name, 24);
if(n == -1) {
printf("Your name is too long!\n");
}
else
printf("%s\n", name);
return 0;
}
然后让两个测试程序在 Linux-0.11 中执行,即在 Ubuntu 环境下用下面的命令挂载访问 Linux-0.11 下的 /usr/local
目录,然后将两个测试程序放到这个目录:
cd ~/oslab
sudo ./mount-hdc
cp ~/iam.c hdc/usr/local
cp ~/whoami.c hdc/usr/local
同时还要在 Linux-0.11 下的 /usr/include/unistd.h
头文件中添加下面两个宏,否则测试程序无法编译:
#define __NR_iam 72
#define __NR_whoami 73
用 ./run
命令启动 Linux-0.11 ,进入 usr/local
,编译上述两个测试程序并运行,结果如下:
将实验楼中的 testlab2.c
和 testlab2.sh
两个评分程序复制到 Linux-0.11 中的 usr/local
目录下执行,结果如下:
测试通过。
用户通常都是通过函数库中的接口函数来使用系统调用的。这些接口函数会把系统调用的编号存入 eax 寄存器,把函数参数存入 ebx、ecx 等寄存器,触发 int 0x80
中断来进入系统调用处理函数。其中系统调用的编号由 include/unistd.h
中的宏定义 #define __NR_##name XX
获取。系统调用处理函数返回后,如果返回值小于0,则设置出错号 errno 为返回值的相反数,接口函数返回 -1;否则接口函数也返回该值。
这个过程在 include/unistd.h
中实现:第 133-185 行中宏定义的 syscall0 - syscall3 分别就是0-3个参数的系统调用接口宏函数。因此上述代码中的 _syscall1(int,iam,const char*,name)
就实现了 int iam(const char* name)
接口函数。
int 0x80
中断的处理过程在 kernel/system_call.s
中实现。其中 nr_system_calls
定义了系统调用总数,用在后面判断 eax 寄存器的功能号是否在系统调用编号的合法范围内;然后保存一些寄存器到栈上,将fs指向用户数据段、ds和es指向内核数据段,接着通过地址跳转表调用相应系统调用的C处理函数(少数处理函数由汇编语言实现),处理函数返回后,程序就把返回值压入栈中保存起来,然后做一些进程调度工作,判断进程类型,最后弹出堆栈内容恢复寄存器的值,退出系统调用中断。如下图流程所示:
system_call.s
中调用系统调用C处理函数的代码为 call _sys_call_table(,%eax,4)
,即调用地址为 [_sys_call_table + %eax*4]
,其中数组 _sys_call_table[]
在 include/linux/sys.h
中定义,是系统调用处理函数指针表,通过它和 %eax 寄存器上的功能号就能跳转到指定的系统调用实现函数执行。
系统调用实现函数,由 kernel/
目录中的 system_call.s
、sys.c
、signal.c
等文件实现,有的由汇编语言实现,有的由C语言实现,函数名的形式为 sys_XXX
。它们的代码便是实现系统调用所需的功能。
在实验报告中回答如下问题:
从 Linux 0.11 现在的机制看,它的系统调用最多能传递几个参数?你能想出办法来扩大这个限制吗?
直接能传递的参数至多有 3 个。
在 Linux-0.11 中,程序使用 ebx、ecx、edx 这三个通用寄存器保存参数,可以直接向系统调用服务过程传递至多三个参数 (不包括放在 eax 寄存器中的系统调用号)。
如果使用指向用户数据空间的指针,将指针信息通过寄存器传递给系统调用服务,然后系统调用就可以通过该指针访问用户数据空间中预置的更多数据,就可以达到传递更多参数的目的。
用文字简要描述向 Linux 0.11 添加一个系统调用 foo() 的步骤。
首先实现新系统调用的处理函数
sys_foo()
,这个函数可以放在kernel/sys.c
中(sys.c
程序中含有很多系统调用功能的实现函数),也可以在kernel/
下新建一个 C 语言文件(需要修改 Makefile 以将它编译进内核);然后在
include/unistd.h
中添加新系统调用的功能号和函数原型定义;接着在
include/linux/sys.h
头文件中加入外部函数声明并在函数指针表sys_call_table
末端插入新系统调用处理函数的名称;再然后,将
kernel/system_call.s
程序中的第 61 行nr_system_calls = 72
的值加一,该值表示内核中的系统调用总数;最后还可以参照
lib/
目录下库函数的实现方法在 libc 库中增加新的系统调用库函数。
至此,一个新的系统调用就添加完了。