linux c信号(1)-基本概念
首页 计算机相关 linux系统编程 linux c信号(1)-基本概念
信号是事件发生时对进程的通知机制。有时也称之为软件中断。信号与硬件中断的相似之处在于打断了程序执行的正常流程,大多数情况下,无法预测信号到达的精确时间。
一个(具有合适权限的)进程能够向另一进程发送信号。信号的这一用法可作为一种同步技术,甚至是进程间通信( IPC )的原始形式。进程也可以向自身发送信一号。然而,发往进程的诸多信号,通常都是源于内核。引发内核为进程产生信号的各类事件如下。
1.硬件发生异常,即硬件检测到一个错误条件并通知内核,随即再由内核发送相应信号给相关进程。硬件异常的例子包括执行一条异常的机器语言指令,诸如,被 0 除,或者引用了无法访问的内存区域。
2.用户键入了能够产生信号的终端特殊字符。其中包括中断字符(通常是 Control-C )、暂停字符(通常是 Control-Z )。
3.发生了软件事件。例如,针对文件描述符的输出变为有效,调整了终端窗口大小,定时器到期,进程执行的 CPU 时间超限,或者该进程的某个子进程退出。
一般进程里为了防止重要的事情处理时被信号打断,一般用signal mask先屏蔽,此时收到的信号都会是pending状态,待后面取消屏蔽时候会触发。
/proc/PID/status 包含了各种位掩码字段,以16进制显示,最低位为信号1,相邻左边为信号2,以此类推。这些字段分别为名称描述
SigPnd线程等待信号
ShdPnd进程等待信号
SigBlk阻塞信号
SigIgn忽略信号
SigCgt捕获信号#defineSIGHUP1/* Hangup 重新读取配置文件 */
#defineSIGINT2/* Interrupt Control-c */
#defineSIGQUIT3/* Control-\ */
#defineSIGILL4/* Illegal instruction (ANSI). */
#defineSIGABRTSIGIOT/* produce a core dump for debugging */
#defineSIGTRAP5/* Trace trap (POSIX). */
#defineSIGIOT6/* IOT trap (4.2 BSD). */
#defineSIGEMT7/* EMT trap (4.2 BSD). */
#defineSIGFPE8/* Floating-point exception (ANSI). */
#defineSIGKILL9/* Kill, unblockable */
#defineSIGBUS10/* Bus error (4.2 BSD). */
#defineSIGSEGV11/* Segmentation violation (ANSI). */
#defineSIGSYS12/* Bad argument to system call (4.2 BSD). */
#defineSIGPIPE13/* Broken pipe (POSIX). */
#defineSIGALRM14/* 由 alarm() or setitimer() 生成 */
#defineSIGTERM15/* Termination kill的默认值 */
#defineSIGURG16/* Urgent condition on socket (4.2 BSD). */
#defineSIGSTOP17/* Stop, unblockable (POSIX). */
#defineSIGTSTP18/* job-control stop signal */
#defineSIGCONT19/* Continue (POSIX). */
#defineSIGCHLD20/* Child status has changed (POSIX). */
#defineSIGCLDSIGCHLD/* Same as SIGCHLD (System V). */
#defineSIGTTIN21/* Background read from tty (POSIX). */
#defineSIGTTOU22/* Background write to tty (POSIX). */
#defineSIGIO23/* I/O now possible (4.2 BSD). */
#defineSIGPOLLSIGIO/* Same as SIGIO? (SVID). */
#defineSIGXCPU24/* CPU limit exceeded (4.2 BSD). */
#defineSIGXFSZ25/* File size limit exceeded (4.2 BSD). */
#defineSIGVTALRM26/* Virtual alarm clock (4.2 BSD). */
#defineSIGPROF27/* Profiling alarm clock (4.2 BSD). */
#defineSIGWINCH28/* Window size change (4.3 BSD, Sun). */
#define SIGINFO29/* Information request (4.4 BSD). */
#defineSIGUSR130/* User-defined signal 1 (POSIX). */
#defineSIGUSR231/* User-defined signal 2 (POSIX). */
#define SIGLOST32/* Resource lost (Sun); server died (GNU). */
改变信号处置:signal()
signal() 为比较老的处理函数,可移植性不如sigaction(),功能也没它强大。sigaction()是首选的信号处理。不过signal()简单。#include
void ( *signal(int sig, void (*handler)(int)) ) (int);
//成功返回之前的处理函数指针,失败返回SIG_ERR
//函数原型也可以这样写
typedef void (*sighandler_t)(int);
sighandler_t signal(int sig, sighandler_t handler);
如下代码暂时建立信号处置,然后还原,signal() 做不到 获取到当前的信号处置而不改变信号处置,sigaction() 则能做到。void (*oldHandler)(int);
oldHandler = signal(SIGINT, newHandler);
if (oldHandler == SIG_ERR)
perror("signal");
//do something
if (signal(SIGINT, oldHandler) == SIG_ERR)
perror("signal");
handler 也可以指定为如下值,所以调用signal() 也有可能返回如下其中一个值。
SIG_DEL 将信号重置为默认值。
SIG_IGN 忽略信号。if (signal(SIGINT, SIG_DFL) == SIG_ERR) perror("signal");
if (signal(SIGINT, SIG_IGN) == SIG_ERR) perror("signal");
信号处理简介
调用信号处理器程序,可能会随时打断主程序流程。内核代表进程来调用处理器程序,当处理器返回时,主程序会在处理器打断的位置恢复执行。
#include
#include /* Type definitions used by many programs */
#include /* Standard I/O functions */
#include /* Prototypes of commonly used library functions,
plus EXIT_SUCCESS and EXIT_FAILURE constants */
#include /* Prototypes for many system calls */
#include /* Declares errno and defines error constants */
#include /* Commonly used string-handling functions */
static void
sigHandler(int sig)
{
printf("hello freecls!\n");
}
int
main(int argc, char *argv[])
{
int j;
if (signal(SIGINT, sigHandler) == SIG_ERR)
return 1;
for (j = 0; ; j++) {
printf("%d\n", j);
sleep(3); /* Loop slowly... */
}
}
该程序执行时,当你按下ctrl+c时,不会终止程序(因为默认行为被改变了)而会输出 helo freecls 然后继续执行。最后按下 ctrl+\终止。
发送信号:kill()
与shell 的kill 命令类似,一个进程可以向另一个进程发送信号(必须要有权限)。#include
int kill(pid_t pid, int sig);
//成功返回0, 失败-1
//pid > 0发送为指定进程
//pid = 0发送给当前进程组里所有进程包括自己
//pid < -1 发送给当前进程组里的进程组id = pid\
//的绝对值 = killpg(pid_t pgrp, int sig);
//pid = -1发送给所有进程(必须有权限)除了init和自己
//sig = 0为空信号用来检测进程是否存在,不可靠因为进程id可以重复使用,或是僵尸进程
特权级进程可以向任何进程发送信号,非特权级必须满足如下图条件。
向自己发送信号:raise()#include
int raise(int sig);
//成功返回0,失败返回非0
//单线程程序相当于
kill(getpid(), sig);
//多线程相当于
pthread_kill(pthread_self(), sig);
向进程组发送信号#include
int killpg(pid_t pgrp, int sig);
//成功返回0,失败-1
//相当于
kill(-pgrp, sig);
如果pgrp=0,则向进程组所有进程发送信号。
信号描述
每个信号都有一段与之相对应的描述,可以用sys_siglist[sig] 来获取,但是推荐用strsignal() 函数,因为它支持locale。#define _BSD_SOURCE
#include
extern const char *const sys_siglist[];
#define _GNU_SOURCE
#include
char *strsignal(int sig);
直接打印到错误输出#include
void psignal(int sig, const char *msg);#include
#include
#include
extern const char *const sys_siglist[];
char *strsignal(int sig);
int main(){
char *tmp = strsignal(SIGINT);
printf("%s\n", tmp);
psignal(SIGINT,"sigerr");
}[root@izj6cfw9yi1iqoik31tqbgz c]# ./a.out
Interrupt
sigerr: Interrupt
信号集
许多信号相关的系统调用都需要能表示一组不同的信号。多个信号可使用一个称之为信号集的数据结构来表示。数据类型为 sigset_t。
sigemptyset() 初始化一个未包含任何成员的信号集。sigfillset() 初始化一个信号集,包含所有信号(包括实时信号)。#include
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
//成功返回0,失败-1
必须使用上述2个函数初始化信号集,因为C语言不会对自动变量进行初始化。
添加或移除单个信号#include
int sigaddset(sigset_t *set, int sig);
int sigdelset(sigset_t *set, int sig);
//成功返回0,失败-1
测试信号 sig 是否为 set的成员#include
int sigismember(const sigset_t *set, int sig);
//1表示是,0不是
另外3个非标准函数#define _GNU_SOURCE
#include
//left与right交集
int sigandset(sigset_t *set, sigset_t *left, sigset_t *right);
//left与right并集
int sigorset(sigset_t *dest, sigset_t *left, sigset_t *right);
//成功返回0,失败-1
//判断是否为空,是返回1,不是0
int sigisemptyset(const sigset_t *set);
信号掩码(阻塞信号传递)
也可叫信号遮罩,信号屏蔽。内核会为每个进程维护一个信号掩码,即一组信号,并将阻塞其针对该进程传递。在多线程环境中,每个线程都可以使用pthread_sigmask()函数来独立检查和修改其信号。#include
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
//Returns 0 on success, or –1 on error
sigprocmask() 既可以修改信号掩码,又可获取现有的掩码,或两者兼具。
how参数指定了该函数信号掩码带来的变化:
SIG_BLOCK 将set 指向信号集并集到当前信号掩码中。
SIG_UNBLOCK 将set指向的信号集中的信号从信号掩码中移除,就算信号掩码中没有要解除的信号也不报错。
SIG_SETMASK 将set指向的信号集设置为信号掩码。
如果 oldset 参数不为NULL,则其指向sigset_t 结构缓冲区,用于返回之前的信号掩码。
如果只是想获取信号掩码, set 参数设为NULL即可,这时将忽略how 参数。
如果解除了对某个等待信号的阻塞,会立刻将该信号传递给进程。
下面代码时暂时阻止 SIGINT 信号的传递。sigset_t blockSet, prevMask;
sigemptyset(&blockSet);
sigaddset(&blockSet, SIGINT);
if (sigprocmask(SIG_BLOCK, &blockSet, &prevMask) == -1)
perror("sigprocmask1");
if (sigprocmask(SIG_SETMASK, &prevMask, NULL) == -1)
perror("sigprocmask2");
SIGKILL 和 SIGSTOP 信号是不允许阻塞的,也就是下面的代码会阻塞除这2个信号以外的任何信号。sigfillset(&blockSet);
if (sigprocmask(SIG_BLOCK, &blockSet, NULL) == -1)
perror("sigprocmask");
处于等待中的信号
如果某进程接收了一个正在阻塞的信号,那么会将该信号添加到进程的等待信号集中。之后如果解除了对该信号的阻塞,就会把该信号传递给此进程(就算在阻塞期间发生了N次,解除时只会传递1次,而实时信号可以排队)。
sigpending() 系统调用返回进程处于等待状态的信号集,并存入set 指向的sigset_t 结构中。#include
int sigpending(sigset_t *set);
//Returns 0 on success, or –1 on error
改变信号处置:sigaction()
除signal() 之外,sigaction() 系统调用是设置信号处置的另一选择。用法复杂一点,但是功能强大,且可移植性强。#include
struct sigaction {
void (*sa_handler)(int);
sigset_t sa_mask;
int sa_flags; /* Flags controlling handler invocation */
void (*sa_restorer)(void); /* Not for application use */
};
int sigaction(int sig, const struct sigaction *act, struct sigaction *oldact);
//Returns 0 on success, or –1 on error
sig为想要获取或改变的信号编号,除去 SIGKILL SIGSTOP。act是指向信号新处置的数据结构,oldact用来返回之前的信号处置结构,都可以设置为 NULL。
sigaction 结构实际更为复杂,下面文章再讲。sa_handler 对应于 signal()的handler。其所对应的值为信号处理器函数或是SIG_IGN、SIG_DFL。仅当sa_handler 为函数地址时,才会对sa_mask 和 sa_flags 加以处理。
sa_mask 定义了一组信号,在调用sa_handler 所定义的处理程序时阻塞,也就是在调用处理器程序之前,将这些信号添加到信号掩码中。处理器函数返回时自动删除。利用这一特性可以指定哪些信号不允许打断处理器的执行。此外,引发此处理器程序的信号也将自动添加到信号掩码中。这就保证了同一个信号抵达多次不会递归中断自己。
sa_flags字段是一个位掩码,可以是如下的选项相或(|):
SA_NOCLDSTOP 若 sig 为SIGCHLD 信号,则当因接受一信号而停止或恢复某一子进程时,将不产生此信号。
SA_NOCLDWAIT 若 sig 为SIGCHLD 信号,则当子进程终止时不会将其转化为僵尸进程。
SA_NODEFER 捕获信号时,不会在执行处理器程序时自动将该信号添加到进程掩码中。别名 SA_NOMASK。
SA_ONSTACK 针对此信号调用处理器函数时,使用了由 sigaltstack() 安装的备选栈。
SA_RESETHAND 当捕获该信号时,会在调用处理函数之前将信号处置重置为默认值(SIG_DFL)。
SA_RESTART 自动重启由信号处理器程序中断的系统调用。
SA_SIGINFO 调用信号处理器程序时携带额外的参数。
等待信号:pause()#include
int pause(void);
//Always returns –1 with errno set to EINTR
暂停进程的执行,直至信号处理器函数中断该调用为止(或者直至一个未处理信号终止进程为止)。