目录
1.1信号描述
1.2信号相关的概念
1.2.1未决:
1.2.2递达:
1.2.3信号处理方式:
1.2.4阻塞信号集(信号屏蔽字):
1.2.5未决信号集:
1.3信号4要素
1.4信号的产生
1.4.1按键产生
1.4.2系统调用产生
1.4.3软件条件产生
1.4.4硬件异常产生信号
1.4.5命令产生。
1.5kill 函数、命令产生信号
1.6alarm函数产生信号
1.7setitimer函数
1.8信号集操作函数
1.8.1操作自定义信号集函数
1.8.2操作信号屏蔽字 mask 的信号集操作函数
例题:创建函数,打印未决信号集中的每一个二进制位
1.9信号捕捉
1.9.1signal函数
1.9.2sigaction函数
1.9.3 信号捕捉特性:
1.10借助信号捕捉,完成子进程回收
1.10.1SIGCHLD 产生条件
1.10.2内核实现信号捕捉过程
1.1信号描述
- 信号的共性:
1. 简单。
2. 不能携带大量数据。
3. 满足某一特定条件才发送。
- 信号的特质:
- 信号软件层面上的 “中断”。一旦信号产生,无论程序执行到什么位置,必须立即停止,处理信号,处理结束后,再继续执行后续指令。
- 所有的信号,产生、处理都是由内核完成。
- 信号的实现手段导致,信号有很强的延时。对用户而言,依然感觉不到。
1.2信号相关的概念
1.2.1未决
- 产生与递达(处理)之间的状态。 该状态主要受 阻塞(屏蔽)影响。
1.2.2递达
- 内核产生信号后递送并且成功到达进程。递达的信号,会被内核立即处理。
1.2.3信号处理方式
1. 执行默认动作。
2. 忽略(丢弃)。
3. 捕捉(调用用户指定的函数)。
1.2.4阻塞信号集(信号屏蔽字)
- 本质:位图。用来记录信号的屏蔽状态。
- 该信号集中的信号,表示成功被设置屏蔽。再次收到该信号,其处理动作将延后至解除屏蔽。此期间该信号一直处于未决态。
1.2.5未决信号集
- 本质:位图。用来记录信号的处理状态。
- 该信号集中的信号,表示,信号已经产生,但尚未被处理。
1.3信号4要素
- 信号使用之前,“必须”先确定该信号的 4 要素,再使用!
- 四要素内容:
1. 编号 (信号的编号范围1-31)
2. 名称
3. 事件
4. 默认处理动作
- 使用命令 kill -l 查看Linux 系统中,支持的所有信号。1~31 号:常规信号。
- man 7 signal 查看信号 4 要素。
- SIGKILL和SIGSTOP 信号,不允许忽略、捕捉和阻塞,只能执行默认动作。
1.4信号的产生
1.4.1按键产生
- Ctrl + c → 2) SIGINT(终止/中断)
- Ctrl + \ → 3) SIGQUIT(退出)
1.4.2系统调用产生
- alarm() → 14) SIGALRM
- abort()
- raise()
1.4.3软件条件产生
- alarm() → 14) SIGALRM
- setitimer() → 14) SIGALRM
1.4.4硬件异常产生信号
- 段错误:内存访问异常 。→ 11) SIGSEGV
- 浮点数例外:除 0。 → 8) SIGFPE
- 总线错误。内存对齐出错。 → 7) SIGBUS
1.4.5命令产生。
- kill 命令
1.5kill 函数、命令产生信号
kill命令:
#include <signal.h>
int kill(pid_t pid, int sig); // 发送信号给一个指定的进程。
参数:
pid: >0: 发送信号给指定进程。
=0: 发送信号给跟调用kill函数的那个进程,处于同一进程组的进程。
<-1: 取绝对值,当做进组id, 发送信号给该进程组的所有组员。 kill -SIGKILL -9527
-1: 发送信号给,有权限发送的所有进程。
sig:信号编号。
返回值:
成功:0
失败:-1, errno
1.6alarm函数产生信号
- 一个进程有且只有唯一的一个闹钟
unsigned int alarm(unsigned int seconds); // 设置定时,发送 SIGALRM 信号。
参数:
seconds: 定时的秒数。 采用自然计时法。
返回值:
上次定时剩余时间。
不会出错!
alarm(0): 取消闹钟。
- 例题:统计当前使用的计算机, 1s 最多能数多少数。
int main(int argc, char *argv[])
{// 设置 1s 的定时器alarm(1);int i = 0;for (i=0; ;i++){printf("%d\n", i);}return 0;
}
- 使用 time 命令 查看 程序执行消耗的时间。
- 实际时间 = 用户时间 + 内核时间 + 等待时间
- time ./alarm > out ---- 程序优化的瓶颈在 IO
1.7setitimer函数
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
参数:
which:指定计时方式。
ITIMER_REAL: 采用自然计时法。 ———— 发送 SIGLARM
ITIMER_VIRTUAL:采用用户空间计时。 ———— 发送 SIGVTALRM
ITIMER_PROF:采用内核+用户空间计时。 ———— 发送 SIGPROF
new_value: 传入参数。定时时长。
struct itimerval{
struct timeval{
time_t tv_sec; /* seconds */
suseconds_t tv_usec; /* microseconds */
}it_interval; /* 周期定时秒数 */
struct timeval{
time_t tv_sec; /* seconds */
suseconds_t tv_usec; /* microseconds */
}it_value; /* 第一次定时秒数 */
};
old_value: 传出参数。上次定时剩余时间。
返回值:
成功:0
失败:-1, errno
例如:
struct itimerval new_t;
struct itimerval old_t;
new_t.it_interval.tv_sec = 0;
new_t.it_interval.tv_usec = 0;
new_t.it_value.tv_sec = 1;
new_t.it_value.tv_usec = 0; // 等价于 alarm(1)
int ret = setitimer(ITIMER_REAL, &new_t, &old_t);
1.8信号集操作函数
1.8.1操作自定义信号集函数
#include <signal.h>
sigset_t set; 自定义信号集。 // typedef unsigned long sigset_t;
int sigemptyset(sigset_t *set); 清空自定义信号集
int sigfillset(sigset_t *set); 将自定义信号集,全部置1
int sigaddset(sigset_t *set, int signum); 将一个信号添加到自定义集合中。
int sigdelset(sigset_t *set, int signum); 将一个信号从自定义集合中移除。
上述 4 个函数返回值:成功:0, 失败:-1, errno
int sigismember(const sigset_t *set, int signum); 判断一个信号是否在集合中
返回值:在:返回 1 --- 真
不在:返回 0 --- 假
1.8.2操作信号屏蔽字 mask 的信号集操作函数
//设置屏蔽信号、解除屏蔽。都使用 sigprocmask
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
参数:
how:
SIG_BLOCK; 设置阻塞。
SIG_UNBLOCK;解除屏蔽。
SIG_SETMASK;用自定义的set替换mask (不推荐直接用)
set:自定义set。
oldset:保存修改前的 mask状态,以便将来恢复。 也可以传 NULL\
返回值:
成功:0, 失败:-1, errno
int sigpending(sigset_t *set); //查看未决信号集函数 sigpending
参set:传出参数。 未决信息号集。
返回值:成功:0, 失败:-1, errno
例题:创建函数,打印未决信号集中的每一个二进制位
void print_pedset(sigset_t *set)
{int i = 0;for (i = 1; i < 32; i++) //信号的编号范围1-31{if (sigismember(set, i)) {putchar('1');} else {putchar('0');}}printf("\n");
}int main(int argc, char *argv[])
{// 定义自定义信号集, 保存旧mask, 保存未决信号集sigset_t set, oldset, pedset;// 清空自定义信号集.sigemptyset(&set);// 将 2 号信号添加到自定义信号集sigaddset(&set, SIGINT);sigaddset(&set, SIGQUIT);// 借助自定义信号, 设置 pcb中的 信号屏蔽字中的 2 号信号为屏蔽.int ret = sigprocmask(SIG_BLOCK, &set, &oldset);if (ret == -1)sys_err("sigprocmask error");while (1) {// 获取当前的未决信号集ret = sigpending(&pedset);if (ret == -1)sys_err("sigpending error");// 打印未决信号集print_pedset(&pedset);sleep(1);}return 0;
}
1.9信号捕捉
1.9.1signal函数
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
参1:待捕捉的信号编号。
参2:一旦捕捉到该信号,执行的回调函数。
- 捕捉信号测试用例
void sig_catch(int signum)
{if (signum == SIGINT)printf("catch you!! %d\n", signum);else if (signum == SIGQUIT)printf("哈哈, %d, 你被我抓住了\n", signum);else if (signum == SIGKILL)printf("byby %d\n", signum);return ;
}int main(int argc, char *argv[])
{// 注册信号捕捉函数signal(SIGINT, sig_catch);signal(SIGQUIT, sig_catch);signal(SIGKILL, sig_catch);while (1); // 模拟当前进程还有很多代码要执行.return 0;
}
1.9.2sigaction函数
//注册某一个信号的捕捉事件,指定回调函数
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
参1:待捕捉的信号
参2:传入参数,指定新的处理方式。
参3:传出参数,保存旧有的信号处理方式。
返回值:
成功:0, 失败:-1, errno
struct sigaction{
void (*sa_handler)(int); //捕捉函数名,SIG_IGN表忽略;SIG_DFL表示执行默认动作
void (*sa_sigaction)(int, siginfo_t *, void *); //信号传参。
sigset_t sa_mask; //信号捕捉函数,调用期间,所要屏蔽的信号,加入此集合中。
int sa_flags; //通常设置为0,表使用默认属性。 本信号,自动被屏蔽。
void (*sa_restorer)(void); 被废弃!!
};
- 重点掌握:
1. sa_handler:指定捕捉函数名。
2. sa_mask: 在信号捕捉函数调用期间,指定屏蔽哪些信号。【注意】:仅在捕捉函数调用期间有效。
3. sa_flags: 默认值0。 本信号,在信号捕捉函数调用期间,自动被屏蔽。
- 捕捉信号测试用例
void sig_catch(int signum)
{if (signum == SIGINT) {printf("---- catch %d signal\n", signum);sleep(10); // 模拟信号捕捉函数,执行了很长时间.}
}int main(int argc, char *argv[])
{struct sigaction act, oldact;act.sa_handler = sig_catch;sigemptyset(&(act.sa_mask)); // 清空 sa_mask屏蔽字sigaddset(&act.sa_mask, SIGQUIT); // 信号SIGINT捕捉函数执行期间, SIGQUIT信号会被屏蔽act.sa_flags = 0; // 本信号自动屏蔽.// 注册信号捕捉函数.int ret = sigaction(SIGINT, &act, &oldact);if (ret == -1)sys_err("sigaction error");while(1); // 模拟程序还有很多事要做.return 0;
}
1.9.3 信号捕捉特性:
1. 捕捉函数执行期间, 信号屏蔽字,由原来pcb 中的 mask 改换为 sa_mask,捕捉函数执行结束,恢复回mask。
2. 捕捉函数执行期间,本信号,自动被屏蔽(sa_flags = 0)
3. 捕捉函数执行期间,被屏蔽的信号,多次发送,解除屏蔽后只处理一次
1.10借助信号捕捉,完成子进程回收
1.10.1SIGCHLD 产生条件
- 子进程运行状态发生变化,就会给父进程发送 SIGCHLD
回收子进程代码实现:
void catch_child(int signum)
{pid_t wpid;int status;//if ((wpid = wait(NULL)) != -1) // 这样回收产生僵尸子进程while ((wpid = waitpid(-1, &status, 0)) != -1) {if (WIFEXITED(status)) {printf("-------------------catch child pid = %d, ret = %d\n", wpid, WEXITSTATUS(status));}}
}int main(int argc, char *argv[])
{int i;pid_t pid;for (i = 0; i<15;i++) {if ((pid = fork())== 0)break;}if (15 == i) { // 父进程struct sigaction act;act.sa_handler = catch_child;sigemptyset(&act.sa_mask);act.sa_flags = 0;sigaction(SIGCHLD, &act, NULL);printf("I am parent, pid = %d\n", getpid());while (1); // 模拟程序执行很长时间} else { // 子进程printf("I am child, pid = %d\n", getpid());return i;}return 0;
}
```