信号
信号描述
信号的共性:
- 简单
- 不能携带大量数据
- 满足某一特定条件才能发送
信号的本质
- 信号软件层面上的”中断”,一旦信号产生,无论程序执行到哪里,都立即停止,处理信号,处理完成,在继续后续指令。
- 所有的信号,产生、处理都是由内核完成。
- 信号的实现手段导致,信号有很强的延时。对用户而言,依然感觉不到。
信号相关的概念
-
未决
- 产生与递达(处理)之间的状态。主要受阻塞(屏蔽)影响
-
递达
- 内核产生信号后提送并且送达进程。递达的信号会被内核立即处理。
-
信号处理方式
- 执行默认动作
- 忽略(丢弃)。
- 捕捉(调用用户指定函数)
-
阻塞信号集(信号屏蔽字):
本质:位图,用来记录信号的屏蔽状态- 该信号集中的信号,表示成功被设置屏蔽,再次收到该信号,其处理动作将延迟后解除屏蔽。此期间该信号一直处于未决态。
-
未决信号集
- 本质:位图,用来记录信号的处理状态。
- 该信号集中信号,表示信号已经产生,但尚未被处理。
信号4要素
- 信号使用前,必须先确定四要素,在使用。
- 四要素内容:
- 编号
- 名称
- 事件
- 默认处理动作
- 使用命令kill -l 查看Linux系统中支持的所有信号。
- sigkill 和 sigstop信号不允许忽略和捕捉,只能执行默认动作。甚至不能设置为阻塞。
信号的产生
- 按键的产生
- Ctrl +c ->2 sigint(终止/中断)
- Ctrl + \ -> 3sigquit(退出))
- 系统调用产生
- alarm()-> sigalarm
- abort()
- raise()
- 软件条件产生
- alarm()->sigalarm
- setitimer()->sigalarm
- 硬件异常产生信号
- 段错误:内存访问异常-> sigsegv
- 浮点数除外:除0 -> sigfpe
- 总线错误。内存对齐出错。->sigbus.
- 命令产生
- kill命令
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
案例
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<fcntl.h>
#include<errno.h>
#include<dirent.h>
#include <sys/mman.h>
#include<signal.h>
void sys_err(const char*str)
{
perror(str);
exit(1);
}
int main(int argc,char*argv[])
{
pid_t pid = fork();
if(pid == 0)
{
sleep(2);
kill(getppid(),SIGKILL);
}
else if(pid > 0)
{
while(1)
{
printf("I'm a parent,pid = %d\n",getpid());
usleep(10000);
}
}
return 0;
}
alarm函数产生信号
- 一个进程有且仅有一个唯一的闹钟
unsigned int alarm(unsigned int seconds); // 设置定时,发送SIGALARM信号
seconds:定时的秒数.
返回值:
上次定时剩余实现
不会出错
alarm(0):取消闹钟
- 统计当前使用的计算机,1s最多能数多少数。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<fcntl.h>
#include<errno.h>
#include<dirent.h>
#include <sys/mman.h>
#include<signal.h>
void sys_err(const char*str)
{
perror(str);
exit(1);
}
int main(int argc,char*argv[])
{
// 设置1s的定时器
alarm(1);
int i = 0;
for(i = 0;;i++)
{
printf("i = %d\n",i);
}
return 0;
}
- 使用time命令查看程序执行消耗的时间
- 实际时间 = 用户时间 + 内核时间 + 等待时间
- time ./alarm > out ----程序优化的瓶经在IO
setitimer函数
#include <sys/time.h>
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
功能:
设置定时器(闹钟)。 可代替alarm函数。精度微秒us,可以实现周期定时。
参数:
which:指定定时方式
a) 自然定时:ITIMER_REAL → 14)SIGALRM计算自然时间
b) 虚拟空间计时(用户空间):ITIMER_VIRTUAL → 26)SIGVTALRM 只计算进程占用cpu的时间
c) 运行时计时(用户 + 内核):ITIMER_PROF → 27)SIGPROF计算占用cpu及执行系统调用的时间
new_value:struct itimerval, 负责设定timeout时间
struct itimerval {
struct timerval it_interval; // 闹钟触发周期
struct timerval it_value; // 闹钟触发时间
};
struct timeval {
long tv_sec; // 秒
long tv_usec; // 微秒
}
itimerval.it_value: 设定第一次执行function所延迟的秒数
itimerval.it_interval: 设定以后每几秒执行function
old_value: 存放旧的timeout值,一般指定为NULL
返回值:
成功:0
失败:-1
void myfunc(int sig)
{
printf("hello\n");
}
int main()
{
struct itimerval new_value;
//定时周期
new_value.it_interval.tv_sec = 1;
new_value.it_interval.tv_usec = 0;
//第一次触发的时间
new_value.it_value.tv_sec = 2;
new_value.it_value.tv_usec = 0;
signal(SIGALRM, myfunc); //信号处理
setitimer(ITIMER_REAL, &new_value, NULL); //定时器设置
while (1);
return 0;
}
信号机操作函数
操作自定义的操作函数
#include <signal.h>
sigset_t set ; 自定义信号集 //typedef unsigned long sigset_t
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信号0 -1
int sigismember(const sigset_t *set, int signo); //判断信号是否存在1 0
操作信号屏蔽字mask的信号集操作函数
- 设置屏蔽信号、接触屏蔽。
#include <signal.h>
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 不合法。
- 查看 未决集合函数sigpending
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<fcntl.h>
#include<errno.h>
#include<dirent.h>
#include<sys/time.h>
#include<signal.h>
void sys_err(const char*str)
{
perror(str);
exit(1);
}
// 创建函数,打印未决的每一个二进制位
void print_pedset(sigset_t*set)
{
int i = 0;
for (i=1;i<32;i++)
{
if(sigismember(set,i))
{
putchar('1');
}
else
{
putchar('0');
}
}
printf("\n");
}
int main(int argc,char*argv[])
{
//自定义信号集
sigset_t set, oldset,pedset;
// 清空自定义信号集合
sigemptyset(&set);
//将2号信号添加到自定义信号集
sigaddset(&set, SIGINT);
// 借助自定义信号,设置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;
}
信号捕捉
signal函数
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
参1:待捕捉的信号编号
参2; 一旦捕捉到该信号,执行的回调函数
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<fcntl.h>
#include<errno.h>
#include<dirent.h>
#include<sys/time.h>
#include<signal.h>
void sys_err(const char*str)
{
perror(str);
exit(1);
}
void sig_catch(int signum)
{
if(signum == SIGINT)
printf("catch you!! %d\n",signum);
else if(signum == SIGQUIT)
{
printf("哈哈,%d,你被我抓住了\n",signum);
}
return ;
}
int main(int argc,char*argv[])
{
// 注册信号捕捉函数
signal(SIGINT,sig_catch);
signal(SIGQUIT,sig_catch);
while(1); //模拟当前进程还有很多代码药执行
return 0;
}
sigaction函数
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
功能:
检查或修改指定信号的设置(或同时执行这两种操作)。
参数:
signum:要操作的信号。
act: 要设置的对信号的新处理方式(传入参数)。
oldact:原来对信号的处理方式(传出参数)。
如果 act 指针非空,则要改变指定信号的处理方式(设置),如果 oldact 指针非空,则系统将此前指定信号的处理方式存入 oldact。
返回值:
成功:0
失败:-1
struct sigaction结构体:
struct sigaction {
void(*sa_handler)(int); //旧的信号处理函数指针
void(*sa_sigaction)(int, siginfo_t *, void *); //新的信号处理函数指针
sigset_t sa_mask; //信号阻塞集
int sa_flags; //信号处理的方式
void(*sa_restorer)(void); //已弃用
};
- sa_handler、sa_sigaction:信号处理函数指针,和 signal() 里的函数指针用法一样,应根据情况给sa_sigaction、sa_handler 两者之一赋值,其取值如下:
a) SIG_IGN:忽略该信号
b) SIG_DFL:执行系统默认动作
c) 处理函数名:自定义信号处理函数
- sa_mask:信号阻塞集,在信号处理函数执行过程中,临时屏蔽指定的信号。
- sa_flags:用于指定信号处理的行为,通常设置为0,表使用默认属性。它可以是一下值的“按位或”组合:
Ø SA_RESTART:使被信号打断的系统调用自动重新发起(已经废弃)
Ø SA_NOCLDSTOP:使父进程在它的子进程暂停或继续运行时不会收到 SIGCHLD 信号。
Ø SA_NOCLDWAIT:使父进程在它的子进程退出时不会收到 SIGCHLD 信号,这时子进程如果退出也不会成为僵尸进程。
Ø SA_NODEFER:使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号。
Ø SA_RESETHAND:信号处理之后重新设置为默认的处理方式。
Ø SA_SIGINFO:使用 sa_sigaction 成员而不是 sa_handler 作为信号处理函数。
- 捕捉信号测试用例
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<fcntl.h>
#include<errno.h>
#include<dirent.h>
#include<sys/time.h>
#include<signal.h>
void sys_err(const char* str)
{
perror(str);
exit(1);
}
void sig_catch(int signum)
{
if(signum == SIGINT)
printf("catch you!! %d\n", signum);
else if(signum == SIGQUIT)
{
printf("哈哈, %d, 你被我抓住了\n", signum);
}
return;
}
int main(int argc, char* argv[])
{
struct sigaction act, oldact;
act.sa_handler = sig_catch;
sigemptyset(&(act.sa_mask));
sigaddset(&act.sa_mask,SIGQUIT);
act.sa_flags = 0; // 本信号自动屏蔽
// 注册信号捕捉函数
int ret = sigaction(SIGINT, &act, &oldact);
if(ret == -1)
{
sys_err("sigaction error");
}
while(1) {
// Add some code here if needed
}
return 0;
}
- 捕捉函数执行期间,信号屏蔽字,由原来的pcb中的mask该换为sa_mask,捕捉函数执行结束,恢复回mask。
- 期间,本信号自动被屏蔽(sa_flags = 0)
- 期间,被屏蔽的信号,多次发送,解除后只处理一次。
借助信号捕捉,完成子进程回收
sigchld 产生条件
- 子进程运行状态发送变化,就会给父进程发送SIGCHLD
回收子进程代码实现
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<fcntl.h>
#include<errno.h>
#include<dirent.h>
#include<sys/time.h>
#include<signal.h>
void sys_err(const char* str)
{
perror(str);
exit(1);
}
void catch_child(int signum)
{
pid_t wpid;
int status;
// if((wpid = wait(NULL))!=-1)//会产生僵尸进程
// {
// printf("catch child pid = %d\n",wpid);
// }
while((wpid = waitpid(-1,&status,0))!=-1)
{
if(WIFEXITED(status))
{
printf("catch child wpid = %d\n",wpid);
}
}
}
int main(int argc,char*argv[])
{
int i;
pid_t pid ;
for(i = 0;i<5;i++)
{
pid=fork();
if(pid==0)
{
break;
}
}
if( 5== i)
{
struct sigaction act;
act.sa_handler = catch_child;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGCHLD,&act,NULL);
printf("I'm a parent,pid = %d\n",getpid());
while(1);
}
else
{
printf("I'm a child,pid = %d\n",getpid());
}
return 0;
}