Chinaunix首页 | 论坛 | 博客
  • 博客访问: 149709
  • 博文数量: 21
  • 博客积分: 1400
  • 博客等级: 上尉
  • 技术积分: 355
  • 用 户 组: 普通用户
  • 注册时间: 2007-12-12 17:44
文章分类

全部博文(21)

文章存档

2011年(1)

2010年(5)

2009年(8)

2008年(7)

我的朋友

分类: LINUX

2009-02-09 11:02:50

    内核代码(尤其是驱动程序)除了使用定时器或下半部机制以外还需要其他方法来推迟执行任务。这种推迟通常发生在等待硬件完成某些工作时,而且等待的时间往往非常短。有许多延迟方法(所有的延迟方法都应该在进城上下文中使用):
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<<12 */

        loops_per_jiffy = (1<<12);

        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,2f表示forward到label 2,即跳到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) */
}

阅读(3116) | 评论(3) | 转发(1) |
0

上一篇:GCC汇编器语法

下一篇:浅谈Encoding(一)

给主人留下些什么吧!~~

chinaunix网友2010-01-19 11:00:13

好高深!!!(可乐)

Zetalog2009-09-07 16:31:37

一个ARCH特定实现需要实现__udelay和__delay。__delay就是一个loop要执行的代码。__udelay就是为了对付经过HZ和loops_per_jiffy计算后产生的误差,因为计算本身要消耗CPU时间。

zetalog2009-09-07 16:26:15

preset_lpj如果loops_per_jiffies已经校准,可以通过bootloader传入。