Chinaunix首页 | 论坛 | 博客
  • 博客访问: 198805
  • 博文数量: 29
  • 博客积分: 1280
  • 博客等级: 中尉
  • 技术积分: 320
  • 用 户 组: 普通用户
  • 注册时间: 2005-02-22 16:23
文章分类

全部博文(29)

文章存档

2009年(3)

2008年(1)

2007年(1)

2006年(3)

2005年(21)

我的朋友

分类: BSD

2005-10-17 15:04:56

好象是04年写的,而且用到了我的服务器NETBSD上.蛮好用的

操作系统系统调用设计规范
           ------基于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  和上一行一样

阅读(2798) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~