文章目录
使用环境:Ubuntu18.04
使用工具:VMWare workstations ,xshell
作者在学习Linux的过程中对常用的命令进行记录,通过思维导图的方式梳理知识点,并且通过xshell连接vmware中ubuntu虚拟机进行操作,并将练习的截图注解,每句话对应相应的命令,读者可以无障碍跟练。
第九次练习的重点在于Linux的进程如何使用 信号进行进程通信。
注意:本篇所说的 信号要和 信号量有所区别。信号量是进程通信之间处理同步互斥的机制,负责协调线程进程,保证临界资源的合理性。而信号是一冲处理异步事件的方式,用于通知进程某事件的发生。
1 信号概念
信号是进程在运行过程中,由自身产生或由进程外部发过来的消息。信号是硬件中断的软件模拟(软中断)。每个信号用一个整型常量宏表示,以 SIG 开头,比如 SIGCHLD、SIGINT 等,它们在系统头文件<signal.h>中定义,也可以通过在 shell 下键入 kill –l 查看信号列表,或者键入 man 7 signal 查看更详细的说明。
信号的生成来自内核,让内核生成信号的请求来自 3 个地方:
- 用户:用户能够通过输入CTRL+C、CTRL+Z,或者终端驱动程序分配给信号控制字符的其他任何键位请求内核产生信号。
- 内核:当程序出错时,内核会给进程才能发送一个信号,如非法段存取、浮点数溢出等。
- 进程:一个进程可以通过系统调用kill给另一个进程发送信号,一个进程可以通过信号给另一个进程进行通信。
由进程的某个操作产生的信号称为同步信号(synchronous signals),例如除 0;由像用户击键这样的进程外部事件产生的信号叫做异步信号(asynchronous signals)。
进程接收到信号后,一般会做如下三种处理:
- 接收默认处理:接收默认处理的进程通常会导致进程本身消亡。例如连接到终端的进程,用户按下 CTRL+c,将导致内核向进程发送一个 SIGINT 的信号,进程如果不对该信号做特殊的处理,系统将采用默认的方式处理该信号,即终止进程的执行signal(SIGINT, SIG_DFL);
- 忽略信号:进程可以通过代码,显示地忽略某个信号的处理。例如:**signal(SIGINT,SIG_IGN);**但是某些信号是不能被忽略的,例如 9号信号。
- 捕捉信号并处理:进程可以事先注册信号处理函数,当接收到信号时,由信号处理函数自动捕捉并处理信号。
**注意:**有两个信号既不能被忽略也不能被捕捉,它们是 SIGKILL 和 SIGSTOP。即进程接收到这两个信号后,只能接受系统的默认处理,即终止进程。SIGKILL是终止进程。SIGSTOP 是暂停进程。
2 signal信号处理机制
可以使用函数signal注册一个信号捕捉函数:
#include <signal.h>
typedef void (*sighandler_t)(int); //函数指针
sighandler_t signal(int signum, sighandler_t handler);
- signal的第一个参数signum表示要捕捉的信号,第二个参数是函数指针,表示要对该信号进行捕捉的函数,该参数可以是SIG_DEL(表示交给系统缺省处理)或SIG_IGN(表示忽略该信号不做任何处理)。signal函数如果调用成功,返回该信号的处理函数的地址,否则返回SIG_ERR。
- sighandler_t是信号捕捉函数,由signal注册,注册疑惑,在整个进程运行过程中均有效,并且对不同的信号可以注册同一个信号捕捉函数。该函数只有一个整型参数,代表信号值。
示例 2-1:捕捉终端 CTRL+c 产生的 SIGINT 信号
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <signal.h>
void SignHandler(int iSignNo)
{
printf("捕捉到的信号为:%d\n",iSignNo);
}
int main()
{
signal(SIGINT,SignHandler);
while(1)
sleep(1);
return 0;
}
该程序运行后,通过按CTRL+c将不会终止程序的运行(另一个xshell终端使用kill命令效果一样),因为CTRL+c产生的SIGINT信号已经由进程中注册的SignHandler函数捕捉了。该程序可以通过Ctrl+**终止,因为Ctrl+**能够产生SIGQUIT信号,而该信号的捕捉函数尚未在程序中注册。
示例 2-2:忽略掉CTRL+c产生的SIG_INT
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <signal.h>
int main()
{
signal(SIGINT,SIG_IGN);
while(1)
sleep(1);
return 0;
}
该程序运行起来以后,将 CTRL+C 产生的 SIGINT 信号忽略掉了,所以 CTRL+C 将不能使该进程终止,要终止该进程,可以向进程发送 SIGQUIT 信号,即组合键 CTRL+\。或者另外开一个端口,然后执行 ps –aux 查看进程,发现该进程号之后用 kill -9 进程号杀掉该进程。
示例 2-3:接受信号的默认处理,接受默认处理就相当于没有写信号处理程序
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <signal.h>
int main()
{
signal(SIGINT,SIG_DFL);
while(1)
sleep(1);
return 0;
}
SIGINT信号就是终止进程的信号,所以采用默认处理后系统将正常关闭该进程。
3 sigaction 信号处理机制
3.1 信号处理的多种情况
- 注册一个信号处理函数,并且处理完一个信号后,不需要再重新注册,就能继续捕捉下一个信号。
- 如果信号处理函数正在处理信号,并且还没有处理完毕,后续发生相同的信号会挨着执行(再执行一次)。
- 如果信号处理函数正在处理信号,并且还没有处理完毕时,又发生了一个不同类型的信号,跳转去执行另一个信号,之后在回过来执行没有处理完的信号。类似于中断机制。
- 如果程序在系统调用时阻塞(如read调用),此时发生了一个信号,就会让系统调用返回错误,再进入信号处理函数,还是先跳转到信号处理函数,等信号处理完毕后,系统调用会重启该系统调用。
示例 3-1-1:信号互相打断就是执行一次,同一个信号处理函数不重入
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<signal.h>
#include<error.h>
int g_iSeq = 0; //计数器,记录信号处理次数
//信号捕捉函数
void SignHandler(int iSignNo)
{
int iSeq = g_iSeq++;
printf("第%d次信号%d进入信号处理函数\n",iSeq,iSignNo);
sleep(3);
printf("第%d次信号%d退出信号处理函数\n",iSeq,iSignNo);
}
int main()
{
char szBuf[8];
int iRet;
//注册两种不同的信号,根据输入处理
signal(SIGINT,SignHandler);
signal(SIGQUIT,SignHandler);
do{
iRet = read(STDIN_FILENO,szBuf,sizeof(szBuf)-1);//系统调用read
if(iRet<0){
perror("read fail");
break;
}
szBuf[iRet] = 0;
printf("read读到的字符:%s\n",szBuf);
}while(strcmp(szBuf,"quit\n")!=0);
return 0;
}
程序运行时,针对于如下几种输入情况(要输入得快一些),看输出结果:
1、 [CTRL+c] [CTRL+c] (一个一个挨着执行)
2、 [CTRL+c] [CTRL+\] (先执行 c 的进入,被\打断,转而执行\,最后执行 c 的退出)
3、 hello [CTRL+\] [Enter] (先执行中断,没有任何输出)
4、 [CTRL+\] hello [Enter] (先执行中断,输出内容)
5、 hel [CTRL+\] lo[Enter] (先执行中断,只输出 lo)
运行结果及分析:
3.2 sigaction 信号处理注册
函数原型:
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
sigaction也是一个用于注册一个信号的处理函数,函数调用成功返回0,失败返回-1。
- 参数signum为需要捕捉的信号
- 参数cat是一个sigaction的结构体,里面包含信号处理函数、处理方式等信息
- 参数oldact是一个传出参数,sigaction函数调用成功后,oldact里面包含以前对signum的处理方式的信息,通常为NULL
结构体 struct sigaction(注意名称与函数 sigaction 相同)的原型为:
struct sigaction {
void (*sa_handler)(int); //老类型的信号处理函数指针
void (*sa_sigaction)(int, siginfo_t *, void *);//新类型的信号处理函数指针
sigset_t sa_mask; //将要被阻塞的信号集合
int sa_flags; //信号处理方式掩码 ( SA_SIGINFO )
void (*sa_restorer)(void); //保留,不要使用
};
- 字段sa_handler是一个函数指针,用于指向void handler(int)的信号函数处理地址,如果将sa_flags=0,就等同于signal函数了
- 字段sa_sigaction也是一个函数指针。用于指向原型为:
void handler(int iSignNum, siginfo_t *pSignInfo, void *pReserved
的信号处理函数,即新类型的信号处理函数,三个参数的含义为:
iSignNUm:传入的信号
pSignInfo:与该信号相关的一些信息,它是一个结构体
pReserved:保留,通常为NULL - 字段sa_handler和sa_sigaction只应该有一个生效,如果想采用旧的signal函数信号处理机制,就应该让sa_handler指向正确的信号处理函数,并将字段sa_flags置为0;如果采用新的函数处理机制,应该让sa_sigaction指向正确的信号处理函数,并且让字段sa_flags包含SA_SIGINFO选项。
- 字段sa_mask是一个包含信号集合的结构体,该结构体内的信号表示在处理信号时,将要被阻塞的信号。针对sigset_t结构体,有一组专门的函数对它进行处理:
#include <signal.h>
int sigemptyset(sigset_t *set); //清空信号集合 set
int sigfillset(sigset_t *set); //将所有信号填充进 set 中
int sigaddset(sigset_t *set, int signum); //往 set 中添加信号 signum
int sigdelset(sigset_t *set, int signum); //从 set 中移除信号 signum
int sigismember(const sigset_t *set, int signum); //判断 signum 是否包含在 set 中(是:返回 1,否:0)
int sigpending(sigset_t *set); //将被阻塞的信号集合由参数 set 指针返回(挂起信号)
其中对于函数sigismumer,如果signum在set集合中,则返回1,不在返回0,出错返回-1,其他的函数都是成功返回0,失败返回-1。
例如:在处理信号后SIGINT时,只阻塞SIGQUIT信号的处理,可以如下操作:
struct sigaction act;
act.sa_flags = SA_SIGINFO; //设置使用sigaction指向的函数
act.sa_sigaction = newHandler; //设置信号处理函数
sigemptyset(&act.sa_mask); //清空信号集合
sigaddset(&act.sa_mask, SIGQUIT); //将SIGQUIT加入信号集合
sigaction(SIGINT,&act,NULL); //设置SIGINT为捕捉信号,将包含处理信号方式的act结构体作为参数
- 字段sa_flag是一组掩码的合成值,表示信号处理时应该采取的一些行为,如下:
掩码 | 描述 |
---|---|
SA_RESETHAND | 处理完毕要捕捉的信号后,将自动撤消信号处理函数的注册,即必须再重新注册信号处理函数,才能继续处理接下来产生的信号。该选项不符合一般的信号处理流程,现已经被废弃。 |
SA_NODEFER | 在处理信号时,如果又发生了其它的信号,则立即进入其它信号的处理,等其它信号处理完毕后,再继续处理当前的信号,即递规地处理。(不常用)不断重入,次数不丢失 |
SA_RESTART | 如果在发生信号时,程序正阻塞在某个系统调用,例如调用read()函数,则在处理完毕信号后,接着从阻塞的系统返回。如果不指定该参数,中断处理完毕之后,read 函数读取失败。 |
SA_SIGINFO | 指示结构体的信号处理函数指针是哪个有效,如果 sa_flags 包含该掩码,则 sa_sigaction 指针有效,否则是 sa_handler 指针有效。(常用) |
示例 3-2-1:用函数sigaction实现和signal函数一样的功能
#include<signal.h>
#include<stdio.h>
void handle(int signo)
{
printf("捕获%d号信号\n",signo);
}
int main()
{
struct sigaction st;
st.sa_handler = handle; //使用旧函数处理信号
st.sa_flags = 0; //使得sigaction等同于signal函数
sigaction(SIGINT,&st,NULL);
while(1)
{
sleep(1);
}
return 0;
}
示例 3-2-2:用sigaction实现调用新的信号处理函数
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<signal.h>
int g_iSeq = 0;
void SignHandlerNew(int iSignNo,siginfo_t* pInfo,void* pReserved)
{
int iSeq = ++g_iSeq;
printf("第%d次信号%d进入信号处理函数\n",iSeq,iSignNo);
sleep(3);
printf("第%d次信号%d退出信号处理函数\n",iSeq,iSignNo);
}
int main()
{
struct sigaction act; //定义结构体
act.sa_sigaction = SignHandlerNew; //指定信号处理函数
act.sa_flags = SA_SIGINFO; //表示使用sa_sigaction处理信号
sigaction(SIGINT,&act,NULL);
sigaction(SIGQUIT,&act,NULL);
while(1){
sleep(1);
}
return 0;
}
示例 3-2-3:对于之前signal的五种信号输入情况,添加代码使其实现如下响应:
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<signal.h>
int g_iSeq = 0;
void SignHandlerNew(int iSignNo,siginfo_t* pInfo,void* pReserved)
{
int iSeq = ++g_iSeq;
printf("第%d次信号%d进入信号处理函数\n",iSeq,iSignNo);
sleep(3);
printf("第%d次信号%d退出信号处理函数\n",iSeq,iSignNo);
}
int main()
{
char szBuf[8] = {0};
int iRet = 0;
struct sigaction act;
act.sa_sigaction = SignHandlerNew; //设置信号处理函数
act.sa_flags = SA_SIGINFO|SA_RESTART; //使sa_sigaction生效,并且如果系统调用被信号打断,执行信号后,重新进行系统调用
sigemptyset(&act.sa_mask); //清空信号集
sigaddset(&act.sa_mask,SIGQUIT); //将SIGQUIT添加信号集
sigaction(SIGINT,&act,NULL); //设置捕捉信号为SIGINT
do{
iRet = read(STDIN_FILENO,szBuf,sizeof(szBuf)-1);
if(iRet<0){
perror("read fail");
break;
}
szBuf[iRet] = 0;
printf("从标准输入中读取字符为:%s\n",szBuf);
}while(strcmp(szBuf,"quit\n")!=0); //szBuf 为 quit\n 时退出循环
return 0;
}
3.3 sigprocmask信号阻塞函数
函数sigaction中设置的被阻塞信号的集合只是针对于要处理的信号,如
struct sigaction act;
sigemptyset(&act.sa_mask);
sigaddset(&act.sa_mask,SIGQUIT);
sigaction(SIGINT,&act,NULL);
上述代码表示,只有在处理信号SIGINT时,才会阻塞信号SIGQUIT。
而函数sigprocmask与sigaction不同,是全程阻塞,在该函数中设置了阻塞集合后,被阻塞的信号将不能再被信号处理函数捕捉,直到重新设置阻塞信号集合。
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
-
参数how的值为如下三种之一:
a:SIG_BLOCK,将参数2的信号集合添加到进程原有的阻塞信号集合中
b:SIG_UNBLOCK,从进程原有的阻塞信号集合移除参数2中包含的信号
c:SIG_SETMASK,重新设置进程的阻塞信号集合为参数2的信号集 -
set为阻塞信号集
-
oldset是传出参数,存放进程原有的信号集,通常设为NULL
示例 3-3-1:使用sigprocmask实现全程阻塞(屏蔽)功能
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<signal.h>
int g_iSeq = 0;
void SignHandlerNew(int iSignNo,siginfo_t* pInfo,void* pReserved)
{
int iSeq = ++g_iSeq;
printf("第%d次信号%d进入信号处理函数\n",iSeq,iSignNo);
sleep(3);
printf("第%d次信号%d退出信号处理函数\n",iSeq,iSignNo);
}
int main()
{
char szBuf[8];
int iRet;
//屏蔽SIGQUIT信号
sigset_t sigSet; //设置信号集合
sigemptyset(&sigSet); //清空集合
sigaddset(&sigSet,SIGQUIT); //将SIGQUIT添加到集合中
sigprocmask(SIG_BLOCK,&sigSet,NULL); //将命令集合添加到屏蔽集合
//此处注释就不打了,上面写了好几遍了,熟能生巧
struct sigaction act;
act.sa_sigaction = SignHandlerNew;
act.sa_flags = SA_SIGINFO;
sigemptyset(&act.sa_mask); //清空act结构体中将要被阻塞的集合
sigaction(SIGINT,&act,NULL);//设置捕捉信号为SIGINT
while(1){
sleep(1);
}
return 0;
}
示例 3-3-2:取消指定信号的全程阻塞
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<signal.h>
int g_iSeq = 0;
void SignHandlerNew(int iSignNo,siginfo_t* pInfo,void* pReserved)
{
int iSeq = ++g_iSeq;
printf("第%d次信号%d进入信号处理函数\n",iSeq,iSignNo);
sleep(3);
printf("第%d次信号%d退出信号处理函数\n",iSeq,iSignNo);
}
int main()
{
//屏蔽掉SIGINT和SIGQUIT信号,使信号捕捉函数无法再捕捉这两个信号
sigset_t sigSet;
sigemptyset(&sigSet);
sigaddset(&sigSet,SIGINT);
sigaddset(&sigset,SIGQUIT);
siprocmask(SIG_BLOCK,&sigSet,NULL); //将SIGINT和SIGQUIT两个信号所在的信号集,加入屏蔽集合
struct sigaction act;
act.sa_sigaction=SignHandlerNew;
act.sa_flags = SA_SIGINFO;
sigempty(&act.sa_mask);
sigaction(SIGINT,&act,NULL);
sigaction(SIGQUIT,&act,NULL);
int iCount = 0;
whilt(1){
if(iCount >3 ){
sigset_t sigSet2;
sigemptyset(&sigSet2);
sigaddset(&sigSet2,SIGINT);
sigprocmask(SIG_UNBLOCK,&sigSet2,NULL); //解除对SIGINT的屏蔽
}
iCount++;
sleep(2);
}
return 0;
}
示例 3-3-3:使用sigpending测试信号,采用下图的模型
#include<signal.h>
#include<unistd.h>
#include<stdio.h>
void handler(int signum,siginfo_t* pInfo,void* pReversed)
{
printf("收到了信号%d\n",signum);
}
int main()
{
sigset_t new_mask,old_mask,pending_mask;
sigemptyset(&new_mask);
sigaddset(&new_mask,SIGINT);
if(sigprocmask(SIG_BLOCK,&new_mask,&old_mask)){ //将SIGINT信号加入阻塞,并将旧的阻塞集合返回给old_mask
printf("阻塞SIGINT信号失败\n");
}
struct sigaction act;
sigemptyset(&act.sa_mask);
act.sa_flags = SA_SIGINFO;
act.sa_sigaction = handler;
if(sigaction(SIGINT,&act,NULL)){
printf("注册SIGINT信号失败\n");
}
sleep(10);
printf("现在测试屏蔽和非阻塞信号SIGINT\n");
if(sigpending(&pending_mask)<0){ //将阻塞信号全部添加到pending_mask信号集中并返回,用于测试
printf("获取挂起的屏蔽失败\n");
}
if(sigismember(&pending_mask, SIGINT)) { //判断SIGINT信号是否在测试的屏蔽集合中
printf("信号SIGINT不在屏蔽集合中\n");
}
if(sigprocmask(SIG_SETMASK, &old_mask, NULL) < 0){ //将旧的阻塞信号集设为信号集,即复原操作
printf("信号不阻塞失败\n");
}
printf("信号已经不阻塞了\n");
sleep(10);
return 0;
}
运行效果:
4 用程序发送信号
4.1 kill命令发送信号
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid,int sig);
- 参数pid为将要接收信号的进程id,可以通过getpid()函数获得来给自身发送信号,还可以发送信号给指定的进程,此时pid有如下描述:
pid取值 | 含义 |
---|---|
pid > 0 | 将信号分给pid的进程 |
pid == 0 | 将信号发给与发送进程属于同一个组的所有进程 |
pid < 0 | 将信号发送给进程组Id等于pid绝对值的所有进程 |
pid == -1 | 将信号发送给该进程有权限发送的系统里的所有进程 |
- 参数sig为要发送的信号
函数执行成功返回0,否则返回-1
示例 4-1:输入内容结束后,通过发送信号SIGQUIT把自己的进程杀掉
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{
while(1){
if(getchar() == EOF) //输入后,按下CTRL+d(EOF)即可关闭进程
kill(getpid(),SIGQUIT);
}
return 0;
}
5 计时器和信号
5.1 睡眠函数
#include <unistd.h>
unsigned int sleep(unsigned int seconds);
void usleep(unsigned long usec);
函数sleep让进程睡眠seconds秒,函数usleep让进程睡眠usec微秒
sleep睡眠函数内部是用信号机制进行处理的,用到的函数有:
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
//告知自身进程,要进程在 seconds 秒后自动产生一个 SIGALRM 的信号
int pause(void); //将自身进程挂起,直到有信号发生时才从 pause 返回
示例 5-1:模拟睡眠3秒
#include<signal.h>
#include<stdio.h>
#include<unistd.h>
void SignHandler(int iSignNo)
{
printf("捕捉信号%d\n",iSignNo);
}
int main()
{
signal(SIGALRM,SignHandler);
alarm(3); //等待3秒后,自动产生SIGALRM信号
printf("暂停之前\n");
pause(); //将进程挂起,有信号发生才会退出挂起状态
printf("暂停之后\n");
return 0;
}
注意: sleep的内部使用alarm实现,所以在程序中最好不要用sleep和alarm混用,以免造成混乱。
5.2 时钟处理
linux为每个进程维护3个计时器,分别是真实计时器、虚拟计时器和使用计时器。
- 真实计时器计算的是程序运行的实际时间。——直接
- 虚拟计时器计算的是程序运行在用户态所消耗的时间。——需要了解内核
- 实用计时器计算的是程序处于用户态和处于内核态所消耗的时间之和。——常用
例如:有一程序运行,在用户态运行了 5 秒,在内核态运行了 6 秒,还睡眠了 7 秒,则真实计算器计算的结果是 18 秒,虚拟计时器计算的是 5 秒,实用计时器计算的是 11 秒。
用指定的初始间隔和重复间隔时间为进程设定好一个计时器后,该计时器就会定时地向进程发送时钟信号。3 个计时器发送的时钟信号分别为:SIGALRM,SIGVTALRM 和 SIGPROF。
//头文件
#include<sys/time.h>
- 函数
int getitimer(int which, struct itimerval *value);
用于获取计时器的设置,调用成功返回0,否则返回-1。
参数which指定计时器,可填写ITIMER_REAL(真实计时器)、ITIMER_VIRTUAL(虚拟计时器、ITIMER_PROF(实用计时器)
参数value为一结构体的传出参数,用于传出该计时器的初始间隔和重复间隔时间
- 函数
int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue);
用于设置计时器,调用成功返回0,否则返回-1。
参数which、value同上
参数ovalue为一结构体传出参数,用于传出以前的计时器时间设置
struct itimerval {
struct timeval it_interval; /* next value */ //重复间隔
struct timeval it_value; /* current value */ //初始间隔
};
struct timeval {
long tv_sec; /* seconds */ //时间的秒数部分
long tv_usec; /* microseconds */ //时间的微秒部分
};
示例 5-2:启用真实计时器进行时钟处理,获取当前系统时间,并且一秒钟更新一次
#include<signal.h>
#include<sys/time.h>
#include<time.h>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<error.h>
void sigHandler(int iSigNum)
{
time_t tt; //定义一个时间类型的变量
time(&tt); //获取当前系统时间
struct tm* pTm = gmtime(&tt); //将日期和时间转换成tm结构体特定的形式
printf("%04d-%02d-%02d %02d:%02d:%02d\n", (1900+pTm->tm_year), (1+pTm->tm_mon), pTm->tm_mday,(8+pTm->tm_hour), pTm->tm_min, pTm->tm_sec);
}
void InitTime(int tv_sec,int tv_usec)
{
signal(SIGALRM,sigHandler); //对SIGALRM信号进行捕捉,函数是sigHandler
alarm(0); //计时0秒
struct itimerval tm;
//设置初始值
tm.it_value.tv_sec = tv_sec;
tm.it_value.tv_usec = tv_usec;
//设置重复间隔
tm.it_interval.tv_sec = tv_sec;
tm.it_interval.tv_usec = tv_usec;
//设置真实计时器
if(setitimer(ITIMER_REAL,&tm,NULL) == -1){
perror("setitimer error");
exit(-1);
}
}
int main()
{
InitTime(2,0); //给函数传入参数,2秒0微秒,并通过函数设置初始和间隔值
while(1);
return 0;
}