Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1604165
  • 博文数量: 695
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 4027
  • 用 户 组: 普通用户
  • 注册时间: 2013-11-20 21:22
文章分类

全部博文(695)

文章存档

2018年(18)

2017年(74)

2016年(170)

2015年(102)

2014年(276)

2013年(55)

分类: LINUX

2013-12-16 17:01:56

原文地址:Linux下的信号 作者:soqsoq

2009.12

 

【摘要】

信号。

【关键词】

信号。

一、问题的提出

主要介绍有关信号的问题。

二、解决思路

A) 信号的处理主要包括:

1)忽略此信号。大多数信号都可使用这种方式进行处理,但有两种信号却不能被忽略。它们是SIGKILLSIGSTOP。这两种信号不能被忽略的原因是:它们向超级用户提供了使进程终止或停止的可靠方法。

2)捕捉信号。不能捕捉SIGKILLSIGSTOP信号。

3)执行系统默认动作。针对大多数信号的系统默认动作是终止进程。

B) Signal函数

Void (*signal(int signo, void (*func)(int)))(int)

  func的值是常量SIG_IGN、常量SIG_DFL或当接到此信号后要调用的函数的地址。如果指定SIG_IGN,则向内核表示忽略此信号。如果指定SIG_DFL,则表示接到此信号后的动作是系统默认动作。当指定函数地址是,则在信号发生时,调用该函数,我们称这种处理为“铺张”该信号。称此函数为信号处理程序或信号捕捉函数。

  Signal函数原型说明此函数需要两个参数,返回一个函数指针,而该函数指针指向的函数无返回值。第一个参数signo是一个整数,第二个参数是函数指针,它所指向的函数需要一个整型参数,无返回值。Signal的返回值是一个函数指针,指向之前的信号处理程序。信号处理函数的参数就是触发的信号。

例如

static void sig_usr(int);

int main(void)

{

  if(signal(SIGUSR1, sig_usr) == SIG_ERR)

   Err_sys(”cann’t catch SIGUSR1”);

  If(signal(SIGUSR2, sig_usr) == SIG_ERR)

   Err_sys(”cann’t catch SIGUSR2”);

  For(;;)

    Pause();

}

Static void sig_usr(int signo)

{

   If(signo == SIGUSR1)

     Printf(”received SIGUSR1\n”);

   Else if(signo == SIGUSR2)

     Printf(“received SIGUSR2\n”);

   Else

     Err_dump(“received signal %d\n”, signo);

}

1.程序启动

 当执行一个程序时,所有信号的状态都是系统默认或忽略。通常所有信号都被设置为它们的默认动作,除非调用exec的进程忽略该信号。确切的讲,exec函数讲原来设置为要捕捉的信号都更改为它们的默认动作,其它信号的状态则不变。

 一个具体的例子是一个交互式shell如何处理针对后台进程的中断和退出信号。对于一个非作业控制shell,当在后台执行一个进程时,例如:cc main.c & shell自动讲后台进程对中断和退出信号的处理方式设置为忽略。于是,当按中断键时,就不会影响到后台进程。如果没有执行这样的处理,那么当按中断键时,它不但会终止前台进程,还会终止所有后台进程。

2.进程创建

当一个进程调用fork时,其子进程继承父进程的信号处理方式。因为子进程在开始时复制了父进程的存储映像,所以信号捕捉函数的地址在子进程中是有意义的。

C) 不可靠信号

   不可靠的是指:信号可能会丢失,一个信号发生了,但进程却可能一直不知道这一点。同时,进程对信号的控制能力也很差,它能捕捉信号或忽略它。

   早期版本中的一个问题是在进程每次接到信号对其进行处理时,随即讲该信号动作复位为默认值。

 另一个问题是在进程不希望某种信号发生时,它不能关闭该信号。进程能做的一切就是忽略该信号。

D) 中断系统调用

    早期系统的一个特点是:如果进程在执行一个低速系统调用而阻塞期间捕捉到一个信号,则该系统调用就被中断不再继续执行。该系统调用返回出错,其errno被设置为EINTR。这样处理的理由是因为一个信号发生了,进程捕捉到了它,这意味着已经发生了某种事情,所以是个应当唤醒阻塞的系统调用的好机会。

    系统调用分成两类:低速系统调用和其它系统调用。低速系统调用是可能会使进程永远阻塞的一类系统调用。

可以被中断系统调用这种处理方法来处理的一个例子是:一个进程启动了读中断操作,而使用该终端设备的用户却离开该终端很长时间。在这种情况下进程可能处于阻塞状态几个小时甚至数天,除非系统停机,否则一直如此。

为了帮助应用程序使其不必处理被中断的系统调用,4.2BSD引入了某些被中断系统调用的自动重启动。自动重启动的系统调用包括ioctlreadreadvwritewritevwaitwaitpid。其中前5个函数只有对低速设备进行操作时才被中断。而waitwaitpid在捕捉到信号时总是被中断。因为这种自动重启动的处理方式也会带来问题,所以某些应用程序并不希望这些函数被中断后重启动。为此4.3BSD允许进程基于每个信号禁用此功能。

E) 可重入函数

    进程捕捉到信号并对其进行处理时,进程正在执行的指令序列就被信号处理程序临时中断,它首先执行该信号处理程序中的指令。如果从信号处理函数返回(例如没有调用exitlongjmp),则继续执行在捕捉到信号时进程正在执行的正常指令序列。但在信号处理程序中,不能判断捕捉到信号时进程在何处执行。如果进程正在执行malloc,在其堆中分配另外的存储空间,而此时由于捕捉到信号而插入执行该信号处理程序,其中又调用malloc,这时会发生什么?又例如若进程正在执行getpwnam这种将其结果存放在静态存储单元中的函数,其间插入执行信号处理程序,它又调用这样的函数,这时又会发生什么呢?malloc例子中,可能会对进程造成破坏,因为malloc通常为它所分配的存储区维护一个连接表,而插入执行信号处理程序时,进程可能正在更改此链接表。在getpwnam的例子中,返回给正常调用者的信息可能被返回给信号处理程序的信息覆盖。

    可重入函数是很少的,大多数是不可重入的,其原因为:1.已知它们使用静态数据结构2.它们调用mallocfree3.它们是标准I/O函数。标准I/O库的很多实现都以不可重入的方式使用全局数据结构。

    由于每个线程只有一个errno变量,所以信号处理程序可能会修改其原先值。例如,如果该信号处理程序调用read这类函数,则它可能更改errno的值,从而取代了刚刚由main设置的值。因此,作为一个通用的规则,当在信号处理程序中调用上表列出的函数时,应当在其前保存,在其后恢复errno表中没有longjmpsiglongjmp,这是因为主例程以非可重入方式正在更改数据结构时可能产生信号。如果不是从信号处理程序返回而是调用siglongjmp,那么该数据结构可能是部分更新的。如果应用程序将要做更改全局数据结构这样的事情,同时要捕捉某些信号,而这些信号的处理程序又会引起执行sigsetjmp,则在更新这种数据结构时要阻塞此类信号。

    若在信号处理程序中调用一个不可重入函数,则其结果是不可预见的。

G) 可靠信号术语和语义

    在信号产生和递送之间的时间间隔内,称信号是未决的。

    进程可以选用信号递送阻塞。如果为进程产生了一个选择为阻塞的信号,而且对该信号的动作是系统默认的动作或捕捉该信号,则为该进程将此信号保持为未决状态,知道该进程a对此信号解除了阻塞,或者b将对此信号的动作更改为忽略。

    如果在进程解除对某个信号的阻塞之前,这种信号发生了多次,除非支持posix.1实时扩展,否则大多数并不对信号进行排队,代之以内核只递送这种信号一次。

    如果有多个信号要递送给一个进程,posix.1并没有规定这种信号的递送顺序。但是posix.1rationale建议:在其它信号之前递送与进程当前状态有关的信号,例如SIGSEGV

H) killraise函数

    Int kill(pid_t pid, int signo);

    Int raise(int signo);

    Kill函数将信号发送给进程或进程组。Raise函数则允许进程向自身发送信号。

    Killpid单数有4中不同的情况:

    pid > 0  将该信号发送给进程IDpid的进程

    pid == 0  将该信号发送给与发送进程属于同一进程组的所有进程,而且发送进程具有向这些进程发送信号的权限。

    pid < 0  将该信号发送给其进程组ID等于pid的绝对值,而且发送进程具有向其发送信号的权限。

    pid == -1 将该信号发送给发送进程有权限向它们发送信号的系统上的所有进程。

    进程将信号发送给其他进程需要权限。超级用户可将信号发送给任一进程。对于非超级用户,其基本规则是发送者的实际或有效用户ID必须等于接收者的实际或有效用户ID

    如果signo参数是0,则kill仍执行正常的错误检查,但不发送信号。这常被用来确定一个特定进程是否仍旧存在。如果向一个并不存在的进程发送空信号,则kill返回-1,并将errno设置为ESRCH

I) alarmpause函数

    Unsigned int alarm(unsigned int seconds);

    使用alarm函数可以设置一个计时器,在将来某个指定的时间该计时器会超时。当计时器超时时,产生SIGALRM信号。如果不忽略或不捕获此信号,则其默认动作是终止调用该alarm函数的进程。

    每个进程只能有一个闹钟时钟。如果在调用alarm时,以前已为该进程设置过闹钟时钟,而且它还没有超时,则将该闹钟时钟的余留值作为本次alarm函数调用的返回值。以前登记的闹钟时钟则被新值代替。

    如果有以前为进程登记的尚未超时的闹钟时钟,而且本次调用的seconds的值为0,则取消以前的闹钟时钟,其余留值作为alarm函数的返回值。

    int pause(void);

    pause函数使调用进程挂起直至捕捉到一个信号。只有执行了一个信号处理函数程序并从其返回时,pause才返回。在这种情况下,pause返回-1,并将errno设置为EINTR

J) 信号集

    Int sigemptyset(sigset_t *set);

    Int sigfillset(sigset_t *set);

    Int sigaddset(sigset_t *set, int signo);

    Int sigdelset(sigset_t *set, int signo);

    Int sigismember(const sigset_t *set, int signo);

    函数sigemptyset初始化由set指向的信号集。

    函数sigfillset初始化由set指向的信号集,使其包括所有信号。

    函数sigaddset将一个信号添加到现有集中,sigdelset则从信号集中删除一个信号。

    函数sigismember查询信号是否在信号集合之中。

K) sigpromask函数

    Int sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);

    调用sigprocmask可以检测或更改其信号屏蔽字,或者在一个步骤中同时执行这两个动作。

    oset是非空指针,那么进程的当前信号屏蔽字通过oset返回。若set是一个非空指针,则参数how指示如何修改当前的屏蔽字。

     SIG_BLOCK:该进程新的信号屏蔽字是其当前信号屏蔽字和set指向信号集的并集。Set包含我们希望阻塞的附加信号。

     SIG_UNBLOKC:该进程新的信号屏蔽字是当前信号屏蔽字和set所指向信号集补集的交集。Set包含了我们希望阻塞的信号。

     SIG_SETMASK:该进程新的信号屏蔽字将被set指向的信号集的值代替。

L) sigpending函数

    int sigpending(sigset_t *set);

    sigpending函数返回信号集,其中的各个信号对于调用进程是阻塞的而不能递送,因而也一定是当前未决的。该信号集通过set参数返回。

M) sigaction函数

    Int sigaction(int signo, const struct sigaction *restrict act, struct sigaction *restrict oact);

    Sigaction函数的功能是检测或修改与指定信号相关联的处理动作。

    其中参数signo是检测或修改其具体动作的信号编号。若act指针非空,则要修改其动作。如果oact指针非空,则系统经由oact指针返回该信号的上一个动作。

    Struct sigaction

    {

      Void (*sa_handler)(int);

      Sigset_t sa_mask;

      Int sa_flags;

      Void  (*sa_sigaction)(int, siginfo_t *, void *);

}

当更改信号灯动作时,如果sa_handler字段包含一个信号捕捉函数地址(与常量SIG_IGNSIG_DFL相对),sa_mask字段说明了一个信号集,在调用该信号捕捉函数之前,这一信号集要加到进程的信号屏蔽字中。仅当从信号捕捉函数返回时再将进程的信号屏蔽字复位为原先值这样,在调用信号处理程序时就能阻塞某些信号。在信号处理程序被调用时,操作系统建立的新信号屏蔽字段包含正被递送的信号。因此保证了在处理一个给定信号时,如果这种信号再次发生,那么它会被阻塞到对前一个信号的处理结束为止。若同一种信号多次发生,通常并不将它们排队,所以如果在某种信号被阻塞时它发生了五次,那么它对这种信号解除阻塞后,其信号处理函数通常只会被调用一次。

act结构的sa_flags字段指定对信号进行处理的各个选项。

SA_INTERUPT:由此信号中断的系统调用不会自动重启。

SA_NOCLDSTOP:若signoSIGCHLD,当子进程停止时,不产生次信号。当子进程终止时,仍旧产生此信号。若已设置此标志,则当停止的进程继续运行时,作为XSI扩展,不发送SIGCHLD信号。

SA_NOCLDWAIT:若signoSIGCHLD,则当调用进程的子进程终止时,不创建僵死进程。若调用进程在后面调用wait,则调用进程阻塞,直到其所有子进程都终止,此时返回-1,并将errno设置为ECHILD

SA_NODEFER:当捕捉到此信号时,在执行其信号捕捉函数时,系统不自动阻塞该信号(除非sa_mask包括此信号)。注意,此种类型操作对应于早期的不可靠信号。

SA_ONSTACK:若用sigaltstack2)声明一个替换栈,则将此信号递送给替换栈上的进程。

SA_RESETHAND:在此信号捕捉函数的入口,将此信号的处理方式复位为SIG_DFL,并清除SA_SIGINFO标志。注意,此种信号类型对应于早期的不可靠信号。但是,不能自动复位SIGILLSIGTRAP这两个信号的配置。设置此标志是sigaction的行为如同SA_NODEFER标志也设置一样。

SA_RESTART:由此信号中断的系统调用会自动重启动。

SA_SIGINFO:此选项对信号处理程序提供了附加信息:一个指向siginfo结构的指针以及一个指向进程上下文标识符的指针。

sa_sigaction字段是一个替代的信号处理函数,当在sigaction结构中使用了SA_SIGINFO标志时,使用该信号处理程序。对于sa_sigation字段和sa_handler字段这两者,其实现可能使用同一存储区,所以应用程序只能一次使用这两个字段中的一个。

Siginfo_t结构包含了信号产生原因的有关信息。

struct siginfo

{

  Int si_signo;

  Int si_errno;

  Int si_code ;

  Pid_t si_pid ;

  Uid_t si_uid ;

Void *si_addr ;

Int  si_status ;

Long si_band ;

}

N) sigsetjmpsiglongjmp函数

    调用longjmp有一个问题。当捕捉到一个信号时,进入信号捕捉函数,此时当前信号被自动的加到进程的屏蔽字中。这阻止了后来产生的这种信号中断该信号处理程序。

    Int sigsetjmp(sigjmp_buf env, int savemask);

    Void siglongjmp(sigjmp_buf env, int val);

    这两个函数与setjmplongjmp之间的唯一区别是sigsetjmp增加了一个参数。如果savemask0,则segsetjmpenv中保存进程的当前信号屏蔽字。调用siglongjmp时,如果带非0savemasksigsetjmp调用已经保存了env,则siglongjmp从其中恢复保存的信号屏蔽字。

#include

#include

#include

#include

#include

#include

 

 

static void sig_usr1(int), sig_alrm(int);

static sigjmp_buf jmpbuf;

static volatile sig_atomic_t canjump;

 

void err_sys(char *str)

{

        printf("%s", str);

        exit(0);

}

 

 

void pr_mask(const char *str)

{

  sigset_t sigset;

  int errno_save;

 

  errno_save = errno;

  if(sigprocmask(0, NULL,&sigset)<0)

     err_sys("sigpromask error");

  printf("%s", str);

  if(sigismember(&sigset, SIGINT))

    printf("SIGINT ");

  if(sigismember(&sigset, SIGQUIT))

    printf("SIGQUIT ");

  if(sigismember(&sigset, SIGUSR1))

    printf("SIGUSR1 ");

  if(sigismember(&sigset, SIGALRM))

    printf("SIGALRM ");

  printf("\n");

  errno = errno_save;    

}

 

int main(void)

{

  if(signal(SIGUSR1, sig_usr1) == SIG_ERR)

    err_sys("signal(SIGUSR1) error");

  if(signal(SIGALRM, sig_alrm) == SIG_ERR)

    err_sys("signal(SIGALRM) error");

  pr_mask("starting main:");

  if(sigsetjmp(jmpbuf, 1))

  {

    pr_mask("ending main:");

    exit(0);

  }   

  canjump = 1;

 

  for(;;)

   pause();

}

 

static void sig_usr1(int signo)

{

  time_t starttime;

 

  if(canjump == 0)

  {

    return;

  }

  pr_mask("starting sig_usr1:");

  alarm(3);

  starttime = time(NULL);

  for(;;)

    if(time(NULL) > starttime+5)

      break;

  pr_mask("finishing sig_usr1:");

  canjump = 0;

  siglongjmp(jmpbuf, 1);   

}

 

static void sig_alrm(int signo)

{

  pr_mask("in sig_alrm:");

}

运行该程序:

[root@localhost home]# starting sig_usr1:SIGUSR1

in sig_alrm:SIGUSR1 SIGALRM

finishing sig_usr1:SIGUSR1

ending main:

 

[4]+  Done                    ./siglongjmp

如果将if(sigsetjmp(jmpbuf, 1))改为if(sigsetjmp(jmpbuf, 0)),或者改为if(setjmp(jmpbuf)),运行该程序

[root@localhost home]# ./siglongjmp &

[4] 1126

[root@localhost home]# starting main:

kill -USR1 1126

[root@localhost home]# starting sig_usr1:SIGUSR1

in sig_alrm:SIGUSR1 SIGALRM

finishing sig_usr1:SIGUSR1

ending main:SIGUSR1

 

[4]+  Done                    ./siglongjmp

可见程序if(sigsetjmp(jmpbuf, 1))的实现是恢复原来的信号屏蔽字,而if(sigsetjmp(jmpbuf, 0))if(setjmp(jmpbuf))信号屏蔽字保留了捕捉到的信号。

O) sigsuspend函数

    Int sigsuspend(const sigset_t *sigmask);

    该函数实现了对一个信号解除阻塞,然后一pause等待以前被阻塞的信号发生。它是把这两个动作在一个系统调用中完成,保证了解除阻塞和pause之间不会被外界打断。

    将进程的信号屏蔽字设置为有sigmask指向的值。在捕捉到一个信号或发生了一个会终止该进程的信号之前,该进程被挂起。如果捕捉到一个信号而且从该信号处理程序返回,则sigsuspend返回,并且将该进程的信号屏蔽字设置为调用sigsuspend之前的值。

Pabort函数

    Void abort(void);

    Abort函数的功能是使异常程序终止。此函数将SIGABRT信号发送给调用进程。

   void abort(void)

{

  sigset_t mask;

  struct sigaction action;

 

  sigaction(SIGABRT, NULL, &action);

  if(action.sa_handler == SIG_IGN)

  {

    action.sa_handler = SIG_DFL;

    sigaction(SIGABRT, &action, NULL);

  }

  if(action.sa_handler == SIG_DFL)

  {

    fflush(NULL);

  }

 

  sigfillset(&mask);

  sigdelset(&mask, SIGABRT);

  sigpromask(SIG_SETMASK, &mask, NULL);

  kill(getpid(), SIGABRT);

 

  fflush(NULL);

  action.sa_handler = SIG_DFL;

  sigaction(SIGABRT, &action, NULL);

  sigpromask(SIG_SETMASK, &mask, NULL);

  kill(getpid(), SIGABRT);

  exit(1);

}

 

Rsleep函数

    Unsigned int sleep(unsigned int seconds);

    此函数使进程被挂起,直到满足以下条件之一:

    1)已经过了seconds所指定的墙上时钟时间。

    2)调用进程捕捉到一个信号并从信号处理函数返回。

    在第一种情形下,返回0.当由于捕捉到某个信号sleep提早返回时,返回值是未睡够的秒数。

static void sig_alrm(int signo)

{

      

}

 

 

unsigned int sleep(unsigned int nsecs)

{

  struct sigaction newact, oldact;

  sigset_t newmask, oldmask, suspmask;

  unsigned int unslept;

 

  newact.sa_handler = sig_alrm;

  sigemptyset(&newact.sa_mask);

  newact.sa_flags = 0;

  sigcation(SIGALRM, &newact, &oldact);

 

  sigemptyset(&newmask);

  sigaddset(&newmask, SIGALRM);

  sigprocmask(SIG_BLOCK, &newmask, &oldmask);

 

  alarm(nsecs);

 

  suspmask = oldmask;

  sigdelset(&suspmask, SIGALRM);

  sigsuspend(&suspmask);

 

  unslept = alarm(0);

  sigaction(SIGALRM, &oldact, NULL);

 

  sigprocmask(SIG_SETMASK, &oldmask, NULL);

  return (unslept);

 

}

阅读(521) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~