综述
在本章中,我们要掌握以下知识点
如何独立时间差,如何比较时间
如何获取当前时间
如何将操作延迟指定的一段时间
如何调度异步函数到指定的时间之后学习
1.度量时间差
1.内核通过定时器中断来跟踪时间流。
时钟中断由系统的定时硬件以周期性的间隔产生,
这个间隔有内核跟进HZ的值设定,定义在<linux/param.h>
/usr/include/asm-generic/param.h
#ifndef HZ
#define HZ 100
#endif
2.每次时钟中断发生,内核内部计数器的值+1,这个值在开机时默认初始化为0,因此它的值就是上次系统开机以来的时钟滴答数。
3.计数器是64位的变量(即使在32位操作系统上),称为"jiffies_64",
通常驱动开发使用jiffies(unsigned long),因为访问很快。
jiffies要么和jiffies_64相同,要么是jiffies_64的低32位。
2.使用jiffies计数器
1.计数器工具包含在<linux/jiffies.h>中,通常引入<linux/sched.h>,此头文件中有引入jiffies.h
2.jiffies和jiffies_64只读变量,不可写
3.jiffies定义在
external/syslinux/com32/include/syslinux/pmapi.h
/* Should be "const volatile", but gcc miscompiles that sometimes */
volatile uint32_t *jiffies;
volatile uint32_t *ms_timer;
或者
kernel-3.18/mm/kmemleak.c
unsigned long jiffies; /* creation timestamp */
注意volatile是避免编译器对访问该变量的语句进行优化!
4.计时器使用示例
#include <linux/jiffies.h>
unsigned long j,stamp_1,stamp_half,stamp_n;
j = jiffies;//读取当前值
stamp_1 = j+ HZ; //未来1秒
stamp_half = j+HZ/2 //未来半秒
stamp_n = j+ n*HZ/1000 //n毫秒
5.时间比较
时间比较大小,我们不能用大于等于小于来判断,应当使用相应的宏
#include<linux/jiffies.h>
int time_after(unsigned long a,unsigned long b);
a时间比b时间靠后,返回true
int time_before(unsigned long a,unsigned long b));
a时间比b时间靠前,返回true
int time_after_eq(unsigned long a,unsigned long b));
a时间比b时间靠后或者相等,返回true
int time_before_equnsigned long a,unigned long b)();
a时间比b时间靠前或者相等,返回true
这些宏会将计数器的值转换为signed long,相减,然后比较结果!
6.计算两个时间的差
unsigned long diff = (long)t2-(long)t1
7.将时间转换成毫秒
msec = diff * 1000 / HZ;
8.用户空间和内核空间时间的自由转换
用户空间时间表示结构体
struct timeval; 表示秒和毫秒,出现早,常用
struct timespec; 表示秒和纳秒,出现晚,少用
内核空间提供转的辅助函数
#include <linux/time.h>
unsigned long timespec_to_jiffies(strcut timespec *value);
void jiffies_to_timespec(unsigned long jiffies,struct timespec *value);
unsigned long timeval_to_jiffies(struct timeval *value);
void jiffies_to_timeval(unsigned long jiffies,struct timeval *value);
注意,对64位jiffies_64的访问不是原子的,直接读取值很靠不住,需要下面的函数读取
#include <linux/jiffies.h>
u64 get_jiffies_64(void);
9.用户空间获取HZ的值
这个值对于用户空间时不可见的,只能通过以下方式获取
/proc/interrupts获得计数值
除以
/proc/uptime文件报告的系统运行时间
=内核确切的HZ的值
3.处理特定的寄存器
有时候需要度量的时间特别短,要求极高的时间精度,可以使用与特定平台相关的计数器寄存器。
最出名的计数器寄存器就是TSC(timestamp counter,时间戳计数器)
,64位寄存器,记录CPU时钟周期,内核空间和用户空间均可访问。
#include <asm/msr.h>
rdtsc(low32,high32);
原子性的把64位的值保存到两个32位的变量中;
rdtscl(low32);
原子性的把寄存器低32位的值读入一个32位的变量中;
rdtscll(var64);
原子性地将64位寄存器的值读入一个long long 型的变量中
读取寄存器低32位的值,测量该指令自身的运行时间
unsigned long ini,end;
rdtscl(ini);rdtscl(end);
printk("time");
可代替rdtsc的函数
#include <linux/timex.h>
cycles_t get_cycles(void);
各个平台都可以使用这个函数,没有时钟周期计数器的寄存器的平台上,该函数总是返回0
4.获取当前寄存器的值
1.内核通过jiffies的值来获取当前时间。
2.把年月日转换为jiffies
#include <linux/time.h>
unsigned long mktime (unsigned int year,unsigned int mon,
unsigned int day,unsigned int hour,
unsigned int min,unsigned int sec);
3.获取当前时间
#include <linux/time.h>
void do_gettimeofday(struct timeval *tv)
以秒数和毫秒数的形式返回当前时间
struct timespec current_kernel_time(void);
以jiffies为分辨率返回当前时间
5.延迟
#include <linux/wait.h>
long wait_event_interruptible_timeout(wait_queue_head_t *q,
condition,signed long timeout)
使当前进程休眠在等待队列上,并指定用jiffies表达的超时值。
如果要进入不可中断休眠,则应使用schedule_timeout
#include <linux/sched.h>
signed long schedule_timeout(signed long timeout);
调用调度器,确保当前进程可以在给定的超时值之后被唤醒。
调用者必须首先调用set_current_state将自己置于可中断或者不可中断的休眠状态
#include <linux/delay.h>
void ndelay(unsigned long nsecs);//纳秒
void udelay(unsigned long usecs);//微秒
void mdelay(unsigned long msecs);//毫秒
实际延迟时间至少是请求的值,但可能会更长。
6.内核定时器
#include <asm/hardirq.h>
int in_interrupt(void);
int in_atomic(void);
返回bool值告知调用代码 是否在中断上下文或者原子上下文中执行。
中断上下文在进程上下文之外,可能正在处理硬件或者软件中断。
原子上下文是指不能进行调度的时间点,比如中断上下文或者拥有自旋锁时的进程上下文。
#include <linux/timer.h>
void init_timer(struct timer_list *timer);
strcut timer_list TIMER_INITIALIZER(_function,_expires,_data);
初始化timer_list数据结构的两种方式
void add_timer(strcut timer_list *timer);
注册定时器结构
int mod_timer(strcut timer_list *timer,unsigned long expites);
修改一个已经调度的定时器结构的到期时间,可以替代add_timer使用
int timer_pending(strcut timer_list *timer);
判断给定的定时器是否已经被注册运行
void del_timer(strcut timer_list * timer);
void del_timer_sync();
删除指定的定时器,后一个函数确保定时器不会再其他cpu上运行。
7.tasklet
#include <linux/interrupt.h>
DECLARE_TASKLET(name,func,data);
DECLARE_TASKLET_DISABLE(name,func,data);
void tasklet_init(stcuct tsklet_strcut *t,
void (*func)(unsigned long),unsigned long data);
前面连个宏声明一个tasklet结构,而tasklet_init函数初始化tasklet结构。
第二个DESCLARE宏禁用给定的tasklet。
void tasklet_disable(struct tasklet_struct *t);
void tasklet_disable_nosync(struct tasklet_struct *t);
void tasklet_enable(struct tasklet_struct *t);
禁用或者重启某个tasklet,tasklet_disable会在tasklet正在其他cpu上
运行时等待,而nosync不需要。
void tasklet_schedule(struct tasklet_struct *t);
void tasklet_hi_schedule(struct tasklet_struct *t)
调度运行某个tasklet,当执行软件中断时,高优先级的tasklet会被优先处理。
void tasklet_kill(struct tasklet_struct *t);
如果知道的tasklet被调度运行,则把它从活动链表中删除
8.工作队列
#include <linux/workqueue.h>
struct workqueue_struct;//工作队列结构体
struct work_struct;//实际工作
struct workqueue_struct * create_workqueue(const char *name);
struct workqueue_struct * create_singlethread_workqueue(const char *name);
void destroy_workqueue(struct workqueue_struct *queue);
用于创建和销毁工作队列的函数。
调用create_workqueue将创建一个队列,
调用create_singlethread_workqueue只会创建单个工作进程
DECLARE_WORK(name,void (*function)(void *),void *data);
INIT_WORK(struct work_struct *work,void (*function)(void *),void * data);
PREPARE_WORK(struct work_struct *wor,void (*function)(void *),void * data);
声明和初始化工作队列
int queue_work(struct workqueue_struct * queue,struct work_struct *work);
int queue_delay_work(struct workqueue_struct * queue,
struct work_struct *work,unsigned long delay);
用于安排工作,以便从工作队列中执行的函数
int cancel_delayed_work(struct work_struct *work);
从工作队列中删除一个入口项
void flush_workqueue(struct workqueue_struct *queue);
确保系统中的任何地方都不会运行任何工作队列入口项
int schedule_work(struct work_struct *work);
int schedule_delay_work(struct work_struct *work,unsigned long delay)
void flush_schedule_work(struct work_struct *work);
共享队列的工作函数