这次内容是在Linux0.11上添加两个系统调用,并编写两个简单的应用程序测试它们
- iam
第一个系统调用是 iam(),其原型为:
int iam(const char * name);
完成的功能是将字符串参数 name 的内容拷贝到内核中保存下来。要求 name 的长度不能超过 23 个字符。返回值是拷贝的字符数。如果 name 的字符个数超过了 23,则返回 “-1”,并置 errno 为 EINVAL。
在 kernal/who.c 中实现此系统调用。
- whoami
第二个系统调用是 whoami(),其原型为:
int whoami(char* name, unsigned int size);
它将内核中由 iam() 保存的名字拷贝到 name 指向的用户地址空间中,同时确保不会对 name 越界访存(name 的大小由 size 说明)。返回值是拷贝的字符数。如果 size 小于需要的空间,则返回“-1”,并置 errno 为 EINVAL。
也是在 kernal/who.c 中实现。
- 测试程序
运行添加过新系统调用的 Linux 0.11,在其环境下编写两个测试程序 iam.c 和 whoami.c。
调用系统调用,是调用系统库中为该系统调用编写的一个接口函数,叫 API(Application Programming Interface)。API 并不能完成系统调用的真正功能,它要做的是去调用真正的系统调用,过程是:
把系统调用的编号存入 EAX;
把函数参数存入其它通用寄存器;
触发 0x80 号中断(int 0x80)。
查看一下/lib/close.c,研究一下close() 的API:
#define __LIBRARY__
#include <unistd.h>
_syscall1(int, close, int, fd)
其中_syscall1是一个宏定义,在include/unistd.h中定义
#define _syscall1(type,name,atype,a) \
type name(atype a) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name),"b" ((long)(a))); \
if (__res >= 0) \
return (type) __res; \
errno = -__res; \
return -1; \
}
这里将__NR_close的调用号存入eax中,fd存入ebx中,然后执行 int $0x80中断调用。
int 0x80触发以后,就是内核的中断处理了。
调用IDT(中断描述表)中0x80对应位置的system_call。
__NR_close在include/unistd.h中定义:
#define __NR_close 6
/*
所以添加系统调用时需要修改include/unistd.h文件,
使其包含__NR_whoami和__NR_iam。
*/
_NR##name用于确定调用sys_call_table表中的哪一个中断处理函数,sys_call_table表定义在include/linux/sys.h
extern int sys_setup();
extern int sys_exit();
extern int sys_fork();
extern int sys_read();
extern int sys_write();
extern int sys_open();
extern int sys_close();
extern int sys_waitpid();
fn_ptr sys_call_table[] = { sys_setup, sys_exit, sys_fork, sys_read,
sys_write, sys_open, sys_close, sys_waitpid....}
而使用int 0x80来调用系统调用,实在操作系统初始化时,在
sched_init();
中完成了对系统调用的初始化。
sched_init()
{
set_system_gate(0x80,&system_call);
}
#define set_system_gate(n,addr) \
_set_gate(&idt[n],15,3,addr)
#define _set_gate(gate_addr,type,dpl,addr) \
__asm__ ("movw %%dx,%%ax\n\t" \
"movw %0,%%dx\n\t" \
"movl %%eax,%1\n\t" \
"movl %%edx,%2" \
: \
: "i" ((short) (0x8000+(dpl<<13)+(type<<8))), \
"o" (*((char *) (gate_addr))), \
"o" (*(4+(char *) (gate_addr))), \
"d" ((char *) (addr)),"a" (0x00080000))
movw %%dx,%%ax 将ax置为0008:addr
movw %0,%%dx 将dx置为0x8000+(dpl<<13)+(type<<8),
分别赋给idt中0x80偏移的低4字节,高4字节。
所以当执行int 0x80中断时,就调用system_call
在本实验中,我们在/oslab/linux-0.11/include/unistd.h 中添加两个调用号
#define __NR_whoami 72
#define __NR_iam 73
并且将kernel/system_call.s中
nr_system_calls = 72
改为
nr_system_calls = 74
在include/linux/sys.h中添加系统调用名,并且更新系统调用表
extern int sys_whoami();
extern int sys_iam();
fn_ptr sys_call_table[] = { sys_setup, sys_exit, sys_fork, sys_read,
sys_write, sys_open, sys_close, sys_waitpid....sys_whoami, sys_iam}
接下来为新添加的系统调用编写实现代码
在linux-0.11/kernel目录下,创建who.c
#include <asm/segment.h>
#include <errno.h>
#include <string.h>
char _myname[24];
int sys_iam(const char *name)
{
char str[25];
int i = 0;
do
{
// get char from user input
str[i] = get_fs_byte(name + i);
} while (i <= 25 && str[i++] != '\0');
if (i > 24)
{
errno = EINVAL;
i = -1;
}
else
{
// copy from user mode to kernel mode
strcpy(_myname, str);
}
return i;
}
int sys_whoami(char *name, unsigned int size)
{
int length = strlen(_myname);
printk("%s\n", _myname);
if (size < length)
{
errno = EINVAL;
length = -1;
}
else
{
int i = 0;
for (i = 0; i < length; i++)
{
// copy from kernel mode to user mode
put_fs_byte(_myname[i], name + i);
}
}
return length;
}
之后修改kernel/Makefile文件,用以链接kernel/who.c与其它代码
vim +27 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
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
再完成系统的编译,为linux-0.11编写测试程序iam.c, whoami.c
/* iam.c */
#define __LIBRARY__
#include <unistd.h>
#include <errno.h>
#include <asm/segment.h>
#include <linux/kernel.h>
_syscall1(int, iam, const char*, name);
int main(int argc, char *argv[])
{
/*调用系统调用iam()*/
iam(argv[1]);
return 0;
}
/* whoami.c */
#define __LIBRARY__
#include <unistd.h>
#include <errno.h>
#include <asm/segment.h>
#include <linux/kernel.h>
#include <stdio.h>
_syscall2(int, whoami,char *,name,unsigned int,size);
int main(int argc, char *argv[])
{
char username[64] = {0};
/*调用系统调用whoami()*/
whoami(username, 24);
printf("%s\n", username);
return 0;
}
/*
在应用程序中,要有:
*/
/* 有它,_syscall1 等才有效。详见unistd.h */
#define __LIBRARY__
/* 有它,编译器才能获知自定义的系统调用的编号 */
#include "unistd.h"
/* iam()在用户空间的接口函数 */
_syscall1(int, iam, const char*, name);
/* whoami()在用户空间的接口函数 */
_syscall2(int, whoami,char*,name,unsigned int,size);
把编写好的测试程序通过挂载到虚拟机操作系统
~/oslab$ sudo ./mount-hdc
~/oslab$ cp iam.c whoami.c hdc/usr/root
执行脚本./run 进入虚拟机操作系统修改虚拟机系统调用号跟第一步是一样的
#define __NR_whoami 72
#define __NR_iam 73
在虚拟机 gcc 编译执行测试程序
到这里就算是完成了该实验