按下 CTRL + C之后,命令行中的前台进程会被终止。为什么???
信号是一种“软件中断”,用来处理异步事件
信号是一种类型的进程间通信方式(一个进程向另外一个进程发送信号)
硬件异常
内核检测到硬件错误,发送相应信号给相关进程
终端信号(用户交互信号,最典型:ctrl+c)
在终端输入 “特殊字符” 等价于向前台进程组发送信号
软件信号
在软件层面(进程代码中)触发的信号(发送给自身或其他进程)
硬件异常信号
信号 | 值 | 说明 |
SIGBUS | 7 | 总线错误,进程发生了内存访问错误 |
SIGFPE | 8 | 算术错误,FPE表示浮点异常 |
SIGILL | 9 | 指令错误,进程尝试执行非法指令 |
SIGSEGV | 11 | 段错误,进程访问了非法内存区域 |
终端相关信号
软件相关信号
内核与信号
1.内核检测到相关的错误,将信号发送给对应的进程。
2.进程可以通过系统调用,告诉内核,自己需要信号。比如:时钟信号。比如进程A告诉内核,我需要时钟信号,那么内核就定期给进程A发送时钟信号。
3.进程间通信:进程A通过内核 将信号 发送给进程B
信号的发送与处理,无非就是以上三种情况。
默认处理方式 | 说明 | 示例 |
ignore | 进程丢弃信号不会产生任何影响 | SIGCHLD SIGURG |
terminate | 终止进程 | SIGKILL SIGHUP |
coredump | 终止进程并产生转储文件 | SIGQUIT SIGILL |
stop/continue | 停止进程执行/恢复进程执行 | SIGSTOP,SIGCONT |
知识加油站:System V vs BSD
System V :也被称为 AT&T SystemV,是Unix操作系统众多版本中的一支
BSD :加州大学巴克利分校开创,Unix衍生系统,代表由此派生出的各种套件集合
Linux之所以被称为类Unix操作系统(Unix Like),部分原因就是Linux的操作风格是介于上述二者之间,且不同的厂商为了照顾不同的用户,其发行版本的操作风格有存在差异。
#include
#include
int kill(pid_t pid,int sig);
//pid > 0 ==> 发送信号给进程 ,pid == 0 ==> 发送信号给进程组 ,pid < -1 比如-1000,将信号发送给PGID为-1000的进程组
int raise(int sig); //信号处理完毕才返回
notes:
标准信号是unix系统中的信号,编号范围从1到31。
实时信号是Linux独有的信号,编号范围从32到64。
下面来看几个demo示例。
signal
可以看到在终端按下 CTRL + C 之后,信号处理函数 signal_handler就不断地调用。
sysv_signal
#define _GNU_SOURCE
#include
#include
#include
#include
#include
void signal_handler(int sig)
{
printf("handler : sig = %d\n",sig);
}
int main(void)
{
//signal(SIGINT,signal_handler);
sysv_signal(SIGINT,signal_handler);
while(1)
{
sleep(1);
}
return 0;
}
zhaixue@ubuntu:~/DTLinux/0-11$ gcc main.c -o main.out
zhaixue@ubuntu:~/DTLinux/0-11$ ./main.out
^Chandler : sig = 2
^C
zhaixue@ubuntu:~/DTLinux/0-11$
bsd_signal
//#define _GNU_SOURCE
#define _XOPEN_SOURCE 600
#define _POSIX_C_SOURCE 200800L
#include
#include
#include
#include
#include
void signal_handler(int sig)
{
printf("handler : sig = %d\n",sig);
}
int main(void)
{
//signal(SIGINT,signal_handler);
//sysv_signal(SIGINT,signal_handler);
bsd_signal(SIGINT,signal_handler);
while(1)
{
sleep(1);
}
return 0;
}
zhaixue@ubuntu:~/DTLinux/0-11$ gcc main.c -o main.out
zhaixue@ubuntu:~/DTLinux/0-11$ ./main.out
^Chandler : sig = 2
^Chandler : sig = 2
^Chandler : sig = 2
^Chandler : sig = 2
^Chandler : sig = 2
^Chandler : sig = 2
^Chandler : sig = 2
^\Quit (core dumped)
zhaixue@ubuntu:~/DTLinux/0-11$
由于信号在表现形式上是一个整数值,所以将值为40的信号映射到 信号处理函数 signal_handler上来。
main.c
//#define _GNU_SOURCE
#define _XOPEN_SOURCE 600
#define _POSIX_C_SOURCE 200800L
#include
#include
#include
#include
#include
void signal_handler(int sig)
{
printf("handler : sig = %d\n",sig);
}
int main(void)
{
signal(SIGINT,signal_handler);
signal(40,signal_handler);
//sysv_signal(SIGINT,signal_handler);
//bsd_signal(SIGINT,signal_handler);
while(1)
{
sleep(1);
}
return 0;
}
想发送值为40的信号给信号处理函数。signal(40,signal_handler);
test.c
#include
#include
#include
#include
#include
int main(int argc,char* argv[])
{
int pid = atoi(argv[1]);
int sig = atoi(argv[2]);
printf("send sig(%d) to process(%d)...\n",sig,pid);
kill(pid,sig); //send signal
raise(SIGINT); //end execute
while(1)
{
printf("while...\n");
sleep(1);
}
return 0;
}
编译输出:
zhaixue@ubuntu:~/DTLinux/0-11$ gcc main.c -o main.out
zhaixue@ubuntu:~/DTLinux/0-11$ ./main.out &
[1] 9437
zhaixue@ubuntu:~/DTLinux/0-11$ gcc test.c -o test.out
zhaixue@ubuntu:~/DTLinux/0-11$ ./test.out 9437 40
send sig(40) to process(9437)...
handler : sig = 40
zhaixue@ubuntu:~/DTLinux/0-11$
以上signal、sysv_signal、bsd_signal 三种自定义信号处理函数有什么区别?
System V风格的signal函数,注册的信号处理是一次性的。
在信号处理函数执行期间,很有可能再次收到当前信号
即:处理A信号的时候,再次收到A信号
对于 System V风格的 signal 函数,会引起信号处理函数的重入
即:调用处理函数的过程中,再次触发处理函数的调用
在注册信号处理函数时:
System V风格的 signal 不屏蔽任何信号
BSD风格的 signal 会屏蔽当前注册的信号
问题:BSD风格的 signal 函数,处理A信号期间,如果收到B信号会发生什么?
系统调用重启特性
系统调用期间,可能受到信号,此时进程必须从系统调用中返回
对于执行时间较长的系统调用(write / read),被信号中断的可能性很大
如果希望信号处理之后,被中断的系统调用能够重启,则:
可以通过条件 errno == EINTR 判断重启系统调用
-System V风格的 signal 函数(只能手动重启)
系统调用被信号中断后,直接返回 -1,并且 errno == EINTR
-BSD风格的 signal 函数(自动重启)
系统调用被中断,内核在信号处理函数结束后,自动重启系统调用
OneShot特性,只有System V风格的 signal 函数才有。
main.c
#define _GNU_SOURCE
//#define _XOPEN_SOURCE 600
//#define _POSIX_C_SOURCE 200800L
#include
#include
#include
#include
#include
void signal_handler(int sig)
{
printf("handler : sig = %d\n",sig);
}
int main(void)
{
//signal(SIGINT,signal_handler);
//signal(40,signal_handler);
sysv_signal(SIGINT,signal_handler);
//bsd_signal(SIGINT,signal_handler);
while(1)
{
sleep(1);
}
return 0;
}
编译运行:
zhaixue@ubuntu:~/DTLinux/0-11$ gcc main.c -o main.out
zhaixue@ubuntu:~/DTLinux/0-11$ ./main.out
^Chandler : sig = 2
^C
zhaixue@ubuntu:~/DTLinux/0-11$
可以看到,只按下了一次 ctrl + c ,进程就退出了。因为 sysv_signal(SIGINT,signal_handler); 是一次性的,一旦 signal_handler 信号处理函数被调用了,那么这个信号处理函数:signal_handler 就已失效了。
接下来,我们接着实验 BSD风格的信号处理函数。
//#define _GNU_SOURCE
#define _XOPEN_SOURCE 600
#define _POSIX_C_SOURCE 200800L
#include
#include
#include
#include
#include
void signal_handler(int sig)
{
printf("handler : sig = %d\n",sig);
}
int main(void)
{
//signal(SIGINT,signal_handler);
//signal(40,signal_handler);
//sysv_signal(SIGINT,signal_handler);
bsd_signal(SIGINT,signal_handler);
while(1)
{
sleep(1);
}
return 0;
}
zhaixue@ubuntu:~/DTLinux/0-11$ gcc main.c -o main.out
zhaixue@ubuntu:~/DTLinux/0-11$ ./main.out
^Chandler : sig = 2
^Chandler : sig = 2
^Chandler : sig = 2
^Chandler : sig = 2
^Chandler : sig = 2
^Chandler : sig = 2
^Chandler : sig = 2
^Chandler : sig = 2
^Chandler : sig = 2
^\Quit (core dumped)
zhaixue@ubuntu:~/DTLinux/0-11$
可以看到每次按下 ctrl + c,就会调用一次 信号处理函数。Linux中默认的信号处理函数 signal 和 BSD风格的信号处理函数行为是一致的。
main.c
#define _GNU_SOURCE
//#define _XOPEN_SOURCE 600
//#define _POSIX_C_SOURCE 200800L
#include
#include
#include
#include
#include
void delay_handler(int sig)
{
int i = 0;
printf("begin delay handler...\n");
for(i=0;i<5;i++)
{
printf("sleep %d ...\n",i);
sleep(1);
}
printf("end delay handler...\n");
}
void signal_handler(int sig)
{
printf("handler : sig = %d\n",sig);
}
int main(void)
{
//signal(SIGINT,signal_handler);
//signal(40,signal_handler);
sysv_signal(SIGINT,delay_handler);
//bsd_signal(SIGINT,signal_handler);
while(1)
{
sleep(1);
}
return 0;
}
zhaixue@ubuntu:~/DTLinux/0-11$ gcc main.c -o main.out
zhaixue@ubuntu:~/DTLinux/0-11$ ./main.out
^Cbegin delay handler...
sleep 0 ...
sleep 1 ...
sleep 2 ...
sleep 3 ...
sleep 4 ...
end delay handler...
^C
zhaixue@ubuntu:~/DTLinux/0-11$
可以看到这一次 oneShot特性。
zhaixue@ubuntu:~/DTLinux/0-11$ gcc main.c -o main.out
zhaixue@ubuntu:~/DTLinux/0-11$ ./main.out
^Cbegin delay handler...
sleep 0 ...
sleep 1 ...
^C
zhaixue@ubuntu:~/DTLinux/0-11$
来修改一下 void delay_handler(int sig) 函数,在void delay_handler(int sig)函数中再一次调用 sysv_signal(SIGINT,delay_handler); 那么就相当于去掉OneShot特性。
#define _GNU_SOURCE
//#define _XOPEN_SOURCE 600
//#define _POSIX_C_SOURCE 200800L
#include
#include
#include
#include
#include
void delay_handler(int sig)
{
int i = 0;
sysv_signal(SIGINT,delay_handler);
printf("begin delay handler...\n");
for(i=0;i<5;i++)
{
printf("sleep %d ...\n",i);
sleep(1);
}
printf("end delay handler...\n");
}
void signal_handler(int sig)
{
printf("handler : sig = %d\n",sig);
}
int main(void)
{
//signal(SIGINT,signal_handler);
//signal(40,signal_handler);
sysv_signal(SIGINT,delay_handler);
//bsd_signal(SIGINT,signal_handler);
while(1)
{
sleep(1);
}
return 0;
}
编译运行:
zhaixue@ubuntu:~/DTLinux/0-11$ gcc main.c -o main.out
zhaixue@ubuntu:~/DTLinux/0-11$ ./main.out
^Cbegin delay handler...
sleep 0 ...
sleep 1 ...
sleep 2 ...
^Cbegin delay handler...
sleep 0 ...
sleep 1 ...
sleep 2 ...
^Cbegin delay handler...
sleep 0 ...
sleep 1 ...
sleep 2 ...
sleep 3 ...
sleep 4 ...
end delay handler...
sleep 3 ...
sleep 4 ...
end delay handler...
sleep 3 ...
sleep 4 ...
end delay handler...
^Cbegin delay handler...
sleep 0 ...
sleep 1 ...
sleep 2 ...
sleep 3 ...
sleep 4 ...
end delay handler...
^\Quit (core dumped)
zhaixue@ubuntu:~/DTLinux/0-11$
可以看到OneShot特性相当于已经没有了。
接下来看 BSD风格的信号处理函数,会有什么样的结果。
main.c
//#define _GNU_SOURCE
#define _XOPEN_SOURCE 600
#define _POSIX_C_SOURCE 200800L
#include
#include
#include
#include
#include
void delay_handler(int sig)
{
int i = 0;
//sysv_signal(SIGINT,delay_handler);
printf("begin delay handler...\n");
for(i=0;i<5;i++)
{
printf("sleep %d ...\n",i);
sleep(1);
}
printf("end delay handler...\n");
}
void signal_handler(int sig)
{
printf("handler : sig = %d\n",sig);
}
int main(void)
{
//signal(SIGINT,signal_handler);
//signal(40,signal_handler);
//sysv_signal(SIGINT,delay_handler);
bsd_signal(SIGINT,delay_handler);
while(1)
{
sleep(1);
}
return 0;
}
zhaixue@ubuntu:~/DTLinux/0-11$ gcc main.c -o main.out
zhaixue@ubuntu:~/DTLinux/0-11$ ./main.out
^Cbegin delay handler...
sleep 0 ...
sleep 1 ...
sleep 2 ...
^Csleep 3 ...
sleep 4 ...
end delay handler...
begin delay handler...
sleep 0 ...
sleep 1 ...
sleep 2 ...
sleep 3 ...
sleep 4 ...
end delay handler...
begin delay handler...
sleep 0 ...
^Csleep 1 ...
sleep 2 ...
^Csleep 3 ...
sleep 4 ...
end delay handler...
begin delay handler...
sleep 0 ...
sleep 1 ...
sleep 2 ...
sleep 3 ...
sleep 4 ...
end delay handler...
^\Quit (core dumped)
zhaixue@ubuntu:~/DTLinux/0-11$
可以看到不会发生重入,必须等待当前的信号处理函数处理完,才能够处理下一次的信号。当下一次信号来的时候,必须排队等着。
Linux 默认的 signal 信号处理函数 和 BSD风格的 bsd_signal(SIGINT,delay_handler) 信号处理函数是一致的。
main.c
#define _GNU_SOURCE
//#define _XOPEN_SOURCE 600
//#define _POSIX_C_SOURCE 200800L
#include
#include
#include
#include
#include
#include
void delay_handler(int sig)
{
int i = 0;
//sysv_signal(SIGINT,delay_handler);
printf("begin delay handler...\n");
for(i=0;i<5;i++)
{
printf("sleep %d ...\n",i);
sleep(1);
}
printf("end delay handler...\n");
}
void signal_handler(int sig)
{
printf("handler : sig = %d\n",sig);
}
int r_read(char* data,int len)
{
int ret = -1;
while(data && ((ret = read(STDIN_FILENO,data,len-1)) == -1) && (errno == EINTR))
{
printf("restart syscall mannually...\n");
}
if(ret != -1)
{
data[ret] = 0;
}
return ret;
}
int main(void)
{
char buf[32]={0};
//signal(SIGINT,delay_handler);
//signal(40,signal_handler);
sysv_signal(SIGINT,signal_handler);
//bsd_signal(SIGINT,delay_handler);
r_read(buf,32);
printf("input: %s\n",buf);
return 0;
}
zhaixue@ubuntu:~/DTLinux/0-11$ gcc main.c -o main.out
zhaixue@ubuntu:~/DTLinux/0-11$ ./main.out
1234^Chandler : sig = 2
restart syscall mannually...
test
input: test
zhaixue@ubuntu:~/DTLinux/0-11$
可以看到 read 系统调用,被信号给中断了。
实验 BSD 风格的 signal 函数。
//#define _GNU_SOURCE
#define _XOPEN_SOURCE 600
#define _POSIX_C_SOURCE 200800L
#include
#include
#include
#include
#include
#include
void delay_handler(int sig)
{
int i = 0;
//sysv_signal(SIGINT,delay_handler);
printf("begin delay handler...\n");
for(i=0;i<5;i++)
{
printf("sleep %d ...\n",i);
sleep(1);
}
printf("end delay handler...\n");
}
void signal_handler(int sig)
{
printf("handler : sig = %d\n",sig);
}
int r_read(char* data,int len)
{
int ret = -1;
while(data && ((ret = read(STDIN_FILENO,data,len-1)) == -1) && (errno == EINTR))
{
printf("restart syscall mannually...\n");
}
if(ret != -1)
{
data[ret] = 0;
}
return ret;
}
int main(void)
{
char buf[32]={0};
//signal(SIGINT,signal_handler);
//signal(40,signal_handler);
//sysv_signal(SIGINT,signal_handler);
bsd_signal(SIGINT,signal_handler);
r_read(buf,32);
printf("input: %s\n",buf);
return 0;
}
zhaixue@ubuntu:~/DTLinux/0-11$ gcc main.c -o main.out
zhaixue@ubuntu:~/DTLinux/0-11$ ./main.out
1234^Chandler : sig = 2
^Chandler : sig = 2
^Chandler : sig = 2
^\Quit (core dumped)
zhaixue@ubuntu:~/DTLinux/0-11$
可以看到,BSD风格的 signal 函数会自动重启系统调用。和 sysv 风格的信号函数不一样,没有打印 restart syscall mannually... 出来。
默认风格的 signal 函数 和 BSD风格的 signal 函数一样,会自动重启系统调用。
问题:BSD风格的 signal 函数,处理A信号期间,如果收到B信号会发生什么?会不会重入?
main.c
//#define _GNU_SOURCE
#define _XOPEN_SOURCE 600
#define _POSIX_C_SOURCE 200800L
#include
#include
#include
#include
#include
#include
void delay_handler(int sig)
{
int i = 0;
//sysv_signal(SIGINT,delay_handler);
printf("begin delay handler...\n");
for(i=0;i<5;i++)
{
printf("sleep %d ...\n",i);
sleep(1);
}
printf("end delay handler...\n");
}
void signal_handler(int sig)
{
printf("handler : sig = %d\n",sig);
}
int r_read(char* data,int len)
{
int ret = -1;
while(data && ((ret = read(STDIN_FILENO,data,len-1)) == -1) && (errno == EINTR))
{
printf("restart syscall mannually...\n");
}
if(ret != -1)
{
data[ret] = 0;
}
return ret;
}
int main(void)
{
char buf[32]={0};
//signal(SIGINT,signal_handler);
bsd_signal(40,signal_handler);
//sysv_signal(SIGINT,signal_handler);
bsd_signal(SIGINT,delay_handler);
r_read(buf,32);
printf("input: %s\n",buf);
return 0;
}
test.c
#include
#include
#include
#include
#include
int main(int argc,char* argv[])
{
int pid = atoi(argv[1]);
int sig = atoi(argv[2]);
printf("send sig(%d) to process(%d)...\n",sig,pid);
kill(pid,sig); //send signal
raise(SIGINT); //end execute
while(1)
{
printf("while...\n");
sleep(1);
}
return 0;
}
zhaixue@ubuntu:~/DTLinux/0-11$ gcc main.c -o main.out
zhaixue@ubuntu:~/DTLinux/0-11$ ./main.out
^Cbegin delay handler...
sleep 0 ...
sleep 1 ...
sleep 2 ...
handler : sig = 40
sleep 3 ...
sleep 4 ...
end delay handler...
^Cbegin delay handler...
sleep 0 ...
sleep 1 ...
sleep 2 ...
sleep 3 ...
sleep 4 ...
end delay handler...
^\Quit (core dumped)
zhaixue@ubuntu:~/DTLinux/0-11$
zhaixue@ubuntu:~/DTLinux/0-11$ gcc test.c -o test.out
zhaixue@ubuntu:~/DTLinux/0-11$ ps -ajx | grep main.out
8966 11517 11517 8966 pts/0 11517 S+ 1000 0:00 ./main.out
11392 11524 11523 11392 pts/1 11523 S+ 1000 0:00 grep --color=auto main.out
zhaixue@ubuntu:~/DTLinux/0-11$ ./test.out 11517 40
send sig(40) to process(11517)...
zhaixue@ubuntu:~/DTLinux/0-11$
问题:BSD风格的 signal 函数,处理A信号期间,如果收到B信号会发生什么?会不会重入?
会立即马上的处理B信号,处理完B信号之后,马上返回处理A信号。
-并非所有的系统调用对信号中断都表现同样的行为
一些系统调用支持信号中断后自动重启
read()、write()、wait()、waitpid()、ioctl() ...
一些系统调用完全不支持中断后自动重启
poll()、select()、usleep()、...
函数 | OneShot | 屏蔽自身 | 重启系统调用 |
signal(...) | false | true | true |
sysv_signal(...) | yes | false | false |
bsd_signal(...) | false | true | true |
在信号处理上,Linux系统更接近 BSD风格的操作;默认的signal函数在不同的 Linux发行版上语义可能不同,从代码移植行角度,避免直接使用 signal(...)函数。