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

建设一个外贸网站多少钱呢高端做网站公司

建设一个外贸网站多少钱呢,高端做网站公司,建设免费网站登录网址,网站前台的网址目录 信号(signal)入门 技术应用角度的信号 注意 用kill -l命令可以察看系统定义的信号列表 信号处理常见方式概览 产生信号 1.通过终端(键盘)按键产生信号 signal函数 2. 调用系统函数向进程发信号 kill 函数 raise 函数 3.由软件条件产生的信号 alarm 函数 4.硬…目录 信号(signal)入门 技术应用角度的信号 注意 用kill -l命令可以察看系统定义的信号列表 信号处理常见方式概览 产生信号 1.通过终端(键盘)按键产生信号 signal函数 2. 调用系统函数向进程发信号 kill 函数 raise 函数 3.由软件条件产生的信号 alarm 函数 4.硬件异常产生信号 核心转储文件(core dump) ulimit指令 总结 阻塞(保存)信号 1.信号其他相关常见概念 2. 在内核中的表示 3.sigset_t类型 4.信号集操作函数 1. sigemptyset 2. sigfillset 3. sigaddset 4. sigdelset 5. sigismember sigprocmask sigpending 捕捉信号(信号的处理) 1.内核如何实现信号的捕捉 2.sigaction 可重入函数 volatile关键字 gcc/g在进行编译时是有一些优化级别的选项 信号(signal)入门 在计算机系统中信号是一种用于通知进程发生了某种事件的软件中断。它是一种进程间通信的方式通常用于在异步事件发生时通知进程例如用户输入、硬件错误、或者其他进程的状态变化。 信号的特点包括 1.异步性 信号的产生和处理是异步的即信号可以在任何时间点发生而进程必须随时准备好处理信号。 2.瞬时性 信号是一种瞬时事件通常是由硬件或其他进程生成被发送到目标进程后立即执行相应的处理函数。 3.中断性 信号是一种中断处理流程的机制进程在接收到信号时会中断当前的执行执行与该信号相关联的处理函数然后继续执行原来的流程。 信号的产生 信号可以由多种事件触发其中包括 4.硬件事件 例如除零错误、段错误等硬件异常可以触发相应的信号。 5.软件事件 进程可以使用系统调用 kill 主动发送信号给其他进程或者使用 raise 或 kill 自己产生信号。 6.用户操作 例如按下 CtrlC 键盘组合会发送一个 SIGINT 信号给前台进程。 7.其他进程的状态变化 当子进程终止时父进程会收到 SIGCHLD 信号。 在Linux系统中可以使用 kill -l命令显示所有的信号或系统调用 kill 来向进程发送信号。每个信号都有一个唯一的编号例如SIGINT 的编号是2SIGTERM 的编号是15。除了标准的信号还有一些特殊的信号如 SIGKILL 用于强制终止进程。 进程可以注册信号处理函数用于在接收到信号时执行特定的操作。这可以通过 signal 函数或 sigaction 函数来完成。处理函数可以是系统提供的默认处理函数也可以是用户自定义的函数。 总的来说信号是一种重要的进程间通信机制用于处理各种事件和异常情况使得进程能够响应外部环境的变化。 技术应用角度的信号 比如用户输入命令,在Shell下启动一个前台进程。用户按下Ctrl-C ,这个键盘输入产生一个硬件中断被OS获取解释成信号发送给目标前台进程前台进程因为收到信号进而引起进程退出如下 [rooterciyuan Day11]# ll 总用量 20 -rw-r--r-- 1 root root 82 11月 28 01:15 Makefile -rwxr-xr-x 1 root root 9184 11月 28 01:20 mysignal -rw-r--r-- 1 root root 221 11月 28 01:20 mysignal.cc [rooterciyuan Day11]# [rooterciyuan Day11]# cat mysignal.cc #include iostream #includeunistd.husing namespace std;int main() {while(true){cout 我是一个进程正在运行...pid: getpid() endl;sleep(1);}return 0; } [rooterciyuan Day11]# ./mysignal 我是一个进程正在运行...pid: 19927 我是一个进程正在运行...pid: 19927 我是一个进程正在运行...pid: 19927 我是一个进程正在运行...pid: 19927 ^C [rooterciyuan Day11]# 硬件中断 硬件中断是计算机体系结构中的一种机制用于处理和响应外部设备发出的信号或事件。当外部设备需要与计算机进行通信或发出某种请求时它会通过硬件中断发送一个信号给计算机的中央处理器CPU。 硬件中断可以是由各种外部设备触发的例如键盘、鼠标、网络适配器、磁盘控制器等。当外部设备发生相关事件或需要处理时它会发出一个硬件中断信号这个信号会被CPU的中断控制器接收。 硬件中断的处理过程如下 外部设备发出中断请求信号。CPU的中断控制器接收到中断请求信号并将其转发给中央处理器。中央处理器暂停当前正在执行的任务保存当前的执行状态并跳转到预定义的中断处理程序。中断处理程序会执行特定的操作来响应中断请求根据中断源的不同进行相应的处理。处理完后中断处理程序会恢复之前保存的执行状态并返回到中断发生的地方继续执行。 硬件中断的主要作用是允许外部设备与计算机进行异步通信而不需要不断地轮询设备的状态。它使得计算机能够响应外部设备的事件并及时进行处理提高了系统的效率和响应性能。 在操作系统中中断处理程序通常由设备驱动程序编写用于处理特定设备发出的中断请求。操作系统负责管理和分发中断请求将其分派给合适的中断处理程序进行处理。 进程是如何记录保存对应的信号 进程该如何记录对应产生的信号记录在哪里先描述在组织怎么描述一个信号用0和1来描述一个信号。用什么数据结构管理这个信号通过位图来管理产生的信号。 task _struct内部必定要存在一个位图结构用int表示: uint32_t signals; 0000 0000 0000 0000 0000 0001 0000 0000 (比特位的位置信号的编号比特位的内容,是否收到该信号) 所谓的发送信号本质其实写入信号直接修改特定进程的信号位图中的特定的比特位0-1 task_struct数据内核结构只能由OS进行修改--无论后面我们有多少种信号产生的方式最终都必须让OS来完成最后的发送过程! 信号产生之后,不是立即处理的。是在合适的时候进行处理。 注意 1. Ctrl-C 产生的信号只能发给前台进程。一个命令后面加个可以放到后台运行,这样Shell不必等待进程结束就可以接受新的命令,启动新的进程。 2. Shell可以同时运行一个前台进程和任意多个后台进程,只有前台进程才能接到像 Ctrl-C 这种控制键产生的信号 3. 前台进程在运行过程中用户随时可能按下 Ctrl-C 而产生一个信号,也就是说该进程的用户空间代码执行到任何地方都有可能收到 SIGINT 信号而终止,所以信号相对于进程的控制流程来说是异步(Asynchronous)的。 前台进程Foreground Process是指在终端或控制台中正在直接运行的进程前台进程在运行时我们无法输入指令。前台进程通常是用户当前正在交互的进程接收用户的输入并将输出显示在终端上。与之相对的是后台进程Background Process后台进程在终端不接受用户输入但仍然可以在系统中运行。 在Linux或类Unix系统中可以使用一些命令和操作符来控制前台和后台进程 启动前台进程 在终端中运行一个程序该程序将成为前台进程。例如 bash./my_program 启动后台进程 在命令末尾加上 符号可以将一个进程放到后台运行使终端立即返回可输入状态例如 bash./my_program 查看前台和后台进程 使用 jobs 命令可以列出当前终端中运行的所有作业包括前台和后台以及它们的状态。 将后台进程切换到前台 使用 fg 命令可以将一个后台进程切换到前台运行。例如fg %1 将编号为1的后台进程切换到前台。 将前台进程放到后台 使用 CtrlZ 可以将当前正在前台运行的进程暂停并将其放到后台。然后可以使用 bg 命令将其继续在后台运行。 终止进程 使用 CtrlC 可以发送 SIGINT 信号终止当前前台进程。使用 kill 命令可以发送其他信号例如 kill -9 PID 可以强制终止一个进程。 前台进程的交互性使得它们适合用户直接操作而后台进程则可以在不阻塞终端的情况下在后台执行任务。控制前台和后台进程的方法可以提供更灵活的进程管理。 用kill -l命令可以察看系统定义的信号列表 普通信号和实时信号是两种不同类型的信号它们在处理机制和特性上有一些区别。下面是它们的主要区别 实时信号的引入 普通信号 普通信号是早期UNIX系统中引入的其处理机制并没有特别强调对实时性的支持。实时信号 实时信号是为了满足对实时性和精确性要求更高的应用而引入的。它们在POSIX标准中定义并且相对于普通信号提供了更可靠的信号传递机制。 排队特性 普通信号 普通信号在接收端排队的能力有限同一种类型的信号在排队时可能会被合并成一个。实时信号 实时信号具有排队特性即同一种类型的信号可以被排队不会丢失。 信号编号范围 普通信号 普通信号的编号范围通常比较有限取值在1到31之间且不包括0。实时信号 实时信号的编号范围相对较大可以是任意正整数不受限于1到31的范围。 实时信号的优先级 普通信号 普通信号没有定义优先级的概念它们在信号队列中按照到达的顺序被处理。实时信号 实时信号可以具有优先级低编号的实时信号比高编号的实时信号具有更高的优先级。 实时信号的可靠性 普通信号 普通信号在传递和处理过程中可能会出现一些不可靠的情况例如丢失信号。实时信号 实时信号提供了更可靠的信号传递机制确保信号在传递和处理时的可靠性。 在使用信号时选择使用普通信号还是实时信号通常取决于应用程序的实际需求。如果应用程序对信号的实时性和可靠性有较高的要求那么使用实时信号可能更为适合。否则普通信号可能足够满足一般的信号通知需求。 信号处理常见方式概览 可选的处理动作有以下三种: 1. 忽略此信号。 2. 执行该信号的默认处理动作。 3. 提供一个信号处理函数(signal、sigaction函数),要求内核在处理该信号时切换到用户态执行这个处理函数,这种方式称为捕捉(Catch)一个信号 产生信号 1.通过终端(键盘)按键产生信号 signal函数 在Linux中signal 函数用于注册信号处理函数以便在程序接收到指定信号时执行相应的操作(简单来说signal的作用就是捕捉发送的信号并执行相应的自定义函数)。signal 函数的原型如下 #include signal.htypedef void (*sighandler_t) (int)//函数指针 sighandler_t signal(int signum, sighandler_t handler); typedef void (*sighandler_t)(int); 这行代码定义了一个类型别名 sighandler_t它是一个函数指针类型指向一个函数该函数接受一个整数参数代表信号编号返回 void。 然后signal 函数的原型是 sighandler_t signal(int signum, sighandler_t handler);这表示 signal 函数接受两个参数 signum表示要处理的信号的编号。可以是预定义的信号常量如 SIGINT 表示中断信号或自定义的信号编号。handler是一个函数指针指向用户定义的信号处理函数。当程序接收到指定信号时系统将调用这个函数执行相应的操作。如果 handler 的值是 SIG_IGN表示忽略该信号如果是 SIG_DFL表示使用系统默认的处理方式。 函数返回之前与指定信号相关联的信号处理函数的值。如果发生错误返回 SIG_ERR。 所以typedef void (*sighandler_t)(int); 定义了一个函数指针类型用于表示信号处理函数的类型而 signal 函数则用于注册信号处理函数。 下面是一个简单的例子演示了如何使用 signal 函数注册一个信号处理函数 #include stdio.h #include signal.h #include unistd.hvoid handler(int signo) {cout get a singal: signo endl; }int main() {// 注册 SIGINT 信号的处理函数为 sigint_handlerif (signal(SIGINT, sigint_handler) SIG_ERR) {perror(Unable to register SIGINT handler);return 1;}// 进入一个无限循环while(true){cout 我是一个进程正在运行...pid: getpid() endl;sleep(1);}return 0; } 在这个例子中程序在 main 函数中使用 signal 函数注册了 SIGINT 信号的处理函数为 sigint_handler。当用户按下CtrlC时程序将收到 SIGINT 信号然后调用 sigint_handler 函数来处理这个信号。 需要注意的是signal 函数在一些平台上被认为是不可靠的因为它对信号处理的具体实现可能有所不同。在现代的程序中更推荐使用 sigaction 函数因为它提供了更多的控制选项和可移植性。 #include iostream #include unistd.h #include signal.husing namespace std;//自定义方法 //signal作用:特定信号被发送给当前进程的时候执行handler方法的时候要自动填充对应的信号给handler方法 //我们甚至可以给所以信号设置同一个处理函数 void handler(int signo) {cout get a singal: signo endl;exit(2); }int main() {signal(2, handler);//ctrl csignal(3, handler);//ctrl \//signal(9, handler);// 9号 信号不可被捕捉因为9号只会执行默认动作。while(true){cout 我是一个进程正在运行...pid: getpid() endl;sleep(1);}return 0; } 捕捉键盘发送的2号和3号 信号 这里只介绍1 - 31号 信号(普通信号)这些信号各自在什么条件下产生,默认的处理动作是什么,在signal(7)手册中都有详细说明: man 7 signal。 2. 调用系统函数向进程发信号 kill 函数 在Linux中kill函数用于向指定的进程发送信号。具体语法如下 #include sys/types.h #include signal.hint kill(pid_t pid, int sig); 该函数的参数解释如下 pid要发送信号的进程ID。可以使用进程IDpid或进程组ID-pid发送信号。特殊值0表示发送给当前进程所属的进程组特殊值-1表示发送给所有具有权限的进程。其他特殊值如-2、-3和-4有特殊的含义用于特定情况下的信号发送。sig要发送的信号编号可以使用signal.h中定义的宏常量例如SIGINT表示中断信号(也可以改为使用这些宏常量的编号)。 该函数的返回值为成功发送信号的数量如果出错则返回-1并设置errno变量来指示错误类型。 以下是kill函数的一些常见用法 给指定进程发送信号 kill(pid, SIGTERM); // 发送SIGTERM信号给pid进程 发送终止信号给进程组 kill(-pid, SIGKILL); // 向进程组ID为pid的进程组发送SIGKILL信号 发送信号给当前进程所属的进程组 kill(0, SIGINT); // 向当前进程所属的进程组发送SIGINT信号 需要注意的是只有具有足够权限的进程才能向其他进程发送信号。进程接收到信号后可以通过注册信号处理函数来处理信号。 使用kill函数封装实现一个kill指令 //mykill.cc #include iostream #include cstdlib #include cstring #include unistd.h #include cerrno #include cassert #include string #include signal.h #include sys/types.husing namespace std;int count 0;void Usage(std::string proc) {//指令用法提示cout \tUsage: \n\t;cout proc 信号编号 目标进程\n endl; }int main(int argc, char *argv[]) {if(argc ! 3){Usage(argv[0]);exit(1);}int signo atoi(argv[1]);int target_id atoi(argv[2]);int n kill(target_id, signo);if(n ! 0){cerr errno : strerror(errno) endl;}return 0; } raise 函数 在Linux中raise 函数通常用于向当前进程发送信号(意思是谁调用我我就给谁发送信号)。这个函数的声明如下 #include signal.hint raise(int sig); 这个函数的目的是向当前进程发送信号 sig。如果成功返回0否则返回非零值。 使用 raise 函数你可以在程序中发送信号触发信号处理函数或默认的信号处理行为。例如如果你想向当前进程发送信号你可以这样做 #include stdio.h #include stdlib.h #include signal.hvoid myhandler(int signo) {cout get a signal: signo endl; }int main(int argc, char *argv[]) {signal(SIGINT, myhandler);while(true){sleep(1);raise(2);//自动发送信号}return 0; } 需要注意的是使用信号处理函数时要谨慎因为它们在异步环境中执行可能会导致一些不可预测的行为。 在C和C中也有一个类似raise系统函数的函数abort 函数用于终止程序的运行并生成一个程序终止信号。其声明如下 #include stdlib.hvoid abort(void); 调用 abort 函数会导致程序异常终止同时产生一个 SIGABRT 信号(6号信号)。默认情况下如果程序收到 SIGABRT 信号会产生一个核心转储文件core dump该文件包含程序在崩溃时的内存映像有助于调试。但是你可以通过设置环境变量 COREDUMP_DISABLE 来禁用核心转储文件的生成。 注abort发送的信号可以被捕捉就算是被捕捉了当前进程也会退出。 3.由软件条件产生的信号 SIGPIPE是一种由软件条件产生的信号,在“管道”中已经介绍过了。在操作系统中信号是用于在进程之间或由操作系统向进程发送通知的一种机制。信号可以由不同的条件产生包括硬件条件和软件条件。 软件条件产生的信号是由软件或操作系统内部的事件或条件引发的。这些信号用于与进程通信传递某些特定的事件或请求。 alarm 函数 在Linux系统中alarm 函数用于设置一个定时器以在指定的时间间隔后发送 SIGALRM 信号给正在运行的进程。这个函数的声明如下 #include unistd.hunsigned int alarm(unsigned int seconds); alarm 函数接受一个正整数参数 seconds也就是指定了定时器的时间间隔单位为秒。函数返回的是上一次设置的定时器剩余的时间如果之前没有设置定时器则返回0。 使用 alarm 函数可以在程序中创建一个简单的定时器。当指定的时间间隔过去后进程将收到 SIGALRM 信号。可以通过注册 SIGALRM 的信号处理函数来处理该信号并执行相应的操作。 注把alarm的参数设置为0就是取消闹钟。 下面是一个简单的示例演示了如何使用 alarm 函数创建一个定时器 #include stdio.h #include unistd.h #include signal.hvoid alarm_handler(int signum) {printf(Alarm received\n);// 执行需要在定时器触发时执行的操作 }int main() {signal(SIGALRM, alarm_handler); // 注册 SIGALRM 的信号处理函数unsigned int seconds 5;printf(Setting alarm for %u seconds\n, seconds);alarm(seconds); // 设置定时器// 其他的程序逻辑// ...while (1) {// 进程的其他工作// ...}return 0; } 在上面的例子中alarm_handler 函数是注册给 SIGALRM 信号的处理函数。当定时器触发时进程将收到 SIGALRM 信号并调用该处理函数在该函数中执行需要在定时器触发时执行的操作。 请注意alarm 函数只能设置一个全局定时器并且在调用 alarm 函数时之前设置的定时器将被新的定时器替换。如果你需要多个定时器可以考虑使用 timer_create 和 timer_settime 函数它们提供了更灵活和精确的定时器功能。 实验例子1 实验例子2 4.硬件异常产生信号 硬件异常被硬件以某种方式被硬件检测到并通知内核,然后内核向当前进程发送适当的信号。例如当前进程执行了除以0的指令,CPU的运算单元会产生异常,内核将这个异常解释 (8号)为SIGFPE信号发送给进程。再比如当前进程访问了非法内存地址,,MMU会产生异常,内核将这个异常解释为(11号)SIGSEGV信号发送给进程。 以下是一些常见的硬件异常及其相关的软件信号 SIGSEGV段错误由于试图访问未分配的内存或对只读内存执行写操作等引起的内存访问错误。硬件检测到这种错误时会发送SIGSEGV信号。SIGILL非法指令当进程执行了不合法或未定义的指令时产生。这可能是由于程序错误、二进制文件损坏等原因引起的。SIGFPE浮点异常由于进行了不合法的浮点运算如除以零而产生的信号。SIGBUS总线错误由于对计算机硬件总线上的地址执行了不合法的内存访问而产生。 这些信号是在进程运行时由硬件检测到的表明了发生了某些严重的错误。当进程收到这些信号时通常会执行相应的信号处理函数以进行清理操作、记录错误信息或终止进程。 要注意的是硬件异常通常表示程序中存在错误因此在正常情况下应该避免它们的发生。合理的错误处理和调试手段是确保程序健壮性的关键。 MMU MMU 是内存管理单元Memory Management Unit的缩写。它是计算机系统中的一个硬件组件负责执行虚拟内存到物理内存的地址映射以及访问内存时的权限控制。 主要功能包括 地址映射 将程序中使用的虚拟地址映射到物理内存中的实际地址。这样程序可以使用虚拟地址而不需要知道实际物理地址。内存保护 控制对内存的访问权限包括读、写、执行等。通过在页表中设置相应的权限位MMU 可以确保程序只能访问它被授权的内存区域。地址转换 将虚拟地址转换为物理地址。当程序访问某个虚拟地址时MMU 负责将其转换为实际的物理地址。缓存控制 管理虚拟内存与物理内存之间的数据缓存以提高访问速度。TLBTranslation Lookaside Buffer 一种用于加速地址转换的高速缓存存储了最近使用的虚拟地址到物理地址的映射。这有助于避免每次地址访问都要完全查询页表的开销。 MMU 的引入使得操作系统能够实现虚拟内存的概念从而提供了一种抽象层使得程序可以使用比实际物理内存更大的虚拟地址空间。这对于多任务处理、内存保护和地址空间隔离等方面都有很大的好处。 总的来说MMU 在计算机体系结构中发挥着至关重要的作用为操作系统提供了有效管理内存的手段同时提高了系统的可靠性和安全性。 核心转储文件(core dump) ulimit指令 ulimit 是一个用于设置或显示用户级资源限制的命令。这个命令通常在命令行终端中使用它允许用户限制特定的资源以防止单个用户或进程占用过多的系统资源。 以下是一些常见的用法和选项 ulimit -a 或 ulimit -all显示所有的资源限制。这将列出当前 shell 的所有资源限制包括软限制和硬限制。 bashulimit -a ulimit -c [限制]设置或显示核心转储文件的大小限制。如果没有给定限制它将显示当前限制。 bashulimit -c unlimited ulimit -n [限制]设置或显示文件描述符的数量限制。 bashulimit -n 1024 ulimit -u [限制]设置或显示用户进程数限制。 bashulimit -u 500 ulimit -q [限制]设置或显示队列的大小限制。 bashulimit -q 1000 ulimit -f [限制]设置或显示文件的大小限制。 bashulimit -f unlimited ulimit -l [限制]设置或显示锁定内存的大小限制。 bashulimit -l 64 ulimit -s [限制]设置或显示堆栈的大小限制。 bashulimit -s 8192 ulimit -v [限制]设置或显示虚拟内存的大小限制。 bashulimit -v 1048576 这些是 ulimit 命令的一些常见用法。请注意ulimit 命令设置的资源限制通常只对当前的 shell 会话有效并且这些限制可能会被子进程继承。如果你希望更改全局系统范围内的资源限制通常需要在系统启动时或者使用特定配置文件中进行设置。 总结 1.上面所说的所有信号产生最终都要有OS来进行执行为什么 因为OS是进程的管理者只有OS有权利修改进程PCB当中的数据。 2.信号的处理是否是立即处理的 在合适的时候进行处理。 3.信号如果不是被立即处理那么信号是否需要暂时被进程记录下来记录在哪里最合适呢 是的记录在PCB当中。 4.一个进程在没有收到信号的时候能否能知道自己应该对合法信号作何处理呢 知道因为他已经被默认设置进了编码进程的处理逻辑当中。 5.如何理解OS向进程发送信号能否描述一下完整的发送处理过程 不管是用户通过键盘系统调用、还是软件条件或者硬件异常无论什么方式操作系统都一定能识别到识别到之后向目标进程写信号这就是发信号的过程。 阻塞(保存)信号 1.信号其他相关常见概念 实际执行信号的处理动作称为信号递达(Delivery)。 信号从产生到递达之间的状态,称为信号未决(Pending)。 进程可以选择阻塞 (Block )某个信号。被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。 注意阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。 2. 在内核中的表示 信号在内核中的表示示意图 1、每个信号都有两个标志位分别表示阻塞(block也称为信号屏蔽集)和未决(pending也称为pending信号集),还有一个函数指针表示处理动作。信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。在上图的例子中,SIGHUP信号未阻塞也未产生过,当它递达时执行默认处理动作。 2、SIGINT信号产生过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。 3、SIGQUIT信号未产生过,一旦产生SIGQUIT信号将被阻塞,它的处理动作是用户自定义函数sighandler。如果在进程解除对某信号的阻塞之前这种信号产生过多次,将如何处理?POSIX.1允许系统递送该信号一次或多次。Linux是这样实现的:常规信号在递达之前产生多次只计一次,而实时信号在递达之前产生多次可以依次放在一个队列里。本章不讨论实时信号 3.sigset_t类型 从上图来看,每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。因此未决和阻塞标志可以用相同的数据类型sigset_t(sigset_t类型是一个位图结构)来存储sigset_t称为信号集这个类型可以表示每个信号的“有效”或“无效”状态在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞而在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态。我们将详细介绍信号集的各种操作。 阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask),这里的“屏蔽”应该理解为阻塞而不是忽略。(直白点就是sigset_t类型里面包含了两张表分别是block表和pending表 4.信号集操作函数 sigset_t类型对于每种信号用一个bit表示“有效”或“无效”状态,至于这个类型内部如何存储这些bit则依赖于系统实现,从使用者的角度是不必关心的,使用者只能调用以下函数来操作sigset_ t类型变量而不应该对它的内部数据做任何解释,比如用printf直接打印sigset_t变量是没有意义的。 以下是与 sigset_t 相关的一些常见函数 1. sigemptyset 函数原型int sigemptyset(sigset_t *set);功能清空信号集合将所有信号从集合中移除。示例 sigset_t my_set; sigemptyset(my_set); 2. sigfillset 函数原型int sigfillset(sigset_t *set);功能将所有信号添加到信号集合中即将信号集合设置为包含所有信号。示例 sigset_t my_set; sigfillset(my_set); 3. sigaddset 函数原型int sigaddset(sigset_t *set, int signum);功能将指定的信号添加到信号集合中。示例 sigset_t my_set; sigemptyset(my_set); sigaddset(my_set, SIGINT); 4. sigdelset 函数原型int sigdelset(sigset_t *set, int signum);功能从信号集合中删除指定的信号。示例 sigset_t my_set; sigfillset(my_set); sigdelset(my_set, SIGTERM); 5. sigismember 函数原型int sigismember(const sigset_t *set, int signum);功能检查指定的信号是否包含在信号集合中。示例 sigset_t my_set; sigfillset(my_set); if (sigismember(my_set, SIGUSR1)) {// SIGUSR1 在信号集合中 } 这些函数通常用于在信号处理中设置和管理信号集合。例如在使用 sigprocmask 函数时你可能会使用 sigset_t 来指定哪些信号需要被阻塞。这些函数提供了对信号集合进行操作的便利方式。 前四个函数都是成功返回0,出错返回-1。sigismember是一个布尔函数,用于判断一个信号集的有效信号中是否包含某种 信号,若包含则返回1,不包含则返回0,出错返回-1。 sigprocmask 在Linux中sigprocmask 函数用于检查或修改进程的信号屏蔽集signal mask。信号屏蔽集(信号屏蔽集指的是block表)是一个集合用于指定哪些信号在调用时应该被阻塞即不被传递给进程。 以下是 sigprocmask 函数的基本信息 #include signal.hint sigprocmask(int how, const sigset_t *set, sigset_t *oldset); //返回值若成功则为0若出错则为-1 how表示要执行的操作可以是以下值之一 SIG_BLOCK(添加)将指定的信号集合添加到当前信号屏蔽集中阻塞这些信号。SIG_UNBLOCK(删除)从当前信号屏蔽集中移除指定的信号集合解除对这些信号的阻塞。SIG_SETMASK(覆盖)更改当前进程信号屏蔽集将当前参数信号屏蔽集(set)设置为指定的信号集合(how)。 set对应于 how 操作的信号集合。oldset如果不为 NULL则在函数调用结束时存储之前的信号屏蔽集。 如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出。如果set是非空指针,则 更改进程的信号屏蔽字,参数how指示如何更改。如果oset和set都是非空指针,则先将原来进程的信号 屏蔽字备份到oset里,然后根据set和how参数更改信号屏蔽字。假设当前的信号屏蔽字为mask,下表说明了how参数的可选值 下面是一些示例用法 using namespace std;void showBlock(sigset_t *oset) {int signo 1;//从1开始因为没有0号信号for(; signo 31; signo)//遍历所有的比特位{if(sigismember(oset, signo))cout 1;elsecout 0;}cout endl; }int main() {//1.只是在用户层面上进行设置。设置的什么设置的信号//说直白点sigaddset(set, 2);只是把信号设置到了set变量里//通过第2步调用sigprocmask(SIG_SETMASK, set, oset);才是设置到进程里sigset_t set, oset;sigemptyset(set);//清空(初始化)sigemptyset(oset);sigaddset(set, 2);//SIGIN//2.设置进入进程谁调用设置给谁int cnt 0;sigprocmask(SIG_SETMASK, set, oset);while(true){showBlock(oset);//输出旧的信号集的所有信号sleep(1);cnt;if(cnt 10){cout recover block endl;sigprocmask(SIG_SETMASK, oset, set);//恢复旧的信号集showBlock(set);//输出旧的信号集的所有信号showBlock(oset);//输出旧的信号集的所有信号sleep(10);}}return 0; } 这个例子演示了如何使用 sigprocmask 函数来设置和修改信号屏蔽集。 sigpending sigpending 函数用于获取当前进程被阻塞但是已经产生的待处理信号集。这个函数允许程序查询在信号阻塞状态下已经产生但尚未处理的信号。以下是 sigpending 函数的基本信息 #include signal.hint sigpending(sigset_t *set); set用于存储待处理信号的信号集。函数成功调用后set 将被设置为包含了当前被阻塞的、但已经产生的信号。 函数返回值 如果成功返回0。如果失败返回-1并设置 errno 表示错误的原因。 下面是一个简单的示例演示如何使用 sigpending 函数 #include iostream #include signal.h #include assert.h #include unistd.husing namespace std;void handler(int signo) {cout 对特定信号 signo 执行捕捉 endl; }void PrintPemding(const sigset_t pending) {cout 当前进程的pending位图;for(int signo 1; signo 31; signo){if(sigismember(pending, signo))cout 1;elsecout 0;}cout endl; }int main() {//2.0 设置对2号信号的自定义捕捉以防止解除对2号信号的屏蔽之后退出进程signal(2, handler);sigset_t set, oset;//1.1 初始化sigemptyset(set);sigemptyset(oset);//1.2 将2号信号添加到set中sigaddset(set, 2);//1.3 将新的信号屏蔽字设置到进程sigprocmask(SIG_BLOCK, set, oset);int cnt 0;while(true){//2.1 先获取pending信号集sigset_t pending;//用来存储被阻塞的信号sigemptyset(pending);int n sigpending(pending);//获取被阻塞的信号assert(n 0);(void)n;//2.2 打印方便进行查看PrintPemding(pending);//2.3 休眠时间sleep(1);//2.4 10s之后恢复所以信号的屏蔽(block)动作if(cnt 10){cout 解除对2号信号的屏蔽 endl;sigprocmask(SIG_SETMASK, oset, nullptr);}}return 0; } 捕捉信号(信号的处理) 我们之前说过信号的处理(信号的递达)可以不是立即执行而是合适的时候那么这个合适指的又是什么时候 信号可以立即被处理吗如果一个信号之前被block了当他解除block的时候对应的信号会被立即递达因为信号的产生是异步的当前进程可能正在做更重要的事情 什么时候是合适的时候当进程从内核态 切换回 用户态的时候进程会在OS的指导下进行信号的检测与处理 用户态执行你写的代码的时候进程所处的状态。 内核态执行OS的代码的时候进程所处的状态 所以什么时候从用户态进入内核态呢 1.进程时间片到了需要切换就要执行进程切换逻辑。2.系统调用 1.内核如何实现信号的捕捉 如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号。由于信号处理函数的代码是在用户空间的,处理过程比较复杂,举例如下: 用户程序注册(设置捕捉)了SIGQUIT信号的处理函数sighandler。 当前正在执行main函数,这时发生中断或异常切换到内核态。 在中断处理完毕后要返回用户态的main函数之前检查到有信号SIGQUIT递达。 内核决定返回用户态后不是恢复main函数的上下文继续执行,而是执行sighandler函 数,sighandler和main函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系,是 两个独立的控制流程。 sighandler函数返回后自动执行特殊的系统调用sigreturn再次进入内核态。 如果没有新的信号要递达,这次再返回用户态就是恢复main函数的上下文继续执行了。 2.sigaction 在Linux中sigaction 函数用于设置对信号的处理方式调用成功则返回0,出错则返回- 1 。以下是 sigaction 函数的一般形式 #include signal.hint sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); //sigaction函数和signal函数类似都是用来捕捉当前进程发送的信号 signum表示要处理(捕捉)的信号的编号比如 SIGINT 代表中断信号。act是一个指向 struct sigaction 结构体类型的指针该结构包含了对信号的新的处理方式处理函数、标志等。oldact是一个指向 struct sigaction 结构的指针如果不为 NULL则 oldact 将用于存储之前对信号的处理方式。 struct sigaction 这个结构类型定义在 signal.h 头文件中。以下是 struct sigaction 结构的简化原型 cstruct sigaction {void (*sa_handler)(int); // 处理函数的地址或者是 SIG_IGN、SIG_DFLvoid (*sa_sigaction)(int, siginfo_t *, void *); // 用于三参数信号的处理函数的地址sigset_t sa_mask; // 指定在信号处理期间需要被屏蔽的信号集int sa_flags; // 指定信号处理的一些标志void (*sa_restorer)(void); // 恢复函数的地址 }; sa_handler用于设置信号处理函数的地址或者可以指定为 SIG_IGN忽略信号或 SIG_DFL使用默认处理方式。sa_sigaction用于设置三参数信号的处理函数的地址。如果 sa_handler 被使用这个字段将被忽略。sa_mask指定在信号处理期间需要被屏蔽的信号集。sa_flags指定信号处理的一些标志例如 SA_RESTART 表示在系统调用中自动重启被信号中断的系统调用。sa_restorer用于设置恢复函数的地址。在一些旧的系统中可能使用一般置为 NULL。 在使用 sigaction 函数时你可以通过设置 act 参数为指向一个 struct sigaction 结构的指针从而定义对特定信号的处理方式。 注 当某个信号的处理函数被调用时内核自动将当前信号加入进程的信号屏蔽字(说白了就是操作系统正在执行某一个信号的处理函数时哪怕是这个信号曾经没有被设置为block状态(信号屏蔽字)即block表对应的比特位由0置为1操作系统也会自动将这个信号设置为block状态。简单来说就是后来的信号要排队直到当前信号的处理函数被执行完才会轮到下一个信号)当信号处理完函数返回时也会自动恢复原来的信号屏蔽字(即block表对应的比特位由1置为0)这样就保证了在处理某个信号时如果这种信号再次产生那么它会被阻塞到当前处理结束为止。 如果在调用信号处理函数时,除了当前信号被自动屏蔽之外还希望自动屏蔽另外一些信号(比如说再屏蔽3号和4号信号)则用sa_mask字段说明这些需要额外屏蔽的信号(也就是说把3号和4号添加到sa_mask里)当信号处理函数返回时自动恢复原来的信号屏蔽字。 sa_flags字段包含一些选项,本章的代码都把sa_flags设为0sa_sigaction是实时信号的处理函数,本章不详细解释这两个字段,有兴趣的可以自己在了解一下。 下面是一个示例演示如何使用 sigaction 函数 #include iostream #include signal.h #include assert.h #include unistd.h #include string.husing namespace std;void PrintPemding(const sigset_t pending) {cout 当前进程的pending位图;for(int signo 1; signo 31; signo){if(sigismember(pending, signo))cout 1;elsecout 0;}cout endl; }void handler(int signo) {cout 对特定信号 signo 执行捕捉 endl;int cnt 10;while(cnt){cnt--;sigset_t pending;sigemptyset(pending);sigpending(pending);//获取被阻塞的信号PrintPemding(pending);//输出pending表的位图sleep(1);} }int main() {struct sigaction act, oldact;memset(act, 0, sizeof(act));memset(act, 0, sizeof(oldact));act.sa_handler handler;act.sa_flags 0;sigemptyset(act.sa_mask);sigaddset(act.sa_mask, 3);sigaction(2, act, oldact);while(true){cout getpid() endl;sleep(1);}return 0; } 可重入函数 可重入函数reentrant function是指一个函数在被多个任务线程同时调用时能够正确地处理多个调用而不会出现冲突或错误。这通常需要确保函数内部使用的数据是线程安全的不依赖于全局状态而是依赖于函数的参数和局部变量。 main函数调用insert函数向一个链表head中插入节点node1,插入操作分为两步,刚做完第一步的 时候,因为硬件中断(时间片到了)使进程切换到内核,再次回用户态之前检查到有信号待处理,于是切换 到sighandler函 数,sighandler也调用insert函数向同一个链表head中插入节点node2,插入操作的 两步都做完之后从 sighandler返回内核态,再次回到用户态就从main函数调用的insert函数中继续 往下执行,先前做第一步 之后被打断,现在继续做完第二步。结果是,main函数和sighandler先后 向链表中插入两个节点,而最后只有一个节点真正插入链表中了。 像上例这样,insert函数被不同的控制流程调用,有可能在第一次调用还没返回时就再次进入该函数,这称 为重入,insert函数访问一个全局链表,有可能因为重入而造成错乱,像这样的函数称为 不可重入函数,反之, 如果一个函数只访问自己的局部变量或参数,则称为可重入(Reentrant) 函数。想一下,为什么两个不同的 控制流程调用同一个函数,访问它的同一个局部变量或参数就不会造成错乱? 以下是一些确保函数可重入性的常见做法 使用本地变量 避免使用全局变量因为全局变量是共享的可能导致不同线程之间的竞态条件。使用函数的参数和局部变量这样每个线程都有自己的副本。避免使用静态变量 静态变量在多线程环境中可能引发问题。如果确实需要使用静态变量要确保它们在函数内部是线程私有的可以通过关键字 static 和函数作用域来实现。避免使用不可重入的库函数 有些库函数是不可重入的因为它们使用了全局变量或其他共享资源。在多线程环境中应该选择可重入的库函数或者使用线程安全的版本。使用互斥锁 在必要的情况下可以使用互斥锁来保护共享资源确保同一时刻只有一个线程能够访问这些资源。注意信号处理 在信号处理函数中要谨慎使用那些不是异步信号安全async-signal-safe的函数因为信号处理是在中断上下文中执行的可能会中断正在执行的函数。避免递归调用 在一些情况下递归调用可能导致函数不可重入。确保函数能够正确处理递归调用或者避免使用递归。 可重入函数的设计考虑到了并发执行的需求因此在多线程环境中更为安全。在使用现代编程语言和库时通常会提供一些线程安全的工具和函数但程序员仍然需要注意函数的可重入性。 volatile关键字 volatile 是一个在C和C中使用的关键字它主要用于告诉编译器不要对被声明为 volatile 的变量进行优化因为这些变量的值可以在程序的执行流中被意外地改变。 主要作用 禁止编译器优化 当一个变量被声明为 volatile 时编译器会避免对该变量的操作进行优化。这是因为该变量的值可以被意外地改变例如在中断服务例程中。告知编译器不要缓存 对于一些对硬件寄存器进行读写的情况使用 volatile 可以告诉编译器不要将这些寄存器的值缓存在寄存器中而是要每次都从内存中读取。这是因为这些寄存器的值可能会被硬件或者其他并发的代码改变。 示例 cvolatile int flag 0; // 定义一个 volatile 变量void interruptServiceRoutine() {// 在中断服务例程中改变 flag 的值flag 1; }int main() {while (flag 0) {// 在循环中检查 flag 的值// 由于 flag 是 volatile编译器不会进行优化确保每次都从内存中读取 flag 的值}// 执行其他操作...return 0; } 注意事项 不解决并发问题volatile 并不能解决并发访问的问题。它仅仅告诉编译器不要对这个变量进行某些优化但并不提供同步机制。在多线程环境下仍需要使用互斥锁等机制来确保对变量的原子操作。适用于特定场景volatile 通常用于与硬件相关的编程比如在嵌入式系统中对寄存器的访问。不同编译器的实现可能有差异 标准中对 volatile 的语义定义相对宽泛因此不同编译器可能有不同的实现方式特别是在多线程环境下。在需要跨平台的代码中需要注意这一点。 总的来说volatile 是一种告知编译器的工具用于处理一些特定的、容易被优化掉的场景以确保程序的行为符合预期。 gcc/g在进行编译时是有一些优化级别的选项 这些选项是用来设置编译器的优化级别的通常用于控制生成可执行程序时的优化程度。这些选项的含义可能略有不同具体取决于所使用的编译器但一般来说它们包含以下几个级别 -O0 不进行优化。编译器将生成易于调试的代码包括完整的调试信息以便于在调试器中进行源代码级别的调试。这会导致生成的可执行文件较大执行速度较慢但对于调试目的非常有用。-O1 低级别的优化。编译器会执行一些基本的优化如删除不可达代码和一些局部优化但不会进行过多的优化以确保编译速度较快。-O2 中级别的优化。在-O1的基础上编译器会执行更多的优化包括一些可能会增加编译时间的优化。这通常会产生更高效的代码但也可能增加生成可执行文件的时间。-O3 高级别的优化。这一级别会进行更多、更激进的优化包括一些可能会导致编译时间显著增加的优化。生成的代码可能更加紧凑和高效但这也可能导致一些编译器可能无法处理的问题或者增加代码的复杂性。-Os 以尽可能减小目标文件的大小为目标进行优化。这个选项更注重代码大小而非执行速度适用于一些嵌入式系统或者需要优化可执行文件大小的场景。-Ofast 启用除了标准不允许的一些优化例如允许忽略 IEEE 浮点数规范可能会导致数学计算结果的不确定性。这个选项通常用于对执行速度要求非常高、而对精确性要求相对较低的场景。-Og 优化以保留调试信息的方式。这个选项在-O1级别的基础上进行优化但同时保留了对调试的支持用于在开发阶段进行调试。-On 一些编译器可能提供其他的优化级别如 -O4、-O5 等具体含义取决于编译器的实现。 选择优化级别通常是一个权衡需要考虑编译时间和生成代码的效率。在开发和调试阶段通常会选择较低的优化级别以获得更好的调试支持和更快的编译速度。在最终发布版本时可以选择较高的优化级别以获得更好的执行性能。 请注意具体的优化选项和级别可能因编译器而异建议查阅编译器的文档以获取详细信息。在实际应用中选择适当的优化级别需要根据具体情况进行权衡考虑编译时间、可执行文件大小和执行性能。 SIGCHLD(17号信号) 进程一章讲过用wait和waitpid函数清理僵尸进程,父进程可以阻塞等待子进程结束,也可以非阻 塞地查询是否有子进程结束等待清理(也就是轮询的方式)。采用第一种方式,父进程阻塞了就不 能处理自己的工作了;采用第二种方式,父进程在处理自己的工作的同时还要记得时不时地轮询一 下,程序实现复杂。 其实,子进程在终止时会给父进程发SIGCHLD信号,该信号的默认处理动作是忽略,父进程可以自 定义SIGCHLD信号的处理函数,这样父进程只需专心处理自己的工作不必关心子进程了,子进程 终止时会通知父进程,父进程在信号处理函数中调用wait清理子进程即可。 编写一个程序完成以下功能父进程fork出子进程,子进程调用exit(2)终止父进程自定义SIGCHLD信号的处理函数,在其中调用wait获得子进程的退出状态并打印。 方法一 #include iostream #include signal.h #include sys/types.h #include unistd.h #include stdlib.h #includesys/wait.husing namespace std;pid_t id;//因为信号和当前的main是两个执行流所以定义为全局的。void handler(int signo) {cout 捕捉到一个信号 signo , who getpid() endl;sleep(3);//等待3秒期间可以查看子进程是否处于僵尸状态while(1){//0:阻塞式等待但是我们这里绝对不会阻塞为什么呢因为我已经收到了信号所以当前子进程//肯定要退出了所以wait只要调用就会立马回收子进程并返回//-1等待任意一个子进程退出只要有死掉的子进程就会一直回收pid_t res waitpid(-1, NULL, WNOHANG);//返回成功res就是等待的子进程的pidif(res 0){printf(wait success, res: %d, id: %d\n, res, id);}elsebreak;}cout handler done... endl; }int main() {signal(SIGCHLD, handler);//如果一次性创建多个子进程呢int i 1;for(; i 10; i){id fork();if(id 0){//childint cnt 5;while(cnt){cout 我是子进程我的pid getpid() , ppid getppid() endl;sleep(1);cnt--;}exit(1);}}//如果你的父进程没有事干你还是用以前的方法//如果你的父进程很忙而且不退出可以选择信号的方法while(1){sleep(1);}return 0; }事实上,由于UNIX 的历史原因,要想不产生僵尸进程还有另外一种办法父进程调 用 signal/sigaction 将SIGCHLD的处理动作置为SIG_IGN (SIG_IGN表示忽略该信号)这样fork出来的子进程在终止时会自动清理掉,不 会产生僵尸进程,也不会通知父进程。系统默认的忽略动作和用户用sigaction函数自定义的忽略 通常是没有区别的,但这是一个特例。此方法对于Linux可用,但不保证在其它UNIX系统上都可 用。请编写程序验证这样做不会产生僵尸进程 方法二 nt main() {signal(SIGCHLD, SIG_IGN);//将SIGCHLD的参数设置为SIG_IGN即可自动回收子进程//如果一次性创建多个子进程呢int i 1;for(; i 10; i){id fork();if(id 0){//childint cnt 5;while(cnt){cout 我是子进程我的pid getpid() , ppid getppid() endl;sleep(1);cnt--;}exit(1);}}//如果你的父进程没有事干你还是用以前的方法//如果你的父进程很忙而且不退出可以选择信号的方法while(1){sleep(1);}return 0; } 因为子进程在终止时会给父进程发SIGCHLD信号,该信号的默认处理动作是忽略所以父进程调 用 signal/sigaction 将它们参数的SIGCHLD的处理动作置为SIG_IGN (SIG_IGN表示忽略该信号)这样fork出来的子进程在终止时会自动清理掉,不会产生僵尸进程,也不会通知父进程。此方法对于Linux可用,但不保证在其它UNIX系统上都可 用。
http://www.zqtcl.cn/news/949801/

相关文章:

  • 产品展示网站含后台网站模板下载网站开发什么语言好
  • 做知乎网站的图片如何设计好网站
  • 广州企业网站推广织梦学校网站模板
  • 国内响应式网站案例深圳住房和城乡建设局网站
  • 网页制作网站首页中国建筑论坛网
  • 众创空间网站建设少年宫网站建设模块
  • 企业营销型网站的内容科技公司取名大全
  • 哈尔滨云建站模板投资公司的钱从哪里来
  • 海南做网站公司哪家好中国人做外贸生意的网站
  • 没有网站怎么做cpa成都百度推广公司地址
  • 龙湖地产 网站建设高端上海网站设计公司
  • 触屏手机网站模板装修设计软件排名
  • 怎么做盗文网站郑州建设教育培训中心
  • 网站安全解决方案嵌入式软件工程师培训
  • 怎么做一种网站为别人宣传网站界面切片做程序
  • 麻涌网站建设河北网站建设联系方式
  • 建设银行官方网站打不开啊寮步仿做网站
  • 一个人可做几次网站备案峰峰网站建设
  • 怎么盗号网站怎么做北京高端网站设计外包公司
  • 著名的淘宝客网站wordpress博客内容预览
  • 成都网站seo公司甘肃网站建设推广
  • 做网站加班网站项目意义
  • 在虚拟机中如何做二级域名网站个人网站做哪种能赚钱
  • 贵州建设水利厅考试网站wordpress主查询翻页
  • 网站优化网络推广seo天津建设工程信息网几点更新
  • 兰州网站seo技术厂家比较实用的h5网页建设网站
  • 怎样让自己做的网站被百度收录动漫制作软件
  • 西安网站制作哪家公司好怎么向企业推销网站建设
  • 电子商务网站建设新闻深圳坂田网站设计公司有哪些
  • 上海电子商城网站制作wordpress循环该分类子分类