linux---signal

来源:互联网 发布:mt管理器源码 编辑:程序博客网 时间:2024/05/21 12:40
Linux系统学习笔记:信号

主题: Linux系统学习笔记

« Linux系统学习笔记:进程

» Linux系统学习笔记:线程

本篇总结信号。信号是软件中断,它提供了一种处理异步事件的方法。

Contents

  • 信号
    • 信号名字和映射
    • 中断的系统调用
    • 不可重入函数
  • 信号集
  • 发送信号
  • 挂起进程
  • 信号处理
  • 进程同步

信号

前面已经介绍过信号。信号被定义为正整数。很多条件可以产生信号:

  • 用户按某些终端键时引发终端产生信号。
  • 硬件异常产生信号。
  • 进程调用 kill 函数可将信号发送给另一个进程或进程组。发送信号和接收信号的进程的所有者必须相同,或发送信号进程的所有者必须是超级用户。
  • 用户可用 kill 命令发送信号给其他进程。
  • 检测到某种软件条件已经发生并应通知有关进程时也产生信号。

信号是异步事件的经典实例。有三种处理信号的方式:

  • 忽略信号。 SIGKILL 和 SIGSTOP 信号是不能忽略的,它们提供了使进程终止或停止的可靠方法。
  • 捕捉信号。这需通知内核在某种信号发生时调用一个用户函数,用户函数中包含希望对事件进行的处理。 SIGKILL 和 SIGSTOP 信号不能被捕捉。
  • 执行系统默认动作。大多数信号的系统默认动作是终止进程。

执行程序时, exec 函数会将原先设为要捕捉的信号改为执行默认动作。

使用 fork 创建进程时,子进程继承父进程的信号处理方式。

信号名字和映射

有一些办法可以获得信号的名字。使用数组 sys_siglist 可以获得指向信号字符串名字的指针。 psignal类似于 perror ,将有关信号的说明输出到标准错误输出。 strsignal 则返回说明字符串。

#include <signal.h>/* 输出s: sig info\n到标准错误输出 */void psignal(int sig, const char *s);#include <string.h>/* 返回指向描述信号的字符串的指针 */char *strsignal(int sig);

中断的系统调用

系统调用分为低速系统调用和其他系统调用。低速系统调用是可能会使进程永远阻塞的一类系统调用。包括:

  • 读有些类型文件(管道、终端、网络),且数据不存在。
  • 写这些类型文件,不能立即接受数据。
  • 打开某些类型文件,在某种条件发生之前(终端等待)。
  • pause 和 wait 函数。
  • 某些 ioctl 操作。
  • 某些进程间通信函数。

磁盘I/O只会暂时阻塞,所以并非低速系统调用。

早期的UNIX系统中,进程在执行低速系统调用而阻塞期间捕捉到一个信号,则系统调用就被中断不再执行,系统调用返回出错, errno 设置为 EINTR 。现在引入了自动重启动功能,Linux系统默认重启动由信号中断的系统调用。

不可重入函数

进程捕捉到信号时,进程在执行的指令序列就被信号处理程序临时中断,执行完信号处理程序中的指令后,若从信号处理程序返回,则继续执行原来在执行的指令序列。但这可能对进程是具有破坏性的,如正在执行 malloc 函数或一些结果存放在静态变量中的函数时被中断,再返回继续执行时就可能得到错误的结果甚至对进程造成破坏。

大多数函数是不可重入的,有三种原因:

  • 使用了静态数据结构。
  • 调用 malloc 和 free 。
  • 标准I/O函数,它们通常使用不可重入的全局数据结构。

信号集

信号集用来告诉内核不产生该信号集中的信号。下面函数用来处理信号集结构 sigset_t 。

#include <signal.h>/* 初始化set为空集 * @return      成功返回0,出错返回-1 */int sigemptyset(sigset_t *set);/* 初始化set包含所有信号 * @return      成功返回0,出错返回-1 */int sigfillset(sigset_t *set);/* 将signum添加到set中 * @return      成功返回0,出错返回-1 */int sigaddset(sigset_t *set, int signum);/* 从set中删除signum * @return      成功返回0,出错返回-1 */int sigdelset(sigset_t *set, int signum);/* signum在set中返回1,否则返回0,出错返回-1 */int sigismember(const sigset_t *set, int signum);

前面提到进程的信号屏蔽字规定了对进程阻塞的信号集,用 sigprocmask 函数来检测和更改信号屏蔽字。

#include <signal.h>/* 改变进程的信号屏蔽字 * @return      成功返回0,出错返回-1 */int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

参数说明:

how

指定如何修改屏蔽字。

  • SIG_BLOCK :添加 set 中的信号到屏蔽字中。
  • SIG_UNBLOCK :从信号屏蔽字中删除 set 中的信号。
  • SIG_SETMASK :将信号屏蔽字设为 set 。
set
待设信号集,若为空则不改变信号屏蔽字。
oldset
若非空,将当前信号屏蔽字保存在 oldset 中。

sigpending 函数返回当前阻塞信号的信号集。

#include <signal.h>/* 获取当前阻塞信号的信号集 * @return      成功返回0,出错返回-1 */int sigpending(sigset_t *set);

例:

#include <stdio.h>#include <stdlib.h>#include <signal.h>#include "error.h"static void sig_quit(int);int main(void){    sigset_t    newmask, oldmask, pendmask;    if (signal(SIGQUIT, sig_quit) == SIG_ERR)        err_sys("can't catch SIGQUIT");    /* Block SIGQUIT and save current signal mask. */    sigemptyset(&newmask);    sigaddset(&newmask, SIGQUIT);    if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)        err_sys("SIG_BLOCK error");    sleep(5);   /* SIGQUIT here will remain pending */    if (sigpending(&pendmask) < 0)        err_sys("sigpending error");    if (sigismember(&pendmask, SIGQUIT))        printf("\nSIGQUIT pending\n");    /* Reset signal mask which unblocks SIGQUIT. */    if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)        err_sys("SIG_SETMASK error");    printf("SIGQUIT unblocked\n");    sleep(5);   /* SIGQUIT here will terminate */    exit(0);}static void sig_quit(int signo){    printf("caught SIGQUIT\n");    if (signal(SIGQUIT, SIG_DFL) == SIG_ERR)        err_sys("can't reset SIGQUIT");}

发送信号

kill 函数发送信号给进程或进程组, raise 函数发送信号给自己。

#include <sys/types.h>#include <signal.h>/* 发送信号给进程或进程组 * @return      成功返回0,出错返回-1 */int kill(pid_t pid, int sig);#include <signal.h>/* 发送信号给进程或进程组 * @return      成功返回0,出错返回-1 */int raise(int sig);

raise(sig) 相当于 kill(getpid(), sig) 。

pid 参数有四种情况:

  • > 0 时,将信号发送给进程ID等于 pid 的进程。
  • = 0 时,将信号发送给和发送进程在同一进程组的所有进程,且需要具有向这些进程发送信号的权限。
  • < 0 时,将信号发送给进程组ID等于 pid 绝对值的进程,且需要具有向这些进程发送信号的权限。
  • = -1 时,将信号发送给所有进程,且需要具有向这些进程发送信号的权限。

所有进程中不包括系统进程。

关于权限,超级用户可将信号发送给任一进程,非超级用户发送者的实际或有效用户ID须等于接收者的实际或有效用户ID。一个特例是发送信号 SIGCONT 时进程可将它发送给同一会话的所有进程。

0为空信号,做参数时不发送信号。 kill 向不存在的进程发送空信号时返回-1,设置 errno 为 ESRCH 。

alarm 函数可以设置一个闹钟,当闹钟超时时产生 SIGALRM 信号,默认终止调用 alarm 的进程。

#include <unistd.h>/* 设置一个计时器 * @return      0或上次设置的闹钟时间的剩余秒数 */unsigned int alarm(unsigned int seconds);

每个进程只能有一个闹钟。如果调用 alarm 时,有一个未超时的闹钟,则该闹钟被代替或取消(本次调用参数为0时),剩余时间作为本次调用的返回值。

abort 函数可以发送 SIGABRT 信号到调用进程,相当于 raise(SIGABRT) ,它使程序终止。

挂起进程

pause 函数使调用进程挂起直到捕捉到一个信号。

#include <unistd.h>/* 挂起进程 * @return      -1,并设errno为EINTR */int pause(void);

执行了信号处理程序并返回时, pause 才返回。

sigsuspend 函数可以将设置信号屏蔽字和挂起进程组成为一个原子操作,这样就可以正确地实现对某个信号解除阻塞,然后挂起等待以前被阻塞的信号的发生。

#include <signal.h>/* 将进程的信号屏蔽字设为mask,挂起进程,捕捉到信号返回时恢复信号屏蔽字为之前的值 * @return      -1,并设errno为EINTR */int sigsuspend(const sigset_t *mask);

sleep 函数也挂起进程,直到经过指定秒数或捕捉到一个信号并从信号处理程序返回。 usleep 提供微秒级的时间粒度。

#include <unistd.h>/* 挂起进程 * @return      0或剩余秒数 */unsigned int sleep(unsigned int seconds);/* 挂起进程 * @return      成功返回0,出错返回-1 */int usleep(useconds_t usec);

还有一个 nanosleep 函数,它可以提供纳秒级的时间粒度。

#include <time.h>/* 挂起线程 * @return      成功返回0,中断或出错返回-1 */int nanosleep(const struct timespec *req, struct timespec *rem);

req 指定休眠时间, rem 不为 NULL 时被设置为剩余时间。结构 timespec 的定义如下:

struct timespec {    time_t  tv_sec;     /* 秒数 */    long    tv_nsec;    /* 纳秒数 */}

信号处理

sigaction 函数可以检查或修改指定信号的处理动作,它取代了 signal 函数。

#include <signal.h>/* 检查或修改信号的处理动作 * @return      成功返回0,出错返回-1 */int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

若 act 非空,则修改信号的动作;若 oldact 非空,则获取信号的上一个动作。

结构 sigaction 的定义如下:

struct sigaction {    void        (*sa_handler)(int); /* 信号捕捉函数,或SIG_IGN, SIG_DFL */    void        (*sa_sigaction)(int, siginfo_t *, void *); /* 替代动作 */    sigset_t    sa_mask;            /* 额外要阻塞的信号 */    int         sa_flags;           /* 信号选项 */    void        (*sa_restorer)(void);};

在 sa_handler 被调用之前, sa_mask 和 signum 信号被加到信号屏蔽字中,从信号捕捉函数返回时再将信号屏蔽字恢复为原值。

sa_flags 字段的可选项包括:

  • SA_INTERRUPT ,该信号中断的系统调用不会自动重启动。
  • SA_NOCLDSTOP ,对 SIGCHLD 信号,子进程停止或从停止继续时不产生该信号;子进程终止时仍产生该信号。
  • SA_NOCLDWAIT ,对 SIGCHLD 信号,子进程终止时不创建僵死进程。若进程调用 wait ,则进程阻塞,直到所有子进程终止,然后返回-1, errno 设为 ECHILD 。
  • SA_NODEFER ,执行信号捕捉函数时,不阻塞 signum ,除非在 sa_mask 中包含 signum 。
  • SA_ONSTACK ,若用 sigaltstack 函数声明了一替换栈,则将 signum 传递给替换栈上的进程。
  • SA_RESETHAND ,在信号捕捉函数入口处,将 signum 的处理方式复位为 SIG_DFL ,并清除SA_SIGINFO 标志,但不能复位 SIGILL 和 SIGTRAP 的配置。
  • SA_RESTART ,该信号中断的系统调用会自动重启动。
  • SA_SIGINFO ,对信号处理程序提供附加信息:指向 siginfo 结构的指针和指向进程上下文标识符的指针。

使用了 SA_SIGINFO 选项时,使用替代的 sa_sigaction 信号捕捉函数。 siginfo_t 结构的定义参考手册。

可以用 sigaction 函数实现 signal 函数:

sighandler_t signal(int signum, sighandler_t handler){    struct sigaction action, oldaction;    action.sa_handler = handler;    sigemptyset(&action.sa_mask);    action.sa_flags = SA_RESTART;    if (sigaction(signum, &action, &oldaction) < 0)        return SIG_ERR;    return (oldaction.sa_handler);}

进程同步

可以用信号实现父子进程间的同步。

下面是用信号解决竞争条件的版本。 SIGUSR1 由父进程发送给子进程, SIGUSR2 由子进程发送给父进程。这个版本适合在等待信号时休眠,如果在等待信号时希望调用其他系统函数,一般需要使用多线程。

static volatile sig_atomic_t sigflag; /* set nonzero by sig handler */static sigset_t newmask, oldmask, zeromask;static void sig_usr(int signo)  /* one signal handler for SIGUSR1 and SIGUSR2 */{    sigflag = 1;}void TELL_WAIT(void){    if (signal(SIGUSR1, sig_usr) == SIG_ERR)        err_sys("signal(SIGUSR1) error");    if (signal(SIGUSR2, sig_usr) == SIG_ERR)        err_sys("signal(SIGUSR2) error");    sigemptyset(&zeromask);    sigemptyset(&newmask);    sigaddset(&newmask, SIGUSR1);    sigaddset(&newmask, SIGUSR2);    /* Block SIGUSR1 and SIGUSR2, and save current signal mask. */    if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)        err_sys("SIG_BLOCK error");}void TELL_PARENT(pid_t pid){    kill(pid, SIGUSR2);     /* tell parent we're done */}void WAIT_PARENT(void){    while (sigflag == 0)        sigsuspend(&zeromask);  /* and wait for parent */    sigflag = 0;    /* Reset signal mask to original value. */    if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)        err_sys("SIG_SETMASK error");}void TELL_CHILD(pid_t pid){    kill(pid, SIGUSR1);         /* tell child we're done */}void WAIT_CHILD(void){    while (sigflag == 0)        sigsuspend(&zeromask);  /* and wait for child */    sigflag = 0;    /* Reset signal mask to original value. */    if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)        err_sys("SIG_SETMASK error");}
0 0