Linux 信号

目录

第1关:信号处理函数

任务描述

相关知识

信号产生

信号的处理动作

信号处理过程

注册信号处理函数

信号的检测与响应时机

处理过程

signal处理接口

编程要求

答案: 

第2关:signal高级处理之sigaction

任务描述

相关知识

sigaction函数

sigaction结构体详解

编程要求

答案: 

第3关:Linux定时器

任务描述

相关知识

alarm函数

任务描述

相关知识

alarm函数

编程要求

答案: 


第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。

本关的编程任务是补全右侧代码片段中两段BeginEnd中间的代码,具体要求如下:

在 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。

本关的编程任务是补全右侧代码片段中两段BeginEnd中间的代码,具体要求如下:

在 do _sigaction中分别为信号 SIGUSR1 、 SIGUSR2 注册信号处理函数 funcA 和 funcB ,而后将 g_i4event 置为 1;

完成两个信号处理函数,其中 funcA 中将 g_i4event 置为 2, funcB 中将 g_i4event 置为 3。

注:采用_sa_sigactionSA_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 。

本关的编程任务是补全右侧代码片段中两段BeginEnd中间的代码,具体要求如下:

在 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*********/
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

星与星熙.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值