Linux设备驱动程序第三版学习(10)- 时间、延迟及延缓操作
2011-01-16 10:26
483 查看
接下来学习第七章:时间、延迟及延缓操作。本章主要学习了内核代码如何对时间问题进行处理。
关于Linux时钟处理机制的详细内容,参考本博客转载的“Linux时钟处理机制”一文【赵健博(zhaojianbo@ncic.ac.cn),硕士,中国科学院计算技术研究所】一、时间。
内核通过定时器中断来跟踪时间流。定时器中断是硬件产生的,是周期性的。不同的硬件平台的周期不一样,例如x86PC上默认的是1000次/秒。内核维护一个内部的计数器,这个计数器在系统引导时被初始化为0,每次中断发生时,计数器+1。也就是在x86上此计数器每秒钟增加1000。这个计数器是一个64位变量,称为“jiffies_64”。而作为驱动程序的开发者,通常访问的是jiffies变量,unsignedlong型。以x86PC平台为例,该计数器每隔大约50天会溢出一次。计算方法是4294967296次(2的32次方)/1000次/3600秒/24小时=49.7102696只要包含头文件,就可以读取jiffies变量。如果需要比较两个jiffies的快照的时间前后关系,可以使用下面的是个宏:
[code]inttime_before(unsignedlonga,unsignedlongb);
[/code]为了在必要时将用户空间的时间表述和内核表述进行转换,内核提供了下面四个辅助函数:
[code]unsignedlongtimespec_to_jiffies(structtimespec*value);
[/code]可以用看出上面的方法来度量时间是有局限的,主要在度量非常短的时间或者需要极高时间精度时上面方法就无能为力了,这时就要使用处理器特定寄存器。
绝大多数现代处理器都包含一个计数寄存器,其中最有名的当属Pentium处理器的TSC(timestampcounter,时间戳计数器)。TSC是一个64位的寄存器,记录了CPU时钟周期数,内核空间和用户空间都可以读这个寄存器。读取的方法是:
[code]
[/code]*内核中do_gettimeofday函数可以最高得到微秒级的分辨率,这取决于体系结构。
*内核还提供了一个辅助函数current_kernel_time来获得时间:
#include externunsignedlongmktime(constunsignedintyear,constunsignedintmon, constunsignedintday,constunsignedinthour, constunsignedintmin,constunsignedintsec);
[code]voidadd_timer(structtimer_list*timer);
[/code]
[code]intlen,int*eof,void*unused_data)
[/code]
[code]{
[/code]
[/code]
关于Linux时钟处理机制的详细内容,参考本博客转载的“Linux时钟处理机制”一文【
内核通过定时器中断来跟踪时间流。定时器中断是硬件产生的,是周期性的。不同的硬件平台的周期不一样,例如x86PC上默认的是1000次/秒。内核维护一个内部的计数器,这个计数器在系统引导时被初始化为0,每次中断发生时,计数器+1。也就是在x86上此计数器每秒钟增加1000。这个计数器是一个64位变量,称为“jiffies_64”。而作为驱动程序的开发者,通常访问的是jiffies变量,unsignedlong型。以x86PC平台为例,该计数器每隔大约50天会溢出一次。计算方法是4294967296次(2的32次方)/1000次/3600秒/24小时=49.7102696只要包含头文件,就可以读取jiffies变量。如果需要比较两个jiffies的快照的时间前后关系,可以使用下面的是个宏:
inttime_after(unsignedlonga,unsignedlongb);
[code]inttime_before(unsignedlonga,unsignedlongb);
inttime_after_eq(unsignedlonga,unsignedlongb);
inttime_before_eq(unsignedlonga,unsignedlongb);
[/code]为了在必要时将用户空间的时间表述和内核表述进行转换,内核提供了下面四个辅助函数:
#include
[code]unsignedlongtimespec_to_jiffies(structtimespec*value);
voidjiffies_to_timespec(unsignedlongjiffies,structtimespec*value);
unsignedlongtimeval_to_jiffies(structtimeval*value);
voidjiffies_to_timeval(unsignedlongjiffies,structtimeval*value);
[/code]可以用看出上面的方法来度量时间是有局限的,主要在度量非常短的时间或者需要极高时间精度时上面方法就无能为力了,这时就要使用处理器特定寄存器。
绝大多数现代处理器都包含一个计数寄存器,其中最有名的当属Pentium处理器的TSC(timestampcounter,时间戳计数器)。TSC是一个64位的寄存器,记录了CPU时钟周期数,内核空间和用户空间都可以读这个寄存器。读取的方法是:
#include//machine-specificregisters,意为机器特有的寄存器
rdtsc(low32,high32);//把TSC里的64位数值读到两个32位变量中,该操作是原子的
rdtscl(low32);//只把TSC的低半部分读入一个32位变量
rdtscll(var64);//把TSC里的64位数值保存到一个longlong变量中
[code]
注:使用处理器特定寄存器会带来代码的兼容性问题,因为该寄存器是平台相关的。
获取当前时间的方法:
*一般情况,使用jiffies来获取时间
*如果需要更精确的时间,需要使用TSC
*内核提供了将墙钟时间(年,月,日)转换为jiffies值的函数:
#include externunsignedlongmktime(constunsignedintyear,constunsignedintmon, constunsignedintday,constunsignedinthour, constunsignedintmin,constunsignedintsec);
[/code]*内核中do_gettimeofday函数可以最高得到微秒级的分辨率,这取决于体系结构。
*内核还提供了一个辅助函数current_kernel_time来获得时间:
#include externunsignedlongmktime(constunsignedintyear,constunsignedintmon, constunsignedintday,constunsignedinthour, constunsignedintmin,constunsignedintsec);
二、延迟的技术。 长延迟: 当需要延迟比较长的时间时,最好的方法是基于jiffies的超时延迟。 *可以使用wait_event_timeout或者wait_event_interruptible_timeout函数,其中timeout值表示的是要等待的jiffies值。 #include longwait_event_timeout(wait_queue_head_tq,condition,longtimeout); longwait_event_interruptible_timeout(wait_queue_head_tq,condition,longtimeout);
*也可以使用schedule_timeout函数,其中timeout是用jiffies表示的延迟时间。
短延迟: 当需要延迟比较短的时间时,比如说微妙级的延迟,就需要用到几个短延迟的内核函数。 #include voidndelay(unsignedlongnsecs); voidudelay(unsignedlongusecs); voidmdelay(unsignedlongmsecs);
三、内核定时器 内核定时器的使用非常简单,内核专门提供了一组API用来声明、注册、删除内核定时器。 这组API如下:
voidinit_timer(structtimer_list*timer);
[code]voidadd_timer(structtimer_list*timer);
intdel_timer(structtimer_list*timer);
[/code]
下面看看具体的应用代码,此代码就是jit模块中的jit_timer函数。
intjit_timer(char*buf,char**start,off_toffset,
[code]intlen,int*eof,void*unused_data)
{
structjit_data*data;//timer_list结构和tasklet_struct结构都被放在了jit_data结构中
char*buf2=buf;
unsignedlongj=jiffies; //读取当前的jiffies值
data=kmalloc(sizeof(*data),GFP_KERNEL);//给结构变量分配内存
if(!data)
return-ENOMEM;
init_timer(&data->timer);//使用了第一个APIinit_timer来初始化定时器,参数是一个timer_list结构体变量
init_waitqueue_head(&data->wait);//初始化等待队列头
/*writethefirstlinesinthebuffer*/
buf2+=sprintf(buf2,"timedeltainirqpidcpucommand/n");/*我们想要打印的信息
,依次为:时间、时间差、是否在中断上下文、进程id、多处理器时cpuid、当前命令*/
buf2+=sprintf(buf2,"%9li%3li%i%6i%i%s/n",
j,0L,in_interrupt()?1:0,
current->pid,smp_processor_id(),current->comm);
/*fillthedataforourtimerfunction*/
data->prevjiffies=j;//保存原来的jiffies值到data->prevjiffies中
data->buf=buf2;//保存打印内容到data->buf中
data->loops=JIT_ASYNC_LOOPS;//保存想要循环打印的次数到data->loops中,#defineJIT_ASYNC_LOOPS5
/*registerthetimer*/
data->timer.data=(unsignedlong)data;//data中保存回调函数的参数,这里把jit_data结构的指针传递进来
data->timer.function=jit_timer_fn;//回调函数。就是定时器到期后要执行的操作。这里主要执行了打印操作和loops减操作
data->timer.expires=j+tdelay;//到期时间,以时钟滴答为单位。inttdelay=10
add_timer(&data->timer);//使用了第二个APIadd_timer将定时器添加到相应调用此函数的CPUbase中,启动了定时器中断
/*waitforthebuffertofill*/
wait_event_interruptible(data->wait,!data->loops);/*在jit_timer_fn中,loops每循环一次减1,进程在此期间休眠,知道
loops为0*/
if(signal_pending(current))
return-ERESTARTSYS;
buf2=data->buf;//保存缓冲区内容
kfree(data);//释放内存
*eof=1;
returnbuf2-buf;
}
[/code]
使用内核定时器需要特别注意的是,定时器的那个回调函数的设计有诸多限制,之所以有这些限制大概是因为这类似与中断处理程序吧,:
*不能访问用户控件。因为没有进程上下文,无法将任何特定进程与用户空间关联起来。
*current指针在原子模式下是没有任何意义的,也不可用。因为相关代码和被中断的进程没有任何关联。只有一种情况,在持有自旋锁这种情况,current可能
是有效的。但是禁止访问用户空间。
*不能执行休眠或调度。不可以调用schedule或者wait_event,也不能调用任何引起休眠的函数,如kmalloc,也不能用信号量。当然更不能用sleep了。哈哈
我就试了一下sleep,效果杠杠滴。
四、tasklet(小任务)[code]也是来看一下具体的代码吧,此代码就是jit模块中的jit_tasklet函数。基本上和jit_timer差不多。不解释了。
tasklet的使用也非常简单,内核也提供了一些API来完成初始化和调度。这组API如下:voidtasklet_init(structtasklet_struct*t,void(*func)(unsignedlong),unsignedlongdata); /*初始化tasklet,其中func
就是调度tasklet要执行的函数。*/
voidtasklet_disable(structtasklet_struct*t);/*禁用tasklet,即使tasklet被调用也不会执行,直到tasklet_enable*/
voidtasklet_disable_nosync(structtasklet_struct*t);/*也是禁用tasklet,与上一个函数不同的是如果tasklet正在运行,不会等待而直接
返回*/
voidtasklet_enable(structtasklet_struct*t);/*启用一个被禁用的tasklet*/
voidtasklet_schedule(structtasklet_struct*t);/*调度执行一个tasklet*/
voidtasklet_hi_schedule(structtasklet_struct*t);/*调度指定的tasklet以高优先级执行*/
voidtasklet_kill(structtasklet_struct*t);/*顾名思义,这个函数确保tasklet不会被再次调度*/
intjit_tasklet(char*buf,char**start,off_toffset,intlen,int*eof,void*art)
[code]{
structjit_data*data;
char*buf2=buf;
unsignedlongj=jiffies;
longhi=(long)arg;
data=kmalloc(sizeof(*data),GFP_KERNEL);
if(!data)
return-ENOMEM;
init_waitqueue_head(&data->wait);
/*writethefirstlinesinthebuffer*/
buf2+=sprintf(buf2,"timedeltainirqpidcpucommand/n");
buf2+=sprintf(buf2,"%9li%3li%i%6i%i%s/n",
j,0L,in_interrupt()?1:0,
current->pid,smp_processor_id(),current->comm);
/*fillthedataforourtaskletfunction*/
data->prevjiffies=j;
data->buf=buf2;
data->loops=JIT_ASYNC_LOOPS;
/*registerthetasklet*/
tasklet_init(&data->tlet,jit_tasklet_fn,(unsignedlong)data);
data->hi=hi;
if(hi)
tasklet_hi_schedule(&data->tlet);
else
tasklet_schedule(&data->tlet);
/*waitforthebuffertofill*/
wait_event_interruptible(data->wait,!data->loops);
if(signal_pending(current))
return-ERESTARTSYS;
buf2=data->buf;
kfree(data);
*eof=1;
returnbuf2-buf;
}
[/code]
五、工作队列 关于工作的内容以后进行补充。
[/code]
相关文章推荐
- Linux设备驱动程序第三版学习(10)- 时间、延迟及延缓操作
- Linux设备驱动程序第三版学习(10)- 时间、延迟及延缓操作
- Linux设备驱动程序学习(10)-时间、延迟及延缓操作
- Linux设备驱动程序学习(10)-时间、延迟及延缓操作(Jit.c)
- Linux设备驱动程序学习(10) -时间、延迟及延缓操作
- Linux设备驱动程序学习———时间、延迟及延缓操作
- 转载:Linux设备驱动程序学习- 时间、延迟及延缓操作
- Linux设备驱动程式学习(10)-时间、延迟及延缓操作
- linux设备驱动程序学习(7) 时间、延迟及延缓操作
- Linux设备驱动程序学习———时间、延迟及延缓操作
- Linux设备驱动程序_第七章_时间、延迟及延缓操作
- Linux驱动学习--时间、延迟及延缓操作
- Linux设备驱动学习(6) 时间、延迟、延缓操作 jit类设备驱动
- Linux驱动学习--时间、延迟及延缓操作
- Linux设备驱动程序第三版学习(5)- 高级字符驱动程序操作 - ioctl .
- Linux设备驱动程序第三版学习(5)- 高级字符驱动程序操作 - ioctl .
- Linux设备驱动程序第三版学习(6)- 高级字符驱动程序操作(续1)- 进程休眠 .
- Linux驱动学习--时间、延迟及延缓操作(转载)
- Linux驱动学习--时间、延迟及延缓操作2
- Linux驱动学习--时间、延迟及延缓操作1