内核中的时间

Linux 系统内核对于时间的管理依赖于硬件,硬件按一定的周期产生中断,周期由内核的一个配置值HZ决定在系统启动时会将定时器配置为HZ值指定的频率产生中断;同时内核和维护一个64位(X86和X64都是64位)的计数器变量jiffies(jiffies_64)。在系统启动时这个值为0之后每次发生一次定时器中断这个值就会加1 ,所以他代表系统连续运行的嘀嗒次数。在内核中使用这个变量只需要引用头文件include<linux/jiffies.h>这个头文件又经常被linux/sched.h包含。

常用的比较两个jiffies时间的工具宏有

#define time_after(a,b)        \
(typecheck(unsigned long, a) && \
typecheck(unsigned long, b) && \
((long)(b) - (long)(a) < 0))
#define time_before(a,b) time_after(b,a)
#define time_after_eq(a,b) \
(typecheck(unsigned long, a) && \
typecheck(unsigned long, b) && \
((long)(a) - (long)(b) >= 0))
#define time_before_eq(a,b) time_after_eq(b,a)

在用户空间表示时间常常用两个结构体来表示但是精度不同,头文件为#include <linux/time.h>。

表示秒和纳秒的

struct timespec {
time_t tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};

表示秒和微妙

struct timeval {
time_t tv_sec; /* seconds */
suseconds_t tv_usec; /* microseconds */
};

同时还提供了用户空间的时间和jiffies的相互转化的工具函数

unsigned long timespec_to_jiffies(struct 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);

这个接口也可以说明在用户空间struct timespec 和struct timeval 也是常常被用来表示相对时间而不是真实时间(壁钟时间)的。在32操作系统上直接访问jiffies_64 是无法保证原子性的,所以内核需要提供接口

#include <linux/jiffies.h>
u64 get_jiffies_64(void);

当需要测量的时间非常精确时只有硬件提供特殊的机制时才能实现否则,时间的精度会有上限。像X86的平台就提供了一个64bits的寄存器用来记录CPU的时钟级精度的时间刻度。除此之外内核内部还有一个全局的时间计数器xtime它是struct timeval 类型的变量,记录从标准时间基准1970-01-01 00:00:00到当前的相对秒数值。它中断中断底半部分来维护,因为内底半部执行时间具有不确定性所以同时内核还维护了一个xtim的更新时刻值他是和jiffies同类型的变量,每次跟新xtime时都把当前的jiffies赋值给他。

内核中的延时

 短延时

#include <linux/delay.h>
void ndelay(unsigned long nsecs);
void udelay(unsigned long usecs);
void mdelay(unsigned long msecs);

这三个延迟函数均是忙等待函数,在延时过程中无法运行其他任务。想不忙等浪费CPU的处理能力并能够容忍比所请求延迟更长的延迟,则应当使用schedule_timeout、sleep或者ssleep,这几个接口会发送任务切换。

void msleep(unsigned int msecs)
unsigned long msleep_interruptible(unsigned int msecs)
void ssleep(unsigned int seconds)

长延时

第一种实现方式

#define cpu_relax()            barrier()
while (time_before(jiffies, j1))
cpu_relax();

这个方式是忙等待的cpu_relax通常是什么都不做,如果在这之前禁用了抢占并关闭中断则系统内核就停止运行了,除了重启别无他法。这样的延时对系统性的负面影响非常大所以有了下面的版本。这时候系统执行其他处理或执行进程0进入低能耗。

while (time_before(jiffies, j1)) {
schedule();

内核等待

#include <linux/wait.h>
//在一个队列上等待某些事件直到超时或事件发生
long wait_event_timeout(wait_queue_head_t q, condition, long timeout);
//在一个队列上等待某些事件直到超时或事件发生或者中断发生。
long wait_event_interruptible_timeout(wait_queue_head_t q, condition, long timeout);
/*这些函数在给定队列上睡眠, 但是它们在超时(以 jiffies 表示)到后返回。如果超时,函数返回 0;
所以我盟可以定义一个等待队列头,没有人往这个头上添加事件,则这个上面的函数就编程对应的单纯延时的函数。同时内核也提供了这种需
求的接口,以实现进程在超时到期时被唤醒而又不等待特定事件避免多余声明一个等待队列头。*/
#include <linux/sched.h>
signed long schedule_timeout(signed long timeout);
/*timeout 是要延时的 jiffies 数。除非这个函数在给定的 timeout 流失前返回,否则返回值是 0 。
schedule_timeout 要求调用者首先设置当前的进程状态。为获得一个不可中断的延迟, 可使用 TASK_UNINTERRUPTIBLE 代替。
如果你忘记改变当前进程的状态, 调用 schedule_time 如同调用 shcedule,建立一个不用的定时器。一个典型调用如下:*/
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout (delay);

内核中的定时器

内核中用来描述一个定时器实例对象的数据结构定义如下,其中expires为到期时间,data为定时器处理接口传入的参数,function就是定时器到期时执行的处理接口,其余的接口都是内核管理定时器需要的辅助成员。

struct timer_list {
/*
* All fields that change during normal runtime grouped to the
* same cacheline
*/
struct list_head entry;
unsigned long expires;
struct tvec_base *base; void (*function)(unsigned long);
unsigned long data; int slack; #ifdef CONFIG_TIMER_STATS
int start_pid;
void *start_site;
char start_comm[16];
#endif
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};

常见的使用方法是静态定义或动态申请一个定时器数据结构,然后初始化定时器,之后就是将定时器添加到内核定时器管理的链表中,定时器就会在到期时间执行指定的处理接口。

初始化定时器

//初始化定时器 带标志设置
#define __TIMER_INITIALIZER(_function, _expires, _data, _flags)
{ \
.entry = { .prev = TIMER_ENTRY_STATIC }, \
.function = (_function), \
.expires = (_expires), \
.data = (_data), \
.base = (void *)((unsigned long)&boot_tvec_bases + (_flags)), \
.slack = -1, \
__TIMER_LOCKDEP_MAP_INITIALIZER( \
__FILE__ ":" __stringify(__LINE__)) \
}
//初始化定时器 不带flags设置
#define TIMER_INITIALIZER(_function, _expires, _data) \
__TIMER_INITIALIZER((_function), (_expires), (_data), 0)
//定义并初始化 带 TIMER_DEFERRABLE标识
#define TIMER_DEFERRED_INITIALIZER(_function, _expires, _data) \
__TIMER_INITIALIZER((_function), (_expires), (_data), TIMER_DEFERRABLE)
//定义并初始化 标志位全0
#define DEFINE_TIMER(_name, _function, _expires, _data) \
struct timer_list _name = \
TIMER_INITIALIZER(_function, _expires, _data)
#define init_timer(timer) \
__init_timer((timer), 0) #define __init_timer(_timer, _flags) \
init_timer_key((_timer), (_flags), NULL, NULL) void init_timer_key(struct timer_list *timer, unsigned int flags,
const char *name, struct lock_class_key *key)
{
debug_init(timer);
do_init_timer(timer, flags, name, key);
}
#define setup_timer(timer, fn, data)                    \
    __setup_timer((timer), (fn), (data), 0)

添加定时器到内核

void add_timer(struct timer_list* timer);

定义好定时器只有加入到内核定时器管理链表上才能被内核定时器线程管理,从而在超时后执行。也有需求需要在定时器添加后删除定时器则只需要调用del_timer()接口即可

int del_timer(struct timer_list * timer)

添加好之后又想修改超时时间可以吗,当然是可以的,如下

int mod_timer(struct timer_list* timer,unsigned long expires)

这就是Linux内核定时器的简单使用记录部分,后面了解一下定时器的标识flags参数的具体可取值和特性。

//从内核的处理过程来看这个标志在处理时没有进行任何特殊处理
#define TIMER_DEFERRABLE 0x1LU
// 中断安全,意味着执行这个定时器处理接口要关闭中断
#define TIMER_IRQSAFE 0x2LU
//掩码
#define TIMER_FLAG_MASK 0x3LU

内核处理timer过程:

static inline void __run_timers(struct tvec_base *base)
{
struct timer_list *timer; spin_lock_irq(&base->lock);
if (catchup_timer_jiffies(base)) {
spin_unlock_irq(&base->lock);
return;
}
while (time_after_eq(jiffies, base->timer_jiffies)) {
struct list_head work_list;
struct list_head *head = &work_list;
int index = base->timer_jiffies & TVR_MASK; /*
* Cascade timers:
*/
if (!index &&
(!cascade(base, &base->tv2, INDEX(0))) &&
(!cascade(base, &base->tv3, INDEX(1))) &&
!cascade(base, &base->tv4, INDEX(2)))
cascade(base, &base->tv5, INDEX(3));
++base->timer_jiffies;
list_replace_init(base->tv1.vec + index, head);
while (!list_empty(head)) {
void (*fn)(unsigned long);
unsigned long data;
bool irqsafe; timer = list_first_entry(head, struct timer_list,entry);
fn = timer->function;
data = timer->data;
irqsafe = tbase_get_irqsafe(timer->base); timer_stats_account_timer(timer); base->running_timer = timer;
detach_expired_timer(timer, base);
//处理TIMER_IRQSAFE
if (irqsafe) {
spin_unlock(&base->lock);
call_timer_fn(timer, fn, data);
spin_lock(&base->lock);
} else {
spin_unlock_irq(&base->lock);
call_timer_fn(timer, fn, data);
spin_lock_irq(&base->lock);
}
}
}
base->running_timer = NULL;
spin_unlock_irq(&base->lock);
}

以上就是内核定时器的使用简单了解内容,具体的实现有时间了放在内核透视部分深入看看。

Linux 驱动框架---驱动中的时间相关的更多相关文章

  1. Linux 驱动框架---驱动中的阻塞

    描述和API 阻塞IO和非阻塞IO的应用编程时的处理机制是不同的,如果是非阻塞IO在访问资源未就绪时就直接返回-EAGAIN,反之阻塞IO则会使当前用户进程睡眠直到资源可用.从应用场景来说两种方式分别 ...

  2. Linux 驱动框架---驱动中的异步

    异步IO是对阻塞和轮询IO的机制补充,所谓异步IO就是在设备数据就绪时主动通知所属进程进行处理的机制.之所以说是异步是相对与被通知进程的,因为进程不知道也无法知道什么时候会被通知:这一机制非常类似于硬 ...

  3. Linux 驱动框架---驱动中的中断

    在单片机开发中中断就是执行过程中发生了一些事件需要及时处理,所以需要停止当前正在运行的处理的事情转而去执行中断服务函数,已完成必要的事件的处理.在Linux中断一样是如此使用但是基于常见的中断控制器的 ...

  4. Linux 驱动框架---驱动中的并发

    并发指多个执行单元被同时.并行的执行,而并发执行的单元对共享资源的访问就容易导致竟态.并发产生的情况分为抢占和并行(多核)和硬抢占(中断).Linux为解决这一问题增加了一系列的接口来解决并发导致的竟 ...

  5. 驱动框架入门——以LED为例[【转】

    本文转载自;http://blog.csdn.net/oqqHuTu12345678/article/details/72783903 以下内容源于朱有鹏<物联网大讲堂>课程的学习,如有侵 ...

  6. I2C驱动框架(四)

    参考:I2C子系统之platform_driver初始化——I2C_adap_s3c_init() 在完成platform_device的添加之后,i2c子系统将进行platform_driver的注 ...

  7. Linux驱动框架之framebuffer驱动框架

    1.什么是framebuffer? (1)framebuffer帧缓冲(一屏幕数据)(简称fb)是linux内核中虚拟出的一个设备,framebuffer向应用层提供一个统一标准接口的显示设备.帧缓冲 ...

  8. 基于Linux 3.0.8 Samsung FIMC(S5PV210) 的摄像头驱动框架解读(一)

    作者:咕唧咕唧liukun321 来自:http://blog.csdn.net/liukun321 FIMC这个名字应该是从S5PC1x0開始出现的.在s5pv210里面的定义是摄像头接口.可是它相 ...

  9. Linux Framebuffer驱动剖析之二—驱动框架、接口实现和使用

    深入分析LinuxFramebuffer子系统的驱动框架.接口实现和使用. 一.LinuxFramebuffer的软件需求 上一篇文章详细阐述了LinuxFramebuffer的软件需求(请先理解第一 ...

随机推荐

  1. scrapy框架基于管道的持久化存储

    scrapy框架的使用 基于管道的持久化存储的编码流程 在爬虫文件中数据解析 将解析到的数据封装到一个叫做Item类型的对象 将item类型的对象提交给管道 管道负责调用process_item的方法 ...

  2. 牛逼!MySQL 8.0 中的索引可以隐藏了…

    MySQL 8.0 虽然发布很久了,但可能大家都停留在 5.7.x,甚至更老,其实 MySQL 8.0 新增了许多重磅新特性,比如栈长今天要介绍的 "隐藏索引" 或者 " ...

  3. VMware中安装Ubuntu后,安装VMwareTools提示“Not enough free space to extract VMwareTools-10.3.10-13959562.tar.gz”的解决办法

    将加载后的Vmware Tools中的*.tar.gz文件复制到桌面后提取,否则会报错:

  4. Maven 基础详解

    一.编写pom.xml文件     Maven项目的核心是pom.xml.POM(Project Object Model,项目对象模型)定义了项目的基本信息,用于描述项目如何构建,声明项目依赖等等. ...

  5. Property or method "previewUrl" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components,

    Property or method "previewUrl" is not defined on the instance but referenced during rende ...

  6. Linux 通过端口终止进程

    以下命令可用于杀死占用某端口的所有进程. root 用户: kill -9 $(lsof -i tcp:进程号 -t) 非 root 用户: kill -9 $(sudo lsof -i tcp:进程 ...

  7. Unix Socket 代理服务 unix域套接字

    基于Unix Socket的可靠Node.js HTTP代理实现(支持WebSocket协议) - royalrover - 博客园 https://www.cnblogs.com/accordion ...

  8. 获取当前文件路径 import 原理 一般把模块组成的集合称为包(package)

    获取当前文件路径 testpath.py import sysprint(sys.path) [root@d mapReduceLog]# python testpath.py['/data/mapR ...

  9. ASP.NET Core 5.0 MVC中的 Razor 页面 介绍

    Razor 是一个用于将基于服务器的代码嵌入到网页中的标记语法. Razor语法由 Razor 标记.c # 和 HTML 组成. 通常包含 Razor 的文件的扩展名 cshtml Razor 语法 ...

  10. stop脚本

    PID=$(ps -ef | grep eladmin-system-2.0.jar | grep -v grep | awk '{ print $2 }')if [ -z "$PID&qu ...