文章目录
定时器中断
设置时钟定时中断,首先设置时间定时器,定时器到期,产生信号,触发中断,执行中断处理函数。整体流程如下:
1.设置好sigevt信号事件相关(如何处理该信号,信号做标记,…)
2.timer_creat() 创建定时器
3.timer_settime() 设置定时器相关信息
4.信号捕捉函数,捕捉到信号后,进行中断处理函数执行或其他
时钟定时器
POSIX定时器
POSIX 1003.16标准为用户态程序引入了一种新型软定时器,尤其是针对各线程和实时应用程序。这些定时器常被称作POSIX定时器。
。
要执行每个POSIX定时器必须向用户态程序提供些POSIX时钟,也就是说,虚拟时间源预定义了分辨度和属性。只要应用程序想使用POSIX定时器,它就创建一个新的定时器资源并指定一个现存的POSIX时钟来作为定时基准。表6-3列出了允许用户来处理POSIX时钟和定时器的一些系统调用。
表6-3:与POSIX定时器和时钟相关的系统调用系统调用
系统调用 | 说明 |
---|---|
clock_gettime() | 获得一个POSIX时钟的当前值 |
clock_settime() | 设置一个POSIX时钟的当前值 |
clock_getres() | 获得一个POSIX时钟的分辨率 |
timer_creat() | 在指定POSIX时钟基础上创建一个新的POSIX时钟的定时器 |
timer_gettime() | 获得一个POSIX定时器的当前值和增量 |
timer_settime() | 设置一个POSIX定时器的当前值和增量 |
timer_getoverrun() | 获得到期POSIX定时器的当前值和增量 |
timer_delete() | 销毁一个POSIX定时器 |
clock_nanosleep() | 使进程进入睡眠状态并使用一个POSIX时钟作为事件源 |
Linux 2.6内核提供两种类型的POSIX时钟:
- CLOCK FBALTIME
该虚拟时钟表示系统的实时时钟-本质上是xtime变量的值.clock_getres()系统调用返回的分辨率为999848ns,对应1s内更新xtitme大约1000次。 - CLOCK JMONCTONIC
该虚拟时钟表示由于与外部时间源的同步,每次回到初值的系统实时时钟,实际上,该虚拟时钟由xtime和wall_to_monotonic两个变量的和表示.
Linux内核使用动态定时器来实现POSIX定时置,因此,它们与我们在前面一节描述的ITIMER_ REAL间隔定时器相似。不过,POSIX定时器比传统间隔定时器更灵活,更可靠。它们之间有两个显著区别:
-
当传统间隔定时器到期时,内核会发送一个SIGALRM信号给进程来激话定时器。而当一个POSIX定时器到期时,内核可以发送各种信号给整个多线程应用程序,也可以发送给单个指定的线程,内核还能在应用程序的某个线程上强制执行一个通告器函数,或者甚至什么也不做(这取决于处理事件的用户态函数库)。
-
如果一个传统间隔定时器到期了很多次但用户态进程不能接收SIGALRM信号(例如由于信号被阻塞或者进程不处于运行态),那么只有第一个信号被接收到,其他所有SIGALRM信号都丢失了。对于POSIX定时器来说会发生同样的情况,但进程可以调用timer_getoverrun()系统调用来得到自第一个信号产生以来定时器到期的次数。
定时器操作
最强大的定时器接口来自POSIX时钟系列,其创建、初始化以及删除一个定时器的行动被分为三个不同的函数:
timer_create()(创建定时器)
#include <signal.h>
#include <time.h>
函数声明:
int timer_create(clockid_t clockid, struct sigevent *sevp, timer_t *timerid);
功能:创建一个POSIX标准的进程定时器
参数:
@clockid 可选系统系统的宏,比如 CLOCK_REALTIME
@sevp 环境值,结构体struct sigevent变量的地址
@timerid 定时器标识符,结构体timer_t变量的地址
link with -lrt.
返回值:
0 - 成功;-1 - 失败,errno被设置。
进程可以通过调用timer_create()创建特定的定时器,定时器是每个进程自己的,不是在fork时继承的。
- clock_id
clock_id说明定时器是基于哪个时钟的,*timerid装载的是被创建的定时器的ID。该函数创建了定时器,并将他的ID 放入timerid指向的位置中。
clock_id取值为以下:
clock_id | 说明 |
---|---|
CLOCK_REALTIME | Systemwide realtime clock. |
CLOCK_MONOTONIC | Represents monotonic time. Cannot be set. |
CLOCK_PROCESS_CPUTIME_ID | High resolution per-process timer. |
CLOCK_THREAD_CPUTIME_ID | Thread-specific timer. |
CLOCK_REALTIME_HR | High resolution version of CLOCK_REALTIME. |
CLOCK_MONOTONIC_HR | High resolution version of CLOCK_MONOTONIC. |
struct sigevent
{
int sigev_notify; //notification type
int sigev_signo; //signal number (见signum:要操作的信号。)
union sigval sigev_value; //signal value
void (*sigev_notify_function)(union sigval);
pthread_attr_t *sigev_notify_attributes;
}
union sigval
{
int sival_int; //integer value
void *sival_ptr; //pointer value
}
- evp
参数evp指定了定时器到期要产生的异步通知。如果evp为NULL,那么定时器到期会产生默认的信号,对 CLOCK_REALTIMER来说,默认信号就是SIGALRM。如果要产生除默认信号之外的其它信号,程序必须将 evp->sigev_signo设置为期望的信号码。
struct sigevent 结构中的成员evp->sigev_notify说明了定时器到期时应该采取的行动。通常,这个成员的值为SIGEV_SIGNAL,这个值说明在定时器到期时,会产生一个信号。程序可以将成员evp->sigev_notify设为SIGEV_NONE来防止定时器到期时产生信号。
如果几个定时器产生了同一个信号,处理程序可以用 evp->sigev_value来区分是哪个定时器产生了信号。要实现这种功能,程序必须在为信号安装处理程序时,使用struct sigaction的成员sa_flags中的标志符SA_SIGINFO。
通过将evp->sigev_notify设定为如下值来定制定时器到期后的行为:
SIGEV_NONE: | 什么都不做,只提供通过timer_gettime和timer_getoverrun查询超时信息。 |
SIGEV_SIGNAL: | 当定时器到期,内核会将sigev_signo所指定的信号传送给进程。在信号处理程序中,si_value会被设定会sigev_value。 |
SIGEV_THREAD: | 当定时器到期,内核会(在此进程内)以sigev_notification_attributes为线程属性创建一个线程,并且让它执行sigev_notify_function,传入sigev_value作为为一个参数。 |
timer_settime()(初始化定时器)
timer_create()所创建的定时器并未启动。要将它关联到一个到期时间以及启动时钟周期,可以使用timer_settime()。
**int timer_settime(timer_t timerid, int flags, const struct itimerspec value, struct itimerspect ovalue);
头文件:
#include <time.h>
函数声明:
int timer_settime(timer_t timerid, int flags, const struct itimerspec *new_value,struct itimerspec *old_value);
int timer_gettime(timer_t timerid, struct itimerspec *curr_value);
功能:设置或者获得定时器时间值
参数:
@timerid 定时器标识
@flags 0标识相对时间,1标识绝对时间
@new_value 定时器的新初始值和间隔,如下面的it
@old_value 取值通常为0或NULL,若不为NULL,则返回定时器前一个值
link with -lrt.
struct itimespec{
struct timespec it_interval;
struct timespec it_value;
};
如同settimer(),it_value用于指定当前的定时器到期时间。当定时器到期,it_value的值会被更新成it_interval 的值。如果it_interval的值为0,则定时器不是一个时间间隔定时器,一旦it_value到期就会回到未启动状态。timespec的结构提供了纳秒级分辨率:
struct timespec{
time_t tv_sec;
long tv_nsec;
};
如果flags的值为TIMER_ABSTIME,则value所指定的时间值会被解读成绝对值(此值的默认的解读方式为相对于当前的时间)。这个经修改的行为可避免取得当前时间、计算“该时间”与“所期望的未来时间”的相对差额以及启动定时器期间造成竞争条件。
如果ovalue的值不是NULL,则之前的定时器到期时间会被存入其所提供的itimerspec。如果定时器之前处在未启动状态,则此结构的成员全都会被设定成0。
timer_gettime()(获得一个活动定时器的剩余时间)
int timer_gettime(timer_t timerid,struct itimerspec *value);
timer_getoverrun()(取得一个定时器的超限运行次数)
int timer_getoverrun(timer_t timerid);
有可能一个定时器到期了,而同一定时器上一次到期时产生的信号还处于挂起状态。在这种情况下,其中的一个信号可能会丢失。这就是定时器超限。程序可以通过调用timer_getoverrun来确定一个特定的定时器出现这种超限的次数。定时器超限只能发生在同一个定时器产生的信号上。由多个定时器,甚至是那些使用相同的时钟和信号的定时器,所产生的信号都会排队而不会丢失。
执行成功时,timer_getoverrun()会返回定时器初次到期与通知进程(例如通过信号)定时器已到期之间额外发生的定时器到期次数。举例来说,在我们之前的例子中,一个1ms的定时器运行了10ms,则此调用会返回9。如果超限运行的次数等于或大于DELAYTIMER_MAX,则此调用会返回DELAYTIMER_MAX。
执行失败时,此函数会返回-1并将errno设定会EINVAL,这个唯一的错误情况代表timerid指定了无效的定时器。
timer_delete()(删除一个定时器)
int timer_delete (timer_t timerid);
一次成功的timer_delete()调用会销毁关联到timerid的定时器并且返回0。执行失败时,此调用会返回-1并将errno设定会 EINVAL,这个唯一的错误情况代表timerid不是一个有效的定时器。
例程
例程1 采用新线程派驻的通知方式
#include <stdio.h>
#include <signal.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
void timer_thread(union sigval v)
{
printf("timer_thread function! %d\n", v.sival_int);
}
int main()
{
timer_t timerid;
struct sigevent evp;
memset(&evp, 0, sizeof(struct sigevent)); //清零初始化
evp.sigev_value.sival_int = 111; //也是标识定时器的,回调函数可以获得
evp.sigev_notify = SIGEV_THREAD; //线程通知的方式,派驻新线程
evp.sigev_notify_function = timer_thread; //线程函数地址
if (timer_create(CLOCK_REALTIME, &evp, &timerid) == -1)
{
perror("fail to timer_create");
exit(-1);
}
/* 第一次间隔it.it_value这么长,以后每次都是it.it_interval这么长,就是说it.it_value变0的时候会>装载it.it_interval的值 */
struct itimerspec it;
it.it_interval.tv_sec = 1; // 回调函数执行频率为1s运行1次
it.it_interval.tv_nsec = 0;
it.it_value.tv_sec = 3; // 倒计时3秒开始调用回调函数
it.it_value.tv_nsec = 0;
if (timer_settime(timerid, 0, &it, NULL) == -1)
{
perror("fail to timer_settime");
exit(-1);
}
//pause();
while (1);
return 0;
}
/*
* int timer_gettime(timer_t timerid, struct itimerspec *curr_value);
* 获取timerid指定的定时器的值,填入curr_value
*/
例程2 通知方式为信号的处理方式
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
#define CLOCKID CLOCK_REALTIME
void sig_handler(int signo)
{
printf("timer_signal function! %d\n", signo);
}
int main()
{
// XXX int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
// signum--指定的信号编号,可以指定SIGKILL和SIGSTOP以外的所有信号编号
// act结构体--设置信号编号为signum的处理方式
// oldact结构体--保存上次的处理方式
//
// struct sigaction
// {
// void (*sa_handler)(int); //信号响应函数地址
// void (*sa_sigaction)(int, siginfo_t *, void *); //但sa_flags为SA——SIGINFO时才使用
// sigset_t sa_mask; //说明一个信号集在调用捕捉函数之前,会加入进程的屏蔽中,当捕捉函数返回时,还原
// int sa_flags;
// void (*sa_restorer)(void); //未用
// };
//
timer_t timerid;
struct sigevent evp;
struct sigaction act;
memset(&act, 0, sizeof(act));
act.sa_handler = sig_handler;
act.sa_flags = 0;
// XXX int sigaddset(sigset_t *set, int signum); //将signum指定的信号加入set信号集
// XXX int sigemptyset(sigset_t *set); //初始化信号集
sigemptyset(&act.sa_mask);
if (sigaction(SIGUSR1, &act, NULL) == -1)
{
perror("fail to sigaction");
exit(-1);
}
memset(&evp, 0, sizeof(struct sigevent));
evp.sigev_signo = SIGUSR1;
evp.sigev_notify = SIGEV_SIGNAL;
if (timer_create(CLOCK_REALTIME, &evp, &timerid) == -1)
{
perror("fail to timer_create");
exit(-1);
}
struct itimerspec it;
it.it_interval.tv_sec = 2;
it.it_interval.tv_nsec = 0;
it.it_value.tv_sec = 1;
it.it_value.tv_nsec = 0;
if (timer_settime(timerid, 0, &it, 0) == -1)
{
perror("fail to timer_settime");
exit(-1);
}
pause();
return 0;
}
中断与中断处理函数(信号捕捉函数)
信号捕捉函数
signal 函数的使用方法简单,但并不属于 POSIX 标准,在各类 UNIX 平台上的实现不尽相同,因此其用途受到了一定的限制。POSIX标准定义的信号处理接口是**sigaction()**函数,其接口头文件及原型如下:
#include <signal.h>
//修改信号处理动作(通常在Linux用其来注册一个信号的捕捉函数)
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
- signum:要操作的信号:
Signal | Value | Action | Comment |
---|---|---|---|
SIGHUP | 1 | Term | Hangup detected on controlling terminal or death of controlling process |
SIGINT | 2 | Term | Interrupt from keyboard |
SIGQUIT | 3 | Core | Quit from keyboard |
SIGILL | 4 | Core | Illegal Instruction |
SIGABRT | 6 | Core | Abort signal from abort(3) |
SIGFPE | 8 | Core | Floating point exception |
SIGKILL | 9 | Term | Kill signal |
SIGSEGV | 11 | Core | Invalid memory reference |
SIGPIPE | 13 | Term | Broken pipe: write to pipe with no readers |
SIGALRM | 14 | Term | Timer signal from alarm(2) |
SIGTERM | 15 | Term | Termination signal |
SIGUSR1 | 30,10,16 | Term | User-defined signal 1 |
SIGUSR2 | 31,12,17 | Term | User-defined signal 2 |
SIGCHLD | 20,17,18 | Ign | Child stopped or terminated |
SIGCONT | 19,18,25 | Cont | Continue if stopped |
SIGSTOP | 17,19,23 | Stop | Stop process |
SIGTSTP | 18,20,24 | Stop | Stop typed at tty |
… | |||
- act:要设置的对信号的新处理方式。
- oldact:原来对信号的处理方式。
- 返回值:0 表示成功,-1 表示有错误发生。
sigaction结构体
struct sigaction 类型用来描述对信号的处理,定义如下:
struct sigaction
{
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
在这个结构体中,
-
成员 sa_handler 是一个函数指针,其含义与 signal 函数中的信号处理函数类似。它指定信号捕捉后的处理函数名(即注册函数)。也可赋值为SIG_IGN表忽略 或 SIG_DFL表执行默认动作
-
sa_sigaction 则是另一个信号处理函数,它有三个参数,可以获得关于信号的更详细的信息。
- 当sa_flags成员的值包含了SA_SIGINFO标志时,系统将使用sa_sigaction函数作为信号处理函数,即是
sigaction.sa_sigaction = dig_op_function
.否则使用sa_handler作为信号处理函数,sigaction.sa_handler = dig_op_function
如果不设置sa_flags的话,初始化为void (*sa_handler)(int);//这个就和signal差不多了哦
void handler(int signo);
如果设置了SA_SIGINFO则,初始化为void (*sa_sigaction)(int, siginfo_t , void );
//多了个siginfo,这个结构体的定义参见APUE,还有个econtext,用于标识信号传递时进程的上下文
void handler(int signo, siginfo_t info, void context);- 在某些系统中,成员sa_handler 与 sa_sigaction 被放在联合体中,因此使用时不要同时设置。
- 当sa_flags成员的值包含了SA_SIGINFO标志时,系统将使用sa_sigaction函数作为信号处理函数,即是
-
sa_mask 成员用来指定在信号处理函数执行期间需要被屏蔽的信号,
- 特别是当某个信号被处理时,它自身会被自动放入进程的信号掩码,因此在信号处理函数执行期间这个信号不会再度发生。
- 调用信号处理函数时,所要屏蔽的信号集合(信号屏蔽字)。注意:仅在处理函数被调用期间屏蔽生效,是临时性设置。
-
sa_flags 成员用于指定信号处理的行为,它可以是一下值的“按位或”组合。
- SA_RESTART:使被信号打断的系统调用自动重新发起。
- SA_NOCLDSTOP:使父进程在它的子进程暂停或继续运行时不会收到 SIGCHLD 信号。
- SA_NOCLDWAIT:使父进程在它的子进程退出时不会收到 SIGCHLD 信号,这时子进程如果退出也不会成为僵尸进程。
- SA_NODEFER:使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号。
- SA_RESETHAND:信号处理之后重新设置为默认的处理方式。
- SA_SIGINFO:使用 sa_sigaction 成员而不是 sa_handler 作为信号处理函数。
- 通常设置为0,表使用默认属性。
sa_flags的取值在sigaction.h中,如下:
<sigaction.h>
/* Bits in `sa_flags'. */
#define SA_NOCLDSTOP 1 /* Don't send SIGCHLD when children stop. */
#define SA_NOCLDWAIT 2 /* Don't create zombie on child death. */
#define SA_SIGINFO 4 /* Invoke signal-catching function with
three arguments instead of one. */
#if defined __USE_XOPEN_EXTENDED || defined __USE_MISC
# define SA_ONSTACK 0x08000000 /* Use signal stack by using `sa_restorer'. */
#endif
#if defined __USE_XOPEN_EXTENDED || defined __USE_XOPEN2K8
# define SA_RESTART 0x10000000 /* Restart syscall on signal return. */
# define SA_NODEFER 0x40000000 /* Don't automatically block the signal when
its handler is being executed. */
# define SA_RESETHAND 0x80000000 /* Reset to SIG_DFL on entry to handler. */
#endif
#ifdef __USE_MISC
# define SA_INTERRUPT 0x20000000 /* Historical no-op. */
/* Some aliases for the SA_ constants. */
# define SA_NOMASK SA_NODEFER
# define SA_ONESHOT SA_RESETHAND
# define SA_STACK SA_ONSTACK
#endif
sigaction.sa_flags控制内核对该信号的处理标记
SA_NODEFER: 一般情况下,当信号处理函数运行时,内核将阻塞<该给定信号–SIGINT>。但是如果设置了SA_NODEFER标记,那么在该信号处理函数运行时,内核将不会阻塞该信号。SA_NODEFER是这个标记的正式的POSIX名字(还有一个名字SA_NOMASK,为了软件的可移植性,一般不用这个名字)
SA_RESETHAND: 当调用信号处理函数时,将信号的处理函数重置为缺省值。SA_RESETHAND是这个标记的正式的POSIX名字(还有一个名字SA_ONESHOT,为了软件的可移植性,一般不用这个名字)
上面是对sa_flags中常用的两个值的解释.
- re_restorer 成员则是一个已经废弃的数据域,不要使用。
例程1
下面用一个例程来说明 sigaction 函数的使用(handler处理方式),代码如下
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
static void sig_usr(int signum)
{
if(signum == SIGUSR1)
{
printf("SIGUSR1 received\n");
}
else if(signum == SIGUSR2)
{
printf("SIGUSR2 received\n");
}
else
{
printf("signal %d received\n", signum);
}
}
int main(void)
{
char buf[512];
int n;
struct sigaction sa_usr;
sa_usr.sa_flags = 0;
sa_usr.sa_handler = sig_usr; //信号处理函数
sigaction(SIGUSR1, &sa_usr, NULL);
sigaction(SIGUSR2, &sa_usr, NULL);
printf("My PID is %d\n", getpid());
while(1)
{
if((n = read(STDIN_FILENO, buf, 511)) == -1)
{
if(errno == EINTR)
{
printf("read is interrupted by signal\n");
}
}
else
{
buf[n] = '\0';
printf("%d bytes read: %s\n", n, buf);
}
}
return 0;
}
在这个例程中使用 sigaction 函数为 SIGUSR1 和 SIGUSR2 信号注册了处理函数,然后从标准输入读入字符。程序运行后首先输出自己的 PID,如:My PID is 5904
这时如果从另外一个终端向进程发送 SIGUSR1 或 SIGUSR2 信号,用类似如下的命令:kill -USR1 5904
则程序将继续输出如下内容:
SIGUSR1 received
read is interrupted by signal
这说明用 sigaction 注册信号处理函数时,不会自动重新发起被信号打断的系统调用。如果需要自动重新发起,则要设置 SA_RESTART 标志,比如在上述例程中可以进行类似一下的设置:sa_usr.sa_flags = SA_RESTART;
例程2
sigaction 函数的使用(sigaction处理方式),代码如下
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
/*
struct siginfo
{
int si_signo;
int si_errno;
int si_code;
pid_t si_pid;
uid_t si_uid;
void* si_addr;
int si_status;
long s_band
}
*/
void sig_op(int signo, siginfo_t* info, void* context)
{
printf("the signo is %d\n",signo);
printf("sig pid is %d\n", (int)(info->si_pid));
printf("sig uid is %d\n", (int)(info->si_uid));
}
int main(int argc,char** argv)
{
struct sigaction act;
struct sigaction oact;
pid_t pid;
pid=getpid();
sigemptyset(&act.sa_mask);
act.sa_handler=sig_op;
act.sa_flags=SA_SIGINFO;
printf("the pid is %d",pid);
if(sigaction(SIGPIPE,&act,&oact)==-1)
//这里还可以保存原来的signal处理方式,以便有的时候需要恢复oact。
//sigaction(sig,&oact,NULL)
printf("%s","install error~!\n");
while(1)
{
sleep(1);
printf("%s","wait for signal\n");
}
return 0;
}
[kenthy@kenthy c_c]$ ./sig
the pid is 3904
wait for signal
wait for signal
wait for signal
wait for signal
wait for signal
wait for signal
the signo is 13 //这个时候另一个terminal kill -s SIGPIPE 3904
sig pid is 3135
sig uid is 500
wait for signal
wait for signal