内核代码(尤其是驱动程序)除了使用定时器或下半部机制以外还需要其他方法来推迟执行任务。这种推迟通常发生在等待硬件完成某些工作时,而且等待的时间往往非常短。有许多延迟方法(所有的延迟方法都应该在进城上下文中使用):
o. 忙等待:仅仅在想要延迟的时间是节拍的整数倍或者精确率要求不高时才可以使用。
o. 短延迟:比时钟节拍还短的延迟,并且要求延迟时间很精确。
void udelay(unsigned long usecs); //利用忙等待将任务延迟到指定的微秒数后运行
void mdelay(unsigned long msecs); //利用忙等待将任务延迟到指定的毫秒数后运行
o. schedule_timeout()
延迟执行的任务睡眠到指定的延迟时间耗尽后再重新运行。不能保证睡眠时间正好等于指定的延迟时间。调用之前需要把任务设置为TASK_INTERRUPTIBLE或者TASK_UNINTERRUPTIBLE状态,否则任务不会睡眠
o. 设置超时时间,在等待队列上睡眠
我凭着网上的资料和自己的理解来分析一下udelay的实现。
理论部分:udelay函数通过BogoMIPS实现。Bogo的意思是Bogus,MIPS的意思是每秒百万条指令(million of instructions per second)
BogoMIPS纪录处理器在给定时间内忙循环执行的次数(该值存放在loops_per_jiffy),所以udelay函数仅仅需要根据指定时间在1秒所占的比例,就可以知道执行多少次循环能达到要求的推迟时间。
代码分析:(内核版本2.6.20)
1. loops_per_jiffy在内核初始化的时候,在calibrate_delay函数内被计算(calibrate.c)
/*
* This is the number of bits of precision for the loops_per_jiffy. Each
* bit takes on average 1.5/HZ seconds. This (like the original) is a little
* better than 1%
*/
/* 计算的精度,这个值设定的越大,loops_per_jiffy的值越精确 */
#define LPS_PREC 8
void __devinit calibrate_delay(void)
{
unsigned long ticks, loopbit;
int lps_precision = LPS_PREC;
/* 不知道preset_lpj是什么,查了一下,说是可以在引导程序里面设置的 */
if (preset_lpj) {
loops_per_jiffy = preset_lpj;
printk("Calibrating delay loop (skipped)... "
"%lu.%02lu BogoMIPS preset\n",
loops_per_jiffy/(500000/HZ),
(loops_per_jiffy/(5000/HZ)) % 100);
} else if ((loops_per_jiffy = calibrate_delay_direct()) != 0) {
printk("Calibrating delay using timer specific routine.. ");
printk("%lu.%02lu BogoMIPS (lpj=%lu)\n",
loops_per_jiffy/(500000/HZ),
(loops_per_jiffy/(5000/HZ)) % 100,
loops_per_jiffy);
} else {
/* 先假设了loops_per_jiffy是一个挺大的数字1
loops_per_jiffy = (112);
printk(KERN_DEBUG "Calibrating delay loop... ");
/* 第一次循环,每次都将loops_per_jiffy扩大2倍 */
while ((loops_per_jiffy = 1) != 0) {
/* 下面这段是循环知道一个新的节拍的开始 */
/* wait for "start of" clock tick */
ticks = jiffies;
while (ticks == jiffies)
/* nothing */;
/* Go .. */
/* 纪录新的节拍开始的tick值 */
ticks = jiffies;
/* __delay下面说明,是执行loops_per_jiffy次指令 */
__delay(loops_per_jiffy);
/* 执行完了看看当前的ticks是否>0(意味着超过了1个节拍) */
/* 超过一个节拍就跳出循环 */
ticks = jiffies - ticks;
if (ticks)
break;
}
/*
* Do a binary approximation to get loops_per_jiffy set to
* equal one clock (up to lps_precision bits)
*/
/ *第二次循环,*/
/* 第一次循环找出的loops_per_jiffy是满足这样子的条件,*/
/* 即执行它超过一个节拍,但是loop_per_jiffy/2则不到一个节拍 */
/* 具体一个节拍需要多少次loops_per_jiffy, */
/* 则一定在[ loops_per_jiffy/2, loops_per_jiffy ]区间内。*/
/* 这次循环是更加精确的寻找 */
loops_per_jiffy >>= 1;
loopbit = loops_per_jiffy;
/* 从loops_per_jiffy / 2开始探测 */
/* 当前探测值 = loops_per_jiffy | loopbit */
/* lps_precision决定了循环的次数,从而影响了结果的精度 */
while (lps_precision-- && (loopbit >>= 1)) {
loops_per_jiffy |= loopbit;
ticks = jiffies;
while (ticks == jiffies)
/* nothing */;
ticks = jiffies;
__delay(loops_per_jiffy);
/* 大于一个tick,说明loops_per_jiffy取值过大 */
/* 恢复原来的loops_per_jiffy,且loopbit会在下个循环 */
/* 里将值变小*/
if (jiffies != ticks) /* longer than 1 tick */
loops_per_jiffy &= ~loopbit;
}
/* Round the value and print it */
/* loops_per_jiffy表示的是每个tick内执行的指令条数 */
/* 打印的结果应该是1/500000秒执行的指令条数,这只是打印结果 */
/* 并不影响loops_per_jiffy的值 */
printk("%lu.%02lu BogoMIPS (lpj=%lu)\n",
loops_per_jiffy/(500000/HZ),
(loops_per_jiffy/(5000/HZ)) % 100,
loops_per_jiffy);
}
}
2. 下面说明一下__delay函数(arch\i386\lib\delay.c)
static void delay_loop(unsigned long loops)
{
int d0;
__asm__ __volatile__(
"\tjmp 1f\n"
".align 16\n"
"1:\tjmp 2f\n"
".align 16\n"
"2:\tdecl %0\n\tjns 2b"
:"=&a" (d0)
:"0" (loops));
}
static void (*delay_fn)(unsigned long) = delay_loop;
void __delay(unsigned long loops)
{
delay_fn(loops);
}
主要观察delay_loop函数的汇编指令部分:把\t,\n都去掉,简化一下
L1: jmp 1f //跳转指令,1表示label 1,f表示forward,跳到L3指令处
L2: .align 16 //下一条指令对齐在16bit处,大概是这个意思吧。不管它了
L3:1: jmp 2f //跳转指令,1表示label 1,f表示forward,跳到L5指令处
L4: .align 16
L5:2: decl %0 //decl表示long型的dec(减1指令)
L6: jns 2b //jns判断是否有符号数,有符号则跳转到标签2(backward 2)
%0表示参数,由这个指定:"=&a" (d0),把减数的结果放到d0里面
:"0" (loops)); 表示loops使用%0一样的寄存器
它的意思就是从1跳到2,每次减少d0(一开始d0等于loops),如果d0>0则继续跳到2,循环执行直到d0==0
3. 下面来看看udelay的实现(arch\i386\lib\delay.c)
inline void __const_udelay(unsigned long xloops)
{
int d0;
xloops *= 4;
/* 这段汇编取出每CPU的loops_per_jiffy的值,*/
/* 乘上HZ表示每秒执行的指令条数,最后与xloops相乘 */
/* 乘法会溢出,将高32bit放入寄存器edx,将低32bit的值放入寄存器eax */
/* xloops保留了高32bit的值 */
__asm__("mull %0"
:"=d" (xloops), "=&a" (d0)
:"1" (xloops), "0"
(cpu_data[raw_smp_processor_id()].loops_per_jiffy * (HZ/4)));
/* 调用__delay函数,这里之所以要再++,我估计是弥补低32bit的精度损失 */
__delay(++xloops);
}
void __udelay(unsigned long usecs)
{
/* 微妙换算成秒,数值太小,乘上2**32,即左移32bit */
__const_udelay(usecs * 0x000010c7); /* 2**32 / 1000000 (rounded up) */
}
Linux内核学习之BogoMIPS值的计算
--------------------------------------------------------------------------------
作者:曹磊
对于每一个linux编程爱好者来说,他们都有一个共同的心愿,就是了解linux的内核。但是linux内核的庞大与复杂让人望而生畏。往往是鼓足勇气
一头扎进去,学得昏天黑地的,却没有学到什么。这里我想说,初学者不妨先学习学习内核中一些简单的函数,从中既可以得到乐趣,又能了解到内核的一些编程风
格。然后,再将linux划分成几个部分,如进程调度、内存管理等,对每个部分从原理上去把握了解。接着,在详细分析各个部分的具体实现。最后,各部分串
在一起,把过去单独分析时,不懂的地方加以重新了解。这样循环监禁,可以让我们更快更系统的学习linux的内核。
这是我对内核学习的一些理解,欢迎各位提宝贵意见。我今天向大家介绍的是linux内核中一个有趣的函数calibrate_delay()。
calibrate_delay()函数可以计算出cpu在一秒钟内执行了多少次一个极短的循环,计算出来的值经过处理后得到BogoMIPS
值,Bogo是Bogus(伪)的意思,MIPS是millions of instructions per
second(百万条指令每秒)的缩写。这样我们就知道了其实这个函数是linux内核中一个cpu性能测试函数。由于内核对这个数值的要求不高,所以内
核使用了一个十分简单而有效的算法用于得到这个值。这个值虽然不准确,但也足以令我们心动。如果你想了解自己机器的BogoMIPS,你可以察看
/proc/cpuinfo文件中的最后一行。在你知道了自己cpu的BogoMIPS之后,如果你觉得不过瘾,那么让我们一起来看看
calibrate_delay函数是怎么完成工作的。
下面是calibrate_delay的源代码,我在每行之前都加上了行号,以便讲解。
1 #define LPS_PREC 8
2 void __init calibrate_delay(void)
3 {
4 unsigned long ticks,loopbit;
5 int lps_precision=LPS_PREC
6
7 loops_per_sec=(1<<12);
8
9 printk(“Calibrating delay loop…”);
10 while(loops_per_sec<<=1) {
11 /* wait for “start of” clock tick */
12 ticks=jiffies;
13 while(ticks==jiffies)
14 /* nothing */;
15 /* Go… */
16 ticks=jiffies;
17 __delay(loops_per_sec);
18 ticks=jiffies-ticks;
19 if(ticks)
20 break;
21 }
22
23 /* Do a binary approximation to get loops_per_second set
24 * to equal one clock (up to lps_precision bits) */
25 loops_per_sec >>=1;
26 loopbit=loop_per_sec;
27 while(lps_precision-- && (loopbit >>=1) ) {
28 loops_per_sec |= loopbit;
29 ticks=jiffies;
30 while(ticks==jiffies);
31 ticks=jiffies;
32 __delay(loops_per_sec);
33 if(jiffies!=ticks) /* longer than 1 tick */
34 loops_per_sec &=~loopbit;
35 }
36 /* finally,adjust loops per second in terms of seconds
37 * instead of clocks */
38 loops_per_sec *= HZ;
39 /* Round the value and print it */
40 printk(“%lu.%02lu BogoMIPSn”,
41 (loops_per_sec+2500)/500000,
42 ((loops_per_sec+2500)/5000) % 100);
43 }
对calibrate_delay()函数分析如下:
1 定义计算BogoMIPS的精度,这个值越大,则计算出的BogoMIPS越精确。
7 loops_per_sec为每秒钟执行一个极短的循环的次数。
9 printk()是内核消息日志打印函数,用法同printf()函数。
10 第10至21行,是第一次计算loops_per_sec的值,这次计算只是一个粗略的计算,为下面的计算打好基础。
11 第11
至16行,是用于等待一个新的定时器滴答(它大概是百万分之一秒)的开始。可以想象我们要计算loops_per_sec的值,可以在一个滴答的开始时,
立即重复执行一个极短的循环,当一个滴答结束时,这个循环执行了多少次就是我们要求的初步的值,再用它乘以一秒钟内的滴答数就是
loops_per_sec的值。
12 系统用jiffies全局变量记录了从系统开始工作到现在为止,所经过的滴答数。它会被内核自动更新。这行语句用于记录当前滴答数到tick变量中。
13 注意这是一个没有循环体得空循环,第14行仅有一个“;”号。这条循环语句是通过判断tick的值与jiffies的值是否不同,来判断jiffies是否变化,即是否一个新的滴答开始了
16 记录下新的滴答数以备后用。
17 根据loops_per_sec值进行延时(及执行loop_per_sec次极短循环)。
18
以下三行用于判断执行的延时是否超过一个滴答。一般loops_per_sec的初始值并不大,所以循环会逐步加大loops_per_sec的值,直到
延时超过一个滴答。我们可以看出,前一次loops_per_sec的值还因太小不合适时,经过一次增大,它提高了两倍,满足了循环条件,跳出循环,而这
个值实在是误差太大,所以我们还要经过第二次计算。这里还要注意的是通过上面的分析,我们可以知道更加精确的loops_per_sec的值应该在现在的
值与它的一半之间。
23 这里开始就是第二次计算了。它用折半查找法在我们上面所说的范围内计算出了更精确的loops_per_sec的值。
25 义查找范围的最小值,我把它称为起点。
26 定义查找范围,这样我们就可以看到loop_per_sec的值在“起点”与“起点加范围(终点)”之间。
27 进入循环,将查找范围减半。
28 重新定义起点,起点在“原起点加27行减半范围”处,即新起点在原先起点与终点的中间。这时我们可以看出loops_per_sec在“新起点”与“新起点加减半范围(新终点)”之间。
29 第29至32行与第12至17行一致,都是等待新的滴答,执行延时。
33
如果延时过短,说明loops_per_sec的值小了,将会跳过这部分,再次进入循环。它将是通过不断的折半方式来增大。如果延时过长,说明
loops_per_sec的值大了,将起点重新返回原起点,当再次进入循环,由于范围减半,故可以达到减小的效果。
38
计算出每秒执行极短循环的次数。从这里我们可以看出它好像是个死循环,所以加入了lps_precision变量,来控制循环,即LPS_PREC越大,
循环次数越多,越精确。可能这些不太好懂,总的说来,它首先将loop_per_sec的值定为原估算值的1/2,作为起点值(我这样称呼它),以估算值
为终点值.然后找出起点值到终点值的中间值.用上面相同的方法执行一段时间的延时循环.如果延时超过了一个tick,说明loop_per_sec值偏
大,则仍以原起点值为起点值,以原中间值为终点值,以起点值和终点值的中间为中间值继续进行查找,如果没有超过一个tick,说明
loop_per_sec偏小,则以原中间值为起点值,以原终点值为终点值继续查找。
40 出BogoMIPS,并打印。
至此,我们就分析完了calibrate_delay()函数。你从中学到了什么没有?如果你还有什么不明白的地方,可以给我发Email,如果你认为有什么更好的方法,欢迎来信我们一同探讨,我的Email是:feixiangniao@sina.com。
阅读(3252) | 评论(0) | 转发(0) |