Linux系统——进程信号之保存和捕捉信号

发布时间:2026/10/28 18:57:47
Linux系统——进程信号之保存和捕捉信号 ‍♂️个人主页进击的荆棘作者其它专栏《数据结构与算法》《算法》《C起始之路》《Linux》目录1.信号前导2.产生信号3.保存信号4.捕捉信号5.可重入函数6.volatile7.SIGCHLD信号8.附录3.保存信号3.1信号相关概念●实际执行信号的处理动作称为信号递达Delivery●信号从产生到递达之间的状态称为信号未决Pending●进程可以选择阻塞Block某个信号●被阻塞的信号产生时将保持在未决状态直到进程接触对此信号的阻塞才会执行递达的动作●注阻塞和忽略是不同的只有信号被阻塞就不会递达而忽略是在递达之后可选的一种处理动作3.2在内核中的表示信号在内核中的表示示意图●每个信号都有两个标志位分别表示阻塞block和未决pending还有一个函数指针表示处理动作。信号产生时内核在进程控制块中设置该信号的未决标志直到信号递达才清除该标志。在上图例子中SIGHUP信号未阻塞也未产生过当它递达时执行默认处理动作●SIGINT信号产生过但正在被阻塞所以暂时不能递达。虽然它的处理动作是忽略但在没有解除阻塞之前不能忽略这个信号因为进程仍有机会改变处理动作之后再解除阻塞●SIGQUIT信号从未产生过一旦产生SIGQUIT信号将被阻塞它的处理动作是用户自定义函数sighandler若在进程解除对某些信号的阻塞之前这种信号产生过多次将如何处理POSIX.1允许系统递送该信号一次或多次。Linux是这样实现的常规信号在递达之前产生多次只记一次而实时信号在递达之前产生多次可以依次放在一个队列里// 内核结构 2.6.18 struct task_struct { ... /* signal handlers */ struct sighand_struct *sighand; sigset_t blocked struct sigpending pending; ... }; struct sighand_struct { atomic_t count; struct k_sigaction action[_NSIG]; // #define _NSIG 64 spinlock_t siglock; }; struct __new_sigaction { __sighandler_t sa_handler; unsigned long sa_flags; void (*sa_restorer)(void); /* Not used by Linux/SPARC */ __new_sigset_t sa_mask; }; struct k_sigaction { struct __new_sigaction sa; void __user *ka_restorer; }; /* Type of a signal handler. */ typedef void (*__sighandler_t)(int); struct sigpending { struct list_head list; sigset_t signal; };3.3 sigset_t从上图来看每个信号只有一个bit未决信号非0即1不记录该信号产生了多少次阻塞标志也是这样表示的。因此未决和阻塞标志可以用相同的数据类型sigset_t来存储sigset_t称为信号集这个类型可以表示每个信号的“有效”或“无效”状态在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞而在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态。阻塞信号集也叫做当前进程的信号屏蔽字Signal Mask这里的“屏蔽”应理解为阻塞而不是忽略类似于权限那里的umask3.4信号集操作函数sigset_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置1表示该信号集的有效信息包括系统支持的所有信号●注在使用sigset_t类型的变量之前一定要调用sigemptyset或sigfillset做初始化使信号集处于确定的状态。初始化sigset_t变量之后就可以再调用sigaddset和sigdelset在该信号集中添加或删除某种有效信号这四个函数都是成功返回0出错返回-1。sigismember是一个bool函数用于判断一个信号集的有效信号中是否包含某种信号若包含则返回1不包含则返回0出错返回-13.4.1 sigprocmask调用sigprocmask可以读取或更改进程的信号屏蔽字阻塞信号集#include signal.h int sigprocmask(int how, const sigset_t *set, sigset_t *oset); 返回值:若成功则为0,若出错则为-1若oset是非空指针则读取进程的当前信号屏蔽字通过oset参数传出。若set是非空指针则更改进程的信号屏蔽字参数how指示如何更改。若oset和set都是非空指针则先将原来的信号屏蔽字备份到oset里然后根据set和how参数更改信号屏蔽字。假设当前信号屏蔽字位mask下标说明how参数的可选值若调用sigprocmask解除了对当前若干个未决信号的阻塞则在sigprocmask返回前至少将其中一个信号递达3.4.2 sigpending#include signal.h int sigpending(sigset_t *set); 读取当前进程的未决信号集,通过set参数传出。 调⽤成功则返回0,出错则返回-1#include iostream #include unistd.h #include cstdio #include sys/types.h #include sys/wait.h void PrintPending(sigset_t pending) { std::cout curr process[ getpid() ]pending: ; for (int signo 31; signo 1; signo--) { if (sigismember(pending, signo)) { std::cout 1; } else { std::cout 0; } } std::cout \n; } void handler(int signo) { std::cout signo 号信号被递达!!! std::endl; std::cout ------------------------------- std::endl; sigset_t pending; sigpending(pending); PrintPending(pending); std::cout ------------------------------- std::endl; } int main() { // 0. 捕捉2号信号 signal(2, handler); // ⾃定义捕捉 //signal(2, SIG_IGN); // 忽略⼀个信号 //signal(2, SIG_DFL); // 信号的默认处理动作 // 1. 屏蔽2号信号 sigset_t block_set, old_set; sigemptyset(block_set); sigemptyset(old_set); sigaddset(block_set, SIGINT); // 我们有没有修改当前进⾏的内核block表呢没有 // 1.1 设置进⼊进程的Block表中 sigprocmask(SIG_BLOCK, block_set, old_set); // 真正的修改当前进⾏的内核block表完成了对2号信号的屏蔽 int cnt 15; while (true) { // 2. 获取当前进程的pending信号集 sigset_t pending; sigpending(pending); // 3. 打印pending信号集 PrintPending(pending); cnt--; // 4. 解除对2号信号的屏蔽 if (cnt 0) { std::cout 解除对2号信号的屏蔽!!! std::endl; sigprocmask(SIG_SETMASK, old_set, block_set); } sleep(1); } } $ ./run curr process[448336]pending: 0000000000000000000000000000000 curr process[448336]pending: 0000000000000000000000000000000 ^Ccurr process[448336]pending: 0000000000000000000000000000010 curr process[448336]pending: 0000000000000000000000000000010 curr process[448336]pending: 0000000000000000000000000000010 curr process[448336]pending: 0000000000000000000000000000010 curr process[448336]pending: 0000000000000000000000000000010 curr process[448336]pending: 0000000000000000000000000000010 curr process[448336]pending: 0000000000000000000000000000010程序运行时每秒钟把各信号的未决状态打印一遍由于我们阻塞了SIGINT信号按Ctrlc会时SIGINT信号处于未决状态按Ctrl\仍可以终止程序因为SIGQUIT信号没有阻塞4.捕捉信号4.1信号捕捉的流程若信号的处理动作是用户自定义函数在信号递达时就调用这个函数这称为捕捉信号由于信号处理函数的代码是在用户空间的处理过程比较复杂●用户程序注册了SIGQUIT信号的处理函数sighandler●当前正在执行main函数这时发生中断或异常切换到内核态●在中断处理完毕后要返回用户态的main函数之前检查到有信号SIGQUIT递达●内核决定用户态后不是恢复main函数的上下文继续执行而是执行sighanlder函数sighandler和main函数使用不同的堆栈空间它们之间不存在调用和被调用的关系是两个独立的控制流程●sighandler函数返回后自动执行特殊的系统调用sigreturn再次进入内核态●若没有新的信号要递达这次再返回用户态就是恢复main函数的上下文继续执行4.2 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赋值为常数SIG_IGN传给sigaction表示忽略信号赋值为常数SIG_DFL表示执行系统默认动作赋值为一个函数指针表示用自定义函数捕捉信号或者说向内核注册了一个信号处理函数该函数返回值为void可以带一个int函数通过参数可以得知当前信号的编号这样就可以用同一个函数处理多种信号。显然这也是一个回调函数不是被main函数调用而是被系统调用当某个信号的处理函数被调用时内核自动将当前信号加入进程的信号屏蔽字当信号处理函数返回时自动恢复原来的信号屏蔽字这样就保证了在处理某个信号时若这种信号再次产生那么它会被阻塞到当前处理结束为止。若在调用信号处理函数时处理当前信号被自动屏蔽之外还希望自动屏蔽另外一些信号则用sa_mask字段说明这些需要额外屏蔽的信号当信号处理函数返回时自动恢复原来的信号屏蔽字。sa_flags字段包含一些选项这里都把它设为0sa_sigaction是实时信号的处理函数4.3操作系统是如何运行的4.3.1硬件中断●中断向量表就是操作系统的一部分启动就加载到内存中了●通过外部硬件中断操作系统就不需要对外设进行任何周期性的检测或轮询●由外部设备触发的中断系统运行流程叫做硬件中断//Linux内核0.11源码 void trap_init(void) { int i; 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);// 设置并⾏⼝的陷阱⻔。 } void rs_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 中断信号请求。 }4.3.2时钟中断问题●进程可以在操作系统的指挥下被调度被执行那么操作系统自己被谁指挥被谁推动执行●外部设备可以触发硬件中断但是这个是需要用户或设备自己触发有无自己可以定期触发的设备此时操作系统就可以在硬件的推动下自动调度// Linux 内核0.11 // main.c sched_init(); // 调度程序初始化(加载了任务0 的tr, ldtr) kernel/sched.c // 调度程序的初始化⼦程序。 void sched_init(void) { ... set_intr_gate(0x20, timer_interrupt); // 修改中断控制器屏蔽码允许时钟中断。 outb(inb_p(0x21) ~0x01, 0x21); // 设置系统调⽤中断⻔。 set_system_gate(0x80, system_call); ... } // system_call.s _timer_interrupt: ... ;// do_timer(CPL)执⾏任务切换、计时等⼯作在kernel/shched.c,305 ⾏实现。 call _do_timer ;// do_timer(long CPL) does everything from // 调度⼊⼝ void do_timer(long cpl) { ... schedule(); } void schedule(void) { ... switch_to(next); // 切换到任务号为next 的任务并运⾏之。 }4.3.3死循环若是这样那操作系统不就什么也不用做了吗是的操作系统就是什么也不用做需要什么功能就像中断向量表中添加方法即可。操作系统的本质就是一个死循环void main(void) /* 这⾥确实是void并没错。 */ { /* 在startup 程序(head.s)中就是这样假设的。 */ ... /* * 注意!! 对于任何其它的任务pause()将意味着我们必须等待收到⼀个信号才会返 * 回就绪运⾏态但任务0task0是唯⼀的意外情况参⻅schedule()因为任 * 务0 在任何空闲时间⾥都会被激活当没有其它任务在运⾏时 * 因此对于任务0pause()仅意味着我们返回来查看是否有其它任务可以运⾏如果没 * 有的话我们就回到这⾥⼀直循环执⾏pause()。 */ for (;;) pause(); } // end main●这样操作系统就可以在硬件时钟的推动下自动调度了●所以什么是时间片CPU为什么有主频为什么主频越快CPU越快主频可以作为OS调度执行速度的参考之一4.3.4软中断●上述硬件中断需要硬件设备触发●有无可能因为软件原因也能触发上面的逻辑有●为让操作系统支持进行系统调用CPU也设计了对应的汇编指令int或syscall可以让CPU内部触发中断逻辑所以问题●用户层如何把系统调用号给操作系统 寄存器如EAX●操作系统如何把返回值给用户 寄存器或用户传入的缓冲区地址●系统调用的过程其实就是先int 0x80、syscall陷入内核本质就是触发软中断CPU就会自动执行系统调用的处理方法而这个方法会根据系统调用号自动查表执行对应的方法●系统调用号本质数组下标// sys.h // 系统调⽤函数指针表。⽤于系统调⽤中断处理程序(int 0x80)作为跳转表。 extern int sys_setup (); // 系统启动初始化设置函数。 (kernel/blk_drv/hd.c,71) extern int sys_exit (); // 程序退出。 (kernel/exit.c, 137) extern int sys_fork (); // 创建进程。 (kernel/system_call.s, 208) extern int sys_read (); // 读⽂件。 (fs/read_write.c, 55) extern int sys_write (); // 写⽂件。 (fs/read_write.c, 83) extern int sys_open (); // 打开⽂件。 (fs/open.c, 138) extern int sys_close (); // 关闭⽂件。 (fs/open.c, 192) extern int sys_waitpid (); // 等待进程终⽌。 (kernel/exit.c, 142) extern int sys_creat (); // 创建⽂件。 (fs/open.c, 187) extern int sys_link (); // 创建⼀个⽂件的硬连接。 (fs/namei.c, 721) extern int sys_unlink (); // 删除⼀个⽂件名(或删除⽂件)。 (fs/namei.c, 663) extern int sys_execve (); // 执⾏程序。 (kernel/system_call.s, 200) extern int sys_chdir (); // 更改当前⽬录。 (fs/open.c, 75) extern int sys_time (); // 取当前时间。 (kernel/sys.c, 102) extern int sys_mknod (); // 建⽴块/字符特殊⽂件。 (fs/namei.c, 412) extern int sys_chmod (); // 修改⽂件属性。 (fs/open.c, 105) extern int sys_chown (); // 修改⽂件宿主和所属组。 (fs/open.c, 121) extern int sys_break (); // (-kernel/sys.c, 21) extern int sys_stat (); // 使⽤路径名取⽂件的状态信息。 (fs/stat.c, 36) extern int sys_lseek (); // 重新定位读/写⽂件偏移。 (fs/read_write.c, 25) extern int sys_getpid (); // 取进程id。 (kernel/sched.c, 348) extern int sys_mount (); // 安装⽂件系统。 (fs/super.c, 200) extern int sys_umount (); // 卸载⽂件系统。 (fs/super.c, 167) extern int sys_setuid (); // 设置进程⽤⼾id。 (kernel/sys.c, 143) extern int sys_getuid (); // 取进程⽤⼾id。 (kernel/sched.c, 358) extern int sys_stime (); // 设置系统时间⽇期。 (-kernel/sys.c, 148) extern int sys_ptrace (); // 程序调试。 (-kernel/sys.c, 26) extern int sys_alarm (); // 设置报警。 (kernel/sched.c, 338) extern int sys_fstat (); // 使⽤⽂件句柄取⽂件的状态信息。(fs/stat.c, 47) extern int sys_pause (); // 暂停进程运⾏。 (kernel/sched.c, 144) extern int sys_utime (); // 改变⽂件的访问和修改时间。 (fs/open.c, 24) extern int sys_stty (); // 修改终端⾏设置。 (-kernel/sys.c, 31) extern int sys_gtty (); // 取终端⾏设置信息。 (-kernel/sys.c, 36) extern int sys_access (); // 检查⽤⼾对⼀个⽂件的访问权限。(fs/open.c, 47) extern int sys_nice (); // 设置进程执⾏优先权。 (kernel/sched.c, 378) extern int sys_ftime (); // 取⽇期和时间。 (-kernel/sys.c,16) extern int sys_sync (); // 同步⾼速缓冲与设备中数据。 (fs/buffer.c, 44) extern int sys_kill (); // 终⽌⼀个进程。 (kernel/exit.c, 60) extern int sys_rename (); // 更改⽂件名。 (-kernel/sys.c, 41) extern int sys_mkdir (); // 创建⽬录。 (fs/namei.c, 463) extern int sys_rmdir (); // 删除⽬录。 (fs/namei.c, 587) extern int sys_dup (); // 复制⽂件句柄。 (fs/fcntl.c, 42) extern int sys_pipe (); // 创建管道。 (fs/pipe.c, 71) extern int sys_times (); // 取运⾏时间。 (kernel/sys.c, 156) extern int sys_prof (); // 程序执⾏时间区域。 (-kernel/sys.c, 46) extern int sys_brk (); // 修改数据段⻓度。 (kernel/sys.c, 168) extern int sys_setgid (); // 设置进程组id。 (kernel/sys.c, 72) extern int sys_getgid (); // 取进程组id。 (kernel/sched.c, 368) extern int sys_signal (); // 信号处理。 (kernel/signal.c, 48) extern int sys_geteuid (); // 取进程有效⽤⼾id。 (kenrl/sched.c, 363) extern int sys_getegid (); // 取进程有效组id。 (kenrl/sched.c, 373) extern int sys_acct (); // 进程记帐。 (-kernel/sys.c, 77) extern int sys_phys (); // (-kernel/sys.c, 82) extern int sys_lock (); // (-kernel/sys.c, 87) extern int sys_ioctl (); // 设备控制。 (fs/ioctl.c, 30) extern int sys_fcntl (); // ⽂件句柄操作。 (fs/fcntl.c, 47) extern int sys_mpx (); // (-kernel/sys.c, 92) extern int sys_setpgid (); // 设置进程组id。 (kernel/sys.c, 181) extern int sys_ulimit (); // (-kernel/sys.c, 97) extern int sys_uname (); // 显⽰系统信息。 (kernel/sys.c, 216) extern int sys_umask (); // 取默认⽂件创建属性码。 (kernel/sys.c, 230) extern int sys_chroot (); // 改变根系统。 (fs/open.c, 90) extern int sys_ustat (); // 取⽂件系统信息。 (fs/open.c, 19) extern int sys_dup2 (); // 复制⽂件句柄。 (fs/fcntl.c, 36) extern int sys_getppid (); // 取⽗进程id。 (kernel/sched.c, 353) extern int sys_getpgrp (); // 取进程组id等于getpgid(0)。(kernel/sys.c, 201) extern int sys_setsid (); // 在新会话中运⾏程序。 (kernel/sys.c, 206) extern int sys_sigaction (); // 改变信号处理过程。 (kernel/signal.c, 63) extern int sys_sgetmask (); // 取信号屏蔽码。 (kernel/signal.c, 15) extern int sys_ssetmask (); // 设置信号屏蔽码。 (kernel/signal.c, 20) extern int sys_setreuid (); // 设置真实与/或有效⽤⼾id。 (kernel/sys.c,118) extern int sys_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 }; // 调度程序的初始化⼦程序。 void sched_init(void) { ... // 设置系统调⽤中断⻔。 set_system_gate(0x80, system_call); } _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 parameters push ebx ;// to the system call mov edx,10h ;// set up ds,es to kernel space mov ds,dx ;// ds,es 指向内核数据段(全局描述符表中数据段描述符)。 mov es,dx mov edx,17h ;// fs points to local data space mov 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 ;// state jne reschedule cmp dword ptr [countereax],0 ;// counter je reschedule ;// 以下这段代码执⾏从系统调⽤C 函数返回后对信号量进⾏识别处理。 ret_from_sys_call:●那为什么我们用的系统调用从来没有见过int 0x80或syscall呢都是直接调用上层的函数●那是因为Linux的gnu C标准库给我们把几乎所有的系统调用全部封装了●#define SYS_ify(syscall_name) __NR_##syscall_name是一个宏定义用于将系统调用的名称转换为对应的系统调用号。如SYS_ify(open)会被展开为__NR_open●而系统调用号不是glibc提供的是内核提供的内核提供系统调用入口展开函数man 2 syscall或者直接提供汇编级别软中断命令int or syscall并提供对应的头文件或开发入口让上层语言的设计者使用系统调用号完成系统调用过程源代码路径linux-2.6.18\linux-2.6.18\include\asm-x86_64\unistd.h /* at least 8 syscall per cacheline */ #define __NR_read 0 __SYSCALL(__NR_read, sys_read) #define __NR_write 1 __SYSCALL(__NR_write, sys_write) #define __NR_open 2 __SYSCALL(__NR_open, sys_open) #define __NR_close 3 __SYSCALL(__NR_close, sys_close) #define __NR_stat 4 __SYSCALL(__NR_stat, sys_newstat) #define __NR_fstat 5 __SYSCALL(__NR_fstat, sys_newfstat) #define __NR_lstat 6 __SYSCALL(__NR_lstat, sys_newlstat) #define __NR_poll 7 __SYSCALL(__NR_poll, sys_poll) #define __NR_lseek 8 __SYSCALL(__NR_lseek, sys_lseek) #define __NR_mmap 9 __SYSCALL(__NR_mmap, sys_mmap) #define __NR_mprotect 10 __SYSCALL(__NR_mprotect, sys_mprotect) #define __NR_munmap 11 __SYSCALL(__NR_munmap, sys_munmap) #define __NR_brk 12 __SYSCALL(__NR_brk, sys_brk) #define __NR_rt_sigaction 13 __SYSCALL(__NR_rt_sigaction, sys_rt_sigaction) #define __NR_rt_sigprocmask 14 __SYSCALL(__NR_rt_sigprocmask, sys_rt_sigprocmask) #define __NR_rt_sigreturn 15 __SYSCALL(__NR_rt_sigreturn, stub_rt_sigreturn) ...或部分版本在glibc中库函数调用实现方式# define INTERNAL_SYSCALL_NCS(name, err, nr, args...) \ ({ \ unsigned long int resultvar; \ LOAD_ARGS_##nr (args) \ LOAD_REGS_##nr \ asm volatile ( \ syscall\n\t \ : a (resultvar) \ : 0 (name) ASM_ARGS_##nr : memory, cc, r11, cx); \ (long int) resultvar; })4.3.5缺页中断内存碎片处理除零野指针错误void trap_init(void) { int i; 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.4如何理解用户态和内核态结论●操作系统无论怎么切换进程都能找到同一个操作系统。即操作系统调用发法的执行是在进程的地址空间中执行的●用户态就是执行用户[0,3]GB时所处的状态●内核态就是执行内核[3,4]GB时所处的状态●区分就是按照CPU内的CPL决定CPLCurrent Privilege Level即当前特权级别●一般执行int 0x80或syscall软中断CPL会在校验之后自动变更

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询