(笔记)Linux下的准确延时,#includelinuxdelay.h调⽤出错
在编写应⽤层程序时,有时需要延时⼀下,这个时候该怎么办呢?
在内核代码中,我们经常会看到这样的头⽂件使⽤#include <linux/delay.h>,⼼想着直接调⽤这个就可以了吧!可是在编译时发现,压根通不过,
提⽰错误如下:error: No such file or directory.
是不是觉得很奇怪,明明⽂件是存在的,怎么就不能调⽤了,⽽且内核很多⽂件调⽤得很欢。这是为什么呢?
因为内核程序跟应⽤程序是有区别的,有些特殊的内核头⽂件编译器不允许被应⽤程序调⽤。故编译应⽤程序使⽤内核的头⽂件,报错是难免的
但是,这个时候该怎么呢?
哈哈!#include <unistd.h>头⽂件出现了!功能与#include <linux/delay.h>⼀致,但是可以在应⽤层随便调⽤。不错的东西吧!以下是其详细介绍:
应⽤层:
#include <unistd.h>
1、unsigned int sleep(unsigned int seconds); 秒级
2、int usleep(useconds_t usec); 微秒级:1/10^-6
#define _POSIX_C_SOURCE 199309
#include <time.h>
3、int nanosleep(const struct timespec *req, struct timespec *rem);
struct timespec {
time_t tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};
// The value of the nanoseconds field must be in the range 0 to 999999999.
内核层:
include <linux/delay.h>
1、void ndelay(unsigned long nsecs); 纳秒级:1/10^-10
2、void udelay(unsigned long usecs); 微秒级: 1/10^-6
3、void mdelay(unsigned long msecs); 毫秒级:1/10^-3
udelay⽤软件循环指定的微妙数,mdelay调⽤前者达到延迟毫秒级。udelay 函数只能⽤于获取较短的时间延迟,因为loops_per_second值的精度只有8位,所以,当计算更长的延迟时会积累出相当⼤的误差。尽管最⼤能允许的延迟将近1秒(因为更长的延迟就要溢出),推荐的 udelay 函数的参数的最⼤值是取1000微秒(1毫秒)。延迟⼤于 11毫秒时可以使⽤函数 mdelay。mdelay 在 Linux 2.0 中并不存在,头⽂件 sysdep.h 弥补了这⼀缺陷。
要特别注意的是 udelay 是个忙等待函数(所以 mdelay 也是),在延迟的时间段内⽆法运⾏其他的任务,因此要⼗分⼩⼼,尤其是 mdelay,除⾮别⽆他法,要尽量避免使⽤。
⾸先, 我会说不保证你在使⽤者模式 (user-mode) 中执⾏的⾏程 (process) 能够精确地控制时序因为 Linu
x 是个多⼯的作业环境. 你在执⾏中的⾏程 (process) 随时会因为各种原因被暂停⼤约 10 毫秒到数秒 (在系统负荷⾮常⾼的时候). 然⽽, 对於⼤多数使⽤ I/O 埠的应⽤⽽⾔, 这个延迟时间实际上算不了什麽. 要缩短延迟时间, 你得使⽤函式nice 将你在执⾏中的⾏程 (process ) 设定成⾼优先权(请参考nice(2)使⽤说明⽂件) 或使⽤即时排程法 (real-time scheduling) (请看下⾯).
如果你想获得⽐在⼀般使⽤者模式 (user-mode) 中执⾏的⾏程 (process) 还要精确的时序, 有⼀些⽅法可以让你在使⽤者模式 (user-mode) 中做到 `即时' 排程的⽀援. Linux 2.x 版本的核⼼中有软体⽅式的即时排程⽀援; 详细的说明请参考 sched_setscheduler(2)
使⽤说明⽂件. 有⼀个特殊的核⼼⽀援硬体的即时排程; 详细的资讯请参考⽹页 du/~rtlinux/
休息中 (Sleeping) :
sleep() 与 usleep()
现在, 让我们开始较简单的时序函式呼叫. 想要延迟数秒的时间, 最佳的⽅法⼤概是使⽤函式 sleep(). 想要延迟⾄少数⼗毫秒的时间 (10 ms 似乎已是最短的延迟时间了),函式 usleep()应该可以使⽤.
这些函式是让出 CPU 的使⽤权给其他想要执⾏的⾏程 (processes) (“⾃⼰休息去了''), 所以没有浪费掉 CPU 的时间. 细节请参考: sleep(3) 与 usleep(3) 的说明⽂件.
如果让出 CPU 的使⽤权因⽽使得时间延迟了⼤约 50 毫秒 (这取决於处理器与机器的速度, 以及系统的负荷), 就浪费掉 CPU 太多的时间, 因为 Linux 的排程器 (scheduler) (单就 x86 架构⽽⾔) 在将控制权发还给你的⾏程 (process) 之前通常⾄少要花费 10-30 毫秒的时间. 因此, 短时间的延迟, 使⽤函式 usleep(3) 所得到的延迟结果通常会⼤於你在参数所指定的值, ⼤约⾄少有 10 ms.
nanosleep()
在 Linux 2.0.x ⼀系列的核⼼发⾏版本中, 有⼀个新的系统呼叫 (system call),nanosleep() (请参考 nanosleep(2)的说明⽂件), 他让你能够休息或延迟⼀个短的时间 (数微秒
如果延迟的时间 <= 2 ms, 若(且唯若)你执⾏中的⾏程 (process) 设定了软体的即时排程 (就是使⽤函式 tt/sched_setscheduler()/), 呼叫函式 nanosleep() 时不是使⽤⼀个忙碌回圈来延迟时间; 就是会像函式 usleep() ⼀样让出 CPU 的使⽤权休息去了.
这个忙碌回圈使⽤函式 udelay() (⼀个驱动程式常会⽤到的核⼼内部的函式) 来达成, 并且使⽤ BogoMips 值 (BogoMips 可以准确量测这类忙碌回圈的速度) 来计算回圈延迟的时间长度. 其如何动作的细节(请参考/usr/include/asm/delay.h).
使⽤ I/O 埠来延迟时间
另⼀个延迟数微秒的⽅法是使⽤ I/O 埠. 就是从埠位址 0x80 输⼊或输出任何 byte 的资料 (请参考前⾯) 等待的时间应该⼏乎只要 1 微秒这要看你的处理器的型别与速度.如果要延迟数微秒的时间你可以将这个动作多做⼏次. 在任何标准的机器上输出资料到该埠位址应该不会有不良的後果□对 (⽽且有些核⼼的设备驱动程式也在使⽤他). {in|out}[bw]_p()等函式就是使⽤这个⽅法来产⽣时间延迟的 (请参考档案asm/io.h).
实际上, ⼀个使⽤到埠位址范围为 0-0x3ff 的 I/O 埠指令⼏乎只要 1 微秒的时间, 所以如果你要如此做, 例如, 直接使⽤并列埠, 只要加上⼏个inb()函式从该埠位址□围读⼊byte 的资料即可.
使⽤组合语⾔来延迟时间
如果你知道执⾏程式所在机器的处理器型别与时钟速度, 你可以执⾏某些组合语⾔指令以便获得较短的延迟时间 (但是记住, 你在执⾏中的⾏程 (process) 随时会被暂停, 所以有时延迟的时间会⽐实际长). 如下⾯的表格所⽰, 内部处理器的速度决定了所要使⽤的时钟周期数; 如, ⼀个 50 MHz 的处理器 (486DX-50 或 486DX2-50), ⼀个时钟周期要花费 1/50000000 秒 (=200 奈秒).
指令 i386 时钟周期数 i486 时钟周期数
nop 3 1
xchg %ax,%ax 3 3
or %ax,%ax 2 1
mov %ax,%ax 2 1
add %ax,0 2 1
(对不起, 我不知道 Pentiums 的资料, 或许与 i486 接近吧. 我⽆法在 i386 的资料上到只花费⼀个时钟周期的指令. 如果能够就请使⽤花费⼀个时钟周期的指令, 要不然就使⽤管线技术的新式处理器也是可以缩短时间的.)
上⾯的表格中指令 nop 与 xchg 应该不会有不良的後果. 指令最後可能会改变旗号暂存器的内容, 但是这没关系因为 gcc 会处理. 指令 nop 是个好的选择.
想要在你的程式中使⽤到这些指令, 你得使⽤ asm("instruction"). 指令的语法就如同上⾯表格的⽤法; 如果你想要在单⼀的asm()叙述中使⽤多个指令, 可以使⽤分号将他们隔开.
例如,
asm("nop ; nop ; nop ; nop")
会执⾏四个 nop 指令, 在 i486 或 Pentium 处理器中会延迟四个时钟周期 (或是 i386 会延迟 12 个时钟周期).
gcc 会将 asm() 翻译成单⾏组合语⾔程式码, 所以不会有呼叫函式的负荷.
在 Intel x86 架构中不可能有⽐⼀个时钟周期还短的时间延迟.
在 Pentiums 处理器上使⽤函式 rdtsc
对於 Pentiums 处理器⽽⾔, 你可以使⽤下⾯的 C 语⾔程式码来取得⾃从上次重新开机到现在经过了多少个时钟周期:
extern __inline__ unsigned long long int rdtsc()
{
unsigned long long int x;
__asm__ volatile (".byte 0x0f, 0x31" : "=A" (x));
return x;
}
你可以询问参考此值以便延迟你想要的时钟周期数.
想要时间精确到⼀秒钟, 使⽤函式 time() 或许是最简单的⽅法. 想要时间更精确, 函式 gettimeofday() ⼤约可以精确到微秒 (但是如前所述会受到 CPU 排程的影响). ⾄於Pentiums 处理器, 使⽤上⾯的程式码⽚断就可以精确到⼀个时钟周期.
如果你要你执⾏中的⾏程 (process) 在⼀段时间到了之後能够被通知 (get a signal), 你得使⽤函式 setitimer() 或 alarm(). 细节请参考函式的使⽤说明⽂件.
#include <syswait.h>
usleep(n) //n微秒
Sleep(n)//n毫秒
sleep(n)//n秒
驱动程序:
#include <linux/delay.h>
mdelay(n) //milliseconds 其实现
#ifdef notdef
#define mdelay(n) (\
c语言编译器怎么用文件格式提交作业{unsigned long msec=(n); while (msec--) udelay(1000);})
#else
#define mdelay(n) (\
(__builtin_constant_p(n) && (n)<=MAX_UDELAY_MS) ? udelay((n)*1000) : \
({unsigned long msec=(n); while (msec--) udelay(1000);}))
#endif
调⽤ asm/delay.h的udelay,udelay应该是纳秒级的延时
Dos:
sleep(1); //停留1秒
delay(100); //停留100毫秒
Windows:
Sleep(100); //停留100毫秒
Linux:
sleep(1); //停留1秒
usleep(1000); //停留1毫秒
每⼀个平台不太⼀样, 最好⾃⼰定义⼀套跨平台的宏进⾏控制秒还是微秒?
关于延时函数sleep()
因为要写⼀段代码,需要⽤到sleep()函数,在我印象中,sleep(10)好像是休眠10微秒,结果却是休眠了10秒(在Linux下)。觉得很奇怪,因为头⼉也记得好像是微秒为单位的。所以就查了⼀下。
原来linux下的sleep函数原型为:
unsigned int sleep(unsigned int seconds);
⽽MFC中的 Sleep函数原型为:
void Sleep(DWORD dwMilliseconds);
也就是说,Linux下(使⽤的gcc的库),sleep()函数是以秒为单位的,sleep(1);就是休眠1秒。⽽MFC下的sleep()函数是以微秒为单位的,sleep(1000); 才是休眠1秒。原来如此啊。⽽如果在Linux下也⽤微妙为单位休眠,可以使⽤线程休眠函数:void usleep(unsigned long usec);当然,使⽤的时候别忘记#include <system.h>哦。
另外值得⼀提的是,linux下还有个delay()函数,原型为extern void delay(unsigned int msec);它可以延时msec*4毫秒,也就是如果想延时⼀秒钟的话,可以这么⽤delay(250);
当⼀个设备驱动需要处理它的硬件的反应时间, 涉及到的延时常常是最多⼏个毫秒.
#include <linux/delay.h>
void ndelay(unsigned long nsecs);
void udelay(unsigned long usecs);
void mdelay(unsigned long msecs);
有另⼀个⽅法获得毫秒(和更长)延时⽽不⽤涉及到忙等待. ⽂件 <linux/delay.h> 声明这些函数:
void msleep(unsigned int millisecs);
unsigned long msleep_interruptible(unsigned int millisecs);
void ssleep(unsigned int seconds)
前 2 个函数使调⽤进程进⼊睡眠给定的毫秒数. ⼀个对 msleep 的调⽤是不可中断的; 你能确保进程睡眠⾄少给定的毫秒数. 如果你的驱动位于⼀个等待队列并且你想唤醒来打断睡眠, 使⽤ msleep_interruptible. 从 msleep_interruptible 的返回值正常地是 0; 如果, 但是, 这个进程被提早唤醒, 返回值是在初始请求睡眠周期中剩余的毫秒数. 对ssleep 的调⽤使进程进⼊⼀个不可中断的睡眠给定的秒数.
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论