在Linux中,信号是进程间通讯的一种方式,它采用的是异步机制。当信号发送到某个进程中时,操作系统会中断该进程的正常流程,并进入相应的信号处理函数执行操作,完成后再回到中断的地方继续执行。 需要说明的是,信号只是用于通知进程发生了某个事件,除了信号本身的信息之外,并不具备传递用户数据的功能。 1 信号的响应动作每个信号都有自己的响应动作,当接收到信号时,进程会根据信号的响应动作执行相应的操作,信号的响应动作有以下几种:
用户可以通过 2 信号类型Linux支持的信号类型可以参考下面给出的列表。 2.1 在POSIX.1-1990标准中的信号列表
注:其中 2.2 在SUSv2和POSIX.1-2001标准中的信号列表
注:在Linux 2.2版本之前, 2.3 其它信号
注意:列表中有的信号有三个值,这是因为部分信号的值和CPU架构有关,这些信号的值在不同架构的CPU中是不同的,三个值的排列顺序为:1,Alpha/Sparc;2,x86/ARM/Others;3,MIPS。 例如 3 信号机制文章的前面提到过,信号是异步的,这就涉及信号何时接收、何时处理的问题。 我们知道,函数运行在用户态,当遇到系统调用、中断或是异常的情况时,程序会进入内核态。信号涉及到了这两种状态之间的转换,过程可以先看一下下面的示意图: 接下来围绕示意图,将信号分成接收、检测和处理三个部分,逐一讲解每一步的处理流程。 3.1 信号的接收接收信号的任务是由内核代理的,当内核接收到信号后,会将其放到对应进程的信号队列中,同时向进程发送一个中断,使其陷入内核态。 注意,此时信号还只是在队列中,对进程来说暂时是不知道有信号到来的。 3.2 信号的检测进程陷入内核态后,有两种场景会对信号进行检测:
当发现有新信号时,便会进入下一步,信号的处理。 3.3 信号的处理信号处理函数是运行在用户态的,调用处理函数前,内核会将当前内核栈的内容备份拷贝到用户栈上,并且修改指令寄存器(eip)将其指向信号处理函数。 接下来进程返回到用户态中,执行相应的信号处理函数。 信号处理函数执行完成后,还需要返回内核态,检查是否还有其它信号未处理。如果所有信号都处理完成,就会将内核栈恢复(从用户栈的备份拷贝回来),同时恢复指令寄存器(eip)将其指向中断前的运行位置,最后回到用户态继续执行进程。 至此,一个完整的信号处理流程便结束了,如果同时有多个信号到达,上面的处理流程会在第2步和第3步骤间重复进行。 4 信号的使用4.1 发送信号用于发送信号的函数有 raise函数:向进程本身发送信号 函数声明如下: #include <signal.h> int raise(int sig); 函数功能是向当前程序(自身)发送信号,其中参数 kill函数:向指定进程发送信号 函数声明如下: #include <sys/types.h> #include <signal.h> int kill(pid_t pid, int sig); 函数功能是向特定的进程发送信号,其中参数 在这里的参数
另外,当 4.2 等待信号被捕获等待信号的过程,其实就是将当前进程(线程)暂停,直到有信号发到当前进程(线程)上并被捕获,函数有 pause函数:将进程(或线程)转入睡眠状态,直到接收到信号 函数声明如下: #include <unistd.h> int pause(void); 该函数调用后,调用者(进程或线程)会进入睡眠(Sleep)状态,直到捕获到(任意)信号为止。该函数的返回值始终为-1,并且调用结束后,错误代码(errno)会被置为EINTR。 sigsuspend函数:将进程(或线程)转入睡眠状态,直到接收到特定信号 函数声明如下: #include <signal.h> int sigsuspend(const sigset_t *mask); 该函数调用后,会将进程的信号掩码临时修改(参数 4.3 修改信号的响应动作用户可以自己重新定义某个信号的处理方式,即前面提到的修改信号的默认响应动作,也可以理解为对信号的注册,可以通过 首先看一下函数声明: #include <signal.h> typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler); 第一个参数 下面为示例代码: #include <stdio.h> #include <signal.h> #include <unistd.h> /* 信号处理函数 */ void sig_callback(int signum) { switch (signum) { case SIGINT: /* SIGINT: Ctrl+C 按下时触发 */ printf("Get signal SIGINT. \r\n"); break; /* 多个信号可以放到同一个函数中进行 通过信号值来区分 */ default: /* 其它信号 */ printf("Unknown signal %d. \r\n", signum); break; } return; } /* 主函数 */ int main(int argc, char *argv[]) { printf("Register SIGINT(%u) Signal Action. \r\n", SIGINT); /* 注册SIGINT信号的处理函数 */ signal(SIGINT, sig_callback); printf("Waitting for Signal ... \r\n"); /* 等待信号触发 */ pause(); printf("Process Continue. \r\n"); return 0; } 源文件下载:链接 例子中,将 ./linux_signal_example Register SIGINT(2) Signal Action. Waitting for Signal ... ^CGet signal SIGINT. Process Continue. 进程收到 |
|