Linux C语言定时器的定时器嵌套使用
Linux C 语言定时器概述
在 Linux 环境下,C 语言提供了多种定时器机制来满足不同的应用需求。定时器在许多场景中都至关重要,比如网络通信中的超时控制、系统任务的定时调度等。常见的定时器类型有间隔定时器(Interval Timer)和单次定时器(One - shot Timer)。
常见定时器类型
- 间隔定时器:这种定时器会按照设定的时间间隔周期性地触发。例如,一个网络心跳检测机制,每隔一定时间(如 1 秒)向服务器发送一个心跳包,以确保连接的有效性。在 Linux 中,可以使用
setitimer
函数来创建间隔定时器。 - 单次定时器:单次定时器在设定的时间到达后只触发一次。例如,在一个文件下载任务中,如果超过一定时间(如 10 分钟)还未完成下载,就终止该任务。这种情况下就可以使用单次定时器。
alarm
函数是实现单次定时器的一种简单方式。
Linux C 语言定时器的基本使用
使用 alarm 函数创建单次定时器
alarm
函数是一个简单的用于设置单次定时器的函数,它的原型如下:
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
该函数设置一个定时器,在 seconds
秒后向进程发送 SIGALRM
信号。返回值是之前设置的定时器剩余的秒数,如果之前没有设置定时器,则返回 0。
以下是一个简单的示例代码:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void sigalrm_handler(int signum) {
printf("Received SIGALRM signal\n");
}
int main() {
signal(SIGALRM, sigalrm_handler);
alarm(5);
printf("Setting alarm for 5 seconds\n");
while (1) {
sleep(1);
printf("Waiting...\n");
}
return 0;
}
在上述代码中,我们首先定义了一个信号处理函数 sigalrm_handler
来处理 SIGALRM
信号。然后在 main
函数中,通过 signal
函数将 SIGALRM
信号与我们定义的处理函数关联起来。接着使用 alarm(5)
设置一个 5 秒的定时器。在 while
循环中,程序会每秒打印一次 “Waiting...”,直到 5 秒后触发 SIGALRM
信号,执行信号处理函数。
使用 setitimer 函数创建间隔定时器和单次定时器
setitimer
函数功能更为强大,它可以创建间隔定时器和单次定时器。其原型如下:
#include <sys/time.h>
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
which
参数指定定时器类型,有三种取值:
ITIMER_REAL
:以系统真实的时间来计算,它送出的信号是SIGALRM
。ITIMER_VIRTUAL
:以该进程在用户态下花费的时间来计算,送出的信号是SIGVTALRM
。ITIMER_PROF
:以该进程在用户态和内核态下所花费的时间来计算,送出的信号是SIGPROF
。
new_value
是一个指向 itimerval
结构体的指针,用于设置定时器的新值。old_value
也是一个指向 itimerval
结构体的指针,用于返回定时器的旧值,如果不需要旧值,可以设为 NULL
。
itimerval
结构体定义如下:
struct itimerval {
struct timeval it_interval;
struct timeval it_value;
};
struct timeval {
time_t tv_sec;
suseconds_t tv_usec;
};
it_interval
表示定时器的间隔时间,it_value
表示定时器首次触发的时间。
以下是创建间隔定时器的示例代码:
#include <stdio.h>
#include <sys/time.h>
#include <signal.h>
void sigalrm_handler(int signum) {
printf("Received SIGALRM signal\n");
}
int main() {
struct itimerval itv;
signal(SIGALRM, sigalrm_handler);
itv.it_interval.tv_sec = 2;
itv.it_interval.tv_usec = 0;
itv.it_value.tv_sec = 2;
itv.it_value.tv_usec = 0;
setitimer(ITIMER_REAL, &itv, NULL);
while (1) {
sleep(1);
printf("Waiting...\n");
}
return 0;
}
在这段代码中,我们设置了一个间隔为 2 秒的定时器。itv.it_interval
设置了定时器的间隔时间为 2 秒,itv.it_value
设置了首次触发时间也为 2 秒。通过 setitimer
函数将这个定时器设置为 ITIMER_REAL
类型,即使用系统真实时间。当定时器触发时,会执行 sigalrm_handler
函数。
定时器嵌套使用的概念
什么是定时器嵌套
定时器嵌套是指在一个定时器的处理函数中,再次设置另一个定时器。这种技术可以实现复杂的定时任务调度。例如,在一个网络爬虫程序中,可能需要先定时获取一批网页链接,然后在获取到链接后,针对每个链接再定时发起请求。这里就可以使用定时器嵌套,外层定时器控制获取链接的频率,内层定时器控制对每个链接发起请求的时间。
定时器嵌套的优势
- 灵活的任务调度:可以根据不同的任务需求,在不同的时间层次上进行任务调度。比如在一个游戏开发中,外层定时器可以控制游戏场景的切换频率,内层定时器可以控制场景中具体角色的动作时间间隔。
- 提高系统资源利用率:避免了一次性启动大量定时器造成的资源浪费。通过嵌套,可以根据前一个定时器的执行结果,动态地决定是否需要启动下一个定时器。
定时器嵌套使用的实现
基于 alarm 函数的定时器嵌套
我们可以在 SIGALRM
信号处理函数中再次调用 alarm
函数来实现定时器嵌套。以下是一个简单的示例:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void inner_alarm_handler(int signum) {
printf("Inner alarm triggered\n");
}
void outer_alarm_handler(int signum) {
printf("Outer alarm triggered\n");
signal(SIGALRM, inner_alarm_handler);
alarm(3);
}
int main() {
signal(SIGALRM, outer_alarm_handler);
alarm(5);
printf("Setting outer alarm for 5 seconds\n");
while (1) {
sleep(1);
printf("Waiting...\n");
}
return 0;
}
在上述代码中,我们定义了两个信号处理函数 inner_alarm_handler
和 outer_alarm_handler
。在 main
函数中,首先设置了一个 5 秒的外层定时器,当 5 秒后 outer_alarm_handler
被调用,在这个函数中,我们又设置了一个 3 秒的内层定时器,并将 SIGALRM
信号处理函数切换为 inner_alarm_handler
。这样就实现了定时器的嵌套。
基于 setitimer 函数的定时器嵌套
同样,我们也可以使用 setitimer
函数来实现定时器嵌套。以下是一个示例:
#include <stdio.h>
#include <sys/time.h>
#include <signal.h>
void inner_setitimer_handler(int signum) {
printf("Inner setitimer triggered\n");
}
void outer_setitimer_handler(int signum) {
printf("Outer setitimer triggered\n");
struct itimerval itv_inner;
itv_inner.it_interval.tv_sec = 2;
itv_inner.it_interval.tv_usec = 0;
itv_inner.it_value.tv_sec = 2;
itv_inner.it_value.tv_usec = 0;
signal(SIGALRM, inner_setitimer_handler);
setitimer(ITIMER_REAL, &itv_inner, NULL);
}
int main() {
struct itimerval itv_outer;
signal(SIGALRM, outer_setitimer_handler);
itv_outer.it_interval.tv_sec = 5;
itv_outer.it_interval.tv_usec = 0;
itv_outer.it_value.tv_sec = 5;
itv_outer.it_value.tv_usec = 0;
setitimer(ITIMER_REAL, &itv_outer, NULL);
while (1) {
sleep(1);
printf("Waiting...\n");
}
return 0;
}
在这个示例中,外层定时器设置为 5 秒触发一次,当外层定时器触发后,在 outer_setitimer_handler
函数中,我们设置了一个 2 秒触发一次的内层定时器。这里通过 setitimer
函数分别设置了外层和内层定时器,并且在定时器触发时执行相应的信号处理函数。
定时器嵌套使用中的注意事项
信号处理与竞态条件
在定时器嵌套中,由于信号处理函数是异步执行的,可能会与主程序或其他信号处理函数产生竞态条件。例如,在信号处理函数中修改共享变量时,如果主程序也在同时访问该变量,就可能导致数据不一致。为了避免这种情况,可以使用互斥锁(Mutex)或信号量(Semaphore)来保护共享资源。
以下是一个使用互斥锁的简单示例:
#include <stdio.h>
#include <sys/time.h>
#include <signal.h>
#include <pthread.h>
pthread_mutex_t mutex;
int shared_variable = 0;
void inner_setitimer_handler(int signum) {
pthread_mutex_lock(&mutex);
shared_variable++;
printf("Inner setitimer triggered, shared_variable: %d\n", shared_variable);
pthread_mutex_unlock(&mutex);
}
void outer_setitimer_handler(int signum) {
pthread_mutex_lock(&mutex);
shared_variable++;
printf("Outer setitimer triggered, shared_variable: %d\n", shared_variable);
pthread_mutex_unlock(&mutex);
struct itimerval itv_inner;
itv_inner.it_interval.tv_sec = 2;
itv_inner.it_interval.tv_usec = 0;
itv_inner.it_value.tv_sec = 2;
itv_inner.it_value.tv_usec = 0;
signal(SIGALRM, inner_setitimer_handler);
setitimer(ITIMER_REAL, &itv_inner, NULL);
}
int main() {
struct itimerval itv_outer;
signal(SIGALRM, outer_setitimer_handler);
itv_outer.it_interval.tv_sec = 5;
itv_outer.it_interval.tv_usec = 0;
itv_outer.it_value.tv_sec = 5;
itv_outer.it_value.tv_usec = 0;
pthread_mutex_init(&mutex, NULL);
setitimer(ITIMER_REAL, &itv_outer, NULL);
while (1) {
sleep(1);
printf("Waiting...\n");
}
pthread_mutex_destroy(&mutex);
return 0;
}
在这个示例中,我们使用 pthread_mutex_t
类型的互斥锁 mutex
来保护共享变量 shared_variable
。在信号处理函数中,首先通过 pthread_mutex_lock
函数获取锁,然后对共享变量进行操作,操作完成后通过 pthread_mutex_unlock
函数释放锁。
定时器资源管理
过多的定时器嵌套可能会消耗大量的系统资源,特别是在长时间运行的程序中。因此,需要合理管理定时器资源。例如,在不需要内层定时器时,及时取消它。可以通过 setitimer
函数将 it_value
和 it_interval
都设置为 0 来取消定时器。
struct itimerval itv_cancel;
itv_cancel.it_interval.tv_sec = 0;
itv_cancel.it_interval.tv_usec = 0;
itv_cancel.it_value.tv_sec = 0;
itv_cancel.it_value.tv_usec = 0;
setitimer(ITIMER_REAL, &itv_cancel, NULL);
这样就可以取消正在运行的定时器,释放相关资源。
定时器嵌套在实际项目中的应用
网络通信中的应用
在网络通信中,定时器嵌套可以用于实现复杂的连接管理和数据传输控制。例如,在一个 TCP 客户端 - 服务器模型中,客户端可以使用外层定时器定期检查与服务器的连接状态。如果发现连接断开,就使用内层定时器控制重连的时间间隔。以下是一个简单的示意代码:
#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/time.h>
#include <signal.h>
#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 8888
int sockfd;
void reconnect_handler(int signum) {
sockfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in servaddr;
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERVER_PORT);
servaddr.sin_addr.s_addr = inet_addr(SERVER_IP);
if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == 0) {
printf("Reconnected to server\n");
} else {
printf("Reconnection failed\n");
}
}
void check_connection_handler(int signum) {
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 0;
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof tv);
char buffer[1024];
int n = recv(sockfd, buffer, sizeof(buffer), MSG_DONTWAIT);
if (n < 0) {
printf("Connection lost, attempting to reconnect\n");
struct itimerval itv_reconnect;
itv_reconnect.it_interval.tv_sec = 5;
itv_reconnect.it_interval.tv_usec = 0;
itv_reconnect.it_value.tv_sec = 5;
itv_reconnect.it_value.tv_usec = 0;
signal(SIGALRM, reconnect_handler);
setitimer(ITIMER_REAL, &itv_reconnect, NULL);
}
}
int main() {
sockfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in servaddr;
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERVER_PORT);
servaddr.sin_addr.s_addr = inet_addr(SERVER_IP);
if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) != 0) {
printf("Connection failed\n");
return 1;
}
printf("Connected to server\n");
struct itimerval itv_check;
itv_check.it_interval.tv_sec = 10;
itv_check.it_interval.tv_usec = 0;
itv_check.it_value.tv_sec = 10;
itv_check.it_value.tv_usec = 0;
signal(SIGALRM, check_connection_handler);
setitimer(ITIMER_REAL, &itv_check, NULL);
while (1) {
sleep(1);
// 其他业务逻辑
}
close(sockfd);
return 0;
}
在这段代码中,外层定时器 itv_check
每隔 10 秒触发一次 check_connection_handler
函数,该函数检查与服务器的连接状态。如果连接丢失,就启动内层定时器 itv_reconnect
,每隔 5 秒尝试重连一次。
嵌入式系统中的应用
在嵌入式系统中,定时器嵌套可以用于任务调度和设备控制。例如,在一个智能家居系统中,可能有一个主定时器定期检查各个传感器的数据(如温度、湿度传感器)。当获取到传感器数据后,根据数据情况使用内层定时器控制相应的执行器(如空调、加湿器)的工作时间。以下是一个简化的示例:
#include <stdio.h>
#include <sys/time.h>
#include <signal.h>
// 模拟传感器数据获取函数
int get_temperature() {
// 实际中这里会从传感器读取数据
return 25;
}
void control_air_conditioner(int signum) {
int temperature = get_temperature();
if (temperature > 28) {
printf("Temperature is high, turning on air conditioner\n");
} else {
printf("Temperature is normal, turning off air conditioner\n");
}
}
void read_sensor_handler(int signum) {
printf("Reading sensor data\n");
struct itimerval itv_control;
itv_control.it_interval.tv_sec = 10;
itv_control.it_interval.tv_usec = 0;
itv_control.it_value.tv_sec = 10;
itv_control.it_value.tv_usec = 0;
signal(SIGALRM, control_air_conditioner);
setitimer(ITIMER_REAL, &itv_control, NULL);
}
int main() {
struct itimerval itv_read;
itv_read.it_interval.tv_sec = 60;
itv_read.it_interval.tv_usec = 0;
itv_read.it_value.tv_sec = 60;
itv_read.it_value.tv_usec = 0;
signal(SIGALRM, read_sensor_handler);
setitimer(ITIMER_REAL, &itv_read, NULL);
while (1) {
sleep(1);
// 其他嵌入式系统业务逻辑
}
return 0;
}
在这个示例中,外层定时器 itv_read
每隔 60 秒触发一次 read_sensor_handler
函数,该函数模拟读取传感器数据。然后启动内层定时器 itv_control
,每隔 10 秒根据传感器数据控制空调的开关。
通过以上对 Linux C 语言定时器嵌套使用的详细介绍,包括基本概念、实现方法、注意事项以及实际应用案例,希望能帮助开发者更好地理解和运用这一技术,在实际项目中实现更高效、灵活的定时任务调度。