存档

文章标签 ‘不一致’

Linux VIRTT定时器设置时间与初始时间不一致的问题

2009年5月13日 没有评论

首先,来看一段测试程序,该程序设定了一个virtual 的定时器,然后用getitimer()得到当前的定时器的值
#include <sys/time.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main()
{
struct itimerval virtt;
virtt.it_interval.tv_sec = 1;
virtt.it_interval.tv_usec = 0;
virtt.it_value.tv_sec = 1;
virtt.it_value.tv_usec = 0;
setitimer(ITIMER_VIRTUAL, &virtt, NULL);

while (1)
{
getitimer(ITIMER_VIRTUAL,&virtt);
printf("tv_sec %ld\n",virtt.it_value.tv_sec);
printf("tv_usec %ld\n",virtt.it_value.tv_usec);
}
return 0;
}

运行程序得到测试结果如下:
tv_sec 1
tv_usec 4062
tv_sec 1
tv_usec 4062
tv_sec 1
tv_usec 4062

//好多好多重复
tv_sec 1
tv_usec 62
tv_sec 1
tv_usec 62
tv_sec 1
tv_usec 62

//好多好多重复
tv_sec 0
tv_usec 996062
tv_sec 0
tv_usec 996062
tv_sec 0
tv_usec 996062
tv_sec 0
tv_usec 996062
//…
//下面继续递减4000us(一个jiffy的值)
OK,为什么明明设置的是1s的定时器,怎么变成了1.004062s了,这多出来的是4062哪来的?让我们看一下Linux的源代码
首先找到do_setitimer这个函数,setitimer调用的就是这个函数,贴个代码片段:
int do_setitimer(int which, struct itimerval *value, struct itimerval *ovalue)
{
struct task_struct *tsk = current;
struct hrtimer *timer;
ktime_t expires;
cputime_t cval, cinterval, nval, ninterval;

/*
* Validate the timevals in value.
*/
if (!timeval_valid(&value->it_value) ||
!timeval_valid(&value->it_interval))
return -EINVAL;

switch (which) {
case ITIMER_REAL:
...
break;
case ITIMER_VIRTUAL:
nval = timeval_to_cputime(&value->it_value);//使用我们自己设置的定时器的数值,返回一个long赋值给nval
ninterval = timeval_to_cputime(&value->it_interval);
spin_lock_irq(&tsk->sighand->siglock);
cval = tsk->signal->it_virt_expires;
cinterval = tsk->signal->it_virt_incr;
if (!cputime_eq(cval, cputime_zero) ||//如果nval或者cval的值不为0
!cputime_eq(nval, cputime_zero)) {
if (cputime_gt(nval, cputime_zero))//如果nval的值大于0,则给nval加上一个jiffy的时间,这就解释了为什么一开始设置的0一下子多了4000us,那还有多出来的62us是哪来的,让我们再来看一下上面的timeval_to_cputime函数是干什么的,请转到下一段代码片段
nval = cputime_add(nval,
jiffies_to_cputime(1));
set_process_cpu_timer(tsk, CPUCLOCK_VIRT,//开始设置进程的计时器,并将其挂到相应的队列
&nval, &cval);
}
tsk->signal->it_virt_expires = nval;
tsk->signal->it_virt_incr = ninterval;
spin_unlock_irq(&tsk->sighand->siglock);
if (ovalue) {
cputime_to_timeval(cval, &ovalue->it_value);
cputime_to_timeval(cinterval, &ovalue->it_interval);
}
break;
case ITIMER_PROF:
...
default:
return -EINVAL;
}
return 0;
}

下面是timeval_to_cputime的代码片段:

unsigned long sec = value->tv_sec;
long usec = value->tv_usec;

if (sec >= MAX_SEC_IN_JIFFIES){
sec = MAX_SEC_IN_JIFFIES;
usec = 0;
}
return (((u64)sec * SEC_CONVERSION) +
(((u64)usec * USEC_CONVERSION + USEC_ROUND) >>//这里有一个复杂的运算操作,这个值就是上面的nval,具体的宏定义以及这段代码的意思可以去看源码上面的说明
(USEC_JIFFIE_SC - SEC_JIFFIE_SC))) >> SEC_JIFFIE_SC;

紧接着,我们再来研究一下do_getitimer,和do_setitimer相反,这个函数通过调用cputime_to_timeval函数将当前定时器的值赋值给应用程序的timeval结构,下面是代码片段:

u32 rem;

value->tv_sec = div_u64_rem((u64)jiffies * TICK_NSEC,
NSEC_PER_SEC, &rem);
value->tv_usec = rem / NSEC_PER_USEC;//这里有一个除法操作,看到这里我猜多出来的62us就是因为上面的复杂运算和这个除法操作共同导致的

OK,现在我们已经从实现上了解了为什么我们设置的定时器一开始的时候与设置的值不同,那么,为什么Linux要这么设计呢?
贴一段因为英文说明:
Implementations may place limitations on the timer value. To make sure that a process gets at least as much time as requested, the timer value is rounded up to the next timer tick (a timer tick is the smallest supported value).
The timer value is rounded up to the next timer tick because, the timer will be initialize somewhere between timer ticks.
If a setitimer() is followed by a getitimer() without a timer tick in between, it is possible that the value returned by getitimer() may be more than the initial value requested by setitimer() due to this rounding.
简单的来说,就是Linux想让一个进程的计时开始时间一定要大于等于我们自己设置的时间。