原标题:Linux延时实现
本文转载自 Linux内核那些事
链接:https://mp.weixin.qq.com/s/Y_oJamxHXeOxFOCpVybN5Q
很多时候我们需要在程序中等待一定的时间再运行,例如我们需要每隔10秒去执行某个任务,这就需要系统提供接口来等待指定的时间再运行.在Linux系统中提供了sleep(nsec)系统调用来等待nsec秒.
严格来说sleep()并不是Linux提供的系统调用,而是Glibc库实现的.在Glibc的sysdeps/posix/sleep.c文件中可以找到具体的实现:
unsigned int
DEFUN(sleep, (seconds), unsigned int seconds)
{
......
act.sa_handler = sleep_handler;
act.sa_flags = 0;
if (sigemptyset (&act.sa_mask) < 0 ||
sigaction (SIGALRM, &act, &oact)< 0)
return seconds;
before = time ((time_t *) NULL);
remaining = alarm (seconds);
......
sigsuspend (&oset);
after = time ((time_t *) NULL);
(void) sigaction (SIGALRM, &oact, (struct sigaction *) NULL);
......
}
代码去掉了很多无关重要的细节,加粗的代码是实现的主要部分,流程大概如下:
1) 调用alarm(nsec)系统调用设置一个定时器, nsec秒后内核会发送SIGALRM信号给进程.
2) 调用sigsuspend()系统调用等待信号的发生. Sigsuspend()系统调用会阻塞当前进程,直到当前进程收到信号才会被唤醒.
调用alarm()系统调用会进入到内核态,并调用sys_alarm()函数, sys_alarm()函数会调用_setitimer()函数来,而_setitimer()函数最终会调用add_timer()函数把当前进程添加到定时器队列中, add_timer()代码如下:
void add_timer(struct timer_list * timer)
{
......
p = &timer_head;
save_flags(flags);
cli();
do {
p = p->next;
} while (timer->expires > p->expires);
timer->next = p;
timer->prev = p->prev;
p->prev = timer;
timer->prev->next = timer;
restore_flags(flags);
}
从代码实现可以看到,就是把当前进程添加到timer_head队列中, timer_head就是定时器队列.
把当前进程添加到定时器队列后,内核会在什么时候发送SIGALRM信号给进程呢?答案就是:时钟中断.
时钟中断:Linux的OS时钟的物理产生原因是可编程定时/计数器产生的输出脉冲,这个脉冲送入CPU,就可以引发一个中断请求信号,我们就把它叫做时钟中断。
简单的说,时钟中断就是硬件定时产生的一个电信号,然后发送给CPU, CPU接收到这个电信号就会做一些动作:调用中断处理函数.而Linux的时钟中断处理函数是do_timer(),而do_timer()会调用mark_bh(TIMER_BH)来开启定时器的下半部处理.定时器下半部会调用timer_bh()函数来处理,然后timer_bh()函数调用run_timer_list()函数来处理定时器. run_timer_list()代码如下:
static inline void run_timer_list(void)
{
struct timer_list * timer;
cli();
while ((timer = timer_head.next)
!= &timer_head && timer->expires <= jiffies)
{
void (*fn)(unsigned long) = timer->function;
unsigned long data = timer->data;
timer->next->prev = timer->prev;
timer->prev->next = timer->next;
timer->next = timer->prev = NULL;
sti();
fn(data);
cli();
}
sti();
}
从代码可以知道, run_timer_list()函数就是遍历timer_head队列,如果其中的定时器到期了,那就调用定时器的回调函数(timer的function字段).
而对于alarm定时器的回调函数是it_real_fn(). it_real_fn()函数代码如下:
void it_real_fn(unsigned long __data)
{
struct task_struct * p = (struct task_struct *)__data;
unsigned long interval;
send_sig(SIGALRM, p, 1);
interval = p->it_real_incr;
if (interval) {
unsigned long timeout = jiffies + interval;
if (timeout < interval)
timeout = ULONG_MAX;
p->real_timer.expires = timeout;
add_timer(&p->real_timer);
}
}
上面加粗的代码就是用来发送SIGALRM信号给进程.
从上面的分析可以知道,当进程调用sleep()函数时会调用alarm()系统调用设置一个定时器,然后调用sigsuspend()等待信号的发生.当定时器到期时,内核通过send_sig()函数发送SIGALARM信号给进程,进程收到SIGALARM信号后会被唤醒.返回搜狐,查看更多
责任编辑: