当前位置: 首页 > 科技观察

在Linux中创建计时器

时间:2023-03-17 15:13:18 科技观察

为某些事件计时是开发人员的常见任务。计时器的常见场景是看门狗、重复执行任务或在特定时间安排事件。在本文中,我将演示如何使用timer_create(...)创建符合POSIX标准的间隔计时器。您可以从GitHub下载以下示例的源代码。准备QtCreator我使用QtCreator作为这个示例的IDE。为了在QtCreator中运行和调试示例代码,请克隆GitHub上的存储库,打开QtCreator,转到“文件->打开文件或项目...打开文件或项目...”并选择“CMakeLists.txt”:在QtCreator中打开工程选择工具链后,点击“ConfigureProject”。该项目包括三个独立的示例(我们将在本文中仅使用其中的两个)。使用标记为绿色的菜单,可以在每个示例的配置之间切换并为每个示例(标记为黄色)激活“在终端中运行”。可以通过左下角的“调试”按钮选择当前用于构建和调试的活动实例(参见下面的橙色标记)。项目配置线程计时器让我们看一下simple_threading_timer.c示例。这是最简单的一个。它显示了如何创建一个调用超时函数expired的间隔计时器。每次过期时,都会创建一个新线程,在该线程中调用过期函数:h>#include#includevoidexpired(unionsigvaltimer_data);pid_tgettid(void);structt_eventData{intmyData;};intmain(){intres=0;timer_ttimerId=0;结构t_eventDataeventData={.myData=0};/*sigevent指定过期时要执行的操作*/structsigeventsev={0};/*指定开始延迟时间和间隔时间*it_value和it_interval不能为零*/structitimerspecits={.it_value.tv_sec=1,.it_value.tv_nsec=0,.it_interval.tv_sec=1,.it_interval.tv_nsec=0};printf("简单线程计时器-thread-id:%d\n",gettid());sev.sigev_notify=SIGEV_THREAD;sev.sigev_notify_function=&expired;sev.sigev_value.sival_ptr=&eve数据;/*创建定时器*/res=timer_create(CLOCK_REALTIME,&sev,&timerId);if(res!=0){fprintf(stderr,"错误timer_create:%s\n",strerror(errno));退出(-1);}/*启动定时器*/res=timer_settime(timerId,0,&its,NULL);if(res!=0){fprintf(stderr,"Errortimer_settime:%s\n",strerror(errno));退出(-1);}printf("按ETNER键退出\n");while(getchar()!='\n'){}return0;}voidexpired(unionsigvaltimer_data){structt_eventData*data=timer_data.sival_ptr;printf("Timerfired%d-thread-id:%d\n",++data->myData,gettid());}这种方法的优点在于代码和易于调试方面使用的小缺点是由于在线程过期时创建新线程的额外开销,该行为的确定性较低。中断信号计时器超时计时器通知的另一种可能性是基于内核信号。不是每次定时器到期都创建一个新线程,而是内核向进程发送一个信号,进程被中断,并调用相应的信号处理程序。由于收到信号时的默认动作是终止进程(参考信号手册页),我们必须提前设置QtCreator才能正确调试。当被调试者接收到信号时,QtCreator的默认行为是中断执行并切换到调试器上下文。显示一个弹出窗口,通知用户已收到信号。这些操作都不需要,因为信号的接收是我们应用程序的一部分。QtCreator在后台使用GDB。要防止GDB在进程接收到信号时停止执行,请转至工具->选项菜单,选择调试器,然后导航至局部变量和表达式。将以下表达式添加到“DebuggingHelperCustomization”:handleSIG34nostoppassSig34Donotstop您可以在GDB文档中找到有关GDB信号处理的更多信息。接下来,当我们在信号处理程序中停止时,我们想要抑制每次收到信号时通知我们的弹出窗口:Signal34Popup为此,导航到“GDB”选项卡并取消选中标记的复选框:Timersignalwindownow您可以正确调试signal_interrupt_timer。ev.sigev_signo=SIGRTMIN;sev.sigev_value.sival_ptr=&eventData;/*创建定时器*/res=timer_create(CLOCK_REALTIME,&sev,&timerId);if(res!=0){fprintf(stderr,"错误timer_create:%s\n",strerror(errno));退出(-1);}/*指定信号和处理程序*/sa.sa_flags=SA_SIGINFO;sa.sa_sigaction=处理程序;/*初始化信号*/sigemptyset(&sa.sa_mask);printf("正在为信号%d\n建立处理程序",SIGRTMIN);/*注册信号处理程序*/if(sigaction(SIGRTMIN,&sa,NULL)==-1){fprintf(stderr,"Errorsigaction:%s\n",strerror(errno));退出(-1);}/*启动定时器*/res=timer_settime(timerId,0,&its,NULL);if(res!=0){fprintf(stderr,"Errortimer_settime:%s\n",strerror(errno));退出(-1);}printf("按回车键退出\n");while(getchar()!='\n'){}return0;}staticvoidhandler(intsig,siginfo_t*si,void*uc){未使用(sig);未使用(uc);结构t_eventData*data=(structt_eventData*)si->_sifields._rt.si_sigval.sival_ptr;printf("Timerfired%d-thread-id:%d\n",++data->myData,gettid());}相比于线程计时器,我们必须初始化信号并注册一个信号处理程序。此方法的性能更高,因为它不会导致创建额外的线程。因此,信号处理程序的执行也更具确定性。缺点显然是正确调试所需的额外配置工作。小结本文介绍的两种方法都是接近内核的定时器实现。然而,尽管timer_create(...)函数是POSIX规范的一部分,但由于数据结构的细微差别,无法在FreeBSD系统上编译示例代码。除了这个缺点,这个实现为通用定时应用程序提供了细粒度的控制。