定时器中断

定时器中断

设置时钟定时中断,首先设置时间定时器,定时器到期,产生信号,触发中断,执行中断处理函数。整体流程如下:


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_REALTIMESystemwide realtime clock.
CLOCK_MONOTONICRepresents monotonic time. Cannot be set.
CLOCK_PROCESS_CPUTIME_IDHigh resolution per-process timer.
CLOCK_THREAD_CPUTIME_IDThread-specific timer.
CLOCK_REALTIME_HRHigh resolution version of CLOCK_REALTIME.
CLOCK_MONOTONIC_HRHigh 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 取值通常为0NULL,若不为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:要操作的信号:
SignalValueActionComment
SIGHUP1TermHangup detected on controlling terminal or death of controlling process
SIGINT2TermInterrupt from keyboard
SIGQUIT3CoreQuit from keyboard
SIGILL4CoreIllegal Instruction
SIGABRT6CoreAbort signal from abort(3)
SIGFPE8CoreFloating point exception
SIGKILL9TermKill signal
SIGSEGV11CoreInvalid memory reference
SIGPIPE13TermBroken pipe: write to pipe with no readers
SIGALRM14TermTimer signal from alarm(2)
SIGTERM15TermTermination signal
SIGUSR130,10,16TermUser-defined signal 1
SIGUSR231,12,17TermUser-defined signal 2
SIGCHLD20,17,18IgnChild stopped or terminated
SIGCONT19,18,25ContContinue if stopped
SIGSTOP17,19,23StopStop process
SIGTSTP18,20,24StopStop 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_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

参考资料

深入理解LINUX内核 pp. 256-257

【C程序】timer_create系列定时器函数

定时器、sigevent结构体详解

linux中sigaction函数详解

Linux信号:sigaction函数sa_flags各标志影响的实例讲解

linux的信号捕捉函数详解

Linux系统sigaction函数的使用示例

Linux时间子系统之(六):POSIX timer

Linux Programmer’s Manual

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值