信号基础

信号概念

信号是异步事件的经典实例,产生信号的事件对进程而言是随机出现的。进程不能简单地测试一个变量来判断是否发生了一个信号,而是必须告诉内核"在此信号发生时,请执行下列操作"。

某个信号出现时,可以告诉内核按照下列3种方式处理:

  • 忽略信号
    SIGKILL和SIGSTOP不能被忽略,其他信号可以用signal(signo, SIG_IGN)表示忽略信号。

  • 捕捉信号
    调用一个信号处理函数。

  • 执行系统默认动作
    大多数系统默认动作是终止该进程。可以用signal(signo, SIG_DFL)表示执行系统默认对该信号的动作。

  • 信号说明

    名字 说明 默认动作
    SIGABRT 异常终止 终止+core
    SIGCHLD 子进程终止 忽略
  • SIGSEGV
    说明进程进行了一次无效的内存引用,比如访问了一个未经初始化的指针。
键盘快捷键发送信号
  • ctrl-c: ( kill foreground process ) 发送 SIGINT 信号给前台进程组中的所有进程,强制终止程序的执行;

  • ctrl-z: ( suspend foreground process ) 发送 SIGTSTP 信号给前台进程组中的所有进程,常用于挂起一个进程,而并非结束进程,用户可以使用使用fg/bg操作恢复执行前台或后台的进程。fg命令在前台恢复执行被挂起的进程,此时可以使用ctrl-z再次挂起该进程,bg命令在后台恢复执行被挂起的进程,而此时将无法使用ctrl-z再次挂起该进程;一个比较常用的功能:正在使用vi编辑一个文件时,需要执行shell命令查询一些需要的信息,可以使用ctrl-z挂起vi,等执行完shell命令后再使用fg恢复vi继续编辑你的文件(当然,也可以在vi中使用!command方式执行shell命令,但是没有该方法方便)。

  • ctrl-d: ( Terminate input, or exit shell ) 一个特殊的二进制值,表示 EOF,作用相当于在终端中输入exit后回车;
    -ctrl-/ 发送 SIGQUIT 信号给前台进程组中的所有进程,终止前台进程并生成 core 文件

  • ctrl-s 中断控制台输出

  • ctrl-q 恢复控制台输出

  • ctrl-l 清屏

其实,控制字符都是可以通过stty命令更改的,可在终端中输入命令"stty -a"查看终端配置

函数signal
#include 

void (*signal(int signo, void (*func)(int)))(int);
    函数返回值:成功返回以前信号处理函数的指针,出错返回SIG_ERR

#define SIG_ERR (void (*)())-1
#define SIG_ERR (void (*)())0  // 默认
#define SIG_ERR (void (*)())1  // 忽略
中断的系统调用

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

为了支持这一特性,将系统调用分为2类:低速的系统调用和其他系统调用。低速系统调用是指可能会使进程永远阻塞的一类系统调用。包括:

  • 某些类型文件按不存在(如管道,终端设备和网络设备)的数据不存在。则读操作可能会使调用者永久阻塞

与被中断的系统调用相关的是必须显示地处理出错并返回。

again:
    if ( (n = read(fd, buf, BUFSIZE)) < 0)
    {
        if (errno == EINTR)
            goto again;
        /*  处理其他错误 */  
    }

函数kill和raise

kill
#include 

int kill(pid_t pid, int signo);
    函数返回值:成功返回0, 出错返回-1

kill的pid参数有以下4种情况:
a pid > 0  将该信号发送给进程ID为pid的进程
b pid == 0 将该信号发送给与发送
c pid < 0  将该信号发送给进程组ID等于pid绝对值。
d pid == -1  将该信号发送给进程有权限向他们发送信号的所有进程。

kill 将信号发送给进程或者进程组。

信号0定义为空信号。kill可以发送信号0用来确定进程是否存在,如果向一个并不存在的进程发送空信号,则 kill返回-1,errno被设置为ESRCH。

rasize
#include 

int raise(int signo);
    函数返回值:成功返回0, 出错返回-1

raise函数允许进程向自身发送信号。

函数alarm和pause

alarm
#include 
unsigned int alarm(unsigned int seconds)
    返回值:0或以前设置的闹钟时间的预留秒数

alarm用来设置一个定时器,当定时器超时,会给调用进程产生一个SIGALRM信号。

每个进程只能有一个闹钟时间。如果在调用alarm时,之前已为该就弄成注册的闹钟时间还没有超时,则该闹钟时间的余留值作为本次alarm调用返回值。以前注册的闹钟时间则被新值代替。

如有以前注册的尚未超过闹钟时间,而且本次调用的seconds值是0,则取消以前的闹钟时间,则余留值仍作为alarm函数的返回值。

pause
#include 

int pause();
    返回值:-1,errno设置为EINTR

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

信号集

不可靠信号与可靠信号

在早期的UNIX版本中,信号是不可靠的,不可靠这里指的是:信号可能会丢失,一个信号发生了,但进程却可能一直不知道这一点。同时,进程对信号的控制能力也很差。它能捕捉信号或忽略它。但是有有时用户希望通知内核阻塞某个信号,不要忽该信号。在其发生时记住它。然后再进程做好了准备时再通知它。

当一个信号产生时,内核通常在进程表中以某种形式设置了一个标志。当对信号采取这种动作时,我们说向进程递送了一个信号。在信号产生和递送之间的时间间隔内,称信号是未决的。

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

每个进程都有一个信号屏蔽字,它规定了当前要阻塞递送到该进程的信号集。
sigset_t 信号集,用来表示多个信号的数据类型。

#include 

int sigemptyset(sigset_t *set);    // 清楚所有
int sigfillset(sigset_t *set);     //  包括所有
int sigaddset(sigset_t *set, int signo);    // 添加
int sigdelset(sigset_t *set, int signo);     // 删除
    4个函数返回值:成功返回0, 出错返回-1

int sigismmeber(const sigset_t* set, int signo)
    若真,返回1,若假,返回-1

函数sigprocmask

#include 
int sigprocmask(int how,  const sigset_t *restrict set,  sigset_t *restrict oset)
    返回值:成功返回0。出错返回-1

前面提到了一个进程的信号屏蔽字。函数sigprocmask可以检测或更改。

  • 检测
    如果oset是非空指针,那么进程当前屏蔽字通过oset返回,此时参数how设置为0

  • 修改
    如果set是一个非空指针,则参数how指示如何修改当前信号屏蔽字。

    名字 说明
    SIG_BLOCK 该进程新的信号屏蔽字是其当前信号屏蔽字和set指向信号集的并集。set包含了希望阻塞的附 加信号
    SIG_ UNBLOCK 该进程新的信号屏蔽字是其当前信号屏蔽字减去set指向信号集的并集。set包含了希望解除的阻塞附加信号
    SIG_ SETMASK 该进程新的信号屏蔽字是set信号集

函数sigpending

#include 

int sigpend(sigset_t *set)
    返回值:成功返回0;出错返回-1

sigpend函数返回一信号集,对于调用进程而言,其中的各信号是阻塞不能递送的。所以一定是当前未决信号的集合。

static void sig_quit(int signo) {
    printf("\ncaught SIGQUIT\n");
    if (signal(SIGQUIT, SIG_DFL) == SIG_ERR)
        err_sys("can't reset SIGQUIT");
}

int main(int argc, char **argv) {
    sigset_t newmask, oldmask, pendmask;

    if (signal(SIGQUIT, sig_quit) == SIG_ERR)
        err_sys("can't catch SIGQUIT");

    sigemptyset(&oldmask);      // 初始化信号集
    sigemptyset(&newmask);    // 初始化信号集
   
    /* 将SIGQUIT加入信号屏蔽字 */
    sigaddset(&newmask, SIGQUIT);         // 将SIGQUIT加入信号集
    if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
        err_sys("SIG_BLOCK error");
    printf("SIGQUIT block\n");

    sleep(5);

    /* 测试未决信号集中是否含有SIGQUIT */
    if (sigpending(&pendmask) < 0)    // 未决信号集合保存在pendmask中
        err_sys("sigpending error");
    if (sigismember(&pendmask, SIGQUIT))
        printf("\nSIGQUIT pending");

    /* 恢复之前的信号屏蔽字 */
    if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
        err_sys("SIG_SETMASK error");
    printf("SIGQUIT unblock\n");

    sleep(5);
}


函数sigaction

#include 

int sigaction(int signo, const struct sigaction *restrict, struct sigaction*oldrestrict)
    返回值:成功返回0,出错返回-1

struct sigaction {
    void (*sa_handler)(int);        // 信号捕捉函数地址
    sigset_t sa_mask;                // 要阻塞的信号集
    int sa_flags;                         // 
    void (*sa_sigaction)(int, siginfo_t *, void);
};

|选项|说明|
|SA_INTERRUPT|由此信号中断的系统调用不会自动重启
|SA_RESTART|由此信号中断的系统调用会自动重启

#include 

void sig_chld(int signo) {
    printf("get signal: %s\n", strsigno(signo));    
}

int main() {
    pid_t pid;
    struct sigaction new_action, old_action;

    new_action.sa_handler = sig_chld;
    sigemptyset(&new_action.sa_mask);
    new_action.sa_flags = 0;
    sigaction(SIGCHLD, &new_action, &old_action);

    if ( (pid=fork()) == -1){
        perror("fork error");       
        exit(1);
    } 
    else if(pid == 0) {
        if ( kill(getpid(), SIGSTOP) == -1){
          printf("send SIGSTOP to child error\n");
          exit(1);
      }
      printf("child done\n");
    } else {
      sleep(1);
      kill(pid, SIGCONT);
      sleep(2);
      printf("parent done\n");
  }
}

你可能感兴趣的:(信号基础)