您的位置:首页 > 教育 > 培训 > 广州肇庆疫情最新消息_制作h5网页软件_西安seo服务公司排名_如何做企业网站

广州肇庆疫情最新消息_制作h5网页软件_西安seo服务公司排名_如何做企业网站

2025/2/24 21:12:57 来源:https://blog.csdn.net/lyy42995004/article/details/144161142  浏览:    关键词:广州肇庆疫情最新消息_制作h5网页软件_西安seo服务公司排名_如何做企业网站
广州肇庆疫情最新消息_制作h5网页软件_西安seo服务公司排名_如何做企业网站

Linux系统 信号

  • 发送信号
    • 1. 进程组
    • 2. /bin/kill 程序发送信号
    • 3. 键盘发送信号
    • 4. 用kill函数发送信号
    • 5. 用alarm函数发送信号
  • 接收信号
  • 阻塞和解除阻塞信号
  • 编写信号处理程序
  • 显式等待信号

信号是一种软件中断形式,用于通知进程发生了特定事件。它是进程间异步通信的一种方式,意味着信号可以在进程执行的任意时刻到达,而进程不一定正在等待该信号。例如,当用户在终端按下 Ctrl+C 时,内核会向当前正在运行的进程发送一个SIGINT信号,通知进程用户希望中断其执行。

序号名称默认行为相应事件
1SIGHUP终止终端线挂断
2SIGINT终止来自键盘的中断
3SIGQUIT终止来自键盘的退出
4SIGILL终止非法指令
5SIGTRAP终止并转储内存跟踪陷阱
6SIGABRT终止并转储内存来自 abort 函数的终止信号
7SIGBUS终止总线错误
8SIGFPE终止并转储内存浮点异常
9SIGKILL终止(不能被捕获和忽略)杀死程序
10SIGUSR1终止用户定义的信号 1
11SIGSEGV终止并转储内存无效的内存引用(段故障)
12SIGUSR2终止用户定义的信号 2
13SIGPIPE终止向一个没有读用户的管道做写操作
14SIGALRM终止来自 alarm 函数的定时器信号
15SIGTERM终止软件终止信号
16SIGSTKFLT终止协处理器上的栈故障
17SIGCHLD忽略一个子进程停止或者终止
18SIGCONT忽略继续进程如果该进程停止
19SIGSTOP停止直到下一个SIGCONT(不能被捕获和忽略)不是来自终端的停止信号
20SIGTSTP停止直到下一个SIGCONT来自终端的停止信号
21SIGTTIN停止直到下一个SIGCONT后台进程从终端读
22SIGTTOU停止直到下一个SIGCONT后台进程向终端写
23SIGURG忽略套接字上的紧急情况
24SIGXCPU终止CPU 时间限制超出
25SIGXFSZ终止文件大小限制超出
26SIGVTALRM终止虚拟定时器期满
27SIGPROF终止剖析定时器期满
28SIGWINCH忽略窗口大小变化
29SIGIO终止在某个描述符上可执行 I/O 操作
30SIGPWR终止电源故障

传送一个信号到目的进程是由两个不同步骤组成的:

  • 发送信号:内核通过更新目的进程上下文中的某个状态,发送一个信号给目的进程。发送信号可以有如下两种原因:

    • 1)内核检测到一个系统事件,比如除零错误或者子进程终止。
    • 2)一个进程调用了 kill 函数(在下一节中讨论),显式地要求内核发送一个信号给目的进程。一个进程可以发送信号给它自己。
  • 接收信号:当目的进程被内核强迫以某种方式对信号的发送做出反应时,它就接收了信号。进程可以忽略这个信号,终止或者通过执行一个称为信号处理程序(signal handler)的用户层函数捕获这个信号。图 8-27 给出了信号处理程序捕获信号的基本思想。

在这里插入图片描述

待处理信号:每种信号类型在同一时刻至多有一个待处理信号,新发送的同类型信号若已有待处理信号则会被丢弃。内核通过pending位向量记录待处理信号,信号传送时设置相应位,接收后清除该位。

信号阻塞机制:进程可选择性阻塞信号,被阻塞信号仍可发送但产生的待处理信号暂不被接收。内核用blocked位向量维护被阻塞信号,进程可操作此位向量控制信号阻塞状态,从而灵活管理信号接收时机,有助于系统资源管理和进程执行的稳定性与可预测性。

发送信号

Unix 系统提供了大量向进程发送信号的机制。所有这些机制都是基于进程组(process group)这个概念的。

1. 进程组

每个进程都只属于一个进程组,进程组是由一个正整数进程组 ID 来标识的。

#include <unistd.h>pid_t getpgrp(void);
// 返回:调用进程的进程组 ID。

getpgrp 用于获取调用进程所属的进程组 ID。

#include <unistd.h>int setpgid(pid_t pid, pid_t pgid);
// 返回:若成功则为o,若错误则为 -1。

setpgid 用于设置进程的进程组 ID,从而将进程加入某个进程组或创建新的进程组。

2. /bin/kill 程序发送信号

使用完整路径 /bin/kill,因为有些 Unix shell 有自己内置的 kill 命令。

/bin/kill -9 15213 发送信号9(SIGKILL)给进程15213。

/bin/kill -9 -15213发送信号9(SIGKILL)给进程组15213的每个进程。

3. 键盘发送信号

ls | sort会创建一个由两个进程组成的前台作业,这两个进程是通过 Unix 管道连接起来的:一个进程运行 ls 程序,另一个运行 sort 程序。shell 为每个作业创建一个独立的进程组。进程组 ID 通常取自作业中父进程中的一个。

在这里插入图片描述

在键盘上输入 Ctrl+C 会导致内核发送一个 SIGINT 信号到前台进程组中的每个进程,会终止前台作业。输入 Ctrl+Z 会发送一个 SIGTSTP 信号到前台进程组中的每个进程,会停止(挂起)前台作业。

4. 用kill函数发送信号

#include <sys/types.h>
#include <signal.h>int kill(pid_t pid, int sig);
// 返回:若成功则为 0,若错误则为 -1。

kill 用于向指定的进程或进程组发送信号。

  • pid > 0:信号发送给指定 PID 的进程。
  • pid = 0:信号发送给与调用进程在同一进程组的所有进程。
  • pid < 0:信号发送给进程组 ID 为 -pid 的所有进程。
  • pid = -1:信号发送给调用进程有权限发送的所有进程。

5. 用alarm函数发送信号

#include <unistd.h>unsigned int alarm(unsigned int secs);
// 返回:前一次闹钟剩余的秒数,若以前没有设定闹钟,则为0。

alarm 用于设置一个定时器,在指定时间后向当前进程发送 SIGALRM 信号。

接收信号

内核在将进程从内核模式切换到用户模式时(如系统调用返回或上下文切换完成后)检查进程未被阻塞的待处理信号集合(通过**pending &~blocked**计算得出)。若集合为空,进程正常执行下一条指令;若不为空,内核选择一个信号(通常是最小的k)让进程接收。每个信号类型都有一个预定义的默认行为,是下面中的一种:

  • 进程终止。
  • 进程终止并转储内存。
  • 进程停止(挂起)直到被 SIGCONT 信号重启。
  • 进程忽略该信号。
#include <signal.h>
typedef void (*sighandler_t)(int);sighandler_t signal(int signum, sighandler_t handler);
// 返回:若成功则为指向前次处理程序的指针,若出错则为 SIG_ERR(不设置 errno)。

signum要处理的信号编号,例如 SIGINT(Ctrl+C)触发。

handler指定对信号的处理方式,有以下几种:

  • SIG_DFL:恢复信号的默认行为。
  • SIG_IGN:忽略信号。
  • 用户定义的信号处理函数,接受作为参数的函数指针。
// 用信号处理程序捕获SIGINT信号的程序void signt_handler(int sig)
{printf("Caught SIGINT\n");exit(0);
}int main()
{// Install the SIGINT handlerif (signal(SIGINT, signt_handler) == SIG_ERR)unix_error("signal error");Pause();return 0;
}

在这里插入图片描述

阻塞和解除阻塞信号

Linux 提供阻塞信号的隐式和显式的机制:

  • 隐式阻塞机制:内核默认阻塞任何当前处理程序正在处理信号类型的待处理的信号。
  • 显式阻塞机制:应用程序可以使用 sigprocmask 函数和它的辅助函数,明确地阻塞和解除阻塞选定的信号。
#include <signal.h>int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);
//返回;如果成功则为 0,若出错则为 -1。int sigismember(const sigset_t *set, int signum);
// 返回:若 signum 是 set 的成员则为 1,如果不是则为 0,若出错则为 -1。
  • sigprocmask: 函数改变当前阻塞的信号集合。如果 oldset 非空,那么 blocked 位向量之前的值保存在 oldset 中。具体的行为依赖于 how 的值:
    • 阻塞信号:通过 SIG_BLOCK 防止某些信号被处理。
    • 解除阻塞:通过 SIG_UNBLOCK 恢复信号处理。
    • 替换信号屏蔽字:通过 SIG_SETMASK 设置新的信号屏蔽字。
  • sigemptyset: 初始化 set 为空集合。
  • sigfillset: 把每个信号都添加到 set 中。
  • sigaddset: 把 signum 添加到 set。
  • sigdelset: 从 set 中删除 signum。
  • sigismember:检查信号是否在信号集中,如果 signum 是 set 的成员,那么 sigismember 返回 1,否则返回 0。

sigprocmask 来临时阻塞接收 SIGINT 信号的方法。

sigset_t mask, prev_mask;Sigemptyset(&mask);
Sigaddset(&mask, SIGINT);/* Block SIGINT and save previous blocked set */
Sigprocmask(SIG_BLOCK, &mask, &prev_mask);// code...// Code region that will not be interrupted by SIGINT/* Restore previous blocked set, unblocking SIGINT */
Sigprocmask(SIG_SETMASK, &prev_mask, NULL);

编写信号处理程序

信号处理是 Linux 系统编程最棘手的一个问题。处理程序有几个属性使得它们很难推理分析:

  • 处理程序与主程序并发运行,共享同样的全局变量,因此可能与主程序和其他处理程序互相干扰。
  • 如何以及何时接收信号的规则常常有违人的直觉。
  • 不同的系统有不同的信号处理语义。

保守的安全的信号处理的原则:G0. 处理程序要尽可能简单。G1. 在处理程序中只调用异步信号安全的函数。G2. 保存和恢复 errno。G3. 阻塞所有的信号,保护对共享全局数据结构的访问。G4. 用 volatile 声明全局变量。G5. 用 sig_atomic_t 声明标志。

信号处理程序中产生输出唯一安全的方法是使用 write 函数。调用 printf 或 sprintf 是不安全的。为了绕开这个限制,我们开发一些安全的函数,称为 SIO(安全的 I/O)包,可以用来在信号处理程序中打印简单的消息。可以从 CS:APP 网站上在线地得到这些代码。

#include "csapp.h"ssize_t sio_putl(long v);
ssize_t sio_puts(char s[]);
// 返回:如果成功则为传送的字节数,如果出错,则为 -1。void sio_error(char s[]);
// 返回:空。ssize_t sio_puts(char s[]) /* Put string */
{return write(STDOUT_FILENO, s, sio_strlen(s));
}ssize_t sio_putl(long v) /* Put long */
{char s[128];sio_ltoa(v, s, 10); /* Based on K&R itoa() */return sio_puts(s);
}void sio_error(char s[]) /* Put error message and exit */
{sio_puts(s);_exit(1);
}
void handler(int sig)
{int olderrno = errno;if (waitpid(-1, NULL, 0) < 0)sio_error("waitpid error");sio_puts("Hanler reaped child\n");sleep(1);errno = olderrno;
}int main()
{int i, n;char buf[MAXBUF];if (signal(SIGCHLD, handler) == SIG_ERR)unix_error("signal error");for (int i = 0; i < 3; i++) {if (Fork() == 0) {printf("Hello from child %d\n", (int)getpid());exit(0);}}// Parents waits for terminal input and then processes itif ((n = read(STDERR_FILENO, buf, sizeof(buf))) < 0)unix_error("read");printf("Parent processing input\n");while(1);return 0;
}

当在 Linux 系统上运行它时,我们得到如下输出:

./signal2
Hello from child 73020
Hello from child 73019
Hanler reaped child
Hello from child 73021
Hanler reaped child

尽管发送了 3 个 SIGCHLD 信号给父进程,但是其中只有两个信号被接收了,因此父进程只是回收了两个子进程。如果挂起父进程,我们看到,实际上子进程 73021 没有被回收,它成了一个僵死进程(在 ps 命令的输出中由字符串 “defunct” 表明)。

ps axj | grep signal1901   73018   73018     901 pts/4      73018 S+    1000   0:00 ./signal173018   73021   73018     901 pts/4      73018 Z+    1000   0:00 [signal1] <defunct>

父进程先接收并捕获第一个信号,在处理该信号时,第二个信号传送过来但因 SIGCHLD 信号被其处理程序阻塞而未被接收,随后第三个 SIGCHLD 信号到达,因已有待处理的 SIGCHLD 信号,它被丢弃。之后处理程序返回,内核让父进程接收待处理的 SIGCHLD 信号,父进程再次执行处理程序处理第二个信号,处理完后无待处理的 SIGCHLD 信号且第三个 SIGCHLD 信号信息已丢失。不能用信号对其他进程中发生的事件计数。

下面是改进版本,能够正确解决信号不会排队等待的情况

void handler2(int sig)
{int olderrno = errno;while (waitpid(-1, NULL, 0) > 0)sio_puts("Hanler reaped child\n");if (errno != ECHILD)sio_error("waitpid error");sleep(1);errno = olderrno;
}
./signal2
Hello from child 75801
Hello from child 75802
Hanler reaped child
Hanler reaped child
Hello from child 75803
Hanler reaped child

显式等待信号

#include "csapp.h"volatile sig_atomic_t pid;void sigchld_handler(int s)
{int olderrno = errno;pid = Waitpid(-1, NULL, 0);errno = olderrno;
}void sigint_handler(int s)
{
}int main(int argc, char **argv)
{sigset_t mask, prev;Signal(SIGCHLD, sigchld_handler);Signal(SIGINT, sigint_handler);Sigemptyset(&mask);Sigaddset(&mask, SIGCHLD);while (1) {Sigprocmask(SIG_BLOCK, &mask, &prev); /* Block SIGCHLD */if (Fork() == 0) /* Child */exit(0);/* Wait for SIGCHLD to be received */pid = 0;while (!pid)sigsuspend(&prev);/* Optionally unblock SIGCHLD */Sigprocmask(SIG_SETMASK, &prev, NULL);/* Do some work after receiving SIGCHLD */printf(".");}exit(0);
}

父进程先注册 SIGCHLD 和 SIGINT 信号处理函数,接着阻塞 SIGCHLD 信号后创建子进程(子进程立即退出),然后通过 sigsuspend 挂起等待 SIGCHLD 信号,收到信号后由 sigchld_handler 处理程序回收子进程,之后恢复原来的信号屏蔽字,最后打印 “.” 表示完成子进程处理并继续后续操作。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com