【linux操作系统】信号

发布时间:2026/7/10 8:48:30

【linux操作系统】信号 目录信号产生信号处理signalCore Dump信号捕捉信号保存sigset_tsigprocmasksigpendingsigaction可重入函数volatile生活中处处有信号上课铃闹钟等当我们收到信号我们都知道该去做这个信号对应的事情进程也是操作系统也是cpu也是。信号产生各信号对应的编码。1到31是常规信号34到64是实时信号。各信号对应的做法用man 7 signal查看产生信号的做法1.kill指令如kill -9 pid。给当前进程发送9号信号。2.键盘ctrl cctrl \ctrl z等分别是给进程发送2号3号19号3.发生除0野指针访问等行为时os会检测到硬件异常给进程写信号。4.kill系统调用给指定进程发送指定信号参数1进程pid参数2信号编码一个实现kill的demo#include iostream #include sys/types.h #include signal.h #include cstring void Usage() { std::cout Usage : ./Process signumber pid std::endl; } int main(int argc, char *argv[]) { if (argc ! 3) { Usage(); exit(1); } int signumber std::stoi(argv[1]); int pid std::stoi(argv[2]); int n kill(pid, signumber); return 0; }5.raise给当前进程发送指定信号6.abortabort 函数使当前进程接收到信号SIG_ABRT而异常终止。7.由软件产生的信号如管道信号SIG_PIPE闹钟alarm信号SIG_ALRM等。alarm函数可以设定一个闹钟参数指定闹钟的秒数设为0表示取消上一个闹钟。返回值返回上一个闹钟的剩余秒数。信号处理信号的处理方法有三种分别是忽略默认自定义。signal函数的功能是捕捉指定信号并定义其行为。参数1待捕捉的信号编码参数2行为可以是自定义可以是忽略SIG_IGN可以是默认SIG_DFLsighandler为函数指针类型表示返回值为void参数为int类型的函数。Core Dump信号的退出行为分为正常退出Term异常退出Core。当进程异常退出的时候会发生核心转储生成一个core.xxx的文件保存异常信息便于调试。当发生核心转储后退出信息的core dump标志就被设为1。这是一段测试代码//testsig.cc #include iostream #include signal.h #include unistd.h #include stdlib.h #include sys/types.h #include sys/wait.h // typedef void (*sighandler_t)(int); int count 0; void handler(int signumber) { // int num alarm(0); // int n alarm(3); // std::cout 我是 getpid() 收到信号 signumber std::endl; // std::cout count std::endl; // std::cout n std::endl; // std::cout num std::endl; // exit(1); std::cout catch a sig : signumber std::endl; exit(0); } // int main() // { // // std::cout 我是进程: getpid() std::endl; // // 处理信号 // // 自定义捕捉 // signal(SIGINT, handler); // // 忽略信号 // // signal(SIGINT, SIG_IGN); // // 默认处理 // // signal(SIGINT, SIG_DFL); // // while(true) // // { // // //std::cout I am a process, waiting a signal!! std::endl; // // sleep(1); // // abort(); // // //raise(2); // // } // alarm(100); // // int n 1; // while (true) // { // // std::cout n std::endl; // count; // sleep(1); // } // return 0; // } int main() { // signal(SIGFPE, handler); // int a 10; // a / 0; int id fork(); if (id 0) { // int a 10; // a / 0; char *ptr nullptr; *ptr a; } else { int status 0; waitpid(id, status, 0); std::cout exit code: ((status 8) 0xFF) exit sig code: (status 0x7f) core dump: ((status 7) 0x1) std::endl; } return 0; }信号捕捉如果信号的处理动作是用户自定义函数在信号递达时就调用这个函数这称为捕捉信号。用户程序注册了SIGQUIT信号的处理函数sighandler。当前正在执行main函数这时发生中断或异常从用户态切换到内核态。在中断处理完毕后要返回用户态的main函数之前检查到有信号SIGQUIT递达。内核决定返回用户态后不是恢复main函数的上下文继续执行而是执行sighandler函数sighandler和main函数使用不同的堆栈空间它们之间不存在调用和被调用的关系是两个独立的控制流程。sighandler函数返回后自动执行特殊的系统调用sigreturn再次进入内核态。如果没有新的信号要递达这次再返回用户态就是恢复main函数的上下文继续执行了。简记为∞符号信号保存信号并不是一产生就要被处理的我们收到快递员的电话并不是第一时间就要去取快递而是选择一个合适的时间点。那么在产生到处理的过程中信号保存在哪里呢。要搞清楚这个问题首先得知道三张表。pending-未决信号集block-阻塞信号集(也称信号屏蔽字)handler表。信号在内核中的表示示意图每个信号都有两个标志位分别表示阻塞(block)和未决(pending)还有一个函数指针表示处理动作。信号产生时内核在进程控制块中设置该信号的未决标志直到信号递达才清除该标志。在上图的例子中SIGHUP信号未阻塞也未产生过当它递达时执行默认处理动作。SIGINT信号产生过但正在被阻塞所以暂时不能递达。虽然它的处理动作是忽略但在没有解除阻塞之前不能忽略这个信号因为进程仍有机会改变处理动作之后再解除阻塞。一旦解除阻塞信号将会被立刻递达。SIGQUIT信号未产生过一旦产生SIGQUIT信号将被阻塞它的处理动作是用戶自定义函数sighandler。sigset_tsigset_t类型对于每种信号用一个bit表示“有效”或“无效”状态至于这个类型内部如何存储这些bit则依赖于系统实现从使用者的角度是不必关心的使用者只能调用以下函数来操作sigset_t变量而不应该对它的内部数据做任何解释比如用printf直接打印sigset_t变量是没有意义的。#include signal.h int sigemptyset(sigset_t *set); int sigfillset(sigset_t *set); int sigaddset(sigset_t *set, int signo); int sigdelset(sigset_t *set, int signo); int sigismember(const sigset_t *set, int signo);函数sigemptyset初始化set所指向的信号集使其中所有信号的对应bit清零表示该信号集不包含任何有效信号。函数sigfillset初始化set所指向的信号集使其中所有信号的对应bit置位表示该信号集的有效信号包括系统支持的所有信号。注意在使用sigset_t类型的变量之前一定要调用sigemptyset或sigfillset做初始化使信号集处于确定的状态。初始化sigset_t变量之后就可以在调用sigaddset和sigdelset在该信号集中添加或删除某种有效信号。这四个函数都是成功返回0出错返回-1。sigismember是一个布尔函数用于判断一个信号集的有效信号中是否包含某种信号若包含则返回1不包含则返回0出错返回-1。sigprocmask参数1表示如何更改。how参数的含义参数2你要设定的block表参数3原进程的block表输出型参数。返回值若成功则为0若出错则为-1sigpending读取当前进程的未决信号集通过set参数传出。返回值调用成功则返回0出错则返回-1。#include signal.h int sigpending(sigset_t *set);sigaction#include signal.h int sigaction(int signo, const struct sigaction *act, struct sigaction*oact);sigaction函数可以读取和修改与指定信号相关联的处理动作。调用成功则返回0出错则返回-1。signo是指定信号的编号。若act指针非空则根据act修改该信号的处理动作。若oact指针非空则通过oact传出该信号原来的处理动作。act和oact指向sigaction结构体sa_handler也为回调函数。和sighandler 类似。当某个信号的处理函数被调用时内核自动将当前信号加入进程的信号屏蔽字当信号处理函数返回时自动恢复原来的信号屏蔽字这样就保证了在处理某个信号时如果这种信号再次产生那么它会被阻塞到当前处理结束为止。如果在调用信号处理函数时除了当前信号被自动屏蔽之外还希望自动屏蔽另外一些信号则用sa_mask字段说明这些需要额外屏蔽的信号当信号处理函数返回时自动恢复原来的信号屏蔽字。sa_flags字段包含一些选项本章的代码都把sa_flags设为0sa_sigaction是实时信号的处理函数本章不详细解释。下面是一段测试代码//SignalSave.cc #include iostream #include signal.h #include sys/types.h #include sys/wait.h void PrintPending() { sigset_t set; // sigemptyset(set); sigpending(set); for (int i 31; i 0; i--) { if (sigismember(set, i)) { std::cout 1; } else { std::cout 0; } } std::cout std::endl; } void handler(int signo) { std::cout im is proc : getpid() std::endl; std::cout 我收到了信号 signo std::endl; //在处理之前2号信号就被置为0了 // std::cout ------------------------------- std::endl; // PrintPending(); // std::cout ------------------------------- std::endl; } int main() { std::cout im is proc : getpid() std::endl; // sigset_t set; // sigemptyset(set); // sigpending(set); signal(SIGINT, handler); sigset_t block_set, old_set; sigemptyset(block_set); sigemptyset(old_set); sigaddset(block_set,SIGINT);//设置屏蔽2号信号此时内核还未改变 sigaddset(block_set,SIGQUIT);//3 sigaddset(block_set,SIGILL);//4 sigaddset(block_set,SIGTRAP);//5 sigprocmask(SIG_BLOCK, block_set, old_set); int cnt 15; while (1) { PrintPending(); cnt--; if(cnt 0) { sigprocmask(SIG_SETMASK, old_set,block_set);//解除信号立即跳转到handler PrintPending(); } sleep(1); } return 0; }可重入函数信号的执行流和主控制流程是异步的当两者访问相同的全局资源就有可能出现冲突。二者不存在调用和被调用的关系并且使用不同的堆栈空间。函数被不同控制流程使用在第一次调用的时候还未返回就开始第二次调用这称为重入。如果函数因为重入而造成全局函数混乱则称为不可重入函数反之函数只访问自己的局部变量和参数则成为可重入函数。不可重入函数volatile这是一个关键字告诉编译器其修饰的变量不允许被优化对该变量的任何操作都必须在真实的内存中进行。因为编译器会存在优化的行为在某些情况直接在寄存器中读取数据。这是一段测试代码我发现我不在循环里面设置代码编译器就不在内存检查flag我在程序里面设置输出流编译器就检查我给信号就能正常退出。原因是当在循环里加上 printf或任何函数调用、I/O 操作时编译器不敢再做这种激进优化因为函数调用可能有副作用。它被迫每次循环都去内存重新读取 flag 的值。#include stdio.h #include signal.h #include sys/wait.h #include unistd.h //int flag 0; volatile int flag 0; void handler(int sig) { printf(chage flag 0 to 1\n); flag 1; } int main() { signal(2, handler); while (!flag); // { // //printf(im an test proc!! %d\n, flag); // printf(im an test proc!! \n); // //sleep(1); // } printf(process quit normal\n); return 0; }完。。。

相关新闻