当前位置: 首页 > news >正文

人才网站查询档案网站登录模版

人才网站查询档案,网站登录模版,十大it教育培训机构排名,安康微信公众平台祝大家新年快乐啦#xff01;#xff01;#xff01;新的一年#xff0c;第一篇文章我们来谈谈Linux中的信号 目录 一、引入 二、系统内置的信号 三、前台进程和后台进程 四、signal函数 五、信号的产生 5.1 通过终端按键产生信号 5.2 调用系统函数向进程发信号 5…祝大家新年快乐啦新的一年第一篇文章我们来谈谈Linux中的信号 目录 一、引入 二、系统内置的信号 三、前台进程和后台进程 四、signal函数 五、信号的产生 5.1 通过终端按键产生信号 5.2 调用系统函数向进程发信号 5.2.1 kill 5.2.2 raise 5.2.3 abort 5.3 由软件条件产生的信号 5.3.1 alarm 5.4 硬件异常产生的信号 六、核心转储 6.1 是什么核心转储 6.2 核心转储文件的产生 6.3 核心转储文件的使用举例 6.4 waitpid中status参数的core dump标志位 七、信号的保存 7.1 和信号相关的一些概念 7.2 信号在内核中的存储 7.3 sigset_t信号集 7.4 信号集操作函数 7.4.1 sigprocmask 7.4.2 sigpending 八、信号的处理 8.1 信号处理的原理 8.1.1 用户态和内核态 8.1.2 信号的捕捉 8.1.3 pending位图置0的时机 8.2 sigaction 一、引入 在生活中我们处处都可以遇到信号比如红绿灯、闹钟、鸡叫或者是女朋友/男朋友的脸色 这些事务都有一个共性当我们遇到它们时都会做出相应的行动 那我们为什么会做出对应的动作呢这是因为曾经有人或事“培养”过自己。即便我们现在没有遇到这些信号我们也知道该怎么处理它 所以我们可以认识并处理一个信号进程也不例外进程在没有收到信号的时候其实它早就已经能够知道一个信号该怎么被处理了比如kill -9 这就说明程序员当初在设计进程时就内置了对信号的处理当进程每收到一个信号时都会执行早已内置的代码了 但是信号的产生时间是不确定的比如我们在处理一件重要的事情时突然响起了外卖小哥打来的电话对于这种情况我们会先将手上的事情处理完再去取外卖 进程也不例外其运行与收到信号具有异步性所以在收到信号时并不一定会直接去处理我们将从进程收到信号一直到处理信号的这个时间段称为时间窗口但是过了这段时间窗口进程需要对信号进行处理这就意味着进程必须具有保存信号的能力 对于进程对信号做怎样的处理主要有三种情况默认动作、忽略信号、用户自定义动作 综上所述我们对信号的讲解主要有三个部分信号产生、信号保存、信号处理 二、系统内置的信号 我们在Linux中可以使用kill -l指令来查看系统中提供的所有信号 我们可以看到系统中有1-31/34-64一共62种信号该批信号分为两种1-31编号的信号是普通信号会进行详细的讲解34-64编号的信号是实时信号这部分信号我们不做重点介绍 这些普通信号在C语言中实际上是被定义的宏 /* Signals. */ #define SIGHUP 1 /* Hangup (POSIX). */ #define SIGINT 2 /* Interrupt (ANSI). */ #define SIGQUIT 3 /* Quit (POSIX). */ #define SIGILL 4 /* Illegal instruction (ANSI). */ #define SIGTRAP 5 /* Trace trap (POSIX). */ #define SIGABRT 6 /* Abort (ANSI). */ #define SIGIOT 6 /* IOT trap (4.2 BSD). */ #define SIGBUS 7 /* BUS error (4.2 BSD). */ #define SIGFPE 8 /* Floating-point exception (ANSI). */ #define SIGKILL 9 /* Kill, unblockable (POSIX). */ #define SIGUSR1 10 /* User-defined signal 1 (POSIX). */ #define SIGSEGV 11 /* Segmentation violation (ANSI). */ #define SIGUSR2 12 /* User-defined signal 2 (POSIX). */ #define SIGPIPE 13 /* Broken pipe (POSIX). */ #define SIGALRM 14 /* Alarm clock (POSIX). */ #define SIGTERM 15 /* Termination (ANSI). */ #define SIGSTKFLT 16 /* Stack fault. */ #define SIGCLD SIGCHLD /* Same as SIGCHLD (System V). */ #define SIGCHLD 17 /* Child status has changed (POSIX). */ #define SIGCONT 18 /* Continue (POSIX). */ #define SIGSTOP 19 /* Stop, unblockable (POSIX). */ #define SIGTSTP 20 /* Keyboard stop (POSIX). */ #define SIGTTIN 21 /* Background read from tty (POSIX). */ #define SIGTTOU 22 /* Background write to tty (POSIX). */ #define SIGURG 23 /* Urgent condition on socket (4.2 BSD). */ #define SIGXCPU 24 /* CPU limit exceeded (4.2 BSD). */ #define SIGXFSZ 25 /* File size limit exceeded (4.2 BSD). */ #define SIGVTALRM 26 /* Virtual alarm clock (4.2 BSD). */ #define SIGPROF 27 /* Profiling alarm clock (4.2 BSD). */ #define SIGWINCH 28 /* Window size change (4.3 BSD, Sun). */ #define SIGPOLL SIGIO /* Pollable event occurred (System V). */ #define SIGIO 29 /* I/O now possible (4.2 BSD). */ #define SIGPWR 30 /* Power failure restart (System V). */ #define SIGSYS 31 /* Bad system call. */ #define SIGUNUSED 31 那进程要具有保存信号的能力那该怎么保存呢首先我们要知道一个进程是否有收到信号以及收到了什么样的信号 对于判断一个进程是否收到了信号这个很简单我们用一个数字表示即可0表示没收到和1表示收到了 对于一个进程收到了什么样的信号我们仔细观察一下普通信号的个数一共有31个那我们用位图表示即可一个整型一共有32bit位我们将每个位都用0和1表示是否收到了对应的信号即可 所以在进程的PCB中一定存在一个对应的位图结构来存储收到的信号 三、前台进程和后台进程 进程的种类有很多种下面我们主要来说说前台和后台进程 在Linux中我们可以使用./来将一个进程跑起来在这个进程运行时我们对终端输入任何指令都是没有效果的但我们如果按下ctrlc会直接中断这个进程例如 #includeiostream #includeunistd.hint main() {while(1){std::cout 我是一个进程,pid: getpid() std::endl;sleep(1);}return 0; } 这样的进程被称为前台进程一般情况下前台进程只有一个所以当我们运行起一个前台进程后此时bash进程会退居到后台这时我们输入的指令只能传给当前的前台进程导致了没有任何效果但是当我们输入ctrlc后会终止所有的前台进程 当然我们可以在运行进程的指令后加一个 这样子我们调起的进程会在后台运行 此时我们输入指令是有效的但是按下ctrlc后该进程并不会终止 如果我们想终止一个在后台运行的进程就需要用到kill指令了 四、signal函数 该函数应当在信号处理中进行讲解但是为了方便我们实践的验证操作在这里先进行介绍 如果我们想让某个进程收到某个信号时不执行系统的默认动作而是执行自定义动作这时我们可以用到signal函数 可以看到该函数有两个参数sidnum和handler ● sidnum传入信号编号当进程再一次收到该信号时会对应执行handler方法 ● handler传入void(*)(int)类型的函数地址当收到sidnum信号时执行该方法 signal函数的三种常用用法如下 signal(signum, SIG_IGN)将信号signum的处理方式设置为忽略即当接收到该信号时程序不会做任何响应。signal(signum, SIG_DFL)将信号signum的处理方式恢复为默认即当接收到该信号时操作系统会按照默认操作处理该信号。signal(signum, handler)将信号signum的处理方式设置为由handler函数处理即当接收到该信号时会调用handler函数进行相应的处理。 下面是实例演示 #includeiostream #includeunistd.h #includesignal.hvoid handler(int signum)//handler函数被signal调用时会传入收到的信号编号 {std::coutreceived signal:signumstd::endl; }int main() {signal(2,handler);//将2号信号对应方法该为handler方法while(1){std::cout 我是一个进程,pid: getpid() std::endl;sleep(1);}return 0; } ctrlc本质上就是对前台进程发送2号信号所以在使用ctrlc时进程并没有终止而是执行了handler函数中对应的方法 那这样子的话我们将所有的普通信号对应方法都用signal函数改为handler方法那这个进程会不会变成一个无法杀死的进程 我们来试试看 #includeiostream #includeunistd.h #includesignal.hvoid handler(int signum)//handler函数被signal调用时会传入收到的信号编号 {std::coutreceived signal:signumstd::endl; }int main() {for(int i1;i31;i)//将所有的普通信号对应方法改为handler方法{signal(i,handler);}while(1){std::cout 我是一个进程,pid: getpid() std::endl;sleep(1);}return 0; } 但事实并非我们所料当我们向进程传入9号信号时这个进程还是被终止了 原因是操作系统的设计者早就考虑到该情况了规定9号信号对应的方法不能被修改 五、信号的产生 5.1 通过终端按键产生信号 用户输入命令在Shell下启动一个前台进程 用户按下Ctrl-C 这个键盘输入产生一个硬件中断被OS获取解释成信号发送给目标前台进程前台进程因为收到信号进而引起进程退出 这个信号产生的过程是有键盘的输入产生的作为信号的来源之一 5.2 调用系统函数向进程发信号 5.2.1 kill 我们可以使用kill函数来向对应的进程传递信号 该函数有两个参数 pid传入要发送信号的进程的PID sig传入要发送的信号编号 kill函数的返回值为0表示成功-1表示失败并设置errno来指示错误的原因 下面我们来实操一下利用main函数的两个形参来获取pid和sig不熟悉的同学可以看到这里【Linux】环境变量_linux 环境变量-CSDN博客 #includeiostream #includeunistd.h #includesignal.h #includestring #includecstring #includecerrnovoid User(std::string proc) {std::cout\t Useage:\n\t;std::coutproc 信号编号 目标进程PIDstd::endl; }int main(int argc, char* argv[]) {if(argc!3)//输入格式有误{User(argv[0]);//打印用户手册exit(1);}int sigatoi(argv[1]);int target_idatoi(argv[2]);int nkill(target_id,sig);//调用kill来对目标进程发送信号if(n!0)//失败打印错误码并退出{std::couterrno:strerror(errno)std::endl;exit(2);}return 0; } 运行效果  5.2.2 raise 调用该函数可以向调用它的进程发一个信号 其中sig参数表示要发送的信号编号 raise函数会向当前进程发送指定的信号并返回一个非零值表示成功返回0表示失败 下面是使用举例 #includeiostream #includeunistd.h #includesignal.h #includestring #includecstring #includecerrnovoid myhandler(int signo) {std::coutget a signal:signostd::endl; }int main() {signal(2,myhandler);//修改2号信号对应方法while(1){raise(2);//自己向自己发送2号信号sleep(1);}return 0; } 5.2.3 abort 该函数可以向调用它的进程发送6号信号最后终止进程 下面是实例演示 #includeiostream #includeunistd.h #includesignal.hvoid myhandler(int signo) {std::coutget a signal:signostd::endl; }int main() {signal(6,myhandler);//修改6号信号对应方法while(1){std::coutbeginstd::endl;abort();//自己向自己发送6号信号std::coutendstd::endl;}return 0; }5.3 由软件条件产生的信号 我们在进程间通信的管道一期中说到过当管道的读端被关闭时进程再向管道写入数据就变成了一件无意义的事情所以直接会向子进程传递13号信号SIGPIPE将写入进程杀掉原文地址【Linux】进程间通信——管道_linux任务间通信-CSDN博客 SIGPIPE就是一种由软件条件产生的信号 下面再来介绍alarm函数以及SIGALRM信号 5.3.1 alarm 调用alarm函数可以设定一个闹钟也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号该信号的默认处理动作是终止当前进程 这个函数的返回值是0或者是以前设定的闹钟时间还余下的秒数 下面我们来进行实例演示 #includeiostream #includeunistd.h #includesignal.h void myhandler(int signo) {std::coutget a signal:signostd::endl;exit(14); }int main() {signal(SIGALRM,myhandler);//修改SIGALRM信号对应方法alarm(5);//5秒后向进程发送SIGALRM信号int count0;while(1){std::coutcountstd::endl;sleep(1);}return 0; } 运行效果  下面我们来看一下函数的返回值仍然是不是以前设定的闹钟时间还余下的秒数 #includeiostream #includeunistd.h #includesignal.hvoid myhandler(int signo) {std::coutget a signal:signostd::endl;int nalarm(5);//收到信号后重置alarm函数std::coutn: nstd::endl; }int main() {signal(SIGALRM,myhandler);//修改SIGALRM信号对应方法alarm(5);//5秒后向进程发送SIGALRM信号while(1){sleep(1);}return 0; } 在该进程运行时我们向该进程发送14号信号  可以看到每次发送信号过后alarm返回的值都是以前设定的闹钟时间还余下的秒数 另外将alarm的传入参数seconds的值置为0表示取消以前设定的闹钟 下面来试试看 #includeiostream #includeunistd.h #includesignal.hvoid myhandler(int signo) {std::coutget a signal:signostd::endl;int nalarm(0);//取消以前设定的闹钟std::coutn: nstd::endl; }int main() {std::coutpid:getpid()std::endl;signal(SIGALRM,myhandler);//修改SIGALRM信号对应方法alarm(10);//10秒后向进程发送SIGALRM信号int count0;while(1){std::coutcountstd::endl;sleep(1);}return 0; } 我们在该进程运行的第6秒后向该进程发送了一个14号信号收到该信号后自定义的方法会重置取消之前的alarm最终导致之前设定的alarm过了10秒后也不会发送信号 5.4 硬件异常产生的信号 我们来看到下面的代码 #includeiostreamint main() {int a9;a/0;std::coutdivide end...std::endl;return 0; }运行结果 我们可以看到在a除以0时该进程收到信号终止了 这是由于a/0的结果会在cpu中计算出来并存储在寄存器当中但在cpu的内部有一个状态寄存器该寄存器会存储cpu近期计算结果是否出错溢出当OS检查到状态寄存器有错误时会立即向该进程发送8号信号从而进程终止了 下面我们修改一下一下8号信号对应的默认动作来验证一下 #includeiostream #includesignal.hvoid myhandler(int signo) {std::cout进程确实是收到了signo号信号std::endl; }int main() {signal(SIGFPE,myhandler);int a9;a/0;std::coutdivide end...std::endl;return 0; } 运行结果 可是奇怪的是为什么该进程会一直输出myhandler中对应的语句 原因是每当该进程被OS调度时OS都会检查到对应的状态寄存器出了问题这时会再向该进程发送8号信号导致了会一直执行myhandler方法 在C语言中还有一种常见的硬件异常产生的信号野指针问题 #includeiostreamint main() {int* pnullptr;*p100;std::cout野指针问题 ...std::endl;return 0; } 上面野指针的崩溃原因在于在cpu按照指针的所指向的地址寻址时有个硬件叫做MMU该硬件会将虚拟地址对应页表转换为物理地址在转化的过程中如果虚拟地址在页表没有对应的物理地址将会直接MMU硬件报错如果找到了对应的物理地址但是没有所需要的操作权限MMU也会直接报错OS检查到后会向该进程发送11号SIGSEGV信号 所以这是野指针造成进程崩溃的根本原因 六、核心转储 6.1 是什么核心转储 核心转储Core Dump是指在程序发生崩溃或异常终止时操作系统将程序的内存状态和各种调试信息保存到一个二进制文件中的过程。这个文件被称为核心转储文件或核心文件一般文件名为core.pid。 核心转储文件记录了程序在崩溃时的内存映像包括程序的当前状态、堆栈跟踪、寄存器状态等。它可以提供有关程序崩溃原因和状态的详细信息对于调试和分析程序错误非常有用。 通过核心转储文件我们可以使用调试器例如gdb来还原崩溃发生时的环境并查看程序在崩溃前的状态。调试器可以使用核心转储文件来定位错误发生的位置分析调用栈查找内存溢出、访问越界和其他错误的原因。这对于调试复杂的程序和确定造成崩溃的原因非常有帮助。 需要注意的是核心转储文件可能会包含敏感信息因此在共享或发布时需要格外小心。同时要使用核心转储文件进行调试通常需要使用适当的调试工具和符号文件以还原程序的调试符号信息和源代码行号等。 6.2 核心转储文件的产生 虚拟机下一般是可以看到核心转储文件的但是如果是在云服务器下该功能一般是被关闭的我们可以使用ulimit -a指令来查看Linux下各种类型文件大小的配置 其中第一个就是核心转储文件的大小配置信息为0字节 下面我们用ulimit -c指令将其设为需要的大小 下面我们模拟进程异常退出的场景看看会不会产生核心转储文件 #includeiostream #includeunistd.h #includesignal.hint main() {while(1){std::cout模拟异常退出中,pid:getpid()std::endl;sleep(1);}return 0; } 下面当进程再在运行时我们对应信号表中的信号向其发送不一样的信号看看会发生什么  先向其发送1号信号但是并没有产生核心存储文件 先向其发送2号信号但是也没有产生核心存储文件 再来试试三号信号这时产生了核心转储文件 总结一下上面的规律我们可以发现只有Action为Core的信号终止的进程才能产生核心转储文件 6.3 核心转储文件的使用举例 我们打开一个核心转储文件来看看 发现其存储的全都是二进制乱码所以该文件绝对不会是这样让我们看的 我们可以使用gdb调试工具来查看核心转储文件但是前提是该文件的形成进程是在debug环境下运行的否则我们无法获取到调试信息 下面我们来举例使用 #includeiostreamint main() {int* pnullptr;*p100;//野指针std::cout野指针问题 ...std::endl;return 0; } 我们现在运行上面代码形成的进程在debug环境下进行 可以看到该进程终止了并形成了一个核心转储文件  现在我们使用gdb来调试试试看对于gdb使用不熟悉的同学可以看到这里【Linux】工具5——gdb_linux make debug-CSDN博客 在进入gdb调试后我们输入core-file指令后面接上我们要查看的核心转储文件名 这样子就可以快速定位到程序出错的地方了该调试方法被称为事后调试 那为什么核心转储这么方便在云服务器中默认是被关闭的呢 这是因为云服务器属于生产环境在生产环境中一般是运行着为用户提供服务的程序的但不排除进程有挂掉的风险一旦挂掉就必须尽快重启维持服务这样在不断挂掉和重启的情况下会产生大量的核心转储文件会浪费大量的空间所以导致了在云服务器中该功能是默认被关闭的 6.4 waitpid中status参数的core dump标志位 我们现在回到之前进程控制一期博客【Linux】进程控制-CSDN博客中遗留下的问题 waitpid函数中输出型参数status的core dump标志位表示的就是该进程异常终止后有没有形成核心转储文件1为形成了0表示没形成 来段代码验证一下 #includeiostream #includeunistd.h #include sys/types.h #include sys/wait.hint main() {int idfork();if(id0){std::cout野指针问题 ...std::endl;std::cout野指针问题 ...std::endl;int* pnullptr;*p100;//野指针std::cout野指针问题 ...std::endl;std::cout野指针问题 ...std::endl;exit(0);}int status0;waitpid(id,status,0);std::coutexit code:((status8)0xFF)std::endl;std::coutexit signal:(status0x7F)std::endl;std::coutexit code:((status7)0x1)std::endl;return 0; } 运行效果 七、信号的保存 7.1 和信号相关的一些概念 我们先来介绍一些概念 ● 实际执行信号的处理动作称为信号递达(Delivery) ● 信号从产生到递达之间的状态,称为信号未决(Pending) ● 进程可以选择阻塞 (Block )某个信号 ● 被阻塞的信号产生时将保持在未决状态直到进程解除对此信号的阻塞才执行递达的动作 ● 注意阻塞和忽略是不同的只要信号被阻塞就不会递达而忽略是在递达之后可选的一种处理动作 7.2 信号在内核中的存储 在Linux中信号在PCB中存储结构有三种 pending表位图结构uint32_t。每个比特位的位置表示哪一个信号每个比特位的内容代表是否收到该信号 block表位图结构uint32_t。每个比特位的位置表示哪一个信号每个比特位的内容代表是否对应的信号该被阻塞 handler表函数指针数组void (*sighandler_t) (int) 。该数组的下标表示信号编号数组的特定下标的内容表示该信号的递达动作 这三种结构决定了信号产生后是怎么被进程保存并决定每种信号是否会被阻塞以及其递达动作 如果在进程解除对某信号的阻塞之前这种信号产生过多次将如何处理 在Linux中是这样处理的常规信号在递达之前产生多次只计一次而实时信号在递达之前产生多次可以依次放在一个队列里。本期不讨论实时信号。 7.3 sigset_t信号集 从上面的存储方式来看每个信号只有一个bit的未决标志非0即1不记录该信号产生了多少次阻塞标志也是这样表示的。 因此未决和阻塞标志可以用相同的数据类型sigset_t来存储sigset_t称为信号集这个类型可以表示每个信号的“有效”或“无效”状态在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞而在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态。 sigset_t的定义如下 # define _SIGSET_NWORDS (1024 / (8 * sizeof (unsigned long int))) typedef struct{unsigned long int __val[_SIGSET_NWORDS];} __sigset_t;typedef __sigset_t sigset_t; 我们可以看到该结构就是一个数组构成的位图 7.4 信号集操作函数 sigset_t类型对于每种信号用一个bit表示“有效”或“无效”状态至于这个类型内部如何存储从使用者的角度是不必过于关心的我们只能调用以下函数来操作sigset_ t变量 #include signal.hint sigemptyset (sigset_t *set); //作用清空信号集。参数指向要操作的信号集的指针。返回如果成功清空信号集返回0失败返回-1。int sigfillset (sigset_t *set); //作用将所有信号添加到信号集。参数指向要操作的信号集的指针。返回如果成功将所有信号添加到信号集返回0失败返回-1。int sigaddset (sigset_t *set, int signo); //作用向信号集中添加指定的信号。参数指向要操作的信号集的指针以及要添加的信号编号。返回如果成功将信号添加到信号集返回0失败返回-1。int sigdelset (sigset_t *set, int signo); //作用从信号集中删除指定的信号。参数指向要操作的信号集的指针以及要删除的信号编号。返回如果成功从信号集中删除信号返回0失败返回-1。int sigismember (const sigset_t *set, int signo); //作用检测指定的信号是否在信号集中。参数指向要操作的信号集的指针以及要检测的信号编号。返回如果指定的信号在信号集中返回1如果不在返回0如果出错返回-1。 函数sigemptyset初始化set所指向的信号集使其中所有信号的对应bit清零表示该信号集不包含 任何有效信号。 函数sigfillset初始化set所指向的信号集使其中所有信号的对应bit置位表示该信号集的有效信号包括系统支持的所有信号。 注意在使用sigset_ t类型的变量之前一定要调用sigemptyset或sigfillset做初始化使信号集处于确定的状态。初始化sigset_t变量之后就可以在调用sigaddset和sigdelset在该信号集中添加或删除某种有效信号。 7.4.1 sigprocmask 调用函数sigprocmask可以读取或更改进程的信号屏蔽字(阻塞信号集) 参数how指定了信号屏蔽字的操作方式可以是以下三个值之一 SIG_BLOCK将set中的信号添加到进程的当前信号屏蔽字中。SIG_UNBLOCK将set中的信号从进程的当前信号屏蔽字中移除。SIG_SETMASK将进程的当前信号屏蔽字设置为set中的值。 参数set指向要设置的新信号屏蔽字的信号集。 参数oset是一个可选参数指向一个用于存储原始信号屏蔽字的信号集。如果不为NULL则将进程的当前信号屏蔽字存储到oset中。 返回值如果成功返回0如果出错返回-1。 我们举例使用一下 #includeiostream #includeunistd.h #includesignal.hvoid showBlock(sigset_t *set) {int signo1;for(;signo31;signo){if(sigismember(set,signo))//通过sigismember函数查找signo信号是否在set信号集中std::cout1;elsestd::cout0;}std::coutstd::endl; }int main() {sigset_t set,oset;//定义信号集sigemptyset(set);//初始化设置信号集sigemptyset(oset);//初始化旧的信号集,用来接收sigprocmask函数返回的老信号集sigaddset(set,2);//向新信号集中增加2号信号sigprocmask(SIG_SETMASK,set,oset);//将阻塞信号集全部设置为set信号集while(1){showBlock(oset);//打印旧的信号集sleep(1);}return 0; }运行效果  我们可以看到在按下ctrlc后该进程阻塞了该信号并没有递达 7.4.2 sigpending 该函数可以查看进程当前的pending表 该函数接收一个指向sigset_t类型的输出参数型set并将当前进程接收到的信号集存储在该参数中 函数的返回值为0表示成功-1表示失败并设置errno以指示错误的原因 下面来演示一下使用 #includeiostream #includeunistd.h #includesignal.hvoid showBlock(sigset_t *set) {int signo1;std::cout现在pending表中存储的信号集为;for(;signo31;signo){if(sigismember(set,signo))//通过sigismember函数查找signo信号是否在set信号集中std::cout1;elsestd::cout0;}std::coutstd::endl; }void handler(int signo) {std::cout收到了2号信号std::endl; }int main() {sigset_t set,oset,pending;//定义信号集sigemptyset(set);//初始化设置信号集sigemptyset(oset);//初始化旧的信号集,用来接收sigprocmask函数返回的老信号集sigemptyset(pending);//初始化pending信号集,用来接收sigpending函数返回的信号集sigaddset(set,2);//向新信号集中增加2号信号sigprocmask(SIG_SETMASK,set,oset);//将阻塞信号集全部设置为set信号集signal(2,handler);//自定义2号信号方法int count0;while(1){sigpending(pending);//接受pending信号集showBlock(pending);//打印pending信号集sleep(1);if(count5){std::cout2号信号阻塞解除std::endl;sigprocmask(SIG_SETMASK,oset,set);//将阻塞信号集全部恢复为oset信号集}}return 0; } 八、信号的处理 我们在引入的时候说过信号的产生是异步的当前进程可能正在做更重要的事情所以在收到信号时并不一定会直接去处理需要等到合适的时候再处理 那什么时候是合适的时候呢 当进程从内核态切换回用户态的时候进程会在OS的指导下进行信号的检测与处理 8.1 信号处理的原理 8.1.1 用户态和内核态 我们来看到32位下的进程地址空间 可以看到在4GB内存的情况下0-3GB是属于用户空间的3-4GB是属于内核空间的 我们在之前博客中讲解的都是用户空间下的进程地址空间不熟悉的同学可以看到这里【Linux】进程地址空间-CSDN博客下面我们要仔细说说内核空间的进程地址空间 在所有的进程运行起来都有其自己的内核空间和用户空间其分别对应着两种不一样的页表指向不一样的物理空间 因为进程所执行的功能不一样所以每个进程的用户级别空间是独一无二的但是在同一个OS之下所有进程3-4GB的内核空间都是一样的也都可以看到同一张内核级页表这就意味着所以的进程可以通过统一的窗口看到同一个OS 那这样看来每个进程度可以看到OS的进程空间那OS运行的本质都是在各各不同进程之间的相同的内核进程地址空间中运行的 我们每一次在自己的代码中使用系统调用函数该进程都会到内核空间的地址当中进行函数跳转 综上所述简而言之当进程在跑用户进程空间中的代码时该进程就处于用户态当进程在跑内核进程空间中的代码时该进程就处于内核态 那OS是如何区分进程在跑哪部分的代码的呢 这是在CPU中有个名为CR3的寄存器该寄存器有多个存储状态3表示正在运行的进程执行的级别是用户态0表示正在运行的进程执行的级别是内核态 那每个进程都有内核空间的话岂不是每个进程都可以对OS进行访问了吗 并不是的只有当进程处于内核态时才能对内核空间进行访问 那谁来更改进程所处的状态级别呢 我们如果想要直接访问OS的内核空间是无法做到的但是OS提供的所有的系统调用内部在正式执行调用逻辑的时候会去修改执行级别 8.1.2 信号的捕捉 如果信号的处理动作是用户自定义函数在信号递达时就调用这个函数这称为捕捉信号 下图就是OS信号捕捉的全过程 为什么要返回用户态再执行信号的自定义方法呢难道是内核态的进程无法访问用户进程地址空间吗 并不是这样的内核态的进程权限是比用户态高的这是为了防止用户在自定义函数中进行一些非法操作 OS在检测信号时如果有多个信号需要处理此时OS会全部处理完吗 并不会OS在检测信号时有多个信号需要处理每次只处理一个剩下没有处理完的信号会轮到下一次 8.1.3 pending位图置0的时机 我们在sigpending函数的实际举例中可以发现当OS处理完一个信号后会将其pending结构中对应的位图置0那到底是在OS调用自定义函数之前就置0还是在调用完自定义函数之后置0呢 我们来段代码验证一下 #includeiostream #includeunistd.h #includesignal.hvoid showBlock(sigset_t *set) {int signo1;for(;signo31;signo){if(sigismember(set,signo))//通过sigismember函数查找signo信号是否在set信号集中std::cout1;elsestd::cout0;}std::coutstd::endl; }void handler(int signo) {sigset_t pending;sigemptyset(pending);//初始化pending信号集,用来接收sigpending函数返回的信号集sigpending(pending);//接受pending信号集std::cout收到了2号信号在handler函数内,pending表中存储的信号集为;showBlock(pending);//打印pending信号集 }int main() {sigset_t pending;sigemptyset(pending);//初始化pending信号集,用来接收sigpending函数返回的信号集signal(2,handler);//自定义2号信号方法while(1){std::cout在handler函数外,pending表中存储的信号集为;sigpending(pending);//接受pending信号集showBlock(pending);//打印pending信号集sleep(1);}return 0; }我们可以看到 OS调用自定义函数之前就将pending对应的位图置0了 8.2 sigaction sigaction函数的功能与signal函数类似但是多了一些更强大的细节 我们可以看到该函数有三个参数 ● signum传入要设置信号的编号。 ● act一个指向struct sigaction结构的指针用于指定对传入信号处理的方式。struct sigaction结构定义如下 struct sigaction {void (*sa_handler)(int);void (*sa_sigaction)(int, siginfo_t *, void *);sigset_t sa_mask;int sa_flags;void (*sa_restorer)(void); }; 该结构体中我们重点要注意的是sa_handler和sa_mask参数其他参数与实时信号有关我们可以默认置0。sa_handler接收要传入void(*)(int)类型的函数地址当收到sidnum信号时执行该方法sa_mask接收要在执行sa_handler方法时屏蔽的信号集在未执行完sa_handler方法时即使sa_mask信号集中的信号时会阻塞不进行处理 ● oldact输出型参数一个指向struct sigaction结构的指针用于保存之前的信号处理方式的信息。 下面是使用演示 #include iostream #include unistd.h #include signal.h #include cstringvoid showBlock(sigset_t *set) {int signo 1;for (; signo 31; signo){if (sigismember(set, signo)) // 通过sigismember函数查找signo信号是否在set信号集中std::cout 1;elsestd::cout 0;}std::cout std::endl; }void handler(int signo) {sigset_t pending;int count 20;while (count--){sigemptyset(pending); // 初始化pending信号集,用来接收sigpending函数返回的信号集sigpending(pending); // 接受pending信号集std::cout 执行2号信号对应方法pending表中存储的信号集;showBlock(pending); // 打印pending信号集sleep(1);} }int main() {struct sigaction set, oset;memset(set, 0, sizeof(struct sigaction));memset(oset, 0, sizeof(struct sigaction));set.sa_handler handler;sigemptyset(set.sa_mask);sigaddset(set.sa_mask, 2); // 在handler方法执行时屏蔽2号信号sigaddset(set.sa_mask, 3); // 在handler方法执行时屏蔽3号信号sigaddset(set.sa_mask, 4); // 在handler方法执行时屏蔽4号信号sigaction(1, set, oset); // 自定义1号信号方法while (1){std::cout pid: getpid() std::endl;sleep(1);}return 0; } 我们可以看到在1号信号执行对应方法时我们向该进程发送2/3/4在set.sa_mask信号集中被屏蔽的信号时是无法做出反应的 本期博客到这里就结束了哦内容较多如有纰漏还请各位指出呀 最后祝大家新年快乐万事胜意~
http://www.dnsts.com.cn/news/266201.html

相关文章:

  • 网站登录流程在putty上怎样安装wordpress
  • 本地拖拽网站建设php wordpress 目录
  • 网站底部有很多图标临潼微网站建设
  • 有关网站排名的论文wordpress前大
  • 宁波网站制作建设网站正在建设中...为什么护卫神
  • 建立网站需要多少钱费用沈阳制作网站的公司
  • 网站推广软件哪个最好厦门seo收费
  • 设计网站设计目标安装wordpress程序
  • WordPress找不到站点谷歌搜索引擎
  • wordpress 做购物网站网站建设简述需求分析的基本概念及内容
  • o基础学建网站花西子品牌营销策略研究论文
  • 常州承接网站建设北美跨境电商平台有哪些
  • asp.net企业网站建设wordpress建立企业网站
  • 江门做网站设计如何为wordpress添加ico小图标logo
  • 帮人做网站收费合法吗网站建设招标范文
  • 网站建设宁波企业运营能力指标
  • 北京网站制作公司招聘营销活动方案策划
  • 移动端网站开发前端模板如何查询网站备案号
  • 门户网站开发软件南京银城建设 网站
  • 如何做旅游网站品牌网站建设搜搜磐石网络
  • 用KEGG网站做通路富集分析法国注册公司流程和费用
  • 在线查询网站开发语言国内类似wordpress平台
  • 专业网站开发公司如何注册一个平台
  • 上线了做的网站可以登陆网站开发合同.doc
  • seo网站优化软件价格学院网站建设管理制度
  • 凡科网站建设平台最新版高性能网站建设指南
  • 怎么学会建自己网站的方法公司主页网站开发
  • 常用的软件下载网站四川网络营销
  • 网站建设淘宝评价公司logo设计图片免费
  • 建站前端模板国外手机模板网站推荐