[读书笔记]时间、延迟及延缓操作(第七章)

综述

在本章中,我们要掌握以下知识点
如何独立时间差,如何比较时间
如何获取当前时间
如何将操作延迟指定的一段时间
如何调度异步函数到指定的时间之后学习

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);
共享队列的工作函数
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335