驱动程序利用时间有两种,一种是延时,一种是定时。参考李学松的深入linux设备驱动程序内核机制。
因为读BDBM 代码的时候,看到了ktime_t ktime_get (void); 这个内核函数调用,感觉需要将其彻底理顺。
首先书中说在源代码的.config中配置为CONFIG_HZ,我从自己的linux中/usr/src/linux......-generic/中找到.config文件。但是里面时候CONFIG_HZ_250 配置了,且CONFIG_HZ=250.并不是1000.表示1秒内发生250次时钟中断,每次中断都是一个jiffies.1000就表示1秒中断1000次。jiffies是4ms级别,再精确jiffies也无法满足要求了。而为什么jiffies要引出新的jiffies_64?是因为原来的jiffies是32位,那么在HZ=1000的情况下大约50天就会使jiffies溢出,虽然驱动程序中时间度量会够,但是有的服务器需要知道自己运行时间,因此要引入jiffies_64位来处理,原32位在于低32位。
要使用jiffies从设备驱动程序角度看,只用jiffies变量,头文件为:#include<linux/jiffies.h>和 unsigned long timestamp_1定义就可以使用了;
而BDBM代码中同样有包含#include<linux/jiffies.h>可以看到内核的函数基本上多加一个linux即可。
而对于头文件以及链接库问题,使用echo 'main(){}' | gcc -E -v - 注意这个命令,gcc可以换任意一种编译器,而且-E大写,-v是小写 最后一个-也是选项,与-v之间有空格的。
HZ这个变量可以直接用了,因为在系统内已经定义,自己在驱动用printk打印结果确实是250.
printk("HZ is %d\n",HZ);即可成功。而jiffies也是一个值,这个值是一直在变化的。同样在BDBM中打印可以显示jiffies值。
unsigned long j;
j=jiffies;
timestamp_1=jiffies+2*HZ; //这句话的意思是当前时候往后推2s,
timestamp_1=jiffies+3*HZ/250 //表示timestamp_1为未来的3毫秒 .
根据自己使用的两个时间函数打印出来的值:
struct timeval tv;
gettimeofday (&tv, NULL) ;
unsigned int temp= tv.tv_sec*1000000+tv.tv_usec;
这个是打印出来的usec值,而普通的包含jiffies是1s/250的基准,所以得到了temp值一直在变化,而jiffies值好长时间才会变化一次,显示结果如下:
temp=2424695878 jiffies=4314490130
temp=2428190755 jiffies=4314491004
所以从jiffies推出时间间隔为:(1004-130)/250=3.496s
而temp是:(8190755-4695878)/1000000=3.494877s差距不大。
实际使用jiffies的值的方法:
本来直接比较每个点的jiffies值,但是可能有溢出这样比较就不准确,所以提供了一组宏api来使用,对可能的溢出做了处理了。
分别为:time_after(a,b); 如果时间点a在时间点b之后,该宏返回true.可以如此记忆:将after这个单词放在两个参数之间。
time_before(a,b);
time_after_eq(a,b);该宏与time_after类似,但是当a=b时候也返回true.
time_before_eq(a,b);
time_in_range(a,b,c); 当 b=<a<=c的时候,返回true.
上述a, b定义的时候,使用usigned long型变量即可。 对于jiffies_64类型来说上说所有函数使用时候后面带上64例如:time_after64即可。
上述宏的应用场景如下:
当一个驱动中一个函数需要调用另外一个函数完成一个任务,但是对完成时间有要求,如果超过8ms就算是超时了,那么例子如下:
int demo_funciton()
{
usigned long timeout=jiffies+2*HZ/250;
do_time_task();
if(timeafter(timeout,jiffies))
{
printK("already time out");
return 1;
}
return 0;
}
二、时间转换,
有时候需要将jiffies转换为人能阅读的时间,例如:ms,us.但是此处精度仍然是jiffies自身精度,并不是ms,us精度。例如在驱动程序中打印出一次DMA传输花费时间。dma传输前记录一次jiffies,在结束之后记录一次jiffies,然后使用jiffies_to_msecs(end_jiffies- start_jiffies)函数即可。此时输出值单位是ms.
同样是在#include<linux/jiffies.h>中。
含有:jiffies_to_msecs jiffies_to_usecs msecs_to_jiffies usecs_to_jiffies 看着简单,但是返回值类型和参数类型很重要。
但是该书籍并不讲怎么获取其他精度方法,因为jiffies是各个平台通用,所以只讲jiffies.
三、更高精度时间获取和使用
注意,用户编写的驱动代码肯定想看真正时间,ms,us因此使用上述转换方式。而如果想要更高精度时间,可以使用两个函数:
gettimeofday();
但是从网上查到的,头文件真的是各种各样,有包含#include<sys/time.h>的,还有包含#incldue<linux/time.h>的,都声称可以使用,真的是千奇百怪啊。
按道理,自己验证的时候,使用sys/time.h就是说得正确,但是自己查看/usr/include下面根本没有sys文件夹,这是 ubuntu环境普遍存在的问题,有的即使有/usr/include/sys文件夹也可能没有time.h,那么怎么办?按道理添加即可,网友也给出了答案,一个安装包就能生成/usr/include/sys问题,例如:http://blog.csdn.net/pengwangguo/article/details/52891608
这个网友在ide上添加了真实的/usr/include/x86_64-linux-gnu/sys/socket.h这个路径也不行,最后,
- sudo apt-get install build-essential flex libelf-dev libc6-dev-amd64 binutils-dev libdwarf-dev
这个是将所有缺失的包全部安装了,然后/usr/include/sys就出现了,成功了。只是里面都是链接文件,指向仍然是/usr/include/x86_64-linux-gnu/sys的内容。
所以按照上述解决问题,是正确的,再往上推,直接使用#include<sys/time.h>也是正确的,只要这个路径里面time.h指向/usr/include/x86_64-linux-gnu/sys里的time.h并且time.h可以找到gettimeofday这个函数声明就对了。
还有个知识点:include<time.h>是c库的include<sys/time.h>是linux系统自带的,就像里面有gettimeofday这样的函数。
(到这个地方已经错的不能再错了。下面总结处给出详细解释,请跳跃。)
但是我的问题更奇怪,就是使用的BDBM竟然可以使用gettimeofday函数。就是在我没有/usr/include/sys文件目录的情况下。真是不知道BDBM 是怎么处理的。搞的我都想从简单的时间函数学习上重新跳到makefile文件中去了。。。。。。!!!!!!好烦。
那就按照上篇博客进行学习吧,下载Managing Projects with GNU Make这本书,开始阅读,用于解决看BDBM是怎么可以直接使用gettimeofday函数的。因为这个函数涉及到的头文件我都实验过了,自己编写的代码中直接抄它的头文件都不可行,一定是makefile中的设定,但是CFLAGS也没有见到明确的指向/usr/include/x86_64-linux-gnu/sys的 路径,所以好奇怪。因此学习gnu make这本书。早晚要学习。
真的糊涂了,什么叫做用户态编程?什么叫做内核态编程,为什么gcc -o这种普通的编译使用#include<sys/time.h>就可以用到gettimeofday这个函数?而使用内核模块的编码却不能。实在是不知道啊。怎么回事。
有人说:http://blog.csdn.net/macrossdzh/article/details/4488677
/usr/include/linux是用来编译当前系统的程序的;
/usr/src/linux/include/linux/是用来编译内核的,
而怎么识别编译内核模块,是看kbuild的,普通的用户态是usr/include/linux里面调用即可,是普通的gcc即可。
http://blog.csdn.net/creator_ly/article/details/53097053
看了几十篇网上回答,只有上述是正确或者说理解很清晰的。
最终总结如下:其实看编程目的了,如果是纯粹使用c函数库,那么直接使用#include<time.h>即可。但是与系统完全无关,虽然会间接调用,但是完全隔离的。并且c库中的time.h只有很粗糙的颗粒,最小到毫秒,有struct tm{}结构体,等。c语言中最精密的是到毫秒,一个简单代码为:
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
int main(void)
{
long i=10000000L;
clock_t start,finish;
double duration;
start=clock();
while(i--);
finish=clock();
duration=(double)(finish-start)/CLOCKS_PER_SEC;
printf("%f seconds\n",duration);
}
编译就是普通的gcc编译即可。
第二、如果使用linux的系统函数来做虽然是使用的#include<sys/time.h>但是用户应用程序使用linux系统函数的有各种,#include<linux/xxx>
第三、如果使用kbuild编译的那种内核驱动代码的编写,同样使用#include<linux/xxx>但是根据makefile文件识别出来二、三区别就应该马上进入不同的路径找看似相同的#include<linux/xxx> 第二种的路径是在/usr/inlude/下面接linux或者x86_64-linux-gnu表示sys文件夹。如上sys/time.h是在x86_64-linux-gnu中的sys文件夹下面。 第三种路径是在/usr/src/linux-xxxx下面的include/linux下面,为什么看着这么远,因为kbuild编译器直接在/usr/src/linux-xxx中,所以相对 路径都是include/linux/xxx.h这种
只是最后发现是内核驱动使用的获取struct timeval值是do_gettimeofday()函数,虽然是在/usr/src/linux-xx/include/linux/time.h中,这么远,写在头文件中是#include<linux/time.h>.,而linux系统给用户使用的是/usr/include/x86_64-linux-gnu/sys/time.h中,写在头文件中是#include<sys/time.h>
在第三种kbuild编译中其实已经出现了do_gettimeofday()这个定义是在linux/timekeeping.h中,而BDBM因为kenelmodle和usermodle合在一起使用的,其中#define do_gettimeofday() gettimeofday()这句话最有迷惑性,是作用在usermodle中的,对第二种的/sys/time.h中的gettimeofday()定义名称的,然后可以做到与kernelmodle同时使用do_gettimeofday()这种假象害死人。。。。并且 do_gettimeofday()函数是参数只有一个struct timeval tv;传输地址作为指针。而gettimeofday()内部两个参数,一个也是&tv,一个是时区timezone.一般使用成0即可。
从这个地方接上面书本知识,时间管理,内核函数还有delay分别是三种: void mdelay(usigned long msecs);
void udelay (usigned long usecs); void ndelay(usigned long nsecs);
http://blog.csdn.net/Dreaming_My_Dreams/article/details/7745148
这个是专门对内核编程中的头文件说明,其中有几个很好:
#include <linux/***.h> 是在linux-2.6.29/include/linux下面寻找源文件。
#include <asm/***.h> 是在linux-2.6.29/arch/arm/include/asm下面寻找源文件。
#include <mach/***.h> 是在linux-2.6.29/arch/arm/mach-s3c2410/include/mach下面寻找源文件。
以及各种头文件的作用。
还有:struct timeval 结构是s和us 是do_gettimeofday(&tv)获得。
struct timeval {
time_t tv_sec;
suseconds_t tv_usec;
};
struct timespec是 void do_gettimeofday_nsec(struct timespec *tv)函数获得
struct timespec
{
time_t tv_sec;
long int tv_nsec;
};