jiffies与HZ、定时器、原子操作
前言
内核中定时器有两种,简单定时器和高精度定时器,
简单定时器:以全局变量jiffies衡量
高精度定时器:ktime 的特殊数据类型表示 有绝对时间和相对时间概念
原子操作:在多进程(线程)的操作系统中不能被其它进程(线程)打断的操作就叫原子操作,文件的原子操作是指操作文件时的不能被打断的操作。原子操作是不可分割的,在执行过程中不会被任何其它任务或事件中断。
一、Jiffies与HZ
1、jiffies:
是一个变量,每次当定时器中断发生时,内核内部通过一个64位的变量jiffies_64做加一计数。驱动程序开发者通常访问的是jiffies变量,它是jiffies_64的低32位。
jiffies是unsigned long型的变量,该变量被声明为volatile(直接从对应的内存当中提取 ),这样可避免编译器对访问该变量的语句的优化(由于访问寄存器的速度要快过RAM,所以编译器一般都会作减少存取外部RAM的优化 ) 。
2、HZ:
HZ 即表示每秒钟产生的定时中断次数
HZ就代表1秒
在 Linux 2.6 中,系统时钟每 10ms中断一次(时钟频率,用 HZ 宏表示,定义为 100,即每秒中断 100 次,这个时间单位称为一个 jiffie(如10ms)HZ=jiffies+100次
3、时间与jiffies转换
将以秒为单位的时间转化为jiffies:seconds * Hz
将jiffies转化为以秒为单位的时间:jiffies / Hz
4、jiffies的回绕
因为jiffies为unsigned long 的数据类型 防止因回绕产生错误的方法是将它转换为 long (有符号的补码可以进行比较)
避免回绕API:
二、计时、延迟与睡眠
1、计时
提供两套时间计数的方法
①、timeval
一、用户空间的timeval:
struct timeval {
time_t tv_sec; /* 秒 */
suseconds_t tv_usec; /* 毫秒 */
};
获取当前时间:
void do_gettimeofday(struct timeval *tv);
jiffies值和timeval之间转换:
unsigned long timeval_to_jiffies(struct timeval *value);
void jiffies_to_timeval(unsigned long jiffies,struct timeval *value);
②、timespec
二、用户空间的timespec:
struct timespec {
time_t tv_sec; /* 秒 */
long tv_nsec; /* 纳秒 */
};
获取当前时间:
struct timespec current_kernel_time(void);
jiffies值和timespec之间转换:
unsigned long timespec_to_jiffies(struct timespec *value);
void jiffies_to_timespec(unsigned long jiffie, struct timespec *value);
2、延迟
延时实际上是忙等待,cpu空跑一段代码
①、长延时
while(time_before(jiffies,end_time)){//等待超时
schedule();//休眠,让出CPU,内核调度其他进程
}
signed long schedule_timeout(signed long timeout);
典例
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout(delay);
②、短延时
这三个延迟函数均是忙等待函数,因而在延迟过程中,时间片未到时,内核不会调度其他进程,即无法运行其他任务。表象上看,就是系统很卡,CPU资源被一直耗用这。所以 要特别慎用此类函数。
void ndelay(unsigned long nsecs); /*延时纳秒*/
void udelay(unsigned long usecs); /*延时微妙*/
void mdelay(unsigned long msecs); /*延时毫秒*/
3、睡眠
这2个延迟函数是不使用忙等待的延迟函数,因而在延迟过程中,调用该函数的进程会主动调用schedule,即让出CPU,内核调度其他进程运行。
void msleep(unsigned int millisecs);
unsigned long msleep_interruptible(unsigned int millisecs);
三、定时器
定时器的使用很简单。你只需要执行一些初始化工作,设置一个超时时间,指定超时发生后执行的函数,然后激活定时器就可以了。
定时器有两种 简单定时器和高精度定时器
定时器并不是周期运行,它在超时后就自行销毁
1、简单定时器
简单定时器结构体
定时器由结构timer_list表示,在<linux/timer.h>中:
struct timer_list{
struct list_head entry; /*定时器链表入口*/
unsigned long expires; /*以jiffies单位的定时值*/
spin_lock lock; /*保护定时器的锁*/
void (*func)(unsigned long); /* 定时器处理函数*/
unsigned long data; /*参数*/
struct tvec_t_base_s *base; /*定时内部值,用户不要使用*/
};
使用定时步骤:
1、定义定时器:struct timer_list my_timer;
2、初始化定时器:init_timer(&my_timer);
3、定义定时器中断处理函数:
void my_timer_fuc(unsigned long data);
my_timer.function = my_timer_fuc;
my_timer.data = 0;
data参数使你可以利用同一个处理函数注册多个定时器,只需要通过参数就能区别对待它们。如果不需要参数,可以简单的传递0(或任何其他值)给处理函数。
----以上三个步骤可以用以下直接代替
static struct timer_list my_timer =TIMER_INITIALIZER(_function, _expires, _data);
4、激活定时器
mod_timer(&my_tiner, jiffies+new_delay );
第2个参数表示超时时间,它是以节拍为单位的绝对计数值 ,只要节拍计数大于或等于指定的超时时,内核就开始执行定时器处理函数,然后停止计时。
5、停止定时器
①、del_timer(&my_timer);
被激活或未被激活的定时器都可以使用该函数。如果定时器还未被激活,该函数返回0;否则返回1。
②、del_timer_sync(&my_timer);
同步等待可能在其他处理器上运行的定时器处理程序都退出、不能在中断上下文中使用
2、高精度定时器
尽管简单计时器 API 简单有效,但它并不能提供实时应用程序所需的准确性。为此,内核引入了精确度更高的计时器。
注意:时间不是用 jiffies 表示的,而是以一种名为 ktime 的特殊数据类型表示。这种表示方法隐藏了在这个粒度上有效管理时间的一些细节。hrtimer API 正式确认(formalize)了绝对时间和相对时间之间的区别,要求调用者指定类型。
①、初始化定时器:
void hrtimer_init( struct hrtimer *time, clockid_t clock_id, enum hrtimer_mode mode );
其中,第二个参数clock_id有两种取值,如下:
CLOCK_REALTIME:这种类型的时钟可以反映wall clock time,用的是绝对时间,当系统的时钟源被改变,或者系统管理员重置了系统时间之后,这种类型的时钟可以得到相应的调整,也就是说,系统时间影响这种类型的timer。
CLOCK_MONOTONIC:用的是相对时间,他的时间是通过jiffies值来计算的。该时钟不受系统时钟源的影响,只受jiffies值的影响。
建议使用:CLOCK_MONOTONIC这种时钟更加稳定,不受系统时钟的影响。如果想反映wall clock time,就使用CLOCK_REALTIME。
②、 启动定时器 :
int hrtimer_start(struct hrtimer *timer, ktime_t time, const enum hrtimer_mode mode);
③、取消计时器:
int hrtimer_cancel(struct hrtimer *timer);
如果计时器已经发出,那么它将等待回调函数结束
int hrtimer_try_to_cancel(struct hrtimer *timer);
如果计时器已经发出,它将返回失败
四、原子操作
所谓原子操作,就是该操作绝不会在执行完毕前被任何其他任务或事件打断,也就说,它的最小的执行单位,不可能有比它更小的执行单位。
原子操作需要硬件的支持,因此是架构相关的,其API和原子类型的定义都定义在内核源码树的include/asm/atomic.h文件中,它们都使用汇编语言实现,因为C语言并不能实现这样的操作。
1、atomic_t数据类型
原子操作主要用于实现资源计数,很多引用计数(refcnt)就是
通过原子操作实现的。原子类型定义如下
typedef struct
{
volatile int counter;
} atomic_t;
volatile修饰字段告诉gcc不要对该类型的数据做优化处理,
对它的访问都是对内存的访问,而不是对寄存器的访问。
2、操作API
void atomic_read(atomic_t * v);
该函数对原子类型的变量进行原子读操作,它返回原子类型的变量v的值。
void atomic_set(atomic_t * v, int i);
该函数设置原子类型的变量v的值为i。
void atomic_add(int i, atomic_t *v);
该函数给原子类型的变量v增加值i。
void atomic_sub(int i, atomic_t *v);
该函数从原子类型的变量v中减去i。
int atomic_sub_and_test(int i, atomic_t *v);
该函数从原子类型的变量v中减去i,并判断结果是否为0,如果为0,返回真,否则返回假。
void atomic_inc(atomic_t *v);
该函数对原子类型变量v原子地增加1。
void atomic_dec(atomic_t *v);
该函数对原子类型的变量v原子地减1。
int atomic_dec_and_test(atomic_t *v);
该函数对原子类型的变量v原子地减1,并判断结果是否为0,如果为0,返回真,否则返回假。
int atomic_inc_and_test(atomic_t *v);
该函数对原子类型的变量v原子地增加1,并判断结果是否为0,如果为0,返回真,否则返回假。
int atomic_add_negative(int i, atomic_t *v);
该函数对原子类型的变量v原子地增加I,并判断结果是否为负数,如果是,返回真,否则返回假。
int atomic_add_return(int i, atomic_t *v);
该函数对原子类型的变量v原子地增加i,并且返回指向v的指针。
int atomic_sub_return(int i, atomic_t *v);
该函数从原子类型的变量v中减去i,并且返回指向v的指针。
int atomic_inc_return(atomic_t * v);
该函数对原子类型的变量v原子地增加1并且返回指向v的指针。
int atomic_dec_return(atomic_t * v);
该函数对原子类型的变量v原子地减1并且返回指向v的指针。