sem_wait 未使用 EINTR 解锁

sem_wait not unblocking with EINTR

我是信号量的新手,想在我的程序中添加多线程,但我无法解决以下问题:sem_wait() 应该能够接收 EINTR 并解锁,只要我没有设置 SA_RESTART 标志。我正在向 sem_wait() 中阻塞的工作线程发送一个 SIGUSR1,它确实收到信号并被中断,但它会继续阻塞,所以它永远不会给我一个 -1 return 代码连同 errno = EINTR。但是,如果我从主线程执行 sem_post,它将解除阻塞,给我 EINTR 的错误号,但 RC 为 0。我对这种行为感到非常困惑。它是一些奇怪的 NetBSD 实现还是我在这里做错了什么?根据手册页,sem_wait 符合 POSIX.1 (ISO/IEC 9945-1:1996)。一个简单的代码:

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <signal.h>
#include <pthread.h>
#include <semaphore.h>

typedef struct workQueue_s
{
   int full;
   int empty;
   sem_t work;
   int sock_c[10];
} workQueue_t;

void signal_handler( int sig )
{
   switch( sig )
   {
      case SIGUSR1:
      printf( "Signal: I am pthread %p\n", pthread_self() );
      break;
   }
}

extern int errno;
workQueue_t queue;
pthread_t workerbees[8];

void *BeeWork( void *t )
{
   int RC;
   pthread_t tid;
   struct sigaction sa;
   sa.sa_handler = signal_handler;
   sigaction( SIGUSR1, &sa, NULL );

   printf( "Bee: I am pthread %p\n", pthread_self() );
   RC = sem_wait( &queue.work );
   printf( "Bee: got RC = %d and errno = %d\n", RC, errno );

   RC = sem_wait( &queue.work );
   printf( "Bee: got RC = %d and errno = %d\n", RC, errno );
   pthread_exit( ( void * ) t );
}

int main()
{
   int RC;
   long tid = 0;
   pthread_attr_t attr;
   pthread_attr_init( &attr );
   pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE );

   queue.full = 0;
   queue.empty = 0;
   sem_init( &queue.work, 0, 0 );

   printf( "I am pthread %p\n", pthread_self() );
   pthread_create( &workerbees[tid], &attr, BeeWork, ( void * ) tid );
   pthread_attr_destroy( &attr );

   sleep( 2 );
   sem_post( &queue.work );
   sleep( 2 );
   pthread_kill( workerbees[tid], SIGUSR1 );
   sleep( 2 );

   // Remove this and sem_wait will stay blocked
   sem_post( &queue.work );
   sleep( 2 );
   return( 0 );
}

我知道信号处理程序中的 printf 并不大声,但只是为了它,如果我删除它,我会得到相同的结果。

这些是没有 sem_post 的结果:

I am pthread 0x7f7fffc00000
Bee: I am pthread 0x7f7ff6c00000
Bee: got RC = 0 and errno = 0
Signal: I am pthread 0x7f7ff6c00000

与 sem_post:

I am pthread 0x7f7fffc00000
Bee: I am pthread 0x7f7ff6c00000
Bee: got RC = 0 and errno = 0
Signal: I am pthread 0x7f7ff6c00000
Bee: got RC = 0 and errno = 4

我知道我并不真的需要解锁并且可以简单地从 main 退出,但我还是希望看到它正常工作。我使用 sem_wait 的原因是因为我想让工作线程保持活动状态,并在 sem_post 从主线程唤醒等待时间最长的线程,一旦有新的客户端连接后缀。我不想一直做 pthread_create,因为我每秒会收到多次呼叫,我不想失去速度并使 Postfix 对新的 smtpd 客户端没有响应。它是 Postfix 的策略守护程序,服务器非常繁忙。

我是不是漏掉了什么?是 NetBSD 搞砸了吗?

我的 post 是关于 Linux 上的行为,但我认为您可能有类似的行为,或者至少我认为可能会有所帮助。如果没有,请告诉我,我会删除这个无用的'noise'。

我试图重现您的设置,看到您所描述的情况我感到非常惊讶。更深入地观察帮助我弄清楚实际上还有更微妙的东西;如果你看看 strace,你会看到类似的东西:

[pid  6984] futex(0x6020e8, FUTEX_WAIT_PRIVATE, 0, NULL <unfinished ...>
[pid  6983] rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
[pid  6983] rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
[pid  6983] rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
[pid  6983] nanosleep({2, 0}, 0x7fffe5794a70) = 0
[pid  6983] tgkill(6983, 6984, SIGUSR1 <unfinished ...>
[pid  6984] <... futex resumed> )       = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
[pid  6983] <... tgkill resumed> )      = 0
[pid  6984] --- SIGUSR1 {si_signo=SIGUSR1, si_code=SI_TKILL, si_pid=6983, si_uid=500} ---
[pid  6983] rt_sigprocmask(SIG_BLOCK, [CHLD],  <unfinished ...>
[pid  6984] rt_sigreturn( <unfinished ...>
[pid  6983] <... rt_sigprocmask resumed> [], 8) = 0
[pid  6984] <... rt_sigreturn resumed> ) = -1 EINTR (Interrupted system call)

查看带有ERESTARTSYSEINTR的行:被中断的系统调用实际上是rt_sigreturn resumed,而不是futex([=32下面的系统调用=]) 如您所料。 我必须说我很困惑,但阅读这个人给出了一些有趣的线索(man 7 信号):

   If  a blocked call to one of the following interfaces is interrupted by
   a signal handler, then the call will be automatically  restarted  after
   the  signal  handler returns if the SA_RESTART flag was used; otherwise
   the call will fail with the error EINTR:
[...]

       * futex(2)  FUTEX_WAIT  (since  Linux  2.6.22;  beforehand,  always
         failed with EINTR).

所以我猜你有一个具有类似行为的内核(请参阅 netBSD 文档?)并且你可以观察到系统调用自动重新启动而你没有机会看到它。

就是说,我从你的程序中完全删除了 sem_post() 并且只是向 'break' 发送信号 sem_wait() ans 看着我看到的 strace(过滤蜜蜂线):

[pid  8309] futex(0x7fffc0470990, FUTEX_WAIT_PRIVATE, 0, NULL <unfinished ...>
[pid  8309] <... futex resumed> )       = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
[pid  8309] --- SIGUSR1 {si_signo=SIGUSR1, si_code=SI_TKILL, si_pid=8308, si_uid=500} ---
[pid  8309] rt_sigreturn()              = -1 EINTR (Interrupted system call)
[pid  8309] madvise(0x7fd5f6019000, 8368128, MADV_DONTNEED) = 0
[pid  8309] _exit(0)

我必须说我不掌握细节,但内核似乎找出了我试图站在哪里并使整个事情有正确的行为:

Bee: got RC = -1 and errno = Interrupted system call

感谢您的回答 OznOg,如果我删除最后一个 sem_post 并使最后一个睡眠时间更长一点,我会用 ktrace 得到这个:

PSIG  SIGUSR1 caught handler=0x40035c mask=(): code=SI_LWP sent by pid=10631, uid=0)
CALL  write(1,0x7f7ff7e04000,0x24)
GIO   fd 1 wrote 36 bytes "Signal: I am pthread 0x7f7ff7800000\n"
RET   write 36/0x24
CALL  setcontext(0x7f7ff7bff970)
RET   setcontext JUSTRETURN
CALL  ___lwp_park50(0,0,0x7f7ff7e01100,0x7f7ff7e01100)
RET   __nanosleep50 0
CALL  exit(0)
RET   ___lwp_park50 -1 errno 4 Interrupted system call

似乎 sem_wait 只会 return 通过出口或 sem_post...