快乐虾

http://blog.csdn.net/lights_joy/

lights@hb165.com

 

本文适用于

Cygwin checkout-2008-09-28

vs2008

 

欢迎转载,但请保留作者信息

 

Linux下的程序经常使用signal机制,cygwin对其进行了模仿,下面对它的这一关键技术进行分析。

1.1    信号量接收线程

cygwin初始化的时候,会创建一个叫wait_sig的线程,看看这个线程做了什么:

/* Process signals by waiting for cyg_signal data to arrive in a pipe.

   Set a completion event if one was specified. */

static DWORD WINAPI

wait_sig (VOID *)

{

………….

     sigpacket pack;

     pack.si.si_signo = __SIGHOLD;

     for (;;)

     {

         if (pack.si.si_signo == __SIGHOLD)

              WaitForSingleObject (sigCONT, INFINITE);

         DWORD nb;

         pack.tls = NULL;

         if (!ReadFile (my_readsig, &pack, sizeof (pack), &nb, NULL))

              break;

………………

     }

 

     ForceCloseHandle (my_readsig);

     sigproc_printf ("signal thread exiting");

     ExitThread (0);

}

也就是说,它将反复从一个叫my_readsig的句柄中读取sigpacket这一结构体的数据进行处理。那么my_readsig是什么句柄呢?

static HANDLE my_readsig;

void

wait_for_sigthread (bool forked)

{

  char char_sa_buf[1024];

  PSECURITY_ATTRIBUTES sa_buf = sec_user_nih ((PSECURITY_ATTRIBUTES) char_sa_buf, cygheap->user.sid());

  if (!CreatePipe (&my_readsig, &my_sendsig, sa_buf, 0))

    api_fatal ("couldn't create signal pipe%s, %E", forked ? " for forked process" : "");

  ProtectHandle (my_readsig);

  myself->sendsig = my_sendsig;

………….

}

原来是一个匿名管道,从这里也可以猜想,当程序捕捉到操作系统发送的特定信息时,它只要将这个信息用sigpacket进行封装后再写入my_sendsig就可以了,这样信号量的接收线程就可以进行适当处理。

1.2    信号量发送

Cygwin提供了一个叫sig_send的函数用以发送信号量:

/* Send a signal to another process by raising its signal semaphore.

If pinfo *p == NULL, send to the current process.

If sending to this process, wait for notification that a signal has

completed before returning.  */

int

sig_send (_pinfo *p, siginfo_t& si, _cygtls *tls)

{

     int rc = 1;

     bool its_me;

     HANDLE sendsig;

     sigpacket pack;

     bool communing = si.si_signo == __SIGCOMMUNE;

……………

 

     sigset_t pending;

     if (!its_me)

         pack.mask = NULL;

     else if (si.si_signo == __SIGPENDING)

         pack.mask = &pending;

     else if (si.si_signo == __SIGFLUSH || si.si_signo > 0)

         pack.mask = tls ? &tls->sigmask : &_main_tls->sigmask;

     else

         pack.mask = NULL;

 

     pack.si = si;

     if (!pack.si.si_pid)

         pack.si.si_pid = myself->pid;

     if (!pack.si.si_uid)

         pack.si.si_uid = myself->uid;

     pack.pid = myself->pid;

     pack.tls = tls;

     if (wait_for_completion)

     {

         pack.wakeup = CreateEvent (&sec_none_nih, FALSE, FALSE, NULL);

         sigproc_printf ("wakeup %p", pack.wakeup);

         ProtectHandle (pack.wakeup);

     }

 

     char *leader;

     size_t packsize;

     if (!communing || !(si._si_commune._si_code & PICOM_EXTRASTR))

     {

         leader = (char *) &pack;

         packsize = sizeof (pack);

     }

     else

     {

         size_t n = strlen (si._si_commune._si_str);

         char *p = leader = (char *) alloca (sizeof (pack) + sizeof (n) + n);

         memcpy (p, &pack, sizeof (pack)); p += sizeof (pack);

         memcpy (p, &n, sizeof (n)); p += sizeof (n);

         memcpy (p, si._si_commune._si_str, n); p += n;

         packsize = p - leader;

     }

 

     DWORD nb;

     if (!WriteFile (sendsig, leader, packsize, &nb, NULL) || nb != packsize)

     {

         /* Couldn't send to the pipe.  This probably means that the

         process is exiting.  */

……………….

     }

……………….

}

可以明显看出,这个函数将各种信息包装在sigpacket结构体中,然后再通过前面创建的管道发送出去。由于使用了管道,因此signal不但可以在本进程内发送,也可以发送到其它以cygwin为基础的程序。

sig_send函数可以发现,在timerexceptiontermiosfork等事件发生时都有相应的信号量发送。

1.3    执行cygwin函数时收到信号量

由于信号量来源于不同位置,因此当信号发生时信号处理线程可能处于不同状态,当要求接收信号量的线程处于cygwin函数中时,cygwin并不会立即中止当前函数的执行,而是会在当前函数执行结束后再跳转到信号处理函数。为了达到这个目的,cygwin在每个函数的前面插入了一段叫sigfestab代码:

       pushl       %ebx

       pushl       %edx

       movl       %fs:4,%ebx                         # location of bottom of stack

1:     movl       /$1,%eax                      # potential lock value

       lock xchgl %eax,$tls::stacklock(%ebx) # see if we can grab it

       movl       %eax,$tls::spinning(%ebx)            # flag if we are waiting for lock

       testl %eax,%eax                          # it will be zero

       jz     2f                                #  if so

       xorl  %eax,%eax                          # nope.  It was not zero

       call  _low_priority_sleep                     # should be a short-cyg_time thing, so

       jmp  1b                                # cyg_sleep and loop

2:     movl       /$4,%eax                      # have the lock, now increment the

       xadd       %eax,$tls::stackptr(%ebx)            #  stack pointer and get pointer

       leal   __sigbe,%edx                      # new place to return to

       xchgl      %edx,12(%esp)                          # exchange with real return value

       movl       %edx,(%eax)                       # store real return value on alt stack

       incl  $tls::incyg(%ebx)

       decl $tls::stacklock(%ebx)                   # cyg_remove lock

       popl %edx                                  # restore saved value

       popl %ebx

这一段代码的关键操作是将此函数的返回地址压入了_cygtls::stack里面,然后将_cygtls::incyg1,表示进入了一个cygwin的内部函数,接着它将此函数的返回地址替换为__sigbe,这是一小段汇编代码,这样当这个函数返回时,它将跳转到__sigbe执行:

__sigbe:                                      # return here after cygwin syscall

       pushl       %edx

       pushl       %ebx

       pushl       %eax                                  # don't clobber

1:     movl       %fs:4,%ebx                         # address of bottom of tls

       movl       /$1,%eax                      # potential lock value

       lock xchgl %eax,$tls::stacklock(%ebx) # see if we can grab it

       movl       %eax,$tls::spinning(%ebx)            # flag if we are waiting for lock

       testl %eax,%eax                          # it will be zero

       jz     2f                                #  if so

       xorl  %eax,%eax                          # nope.  not zero

       call  _low_priority_sleep                     # cyg_sleep

       jmp  1b                                #  and loop

2:     movl       /$-4,%eax                            # now decrement aux stack

       xadd       %eax,$tls::stackptr(%ebx)            #  and get pointer

       xorl  %edx,%edx

       xchgl      %edx,-4(%eax)                           # get return address from cyg_signal stack

       xchgl      %edx,8(%esp)                            # restore edx/real return address

       decl $tls::incyg(%ebx)

       decl $tls::stacklock(%ebx)                   # release lock

       popl %eax

       popl %ebx

       ret

这一段代码将检查此时有没有信号量需要处理,如果有就进行相应的处理,如果没有就直接取出函数开头保存的返回地址并按此地址返回。

1.4    执行用户代码时接收到信号

当在执行用户代码时,cygwin的信号量接收线程将进行下面的处理:

static int

setup_handler (int sig, void *handler, struct cyg_sigaction& siga, _cygtls *tls)

{

     CONTEXT cx;

     bool interrupted = false;

……….

     for (i = 0; i < CALL_HANDLER_RETRY; i++)

     {

……………………

         res = SuspendThread (hth);

         /* Just set pending if thread is already suspended */

         if (res)

         {

              ResumeThread (hth);

              break;

         }

         cx.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER;

         if (!GetThreadContext (hth, &cx))

              system_printf ("couldn't get context of main thread, %E");

         else

              interrupted = tls->interrupt_now (&cx, sig, handler, siga);

 

         res = ResumeThread (hth);

         if (interrupted)

              break;

 

         sigproc_printf ("couldn't interrupt.  trying again.");

         low_priority_sleep (0);

     }

………….

}

这段代码表明,它将暂停信号量处理线程的执行,改变其ThreadContext,将其中的pc指针指向信号处理函数(由tls->interrupt_now完成),再继续执行此线程,这样自然就跳转到了信号处理函数了。

1.5    当进行windows系统调用时接收到信号

此时cygwin将延迟一段时间后重试,如果在指定次数后仍然无法进行处理则丢弃此信号量。Cygwin对系统调用的定义是只要调用的函数代码位于windows目录下的dll中就认为是系统调用!

 

 

2       参考资料

ls带来的困惑2009-9-20

fork子进程的第一次跳转(2009-9-8)

cygwin fork子进程对父进程数据的复制(2009-9-8)

cygwin下的共享内存区(2009-9-8)

cygwin下的user heap(2009-9-8)

cygwin下的cygheap:从父进程到子进程的复制(2009-9-7)

 

 

 

 

Logo

更多推荐