1.信号的概念
信号和信号量是不一样的
信号量指的是系统中资源的个数
信号是我们linux操作系统中已经定义好的事件
信号并不传递实质性的有效数据,传输的只是一个标志
信号是多种多样的,并且一个信号对应一个事件,这样才能做到收到一个信号后,知道到底是一个什么事件,应该如何处理(但是要保证必须识别这个信号)
信号是一种软件层次的事件处理机制,模拟的是中断的处理机制
中断--硬件处理机制
特性:
1)信号是在软件层(中断是在硬件层次上的)次上对中断机制的一种模拟,是一种异步通信方式。
2)信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利用它来通知用户空间进程发生了哪些系统事件。
3)如果该进程当前并未处于执行态,则该信号就由内核保存起来,直到该进程恢复执行再传递给它;如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被 取消时才被传递给进程。(未决信号集和阻塞信号集)
linux内核中的信号:kill -l
不存在编号为 0 的信号。 其中 1-31 号信号称之为常规信号(也叫普通信号或标准信号) , 34-64 称之为实时信号, 驱动编程与硬件相关。 名字上区别不大。 而前32 个名字各不相同。
使用信号的时候,可以使用编号,也可以用信号的字符表示
常用的信号:
SIGINT: ctrl+c 回收资源,终止进程
SIGKILL:9号信号 立刻杀死一个进程,不回去回收资源
SIGALRM:定时器功能,设置定时时间
2.信号的产生和处理
中断是如何产生的(触发)----触发事件
中断如何处理的---中断服务函数---自定义
信号的产生:
1、当用户按某些终端键时,将产生信号。
例如:
终端上按“Ctrl+c”组合键通常产生中断信号 SIGINT、终端上按"Ctrl+"键通常产
生中断信号 SIGQUIT、终端上按"Ctrl+z"键通常产生中断信号 SIGSTOP。
2、硬件异常将产生信号。
此刻说的异常,指的是硬件发生故障
除数为 0,无效的内存访问等。这些情况通常由硬件检测到,并通知内核,然后内
核产生适当的信号发送给相应的进程。
3、软件异常将产生信号。--重要
当检测到某种软件条件已发生,并将其通知有关进程时,产生信号。
4、调用 kill函数将发送信号。
注意:接收信号进程和发送信号进程的所有者必须相同,或发送信号进程的
所有者必须是超级用户。
5、运行 kill命令将发送信号。
此程序实际上是使用 kill函数来发送信号。也常用此命令终止一个失控的后
台进程。
信号的处理:
1、执行系统默认动作
对大多数信号来说,系统默认动作是用来终止该进程。
2、忽略此信号
接收到此信号后没有任何动作。
3、执行自定义信号处理函数
用用户定义的信号处理函数处理该信号。
使用指令关闭进程:
3.未决信号集和信号阻塞集
未处理的信号在未决信号集中由 “1”表示 0代表已处理
阻塞状态机:1--信号进入阻塞态 0---已经处理过了。
4.相关API函数:
kill 函数
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
功能: 给指定进程发送指定信号(不一定杀死)
参数:pid : 取值有 4 种情况 :
pid > 0: 将信号传送给进程 ID 为 pid 的进程。
pid = 0 : 将信号传送给当前进程所在进程组中的所有进程。
pid = -1 : 将信号传送给系统内所有的进程。
pid < -1 : 将信号传给指定进程组的所有进程。
这个进程组号等于 pid 的绝对值。
sig : 信号的编号, 这里可以填数字编号, 也可以填信号的宏定义, 可以通过命
令 kill - l("l" 为字母)进行相应查看。 不推荐直接使用数字, 应使用宏名,
因为不同操作系统信号编号可能不同, 但名称一致。
返回值:
成功: 0
失败: -1
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
int main(int argv,char *argc[])
{
//新建进程
pid_t pd;
pd=fork();
if(pd<0)
{
perror("fork:");
exit(1);
}
else if(pd==0)//子进程
{
printf("子进程ID号:%d",getpid());
while(1)
{
printf("子进程正在运行\n");
sleep(1);
}
}
else//父进程
{
printf("父进程正在运行,5秒之后关闭子进程\n");
sleep(5);
kill(pd,SIGINT);
}
return 0;
}
raise函数:
#include <signal.h>
int raise(int sig);
功能: 给当前进程发送指定信号(自己给自己发), 等价于 kill(getpid(), sig)
参数:sig: 信号编号
返回值:
成功: 0
失败: 非 0 值
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
int main(int argv,char *argc[])
{
//新建进程
pid_t pd;
pd=fork();
if(pd<0)
{
perror("fork:");
exit(1);
}
else if(pd==0)//子进程
{
int count=0;
printf("子进程ID号:%d",getpid());
while(1)
{
printf("子进程正在运行\n");
sleep(1);
count++;
if(count>=5) raise(SIGINT);
}
}
else//父进程
{
printf("父进程正在运行,5秒之后关闭子进程\n");
sleep(5);
// kill(pd,SIGINT);
}
return 0;
}
abort 函数
#include <stdlib.h>
void abort(void);
功能: 给自己发送异常终止信号 6) SIGABRT, 并产生 core 文件(日志信息),
等价于 kill(getpid(), SIGABRT);
参数: 无
返回值: 无
abort是人为的让程序异常终止,因为有些时候,我们的设备在执行的时候,当不符合
我们的要求的时候,我们需要去查看当前设备的状态(硬件和软件),就需要人为终止
代码的运行。
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
int main(int argv,char *argc[])
{
//新建进程
pid_t pd;
pd=fork();
if(pd<0)
{
perror("fork:");
exit(1);
}
else if(pd==0)//子进程
{
int count=0;
printf("子进程ID号:%d",getpid());
while(1)
{
printf("子进程正在运行\n");
sleep(1);
// count++;
// if(count>=5) abort();
}
}
else//父进程
{
printf("父进程正在运行,5秒之后关闭子进程\n");
sleep(5);
// kill(pd,SIGINT);
abort();
}
return 0;
}
alarm 函数(闹钟)
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
功能:设置定时器(闹钟)。 在指定 seconds 后, 内核会给当前进程发送 14)
SIGALRM 信号。 进程收到该信号, 默认动作终止。 每个进程都有且只有唯一的
一个定时器。取消定时器 alarm(0), 返回旧闹钟余下秒数。
参数:seconds: 指定的时间, 以秒为单位
返回值:返回 0 或剩余的秒数
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
int main(int argv,char *argc[])
{
//新建进程
int val=0;
val=alarm(6);//初始化
printf("1-->当前剩余时间%d s\n",val);
sleep(3);
val=alarm(6);
printf("2-->当前剩余时间%d s\n",val);
sleep(20);
return 0;
}
setitimer 函数
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
下面是对各个参数的解释:
1.which:表示要设置的定时器类型,它可以是以下三个值之一:
- ITIMER_REAL:用于真实时间。当定时器时间到达时,会触发 SIGALRM 信号。
- ITIMER_VIRTUAL:用于进程在用户态运行的时间。当定时器时间到达时,会触发 SIGVTALRM 信号。
- ITIMER_PROF:用于进程在用户态和内核态运行的时间。当定时器时间到达时,会触发 SIGPROF 信号。
2.new_value:指向 struct itimerval 结构的指针,用于设置新的定时器值。struct itimerval 结构包括以下成员:
- it_interval:用于指定定时器的间隔时间,在定时器时间到达后的重复间隔时间。
- it_value:用于指定初始化定时器的延迟时间,在定时器第一次触发前的延迟时间。
3.old_value:指向 struct itimerval 结构的指针,用于获取之前的定时器值(可选)。如果不需要获取旧的定时器值,可以传入 NULL。
struct itimerval 结构的定义如下:
struct itimerval {
struct timeval it_interval; // 定时器的间隔时间
struct timeval it_value; // 初始化定时器的延迟时间
};
struct timeval {
time_t tv_sec; // 秒
suseconds_t tv_usec; // 微秒
};
struct itimerval 结构用于存储定时器的时间值,包括间隔时间和初始化延迟时间。it_interval 成员用于指定定时器的间隔时间,即定时器时间到达后的重复间隔时间;it_value 成员用于指定初始化定时器的延迟时间,即定时器第一次触发的延迟时间。
在调用 setitimer() 函数时,将 which 参数设置为所需的定时器类型,将 new_value 参数传递给一个指向 struct itimerval 结构的指针,该结构包含所需的定时器值,从而设置间隔定时器的时间参数。如果需要获取之前的定时器值,可以将 old_value 参数传递给一个指向 struct itimerval 结构的指针,用于获取旧的定时器值;如果不需要获取旧的定时器值,可以将 old_value 参数设置为 NULL。
sigemptyset函数(信号集)
是一个 POSIX 标准的函数,用于初始化一个空的信号集。它的原型如下:
int sigemptyset(sigset_t *set);
下面是对参数的解释:
set:指向 sigset_t 类型的指针,用于指定要初始化为空的信号集。
sigset_t 是一个用于表示信号集的数据类型,用于存储一组信号。对信号集进行操作时,需要先将其初始化为空集,然后通过其他函数添加或移除特定的信号。
sigemptyset 函数的作用是将指定的信号集 set 初始化为空集,即清空其中的所有信号。
sigfillset函数(信号集)
作用是将所有信号都添加到指定的阻塞信号集中
int sigfillset(sigset_t *set);
- set 一个指向 sigset_t 类型的指针,表示要进行操作的目标信号集。
sigfillset 函数用于将指定的信号集中的所有信号都设置为被阻塞的状态。这意味着在使用该信号集进行信号屏蔽操作时,所有信号都会被阻塞,不会传递给进程的信号处理函数。
sigaction函数
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
struct sigaction {
void (*sa_handler)(int); // 处理信号的函数指针
void (*sa_sigaction)(int, siginfo_t *, void *); // 扩展的信号处理函数(可选)
sigset_t sa_mask; // 在信号处理函数执行期间要被阻塞的信号集
int sa_flags; // 用于指定信号处理的一些标志
void (*sa_restorer)(void); // 已经废弃,无需使用
};
下面是对各个参数的解释:
- signum:表示要设置或获取的信号编号。
- act:指向 struct sigaction 结构的指针,用于设置新的信号处理函数以及信号的行为标志。struct sigaction 结构包括以下成员:
- void (*sa_handler)(int):表示用于处理信号的函数指针,也可以设置为 SIG_IGN(忽略)或 SIG_DFL(默认)。
- void (*sa_sigaction)(int, siginfo_t *, void *):用于处理信号的另一种处理函数,与 sa_handler 二选一。该函数比较复杂,可以接收更多的信号和附加信息。
- sigset_t sa_mask:用于设置在信号处理函数执行期间需要阻塞的其他信号集。
- int sa_flags:用于设置信号的行为标志。
- oldact:指向 struct sigaction 结构的指针,用于获取之前的信号处理函数和信号的行为标志(可选)。如果不需要获取之前的值,可以传入 NULL。
struct sigaction 结构用于定义信号处理函数及信号的行为。在调用 sigaction 函数时,通过 signum 参数指定要设置或获取的信号编号,通过 act 参数传递一个指向 struct sigaction 结构的指针,该结构包含了新的信号处理函数以及信号的行为标志。如果需要获取之前的信号处理函数和行为标志,可以将 oldact 参数传递给一个指向 struct sigaction 结构的指针来接收。
函数返回值为 0 表示成功,-1 表示失败。
使用信号集设置需要执行的工作,再延时N秒之后运行此信号
sigemptyset--清空信号集中的信号
sigaction--设置需要执行的动作
setitimer--设置延时,延时N秒之后运行
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
#include <sys/time.h>
struct itimerval tim;
struct sigaction my_act;
//执行动作
void my_Function(int val)//类似中断服务函数
{
printf("已经进入动作执行函数中\n");
}
int main(int argv,char *argc[])
{
/*
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
int sigemptyset(sigset_t *set);
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
*/
//清空信号集
//设置执行动作
my_act.sa_handler=my_Function;
// my_act.sa_mask=NULL;//设置阻塞的信号
my_act.sa_flags=NULL;
sigemptyset(&my_act.sa_mask);
//当闹钟信号触发之后,执行my_act
sigaction(SIGALRM,&my_act,NULL);
//间隔时间
tim.it_interval.tv_sec=1;
tim.it_interval.tv_usec=0;
//初始化的延时时间
tim.it_value.tv_sec=5;
tim.it_value.tv_usec=0;
setitimer(ITIMER_REAL,&tim,NULL);
sleep(20);//时间一定要比定时器延时时间长
return 0;
}
5.给信号 注册 自定义函数
#include <signal.h>
typedef void(*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
功能:
注册信号处理函数(不可用于 SIGKILL、SIGSTOP 信号),即确定收到信号后处理函数的入口地
址。此函数不会阻塞。
参数:
signum:信号的编号,这里可以填数字编号,也可以填信号的宏定义,可以通过命令 kill - l("l"
为字母)进行相应查看。
handler : 取值有 3 种情况:
SIG_IGN:忽略该信号
SIG_DFL:执行系统默认动作
信号处理函数名:自定义信号处理函数,如:func
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
void My_func(int val)
{
printf("My_func is running.....\n");
}
int main(int argv,char *argc[])
{
int count=0;
//新建进程
while(1)
{
signal(SIGINT,My_func);
sleep(1);
printf("count-->%d\n",count++);
}
return 0;
}
信号集
内核中的位置:
/usr/include/i386-linux-gnu/bits/sigset.h
作用:
#include <signal.h>
int sigemptyset(sigset_t *set); //将 set 集合置空--初始化
int sigfillset(sigset_t *set); //将所有信号加入 set 集合
int sigaddset(sigset_t *set, int signo); //将 signo 信号加入到 set 集合
int sigdelset(sigset_t *set, int signo); //从 set 集合中移除 signo 信号
int sigismember(const sigset_t *set, int signo); //判断信号是否存在于集合中
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
int main(int argv,char *argc[])
{
sigset_t sig_gather;
int ret;
//创建一个空的信号集合
//sigset_t *set
sigemptyset(&sig_gather);
//添加一个信号到信号集合
// int sigaddset(sigset_t *set, int signum);
ret=sigaddset(&sig_gather,SIGINT);
if(ret==0)
{
printf("SIGINT 已经存在集合中\n");
}
sleep(1);
//int sigdelset(sigset_t *set, int signum);
sigdelset(&sig_gather,SIGINT);
ret=sigismember(&sig_gather,SIGINT);
if(ret==0)
{
printf("当前信号SIGINT已经删除\n");
}
return 0;
}
信号阻塞集
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
功能:
检查或修改信号阻塞集,根据 how 指定的方法对进程的阻塞集合进行修改,新的信 号阻塞集由set 指定,而原先的信号阻塞集合由 oldset 保存。
参数:
how : 信号阻塞集合的修改方法,有 3 种情况:
- SIG_BLOCK:向信号阻塞集合中添加 set 信号集,新的信号掩码是 set 和旧信号掩码的并集。 相当于
mask = mask | set
- SIG_UNBLOCK:从信号阻塞集合中删除 set 信号集,从当前信号掩码中去除 set 中的信号。相当于
mask = mask & ~ set
- SIG_SETMASK:将信号阻塞集合设为 set 信号集,相当于原来信号阻塞集的内 容清空,然后按照 set 中的信号重新设置信号阻塞集。相当于 mask =set。
set : 要操作的信号集地址。
- 若 set 为 NULL,则不改变信号阻塞集合,函数只把当前信号阻塞集合保存到 oldset 中。
oldset : 保存原先信号阻塞集地址
返回值:
- 成功:0,
- 失败:-1,失败时错误代码只可能是 EINVAL,表示参how不合法
思考:
如何往阻塞信号集中添加信号,以及如何从信号阻塞集中删除信号