发信人: tcpip (俺的昵称改了), 信区: cnunix
标  题: Signal Starvation
发信站: 哈工大紫丁香 (Sun Sep 26 15:23:04 1999), 转信

发信人: digger (欧阳疯), 信区: Solaris
发信站: 华南网木棉站 (Wed Aug  5 19:29:14 1998), 转信

                          Signal Starvation      
                          ------------------  Thamer Al-Herbish
                                              (shadows@whitefang.com)


  UNIX signals are probably the dirtiest facility it provides. With
POSIX signals you know that your signals can be caught one at a time with 
sigsuspend(), and you dont receive them in any order. These two
characteristics can manifest themselves in such a way, that signal
"starvation" can occur.

  Since the operating system is free to not keep track of the order of the
signals, and only needs to return one in a sigsuspend() situation, the
following code could be used within the kernel.

  We're going to use 3 signals, SIGNO1 to SIGNO3 each one of them are
represented as 1 bit in a octet. We'll assume an unsigned char will have 8
bits which are easily accessible, for the purpose of this paper.

#define SIGNO1 0x01
#define SIGNO2 0x02
#define SIGNO3 0x04

First we'll have a routine to add the signal to the currently pending signals.

void add_sig(unsigned char sigholder,unsigned char sig)
{
  sigholder |= sig;
  return;
}

As you'll note this is an efficient way to store a pending signal.

We'll have one to check for current signals and return the first one it finds.

unsigned char check_sig(unsigned char sigholder)
{
  if(sigholder&SIGNO1)
    return SIGNO1;
  if(sigholder&SIGNO2)
    return SIGNO2;
  if(sigholder&SIGNO3)
    return SIGNO3;
    /* .... */
}

  The check is a basic run through the different signals which can occur,
and a return with the value if it exists.

  Now stipulate that our program was a very event driven one. Which used
signals to wake itself up before doing a task. It would perform its
task only when a signal occurs, and perform certain tasks for certain
signals.

void wait_for_signals(void)
{
  /* .. code .. */
  while(1)  {
    sigsuspend(..);
  }
  /* ... */
}

  By installing handlers for the signals, one would think each signal
would be taken care of as it occurred. And with POSIX we can block all
other signals while we handle an individual signal.

(Note: without the old UNIX signal race conditions, since we're using POSIX
 signals).

  If SIGNO1 occurred, we'd handle it. While handling it, SIGNO1 occurred again,
and SIGNO2 occurred. The next time we sigsuspend() to catch a signal which
signal would we be awakened with? SIGNO1 would wake us again, and SIGNO2
would remain undetected as long as SIGNO1 is constantly being generated.

  Thus SIGNO1 if generated enough times, and fast enough to always be there
when we call sigsuspend(), it will pretty much take over our program.

  This is called signal starvation, since the other signals are starved away
from being detected by a more "prioritized" signal.

The following program will perform an acid test on your chosen OS, and attempt
to see if signal starvation is possible:

#include <stdio.h>
#include <stdlib.h>

#include <sys/types.h>
#include <signal.h>

#define TRIES 3

unsigned char sig1 = 0;
unsigned char sig2 = 0;
unsigned char sig3 = 0;

void handle_sig1(int i) { sig1++; return; }
void handle_sig2(int i) { sig2++; return; }
void handle_sig3(int i) { sig3++; return; }

int main()
{
  sigset_t blocked, unblocked;
  struct sigaction handler;
  int i;
    
  sigemptyset(&blocked);
  sigemptyset(&unblocked);
  
  sigaddset(&blocked,SIGINT);
  sigaddset(&blocked,SIGUSR1);
  sigaddset(&blocked,SIGUSR2);

  sigprocmask(SIG_SETMASK,&blocked,NULL);

  handler.sa_mask = blocked;
  handler.sa_flags = 0;
  
  handler.sa_handler = &handle_sig1;
  sigaction(SIGINT,&handler,NULL);  

  handler.sa_handler = &handle_sig2;
  sigaction(SIGUSR1,&handler,NULL);
  
  handler.sa_handler = &handle_sig3;
  sigaction(SIGUSR2,&handler,NULL);

  for(i = 0;i < TRIES;i++)  {
    raise(SIGINT);
    raise(SIGUSR1);
    raise(SIGUSR2);
    sigsuspend(&unblocked);
  }
  
  printf("Signal starvation test complete:\n");
  printf("Each of the 3 signals were raised %d times.\n",TRIES);
  
  if(sig1 == TRIES || sig2 == TRIES || sig3 == TRIES)
    printf("A signal was prioritized over the rest.\n");
  else
    printf("No signal starvation occurred\n");

  return;
}

The check was made on:

  FreeBSD-2.2.1-RELEASE, SCO OpenServer, Solaris 2.5 (Sparc), and starvation
occurred in all of them.

  There is a workaround. After a call to sigsuspend(), one can use sigpending
to find any other signals which may have occurred, handle them manualy, and
then set them to SIG_IGN which will clear the pending signal, and reset it
back to the handler before calling sigsuspend() again. This does waste
resources, but not much compared to the havoc signal starvation can inflict
on your program.

  The workaround for OS developers is to use a more intelligent way of keeping
track of signals, a linked list would do wonders, but searching a linked
list can be slower, and it would have to be searched for each signal to only
be added once. Although the "order" a linked list can keep, will eleviate
the problem.

Joe Foster has subsequently contacted me and mentioned another solution. One
could keep the value of the last signal caught to act as an excluder when
searching for pending signals again. Basically if the value of the signal
was 4, you would start checking at 5 untill you wrap around to the signal
once more. This would prevent the same signal from reoccuring twice in a
row, unless it was the only pending signal.

--
                ####################
                #  遥  眺  淑  女  #
                #######################################
                                   #  君  子  好  球  #
                                   ####################
※ 修改:.trueip 于 Sep 26 15:26:48 修改本文.[FROM: dns.mtlab.hit.ed]
--
※ 转寄:.华南网木棉站 bbs.gznet.edu.cn.[FROM: dns.mtlab.hit.ed]

--
☆ 来源:.哈工大紫丁香 bbs.hit.edu.cn.[FROM: trueip.bbs@melon.gzn]
[百宝箱] [返回首页] [上级目录] [根目录] [返回顶部] [刷新] [返回]
Powered by KBS BBS 2.0 (http://dev.kcn.cn)
页面执行时间:3.805毫秒