Linux编程中的信号
1 信号
信号是UNIX和Linux系统响应某些条件而产生的一个事件,接收到信号的进程会采取一些行动。信号是由于某些错误条件而生成的,如内存段冲突、浮点处理器错误或非法指定等。信号由shell或终端处理器生成来引起中断,如果进程接收到信号而没有安排捕获它,进程会立刻终止。通常系统将在当前目录下生成核心转储文件core,该文件是进程在内存中的映像,有助于程序调试。
信号名称在头文件/usr/include/signal.h
中定义,以SIG开头。kill
命令可向非前台进程发送信号,需要有信号名称和目标进程的PID。killall
命令可以给运行着某一命令的所有进程发送信号,当不知道某个进程的PID,或是想给执行相同命令的许多不同进程发送信号,可以使用该命令。
#include <signal.h>
void(*signal(int sig,void(*func)(int)))(int);
signal函数用来对信号进行处理。sig参数指定想要捕获的信号,func函数指针指向接收到指定的信号后将要调用的函数,函数*func的参数为接收到的信号代码(SIGINT为2,如果想要在一个函数中处理多个信号,该参数是很有用的)。返回一个同类型的函数,即先前用来处理这个信号的函数指针,如果未定义信号处理函数则返回SIG_ERR并设置errno,如果给出的是一个无效信号,或者尝试处理的信号是不可捕获或不可忽略的信号(如SIGKILL),errno将被设置为EINVAL。func可用两个宏代替:SIG_IGN:忽略该信号,SIG_DFL:恢复默认行为。
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
void ouch(int sig)
{
printf("OUCH!!!! I got signal %d\n",sig);
signal(SIGINT,SIG_DFL);
}
int main()
{
signal(SIGINT,ouch);
while(1)
{
printf("Hello world\n");
sleep(1);
}
}
在信号处理函数中使用printf是不安全的,我们可以在信号处理函数中设置一个标志,在主程序中检查该标志,如需要则在主程序中打印一条信息。
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
static int flag = 0;
void ouch(int sig)
{
flag = 1;
signal(SIGINT,SIG_DFL);
}
int main()
{
signal(SIGINT,ouch);
while(1)
{
printf("Hello world\n");
if(flag)
{
flag = 0;
printf("OUCH!!!! I got signal\n");
}
sleep(1);
}
}
注意:我们从调用中断函数开始,到信号处理函数的重建为止,如果在这段时间内程序接收到第二个信号,那它就会违背我们的意愿而终止程序的运行。不要用signal,用sigaction。
2 发送与接收信号
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid,int sig);
进程调用kill函数可以向包括它本身在内的其他进程发送一个信号。 sig指明信号类型,pid指明目标进程号,成功时返回0。要想发送一个信号,发送进程必须有相应的权限,即这两个进程必须有相同的UID,但root可以发送信号给任何进程。失败时返回-1并设置errno。
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
alarm函数在seconds秒后向本进程发送一个SIGALRM信号,seconds设置为0将取消所有已设置的闹钟,每个进程只有一个闹钟时间。返回值是之前设置的余留闹钟秒数,失败时返回-1。
#include <unistd.h>
int pause();
pause函数把程序的执行挂起直到有一个信号出现为止,当程序接收到一个信号时,预设好的信号处理函数将开始运行,程序也将恢复正常执行。当它被一个信号中断时,返回-1并把errno设置为EINTR。
闹钟alarm的模拟:
#include <sys/types.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
static int alarm_fired = 0;
void ding(int sig)
{
alarm_fired = 1;
}
int main()
{
printf("alarm application starting\n");
pid_t pid = fork();
switch(pid)
{
case(-1):
perror("fork failed");
exit(1);
case(0):
sleep(5);
kill(getppid(),SIGALRM);
exit(0);
}
printf("waiting for alarm to go off\n");
signal(SIGALRM,ding);
pause();
if(alarm_fired)
printf("Ding\n");
printf("Done\n");
exit(0)
}
注意:使用信号并挂起程序的执行使程序不需要总是在执行着,程序不必在一个循环中无休止地检查某个事件是否已发生。这对于一个单CPU的多用户环境很重要,因为繁忙的等待会对系统的性能造成较大的影响。因为在使用信号的程序中会出现各种各样竞态条件,要进行细致的检查。
3 更加健壮的信号接口
3.1 sigaction
#include <signal.h>
int sigaction(int sig,const struct sigaction* act,struct sigaction* oact);
struct sigaction
{
void(*)(int) sa_handler;
sigset_t sa_mask;
int sa_flags;
};
sigaction函数设置与信号sig相关联的动作,oact若不是空指针,则sigaction把原先对信号的设置写到它指向的位置。成功时返回0,失败时返回-1,并设置errno。
sigaction结构中,
sa_handler是函数指针,指定接收到信号sig时被调用的信号处理函数,特殊值SIG_IGN和SIG_DFL。
sa_mask指定信号集,在调用信号处理函数前,该信号集被加入信号屏蔽字中,它被阻塞且不会传递给该进程。设置信号屏蔽字可以防止信号在它的处理函数还未运行结束时就被接收到的情况。
注意!!!!!!!!!!!!!!!!!!!!! 只在这里写是不行的,不能屏蔽信号!!!!!!!要在sigsuspend处也写上!!!
但这样一来,就无法返回到主函数了。
**sigaction设置的信号默认是不被重置的,可将sa_flags设置为SA_RESETHAND来设置为重置。**不被重置指对设定后的信号一直生效,加上该flags之后只生效一次,之后恢复默认状态。
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
void ouch(int sig)
{
printf("OUCH!!!! - I got signal %d\n",sig);
}
int main()
{
struct sigaction act;
act.sa_handler = ouch;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGINT,&act,0);
while(1)
{
printf("Helllllllllllo\n");
sleep(1);
}
}
3.2 信号集sigset_t
头文件/usr/include/signal.h
中定义了类型sigset_t和用来处理信号集的函数。sigaction和其他函数将用这些信号集来修改进程在接收到信号时的行为。
#include <signal.h>
int sigaddset(sigset_t* set,int signo);
int sigemptyset(sigset_t* set);
int sigfillset(sigset_t* set);
int sigdelset(sigset_t* set,int signo);
sigaddset和sigdelset从信号集中添加或删除给定的信号signo。
sigfillset将信号集初始化为包含所有已定义的信号。
sigemptyset将信号集初始化为空。它们成功时返回0,失败时返回-1并设置errno。
#include <signal.h>
int sigismember(sigset_t* set,int signo);
int sigprocmask(int how,const sigset_t* set,sigset_t* oset);
sigismember判断信号signo是否为信号集set中的成员,如果是则返回1,否则返回0,信号无效返回-1并设置errno。
sigprocmask函数根据how指定的方法修改信号集set,把原先的信号集保存到oset中,set为空时只获取当前的信号集。成功时返回0,否则返回-1并设置errno。how取值:
#include <signal.h>
int sigpending(sigset_t* set);
int sigsuspend(const sigset_t* sigmask);
sigpending将被阻塞的信号中停留在待处理状态的一组信号写到set指向的信号集中,成功时返回0,否则返回-1并设置errno。一个信号被进程阻塞,它就不会传递给进程而停留在待处理状态,当程序需要处理信号,同时又要控制信号处理函数的调用时间,这个函数就很有用了。
sigsuspend函数在进程调用它时挂起进程的执行,直到信号集中的一个信号到达为止,类似pause函数。sigsuspend将进程屏蔽字替换为sigmask,然后挂起进程,直到信号处理函数执行完毕后才继续执行。如果接收到的信号终止了程序,sigsuspend就不会返回,若未终止则返回-1并设置errno。
进行下面实验,先关联信号SIGTSTP与函数func,然后设置信号屏蔽字SIGQUIT与SIGINT,之后挂起main函数,此时我们触发SIGQUIT和SIGINT信号是没有作用的,如下图所示,信号被阻塞待处理。然后我们触发SIGTSTP信号,跳转至func函数,在函数中获取了被阻塞的未处理信号。在func函数中没有结束程序,为什么最后一句输出没有打出???????被阻塞的信号起了作用???
#include <stdio.h>
#include <signal.h>
void func(int sig)
{
printf("I am sig process func,sig = %d\n",sig);
sigset_t set;
printf("try to get come but not processed signal\n");
sigpending(&set);
if(sigismember(&set,SIGINT))
printf("sig int is coming\n");
if(sigismember(&set,SIGQUIT))
printf("sig quit is coming\n");
}
int main()
{
struct sigaction act;
act.sa_handler = func;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
sigaction(SIGTSTP,&act,0);
sigset_t aaa;
sigemptyset(&aaa);
sigaddset(&aaa,SIGINT);
sigaddset(&aaa,SIGQUIT);
printf("main process is suspended\n");
sigsuspend(&aaa);
printf("main process is alive\n");
return 0;
}
上一篇: Linux中的进程信号
下一篇: Gradle自定义插件