目录
第1关:信号处理函数
任务描述
试想这样的场景:
你在某东上面预定了一瓶牛奶,希望快递员在明天早上七点之前将其放在家楼下的牛奶箱里面。
整个事件处理过程如下:
你告知快递公司需要预定一瓶牛奶,快递公司会通知快递员;
快递员拿到指定的牛奶;
快递员将牛奶放入家楼下的牛奶箱。
以信号处理代替整个事件处理过程, 对于上述过程可以理解为注册信号处理函数 A ,B->触发信号处理函数 A->触发信号处理函数 B 。
本关任务:
分别为信号 SIGUSR1 、 SIGUSR2 注册信号处理函数;
完成两个信号处理函数。
相关知识
在 Linux 中,每一个信号都有一个名字,这些名字以 SIG 开头。例如, SIGABRT 是夭折信号,当进程调用 abort 函数时会产生这种信号。SIGALRM 是闹钟信号,由 alarm 函数设置的定时器超时后将产生此信号。
信号产生
信号产生是指触发信号的事件的发生。
例如,通过键盘输入组合键
CTRL+C
系统会收到 SIGINT。 通过killall -sigid processname
以给指定进程发送信号。比如
killall -SIGKILL testsignal
给 testsignal 发送 SIGKILL 信号,即杀死进程的信号。SIGUSR1 和 SIGUSR2 是用户自定义信号,通过上述的方式也可以将信号 SIGUSR1 和 SIGUSR2 传递给进程。
信号的处理动作
信号是异步事件的经典实例,产生信号的事件对进程而言是随机出现的。进程不能简单地测试一个变量来判断是否发生了一个信号,而是必须告诉内核“在此信号发生时,请执行以下操作”。
在某个信号出现时,可以告诉内核按照以下三种方式之一进行处理,我们称之为信号的处理或与信号相关的动作:
忽略此信号。大多数信号可以使用这种方式进行处理,但是 SIGKILL 和 SIGSTOP 除外。
捕获信号。为了做到这一点,要通知内核在某种信号发生时,调用一个用户函数。在用户函数中,可执行用户希望对这种事件进行的处理。
执行系统默认动作。对于大多数信号来说,系统默认动作是终止该进程。
信号处理过程
注册信号处理函数
信号的处理是由内核来代理的,首先程序通过 signal 为每个信号注册处理函数,而内核中有一张信号向量表,对应信号处理机制。这样,信号在进程中注销完毕之后,会调用相应的处理函数进行处理。
信号的检测与响应时机
在系统调用或中断返回用户态的前夕,内核会检查未决信号集,进行相应的信号处理。
处理过程
程序运行在用户态时;
进程由于系统调用或中断进入内核;
转向用户态执行信号处理函数;
信号处理函数完毕后进入内核;
返回用户态继续执行程序。
signal处理接口
signal 函数是最简单的信号处理接口,也是使用比较广泛的一个接口。
#include <signal.h> typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler);
参数的含义:
signum
:信号名,一般不允许是 SIGKILL 或 SIGSTOP ;
handler
:常量 SIG_IGN、常量 SIG_DFL或者当收到此信号后要调用的函数的地址。如果是 SIG_IGN,则忽略此信号。如果是 SIG_DFL,则使用系统默认动作。返回值:返回 sighandler_t句柄或者 SIG_ERR。
应用示例:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <signal.h> int catch(int sig); int main() { signal(SIGINT,catch); printf("hello! "); sleep(10); printf("hello! "); } int catch(int sig) { printf("catch signal! "); return 1; }
运行步骤如下: 运行程序: 在 10s 内按键
CTRL+C
。运行结果如下:
hello!
^Ccatch signal!
hello!
编程要求
在主函数的最开始会初始化一个全部变量 g_i4event 为 0。
本关的编程任务是补全右侧代码片段中两段
Begin
至End
中间的代码,具体要求如下:在 do _signal中分别为信号 SIGUSR1 、 SIGUSR2 注册信号处理函数 funcA 和 funcB ,而后将 g_i4event 置为 1;
完成两个信号处理函数,其中 funcA 中将 g_i4event 置为 2, funcB 中将 g_i4event 为 3。
答案:
根据编程要求编写即可
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <signal.h> int g_i4event; typedef void (*sighandler_t)(int); /********Begin********/ /*实现funcA和funcB*/ void funcA(int signo) { if(signo==SIGUSR1) { g_i4event=2; } } void funcB(int signo) { if(signo==SIGUSR2) { g_i4event=3; } } /*********End*********/ int do_signal(void) { /********Begin********/ signal(SIGUSR1,funcA); signal(SIGUSR2,funcB); g_i4event=1; return 0; /*********End*********/ }
第2关:signal高级处理之sigaction
任务描述
试想这样的场景:
你在某东上面预定了一台电视机,并希望他们先送货并安装。
整个事件处理过程如下:
你告知快递公司需要预定一台电视机,快递公司会通知货仓和安装师傅;
货仓将货送到你家里;
安装师傅第二天到你家里安装电视。
以信号处理代替整个事件处理过程, 对于上述过程可以理解为注册信号处理函数 A ,B->触发信号处理函数 A->触发信号处理函数 B 。
本关任务:
分别为信号 SIGUSR1 、 SIGUSR2 注册信号处理函数;
完成两个信号处理函数。
相关知识
在 Linux 信号处理函数中,signal函数是最基本的,由于系统版本的不同,signal 由ISO C定义。因为 ISO C 不涉及到多进程、进程组以及终端 I /O等,所以它对信号的定义比较模糊。
从Unix system V派生的实现支持 signal 函数,但该函数提供旧的不可靠信号语义。4.4BSD 也提供了 signal 函数,并且提供了新的信号语义。
因此,signal 的语义与实现有关,为了保险起见,最好使用别的函数来代替 signal 函数。这个函数是 sigaction,也是本实训讲解的重点。
sigaction函数
sigaction 函数取代了 UNIX 早期版本使用的 signal 函数。
#include <signal.h> int sigaction(int signo, const struct sigaction *act,struct sigaction *oldact));
参数的含义:
signo :信号的值,可以为除 SIGKILL 及 SIGSTOP 外的任何一个特定有效的信号;
act :指向结构 sigaction 的一个实例的指针,在结构 sigaction 的实例中,指定了对特定信号的处理,但可以为空,进程会以缺省方式对信号处理;
oldact :对象指针,指向的对象用来保存返回的原来对相应信号的处理,可指定 oldact 为 NULL 。
注:如果把第二、第三个参数都设为
NULL
,那么该函数可用于检查信号的有效性。返回值: 0 表示成功,-1 表示有错误发生。
功能: sigaction 函数用于改变进程接收到特定信号后的行为。
sigaction结构体详解
sigaction 函数最重要的部分就是sigaction结构体,这个被应用于参数 act 和 oldact 中,其定义如下:
struct sigaction { union { __sighandler_t _sa_handler; void (*_sa_sigaction)(int,struct siginfo *, void *); }_u sigset_t sa_mask; unsigned long sa_flags; }
联合数据结构中的两个元素
_sa_handler
以及 _sa_sigaction 指定信号关联函数,即用户指定的信号处理函数。除了可以是用户自定义的处理函数外,还可以为SIG_DFL(采用缺省的处理方式),也可以为 SIG_IGN (忽略信号);由 _sa_sigaction 指定的信号处理函数带有三个参数,是为实时信号而设的,它指定一个三参数信号处理函数。第一个参数为信号值,第三个参数没有使用,第二个参数是指向 siginfo_t 结构的指针,结构中包含信号携带的数据值,参数所指向的结构如下:
siginfo_t { int si_signo; /* 信号值,对所有信号有意义*/ int si_errno; /* errno值,对所有信号有意义*/ int si_code; /* 信号产生的原因,对所有信号有意义*/ union { /* 联合数据结构,不同成员适应不同信号 */ //确保分配足够大的存储空间 int _pad[SI_PAD_SIZE]; //对SIGKILL有意义的结构 struct { ... }... ... ... //对SIGILL, SIGFPE, SIGSEGV, SIGBUS有意义的结构 struct { ... }... ... ... } }
sa_mask : 信号集,指定在信号处理程序执行过程中,哪些信号应当被阻塞。缺省情况下当前信号本身被阻塞,防止信号的嵌套发送,除非指定 SA_NODEFER 或者 SA_NOMASK 标志位。
注:请注意 sa_mask 指定的信号阻塞的前提条件:在
sigaction()
安装信号的处理函数执行过程中,由 sa_mask 指定的信号才会被阻塞。在使用 sigaction 之前,请务必清空或者设置自己所需要的屏蔽字段。sa_flags 中包含了许多标志位,包括 SA_NODEFER 及 SA_NOMASK 标志位。另一个比较重要的标志位是 SA_SIGINFO ,当设定了该标志位时,表示信号附带的参数可以被传递到信号处理函数中,因此,应该为 sigaction 结构中的 sa_sigaction 指定处理函数,而不应该为 sa_handler 指定信号处理函数,否则设置该标志变得毫无意义。即使为 sa_sigaction 指定了信号处理函数,如果不设置 SA_SIGINFO ,信号处理函数同样不能得到信号传递过来的数据,在信号处理函数中对这些信息的访问都将导致错误。 一般的做法是,如果采用 _sa_handler 作为处理函数,则将 sa_flags 设定为0;如果采用 _sa_sigaction 作为处理函数,则将 sa_flags 设定为 SA_SIGINFO。
应用示例:
#include <signal.h> int catch(int sig); int main() { struct sigaction act; struct sigaction oldact; /*注册信号处理函数*/ act.sa_handler = catch; sigemptyset(&act.sa_mask);//清空sa_mask,这点尤为重要 act.sa_flags = 0; sigaction(SIGINT, act ,oldact); printf("hello! "); sleep(10); printf("hello! "); } int catch(int sig) { printf("catch signal! "); return 1; }
运行步骤如下:
运行程序;
在 10s 内按键
CTRL +C
。运行结果如下:
hello!
^Ccatch signal!
hello!
编程要求
在主函数的最开始会初始化一个全部变量 g_i4event 为 0。
本关的编程任务是补全右侧代码片段中两段
Begin
至End
中间的代码,具体要求如下:在 do _sigaction中分别为信号 SIGUSR1 、 SIGUSR2 注册信号处理函数 funcA 和 funcB ,而后将 g_i4event 置为 1;
完成两个信号处理函数,其中 funcA 中将 g_i4event 置为 2, funcB 中将 g_i4event 置为 3。
注:采用
_sa_sigaction
和SA_SIGINFO
来实现。
答案:
根据编程要求编写即可
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <signal.h> int g_i4event; /********Begin********/ /*实现funcA和funcB*/ void funcA(int signo, siginfo_t *info, void *context) { if(signo==SIGUSR1) { g_i4event=2; } } void funcB(int signo, siginfo_t *info, void *context) { if(signo==SIGUSR2) { g_i4event=3; } } /*********End*********/ int do_sigaction(void) { /********Begin********/ struct sigaction act1, act2; act1.sa_sigaction=funcA; act1.sa_flags=SA_SIGINFO; act2.sa_sigaction=funcB; act2.sa_flags=SA_SIGINFO; sigaction(SIGUSR1,&act1,NULL); sigaction(SIGUSR2,&act2,NULL); g_i4event=1; return 0; /*********End*********/ }
第3关:Linux定时器
任务描述
试想这样的场景:
你的 darling 希望你下午四点到学校门口等她一起下课,但是你又不希望太早过去傻等,于是你定了一个三点半的闹钟,你开始学习,然后等闹钟一响你立马出门。
整个事件处理过程如下:
你设定三点半的闹钟;
然后你开始学习工作;
闹钟一响你立马出门。
最后你在三点五十五达到门口。 NICE !!!
以信号处理代替整个事件处理过程, 对于上述过程可以理解为设定定时器->注册信号处理函数->定时器超时触发信号处理函数。
本关任务:
设定 5s 的定时器;
为信号 SIGALRM 注册信号处理函数;
实现信号处理函数。
相关知识
在 Linux 系统中,信号处理有很多应用,除了响应信号的实时操作以外,定时器也是一种常见的信号应用。
alarm函数
使用 alarm 函数可以设置一个定时器,在将来的某个时刻,这个定时器就会超时。当超时时,会产生 SIGALRM 信号。如果忽略或者不捕捉此信号,则其默认动作时终止调用该 alarm 函数的进程。
任务描述
试想这样的场景:
你的 darling 希望你下午四点到学校门口等她一起下课,但是你又不希望太早过去傻等,于是你定了一个三点半的闹钟,你开始学习,然后等闹钟一响你立马出门。
整个事件处理过程如下:
你设定三点半的闹钟;
然后你开始学习工作;
闹钟一响你立马出门。
最后你在三点五十五达到门口。 NICE !!!
以信号处理代替整个事件处理过程, 对于上述过程可以理解为设定定时器->注册信号处理函数->定时器超时触发信号处理函数。
本关任务:
设定 5s 的定时器;
为信号 SIGALRM 注册信号处理函数;
实现信号处理函数。
相关知识
在 Linux 系统中,信号处理有很多应用,除了响应信号的实时操作以外,定时器也是一种常见的信号应用。
alarm函数
使用 alarm 函数可以设置一个定时器,在将来的某个时刻,这个定时器就会超时。当超时时,会产生 SIGALRM 信号。如果忽略或者不捕捉此信号,则其默认动作时终止调用该 alarm 函数的进程。
#include <unistd.h> unsigned int alarm(unsigned int seconds);
参数: seconds ,是产生信号需要经过的时钟秒数,也就是定时器的时间。
alarm 安排内核调用进程——在指定的 seconds 秒后发出一个 SIGALRM 的信号。如果指定的参数 seconds 为 0 ,则不再发送 SIGALRM 信号。后一次设定将取消前一次的设定。该调用返回值为上次定时调用到发送之间剩余的时间,或者因为没有前一次定时调用而返回 0 。
注意,在使用时,alarm 只设定为发送一次信号,如果要多次发送,需要多次使用 alarm 调用。
应用示例:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <signal.h> typedef void (*sighandler_t)(int); int catch(int sig) { printf("catch alarm signal! "); return 0; } int main() { alarm(3); sighandler_t res = signal(SIGALRM, catch); printf("wait for alarm signal! "); sleep (5); }
运行结果如下:
wait for alarm signal!
catch alarm signal!
编程要求
在主函数的最开始会初始化一个全部变量 g_i4event 为 0 。
本关的编程任务是补全右侧代码片段中两段
Begin
至End
中间的代码,具体要求如下:在 do _alarm中首先启动 5s 定时器,将 g_i4event 置为 1;
睡眠一秒,然后为信号 SIGALRM 注册信号处理函数 funcalarm ,将 g_i4event 置为 2;
在信号处理函数,将 g_i4event 置为 3。
答案:
根据编程要求编写即可
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <signal.h> int g_i4event; typedef void (*sighandler_t)(int); /********Begin********/ /*实现funcA和funcB*/ void funcalarm(int signo) { if(signo==SIGALRM) { g_i4event=3; } } /*********End*********/ int do_alarm(void) { /********Begin********/ g_i4event=1;//将g_i4event置为1 alarm(5);//在do _alarm中启动5s定时器 sleep(1);//睡眠一秒 signal(SIGALRM,funcalarm);//为信号SIGALRM注册信号处理函数funcalarm g_i4event = 2;//将g_i4event置为2 return 0; /*********End*********/ }