分类: BSD
2005-10-17 15:04:56
操作系统系统调用设计规范
------基于OpenBSD系统
作者:xie_minix
操作系统的大致分层中,系统调用是放在中间层,其下是软件中断或设备驱动例程,其上为用户进程.他执
行以下两个功能:
. 检查由用户进程提交的参数
. 调用核心例程来执行用户所需求功能
不管调用成功是失败,系统都将返回给一个结果给调用进程,一般我们采用全局变量error(位于sys/sys/error.h)
中的值作为返回结果.值为0代表调用成功,非0代表失败,其值对应error.h中的值.在检查到用户提交的参数
有错误时,系统将会由一段C库例程填充error的值,并设置寄存器EAX为-1(即返回值).
BSD类操作系统的系统调用申明在/sys/sys/syscall.h中,我们所说的系统调用号全部在此申明,如:
#define SYS_open 5 说明SYS_open实际上是5号系统调用,用户端的系统调用申明,也就是我们编译器所
使用的头文件位于:/usr/include/sys/syscall.h中。其中所申明的系统调用常量值和/sys/sys/syscall.h中
的值是一样的。我们打开一文件可以使用系统调用:
fd=syscall("/sys/sys/syscall.h",O_RDONLY,S_IRWXU);
或通用的:
fd=open("/sys/sys/syscall.h",O_RDONLY);
我们为了彻底弄清函数的工作过程,不得不了解编译器的一些简单原理。
对于open函数,编译器使用/sys/lib/csu/common.h中的
#define open(name, f, m) __syscall(SYS_open, (name), (f), (m))
来解释open函数。在汇编代码过程中,使用的是/lib/libc/arch/i386/sys/syscall.S中的一个汇编语言过
程,当然我在这指的是Inter的CPU,不同的CPU所使用的编译库是不同的(位于/sys/arch下)
#include "SYS.h"
SYSENTRY(syscall)
/*
SYSENTRY宏展开后为
.text; .align 2, 0x90;
.globl _thread_sys_syscall;
.type _thread_sys_syscall,@function;
_thread_sys_syscall:
.weak syscall;
syscall = _thread_sys_syscall
.globl CERROR
*/
pop %ecx /* 用户进程返回地址 */
pop %eax /* C过程压栈的系统调用号 */
push %ecx /*调换返回地址在栈顶的顺序,使栈顶是返回地址,有利于中断返回*/
int $0x80 /*执行0X80号中断*/
push %ecx /* 保持堆栈的平衡 */
jc err /*进入到核心后(中断程序),如果有错误,中断返回时carry位置位*/
ret
err:
#ifdef PIC
pushl %ebx;
call 666f;/*本身此段在内存中的位置是不确定的,编译后标号666确定了,所以要call 666f*/
666:
popl %ebx;
addl $_C_LABEL(_GLOBAL_OFFSET_TABLE_)+[.-666b], %ebx
movl (CERROR)@GOT(%ebx), %ecx
popl %ebx
jmp *%ecx
#else
jmp CERROR
#endif
/sys/arch/i386/i386/trap.c中对系统调用所做的处理.
void
syscall(frame)
struct trapframe frame;
{
register caddr_t params;
register struct sysent *callp;
register struct proc *p;
int orig_error, error, opc, nsys;
size_t argsize;
register_t code, args[8], rval[2];
u_quad_t sticks;
uvmexp.syscalls++;
p = curproc;
sticks = p->p_sticks;
p->p_md.md_regs = &frame;
opc = frame.tf_eip;
code = frame.tf_eax;
nsys = p->p_emul->e_nsysent;/*最大的系统调用号*/
callp = p->p_emul->e_sysent;/*系统调用过程表的入口*/
params = (caddr_t)frame.tf_esp + sizeof(int);
switch (code) {
case SYS_syscall:/*0号系统调用,没用过,不做进一步解释*/
/*
* Code 是第一个参数,后面的才是实际的参数.
*/
copyin(params, &code, sizeof(int));/*从用户空间拷贝参数到核心空间*/
params += sizeof(int);/*跳过系统调用号,也就是说params指到了真正的参数位置*/
break;
case SYS___syscall:/*第198号系统调用,没用过,不做进一步解释*/
/*
* 类似系统调用, 但code是一个矩阵, 定位其余的参数必须跳过这个矩阵.
*/
if (callp != sysent)
break;
copyin(params + _QUAD_LOWWORD * sizeof(int), &code, sizeof(int));
/*从用户空间拷贝参数到核心空间,但要*/
params += sizeof(quad_t);
break;
default:
break;
}
if (code < 0 || code >= nsys) /*系统调用号不能小于0或大于等于最大的系统调用号*/
callp += p->p_emul->e_nosys;
else
callp += code; /*确定调用过程的入口处*/
argsize = callp->sy_argsize;/*在sysent结构中的sy_argsize是该系统调用参数的总长度*/
if (argsize)
error = copyin(params, (caddr_t)args, argsize);/*从用户空间拷贝参数到核心空间*/
else
error = 0;
orig_error = error;
if (error) /*copyin时出现了错误*/
goto bad;
rval[0] = 0;
rval[1] = frame.tf_edx;
orig_error = error = (*callp->sy_call)(p, args, rval);/*调用相应的过程*/
switch (error) {
case 0:
/*
* 重新初始化进程指针`p' ,因为有可能不同.如:fork调用,返回的就不同了.
*/
p = curproc;/*curproc是全局变量,代表当前CPU的当前进程指针.*/
frame.tf_eax = rval[0];/*用于返回值,用的很少,我的就没用过它.*/
frame.tf_edx = rval[1];/*同上*/
frame.tf_eflags &= ~PSL_C; /* 去掉carry的进位位 */
break;
case ERESTART:
/*
* 调用函数的入口偏移量有问题,好象是入口函数的对齐字或双字有问题,不过我
* 还没试过强制性函数入口按什么来对齐.在编函数时也确实应该注意一下对齐的问题.
*/
frame.tf_eip = opc - frame.tf_err;
break;
case EJUSTRETURN:
/* 什么也不做 */
break;
default:/*其他的数值都代表有错误,*/
bad:
if (p->p_emul->e_errno)
error = p->p_emul->e_errno[error];
frame.tf_eax = error;/*错误值给返回的EAX寄存器中*/
frame.tf_eflags |= PSL_C; /* carry 位置位 */
break;
}
userret(p, frame.tf_eip, sticks);
}
----------------------------------写的OPENBSD的----------
1.设置文件/sys/arch/i386/conf/Makefile
(下面以把sample.c链入核心)
在OBJS=...后加入sample.o,在CFLIG=...处加入源文件路径 $S/net/sample.c。
当然这里假定sample.c是放在/sys/net目录下的。
还有在对象文件和源文件对应的说明中也要加入该描述。
即以下两行说明:sample.o: $S/net/sample.c
${NORMAL_C}
2.sample.c的设计规范
必须包括以下头文件:
#include
#include
#include
#include
#include
#include
#include
#include
#include
以上头文件是在编网络相关核心例程必须要包括的。且必须按照顺序。
在进行网络底层设计时还要包括一些相关的头文件。对于我们要进行的sample.c需要有以下头文件:
#include
#include
#include
#include
#include
#include
现在我们用sample.c来实现一个简单的监测网络数据包接收总数的统计。
/* /sys/net/sample.c 核心内实现简单监测packet进入的数量 */
/* 作者:xie_minix */
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
int sample(ifp)
struct ifnet *ifp;
{
return (int)(ifp->if_ipackets); /*ifp是网络适配器的核心数据结构,内记录了各统计数据,if_ibytes是该网卡的input packets总数*/
}
整个函数功能只是返回该网卡的接收包数。
下面是sample.h头文件,在头文件中必须申明该函数的原型。
/* /sys/net/sample.h */
/* 作者:xie_minix */
#ifndef _NET_SAMPLE_H_
#define _NET_SAMPLE_H_
#ifdef _KERNEL
int sample(struct ifnet *);
#endif
#endif
该文件只是简单的对在核心内使用的函数做了一个申明,在此我使用了缩进格式是为了可读性,BSD的风格不赞成这样,特此说明。
要调用该函数,必须知道ifnet.ifnet在/sys/net/if_ethersubr.c中比较常用。因此我们可以考虑在此调用该函数。
注:if_ethersubr.c是以太网通用代码。
要引用sample(ifp)函电数,必须在if_ethersubr.c中加入头文件:
#include
调用可以安排在ether_input()函数中,比如放到判断网卡是否激活后面。即:
if ((ifp->if_flags & IFF_UP)==0) {
m_freem(m);
return;
}
在下面加入:
tmp_sample=sample(ifp);
printf("device %c input packets:%d",ifp->if_xname,tmp_sample);
当然临时变量tmp_sample在input_ether()函数内要先申明。
即:
ether_input(ifp,eh,m)
{... 其他的申明变量
int tmp_sample; /*只要此行就行了。*/
...
这只是例子。其实要得到进入的包数,可以直接printf("device %c input packets:%d",ifp->if_xname,ifp->if_ipackets);
就能取得包进入的数量。
此外,顺便讲一下编译核心。
以i386结构体系为例:
cd /sys/arch/i386/conf
config 你的设置文件
cd ../compile/你的设置文件
make depend
make
mv /netbsd /netbsd.old 这是netbsd. mv /bsd /bsd.old 这是openbsd
mv netbsd /netbsd 或 mv bsd /bsd 和上一行一样