学习来源:https://www.bilibili.com/video/BV1FE411i7KP?p=6
1. &
2. core 文件
3. 捕获函数的过程
4. 让非正常退出变为正常退出
SIGINT (ctrl+c)
, SIG(ctrl+/)
这种退出是非正常退出。
如果要将异常终止转为正常终止,需要用 exit()
exit()
可以刷新"标准io" 缓存,将没有写入的数据全部读到内核中去。
补充:
atexit() : 注册终止函数 即main执行结束后调用的函数
https://www.cnblogs.com/hanxiaoyu/p/5618882.html
- atexit 注册多个进程终止处理函数,先注册的后执行(先进后出,和栈一样)
- atexit() 用于注册函数结束后所执行的函数
-
return、exit和_exit的区别:
return
和exit
效果一样,都是会执行进程终止处理函数,但是用_exit
终止进程时并不执行atexit
注册的进程终止处理函数。
typedef void (*sighandler_t)(int);
void signal_fun1(int signum)
{
printf("SIGINT singnum=%d\n", signum);
exit(-1);
}
void process_exit(void)
{
printf("After main()\n");
}
int main()
{
sighandler_t ret = NULL;
ret = signal(SIGINT, SIG_IGN);
ret = signal(SIGINT, SIG_DFL);
ret = signal(SIGINT, SIG_DFL);
ret = signal(SIGINT, signal_fun1);
if (ret == SIG_DFL)
{
printf("default");
printf("ret = %p\n", ret); //都是0 x 0
}
else if (ret == SIG_IGN)
{
printf("ignore");
printf("ret = %p\n", ret);
}
else if (ret == SIG_ERR)
{
printf("error");
printf("ret = %p\n", ret);
}
else
{
printf("capture");
printf("ret = %p\n", ret); //只有这个有地址
}
atexit(process_exit);
while (1)
;
return 0;
}
5.自己查 man signal 来接收该函数的返回值
6. pkill 和 kill 的不同
7. 一些信号需要记一记
名称 | 编号 | 意义 |
---|---|---|
SIGTERM | 15 | 用 kill pid 直接 默认终止 ,没写 15 |
SIGQUIT | 3 |
ctrl+\ 终止(和ctrl+c 区别,有core 文件产生) |
SIGPOLL | 轮询事件 | |
SIGBUS | 硬件故障 | |
SIGIO | 异步通知信号 | |
SIGPIPE | 写 没有读权限的管道 | |
SIGSEGV | 无效存储访问 (segment fault 段错误) |
8. KILL保底操作,两个信号忽略不了
SIGKILL SIGSTOP
9. 默认值
10. 父子进程的继承
fork之前,
父进程设置的处理方式是忽略
或 默认
时,exec
加载新程序后,忽略和默认设置依然有效。
这个新的new_pro
就和子进程一样。
父进程设置的处理方式是捕获
,exec
加载新程序后,子进程就只能是默认。
11. 手动重启 调用 pause(), sleep()
当休眠函数不希望被信号打断时,可以重启这个函数的调用。
12. 信号集
意思:所有信号62
个集合到一起。假如用一个long
来存储,它有62位,每一位代表一个信号。在linux中,它的类型是sigset_t
,大小是 64bits。
此时你应该又有疑问了:为何是64bits?原因很简单,因为目前linux流行版本一共有64个信号(不同版本信号格式不同),我们一个bit来表示一种信号,一共只需要64bits就行啦!!!
我们想让有些信号屏蔽,有些信号打开。
通过,我们可以把某一位(某个信号) 设置成 1 或者 0。
而这些,我们用一组函数就可以实现了:
使用函数sigprocmask()
阻塞信号的传递,只是延迟信号的到达。信号会在解除阻塞后继续传递。
举个🌰:
void handler(int signo)
{
printf("hello\n");//如果不加\n会在三秒后一起打出
sleep(3);
printf("world\n");
return;
}
int main()
{
signal(SIGINT, handler);
while (1)
;
return 0;
}
void handler(int signo)
{
int ret;
sigset_t mask, oldset;
printf("SIG_INT respond:%d\n", signo);
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
ret = sigprocmask(SIG_UNBLOCK, &mask, &oldset);
sleep(3);
printf("ret=%d\n", ret);
return;
}
int main()
{
signal(SIGINT, handler);
while (1)
;
return 0;
}
信号的阻塞就是让系统暂时保留信号留待以后发送。由于另外有办法让系统忽略信号,所以一般情况下信号的阻塞只是暂时的,只是为了 防止信号打断敏感的操作。
当然,SIGKILL 和 SIGSTOP 是不能被屏蔽的 ❌ 。
再举个 🌰 :
我们设计一段代码,她让当前进程屏蔽SIG_USR1
,而不屏蔽SIG_USR2
。
然后,我们用kill
命令分别向这个进程发送这两个信号,观察现象
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <setjmp.h>
void sig_usr_new(int signo);
int main(){
//1,注册信号
if(signal(SIGUSR1, sig_usr_new) == SIG_ERR)
{
printf("can't catch SIGUSR1 !\n");
exit(1);
}
if(signal(SIGUSR2, sig_usr_new) == SIG_ERR)
{
printf("can't catch SIGUSR2 !\n");
exit(1);
}
//2, 修改信号屏蔽字
sigset_t sigset;
//2.1 初始化sigset
if (sigemptyset(&sigset) < 0)
{
printf("sigemptyset error !\n");
exit(1);
}
//2.2 添加要屏蔽的信号
if (sigaddset(&sigset, SIGUSR1) < 0)
{
printf("add SIGUSR1 error!\n");
exit(1);
}
//2.3 修改当前信号屏蔽字
if (sigprocmask(SIG_BLOCK, &sigset, NULL) < 0)
{
printf("can't block SIGUSR1!\n");
exit(1);
}
//3,死循环,为了防止进程退出
while(1){
pause();
}
}
void sig_usr_new(int signo)
{
printf("received SIGUSR, signo = %d\n",signo);
}
再来个🌰:
void handler(int signum)
{
printf("SIG_INT respond:%d\n", signum);
return;
}
int main()
{
char tmp = 'a';
sigset_t bset; //用来设置阻塞的信号集
sigemptyset(&bset); //清空信号集
sigaddset(&bset, SIGINT); //将SIG_INT信号添加到信号集中
if (signal(SIGINT, handler) == SIG_ERR) //注册安装处理函数
perror("signal err:");
sigprocmask(SIG_BLOCK, &bset, NULL); //阻塞SIG_INT信号
while (tmp != 'q')
{
tmp = getchar();
}
sigprocmask(SIG_UNBLOCK, &bset, NULL); //解锁阻塞
pause();
return 0;
}
注意 ⚠️ :不同系统可能命名不一样,有些是
SIG_ERR
, 有些是SIGINT
,有些是-USR1
。根据编译出错试着改下其他命名。