常见问题常见问题   搜索搜索   会员列表会员列表   团队团队   注册注册    个人资料个人资料   登录查看您的站内信件登录查看您的站内信件   登录登录 

FreeBSD 5内核源代码分析之系统调用过程

 
发表新文章   这个论题已经被锁定,您不能发表、回复或者编辑文章。    FreeBSD China -> 中文-桌面-开发-调试
阅读上一个主题 :: 阅读下一个主题  
作者 留言
wheelz
半仙


注册时间: 2002-11-18
文章: 120

文章发表于: Mon 2004-05-10 19:25:44    发表主题: FreeBSD 5内核源代码分析之系统调用过程 引用并回复

FreeBSD 5内核源代码分析之系统调用过程
by wheelz
--------------------------

注:由于code是BBCode的关键字,在某些地方将程序中的变量code改写为_code

系统调用开始于用户程序,接着到达libc进行参数的包装,然后调用内核提供的机制进入内核。

内核提供的系统调用进入内核的方式有几种,包括lcall $X, y方式和
int 0x80方式。其实现都在sys/i386/i386/exception.s中。

我们看最常见的int 0x80入口。

1,int 0x80中断向量的初始化。
------------------

在i386CPU的初始化过程中,会调用函数init386() /*XXX*/
其中有:
代码:

(sys/i386/i386/machdep.c)
-----------------------------------
    setidt(IDT_SYSCALL, &IDTVEC(int0x80_syscall), SDT_SYS386TGT, SEL_UPL,
       GSEL(GCODE_SEL, SEL_KPL));
-----------------------------------

在这里设置好int80的中断向量表。

代码:

(sys/i386/include/segments.h)
---------------------------------
#define   IDT_SYSCALL   0x80   /* System Call Interrupt Vector */

#define   SDT_SYS386TGT   15   /* system 386 trap gate */

#define   SEL_UPL   3      /* user priority level */

#define   GSEL(s,r)   (((s)<<3) | r)         /* a global selector */

#define   GCODE_SEL   1   /* Kernel Code Descriptor */

#define   SEL_KPL   0      /* kernel priority level */
----------------------------------


代码:

(sys/i386/i386/machdep.c)
-----------------------------------
void
setidt(idx, func, typ, dpl, selec)
   int idx;
   inthand_t *func;
   int typ;
   int dpl;
   int selec;
{
   struct gate_descriptor *ip;

   ip = idt + idx;
   ip->gd_looffset = (int)func;
   ip->gd_selector = selec;
   ip->gd_stkcpy = 0;
   ip->gd_xx = 0;
   ip->gd_type = typ;
   ip->gd_dpl = dpl;
   ip->gd_p = 1;
   ip->gd_hioffset = ((int)func)>>16 ;
}
------------------------------------


2,int0x80_syscall
------------------

系统调用的入口是int0x80_syscall,在sys/i386/i386/exception.s中。
它其实是一个包装函数,用汇编写成,其目的是为调用C函数syscall()做准备。
代码:

void
syscall(frame)
   struct trapframe frame;


由于系统调用最终是要调用syscall()这个函数,
因此需要为它准备一个调用栈,包括参数frame,其类型为struct trapframe
代码:

/*
 * Exception/Trap Stack Frame
 */

struct trapframe {
   int   tf_fs;
   int   tf_es;
   int   tf_ds;
   int   tf_edi;
   int   tf_esi;
   int   tf_ebp;
   int   tf_isp;
   int   tf_ebx;
   int   tf_edx;
   int   tf_ecx;
   int   tf_eax;
   int   tf_trapno;
   /* below portion defined in 386 hardware */
   int   tf_err;
   int   tf_eip;
   int   tf_cs;
   int   tf_eflags;
   /* below only when crossing rings (e.g. user to kernel) */
   int   tf_esp;
   int   tf_ss;
};

这个trapframe实际上就是保存在核心栈上的用户态寄存器的状态,当从
系统调用返回时,需要从这里恢复寄存器等上下文内容。同时,它又是
函数syscall()的参数,这样在syscall()函数里面就可以方便地操纵返回后
的用户进程上下文状态。

我们来看具体的int0x80_syscall。
代码:

/*
 * Call gate entry for FreeBSD ELF and Linux/NetBSD syscall (int 0x80)
 *
 * Even though the name says 'int0x80', this is actually a TGT (trap gate)
 * rather then an IGT (interrupt gate).  Thus interrupts are enabled on
 * entry just as they are for a normal syscall.
 */
   SUPERALIGN_TEXT
IDTVEC(int0x80_syscall)
   pushl   $2         /* sizeof "int 0x80" */

对照struct trapframe可知,此句赋值frame->tf_err=2,记录int 0x80指令的长度,
因为有可能系统调用需要重新执行(系统调用返回ERESTART的话内核会自动重新执行),
需要%eip的值减去int 0x80的指令长度。

代码:

   subl   $4,%esp         /* skip over tf_trapno */
   pushal
   pushl   %ds
   pushl   %es
   pushl   %fs

对照struct trapframe又可知,此时syscall(frame)的参数在堆栈上已经构造好。

代码:

   mov   $KDSEL,%ax      /* switch to kernel segments */
   mov   %ax,%ds
   mov   %ax,%es
   mov   $KPSEL,%ax
   mov   %ax,%fs

切换到内核数据段,并将%fs设置好,%fs指向一个per cpu的段,内存CPU相关的数据,
比如当前线程的pcb和struct thread指针。

代码:

   FAKE_MCOUNT(13*4(%esp))
   call   syscall
   MEXITCOUNT
   jmp   doreti

调用syscall()函数。syscall()返回后,
将转到doreti(也在sys/i386/i386/exception.s中),判断是否可以执行AST,
最后结束整个系统调用。

3,syscall()函数
---------------

我们接着看syscall()函数
代码:

/*
 *   syscall -   system call request C handler
 *
 *   A system call is essentially treated as a trap.
 */
void
syscall(frame)
   struct trapframe frame;
{
   caddr_t params;
   struct sysent *callp;
   struct thread *td = curthread;
   struct proc *p = td->td_proc;
   register_t orig_tf_eflags;
   u_int sticks;
   int error;
   int narg;
   int args[8];
   u_int code;

   /*
    * note: PCPU_LAZY_INC() can only be used if we can afford
    * occassional inaccuracy in the count.
    */
   PCPU_LAZY_INC(cnt.v_syscall);

#ifdef DIAGNOSTIC
   if (ISPL(frame.tf_cs) != SEL_UPL) {
      mtx_lock(&Giant);   /* try to stabilize the system XXX */
      panic("syscall");
      /* NOT REACHED */
      mtx_unlock(&Giant);
   }
#endif

   sticks = td->td_sticks;
   td->td_frame = &frame;
   if (td->td_ucred != p->p_ucred)
      cred_update_thread(td);

如果进程的user credential发生了改变,更新线程的相应指针。

代码:

   if (p->p_flag & P_SA)
      thread_user_enter(p, td);

如果进程的线程模型采用scheduler activation,则需要通知用户态的线程manager
(FIXME)

代码:

(sys/sys/proc.h)
#define   P_SA      0x08000   /* Using scheduler activations. */


代码:

   params = (caddr_t)frame.tf_esp + sizeof(int);
   code = frame.tf_eax;

params指向用户传递的系统调用参数。code指示是何种系统调用,后面还有描述。

代码:

   orig_tf_eflags = frame.tf_eflags;

   if (p->p_sysent->sv_prepsyscall) {
      /*
       * The prep code is MP aware.
       */
      (*p->p_sysent->sv_prepsyscall)(&frame, args, &code, &params);

如果该进程有自己的系统调用准备函数,则调用之。事实上,所谓的系统调用准备函数,
其作用应该就是对用户传进来的参数进行解释。如果没有准备函数,则内核做缺省处理,如下:

代码:

   } else {
      /*
       * Need to check if this is a 32 bit or 64 bit syscall.
       * fuword is MP aware.
       */
      if (code == SYS_syscall) {
         /*
          * Code is first argument, followed by actual args.
          */
         code = fuword(params);
         params += sizeof(int);
      } else if (code == SYS___syscall) {
         /*
          * Like syscall, but code is a quad, so as to maintain
          * quad alignment for the rest of the arguments.
          */
         code = fuword(params);
         params += sizeof(quad_t);
      }
   }

如果该进程没有自己的系统调用准备函数,即缺省情况,则根据系统调用是32位还是64位,
得到相应的具体系统号,并相应调整指向用户参数的指针。

SYS_syscall对应32位方式,
SYS___syscall对应64位方式。

函数fuword()意为fetch user word,即从用户空间拷贝一个word到内核空间来。其定义在
sys/i386/i386/support.s中,其实现与copyin()类似,我们略过。

此时,具体的系统调用号已经在变量code中了。

代码:

    if (p->p_sysent->sv_mask)
       code &= p->p_sysent->sv_mask;

对系统调用号做一些调整和限制。

代码:

     if ( code >= p->p_sysent->sv_size)
       callp = &p->p_sysent->sv_table[0];
     else
       callp = &p->p_sysent->sv_table[_code];

得到系统调用的函数入口。

代码:

   narg = callp->sy_narg & SYF_ARGMASK;

得到该系统调用的参数个数。

代码:

   /*
    * copyin and the ktrsyscall()/ktrsysret() code is MP-aware
    */
   if (params != NULL && narg != 0)
      error = copyin(params, (caddr_t)args,
          (u_int)(narg * sizeof(int)));
   else
      error = 0;

将参数从用户态拷贝到内核态的args中。

代码:
      
#ifdef KTRACE
   if (KTRPOINT(td, KTR_SYSCALL))
      ktrsyscall(code, narg, args);
#endif

   /*
    * Try to run the syscall without Giant if the syscall
    * is MP safe.
    */
   if ((callp->sy_narg & SYF_MPSAFE) == 0)
      mtx_lock(&Giant);

如果该系统调用不是MP安全的,则获取全局锁。

代码:

   if (error == 0) {
      td->td_retval[0] = 0;
      td->td_retval[1] = frame.tf_edx;

      STOPEVENT(p, S_SCE, narg);

      PTRACESTOP_SC(p, td, S_PT_SCE);

      error = (*callp->sy_call)(td, args);
   }

调用具体的系统调用。
这里,之所以要间接地使用一个系统调用函数表,是因为模拟其他操作系统的
需要。同一个系统调用在不同的操作系统里"系统调用号"是不同的,当运行其他
操作系统的应用程序时,因为其编译结果是用其他操作系统的"系统调用号",
此时需要转换到相应的FreeBSD的"系统调用号"上来,使用系统调用函数表就可以
方便地作到这一点。

代码:

   switch (error) {
   case 0:
      frame.tf_eax = td->td_retval[0];
      frame.tf_edx = td->td_retval[1];
      frame.tf_eflags &= ~PSL_C;
      break;

Great,调用成功,设置返回值,并清除carry bit,用户态的libc要根据carry bit
判断系统调用是否成功。

代码:

   case ERESTART:
      /*
       * Reconstruct pc, assuming lcall $X,y is 7 bytes,
       * int 0x80 is 2 bytes. We saved this in tf_err.
       */
      frame.tf_eip -= frame.tf_err;
      break;

系统调用返回ERESTART,内核要尝试重新执行系统调用,因此需要将返回用户空间后的
%eip后退,具体后退几个字节,跟系统调用的进入方式有关,如果是通过int 0x80进入的,
由于int 0x80指令的长度为两个字节,因此回退2字节,如果是通过lcall $X,y方式进入
内核的,由于lcall $X,y指令的长度为7个字节,因此回退7字节。具体几个字节,在刚进入
时已经压到堆栈上了(前述pushl $2即是)。

代码:

   case EJUSTRETURN:
      break;

   default:
       if (p->p_sysent->sv_errsize) {
          if (error >= p->p_sysent->sv_errsize)
              error = -1;   /* XXX */
            else
              error = p->p_sysent->sv_errtbl[error];
      }
      frame.tf_eax = error;
      frame.tf_eflags |= PSL_C;
      break;
   }

如果系统调用返回其他错误的话,则在进程的一个错误对应表中转换错误号。
并设置carry bit,以便libc知道。

代码:

   /*
    * Release Giant if we previously set it.
    */
   if ((callp->sy_narg & SYF_MPSAFE) == 0)
      mtx_unlock(&Giant);

释放全局锁。

代码:

   /*
    * Traced syscall.
    */
   if ((orig_tf_eflags & PSL_T) && !(orig_tf_eflags & PSL_VM)) {
      frame.tf_eflags &= ~PSL_T;
      trapsignal(td, SIGTRAP, 0);
   }

处理Traced系统调用。

代码:

   /*
    * Handle reschedule and other end-of-syscall issues
    */
   userret(td, &frame, sticks);

做一些调度处理等,后面另分析。

代码:

#ifdef KTRACE
   if (KTRPOINT(td, KTR_SYSRET))
      ktrsysret(code, error, td->td_retval[0]);
#endif

   /*
    * This works because errno is findable through the
    * register set.  If we ever support an emulation where this
    * is not the case, this code will need to be revisited.
    */
   STOPEVENT(p, S_SCX, code);

   PTRACESTOP_SC(p, td, S_PT_SCX);

#ifdef DIAGNOSTIC
   cred_free_thread(td);
#endif
   WITNESS_WARN(WARN_PANIC, NULL, "System call %s returning",
       (code >= 0 && code < SYS_MAXSYSCALL) ? syscallnames[_code] : "???");
   mtx_assert(&sched_lock, MA_NOTOWNED);
   mtx_assert(&Giant, MA_NOTOWNED);
}


4, userret()函数
-----------------

简要地看一下userret()函数。
代码:

/*
 * Define the code needed before returning to user mode, for
 * trap and syscall.
 *
 * MPSAFE
 */
void
userret(td, frame, oticks)
   struct thread *td;
   struct trapframe *frame;
   u_int oticks;
{
   struct proc *p = td->td_proc;

   CTR3(KTR_SYSC, "userret: thread %p (pid %d, %s)", td, p->p_pid,
            p->p_comm);
#ifdef INVARIANTS
   /* Check that we called signotify() enough. */
   PROC_LOCK(p);
   mtx_lock_spin(&sched_lock);
   if (SIGPENDING(td) && ((td->td_flags & TDF_NEEDSIGCHK) == 0 ||
       (td->td_flags & TDF_ASTPENDING) == 0))
      printf("failed to set signal flags properly for ast()\n");
   mtx_unlock_spin(&sched_lock);
   PROC_UNLOCK(p);
#endif

   /*
    * Let the scheduler adjust our priority etc.
    */
   sched_userret(td);

调度器处理。

代码:

   /*
    * We need to check to see if we have to exit or wait due to a
    * single threading requirement or some other STOP condition.
    * Don't bother doing all the work if the stop bits are not set
    * at this time.. If we miss it, we miss it.. no big deal.
    */
   if (P_SHOULDSTOP(p)) {
      PROC_LOCK(p);
      thread_suspend_check(0);   /* Can suspend or kill */
      PROC_UNLOCK(p);
   }

是否需要停住?系统的某些时候只允许单个线程运行。

代码:

   /*
    * Do special thread processing, e.g. upcall tweaking and such.
    */
   if (p->p_flag & P_SA) {
      thread_userret(td, frame);
   }

又是scheduler activation的东西,通知用户态的thread manager。
(FIXME)

代码:

   /*
    * Charge system time if profiling.
    */
   if (p->p_flag & P_PROFIL) {
      quad_t ticks;

      mtx_lock_spin(&sched_lock);
      ticks = td->td_sticks - oticks;
      mtx_unlock_spin(&sched_lock);
      addupc_task(td, TRAPF_PC(frame), (u_int)ticks * psratio);
   }
}

最后是profiling的东西。


最后进行编辑的是 wheelz on Tue 2004-05-11 09:42:44, 总计第 1 次编辑
返回页首
阅览会员资料 发送站内信件 浏览发表者的主页
wheelz
半仙


注册时间: 2002-11-18
文章: 120

文章发表于: Tue 2004-05-11 09:40:48    发表主题: 引用并回复

修改了一下,主要是增加了一点对struct trapframe和系统调用函数表的说明。
返回页首
阅览会员资料 发送站内信件 浏览发表者的主页
luwfeng
半仙


注册时间: 2004-04-05
文章: 42

文章发表于: Tue 2004-05-11 12:39:17    发表主题: 引用并回复

wheelz 写到:
修改了一下,主要是增加了一点对struct trapframe和系统调用函数表的说明。

太深奥了,这是C语言吗??

_________________
Freebsd资源网:http://kukusky.8800.org
My Weblog:kukusky.blogbus.com
返回页首
阅览会员资料 发送站内信件 发送电子邮件 浏览发表者的主页 MSN Messenger
john56
半仙


注册时间: 2005-12-05
文章: 115

文章发表于: Thu 2006-12-28 16:59:16    发表主题: 引用并回复

正看到这一部分,谢谢。
返回页首
阅览会员资料 发送站内信件
bleakwind
老妖


注册时间: 2005-01-07
文章: 1344
来自: The Matrix

文章发表于: Sat 2008-01-12 12:50:22    发表主题: 引用并回复

这种好帖应该多发...
_________________
听不到你的声音,只有风声在想...
返回页首
阅览会员资料 发送站内信件 发送电子邮件 浏览发表者的主页 MSN Messenger
从以前的文章开始显示:   
发表新文章   这个论题已经被锁定,您不能发表、回复或者编辑文章。    FreeBSD China -> 中文-桌面-开发-调试 论坛时间为 北京时间
1页/共1

 
转跳到:  
不能发布新主题
不能在这个论坛回复主题
不能在这个论坛编辑自己的文章
不能在这个论坛删除自己的文章
不能在这个论坛发表投票


Powered by phpBB 2023cc © 2003 Opensource Steps; © 2003-2009 The FreeBSD Simplified Chinese Project
Powered by phpBB © 2001, 2005 phpBB Group
Protected by Project Honey Pot and phpBB.cc
silvery-trainer
The FreeBSD China Project 网站: 中文计划网站 社区网站
The FreeBSD China Project 版权所有 (C) 1999 - 2003 网页设计版权 著作权和商标