信号分类
不可靠信号
Linux信号机制基本上是从UNIX系统中继承过来的。早期UNIX系统中的信号机制比较简单和原始,后来在实践中暴露出一些问题,它的主要问题是:
1.进程每次处理信号后,就将对信号的响应设置为默认动作。在某些情况下,将导致对信号的错误处理;因此,用户如果不希望这样的操作,那么就要在信号处理函数结尾再一次调用signal(),重新安装该信号。
2.因此导致, 早期UNIX下的不可靠信号主要指的是进程可能对信号做出错误的反应以及信号可能丢失(处理时有来了新的信号,则导致信号丢失)。
Linux支持不可靠信号,但是对不可靠信号机制做了改进:在调用完信号处理函数后,不必重新调用该信号的安装函数(信号安装函数是在可靠机制上的实现)。因此,Linux下的不可靠信号问题主要指的是信号可能丢失。
可靠信号
随着时间的发展,实践证明,有必要对信号的原始机制加以改进和扩充。所以,后来出现的各种UNIX版本分别在这方面进行了研究,力图实现"可靠信号"。由于原来定义的信号已有许多应用,不好再做改动,最终只好又新增加了一些信号(SIGRTMIN ~ SIGRTMAX),并在一开始就把它们定义为可靠信号,这些信号支持排队,不会丢失。同时,信号的发送和安装也出现了新版本:信号发送函数sigqueue()及信号安装函数sigaction()。
sigaction和signal函数都是调用内核服务do_signaaction函数;[内核服务函数,应用程序无法调用该函数]
早期UNIX系统只定义了31种信号,而Linux 3.x支持64种信号,编号1-64(SIGRTMIN=34,SIGRTMAX=64),将来可能进一步增加,这需要得到内核的支持。 前31种信号已经有了预定义值,每个信号有了确定的用途及含义,并且每种信号都有各自的缺省动作。如按键盘的CTRL+C时,会产生SIGINT信号,对该信号的默认反应就是进程终止。后32个信号表示实时信号,等同于可靠信号。这保证了发送的多个实时信号都被接收。实时信号是POSIX标准的一部分,可用于应用进程。
非实时信号都不支持排队,都是不可靠信号;实时信号都支持排队,都是可靠信号。
信号发送
1.kill kill既可以向自身发送信号,也可以向其他进程发送信号
int kill(pid_t pid, int signo);
signo参数组合情况解释:
pid>0 将信号sig发给pid进程
pid=0 将信号sig发给同组进程
pid=-1 将信号sig发送给所有进程,调用者进程有权限发送的每一个进程(除了1号进程之外,还有它自身)
pid<-1 将信号sig发送给进程组是pid(绝对值)的每一个进程
//示例
void onSignalAction(int signalNumber)
{
switch(signalNumber)
{
case SIGUSR1:
cout << "SIGUSR1 = " << signalNumber << endl;
break;
default:
cout << "Other Signal ..." << endl;
break;
}
}
int main()
{
if (signal(SIGUSR1,onSignalAction)== SIG_ERR)
{
perror("signal error");
return -1;
}
pid_t pid = fork();
if (pid == -1)
{
perror("fork error");
return -1;
}
else if (pid == 0)
{
/**向父进程发送信号
pid_t ppid = getppid();
kill(ppid,SIGUSR1);
*/
/**向同组所有进程发送信号,子进程也会收到该信号
kill(0,SIGUSR1);
*/
//向本组所有进程发送信号,作用同上
//getpgrp()函数获取进程组pid
pid_t pgid = getpgrp();
killpg(pgid,SIGUSR1);
exit(0);
}
int sleepTime = 3;
while (sleepTime > 0)
{
write(STDOUT_FILENO,"Parent start Sleep...\n",
sizeof("Parent start Sleep...\n"));
sleepTime = sleep(sleepTime);
write(STDOUT_FILENO,"Parent return from Sleep...\n",
sizeof("Parent return from Sleep...\n"));
}
return 0;
}
注意:如果在fork之前安装信号,则子进程可以继承信号。
sleep遇上signal,子进程向父进程发送信号,sleep函数的几点说明
1)sleep函数作用,让进程睡眠。
2)能被信号打断,然后处理信号函数以后,就不再睡眠了。直接向下执行代码
3)sleep函数的返回值,是剩余的秒数
//示例:sleep遇上signal
void onSignalAction(int signalNumber)
{
switch(signalNumber)
{
case SIGINT:
cout << "SIGINT = " << signalNumber << endl;
break;
default:
cout << "Other Signal ..." << endl;
break;
}
}
int main()
{
if (signal(SIGINT,onSignalAction)== SIG_ERR)
{
perror("signal error");
return -1;
}
cout << "Main Start Sleeping..." << endl;
int returnValue = sleep(100); //可中断睡眠
cout << "Main End Sleeping... returnValue = " << returnValue << endl;
return 0;
}
//示例:sleep加强
int main()
{
//...同上
cout << "Main Start Sleeping..." << endl;
//sleep加强版^^
int sleepTime = 20;
do
{
sleepTime = sleep(sleepTime);
cout << "continue..." << endl;
}
while (sleepTime > 0);
cout << "Main End Sleeping... sleepTime = " << sleepTime << endl;
return 0;
}
2.killpg
int killpg(int
给进程组发送信号。killpg(pgrp, sig)等价于kill(-pgrp, sig);
更多信号发送函数
pause
int pause(void);
将进程置为可中断睡眠状态。然后它调用内核函数schedule(),使Linux进程调度器找到另一个进程来运行。
pause使调用者进程挂起,直到一个信号被捕获
//示例
int main()
{
if (signal(SIGINT,handler)== SIG_ERR)
err_exit("signal error");
while(true)
{
pause();
cout << "pause return..." << endl;
}
}
alarm函数
设置一个闹钟延迟发送SIGALRM信号(告诉Linux内核n秒中以后,发送SIGALRM信号);
手册描述-DESCRIPTION
alarm() arranges for a SIGALRM signal to be delivered to the process in seconds seconds.
If seconds is zero, no new alarm() is scheduled.
In any event any previously set alarm() is cancelled.
//alarm 递归调用
void onSignalAction(int signalNumber)
{
switch(signalNumber)
{
case SIGALRM:
cout << "SIGALRM = " << signalNumber << endl;
alarm(1); //继续调用onSignalAction
break;
default:
cout << "Other Signal ..." << endl;
break;
}
}
int main()
{
if (signal(SIGALRM,onSignalAction)== SIG_ERR)
{
perror("signal error");
return -1;
}
alarm(1);
while(true)
{
pause();
cout << "pause returned..." << endl;
}
return 0;
}
setitimer SIGALRM SIGVTALRM SIGPROT
abort SIGABRT
可重入/不可重入函数
为了增强程序的稳定性,在信号处理函数中应使用可重入函数。
所谓可重入函数是指一个可以被多个任务调用的过程,任务在调用时不必担心数据是否会出错。因为进程在收到信号后,就将跳转到信号处理函数去接着执行。如果信号处理函数中使用了不可重入函数,那么信号处理函数可能会修改原来进程中不应该被修改的数据,这样进程从信号处理函数中返回接着执行时,可能会出现不可预料的后果。不可重入函数在信号处理函数中被视为不安全函数。
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
typedef struct
{
int a;
int b;
} TEST;
TEST g_data;
void handler(int sig);
int main(int argc, char *argv[])
{
TEST zeros = {0, 0};
TEST ones = {1, 1};
if (signal(SIGALRM, handler) == SIG_ERR)
ERR_EXIT("signal error");
g_data = zeros;
alarm(1);
for (;;)
{
g_data = zeros;
g_data = ones;
}
return 0;
}
void unsafe_fun()
{
printf("%d %d\n", g_data.a, g_data.b);
}
void handler(int sig)
{
unsafe_fun();
alarm(1);
}
也是程序创建了一个结构体,设置一个全局变量,然后在main函数中利用两个局部变量分别给全局变量赋值,由于这个赋值操作是可被中断的,如以上每一次结构体的赋值可视为两步:
g_data.a=zeros.a;
g_data.b=zeros.b;
所以当g_data.a=one.a;做完然后被中断,跑去执行处理函数,在处理函数中调用unsafe_fun()打印全局变量值,可知结果是全局变量a值变了,b值还是之前的没来的及改变,所以出现了1,0
所以结果不确定
不可重入函数
满足下列条件的函数多数是不可重入的:
(1)使用静态数据结构,如getlogin(),gmtime(),getgrgid(),getgrnam(),getpwuid()以及getpwnam()等等;
(2)函数实现时,调用了malloc()或者free()函数;
(3)实现时使用了标准I/O函数
被中断的函数与中段处理函数共享了数据