信号

  • 信号是事件发生时对进程的通知机制,又称为软中断。当程序正常运行时,如果收到一个信号,那么程序的运行流程会被打断,等到执行完处理流程后再继续执行
  • 一个进程收到的信号来源有两种,一种是内核发送的,另一种是其他进程或者自己给自己发送的。
  • 内核发送信号给进程由以下几种情况产生
  1. cpu执行了错误的程序指令,如除0或者内存越界等,这时候cpu会收到一个硬件中断然后通知内核,进而内核发送信号给进程
  2. 用户从进程的控制终端键入了能够产生信号的字符
  3. 发生了软件事件,如套接字有数据可读或者定时器到期等
  • signal调用可以为调用进程注册信号处理函数,这是sigaction的简易版本。signum是要注册的信号类型,handler是处理函数指针,处理函数是无返回值并且只有一个int参数,且signal函数会返回原来的处理函数指针。其原型如下
       #include <signal.h>

       typedef void (*sighandler_t)(int);

       sighandler_t signal(int signum, sighandler_t handler);
  • 内核在调用信号处理函数时,会将对应的信号编号传递给信号处理函数。当为不同信号注册了一个相同处理函数时,处理函数需要区分到底是哪个信号被触发,这时候可以通过这个参数区分别
  • 一个进程可以通过kill调用来向其他进程发送信号,函数原型为int kill(pid_t pid,int sig)。如果pid是正数,那么发送信号给进程号为pid的进程;如果pid等于0,将信号发送给与当前进程同组的其他所有进程包括自身;如果pid小于-1,将信号发送给进程组ID等于pid的绝对值的所有进程;如果pid等于-1,将信号发送给除了init进程和调用进程自身的所有其他进程。进程能够成功发送信号还需要一定的权限,如果没有权限发送,则kill调用失败,返回-1并设置errno为EPERM;如果pid是负值,那么只要向其中一个进程发送成功,kill调用就成功其权限规则如下
  1. 特权级进程可以发送给任何一个进程
  2. 如果非特权进程的实际用户ID或者有效用户ID和目标进程的实际用户ID或者保存的设置用户ID匹配,那么可以发送
  3. SIGCONT信号无视发送权限,所有进程都可以发送
  • 可以用kill调用检查进程是否存在,只要将sig参数设置为0(表示空信号),然后指定pid为要检查的进程的pid,如果返回-1并且errno为ESRCH,则表示没有此进程
  • 对于单线程进程来说,内核会为该进程维护一个信号屏蔽字(列表),里面存放着要屏蔽的信号;对于多线程进程,后面再说。而对于这个信号屏蔽字的相关操作都是凭借sigset_t这个信号集来操控的。首先要调用sigemptyset初始化函数将一个信号集初始化为空(不要尝试自己去用memset等方式初始化,可能会出错),相对的调用sigfillset则是把信号集全部初始化为1,然后调用sigaddset或者sigdelset函数将感兴趣的信号加入信号集或从信号集中删除;另外,sigismember调用可以查看指定信号是否存在于该信号集中
  • 如果将被阻塞的信号传递给该进程(对于单线程进程来说),那么该信号的传递会被延后(直到从进程信号掩码中移除该阻塞信号,信号掩码实际上是线程属性,在多线程进程中,每个线程都可以使用pthread_sigmask函数独立检查和修改其信号掩码)
  • 对信号掩码的修改有如下几种方式
  • 当调用信号处理函数时,会将引发该调用的信号自动添加到信号掩码中(除非调用sigaction时指定SA_NODEFER标志)
  • 使用sigaction函数建立信号处理程序时,可以额外添加一组信号掩码,这会使得在调用该处理函数时自动的阻塞这些添加的额外信号
  • 调用sigprocmask函数主动的添加或者移除信号掩码
  • sigprocmask函数用来直接改变信号掩码,how参数指定了需要怎么样使用这个信号集,有三种选择。一是SIG_BLOCK,将指定的set信号集与添加入当前信号掩码中;二是SIG_UNBLOCK,将set指定的信号从信号掩码中移除;最后是SIG_SETMASK是将set直接赋给信号掩码。set是需要操作的信号集,若oldset参数不为空,则其指向原先阻塞的信号集。函数原型为int sigprocmask(int how,const sigset_t* set,sigset_t* oldset)
  • 当向一个进程发送了一个被阻塞的信号,该信号会被放入该进程的等待信号集中,只有在解除了阻塞后,该信号才会被发送给进程;而使用sigpending函数可以获得当前进程中处于等待状态的所有信号,原型为int sigpending(sigset_t* set)。其中,set参数指向等待信号集
  • 如果一个信号在被阻塞期间,被发送给一个进程多次;那么等到解除阻塞后,也只会发送该信号给进程一次
  • 除了使用signal函数设置某一个信号的处理函数以外,还可以使用sigaction函数;sigaction函数更为复杂,但也提供了更多的控制。sig是要设置的信号,act是一个结构体指针,指向一个信号处理的结构,oact保存原来的设置。关于sigaction结构体,sa_handler指向处理函数,sa_sigaction一般设置为NULL,sa_mask表示额外的信号掩码,sa_flags表示一些特定的标志,sa_restorer指针一般也设置为NULL。对于这个函数的标志属性,后面再讲。函数原型以及结构体如下
#include <signal.h>

struct sigaction {
               void     (*sa_handler)(int);
               void     (*sa_sigaction)(int, siginfo_t *, void *);
               sigset_t   sa_mask;
               int        sa_flags;
               void     (*sa_restorer)(void);
           };

int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
  • 调用pause函数可以暂停进程的执行,直到一个信号被发送给该进程为止,原型为int pause(void)
  • 对于一个函数来说,如果能被多个执行线程同时安全的调用,那么就称它为可重入的函数;一般,使用全局变量或者静态变量的函数都不是可重入的,比如说加密函数crypt以及标准库函数malloc等。
  • 信号处理函数是异步执行的,所以也是一条单独的执行线程(虽然和主线程不是并发执行)。异步信号安全的函数是指当从处理函数中调用的时候,可以保证其实现是安全的。所以对于编写信号处理函数有以下规则:确保信号处理函数代码本身可重入并且只调用异步信号安全的函数
  • 经常会声明一个在信号处理函数和主函数中共享的全局变量,用于指示某些事件的发生;所以为满足代码安全的要求,需要这个全局变量的读写是原子操作并且要求直接从内存中读取而不是寄存器,所以通常这样声明volatile sig_atmoic_t flag;但是对该类型变量的++或者--操作不保证原子性
  • SIGKILL和SIGSTOP,它们的默认行为不能被改变;SIGKILL信号的默认行为是终止一个进程,SIGSTOP信号的默认行为是暂停一个进程
  • SIGCONT信号可以使某些处于停止状态的进程继续运行
  • sigsuspend调用会将mask所指向的信号集替换当前的信号掩码,然后挂起进程直到捕获一个信号,并从信号处理函数返回;返回以后,会将信号掩码恢复到原来的值。函数原型为int sigsuspend(const sigset_t* mask)
  • 可以使用sigwaitinfo函数来同步等待信号的到达,该函数挂起进程直到set信号集中的某一信号到达,info参数指向了与该信号相关的详细信息。在调用该函数之前,应该先将所有信号阻塞,否则会发生未定义的行为。该函数原型为int sigwaitinfo(const sigset_t* set,siginfo_t* info),调用成功返回信号数值,否则返回-1
  • sigaction的sa_flags标志位中有一个SA_RESTART,这是用来为一些被信号中断的系统调用恢复执行的。如果为某个信号设置了自动重启的标志位且被中断的系统调用能够重启的话,在执行完信号处理程序后就会继续执行系统调用。但是并非所有的系统调用都能支持重启操作,只有在操作慢速设备,像终端,管道,FIFO以及套接字时才能够利用该标志位;除了这个标志位以外,还有一个SA_SIGINFO标志位。如果设置了该标志位,那么在收到信号时可以获得一些额外的附加信息,为了获取该信息,在调用sigaction函数时还需要使用第二个函数指针参数即void (*sa_sigaction)(int,siginfo_t*,void*)来替代使用sa_handler;该函数的第二个参数是一个指向附加信息结构体的指针,具体的每个成员的含义可查看手册,该结构体如下
siginfo_t {
               int      si_signo;     /* Signal number */
               int      si_errno;     /* An errno value */
               int      si_code;      /* Signal code */
               int      si_trapno;    /* Trap number that caused
                                         hardware-generated signal
                                         (unused on most architectures) */
               pid_t    si_pid;       /* Sending process ID */
               uid_t    si_uid;       /* Real user ID of sending process */
               int      si_status;    /* Exit value or signal */
               clock_t  si_utime;     /* User time consumed */
               clock_t  si_stime;     /* System time consumed */
               sigval_t si_value;     /* Signal value */
               int      si_int;       /* POSIX.1b signal */
               void    *si_ptr;       /* POSIX.1b signal */
               int      si_overrun;   /* Timer overrun count;
                                         POSIX.1b timers */
               int      si_timerid;   /* Timer ID; POSIX.1b timers */
               void    *si_addr;      /* Memory location which caused fault */
               long     si_band;      /* Band event (was int in
                                         glibc 2.3.2 and earlier) */
               int      si_fd;        /* File descriptor */
               short    si_addr_lsb;  /* Least significant bit of address
                                         (since Linux 2.6.32) */
               void    *si_lower;     /* Lower bound when address violation
                                         occurred (since Linux 3.19) */
               void    *si_upper;     /* Upper bound when address violation
                                         occurred (since Linux 3.19) */
               int      si_pkey;      /* Protection key on PTE that caused
                                         fault (since Linux 4.6) */
               void    *si_call_addr; /* Address of system call instruction
                                         (since Linux 3.5) */
               int      si_syscall;   /* Number of attempted system call
                                         (since Linux 3.5) */
               unsigned int si_arch;  /* Architecture of attempted system call
                                         (since Linux 3.5) */
           }
  • 通常在调用一个阻塞式的系统调用之前都会设置阻塞时间的上限值,不会让其永久的阻塞下去;对于部分函数,本身就有设置阻塞时间的接口。但是还是有以一些调用需要我们手动设置。一般的处理过程如下
  1. 调用sigaction为SIGALRM信号设置处理函数,并且确保清除SA_RESTART标志,避免系统调用的重新启动
  2. 调用alarm定时器函数设置一个阻塞时间上限
  3. 执行阻塞系统调用
  4. 待系统调用返回后,屏蔽定时器(防止在定时器到期前系统调用就完成的情况)
  5. 检查系统调用失败时,是否将errno设置为EINTR
  • 上面用到的alarm是一个简易定时器函数,其原型为unsigned int alarm(unsigned int seconds),到期时,会向调用进程发送一个SIGALRM信号。需要注意的是调用alarm会覆盖前一个对定时器的设置(如果调用其他的定时器函数也是一样的,比如setitimer),alarm(0)会屏蔽当前的定时器,且alarm的返回值是前一个定时器所剩余的秒数
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,519评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,842评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,544评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,742评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,646评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,027评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,513评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,169评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,324评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,268评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,299评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,996评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,591评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,667评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,911评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,288评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,871评论 2 341

推荐阅读更多精彩内容

  • 一、信号及信号来源 信号本质 信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一...
    丶Em1tu0F阅读 1,418评论 0 1
  • 对于 Linux来说,实际信号是软中断,许多重要的程序都需要处理信号。信号,为 Linux 提供了一种处理异步事件...
    故事狗阅读 84,414评论 2 62
  • 信号处理函数 sigaction的用法 int sigaction ( int signo, const stru...
    小叶大孟阅读 2,208评论 0 0
  • 使用场景:1、为了并发,中断处理其它事件,1、进程间通信1、中断中止(注意不是终止)当前正在执行的程序,转而执行其...
    奥斯特洛司机阅读 676评论 0 0
  • 信号本质 软中断信号(signal,又简称为信号)用来通知进程发生了异步事件。在软件层次上是对中断机制的一种模拟,...
    飞扬code阅读 735评论 0 2