信号
定义:
在计算机科学中,信号是Unix、类Unix以及其他POSIX兼容的操作系统中进程间通讯的一种有限制的方式。它是一种异步的通知机制,用来提醒进程一个事件已经发生。当一个信号发送给一个进程,操作系统中断了进程正常的控制流程,此时,任何非原子操作都将被中断。如果进程定义了信号的处理函数,那么它将被执行,否则就执行默认的处理函数。
信号的特点及状态
特点:
简单
携带的信息量少
使用在某个特定的场景中
状态:
产生
未决状态 - 没有被处理
递达 - 信号被处理了
信号列表:
进程收到信号的三种处理方式
1.默认处理:
忽略信号:内核将信号丢弃,信号没有对进程产生任何影响
终止进程:进程异常终止
产生核心转储文件,同时终止文件
停止进程:暂停进程的执行
恢复之前暂停的进程继续执行
2.忽略处理:
信号来了不做任何处理
注意:不能忽略SIGKILL和SIGSTOP,保护操作系统的方式,必须能够有强制性的手段杀死进程
3.捕获并处理:
信号来了捕获信号,并执行程序员自己写的程序
注意:不能捕获SIGKILL和SIGSTOP,原因同上
信号相关函数
kill
函数原型:
#include <sgnal.h>
int kill(pid_t pid, int sig);
参数:
pid:可能选择有以下四种
pid的取值 | 所代表的意义 |
pid>0 | 将此信号发送给进程ID为pid的进程 |
pid==0 | 将此信号发送给进程组ID和该进程相同的进程 |
pid<0 | 将此信号发送给进程组内进程ID为pid的进程 |
pd==-1 | 将此信号发送给系统所有的进程 |
sig:表示要发送的信号的编号,假如其值为0则没有任何信号送出,但是系统会执行错误检查,通常会利用sig值为0来检验某个进程是否仍在执行。
返回值:
成功返回0,失败返回-1
代码:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <signal.h>
int main(int argc, const char* argv[])
{
pid_t pid = fork();
if(pid == -1)
{
perror("fork error");
exit(1);
}
if(pid > 0)
{
while(1)
{
printf("parent process pid = %d\n", getpid());
sleep(1);
}
}
else if(pid == 0)
{
sleep(2);
// 杀死父亲
kill(getppid(), SIGKILL);
}
return 0;
}
raise
作用:
自己给自己发信号
函数原型:
int raise(int sig);
参数:
sig:表示要发送的信号的编号,假如其值为0则没有任何信号送出,但是系统会执行错误检查,通常会利用sig值为0来检验某个进程是否仍在执行。
返回值:
成功返回0,失败返回-1
代码:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>
int main(int argc, const char* argv[])
{
pid_t pid = fork();
if(pid > 0)
{
//父进程回收子进程资源
int s;
wait(&s);
if(WIFSIGNALED(s))
{
printf("term by signal: %d\n", WTERMSIG(s));
}
}
else if(pid == 0)
{
//自己给自己发信号
raise(SIGQUIT);
}
return 0;
}
abort
函数原型:
void abort(void);
说明:
给自己发送异常终止信号
没有参数没有返回值,永远不会调用失败
代码:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>
int main(int argc, const char* argv[])
{
pid_t pid = fork();
if(pid > 0)
{
//父进程回收子进程资源
int s;
wait(&s);
if(WIFSIGNALED(s))
{
printf("term by signal: %d\n", WTERMSIG(s));
}
}
else if(pid == 0)
{
//自己给自己发信号
while(1)
{
abort();
}
}
return 0;
}
alarm
函数原型:
unsigned int alarm(unsigned int seconds);
参数:
定时秒数
函数说明:
设置定时器(每个进程只有一个定时器)
使用的自然定时法,不受进程状态的影响,也就是说时间一直在走
当时间到达之后, 函数发出一个信号:SIGALRM
返回值:
如果在seconds秒内再次调用了alarm函数设置了新的闹钟,则后面定时器的设置将覆盖前面的设置,即之前设置的秒数被新的闹钟时间取代;当参数seconds为0时,之前设置的定时器闹钟将被取消,并将剩下的时间返回
代码:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
int main(int argc, const char* argv[])
{
int ret = alarm(5);
printf("ret = %d\n", ret);
sleep(2);
//重新设置定时器
ret = alarm(2);
printf("ret = %d\n", ret);
while(1)
{
printf("hello\n");
sleep(1);
}
return 0;
}
setitimer
函数原型:
int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue));
struct itimerval {
struct timeval it_interval;
struct timeval it_value;
};
struct timeval {
long tv_sec;
long tv_usec;
};
说明:
定时器,实现周期性定时
参数:
which为定时器类型,setitimer支持3种类型的定时器:
ITIMER_REAL: 以系统真实的时间来计算,它送出SIGALRM信号。
ITIMER_VIRTUAL: -以该进程在用户态下花费的时间来计算,它送出SIGVTALRM信号。
ITIMER_PROF: 以该进程在用户态下和内核态下所费的时间来计算,它送出SIGPROF信号。
it_interval指定间隔时间,it_value指定初始定时时间。如果只指定it_value,就是实现一次定时;如果同时指定 it_interval,则超时后,系统会重新初始化it_value为it_interval,实现重复定时;两者都清零,则会清除定时器。
tv_sec提供秒级精度,tv_usec提供微秒级精度,以值大的为先,注意1s = 1000000us。
ovalue用来保存先前的值,常设为NULL。
返回值:
和alarm函数类似
代码:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <sys/time.h>
#include <signal.h>
void myfunc(int sig)
{
printf("起床\n");
}
int main(int argc, const char* argv[])
{
//设置定时器
struct itimerval new_value;
//周期性定时
new_value.it_interval.tv_sec = 3;
new_value.it_interval.tv_usec = 0;
//第一次触发时间
new_value.it_value.tv_sec = 2;
new_value.it_value.tv_usec = 0;
signal(SIGALRM, myfunc);
//倒计时2s
setitimer(ITIMER_REAL, &new_value, NULL);
while(1)
{
}
return 0;
}
信号集
概念
未决信号集:
没有被当前进程处理的信号
阻塞信号集:
将某个信号放到阻塞信号集,这个信号就不会被进程处理
阻塞解除之后,信号被处理
未决信号集与阻塞信号集的关系
信号产生,信号处于未决状态,进程收到信号之后,信号被放入未决信号集
放入未决信号集中的信号等待处理,在处理之前需要做一些事情:判断阻塞信号集中该信号对应的标志位是否为1,如果为1,不处理,如果是0则处理该信号
当阻塞信号集中该信号对应的标志位为1时,该信号被处理
自定义信号集相关函数
int sigemptyset(sigset_t *set); 将set集合置空
int sigfillset(sigset_t *set); 将所有信号加入set集合
int sigaddset(sigset_t *set,int signo); 将signo信号加入到set集合
int sigdelset(sigset_t *set,int signo); 从set集合中移除signo信号
int sigismember(const sigset_t *set,int signo); 判断信号是否存在
sigpending函数
作用:
读取当前进程的未决信号集
函数原型:
int sigpending(sigset_t *set)
参数:
内核将未决信号集写入set
返回值:
函数调用成功返回0,否则返回-1;
sigprocmask函数
作用:
将自定义信号集设置给阻塞信号集
函数原型:
int sigprocmask(int how, t_t *set,sigset_t *oldset);
参数:
how:用于指定信号修改的方式,可能选择有三种
SIG_BLOCK//将set所指向的信号集中包含的信号加到当前的信号掩码中。即信号掩码和set信号集进行或操作。
SIG_UNBLOCK//将set所指向的信号集中包含的信号从当前的信号掩码中删除。即信号掩码和set进行与操作。
SIG_SETMASK //将set的值设定为新的进程信号掩码。即set对信号掩码进行了赋值操作。
set:为指向信号集的指针,在此专指新设的信号集,如果仅想读取现在的屏蔽值,可将其置为NULL。
oldset:也是指向信号集的指针,在此存放原来的信号集。可用来检测信号掩码中存在什么信号。
返回值:
成功执行时,返回0。失败返回-1,errno被设为EINVAL。
代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <sys/time.h>
#include <signal.h>
int main(int argc,const char* argv[])
{
//手动屏蔽信号
//自定义信号集集合
sigset_t myset;
//清空集合
sigemptyset(&myset);
//添加要阻塞的信号
sigaddset(&myset,SIGINT); //ctrl+c
sigaddset(&myset,SIGQUIT); //ctrl+反斜杠
sigaddset(&myset,SIGKILL);//反例,不能设置为阻塞的信号
//自定义集合数据设置给内核的阻塞信号集
sigprocmask(SIG_BLOCK,&myset,NULL);
//每隔1s读一次内存的未决信号集,并把未决信号集的值输出到屏幕
while(1)
{
sigset_t pendset;
//注意:读取的是未决信号集,不是阻塞信号集
sigpending(&pendset);
//1-31
for(int i=1;i<32;++i)
{
//对每一个信号一次判断
if(sigismember(&pendset,i))
{
printf("1");
}
else
{
printf("0");
}
}
printf("\n");
sleep(1);
}
return 0;
}
说明,开始时未决信号集中的各信号都被置位0,即默认不阻塞。但按下ctrl+c时,进程会从阻塞信号集中判断该信号对应的标志位是否为1,因为设置了,所以未决信号集中的该信号置为1。
信号捕捉函数
siganl
函数原型:
ypedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
参数:
第一个参数是要捕捉的信号
第二个参数表示我们要对信号进行的处理方式
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <signal.h>
void myfunc(int sig)
{
printf("cathc you signal: %d\n", sig);
}
int main(int argc, const char* argv[])
{
//ctrl+c
// 注册信号捕捉函数
signal(SIGINT, myfunc);
while(1)
{
printf("hello\n");
sleep(2);
}
return 0;
}
sigaction
函数原型:
int sigaction(int signum,
const struct sigaction *act,
struct sigaction *oldact
);
参数:
signum:要操作的信号。
act:要设置的对信号的新处理方式,指向sigaction结构的指针。
oldact:原来对信号的处理方式。一般为NULL
struct sigaction {
void (*sa_handler)(int);//和下面的sa_sigaction二者选一
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;//在信号处理函数执行过程中,临时屏蔽指定的信号
int sa_flags;//只需记住,若是sa_handler,则sa_flags必为0
};
返回值:
0 表示成功,-1 表示有错误发生。
代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <signal.h>
void myfunc(int sig)
{
printf("hello signal: %d\n", sig);
sleep(5);
printf("wake up .....\n");
}
int main(int argc, const char* argv[])
{
// 注册信号捕捉函数
struct sigaction act;
act.sa_flags = 0;
act.sa_handler = myfunc;
// 设置临时屏蔽的信号
sigemptyset(&act.sa_mask); // 0
// ctrl + 反斜杠
sigaddset(&act.sa_mask, SIGQUIT);
sigaction(SIGINT, &act, NULL);
while(1);
return 0;
}
说明:sigemptyset(&act.sa_mask)是设置临时屏蔽的信号,比如按了ctrl+c,此时进入信号处理函数myfunc,然后接着按下ctrl+\,本应该立马结束程序,但是ctrl+\被设置为临时屏蔽的信号,所以等到myfunc函数执行完,才结束了进程。
网络编程相关信号
SIGHUP
SIGHUP 信号在用户终端连接(正常或非正常)结束时发出, 通常是在终端的控制进程结束时, 通知同一session内的各个作业, 这时它们与控制终端不再关联. 系统对SIGHUP信号的默认处理是终止收到该信号的进程。所以若程序中没有捕捉该信号,当收到该信号时,进程就会退出。
SIGHUP会在以下3种情况下被发送给相应的进程:
1.终端连接中断时,SIGHUP 会被发送到控制进程,后者会在将这个信号转发给会话中所有的进程组之后自行了断;
2.session首进程退出时,该信号被发送到该session中的前台进程组中的每一个进程;
3.若父进程退出导致进程组成为孤儿进程组,且该进程组中有进程处于停止状态(收到SIGSTOP或SIGTSTP信号),该信号会被发送到该进程组中的每一个进程。
SIGPIPE
默认情况下,往一个读端关闭的管道或socket连接中写数据将引发SIGPIPE信号。我们需要在代码中捕获并处理该信号,或者至少忽略它,因为程序接受到SIGPIPE信号的默认行为是结束进程,而我们绝对不希望因为错误的写操作而导致程序退出。引发SIGPIPE信号的写操作将设置errno为EPIPE。
SIGURG
在Linux环境下,内核通知应用程序带外数据到达主要有两种方法:一种是I/O复用技术,select等系统调用在接收到带外数据时将返回,并向应用程序报告socket上的异常事件;另一种方法就是使用SIGURG信号。
上一篇: 信号
下一篇: 进程间通信(五)—信号