李治军操作系统实验2——系统调用
操作系统实验2——系统调用
代码仓库
GitLab实验内容
给linux-0.11添加两个系统调用,并在程序中使用他们。
系统调用处理过程
在实验开始之前,我们先来了解一下系统调用是怎么进行的。
如果我们想使用C函数库调用close()系统调用,可以直接在C程序中写:
//int read(int fd);
read(fd);
如果想直接一点,不通过C函数库,可以这样写:
#define __LIBRARY__
#include <unistd.h>
_syscall1(int,close,int,fd)
宏函数_syscalln() 被定义在include/unistd.h中(n是参数数量,0-3)。
#include/unistd.h:172
#define _syscall3(type,name,atype,a,btype,b,ctype,c) \
type name(atype a,btype b,ctype c) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name),"b" ((long)(a)),"c" ((long)(b)),"d" ((long)(c))); \
if (__res>=0) \
return (type) __res; \
errno=-__res; \
return -1; \
}
_syscall1(int,close,int,fd)宏展开后
int close(int fd)
{
long __res;
__asm__ volatile ("int $0x80"
: "=a" (__res)
: "0" (__NR_close),"b" ((long)(fd)));
if (__res >= 0)
return (int) __res;
errno = -__res;
return -1;
}
宏__NR_close被定义在同文件66行,也可以看到其它被定义的系统调用功能号。
//include/unistd.h:64
#define __NR_write 4
#define __NR_open 5
#define __NR_close 6
#define __NR_waitpid 7
#define __NR_creat 8
函数先将宏__NR_close存入EAX(功能号),将参数fd存入EBX,然后进行0x80中断调用。调用返回后,从EAX取出返回值,存入__res,再通过对__res的判断决定传给API的调用者什么样的返回值。
根据IDT(中断描述符表),中断发生后,自动调用函数system_call。
进入内核中的系统调用处理程序kernel/system_call.s的代码会首先检查EAX中的系统调用功能号是否合法,然后根据sys_call_table[]调用相应的系统调用处理程序。
检查EAX系统调用号是否合法。
#kernel/system_call.s:61
nr_system_calls = 72
#kernel/system_call.s:94
cmpl $nr_system_calls-1,%eax
调用地址在_sys_call_table + %eax * 4处的函数。
#kernel/system_call.s:94
call sys_call_table(,%eax,4)
sys_call_table[]函数指针表在include/linux/sys.h。
//include/linux/sys.h:74
fn_ptr sys_call_table[] = { sys_setup, sys_exit, sys_fork, sys_read,
sys_write, sys_open, sys_close, sys_waitpid, sys_creat, sys_link,
sys_unlink, sys_execve, sys_chdir, sys_time, ......};
这些函数都被声明在本文件的开头。
//include/linux/sys.h:1
extern int sys_setup();
extern int sys_exit();
extern int sys_fork();
extern int sys_read();
extern int sys_write();
......
至此我们终于完成了一个系统调用!
实验步骤
1.修改系统调用函数总数,我们要添加两个系统调用函数,所以把原本的72改为74。
#kernel/system_call.s:61
nr_system_calls = 74
2.添加函数声明,修改系统调用函数指针表。添加sys_iam()和sys_whoami()的声明。在系统调用函数指针表sys_call_table[] 后加上:sys_iam和sys_whoami。
//include/linux/sys.h:71
......
extern int sys_setreuid();
extern int sys_setregid();
extern int sys_iam();
extern int sys_whoami();
//include/linux/sys.h:76
fn_ptr sys_call_table[] = {......,sys_ssetmask,
sys_setreuid,sys_setregid,sys_iam,sys_whoami };
3.实现sys_iam()和sys_whoami()。在kernel中添加who.c。使用get_fs_byte()和put_fs_byte()实现用户态和核心态之间传递数据。
//kernel/who.c
#include <string.h>
#include <errno.h>
#include <asm/segment.h>
char msg[24];
int sys_iam(const char *name)
{
int i;
char tmp[30];
for (i = 0; i < 30; i++)
{
//从用户态内存取得数据
tmp[i] = get_fs_byte(name + i);
if (tmp[i] == '\0')
break;
}
i = 0;
while (i < 30 && tmp[i] != '\0')
i++;
if (i > 23)
{
// printk("String too long!\n");
return -(EINVAL); //置errno为EINVAL 并返回-1
}
strcpy(msg, tmp);
return i;
}
int sys_whoami(char *name, unsigned int size)
{
int len = 0;
for (; msg[len] != '\0'; len++)
;
if (len > size)
{
return -(EINVAL);
}
int i = 0;
for (i = 0; i < size; i++)
{
put_fs_byte(msg[i], name + i);
if (msg[i] == '\0')
}
return i;
}
4.修改kernel/Makefile,这样who.c就可以一起编译了。
//kernel/Makefile:29
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
//kernel/Makefile:50
### Dependencies:
who.s who.o: who.c ../include/linux/kernel.h ../include/unistd.h
5.完成编译系统后。启动linux-0.11,修改根目录下/usr/include/unistd.h(如果直接修改linux-0.11源码下的/include/unistd.h,这边并不会跟着修改)。
为我们添加的两个系统调用添加系统调用编号宏。
//NOTE:非源码文件!
//usr/include/unistd.h:130
#define __NR_setreuid 70
#define __NR_setregid 71
#define __NR_iam 72
#define __NR_whoami 73
6.编写测试程序验证新添加的系统调用。编译运行测试程序,带一个参数name字符串,若能正常执行系统调用,应输出我们的参数name。
#include <errno.h>
#define __LIBRARY__
#include <unistd.h>
#include <stdio.h>
_syscall1(int, iam, const char*, name);
_syscall2(int, whoami, char*, name, unsigned int ,size);
int main(int argc,char ** argv)
{
char s[30];
iam(argv[1]);
whoami(s,30);
printf("%s\n",s);
return 0;
}

浙公网安备 33010602011771号