主页 > 互联网  > 

Linux高并发服务器开发第十八天(信号及相关概念信号捕捉)

Linux高并发服务器开发第十八天(信号及相关概念信号捕捉)

目录

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; } ``` 1.10.2内核实现信号捕捉过程

标签:

Linux高并发服务器开发第十八天(信号及相关概念信号捕捉)由讯客互联互联网栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“Linux高并发服务器开发第十八天(信号及相关概念信号捕捉)