您的位置:首页 > 运维架构 > Linux

linux下的信号处理

2007-04-05 13:45 399 查看
linux下的信号处理
-本文来自网络
alarm(设置信号传送闹钟)
相关函数
signal,sleep
表头文件
#include <unistd.h>
定义函数
unsigned int alarm(unsigned int seconds);
函数说明
alarm()用来设置信号SIGALRM在经过参数seconds指定的秒数后传送给目前的进程。如果参数seconds 为0,则之前设置的闹钟会被取消,并将剩下的时间返回。
返回值
返回之前闹钟的剩余秒数,如果之前未设闹钟则返回0。
范例
#include <unistd.h>
#include <signal.h>
void handler() {
printf("hellon");
}
main()
{
int i;
signal(SIGALRM,handler);
alarm(5);
for(i=1;i<7;i++){
printf("sleep %d ...n",i);
sleep(1);
}
}
执行
sleep 1 ...
sleep 2 ...
sleep 3 ...
sleep 4 ...
sleep 5 ...
hello
sleep 6 ...
 

kill(传送信号给指定的进程)
相关函数
raise,signal
表头文件
#include <sys/types.h>
#include <signal.h>
定义函数
int kill(pid_t pid,int sig);
函数说明
kill()可以用来送参数sig指定的信号给参数pid指定的进程。参数pid有几种情况:
pid>0 将信号传给进程识别码为pid 的进程。
pid=0 将信号传给和目前进程相同进程组的所有进程
pid=-1 将信号广播传送给系统内所有的进程
pid<0 将信号传给进程组识别码为pid绝对值的所有进程
参数sig代表的信号编号可参考附录D
返回值
执行成功则返回0,如果有错误则返回-1。
错误代码
EINVAL 参数sig 不合法
ESRCH 参数pid 所指定的进程或进程组不存在
EPERM 权限不够无法传送信号给指定进程
范例
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
main()
{
pid_t pid;
int status;
if(!(pid= fork()))
{
printf("Hi I am child process!n");
sleep(10);
return;
}
else
{
printf("send signal to child process (%d) n",pid);
sleep(1);
kill(pid ,SIGABRT);
wait(&status);
if(WIFSIGNALED(status))
printf("chile process receive signal %dn",WTERMSIG(status));
}
}
执行
sen signal to child process(3170)
Hi I am child process!
child process receive signal 6
 

pause(让进程暂停直到信号出现)
相关函数
kill,signal,sleep
表头文件
#include <unistd.h>
定义函数
int pause(void);
函数说明
pause()会令目前的进程暂停(进入睡眠状态),直到被信号(signal)所中断。
返回值
只返回-1。
错误代码
EINTR 有信号到达中断了此函数。
 

sigaction(查询或设置信号处理方式)
相关函数
signal,sigprocmask,sigpending,sigsuspend
表头文件
#include <signal.h>
定义函数
int sigaction(int signum,const struct sigaction *act ,struct sigaction *oldact);
函数说明
sigaction()会依参数signum指定的信号编号来设置该信号的处理函数。参数signum可以指定SIGKILL和SIGSTOP以外的所有信号。
如参数结构sigaction定义如下
struct sigaction
{
void (*sa_handler) (int);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer) (void);
}
sa_handler此参数和signal()的参数handler相同,代表新的信号处理函数,其他意义请参考signal()。
sa_mask 用来设置在处理该信号时暂时将sa_mask 指定的信号搁置。
sa_restorer 此参数没有使用。
sa_flags 用来设置信号处理的其他相关操作,下列的数值可用。
OR 运算(|)组合
A_NOCLDSTOP : 如果参数signum为SIGCHLD,则当子进程暂停时并不会通知父进程
SA_ONESHOT/SA_RESETHAND:当调用新的信号处理函数前,将此信号处理方式改为系统预设的方式。
SA_RESTART:被信号中断的系统调用会自行重启
SA_NOMASK/SA_NODEFER:在处理此信号未结束前不理会此信号的再次到来。
如果参数oldact不是NULL指针,则原来的信号处理方式会由此结构sigaction 返回。
返回值
执行成功则返回0,如果有错误则返回-1。
错误代码
EINVAL 参数signum 不合法, 或是企图拦截SIGKILL/SIGSTOPSIGKILL信号
EFAULT 参数act,oldact指针地址无法存取。
EINTR 此调用被中断
范例
#include <unistd.h>
#include <signal.h>
void show_handler(struct sigaction * act)
{
switch (act->sa_flags)
{
case SIG_DFL:printf("Default actionn");break;
case SIG_IGN:printf("Ignore the signaln");break;
default: printf("0x%xn",act->sa_handler);
}
}
main()
{
int i;
struct sigaction act,oldact;
act.sa_handler = show_handler;
act.sa_flags = SA_ONESHOT|SA_NOMASK;
sigaction(SIGUSR1,&act,&oldact);
for(i=5;i<15;i++)
{
printf("sa_handler of signal %2d =".i);
sigaction(i,NULL,&oldact);
}
}
执行
sa_handler of signal 5 = Default action
sa_handler of signal 6= Default action
sa_handler of signal 7 = Default action
sa_handler of signal 8 = Default action
sa_handler of signal 9 = Default action
sa_handler of signal 10 = 0x8048400
sa_handler of signal 11 = Default action
sa_handler of signal 12 = Default action
sa_handler of signal 13 = Default action
sa_handler of signal 14 = Default action
 

sigaddset(增加一个信号至信号集)
相关函数
sigemptyset,sigfillset,sigdelset,sigismember
表头文件
#include <signal.h>
定义函数
int sigaddset(sigset_t *set,int signum);
函数说明
sigaddset()用来将参数signum 代表的信号加入至参数set 信号集里。
返回值
执行成功则返回0,如果有错误则返回-1。
错误代码
EFAULT 参数set指针地址无法存取
EINVAL 参数signum非合法的信号编号
 

sigdelset(从信号集里删除一个信号)
相关函数
sigemptyset,sigfillset,sigaddset,sigismember
表头文件
#include <signal.h>
定义函数
int sigdelset(sigset_t * set,int signum);
函数说明
sigdelset()用来将参数signum代表的信号从参数set信号集里删除。
返回值
执行成功则返回0,如果有错误则返回-1。
错误代码
EFAULT 参数set指针地址无法存取
EINVAL 参数signum非合法的信号编号
 

sigemptyset(初始化信号集)
相关函数
sigaddset,sigfillset,sigdelset,sigismember
表头文件
#include <signal.h>
定义函数
int sigemptyset(sigset_t *set);
函数说明
sigemptyset()用来将参数set信号集初始化并清空。
返回值
执行成功则返回0,如果有错误则返回-1。
错误代码
EFAULT 参数set指针地址无法存取
 

sigfillset(将所有信号加入至信号集)
相关函数
sigempty,sigaddset,sigdelset,sigismember
表头文件
#include <signal.h>
定义函数
int sigfillset(sigset_t * set);
函数说明
sigfillset()用来将参数set信号集初始化,然后把所有的信号加入到此信号集里。
返回值
执行成功则返回0,如果有错误则返回-1。
附加说明
EFAULT 参数set指针地址无法存取
 

sigismember(测试某个信号是否已加入至信号集里)
相关函数
sigemptyset,sigfillset,sigaddset,sigdelset
表头文件
#include <signal.h>
定义函数
int sigismember(const sigset_t *set,int signum);
函数说明
sigismember()用来测试参数signum 代表的信号是否已加入至参数set信号集里。如果信号集里已有该信号则返回1,否则返回0。
返回值
信号集已有该信号则返回1,没有则返回0。如果有错误则返回-1。
错误代码
EFAULT 参数set指针地址无法存取
EINVAL 参数signum 非合法的信号编号
 

signal(设置信号处理方式)
相关函数
sigaction,kill,raise
表头文件
#include <signal.h>
定义函数
void (*signal(int signum,void(* handler)(int)))(int);
函数说明
signal()会依参数signum 指定的信号编号来设置该信号的处理函数。当指定的信号到达时就会跳转到参数handler指定的函数执行。如果参数handler不是函数指针,则必须是下列两个常数之一:
SIG_IGN 忽略参数signum指定的信号。
SIG_DFL 将参数signum 指定的信号重设为核心预设的信号处理方式。
关于信号的编号和说明,请参考附录D
返回值
返回先前的信号处理函数指针,如果有错误则返回SIG_ERR(-1)。
附加说明
在信号发生跳转到自定的handler处理函数执行后,系统会自动将此处理函数换回原来系统预设的处理方式,如果要改变此操作请改用sigaction()。
范例
参考alarm()或raise()。
 

sigpending(查询被搁置的信号)
相关函数
signal,sigaction,sigprocmask,sigsuspend
表头文件
#include <signal.h>
定义函数
int sigpending(sigset_t *set);
函数说明
sigpending()会将被搁置的信号集合由参数set指针返回。
返回值执
行成功则返回0,如果有错误则返回-1。
错误代码
EFAULT 参数set指针地址无法存取
EINTR 此调用被中断。
 

sigprocmask(查询或设置信号遮罩)
相关函数
signal,sigaction,sigpending,sigsuspend
表头文件
#include <signal.h>
定义函数
int sigprocmask(int how,const sigset_t *set,sigset_t * oldset);
函数说明
sigprocmask()可以用来改变目前的信号遮罩,其操作依参数how来决定
SIG_BLOCK 新的信号遮罩由目前的信号遮罩和参数set 指定的信号遮罩作联集
SIG_UNBLOCK 将目前的信号遮罩删除掉参数set指定的信号遮罩
SIG_SETMASK 将目前的信号遮罩设成参数set指定的信号遮罩。
如果参数oldset不是NULL指针,那么目前的信号遮罩会由此指针返回。
返回值
执行成功则返回0,如果有错误则返回-1。
错误代码
EFAULT 参数set,oldset指针地址无法存取。
EINTR 此调用被中断
 

sleep(让进程暂停执行一段时间)
相关函数
signal,alarm
表头文件
#include <unistd.h>
定义函数
unsigned int sleep(unsigned int seconds);
函数说明
sleep()会令目前的进程暂停,直到达到参数seconds 所指定的时间,或是被信号所中断。
返回值
若进程暂停到参数seconds 所指定的时间则返回0,若有信号中断则返回剩余秒数。
 

ferror(检查文件流是否有错误发生)
相关函数
clearerr,perror
表头文件
#include <stdio.h>
定义函数
int ferror(FILE *stream);
函数说明
ferror()用来检查参数stream所指定的文件流是否发生了错误情况,如有错误发生则返回非0值。
返回值
如果文件流有错误发生则返回非0值。
 

perror(打印出错误原因信息字符串)
相关函数
strerror
表头文件
#include <stdio.h>
定义函数
void perror(const char *s);
函数说明
perror()用来将上一个函数发生错误的原因输出到标准错误(stderr)。参数s所指的字符串会先打印出,后面再加上错误原因字符串。此错误原因依照全局变量errno的值来决定要输出的字符串。
返回值

范例
#include <stdio.h>
main()
{
FILE *fp;
fp = fopen("/tmp/noexist","r+");
if(fp = =NULL) perror("fopen");
}
执行
$ ./perror
fopen : No such file or diretory
 

strerror(返回错误原因的描述字符串)
相关函数
perror
表头文件
#include <string.h>
定义函数
char * strerror(int errnum);
函数说明
strerror()用来依参数errnum的错误代码来查询其错误原因的描述字符串,然后将该字符串指针返回。
返回值
返回描述错误原因的字符串指针。
范例
/* 显示错误代码0 至9 的错误原因描述*/
#include <string.h>
main()
{
int i;
for(i=0;i<10;i++)
printf("%d : %sn",i,strerror(i));
}
执行
0 : Success
1 : Operation not permitted
2 : No such file or directory
3 : No such process
4 : Interrupted system call
5 : Input/output error
6 : Device not configured
7 : Argument list too long
8 : Exec format error
9 : Bad file descriptor
 

mkfifo(建立具名管道)
相关函数
pipe,popen,open,umask
表头文件
#include <sys/types.h>
#include <sys/stat.h>
定义函数
int mkfifo(const char * pathname,mode_t mode);
函数说明
mkfifo()会依参数pathname建立特殊的FIFO文件,该文件必须不存在,而参数mode为该文件的权限(mode%~umask),因此 umask值也会影响到FIFO文件的权限。Mkfifo()建立的FIFO文件其他进程都可以用读写一般文件的方式存取。当使用open()来打开 FIFO文件时,O_NONBLOCK旗标会有影响
1、当使用O_NONBLOCK 旗标时,打开FIFO 文件来读取的操作会立刻返回,但是若还没有其他进程打开FIFO 文件来读取,则写入的操作会返回ENXIO 错误代码。
2、没有使用O_NONBLOCK 旗标时,打开FIFO 来读取的操作会等到其他进程打开FIFO文件来写入才正常返回。同样地,打开FIFO文件来写入的操作会等到其他进程打开FIFO 文件来读取后才正常返回。
返回值
若成功则返回0,否则返回-1,错误原因存于errno中。
错误代码
EACCESS 参数pathname所指定的目录路径无可执行的权限
EEXIST 参数pathname所指定的文件已存在。
ENAMETOOLONG 参数pathname的路径名称太长。
ENOENT 参数pathname包含的目录不存在
ENOSPC 文件系统的剩余空间不足
ENOTDIR 参数pathname路径中的目录存在但却非真正的目录。
EROFS 参数pathname指定的文件存在于只读文件系统内。
范例
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
main()
{
char buffer[80];
int fd;
unlink(FIFO);
mkfifo(FIFO,0666);
if(fork()>0){
char s[ ] = "hello!n";
fd = open (FIFO,O_WRONLY);
write(fd,s,sizeof(s));
close(fd);
}
else{
fd= open(FIFO,O_RDONLY);
read(fd,buffer,80);
printf("%s",buffer);
close(fd);
}
}
执行
hello!
 

pclose(关闭管道I/O)
相关函数
popen
表头文件
#include <stdio.h>
定义函数
int pclose(FILE * stream);
函数说明
pclose()用来关闭由popen所建立的管道及文件指针。参数stream为先前由popen()所返回的文件指针。
返回值
返回子进程的结束状态。如果有错误则返回-1,错误原因存于errno中。
错误代码
ECHILD pclose()无法取得子进程的结束状态。
范例
参考popen()。
 

pipe(建立管道)
相关函数
mkfifo,popen,read,write,fork
表头文件
#include <unistd.h>
定义函数
int pipe(int filedes[2]);
函数说明
pipe()会建立管道,并将文件描述词由参数filedes数组返回。filedes[0]为管道里的读取端,filedes[1]则为管道的写入端。
返回值
若成功则返回零,否则返回-1,错误原因存于errno中。
错误代码
EMFILE 进程已用完文件描述词最大量。
ENFILE 系统已无文件描述词可用。
EFAULT 参数filedes数组地址不合法。
范例
/* 父进程借管道将字符串"hello!n"传给子进程并显示*/
#include <unistd.h>
main()
{
int filedes[2];
char buffer[80];
pipe(filedes);
if(fork()>0){
/* 父进程*/
char s[ ] = "hello!n";
write(filedes[1],s,sizeof(s));
}
else{
/*子进程*/
read(filedes[0],buffer,80);
printf("%s",buffer);
}
}
执行
hello!
 

popen(建立管道I/O)
相关函数
pipe,mkfifo,pclose,fork,system,fopen
表头文件
#include <stdio.h>
定义函数
FILE * popen( const char * command,const char * type);
函数说明
popen()会调用fork()产生子进程,然后从子进程中调用/bin/sh -c来执行参数command的指令。参数type可使用"r"代表读取,"w"代表写入。依照此type值,popen()会建立管道连到子进程的标准输出设备或标准输入设备,然后返回一个文件指针。随后进程便可利用此文件指针来读取子进程的输出设备或是写入到子进程的标准输入设备中。此外,所有使用文件指针(FILE*)操作的函数也都可以使用,除了fclose()以外。
返回值
若成功则返回文件指针,否则返回NULL,错误原因存于errno中。
错误代码
EINVAL参数type不合法。
注意事项
在编写具SUID/SGID权限的程序时请尽量避免使用popen(),popen()会继承环境变量,通过环境变量可能会造成系统安全的问题。
范例
#include <stdio.h>
main()
{
FILE * fp;
char buffer[80];
fp=popen("cat /etc/passwd","r");
fgets(buffer,sizeof(buffer),fp);
printf("%s",buffer);
pclose(fp);
}
执行
root :x:0 0: root: /root: /bin/bash
 

1.信号的产生
Linux下的信号可以类比于DOS下的INT或者是Windows下的事件。在有一个信号发生时候相信的信号就会发送给相应的进程。在Linux下的信号有以下几个。 我们使用 kill -l 命令可以得到以下的输出结果:
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL
5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE
9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2
13) SIGPIPE 14) SIGALRM 15) SIGTERM 17) SIGCHLD
18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN
22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO
30) SIGPWR
关于这些信号的详细解释请查看man 7 signal的输出结果。信号事件的发生有两个来源:一个是硬件的原因(比如我们按下了键盘),一个是软件的原因(比如我们使用系统函数或者是命令发出信号)。最常用的四个发出信号的系统函数是kill, raise, alarm和setitimer函数。 setitimer函数我们在计时器的使用那一章再学习。
#include
#include
#include
int kill(pid_t pid,int sig);
int raise(int sig);
unisigned int alarm(unsigned int seconds);
kill系统调用负责向进程发送信号sig。
如果pid是正数,那么向信号sig被发送到进程pid。
如果pid等于0,那么信号sig被发送到所以和pid进程在同一个进程组的进程
如果pid等于-1,那么信号发给所有的进程表中的进程,除了最大的哪个进程号。
如果pid由于-1,和0一样,只是发送进程组是-pid。
我们用最多的是第一个情况。还记得我们在守护进程那一节的例子吗?我们那个时候用这个函数杀死了父进程守护进程的创建
raise系统调用向自己发送一个sig信号。我们可以用上面那个函数来实现这个功能的。
alarm函数和时间有点关系了,这个函数可以在seconds秒后向自己发送一个SIGALRM信号。 下面这个函数会有什么结果呢?
#include
main()
{
unsigned int i;
alarm(1);
for(i=0;1;i++)
printf("I=%d",i);
}
SIGALRM的缺省操作是结束进程,所以程序在1秒之后结束,你可以看看你的最后I值为多少,来比较一下大家的系统性能差异(我的是2232)。
2.信号操作
有时候我们希望进程正确的执行,而不想进程受到信号的影响,比如我们希望上面那个程序在1秒钟之后不结束。这个时候我们就要进行信号的操作了。
信号操作最常用的方法是信号屏蔽。信号屏蔽要用到下面的几个函数。
#include
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set,int signo);
int sigdelset(sigset_t *set,int signo);
int sigismember(sigset_t *set,int signo);
int sigprocmask(int how,const sigset_t *set,sigset_t *oset);
sigemptyset 函数初始化信号集合set,将set设置为空。sigfillset也初始化信号集合,只是将信号集合设置为所有信号的集合。 sigaddset将信号signo加入到信号集合之中,sigdelset将信号从信号集合中删除。sigismember查询信号是否在信号集合之中。
sigprocmask是最为关键的一个函数。在使用之前要先设置好信号集合set。这个函数的作用是将指定的信号集合set加入到进程的信号阻塞集合之中去,如果提供了oset那么当前的进程信号阻塞集合将会保存在oset里面。参数how决定函数的操作方式。
SIG_BLOCK:增加一个信号集合到当前进程的阻塞集合之中。
SIG_UNBLOCK:从当前的阻塞集合之中删除一个信号集合。
SIG_SETMASK:将当前的信号集合设置为信号阻塞集合。
以一个实例来解释使用这几个函数。
#include
#include
#include
#include
int main(int argc,char **argv)
{
double y;
sigset_t intmask;
int i,repeat_factor;
if(argc!=2)
{
fprintf(stderr,"Usage:%s repeat_factorna",argv[0]);
exit(1);
}
if((repeat_factor=atoi(argv[1]))<1)repeat_factor=10;
sigemptyset(&intmask);/* 将信号集合设置为空 */
sigaddset(&intmask,SIGINT);/* 加入中断 Ctrl+C 信号*/
while(1)
{
/*阻塞信号,我们不希望保存原来的集合所以参数为NULL*/
sigprocmask(SIG_BLOCK,&intmask,NULL);
fprintf(stderr,"SIGINT signal blockedn");
for(i=0;i
fprintf(stderr,"Blocked calculation is finishedn");
/* 取消阻塞 */
sigprocmask(SIG_UNBLOCK,&intmask,NULL);
fprintf(stderr,"SIGINT signal unblockedn");
for(i=0;i
fprintf(stderr,"Unblocked calculation is finishedn");
}
exit(0);
}
程序在运行的时候我们要使用Ctrl+C来结束。如果我们在第一计算的时候发出SIGINT信号,由于信号已经屏蔽了,所以程序没有反映。只有到信号被取消阻塞的时候程序才会结束。注意我们只要发出一次SIGINT信号就可以了,因为信号屏蔽只是将信号加入到信号阻塞集合之中,并没有丢弃这个信号。一旦信号屏蔽取消了,这个信号就会发生作用。
有时候我们希望对信号作出及时的反映的,比如当拥护按下Ctrl+C时,我们不想什么事情也不做,我们想告诉用户你的这个操作不好,请不要重试,而不是什么反映也没有的。 这个时候我们要用到sigaction函数。
#include
int sigaction(int signo,const struct sigaction *act,
struct sigaction *oact);
struct sigaction {
void (*sa_handler)(int signo);
void (*sa_sigaction)(int siginfo_t *info,void *act);
sigset_t sa_mask;
int sa_flags;
void (*sa_restore)(void);
}
这个函数和结构看起来是不是有点恐怖呢。不要被这个吓着了,其实这个函数的使用相当简单的。我们先解释一下各个参数的含义。 signo很简单就是我们要处理的信号了,可以是任何的合法的信号。有两个信号不能够使用(SIGKILL和SIGSTOP)。 act包含我们要对这个信号进行如何处理的信息。oact更简单了就是以前对这个函数的处理信息了,主要用来保存信息的,一般用NULL就OK了。
信号结构有点复杂。不要紧我们慢慢的学习。
sa_handler是一个函数型指针,这个指针指向一个函数,这个函数有一个参数。这个函数就是我们要进行的信号操作的函数。 sa_sigaction,sa_restore和sa_handler差不多的,只是参数不同罢了。这两个元素我们很少使用,就不管了。
sa_flags用来设置信号操作的各个情况。一般设置为0好了。sa_mask我们已经学习过了
在使用的时候我们用sa_handler指向我们的一个信号操作函数,就可以了。sa_handler有两个特殊的值:SIG_DEL和SIG_IGN。SIG_DEL是使用缺省的信号操作函数,而SIG_IGN是使用忽略该信号的操作函数。
这个函数复杂,我们使用一个实例来说明。下面这个函数可以捕捉用户的CTRL+C信号。并输出一个提示语句。
#include
#include
#include
#include
#include
#define PROMPT "你想终止程序吗?"
char *prompt=PROMPT;
void ctrl_c_op(int signo)
{
write(STDERR_FILENO,prompt,strlen(prompt));
}
int main()
{
struct sigaction act;
act.sa_handler=ctrl_c_op;
sigemptyset(&act.sa_mask);
act.sa_flags=0;
if(sigaction(SIGINT,&act,NULL)<0)
{
fprintf(stderr,"Install Signal Action Error:%sna",strerror(errno));
exit(1);
}
while(1);
}
在上面程序的信号操作函数之中,我们使用了write函数而没有使用fprintf函数。是因为我们要考虑到下面这种情况。如果我们在信号操作的时候又有一个信号发生,那么程序该如何运行呢? 为了处理在信号处理函数运行的时候信号的发生,我们需要设置sa_mask成员。我们将我们要屏蔽的信号添加到sa_mask结构当中去,这样这些函数在信号处理的时候就会被屏蔽掉的。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: