LDD读书笔记_时间,延迟和延缓操作

Agenda

?如何获得当前时间

?如何度量时间差,
如何比较时间

?如何将操作延迟指定的一段时间

?如何调度异步函数到指定的时间之后执行

?如何获得当前时间

?HZ: 宏定义,
每秒的时间中断次数

?Jiffies变量:
系统引导时初始化为0,
每发生一次时间中断就加1

–#include <linux/jiffies.h>

–unsigned long j, stamp_1, stamp_half, stamp_n;

–j = jiffies;
/* current value */

–stamp_1 = j + HZ;
/* 1 s */

–stamp_half = j + HZ/2;
/* 0.5s */

–stamp_n = j + n*HZ/1000;
/* n ms */

?TSC(timestamp counter)寄存器, pentium开始提供, 64位,
记录CPU时间周期

–Rdtsc(low32, high32)
//宏定义,
将64位数值读到2个32位变量

–Rdtscl(low32)
//只读低32位

–Rdtscll(var64)

?如何度量时间差,
如何比较时间

?分别读取jiffies

       Jiffies溢出怎么办

?int time_after(unsigned long a, unsigned long b);

?int time_before(unsigned long a, unsigned long b);

?int time_after_eq(unsigned long a, unsigned long b);

?int time_before_eq(unsigned long a, unsigned long b);

?分别读取TSC

?如何将操作延迟指定的一段时间

?长延时

       最笨的方法(忙等待)

–unsigned long j = jiffies + jit_delay*HZ;

–while (jiffies<j);

?这个忙等待循环在延迟期间会锁住整台计算机,因为调度器不会中断运行在内核空间的进程

?更糟糕的是,如果在进入循环之前关闭了中断, jiffies值就不会得到更新,那么while循环的条件就永真.不得不对机器进行重启.

       稍微好点的方法

?它允许其他进程在延迟的时间间隔内运行,尽管这种方法不能用于实时任务或者其他时间要求很严格的场合:

–while (jiffies<j)

schedule();

?当前任务除了释放CPU
之外不做任何工作,但是它仍在任务队列中. 如果它是系统中唯一的可运行的进程,它还会被运行(系统调用调度器,调度器还是同一个进程,

此进程又再调用调度器,然后...).换句话说,机器的负载(系统中运行的进程个数)至少为1,而idle空闲进程(进程为0,历史性的被称为"swapper")绝不会被运行.

尽管这个问题看来无所谓,当系统空闲时运行idle空闲进程可以减轻处理器负载,降低处理器温度,延长处理器寿命,如果是手提电脑,电池的寿命也可延长.

而且,延迟期间实际上进程是在执行的,因此这段延迟还是记在它的运行时间上的.

       更好点的方法

?在内核态下让进程进入睡眠态的正确方式是设置timeout后睡眠在一个等待队列上.
调度器每次运行时都会比较进程的timeout 值和当前的jiffies值,

只要没有系统事件唤醒进程使它离开等待队列,那么一旦当前时间达到timeout值,调度器就唤醒睡眠进程.这种延迟实现如下:

–struct wait_queue *wait=NULL;

–current->timeout=j;

–interruptible_sleep_on(&wait);

?注意要调用interruptible_sleep_on而不是sleep_on,因为调度器不检查不可中断的进程的timeout值-这种进程的睡眠即使超时也不被中断.

因此,如果你调用sleep_on,就无法中断该睡眠进程.

       好上加好

?如果目的只是插入延迟,这里并没有必要使用等待队列.实际上,如下所示,用current->timeout
而不用等待队列就可以达到目的:

        ?current->timeout=j;
        ?current->state=TASK_INTERRUPTIBLE;
        ?schedule();
        ?current->timeout=0;/* 重置timeout 值*/ 

?这段语句是在调用调度器之前先改变进程的状态.进程的状态被标记为TASK_INTERRUPTIBLE(与TASk_RUNNING
相对应),

这保证了该进程在超时前不会被再次运行(但其他系统事件如信号可能会唤醒它).

这种延迟方法在文件/proc/jitself
中实现了-这个名字强调了,进程是“自己进入睡眠的”,而不是通过调用sleep_on.

?短延时

?有时驱动程序需要非常短的延迟来和硬件同步.此时,使用jiffies值就不能达到目的.

?这时就要用内核函数ndelay,udelay,以及mdelay,使用软件循环将执行延迟指定数量的微秒数,是个忙等待函数,在延迟的时间段内无法运行其他的任务.

?实际上,当前没有平台获得了纳秒的精度.同时,实际的延时比预设的来的长并不会导致问题,因为要求短延时的往往是硬件,而要求的往往是至少需要多少时间.

?如何调度异步函数到指定的时间之后执行

?定时器

#include <linux/timer.h>
struct timer_list
{
struct timer_list *next; /*不要直接修改它 */
struct timer_list *prev; /*不要直接修改它 */
unsigned long expires; /* timeout 超时值,以jiffies 值为单位 */
unsigned long data; /* 传递给定时器处理程序的参数 */
void (*function)(unsigned long); /* 超时时调用的定时器处理程序 */
};

#define TIMER_INITIALIZER(_function, _expires, _data)

定时器相关的API

void init_timer(struct timer_list *timer);
void add_timer(struct timer_list * timer);
   初始化完timer_list 结构,add_timer函数就将它插入一张有序表中.
int del_timer(struct timer_list * timer);
   如果需要在定时器超时前将它从列表中删除,应调用del_timer 函数.但当定时器超时时,系统会自动地将它从列表中删除.
int mod_timer(struct timer_list *timer, unsigned long expires)
   修改timer, 相当于 del_timer(timer); timer->expires = expires; add_timer(timer); 的操作
   <span style="color:#FF0000;"><strong>但是, 在考虑并发的情况下, mod_timer才是修改timeout的安全的方法, 因为add_timer不能去修改一个正在running的timer.</strong></span>
 Note: 定时器中断是软中断

?Tasklet

tasklet和内核定时器很相似,唯一的区别是我们不能要求tasklet在某个给定的时间执行,tasklet典型的应用是在中断服务函数中,硬件中断时要求尽可能快的管理硬件中断,

而大部分的数据管理可以安全的延迟到后期执行. Tasklet 是软中断的一种.

#include <linux/interrupt.h>
struct tasklet_struct
{
	struct tasklet_struct *next;
	unsigned long state;
	atomic_t count;
	void (*func)(unsigned long);
	unsigned long data;
}

API

?void tasklet_disable(struct tasklet_struct *t);

– 这个函数禁止给定的 tasklet. tasklet
可能仍然被 tasklet_schedule
调度,
但是它的执行被延后直到这个 tasklet
被再次使能.

如果这个 tasklet 当前在运行,
这个函数忙等待直到这个tasklet退出;
因此,
在调用tasklet_disable
后, 你可以确保这个 tasklet
在系统任何地方都不在运行.

?void tasklet_disable_nosync(struct tasklet_struct *t);

– 禁止这个 tasklet,不过它无须在返回前等待tasklet执行完毕.这么做往往不太安全,因为你无法估计该tasklet是否仍在执行.

当它返回, 这个tasklet
被禁止并且不会在以后被调度直到重新使能.

?void tasklet_enable(struct tasklet_struct *t);

– 使能一个之前被禁止的 tasklet.
如果这个 tasklet
已经被调度,
它会很快运行.

一个对 tasklet_enable 的调用必须匹配每个对 tasklet_disable
的调用,
因为内核跟踪每个 tasklet
的"禁止次数".

?void tasklet_schedule(struct tasklet_struct *t);

– 调度 tasklet
执行.
如果一个 tasklet在它有机会运行前被再次调度,
它只运行一次.
但是,
如果他在运行中被调度,
它在完成后再次运行;

这保证了在其他事件被处理当中发生的事件收到应有的注意.
这个做法也允许一个 tasklet
重新调度它自己.

?void tasklet_hi_schedule(struct tasklet_struct *t);

– 调度 tasklet
在更高优先级执行.
当软中断处理运行时,
它处理高优先级tasklet
在其他软中断之前, 包括"正常的" tasklet.

?void tasklet_kill(struct tasklet_struct *t);

– 这个函数确保了这个 tasklet
没被再次调度来运行;
它常常被调用当一个设备正被关闭或者模块卸载时.
如果这个 tasklet
被调度来运行,
这个函数等待直到它已执行.

如果这个 tasklet 重新调度它自己,
你必须阻止在调用 tasklet_kill
前它重新调度它自己.

?工作队列

工作队列它把工作推后,交由一个特殊的内核线程去执行,因此可以睡眠.

#include <linux/workqueue.h>
struct work_struct
{
	struct cpu_workqueue_struct *cpu_wq;
	struct list_head list;
	const char *name;
	int singlethread;
	int freezeable;		/* Freeze threads during suspend */
#ifdef CONFIG_LOCKDEP
	struct lockdep_map lockdep_map;
#endif
};

API

?工作队列必须在使用前创建

–struct workqueue_struct *create_workqueue(const char *name);

–struct workqueue_struct *create_singlethread_workqueue(const char
*name);

?提交一个任务给一个工作队列,
你需要填充一个work_struct结构.
如下:

–DECLARE_WORK(name, void (*function)(void *), void *data);

?这里 name
是声明的结构名称, function
是从工作队列被调用的函数,
以及 data
是一个传递给这个function的参数

?如果在运行时需要建立 work_struct
结构,
使用下面 2
个宏定义:

–INIT_WORK(struct work_struct *work, void (*function)(void *), void
*data);

–PREPARE_WORK(struct work_struct *work, void (*function)(void *),
void *data);

?提交工作给一个工作队列

–int queue_work(struct workqueue_struct *queue, struct
work_struct *work);

–int queue_delayed_work(struct workqueue_struct *queue,
struct work_struct *work, unsigned long delay);

?取消一个挂起的工作队列入口

–int cancel_delayed_work(struct work_struct *work);

?用完一个工作队列,可以去掉它

–void destroy_workqueue(struct workqueue_struct *queue);

三者的应用

?对时间有比较精确要求,
用timer

?对时间精度要求不高,
但又要运行的比较迅速,
用tasklet,
一般用在中断响应中

?允许sleep的可以用workqueue

时间: 2024-11-03 03:19:00

LDD读书笔记_时间,延迟和延缓操作的相关文章

LDD读书笔记_内存管理

本部分不仅仅是LDD的介绍部分, 还包括了对linux的内存模型的总结. 一句话总结 伙伴系统是基石, slab基于伙伴系统, kmalloc基于slab. 要点 ?伙伴系统是对连续大内存而言, 得到的内存的单位从1个page到211 page, 解决外部碎片问题. ?Slab分配器是针对小内存而言, 从32B到128KB, 解决的是内部碎片问题, kmalloc是基于slab分配器的. ?如果物理内存加上需要映射的IO空间内存的大小加起来超过896M, 则有必要开启highmen的功能. Ag

LDD读书笔记_调试技术

先写一个个人比较喜欢的调试技巧. 1. menuconfig中打开CONFIG_DEBUG_KERNEL 2. objdump -d -S(大写) *.o > file 可以得到混合C和汇编的代码 或者 make *.lst 也能得到 3. addr2line -f -e vmlinux address(0xcxxxxxxxx) 能得到address对应的函数名和所在的文件中的行数 4. 根据OOPS信息, 查看R13(SP), R14(LR), R15(PC)寄存器的值, ARM架构 ?pri

2013年读书笔记1--暗时间(刘未鹏)

暗时间(刘未鹏)读书笔记 1.过早退出是一切失败的根源 2.兴趣遍地都是,专注和持之以恒才是真正稀缺的 3.反思是改变自己的第一步,我们常常容易发现别人的问题,别人的错误,却难以发现自己思维中的问题,因为我们很少的自己的思维当做目标去思考. 4.一个有趣的例子,饿死在干草堆中间的驴子.有人因为无法做出决定就推迟决定,然而实际上推迟决定是最差的决定.把时间都花在了选择A还是B上,这段时间就不是你的了. 5.娱乐是娱乐,知识是知识,如果真想得到知识,最好过滤一下你得到的信息,不要在别人的思考结果中活

读书笔记_《50 Android Hacks》之一 linearlayout的weightsum及weights

最近在读<50 Android Hacks>,准备谢谢读书笔记,并不断丰满一下. 听到过这样的问题,“如果我想让一个button占父控件的50%,应该怎么办”. 通常来说,我们可以使用linearlayout其中的属性  android:layout_weight属性 在实现方法上来说,有几种方法来实现. android的设备有不同的size,对于不同的屏幕尺寸,我们应该有一种普遍 适用的方法. 我们可以使用layout_weight以及weightSum属性来填满layout的剩余空间. 其

Hadoop读书笔记(三)Java API操作HDFS

Hadoop读书笔记(一)Hadoop介绍:http://blog.csdn.net/caicongyang/article/details/39898629 Hadoop读书笔记(二)HDFS的shell操作:http://blog.csdn.net/caicongyang/article/details/41253927 JAVA URL 操作HDFS OperateByURL.java package hdfs; import java.io.InputStream; import jav

《Boost程序库完全开发指南》读书笔记-日期时间

timer库 #include <boost\timer.hpp> #include <boost\progress.hpp> 1.timer类 // timer类的示例. void Lib_Demo_timer::Demo_timer() { timer t; cout << "可度量的最大单位:" << t.elapsed_max() / 3600 << "小时" << endl; cout

图解HTTP读书笔记_第六章 http首部

6.1HTTP报文首部 1.http协议的请求和响应报文必包含HTTP首部 HTTP协议的请求和响应报文中必定包含HTTP首部,首部内容为客户端和服务器端分别处理请求和响应所提供的的信息. HTTP请求报文:在请求中,HTTP报文由方法.URI.HTTP版本.HTTP首部字段等部分组成 HTTP响应报文:在响应中,HTTP报文由HTTP版本.状态码(数字和原因短语).HTTP首部字段3部分组成. 6.2 HTTP首部字段 1.请求和响应都会使用首部字段,使用首部字段是为了给浏览器和服务器提供报文

Java编程思想读书笔记_第三章

本章提到的关于==的部分,一个完整的实验如下: 1 class Test { 2 public static void main(String[] args) { 3 Integer i = new Integer(47); 4 Integer j = new Integer(47); 5 Integer i1 = 47; 6 Integer j1 = 47; 7 int i2 = new Integer(47); 8 int j2 = new Integer(47); 9 int i3 = 4

读书笔记_深入理解计算机系统_第一章_计算机系统漫游

hello.c #incude <stdio.h> int main() { printf("Hello,world\n"); } 1.1信息就是位+上下文 系统所有的信息,都是由一串位表示的. 在不同的上下文中(可以理解为程序,或者运算),一个同样的字节序列可能表示一个整数,浮点数,字符串或者机器指令. 2.2程序被其他程序翻译成不同的格式 如Hello程序,从源文件hello.c中的每条C语句,需被其他程序转换为一系列低级语言(汇编)指令,然后将这些指令按照一种称为可执