快乐虾
http://blog.csdn.net/lights_joy/
本文适用于
Cygwin checkout-2008-09-28
vs2008
欢迎转载,但请保留作者信息
Linux下的程序经常使用signal机制,cygwin对其进行了模仿,下面对它的这一关键技术进行分析。
在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就可以了,这样信号量的接收线程就可以进行适当处理。
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函数可以发现,在timer,exception,termios,fork等事件发生时都有相应的信号量发送。
由于信号量来源于不同位置,因此当信号发生时信号处理线程可能处于不同状态,当要求接收信号量的线程处于cygwin函数中时,cygwin并不会立即中止当前函数的执行,而是会在当前函数执行结束后再跳转到信号处理函数。为了达到这个目的,cygwin在每个函数的前面插入了一段叫sigfe的stab代码:
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::incyg加1,表示进入了一个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
这一段代码将检查此时有没有信号量需要处理,如果有就进行相应的处理,如果没有就直接取出函数开头保存的返回地址并按此地址返回。
当在执行用户代码时,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完成),再继续执行此线程,这样自然就跳转到了信号处理函数了。
此时cygwin将延迟一段时间后重试,如果在指定次数后仍然无法进行处理则丢弃此信号量。Cygwin对系统调用的定义是只要调用的函数代码位于windows目录下的dll中就认为是系统调用!
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)