帮助文档
专业提供香港服务器、香港云服务器、香港高防服务器租用、香港云主机、台湾服务器、美国服务器、美国云服务器vps租用、韩国高防服务器租用、新加坡服务器、日本服务器租用 一站式全球网络解决方案提供商!专业运营维护IDC数据中心,提供高质量的服务器托管,服务器机房租用,服务器机柜租用,IDC机房机柜租用等服务,稳定、安全、高性能的云端计算服务,实时满足您的多样性业务需求。 香港大带宽稳定可靠,高级工程师提供基于服务器硬件、操作系统、网络、应用环境、安全的免费技术支持。
服务器资讯 / 香港服务器租用 / 香港VPS租用 / 香港云服务器 / 美国服务器租用 / 台湾服务器租用 / 日本服务器租用 / 官方公告 / 帮助文档
ECF机制:信号 (Signal)
发布时间:2024-02-28 02:43:47   分类:帮助文档
ECF机制:信号 (Signal)     💭 写在前面:ECF (异常控制流) 机制是存在于系统的所有层级中的,所以这一块的知识我们需要系统地去学习。前几章我们探讨过了异常 (Exceptions),由硬件触发,在内核代码中处理。讲解了进程的上下文切换 (Process Context Switch),"异常 + 内核代码"。本章我们将探讨信号 (signal),将 "异常 + 内核代码 + 用户代码" 相结合! 📜本文目录: 0x00 什么是内核(Shell) 0x01 简单的 Shell 示例 0x02 不用担心!ECF 救你来了!(ECF Comes to the Rescue!) 0x03 信号(Signals) 0x04 接收信号(Receiving a Signal) 0x05 未决信号和阻塞信号(Pending and Blocking Signals) 0x06 未决/阻断的比特(Pending/Blocked Bits) 0x07 发出信号:进程组(Sending Signals: Process Groups) 0x08 用 /bin/kill 程序发送信号 0x09 从键盘发出信号 0x0A 以程序化的方式发送信号 0x00 什么是内核(Shell) A shell is an application program that runs other programs on behalf of the user Shell 是一种应用程序,它代表用户运行其他程序。 sh // 原始的 Unix shell (Stephen Bourne,AT&T贝尔实验室,1977) csh/tcsh // BSD Unix C shell bash // "Bourne-Again" Shell(默认的 Linux shell) 对于 Shell,其中最熟悉的莫过于 bash 了:  Linux 进程层次结构(Linux Process Hierarchy): 我们可以使用 pstree 指令去查看进程层次结构: $ pstree 0x01 简单的 Shell 示例 "Shell execution is a repeated sequence of read & evaluate" Shell 的本质就是一个循环:从命令行读取一行,执行所请求的操作: 内置命令(例如,退出)从文件加载和执行程序 int main(int argc, char argv) { char cmdline[MAXLINE]; /* command line */ while (1) { /* read */ printf("> "); fgets(cmdline, MAXLINE, stdin); if (feof(stdin)) exit(0); /* evaluate */ eval(cmdline); } ... 在我的 《看表情包学Linux》专栏中有一插叙章节,就是实现一个简单的 Shell 的,感兴趣可以跟着自己实现一个简单的 Shell! 🔗 链接:【Linux】简易Shell的实现 简单的 Shell eval 函数: void eval(char* cmdline) { char* argv[MAXARGS]; /* Argument list execve() */ char buf[MAXLINE]; /* Holds modified command line */ int bg; /* Should the job run in bg or fg? */ pid_t pid; /* Process id */ strcpy(buf, cmdline); bg = parseline(buf, argv); if (argv[0] == NULL) return; /* Ignore empty lines */ if (!builtin_command(argv)) { if ((pid = fork()) == 0) { /* Child runs user job */ execve(argv[0], argv, environ); // If we get here, execve failed. printf("%s: %s\n", argv[0], strerror(errno)); exit(127); } /* Parent waits for foreground job to terminate */ if (!bg) { int status; if (waitpid(pid, &status, 0) < 0) unix_error("waitfg: waitpid error"); } else printf("%d %s\n", pid, cmdline); } return; } 该示例中存在的问题: Shell 必须设计为持续运行,不应该积累不需要的资源(内存、子进程等)。我们的示例 Shell 正确地等待并回收前台作业,但是后台作业呢?当它们终止时将会变成僵尸进程,除非 Shell 终止,否则将永远不会被回收,这将造成内存泄漏,可能会使内核耗尽内存! 0x02 不用担心!ECF 救你来了!(ECF Comes to the Rescue!) 💡 解决方案:异常控制流 (ECF) 当后台进程完成时,内核会中断常规处理以向我们发出 Warning。 我们的 Shell 程序可以收到这些事件的通知,在 Unix 中,这种 Warning 机制称为 信号 (signal)。 ECF(Event-driven Control Flow)是一种 Shell 设计模式,它通过事件驱动方式管理子进程和资源。使用 ECF 设计的 Shell 可以在不积累不必要资源的情况下持续运行,并正确地回收前台和后台作业。ECF 设计模式的关键思想是让 Shell 在执行命令之前将其设置为后台模式或前台模式。当命令运行完成后,Shell 会接收到一个事件,通知它该如何处理该作业。如果作业在前台模式下运行,Shell 会等待作业完成,然后回收资源。如果作业在后台模式下运行,Shell 会立即回收所有相关资源,不会创建僵尸进程,也不会占用不必要的内存资源。 ECF 是一种优秀的 Shell 设计模式,已经被广泛应用于各种操作系统中,例如 Linux 和 macOS 等。使用 ECF 设计的 Shell 具有稳定性高、性能优良等优点,是一种非常实用的 Shell 设计模式。 0x03 信号(Signals) "A signal is a small message that notifies a process that an event of some type has occurred in the system" 信号是一条小消息,通知进程系统中发生了某种类型的事件。 类似于异常和中断。从内核(有时是由另一个进程的请求)发送到进程,信号类型由小整数 ID(1-30)标识,信号中仅包含 ID 和已到达的事实信息。 发送信号 (Sending a Signal): 内核通过更新目标进程的上下文状态向 目标进程 (destination proces) 传递 (send) 信号。 内核发送信号有以下原因之一: 内核检测到系统事件,例如除以零(SIGFPE)或子进程的终止(SIGCHLD)。 另一个进程调用 kill 系统调用来显式请求内核向目标进程发送信号。   0x04 接收信号(Receiving a Signal) 当一个目标进程被内核强迫以某种方式对信号做出反应时,它就会收到一个信号。 一些可能的反应方式: Ignore:忽略该信号(什么都不做)Terminate:终止进程(可选择核心转储 core dump)Catch:通过执行被称为信号处理程序的用户级函数来捕捉信号,这类似于响应异步中断时调用硬件异常处理程序:   0x05 未决信号和阻塞信号(Pending and Blocking Signals) 如果一个信号已发送但尚未收到,那么该信号是 未决 (pending) 的。 每种类型最多只能有一个未接信号 (pending signal), 注意!信号不在队列中: 如果一个进程已经有一个类型为  的待定信号,那么 如果一个进程已经有一个  类型的待处理信号,那么随后发送给该进程的  类型的信号将被丢弃 一个进程可以 阻塞 (Blocking) 某些信号的接收。 被阻塞的信号可以被发送,但不会被接收,直到该信号被解除阻塞为止。 有些信号不能被阻塞,比如 SIGKILL、SIGSTOP。 或只能在其他进程发送时被阻塞,比如 SIGSEGV、SIGILL等 。 当然,这并不意味着你可以向任何进程发送 SIGKILL、SIGSTOP...(权限检查是另一回事) 0x06 未决/阻断的比特(Pending/Blocked Bits) 内核在每个进程的上下文中维护未决和阻断的位向量。 未决信号集 (pending):代表未决信号的集合 内核在发送  类型的信号时设置挂起中的位 内核在收到  类型的信号时清除挂起中的位   阻塞信号集 (blocking):代表阻塞信号的集合 可以通过使用 sigprocmask 函数(内部调用系统)来设置和清除 这个位向量也被称为 信号掩码 (signal mask) 0x07 发出信号:进程组(Sending Signals: Process Groups) 每个进程组有一个领头进程,进程组是一个或多个进程的集合,通常它们与一组作业相关联,可以接受来自同一终端的各种信号。   0x08 用 /bin/kill 程序发送信号 /bin /kill 程序 发送任意信号给一个 进程或进程组。 举个例子: kill -9 24818 向进程24818发送SIGKILL 相当于以下内容: kill -SIGKILL 24818 kill -9 -24817 向进程组中的每个进程发送SIGKILL 进程组中的每个进程24817   0x09 从键盘发出信号 输入 ctrl-c(ctrl-z)会导致内核向前台进程组中的每个工作发送 SIGINT(SIGTSTP) SIGINT:默认动作是终止每个进程SIGTSTP:默认动作是停止(暂停)每个进程。 Example of ctrl-c and ctrl-z: bluefish> ./fork17 Child: pid=28108 pgrp=28107 Parent: pid=28107 pgrp=28107 [1]+ Stopped ./fork17 bluefish> ps u PID TTY ... STAT TIME COMMAND … 28107 pts/8 ... T 0:01 ./fork17 28108 pts/8 ... T 0:01 ./fork17 28109 pts/8 ... R+ 0:00 ps w bluefish> fg ./fork17 bluefish> ps u PID TTY ... STAT TIME COMMAND ... 28110 pts/8 ... R+ 0:00 ps w STAT(过程状态)图例: 第一个字母::终止态、:运行态第二个字母:+:前台进程组、更多细节见 "man ps"。 0x0A 以程序化的方式发送信号 输入 man 2 kill 查看 kill() 函数的手册: void fork12() { pid_t pid[N]; int i, child_status; for (i = 0; i < N; i++) if ((pid[i] = fork()) == 0) while (1); /* Child: Infinite Loop */ for (i = 0; i < N; i++) { printf("Killing process %d\n", pid[i]); kill(pid[i], SIGINT); } for (i = 0; i < N; i++) { pid_t wpid = wait(&child_status); if (WIFEXITED(child_status)) printf("Child %d terminated with exit status %d\n", wpid, WEXITSTATUS(child_status)); else printf("Child %d terminated abnormally\n", wpid); } } 🚩 运行结果如下:(当 N = 5)   📌 [ 笔者 ]   王亦优 📃 [ 更新 ]   2022.4.4 ❌ [ 勘误 ]   /* 暂无 */ 📜 [ 声明 ]   由于作者水平有限,本文有错误和不准确之处在所难免, 本人也很想知道这些错误,恳望读者批评指正! 📜 参考资料  C++reference[EB/OL]. []. http://www.cplusplus.com/reference/. Microsoft. MSDN(Microsoft Developer Network)[EB/OL]. []. . 百度百科[EB/OL]. []. https://baike.baidu.com/.
香港云服务器租用推荐
服务器租用资讯
·广东云服务有限公司怎么样
·广东云服务器怎么样
·广东锐讯网络有限公司怎么样
·广东佛山的蜗牛怎么那么大
·广东单位电话主机号怎么填写
·管家婆 花生壳怎么用
·官网域名过期要怎么办
·官网邮箱一般怎么命名
·官网网站被篡改怎么办
服务器租用推荐
·美国服务器租用
·台湾服务器租用
·香港云服务器租用
·香港裸金属服务器
·香港高防服务器租用
·香港服务器租用特价