1. 信号相关的概念
1.1 基本概念
- 特点:简单, 携带的信息量很少
- linux中的信号特点:
(1)Linux操作系统的消息机制
(2)信号的优先级非常高
(3)可以实现进程通信,但由于优先级非常高,所以不建议; - 产生信号的途径
(1)通过shell命令:如通过kill
命令终止某一个进程。
(2)对终端的操作:如ctrl+c让键盘输入产生一个硬件中断;
(3)通过函数调用:如调用sleep()
函数;
(4)硬件错误:如发生了段错误,访问了非法内存,程序退出;
1.2 操作
- 查看信号列表:
kill -l
- 查看信号的详细信息:
man 7 signal
- Linux中的信号行为:即收到信号之后的处理动作;通过
man 7 signal
命令查询
(1)Term:终止进程
(2)Ign:当前进程忽略掉这个信号
(3)Core:终止进程并生成一个core文件用于gdb调试;默认情况下操作系统不允许生成core文件:ulimit -a
-->core file size,若要放开系统限制:ulimit -c 数量
(4)Stop:暂停当前进程;
(5)Cont:继续执行当前被暂停的进程; - Core文件的使用方法
(1)系统默认不允许生成Core文件,需要通过ulimit -c 数量
设置Core的数量;
(2)gcc -g
来gdb编译文件
(3)运行文件发生段错误后,通过gdb 文件
调试文件
(4)进入gdb调试后,core -file core
命令会自动指出错误地址; - 进程对信号可进行的操作
(1)执行默认的处理操作
(2)捕捉到后忽略
(3)捕捉到后执行自定义的处理操作
(4)注意:9 -SIGKILL
和19 -SIGSTOP
不允许被捕捉、阻塞和忽略;
2. 信号相关的函数
2.1 kill/raise/abort
函数
kill()
函数
给pid进程发送sig信号,返回成功或失败
int kill(pid_t pid, int sig);
pid:
>0:某个接收信号的进程;
=0:当前进程组;
=-1:每个有权限接收当前信号的进程
<-1:某个接收信号的进程组(-pid);
杀死父进程
kill(getppid(), 9);
raise()
函数
给当前进程发送sig信号,返回成功或失败
int raise(int sig);
成功返回-1,失败返回0;
通过kill()函数实现
kill(getpid(), sig);
abort()
函数
给当前进程发送SIGABRT信号,默认结束当前进程
int abort(void)
通过kill()函数实现
kill(getpid(), SIGABRT);
2.2 alarm()
函数
alarm()
函数
从seconds开始倒计时,返回剩余时间,倒计时结束发送SIGALRM信号给当前进程,默认结束当前进程。
unsigned int alarm(unsigned int seconds);
seconds == 0则定时器无效
该函数不阻塞;
alarm(0)结束倒计时;
进行文件IO操作的时候比较浪费时间,应尽量减少读写次数;
2.sititimer()
函数
计时器按照witch方式计算时间,在it_interval时间发出SIGKILL信号后,每隔it_value时间再发送SIGKILL信号;
int setitimer(int which, const struct itimerval *new_value,struct itimerval *old_value);
struct itimerval
{
struct timeval it_interval; /* 第一次时间 */
struct timeval it_value; /* 以后的时间间隔 */
};
struct timeval
{
time_t tv_sec; /* 秒 */
suseconds_t tv_usec; /* 毫秒*/
};
示例:
//1.设置第一次信号5s,此后每间隔2s发送一次
#include <sys/time.h>
#include <signal.h>
#include <stdio.h>
int main()
{
//1.设置时间
struct itimerval newval =
{
//1.1设置时间间隔
.it_interval.tv_sec = 5,
.it_interval.tv_usec = 0,
.it_value.tv_sec = 2,
.it_value.tv_usec = 0,
};
//2.设置信号捕捉
//2.1设置回调函数
void sighandler(int sig);
//2.2捕捉信号
signal(9, sighandler);
//3. 运行计时器
setitimer(ITIMER_REAL, &newval, NULL);
while(1);
}
void sighandler(int sig)
{
printf("signal: sig\n");
}
3. 信号捕捉
3.1 信号捕捉函数
signal()函数
将捕捉信号函数handler作为signum信号的操作方法
sighandler_t signal(int signum, sighandler_t handler);
signum:信号
handler:信号操作的回调函数。
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);
};
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
将signum信号捕捉到后,按照act中sa_flags设定的方式,使用sa_handler()或sa_sigaction()函数,对进程进行操作。再处理信号过程中,可以根据需要设置sa_mask临时阻塞信号集,sa_restorer()函数无用。
//2.设置捕捉信号
//2.1设置结构体
void sighandler(int sig);
struct sigaction myaction =
{
.sa_handler = sighandler,
sigemptyset(&myaction.sa_mask),
.sa_flags = 0,
};
//2.2设置捕获函数
sigaction(SIGALRM, &myaction, NULL);
注意:
signal()
再某些Linux平台下表现是不同的,因其不完全遵循posix,sigaction()
遵循posix标准,应尽量使用sigaction()
。
3.2 信号捕捉时的流程
- 程序正常运行,cpu处理用户区.text段的的代码;
- 产生信号(异常),cpu切到内核区处理未决信号集中的信号;
- 若该信号时自定义处理方式,会再切换到用户区执行自定义信号处理函数;
- 信号处理结束后,会再切换到内核区查看信号出现之前的位置;
- 切换到用户区信号出现之前的位置继续处理剩下的代码。
4. 信号集
4.1 阻塞信号集和未决信号集
- 信号集位于内核的pcb中,分别通过8个byte即64个位来表示每个信号的状态;
(1)每个信号占用信号集中的一位数据,且信号与位置一一对应;
(2)由于位于内核的pcb中,因此只能通过系统的API来修改; - 阻塞信号集:linux中的64个信号在其中都有位置,若某个信号是阻塞状态,则其对应的状态位置1,若非阻塞状态则清0;
(1)信号阻塞则意味着延迟对该信号的处理,但最终会被处理;
(1)阻塞信号集默认非阻塞状态,即状态位为0;
(2)可以通过系统提供的API来修改某些信号的阻塞状态; - 未决信号集:其中的64位表示64个信号是否尚未被处理,若尚未处理则置1,已被处理或未产生则为0;
(1)未决信号代表信号已经产生,尚未被处理;
(2)未决信号在处理前,需要先判断阻塞信号是否为0,即只有信号未阻塞才能处理,否则信号继续阻塞延迟处理;
(3)未决信号无法修改,也无修改的必要;
4.2 修改信号集API
注意:只能修改阻塞信号集。
- 自定义信号集
清0自定义信号集set
int sigemptyset(sigset_t *set);
置1自定义信号集set
int sigfillset(sigset_t *set);
将自定义信号集set的signum信号置1
int sigaddset(sigset_t *set, int signum);
将自定义信号集set的signum信号清0
int sigdelset(sigset_t *set, int signum);
判断自定义信号集set的signum信号是否是1
int sigismember(const sigset_t *set, int signum);
返回值:信号在信号集中1,不在信号集中0,失败-1;
- 自定义信号集->内核
通过how的方式,将自定义set信号集修改到内核oldset信号集
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
SIG_BLOCK
SIG_UNBLOCK
SIG_SETMASK
将未决信号集中的状态读到自定义信号集set中
int sigpending(sigset_t *set);
成功返回0,失败返回-1
- 示例
//阻塞2和3信号,并打印未决信号集,再取消阻塞信号
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
int main()
{
//1.设置自定义信号集
sigset_t myset;
//1.2清空自定义信号集
sigemptyset(&myset);
//2.设置2和3信号阻塞
sigaddset(&myset, 2);
sigaddset(&myset, 3);
//3.将自定义信号集传给内核
sigprocmask(SIG_BLOCK, &myset, NULL);
//4.循环打印未决信号集
int number = 0;
while(1)
{
sigset_t pend;
//4.1获取未决信号集
sig&pending(&pend);
//4.2循环打印未决信号集
for(int i = 0; i < 31; i++)
{
int ret = sigismember(&pend, i+1);
if(ret == -1)
{
perror("sigismember");
}
printf("%d", ret);
}
sleep(1);
printf("\n");
//4.3超过10s则解除阻塞
number++;
if(number > 10)
{
sigprocmask(SIG_UNBLOCK, &myset, NULL);
}
}
return 0;
}
5. SIGCHLD信号
5.1 产生条件
- 当子进程结束(包括自杀和被杀);
- 子进程暂停
- 子进程从暂停恢复运行
以上三种形况子进程都会给父进程发送SIGCHLD信号,父进程的默认处理方式时忽略。
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
#include <stdio.h>
/*
pid_t waitpid(pid_t pid, int *status, int options);
参数:
- pid:
>0: 某个子进程的pid, 比较常用
=0: 回收当前进程组的子进程
-1: 回收所有的子进程 == wait(NULL), 用的最多的
<0: 某个进程组的组ID, 回收指定进程组的子进程
- options: 设置函数阻塞或者非阻塞
- 0: 阻塞
- WNOHANG: 非阻塞
-返回值:
- >0: 回收的子进程的ID
- =0: options=WNOHANG, 还有子进程活着
- -1: 错误, 已经没有子进程了
*/
void myhandler(int sig)
{
printf("catch %d signal\n", sig);
while(1)
{
pid_t ret = waitpid(-1, NULL, WNOHANG);
//1.若有子进程结束
if(ret > 0)
{
printf("%d进程被回收\n", ret);
}
//2.无子进程结束
if(ret == 0)
{
break;
}
//3.子进程全结束
if(ret == -1)
{
break;
}
}
}
int main()
{
//1.创建子进程
pid_t pid;
for(int i = 0; i < 20; i++)
{
pid = fork();
if(pid == 0)
{
break;
}
}
//1.1子进程
if(pid == 0)
{
printf("pid = %d\n", getpid());
}
//2.设置捕获信号
if(pid > 0)
{
//2.1声明捕获信号函数
struct sigaction myact =
{
.sa_handler = myhandler,
sigemptyset(&myact.sa_mask),
.sa_flags = 0,
};
sigaction(SIGCHLD, &myact, NULL);
//2.2父进程
while(1)
{
printf("父进程pid: %d\n", getpid());
sleep(1);
}
}
return 0;
}