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

专注与开发网站的北京网络公司品牌设计公司排名前十强

专注与开发网站的北京网络公司,品牌设计公司排名前十强,上海技术做网站,网站开发 项目职责目录 十七.进程信号 导言 17.1 linux中的信号列表 17.2 标准信号与实时信号 17.3 信号的产生 17.3.1 通过终端按键产生信号 17.3.2 调用系统函数产生信号 17.3.3 软件条件产生信号 17.3.4 硬件异常产生信号 17.3.5 【补充】核心转储 Core Dump 17.4 信号的阻塞 17.4.1 信号相关…目录 十七.进程信号 导言 17.1 linux中的信号列表 17.2 标准信号与实时信号 17.3 信号的产生 17.3.1 通过终端按键产生信号 17.3.2 调用系统函数产生信号 17.3.3 软件条件产生信号 17.3.4 硬件异常产生信号 17.3.5 【补充】核心转储 Core Dump 17.4 信号的阻塞 17.4.1 信号相关常见概念补充 17.4.2 信号在内核的表示 17.4.3 信号集 sigset_t 信号集操作函数 sigprocmask sigpending 17.4.4 代码测试 17.5 信号的捕捉 17.5.1 signal 17.5.2 sigaction 17.5.3 内核态与用户态 17.5.4 重谈进程地址空间 17.5.5 信号捕捉流程图 17.6 SIGCHLD 十七.进程信号 导言 生活中充满着各种信号从交通灯的红绿指示到手机的震动提示信号贯穿着我们日常的方方面面。这些信号是为了在复杂的环境中传递简洁而重要的信息让人们能够迅速做出反应。有趣的是计算机科学中也有一种类似的机制那就是进程信号。 想象一下当你在驾驶时看到红灯亮起你会停下车子或者当你的手机收到一条紧急的消息震动提醒你关注。这些都是生活中的信号通过它们我们获得了环境中的重要信息并且采取了相应的行动。 在Linux操作系统中进程信号就像是计算机世界中的这些生活信号。它们是一种轻量级的通信方式操作系统通过发送信号来通知进程发生了特定事件。这些事件可以包括需要终止进程、重新加载配置、处理错误等。通过对进程信号的理解我们能够使程序更加智能地响应各种情境就像在生活中接收并处理各种信号一样。在本文中我们将以生活中信号的概念为切入点引领你深入了解Linux系统中进程信号的奥妙以及如何利用这种机制构建更加灵活和可靠的计算机程序。随着我们的探索你将发现信号在计算机世界中的重要性以及它们如何成为程序与操作系统之间交流的桥梁。 17.1 linux中的信号列表 在Linux中有一系列预定义的信号每个信号都有一个唯一的编号。这些信号的编号通常以SIG开头后跟一个描述信号用途的大写字母缩写。 kill -l 通过kill -l 命令我们可以查看系统为我们定义好的信号列表 值得注意的是信号编号范围是1到64但并不是每一个编号都对应了一个信号32和33 便没有对应信号 这时大家也能猜到实际上信号的本质就是宏定义这里我们通过源码确定 事实上通过名字和注释我们大概可以知道每个信号其实是由大概的含义。这实际上表示了进程对信号的默认处理方式 17.2 标准信号与实时信号 在Linux系统中信号可以分为两类标准信号Standard Signals和实时信号Real-time Signals这两类信号在其产生和传递的方式上有一些区别。 标准信号Standard Signals 范围 标准信号的编号范围通常是1到31。产生 这些信号是由操作系统或进程直接产生的例如用户按下CtrlC产生的SIGINT或者由操作系统发出的SIGHUP。语义 标准信号通常用于表示一些常见的事件如终止进程、中断操作等。处理 标准信号的处理方式包括终止进程、忽略信号、使用默认的信号处理函数或者注册自定义的信号处理函数。 实时信号Real-time Signals 范围 实时信号的编号范围是34到63。产生 实时信号是由内核和进程共同产生的通常用于实时进程间通信。语义 实时信号的语义更为灵活可用于应用层定义的目的如实现自定义的进程间通信。处理 实时信号的处理方式与标准信号类似可以忽略、使用默认处理函数或者注册自定义的处理函数。 总的来说标准信号是操作系统提供的常见信号用于表示一些标准的事件而实时信号则更为灵活可用于实现更复杂的应用层通信。在处理方式上两者并没有本质的区别都可以通过系统调用注册处理函数。这里我们不会继续深入我们后续讲解只涉及标准信号。 17.3 信号的产生 信号是进程之间事件异步通知的一种方式属于软中断本质也是数据 。信号是给进程发的进程在收到信号后会在合适的时候执行对应的命令。 17.3.1 通过终端按键产生信号 在UNIX或类UNIX系统中信号可以通过多种方式产生其中之一是通过终端按键。用户在终端上按下特定的键盘组合时会生成相应的信号通常用于与正在运行的程序进行交互或控制。 下面是一些常见的通过终端按键产生的信号 CtrlC (SIGINT) 产生原因用户按下CtrlC组合键。默认行为中断Interrupt当前运行的程序。通常用途允许用户通过终端中断正在运行的程序。 CtrlZ (SIGTSTP) 产生原因用户按下CtrlZ组合键。默认行为挂起Suspend当前运行的程序将其放到后台。通常用途将一个正在前台运行的程序放到后台暂停其执行。 Ctrl\ (SIGQUIT) 产生原因用户按下Ctrl\组合键。默认行为类似于CtrlC但可能会生成core dump文件用于调试。通常用途在出现问题时用户可以使用该信号强制终止程序并生成core dump以供调试。 17.3.2 调用系统函数产生信号 在UNIX或类UNIX系统中信号还可以通过系统函数的调用而产生。这种方式通常是由进程主动调用系统函数而不是依赖于外部的用户或终端输入。以下是一些产生信号的常见系统函数 kill函数 函数原型int kill(pid_t pid, int sig);作用向指定进程发送特定信号。例子kill(pid, SIGTERM); 将向进程ID为pid的进程发送SIGTERM信号请求它正常终止。 raise函数 函数原型int raise(int sig);作用使当前进程给自己发送指定信号。例子raise(SIGALRM); 将使当前进程收到SIGALRM信号。 alarm函数 函数原型unsigned int alarm(unsigned int seconds);作用设置一个定时器经过指定秒数后发送SIGALRM信号给调用进程。例子alarm(5); 将在5秒后给当前进程发送SIGALRM信号。 abort函数 函数原型void abort(void);作用使当前进程接收到SIGABRT信号导致进程异常终止。例子abort(); 调用该函数将导致当前进程异常终止可能生成包含堆栈跟踪的核心转储文件。 17.3.3 软件条件产生信号 SIGPIPE信号实际上就是一种由软件条件产生的信号 我们之前在讲到进程间通信当进程在使用管道进行通信时读端进程将读端关闭而写端进程还在一直向管道写入数据那么此时写端进程就会收到SIGPIPE信号进而被操作系统终止。 例如下面代码当中创建匿名管道进行父子进程之间的通信其中父进程是读端进程子进程是写端进程但是一开始通信父进程就将读端关闭了那么此时子进程在向管道写入数据时就会收到SIGPIPE信号进而被终止。 #include stdio.h #include unistd.h #include string.h #include stdlib.h #include sys/types.h #include sys/wait.h int main() {int fd[2] { 0 };if (pipe(fd) 0){ //使用pipe创建匿名管道perror(pipe);return 1;}pid_t id fork(); //使用fork创建子进程if (id 0){//childclose(fd[0]); //子进程关闭读端//子进程向管道写入数据const char* msg hello father, I am child...;int count 10;while (count--){write(fd[1], msg, strlen(msg));sleep(1);}close(fd[1]); //子进程写入完毕关闭文件exit(0);}//fatherclose(fd[1]); //父进程关闭写端close(fd[0]); //父进程直接关闭读端导致子进程被操作系统杀掉int status 0;waitpid(id, status, 0);printf(child get signal:%d\n, status 0x7F); //打印子进程收到的信号return 0; }17.3.4 硬件异常产生信号 在操作系统中硬件异常通常指的是由 CPU 探测到的一些错误或异常情况。当这些异常发生时CPU 会向操作系统发出信号操作系统会相应地采取措施例如发送相应的信号给受影响的进程。 以下是一些常见的硬件异常以及它们在信号中的映射 除零异常Division by Zero 当程序试图执行除以零的操作时CPU 探测到除零异常。操作系统通常会将这个异常映射为 SIGFPE 信号浮点异常。 非法指令异常Illegal Instruction 当程序尝试执行一条非法的机器指令时CPU 探测到非法指令异常。操作系统通常会将这个异常映射为 SIGILL 信号。 非法地址访问异常Illegal Address Access 当程序尝试访问未映射到其地址空间的内存区域时CPU 探测到非法地址访问异常。操作系统通常会将这个异常映射为 SIGSEGV 信号段错误。 浮点溢出异常Floating-Point Overflow 当程序中进行的浮点运算结果过大无法表示时CPU 探测到浮点溢出异常。操作系统通常会将这个异常映射为 SIGFPE 信号。 这些硬件异常产生信号的方式是由操作系统内核管理的操作系统负责捕获硬件异常并向受影响的进程发送相应的信号。 这里我们就常见的除零错误野指针或越界来分解一下从硬件到软件的信号处理 除零错误 硬件层面 当 CPU 进行除法运算时如果除数为零就会导致除零错误。这是硬件级别的异常通常由硬件内部的控制逻辑进行检测。 状态标志 CPU 内部有状态标志位例如溢出标志。如果发生除零错误会设置相应的状态标志。操作系统会定期检查这些标志如果发现异常状态就会采取相应的措施。 信号处理 操作系统可以通过发送信号的方式通知进程。在除零错误的情况下通常会发送 SIGFPE浮点异常信号给进程。 进程退出 默认情况下操作系统可能会终止发生除零错误的进程。但这并非绝对操作系统的具体行为可能受到进程对信号的处理方式的影响。 死循环 如果除零错误引发了异常而进程没有适当处理可能导致程序陷入死循环。这是因为异常没有得到解决寄存器中的异常标志一直保持未解决状态。 野指针或越界 虚拟地址和物理地址 计算机程序中使用的地址是虚拟地址需要通过页表和内存管理单元MMU将其转化为物理地址。MMU 是硬件的一部分负责地址转换。 异常检测 当程序尝试访问非法地址野指针、越界等MMU 会检测到这一异常触发硬件异常。 页表 操作系统通过页表管理虚拟地址到物理地址的映射。非法地址访问可能导致页表中的相应条目不存在从而引发异常。 操作系统处理 操作系统可以通过信号通知进程发生了非法地址访问。常见的是 SIGSEGV段错误信号。 死循环 类似于除零错误如果异常没有被适当处理程序可能会进入死循环导致无法正常执行。 总的来说硬件和操作系统协同工作通过硬件异常检测和操作系统的信号处理确保程序在发生错误时能够得到适当的通知从而采取相应的措施。 对于异常进程不一定会立即退出具体行为可能受到信号处理的影响。但是即使我们不退出基本上也做不了什么因为寄存器上中的异常还没有解决程序会陷入死循环。 17.3.5 【补充】核心转储 Core Dump Core Dump核心转储是在进程发生异常终止时将进程的内存数据保存到磁盘上的文件。 一般情况下当进程遇到致命错误如段错误时操作系统会生成一个 Core Dump 文件。这个文件可以被调试器用于事后调试帮助开发人员定位程序崩溃的原因。通常Core Dump 文件保存在进程当前工作目录中文件名为 core。 在Linux和类Unix系统上通过ulimit命令可以控制 Core Dump 文件的生成大小。默认情况下操作系统可能会禁止生成 Core Dump 文件因为它们可能包含敏感信息。 在云服务器中核心转储是默认被关掉的我们可以通过使用ulimit -a命令查看当前资源限制的设定。 ulimit -a 在开发和调试阶段开发人员可以使用ulimit命令来调整 Core Dump 文件的大小限制。例如 ulimit -c 1024core文件的大小设置完毕后就相当于将核心转储功能打开了。 此时如果我们再使用Ctrl\对进程进行终止就会发现终止进程后会显示core dumped。 并且会在当前路径下生成一个core文件该文件以一串数字为后缀而这一串数字实际上就是发生这一次核心转储的进程的PID。 这里我们写一个除0错误的代码进一步演示 使用gdb对当前可执行程序进行调试然后直接使用core-file core文件命令加载core文件即可判断出该程序在终止时收到了8号信号 还记得进程等待函数waitpid函数的第二个参数吗 pid_t waitpid(pid_t pid, int *status, int options);core dump标志实际上就是用于表示程序崩溃的时候是否进行了核心转储。如果该位是1则进行了核心转储0则没有进行了核心转储 说明一下  ulimit命令是用来设置用户级资源限制的命令它会影响当前Shell进程及其子进程。在Unix-like系统中每个进程都有与之相关的资源限制这些限制定义了进程在运行时可以消耗的资源的上限。 ulimit命令改变的是Shell进程的Resource Limit但myproc进程的PCB是由Shell进程复制而来的所以也具有和Shell进程相同的Resource Limit值。         17.4 信号的阻塞 17.4.1 信号相关常见概念补充 信号递达Signal Delivery 信号递达指的是实际执行信号处理动作的过程。一旦信号递达进程就会执行与该信号关联的处理函数或默认动作。 信号未决Signal Pending 信号从产生到递达之间的状态称为信号未决。即信号已经产生但尚未被进程处理。在信号未决状态下信号可以被阻塞也可以等待进程处理。 信号阻塞Signal Blocking 进程可以选择阻塞某个信号使得被阻塞的信号在进程解除对其阻塞之前不会递达。这种机制允许进程对特定信号进行控制以便在某些时候忽略或推迟对该信号的处理。 阻塞状态下的信号保持未决状态 当一个信号被阻塞时即使信号已经产生它会一直保持在信号未决状态。直到进程解除对该信号的阻塞信号才能递达并触发相应的处理动作。 阻塞与忽略的区别 阻塞和忽略是两个不同的概念。当信号被阻塞时它会保持在未决状态直到解除阻塞而当信号被忽略时它在递达时不会触发任何处理动作。忽略是一种处理信号的方式而阻塞是一种控制信号递达的方式。 17.4.2 信号在内核的表示 在内核中每个进程的内核控制块PCB中会包含阻塞位图、未决位图以及处理动作表用于跟踪和管理该进程的信号状态 阻塞位图Block Bitmap 阻塞位图是一个比特位图每个比特位表示一个特定的信号。如果某个信号的阻塞位被设置为 1说明该信号被阻塞如果为 0说明该信号没有被阻塞。 未决位图Pending Bitmap 未决位图也是一个比特位图每个比特位表示一个特定的信号。如果某个信号的未决位被设置为 1说明该信号在进程中是未决状态如果为 0说明该信号没有产生或已经被处理。 处理动作表Handler Table 处理动作表是一个函数指针数组每个数组元素对应一个特定的信号。函数指针指向信号递达时要执行的处理动作可能是默认动作、忽略动作或用户自定义函数。 在上图的例子中,SIGHUP信号未阻塞也未产生过,当它递达时执行默认处理动作。SIGINT信号产生过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。SIGQUIT信号未产生过,一旦产生SIGQUIT信号将被阻塞,它的处理动作是用户自定义函数sighandler。  如果在进程解除对某信号的阻塞之前这种信号产生过多次,将如何处理? POSIX.1允许系统递送该信号一次或多次。Linux是这样实现的:常规信号在递达之前产生多次只计一次,而实时信号在递达之前产生多次可以依次放在一个队列里。本章不讨论实时信号。 17.4.3 信号集 信号集Signal Set是一个用于表示一组信号状态的数据结构。在 POSIX 操作系统中通常使用 sigset_t 数据类型来表示信号集。每个信号集包含了系统所支持的所有信号并使用比特位图的形式表示每个信号的状态。 在信号集中每个比特位bit代表一个特定的信号。如果某个比特位的值为 1表示该信号在信号集中是有效的被包含如果为 0则表示该信号是无效的未包含。 sigset_t sigset_t 是一个数据类型用于表示信号集。信号集是一个集合其中每个元素对应一个可能的信号。从上面阻塞位图Block Bitmap和 未决位图Pending Bitmap来看,每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。 因此,未决和阻塞标志可以用相同的数据类型sigset_t来存储。 信号集操作函数 #include signal.hint sigemptyset(sigset_t *set);int sigfillset(sigset_t *set);int sigaddset(sigset_t *set, int signum);int sigdelset(sigset_t *set, int signum);int sigismember(const sigset_t *set, int signum); 初始化信号集 sigemptyset将信号集中的所有比特位都清零表示该信号集不包含任何信号。sigfillset将信号集中的所有比特位都设置为 1表示该信号集包含所有系统支持的信号。 添加和删除信号 sigaddset将指定信号的比特位设置为 1将该信号添加到信号集中。sigdelset将指定信号的比特位清零将该信号从信号集中删除。 查询信号是否包含在信号集中 sigismember用于判断指定信号是否包含在信号集中返回 1 表示包含返回 0 表示不包含。 这四个函数都是成功返回 0, 出错返回 -1 。 sigismember 是一个布尔函数 , 用于判断一个信号集的有效信号中是否包含 某种 信号, 若包含则返回 1, 不包含则返回 0, 出错返回 -1 。 sigprocmask  函数可以读取或更改进程的信号屏蔽字阻塞信号集。 int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); 参数说明 如果oset是非空指针则读取进程当前的信号屏蔽字通过oset参数传出。如果set是非空指针则更改进程的信号屏蔽字参数how指示如何更改。如果oset和set都是非空指针则先将原来的信号屏蔽字备份到oset里然后根据set和how参数更改信号屏蔽字。 sigpending 该函数用于读取当前进程的未决信号集 int sigpending(sigset_t *set);sigpending函数读取当前进程的未决信号集并通过 set 参数传出。该函数调用成功返回0出错返回-1 17.4.4 代码测试 首先我们编写一段程序 signal 阻塞1~31号信号并不断的打印pending位图 #include stdio.h #include unistd.h #include signal.h void printPending(sigset_t *pending) {int i 1;for (i 1; i 31; i){if (sigismember(pending, i)){printf(1 );}else{printf(0 );}}printf(\n); } void handler(int signo) {printf(handler signo:%d\n, signo); } int main() {signal(2, handler);sigset_t set, oset;sigemptyset(set);sigemptyset(oset);for(int i1;i31;i){sigaddset(set, i); //SIGINT}sigprocmask(SIG_SETMASK, set, oset); sigset_t pending;sigemptyset(pending);int count 0;while (1){sigpending(pending); //获取pendingprintPending(pending); //打印pending位图1表示未决sleep(1);count;}return 0; } 再编写一个shell脚本来向运行后的程序 signal 发送1~31 号信号这里我们避开9号与19号原因后面会将 #!/bin/bashi1 id$(pidof signal) while [ $i -le 31 ] do if [ $i -eq 9 ];then let icontinuefiif [ $i -eq 19 ];then let icontinuefi kill -$i $id echo kill -$i $idlet isleep 1 done 程序运行后的效果如下 17.5 信号的捕捉 17.5.1 signal signal 函数是一个用于处理信号signals的函数通常在UNIX和类UNIX系统上使用。信号是在计算机系统中用于通知进程发生了某些事件的一种机制。例如当用户按下键盘上的中断键CtrlC操作系统会向前台进程发送一个中断信号SIGINT。程序可以通过注册信号处理函数来捕捉和处理这些信号。 在C语言中signal 函数的原型为 #include signal.hvoid (*signal(int signum, void (*handler)(int)))(int);这个函数的作用是设置对信号 signum 的处理方式。其中signum 是信号的编号handler 是一个指向处理函数的指针。 signal 函数有三种可能的返回值 如果 signum 无效返回 SIG_ERR。如果 handler 为 SIG_DFL表示使用默认的信号处理方式返回当前的信号处理函数。如果 handler 为 SIG_IGN表示忽略该信号返回当前的信号处理函数。 以下是一个使用 signal 函数注册信号处理函数的简单示例 #include stdio.h #include signal.h #include unistd.h// 信号处理函数 void signal_handler(int signum) {printf(Received signal %d\n, signum); }int main() {// 注册信号处理函数if (signal(SIGINT, signal_handler) SIG_ERR) {perror(Error setting up signal handler);return 1;}printf(Press CtrlC to send a SIGINT signal\n);// 进入一个无限循环while (1) {sleep(1);}return 0; }在这个例子中程序注册了一个处理 SIGINT 信号的处理函数 signal_handler。当用户按下 CtrlC 时操作系统将发送 SIGINT 信号触发 signal_handler 函数的执行。 17.5.2 sigaction sigaction 函数是用于设置和检查信号处理动作的系统调用。它提供了更灵活和可移植的信号处理方式相较于 signal 函数sigaction 函数的接口更为强大可以更精确地控制信号处理。 #include signal.hint sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);以下是关于 sigaction 函数参数的详细说明 signum: 指定信号的编号。act: 如果非空根据 act 修改该信号的处理动作。oldact: 如果非空通过 oldact 传出该信号原来的处理动作。 其中参数act和oldact都是结构体指针变量该结构体的定义如下 struct sigaction {void (*sa_handler)(int); // 信号处理函数类似于 signal 函数的第一个参数void (*sa_sigaction)(int, siginfo_t *, void *); // 实时信号的处理函数sigset_t sa_mask; // 用于屏蔽的信号集合int sa_flags; // 一些标志位void (*sa_restorer)(void); // 未使用 };sa_handler: 与 signal 函数中的信号处理函数类似用于处理信号的函数指针。sa_sigaction: 用于处理实时信号的函数指针。如果设置了 sa_sigaction则 sa_handler 会被忽略。sa_mask: 一个信号集合用于指定在处理当前信号时需要阻塞的其他信号。当信号处理函数执行时系统会自动将 sa_mask 中的信号添加到进程的信号屏蔽字中以避免处理函数的递归调用。sa_flags: 一些标志位用于设置 sigaction 的行为。一般将其设置为0。sa_restorer: 未使用忽略即可。 下面是一个简单的使用 sigaction 函数的例子 #include stdio.h #include signal.h// 信号处理函数 void signal_handler(int signum) {printf(Received signal %d\n, signum); }int main() {struct sigaction sa;// 设置信号处理函数sa.sa_handler signal_handler;sa.sa_flags 0;// 注册信号处理函数if (sigaction(SIGINT, sa, NULL) -1) {perror(Error setting up sigaction);return 1;}printf(Press CtrlC to send a SIGINT signal\n);// 进入一个无限循环while (1);return 0; }在这个例子中sigaction 函数被用来注册 SIGINT 信号的处理函数该处理函数为 signal_handler。 17.5.3 内核态与用户态 在计算机系统中操作系统内核和用户程序运行在不同的特权级别或者说不同的模式下这被称为内核态Kernel Mode和用户态User Mode。这种划分是为了提高系统的稳定性和安全性。 内核态Kernel Mode 内核态是操作系统运行的特权级别具有最高的权限。在内核态下操作系统可以直接访问所有硬件资源、执行敏感指令并对系统进行完全的控制。在内核态下运行的代码被称为内核代码。 用户态User Mode 用户态是普通应用程序运行的特权级别权限较低。在用户态下应用程序不能直接访问硬件资源不能执行一些敏感指令只能通过操作系统提供的服务来访问硬件和执行需要特权的操作。在用户态下运行的代码被称为用户代码。 切换模式是通过中断Interrupt或异常Exception来触发的。当应用程序需要操作系统的服务时例如申请内存、进行文件操作等会触发一个中断或异常将控制权从用户态切换到内核态。在完成服务后操作系统会再次将控制权切回用户态。 这种划分有助于保护操作系统的稳定性因为用户程序在用户态下运行时受到较为严格的限制无法直接影响到操作系统的核心部分。操作系统通过中断、异常等机制掌握对硬件的控制确保系统的安全性和可靠性。 17.5.4 重谈进程地址空间 首先简单回顾下 进程地址空间 的相关知识 进程地址空间 是虚拟的依靠 页表MMU机制 与真实的地址空间建立映射关系每个进程都有自己的 进程地址空间不同 进程地址空间 中地址可能冲突但实际上地址是独立的进程地址空间 可以让进程以统一的视角看待自己的代码和数据 所有进程的用户空间 [0, 3] GB 是不一样的并且每个进程都要有自己的 用户级页表 进行不同的映射所有进程的内核空间 [3, 4] GB 是一样的每个进程都可以看到同一张内核级页表从而进行统一的映射看到同一个 操作系统操作系统运行 的本质其实就是在该进程的 内核空间内运行的最终映射的都是同一块区域系统调用 的本质其实就是在调用库中对应的方法后通过内核空间中的地址进行跳转调用   当我们执行系统调用时会从用户态切换到内核态。这个切换是通过 CPU 中的 CR3 寄存器实现的。 当 CR3 寄存器中的值为用户态对应的页表时表示正在执行用户的代码即处于用户态。当 CR3 寄存器中的值为内核态对应的页表时表示正在执行操作系统的代码即处于内核态。 通过修改 CR3 寄存器中的值操作系统可以在用户态和内核态之间进行切换。这个寄存器的值的变化涉及到地址空间的切换确保用户程序无法直接访问内核空间的代码和数据。 17.5.5 信号捕捉流程图 当我们在执行主控制流程的时候可能因为某些情况而陷入内核当内核处理完毕准备返回用户态时就需要进行信号pending的检查。此时仍处于内核态有权力查看当前进程的pending位图在查看pending位图时如果发现有未决信号并且该信号没有被阻塞那么此时就需要该信号进行处理。 如果待处理信号的处理动作是默认或者忽略则执行该信号的处理动作后清除对应的pending标志位如果没有新的信号要递达就直接返回用户态从主控制流程中上次被中断的地方继续向下执行即可。但如果待处理信号是自定义捕捉的即该信号的处理动作是由用户提供的那么处理该信号时就需要先返回用户态执行对应的自定义处理动作执行完后再通过特殊的系统调用sigreturn再次陷入内核并清除对应的pending标志位如果没有新的信号要递达就直接返回用户态继续执行主控制流程的代码。 对于上图的信号捕捉为了方便好记可以画个简化的图 当识别到信号的处理动作是自定义时能直接在内核态执行用户空间的代码吗 当检测到信号的处理动作是自定义时理论上是可以在内核态执行用户空间的代码的因为内核态拥有极高的权限。然而绝对不能采用这样的设计方案。 如果允许在内核态直接执行用户空间的代码用户就有可能在其代码中包含一些非法操作比如尝试清空数据库等。虽然在用户态时没有足够的权限执行这类敏感操作但如果在内核态执行这种非法代码由于内核态权限的高度这些操作就有可能被成功执行导致潜在的系统数据损坏或丧失。 简而言之不应该让操作系统直接执行用户提供的代码因为操作系统无法信任任何用户提供的代码。为了确保系统的稳定性和安全性操作系统采用了分层设计的原则限制了用户空间和内核空间之间的直接交互。在信号处理的背景下通常选择在用户态执行用户提供的信号处理函数通过明确定义的系统调用接口和权限机制来控制用户代码的执行以维护系统的完整性。 17.6 SIGCHLD 进程一章讲过用 wait 和 waitpid 函数清理僵尸进程 , 父进程可以阻塞等待子进程结束 , 也可以非阻 塞地查询是否有子进 程结束等待清理( 也就是轮询的方式 ) 。采用第一种方式 , 父进程阻塞了就不 能处理自己的工作了 ; 采用第二种方式 , 父 进程在处理自己的工作的同时还要记得时不时地轮询一 下, 程序实现复杂。 其实 , 子进程在终止时会给父进程发 SIGCHLD 信号 , 该信号的默认处理动作是忽略 , 父进程可以自 定义 SIGCHLD 信号的处理函数, 这样父进程只需专心处理自己的工作 , 不必关心子进程了 , 子进程 终止时会通知父进程 , 父进程在信号处理 函数中调用wait 清理子进程即可。 下面编写一个程序完成以下功能 : 父进程 fork 出子进程 , 子进程调用 exit(1) 终止 , 父进程自定 义 SIGCHLD 信号的处理函数 , 在其中调用wait 获得子进程的退出状态并打印 #include stdio.h #include stdlib.h #include signal.h #include sys/wait.h #include unistd.hvoid handler(int sig) {pid_t id;while ((id waitpid(-1, NULL, WNOHANG)) 0) {printf(等待子进程成功%d\n, id);}printf(子进程退出%d\n, getpid()); }int main() {signal(SIGCHLD, handler);pid_t cid;if ((cid fork()) 0) { // 子进程printf(子进程%d\n, getpid());sleep(3);exit(1);}while (1) {printf(父进程正在执行一些操作\n);sleep(1);}return 0; }事实上,由于UNIX 的历史原因,要想不产生僵尸进程还有另外一种办法:父进程调 用sigaction将SIGCHLD的处理动作 置为SIG_IGN,这样fork出来的子进程在终止时会自动清理掉,不 会产生僵尸进程,也不会通知父进程。系统默认的忽 略动作和用户用sigaction函数自定义的忽略 通常是没有区别的,但这是一个特例。此方法对于Linux可用,但不保证 在其它UNIX系统上都可用。 下面代码中调用signal函数将SIGCHLD信号的处理动作自定义为忽略。 #include stdio.h #include unistd.h #include signal.h #include stdlib.hint main() {signal(SIGCHLD, SIG_IGN);if (fork() 0){//childprintf(child is running, child dead: %d\n, getpid());sleep(3);exit(1);}//fatherwhile (1);return 0; }此时子进程在终止时会自动被清理掉不会产生僵尸进程也不会通知父进程。
http://www.zqtcl.cn/news/8519/

相关文章:

  • 音乐介绍网站怎么做用asp.net做的购物网站
  • 网站制作建设是做什么盐城市城南建设局网站
  • 网站的制作建站人装潢设计主要学什么
  • 怎么阐述自己做的网站湘潭建设厅官方网站
  • 中小型企业网站优化推广平台页面设计模板
  • 网站SEO做点提升流量象客wordpress图片文件目录
  • 中国工程建设质量管理协会网站网站的二维码怎么做的
  • 河北省建设工程安全生产监督管理网站国外大气网站
  • 网站首页排名国内 wordpress主机
  • 南宁网站制作公司wordpress 添加字段
  • wordpress如何建站群美食网站建设的栏目和模板
  • 励志做的很好的网站徐州网红有哪些人
  • 小说网站论文摘要投资网站
  • 做黑龙头像的网站网页布局及版面设计
  • 南宁网站定制开发自己做的网站怎么在百度上搜到
  • 可以做猫头像的网站人工智能工程师月薪多少
  • 学做室内效果图的网站哪个网站做二微码
  • 腾讯云如何建设网站网络项目设计方案
  • 温州市建设安监局网站深圳58同城招聘网最新招聘
  • 简述制作网站的步骤和过程wordpress内网外网访问不了
  • 网站建设300元网站制作课程介绍
  • 网站建设手机源码工商信息查询官网
  • 罗田县住房和城乡建设局网站成都旅游攻略景点必去十处
  • 网站开发对显卡的要求游戏网站怎么建设
  • 网站建设制作 优帮云wordpress 手机版本
  • 网站模板 手机郑州网站建设+论坛
  • 做淘宝客没网站网站站点结构图
  • 贵州安顺做公司网站wordpress 关闭站点
  • logo注册网站WordPress全站广告
  • 网站 备案 哪个省wordpress视频预览插件