沈阳模板建站公司推荐网站空间试用

张小明 2026/1/12 1:23:50
沈阳模板建站公司推荐,网站空间试用,校园推广活动,高级seo课程目录 4. 信号捕捉4.1 信号捕捉的流程#xff08;CPU在用户态与内核态之间切换#xff09;4.2 操作系统是怎么运行的4.2.1 硬件中断4.2.2 时钟中断4.2.3 死循环4.2.4 软中断4.2.5 缺页中断#xff1f;内存碎片处理#xff1f;除零野指针错误#xff1f; 4.3 用户态和内核态…目录4. 信号捕捉4.1 信号捕捉的流程CPU在用户态与内核态之间切换4.2 操作系统是怎么运行的4.2.1 硬件中断4.2.2 时钟中断4.2.3 死循环4.2.4 软中断4.2.5 缺页中断内存碎片处理除零野指针错误4.3 用户态和内核态4.4 sigaction-更改进程在接收到特定信号时所采取的操作5. 可重入函数重复进入6. volatile保持内存可见性-防止优化7. SIGCHLD信号 - 了解4. 信号捕捉当前阶段信号的处理不是立即处理而是可以等一会再处理即合适的时候进行信号的处理。4.1 信号捕捉的流程CPU在用户态与内核态之间切换合适的时候是指什么时候答进程从内核态返回到用户态时进行信号检查对应上图的2补1内核态用户调用系统调用后OS执行系统调用OS执行系统调用所处的模式就是内核态。补2用户态OS执行用户所写的代码时此时OS的模式就是用户态。上图上半部分执行时是用户态下半部分执行时是内核态示例1如果信号的处理动作是忽略则在第3步之后直接返回main函数。示例2如果信号的处理动作是默认则在第3步之后执行默认动作。暂停就使进程停在某一步终止就直接杀死进程示例3如果信号的处理动作是用户自定义函数在信号递达时就调用这个函数这称为捕捉信号。由于信号处理函数的代码是在用户空间的处理过程比较复杂举例如下:信号捕捉过程用户态内核态切换时机用户程序注册了SIGQUIT 信号的处理函数sighandler 。当前正在执行 main 函数,这时发生中断或异常切换到内核态。在中断处理完毕后要返回用户态的main 函数之前检查到有信号SIGQUIT 递达。内核决定返回用户态后不是恢复main 函数的上下文继续执行而是执行 sighandler 函数, sighandler 和main 函数使用不同的堆栈空间它们之间不存在调用和被调用的关系是两个独立的控制流程。sighandler 函数返回后自动执行特殊的系统调用 sigreturn 再次进入内核态。如果没有新的信号要递达这次再返回用户态就是恢复main 函数的上下文继续执行了。问while(true) {}这种代码在我使用Ctrlc时也会进入内核态吗答会的因为CPU的进程调度方式每次执行到时间片为0时会切换进程继续运行。4.2 操作系统是怎么运行的4.2.1 硬件中断1.OS怎么知道键盘上面有数据了答硬件中断硬件输入设备和CPU有硬件连接目的是为了中断触发。中断向量表就是操作系统的一部分启动就加载到内存中了通过外部硬件中断操作系统就不需要对外设进行任何周期性的检测或者轮询由外部设备触发的中断系统运行流程叫做硬件中断Linux操作系统内核源代码注册中断向量表// Linux内核0.11源码voidtrap_init(void){inti;set_trap_gate(0,divide_error);// 设置除操作出错的中断向量值。以下雷同。set_trap_gate(1,debug);set_trap_gate(2,nmi);set_system_gate(3,int3);/* int3-5 can be called from all */set_system_gate(4,overflow);set_system_gate(5,bounds);set_trap_gate(6,invalid_op);set_trap_gate(7,device_not_available);set_trap_gate(8,double_fault);set_trap_gate(9,coprocessor_segment_overrun);set_trap_gate(10,invalid_TSS);set_trap_gate(11,segment_not_present);set_trap_gate(12,stack_segment);set_trap_gate(13,general_protection);set_trap_gate(14,page_fault);set_trap_gate(15,reserved);set_trap_gate(16,coprocessor_error);// 下面将int17-48 的陷阱门先均设置为reserved以后每个硬件初始化时会重新设置自己的陷阱门。for(i17;i48;i)set_trap_gate(i,reserved);set_trap_gate(45,irq13);// 设置协处理器的陷阱门。outb_p(inb_p(0x21)0xfb,0x21);// 允许主8259A 芯片的IRQ2 中断请求。outb(inb_p(0xA1)0xdf,0xA1);// 允许从8259A 芯片的IRQ13 中断请求。set_trap_gate(39,parallel_interrupt);// 设置并行口的陷阱门。}voidrs_init(void){set_intr_gate(0x24,rs1_interrupt);// 设置串行口1 的中断门向量(硬件IRQ4信号)。set_intr_gate(0x23,rs2_interrupt);// 设置串行口2 的中断门向量(硬件IRQ3信号)。init(tty_table[1].read_q.data);// 初始化串行口1(.data 是端口号)。init(tty_table[2].read_q.data);// 初始化串行口2。outb(inb_p(0x21)0xE7,0x21);// 允许主8259A 芯片的IRQ3IRQ4 中断信号请求。}中断 VS 信号发中断 — 发信号保存终端号 — 记录信号中断号 — 信号编号处理终端 — 处理信号自定义捕捉区别信号纯软件本质是用软件来模拟硬件中断的4.2.2 时钟中断2.当没有中断到来的时候OS在做什么什么都没做OS是暂停的// 内核源代码的main函数中for(;;)pause();问题进程可以在操作系统的指挥下被调度被执行那么操作系统自己被谁指挥被谁推动执行呢外部设备可以触发硬件中断但是这个是需要用户或者设备自己触发有没有自己可以定期触发的设备?前提进程调度这个方法也在OS的中断向量表中。操作系统运行流程时钟源会以固定的、特定的频率向CPU发送特定的中断。操作系统OS每隔这个固定的时间就会执行进程调度的方法。操作系统就在硬件时钟中断的驱动下进行调度了。时钟源原来在CPU的外部为了提高效率现今已经集成到了CPU内部即CPU自己每隔固定的时间自己触发中断执行进程调度的函数CPU的主频就是CPU中断触发的固定频率时间片在进程控制块struct tast_struct 中有一个计数器用来进行时间片计数当计数器减到0时即为时间片耗尽执行进程调度服务schedule()。时钟源每1时间片计数器就对应的-1。时间片的本质就是计数器。current-count--;if(current-count0){schedule();}离线计时这个频率还可以给计算机进行离线计时即电脑关机之后再次开机仍然是准确的时间。有一个变量将时间抓换成时间戳再将时间戳转换成历史总频率再由CPU的固定频率始终在计算当前时刻总结操作系统就是基于中断进行工作的软件。回看2.5.3小节使用示例2模拟操作系统运行方式。Linux操作系统内核代码如下操作系统执行进程调度流程// Linux 内核0.11// main.c有以下函数sched_init();// 调度程序初始化(加载了任务0 的tr, ldtr) kernel/sched.c// 1.调度程序的初始化子程序。voidsched_init(void){...set_intr_gate(0x20,timer_interrupt);// 修改中断控制器屏蔽码允许时钟中断。outb(inb_p(0x21)~0x01,0x21);// 设置系统调用中断门。set_system_gate(0x80,system_call);...}// 2.system_call.s_timer_interrupt:...;// do_timer(CPL)执行任务切换、计时等工作在kernel/shched.c,305 行实现。call _do_timer;// do_timer(long CPL) does everything from// 3.调度入口voiddo_timer(longcpl){...schedule();}// 4.进程切换voidschedule(void){...switch_to(next);// 切换到任务号为next 的任务并运行之。}4.2.3 死循环如果是这样操作系统不就可以躺平了吗对操作系统自己不做任何事情需要什么功能就向中断向量表里面添加方法即可。操作系统的本质就是一个死循环voidmain(void)/* 这里确实是void并没错。 */{/* 在startup 程序(head.s)中就是这样假设的。 */.../* * 注意!! 对于任何其它的任务pause()将意味着我们必须等待收到一个信号才会返 * 回就绪运行态但任务0task0是唯一的意外情况参见schedule()因为任 * 务0 在任何空闲时间里都会被激活当没有其它任务在运行时 * 因此对于任务0pause()仅意味着我们返回来查看是否有其它任务可以运行如果没 * 有的话我们就回到这里一直循环执行pause()。 */for(;;)pause();}// end main这样操作系统就可以在硬件时钟的推动下自动调度了.所以什么是时间片CPU为什么会有主频为什么主频越快CPU越快主频可以作为OS调度执行速度的参考之一4.2.4 软中断上述外部硬件中断需要硬件设备触发。有没有可能因为软件原因也触发上面的逻辑有为了让操作系统支持进行系统调用CPU也设计了对应的汇编指令(int 或者 syscall),可以让CPU内部触发中断逻辑。前提当我们进行系统调用的时候具体是怎么进入操作系统完成系统调用过程的毕竟CPU只有一个- 系统调用表fn_ptr sys_call_table[]每个系统调用都有一个唯一的下标这个下标叫做系统调用号内核中的用户层怎么把系统调用号给操作系统 - 寄存器(比如EAX)操作系统怎么把返回值给用户- 寄存器或者用户传入的缓冲区地址总结系统调用的过程其实就是先int 0x80、syscall陷入内核本质就是触发软中断CPU就会自动执行系统调用的处理方法而这个方法会根据系统调用号自动查表执行对应的方法。补问题我们本不就是直接使用的系统调用吗为什么还要转换成系统调用号来让操作系统执行指定的系统调用呢答OS不提供任何系统调用接口OS只提供系统调用号而我们使用的open()、fork()等所谓的系统调用都是经过glibc封装过的系统调用号的本质数组下标系统调用流程举例用户调用系统调用intopen(constchar*pathname,intflags);pid_tfork(void);OS将通过寄存器eax系统调用转汇编move eax2// 2就是open系统调用的系统调用号int0x80int 0x80触发软中断执行以下函数// int 0x80、syscall触发软中断就跳入以下函数voidCallSystem(){// 1.获取系统调用号n /* int n 0; move n eax */// 2.调用系统调用方法当然肯定要做安全检测sys_call_table[n]();}sys_call_table(n)根据系统调用号查表执行对应的系统调用函数。// sys.h// 系统调用函数指针表。用于系统调用中断处理程序(int 0x80)作为跳转表。externintsys_setup();// 系统启动初始化设置函数。 (kernel/blk_drv/hd.c,71)externintsys_exit();// 程序退出。 (kernel/exit.c, 137)externintsys_fork();// 创建进程。 (kernel/system_call.s, 208)externintsys_read();// 读文件。 (fs/read_write.c, 55)externintsys_write();// 写文件。 (fs/read_write.c, 83)externintsys_open();// 打开文件。 (fs/open.c, 138)externintsys_close();// 关闭文件。 (fs/open.c, 192)externintsys_waitpid();// 等待进程终止。 (kernel/exit.c, 142)externintsys_creat();// 创建文件。 (fs/open.c, 187)externintsys_link();// 创建一个文件的硬连接。 (fs/namei.c, 721)externintsys_unlink();// 删除一个文件名(或删除文件)。 (fs/namei.c, 663)externintsys_execve();// 执行程序。 (kernel/system_call.s, 200)externintsys_chdir();// 更改当前目录。 (fs/open.c, 75)externintsys_time();// 取当前时间。 (kernel/sys.c, 102)externintsys_mknod();// 建立块/字符特殊文件。 (fs/namei.c, 412)externintsys_chmod();// 修改文件属性。 (fs/open.c, 105)externintsys_chown();// 修改文件宿主和所属组。 (fs/open.c, 121)externintsys_break();// (-kernel/sys.c, 21)externintsys_stat();// 使用路径名取文件的状态信息。 (fs/stat.c, 36)externintsys_lseek();// 重新定位读/写文件偏移。 (fs/read_write.c, 25)externintsys_getpid();// 取进程id。 (kernel/sched.c, 348)externintsys_mount();// 安装文件系统。 (fs/super.c, 200)externintsys_umount();// 卸载文件系统。 (fs/super.c, 167)externintsys_setuid();// 设置进程用户id。 (kernel/sys.c, 143)externintsys_getuid();// 取进程用户id。 (kernel/sched.c, 358)externintsys_stime();// 设置系统时间日期。 (-kernel/sys.c, 148)externintsys_ptrace();// 程序调试。 (-kernel/sys.c, 26)externintsys_alarm();// 设置报警。 (kernel/sched.c, 338)externintsys_fstat();// 使用文件句柄取文件的状态信息。(fs/stat.c, 47)externintsys_pause();// 暂停进程运行。 (kernel/sched.c, 144)externintsys_utime();// 改变文件的访问和修改时间。 (fs/open.c, 24)externintsys_stty();// 修改终端行设置。 (-kernel/sys.c, 31)externintsys_gtty();// 取终端行设置信息。 (-kernel/sys.c, 36)externintsys_access();// 检查用户对一个文件的访问权限。(fs/open.c, 47)externintsys_nice();// 设置进程执行优先权。 (kernel/sched.c, 378)externintsys_ftime();// 取日期和时间。 (-kernel/sys.c,16)externintsys_sync();// 同步高速缓冲与设备中数据。 (fs/buffer.c, 44)externintsys_kill();// 终止一个进程。 (kernel/exit.c, 60)externintsys_rename();// 更改文件名。 (-kernel/sys.c, 41)externintsys_mkdir();// 创建目录。 (fs/namei.c, 463)externintsys_rmdir();// 删除目录。 (fs/namei.c, 587)externintsys_dup();// 复制文件句柄。 (fs/fcntl.c, 42)externintsys_pipe();// 创建管道。 (fs/pipe.c, 71)externintsys_times();// 取运行时间。 (kernel/sys.c, 156)externintsys_prof();// 程序执行时间区域。 (-kernel/sys.c, 46)externintsys_brk();// 修改数据段长度。 (kernel/sys.c, 168)externintsys_setgid();// 设置进程组id。 (kernel/sys.c, 72)externintsys_getgid();// 取进程组id。 (kernel/sched.c, 368)externintsys_signal();// 信号处理。 (kernel/signal.c, 48)externintsys_geteuid();// 取进程有效用户id。 (kenrl/sched.c, 363)externintsys_getegid();// 取进程有效组id。 (kenrl/sched.c, 373)externintsys_acct();// 进程记帐。 (-kernel/sys.c, 77)externintsys_phys();// (-kernel/sys.c, 82)externintsys_lock();// (-kernel/sys.c, 87)externintsys_ioctl();// 设备控制。 (fs/ioctl.c, 30)externintsys_fcntl();// 文件句柄操作。 (fs/fcntl.c, 47)externintsys_mpx();// (-kernel/sys.c, 92)externintsys_setpgid();// 设置进程组id。 (kernel/sys.c, 181)externintsys_ulimit();// (-kernel/sys.c, 97)externintsys_uname();// 显示系统信息。 (kernel/sys.c, 216)externintsys_umask();// 取默认文件创建属性码。 (kernel/sys.c, 230)externintsys_chroot();// 改变根系统。 (fs/open.c, 90)externintsys_ustat();// 取文件系统信息。 (fs/open.c, 19)externintsys_dup2();// 复制文件句柄。 (fs/fcntl.c, 36)externintsys_getppid();// 取父进程id。 (kernel/sched.c, 353)externintsys_getpgrp();// 取进程组id等于getpgid(0)。(kernel/sys.c, 201)externintsys_setsid();// 在新会话中运行程序。 (kernel/sys.c, 206)externintsys_sigaction();// 改变信号处理过程。 (kernel/signal.c, 63)externintsys_sgetmask();// 取信号屏蔽码。 (kernel/signal.c, 15)externintsys_ssetmask();// 设置信号屏蔽码。 (kernel/signal.c, 20)externintsys_setreuid();// 设置真实与/或有效用户id。 (kernel/sys.c,118)externintsys_setregid();// 设置真实与/或有效组id。 (kernel/sys.c, 51)// 系统调用函数指针表。用于系统调用中断处理程序(int 0x80)作为跳转表。fn_ptr sys_call_table[]{sys_setup,sys_exit,sys_fork,sys_read,sys_write,sys_open,sys_close,sys_waitpid,sys_creat,sys_link,sys_unlink,sys_execve,sys_chdir,sys_time,sys_mknod,sys_chmod,sys_chown,sys_break,sys_stat,sys_lseek,sys_getpid,sys_mount,sys_umount,sys_setuid,sys_getuid,sys_stime,sys_ptrace,sys_alarm,sys_fstat,sys_pause,sys_utime,sys_stty,sys_gtty,sys_access,sys_nice,sys_ftime,sys_sync,sys_kill,sys_rename,sys_mkdir,sys_rmdir,sys_dup,sys_pipe,sys_times,sys_prof,sys_brk,sys_setgid,sys_getgid,sys_signal,sys_geteuid,sys_getegid,sys_acct,sys_phys,sys_lock,sys_ioctl,sys_fcntl,sys_mpx,sys_setpgid,sys_ulimit,sys_uname,sys_umask,sys_chroot,sys_ustat,sys_dup2,sys_getppid,sys_getpgrp,sys_setsid,sys_sigaction,sys_sgetmask,sys_ssetmask,sys_setreuid,sys_setregid};// 调度程序的初始化子程序。voidsched_init(void){...// 设置系统调用中断门。set_system_gate(0x80,system_call);}// 内核中汇编代码执行系统调用117行call跳转由_sys_call_table4*eac (起始地址4字节*eac偏移量)_system_call:cmp eax,nr_system_calls-1;// 调用号如果超出范围的话就在eax 中置-1 并退出。ja bad_sys_call push ds;// 保存原段寄存器值。push es push fs push edx;// ebx,ecx,edx 中放着系统调用相应的C 语言函数的调用参数。push ecx;// push %ebx,%ecx,%edx as parameterspush ebx;// to the system callmov edx,10h;// set up ds,es to kernel spacemov ds,dx;// ds,es 指向内核数据段(全局描述符表中数据段描述符)。mov es,dx mov edx,17h;// fs points to local data spacemov fs,dx;// fs 指向局部数据段(局部描述符表中数据段描述符)。;// 下面这句操作数的含义是调用地址 _sys_call_table %eax * 4。参见列表后的说明。;// 对应的C 程序中的sys_call_table 在include/linux/sys.h 中其中定义了一个包括72个;// 系统调用C 处理函数的地址数组表。call[_sys_call_tableeax*4]push eax;// 把系统调用号入栈。mov eax,_current;// 取当前任务进程数据结构地址??eax。;// 下面97-100 行查看当前任务的运行状态。如果不在就绪状态(state 不等于0)就去执行调度程序。;// 如果该任务在就绪状态但counter[??]值等于0则也去执行调度程序。cmp dword ptr[stateeax],0;// statejne reschedule cmp dword ptr[countereax],0;// counterje reschedule;// 以下这段代码执行从系统调用C 函数返回后对信号量进行识别处理。ret_from_sys_call:4.2.5 缺页中断内存碎片处理除零野指针错误voidtrap_init(void){inti;set_trap_gate(0,divide_error);// 设置除操作出错的中断向量值。以下雷同。set_trap_gate(1,debug);set_trap_gate(2,nmi);set_system_gate(3,int3);/* int3-5 can be called from all */set_system_gate(4,overflow);set_system_gate(5,bounds);set_trap_gate(6,invalid_op);set_trap_gate(7,device_not_available);set_trap_gate(8,double_fault);set_trap_gate(9,coprocessor_segment_overrun);set_trap_gate(10,invalid_TSS);set_trap_gate(11,segment_not_present);set_trap_gate(12,stack_segment);set_trap_gate(13,general_protection);set_trap_gate(14,page_fault);set_trap_gate(15,reserved);set_trap_gate(16,coprocessor_error);// 下面将int17-48 的陷阱门先均设置为reserved以后每个硬件初始化时会重新设置自己的陷阱门。for(i17;i48;i)set_trap_gate(i,reserved);set_trap_gate(45,irq13);// 设置协处理器的陷阱门。outb_p(inb_p(0x21)0xfb,0x21);// 允许主8259A 芯片的IRQ2 中断请求。outb(inb_p(0xA1)0xdf,0xA1);// 允许从8259A 芯片的IRQ13 中断请求。set_trap_gate(39,parallel_interrupt);// 设置并行口的陷阱门。}缺页中断内存碎片处理除零野指针错误这些问题全部都会被转换成为CPU内部的软中断然后走中断处理例程完成所有处理。有的是进行申请内存填充页表进行映射的。有的是用来处理内存碎片的有的是用来给目标进行发送信号杀掉进程等等。 所以操作系统就是躺在中断处理例程上的代码块CPU内部的软中断比如int 0x80或者syscall我们叫做 陷阱CPU内部的软中断比如除零/野指针等我们叫做 异常。所以能理解“缺页异常”为什么这么叫了吗4.3 用户态和内核态结论操作系统无论怎么切换进程都能找到同一个操作系统换句话说操作系统的系统调用方法的执行是在进程的地址空间中执行的用户和内核都在同一个[0, 4]GB的虚拟地址空间上。用户态就是执行用户[0,3]GB时所处的状态用户态以用户身份智能访问自己的[0, 3]GB。内核态就是执行内核[3,4]GB时所处的状态内核态以内核的身份通过系统调用的方式访问OS[3,4]GB区分就是按照CPU内的CPL决定CPL的全称是Current Privilege Level即当前特权级别。一般执行 int 0x80 或者syscall 软中断CPL会在校验之后自动变更关于特权级别涉及到段段描述符段选择子DPLCPLRPL等概念 而现在芯片为了保证兼容性已经非常复杂了进而导致OS也必须得照顾它的复杂性这块我们不做深究了。4.4 sigaction-更改进程在接收到特定信号时所采取的操作#includesignal.hintsigaction(intsigno,conststructsigaction*act,structsigaction*oact);返回值sigaction函数可以读取和修改与指定信号相关联的处理动作。调用成功则返回0出错则返回-1。参数signo是指定信号的编号。若act指针非空则根据act修改该信号的处理动作。若oact指针非空则通过oact传出该信号原来的处理动作。act和oact指向sigaction结构体:const struct sigaction *act结构体参数将其中的sa_handler赋值为常数SIG_IGN传给sigaction表示忽略信号赋值为常数SIG_DFL表示执行系统默认动作赋值为一个函数指针表示用自定义函数捕捉信号或者说向内核注册了一个信号处理函数该函数返回值为void可以带一个int参数通过参数可以得知当前信号的编号这样就可以用同一个函数处理多种信号。显然这也是一个回调函数不是被main函数调用而是被系统所调用。当某个信号的处理函数被调用时内核自动将当前信号加入进程的信号屏蔽字当信号处理函数返回时自动恢复原来的信号屏蔽字这样就保证了在处理某个信号时如果这种信号再次产生那么它会被阻塞到当前处理结束为止。如果在调用信号处理函数时除了当前信号被自动屏蔽之外还希望自动屏蔽另外一些信号则用sa_mask字段同样为*act结构体中的成员变量说明这些需要额外屏蔽的信号当信号处理函数返回时自动恢复原来的信号屏蔽字。sa_flags字段包含一些选项本章的代码都把sa_flags设为0sa_sigaction是实时信号的处理函数本章不详细解释这两个字段有兴趣的同学可以在了解一下。5. 可重入函数重复进入main函数调用insert函数向一个链表head中插入节点node1插入操作分为两步刚做完第一步的时候因为硬件中断使进程切换到内核再次回用户态之前检查到有信号待处理于是切换 到sighandler函数sighandler也调用insert函数向同一个链表head中插入节点node2插入操作的两步都做完之后从sighandler返回内核态再次回到用户态就从main函数调用的insert函数中继续往下执行先前做第一步之后被打断现在继续做完第二步。结果是main函数和sighandler先后向链表中插入两个节点而最后只有一个节点真正插入链表中了。像上例这样insert函数被不同的控制流程调用有可能在第一次调用还没返回时就再次进入该函数这称为重入insert函数访问一个全局链表有可能因为重入而造成错乱像这样的函数称为不可重入函数反之如果一个函数只访问自己的局部变量或参数则称为可重入(Reentrant) 函数。想一下为什么两个不同的控制流程调用同一个函数访问它的同一个局部变量或参数就不会造成错乱如果一个函数符合以下条件之一则是不可重入的:调用了malloc或free因为malloc也是用全局链表来管理堆的。调用了标准I/O库函数。标准I/O库的很多实现都以不可重入的方式使用全局数据结构。可重入条件函数只有自己的临时变量等情况。6. volatile保持内存可见性-防止优化该关键字在C当中我们已经有所涉猎今天我们站在信号的角度重新理解一下#includeiostream#includeunistd.h#includesignal.h#includecstdlibintflag0;// volatile int flag 0; // 保持内存空间可见性voidhandler(intsignu){std::cout更改全局变量,flag-1std::endl;flag1;}intmain(){signal(2,handler);while(!flag);std::coutprocess quit normal!std::endl;return0;}$ g test.cc $ a.out#Ctrlc#标准情况下键入Ctrlc,2号信号被捕捉,执行自定义动作,修改flag1,while条件不满足,退出循环,进程退出.$ g test.cc -O1# 后缀 -OX,此处为英文字母大写O和数字表示编译器几级优化#优化情况下,键入Ctrl,2号信号被捕捉,执行自定义动作,修改 flag1,但是while条件依旧满足,进程继续运行但是很明显flag肯定已经被修改了,但是为何循环依旧执行很明显,while循环检查的flag,并不是内存中最新的flag这就存在了数据二异性的问题。while检测的flag其实已经因为优化,被放在了CPU寄存器当中。如何解决呢很明显需要volatile# 使用volatile修饰flag# 再次使用优化编译、执行、Ctrlc可见while条件不满足退出循环进程退出。volatile作用保持内存的可见性告知编译器被该关键字修饰的变量不允许被优化对该变量的任何操作都必须在真实的内存中进行操作。7. SIGCHLD信号 - 了解进程一章讲过用wait和waitpid函数清理僵尸进程父进程可以阻塞等待子进程结束也可以非阻塞地查询是否有子进程结束等待清理(也就是轮询的方式)。采用第一种方式父进程阻塞了就不 能处理自己的工作了采用第二种方式父进程在处理自己的工作的同时还要记得时不时地轮询一 下程序实现复杂。其实子进程在终止时会给父进程发SIGCHLD信号该信号的默认处理动作是忽略父进程可以自定义SIGCHLD信号的处理函数这样父进程只需专心处理自己的工作不必关心子进程了子进程终止时会通知父进程父进程在信号处理函数中调用wait清理子进程即可。请编写一个程序完成以下功能父进程fork出子进程子进程调用exit(2)终止父进程自定义SIGCHLD信号的处理函数在其中调用wait获得子进程的退出状态并打印。(见代码1)事实上由于UNIX 的历史原因要想不产生僵尸进程还有另外一种办法父进程调用sigaction将SIGCHLD的处理动作置为SIG_IGN这样fork出来的子进程在终止时会自动清理掉不会产生僵尸进程也不会通知父进程。系统默认的忽略动作和用户用sigaction函数自定义的忽略通常是没有区别的但这是一个特例。此方法对于Linux可用但不保证在其它UNIX系统上都可用。请编写程序验证这样做不会产生僵尸进程。见代码2作用可以通过信号捕捉的方案回收子进程代码1#includeiostream#includeunistd.h#includesignal.h#includecstdlib#includesignal.h#includesys/types.h#includesys/wait.h// 作用可以通过信号捕捉的方案回收子进程voidWaitAll(intnum){while(true){pid_t nwaitpid(-1,nullptr,WNOHANG);// 非阻塞等待if(n0){break;}elseif(n0){std::coutwaitpid errorstd::endl;break;}}std::coutfather get a signal: numstd::endl;}intmain(){// 父进程signal(SIGCHLD,WaitAll);// 父进程for(inti0;i10;i){pid_t idfork();if(id0){std::cout I am child, exitstd::endl;sleep(3);exit(3);}}while(true){std::coutI am father, exitstd::endl;sleep(1);}return0;}代码2#includeiostream#includeunistd.h#includesignal.h#includecstdlib#includesignal.h#includesys/types.h#includesys/wait.h// 作用可以将子进程退出给父进程发送的SIGCHLD信号重定义为SIG_IGN自动回收子进程intmain(){// 父进程signal(SIGCHLD,SIG_IGN);// 父进程for(inti0;i10;i){pid_t idfork();if(id0){sleep(3);std::cout I am child, exitstd::endl;exit(3);}}while(true){std::coutI am father, exitstd::endl;sleep(1);}return0;}此处设置的SIG_IGN和默认的信号处理动作不同默认信号处理动作是SIG_DEF。
版权声明:本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

服务器如何配置php网站wordpress hooks

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容: 请快速生成一个可用于原型开发的vue.config.js配置,要求:1. 配置热重载 2. 设置/api代理到本地3000端口 3. 允许跨域 4. 配置ESLint自动修复 5. 添加vue-rou…

张小明 2026/1/10 10:05:20 网站建设

农村网站建设工作号做文案素材的网站

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

张小明 2026/1/11 16:23:13 网站建设

哪些网站可以做网站wps上怎么做网站点击分析表

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容: 开发一个firewall-cmd效率工具包,包含:1) 常用配置一键脚本 2) 规则备份与恢复工具 3) 配置差异比较功能 4) 批量操作接口。工具应支持将复杂规则集转化为简…

张小明 2026/1/11 23:19:17 网站建设

常规网站建设内容唐山专业网站建设公司

BlenderMCP像素艺术转换:从3D模型到复古游戏资产的终极指南 【免费下载链接】blender-mcp 项目地址: https://gitcode.com/GitHub_Trending/bl/blender-mcp 你是否渴望将精美的3D模型转化为充满复古魅力的像素风格资产?BlenderMCP(Bl…

张小明 2026/1/11 13:59:28 网站建设

学做网站要什么学历孩子发烧反反复复不退烧怎么办

Windows Server 2012性能调优与监控全攻略 1. 系统警告处理 当系统出现警告时,它会告知问题并提供解决步骤。以硬件驱动故障导致的警告为例,系统健康报告通常会给出以下通用建议: 1. 验证是否安装了正确的驱动程序。 2. 尝试使用Windows Update更新驱动程序。 3. 向制造…

张小明 2026/1/10 5:24:55 网站建设

网站建设需要哪些常用技术wordpress网页树叶特效

文章介绍了如何通过微调Embedding模型解决RAG系统在垂直领域"搜不准"的问题。核心是使用"硬负例"(字面相似但语义不同的文档)进行训练,可显著提升模型区分度。文章详细展示了3090单卡微调全流程:使用LLMBM25自动挖掘硬负例数据&…

张小明 2026/1/10 2:52:16 网站建设