Linux时间管理与定时器

学习资料:LINUX驱动程序开发实例教程2
时钟周期是指1s,而时钟频率是指1s内时钟脉冲的个数;
时钟滴答:1s内的时钟中断数

1.问:时间戳和节拍总值jiffies有什么区别?
答:时间戳是通过读取timer寄存器或者RTC芯片的寄存器获取的,timer每来一次节拍
时间戳就会加一;而jiffies是一个软件的概念,是系统启动之后从零开始每来一次
节拍就加一,jiffies的值与时间戳不相等;

2.基本概念:
系统时间:从内核启动开始计时的时间积累,为软件记录的时间;
实时时间:RTC时间,从元年1970年开始计时的时间积累;
墙上时间:数值上与实时时间相等;墙上时间存储在xtime里面;

  问:硬实时与软实时有什么区别?
  答:硬实时是指从RTC芯片里面读出来的时间,软实时是指内核的xtime,软件概念。      
  问:硬实时与软实时有什么关联?
  答:pc断电时,软实时xtime自动消失,而硬实时在RTC芯片里继续计时;
     pc启动时,内核读出RTC芯片的时间值初始化xtime,然后软实时xtime就内核态下自动计时,而RTC停止计时,由软实时
     xtime  负责更新硬实时RTC;
	 pc启动时,软实时工作,pc关闭是硬实时工作;

3.内核获取墙上时间的接口有sys_gettimeofday(struct timeval* tv, struct timezone* tz),
两个参数指针分别是:
tv里面装着1970年走过的秒数和距离上一秒的纳秒数,tz里面装着时区以及夏令时当前
的状态;

4.问:Linux内核的时间管理的核心是什么?
答:是soc的硬件定时器的中断处理程序去更新墙上时间,RTC时间,jiffies,执行内核定时器,计算进程运行时间或者重新设置硬件寄存器本身;

5.内核定义USER_HZ代表用户空间看到的HZ值,内核定义的HZ宏代表的是内核看到的SOC硬件节拍率;

6.体系结构提供4种硬时钟用于计时:实时时钟,墙上时间,时间戳计数,可编程中断定时器。

实时时钟(RTC):是指RTC芯片读回来的墙上时钟,即使PC关掉电源,RTC还能依靠 主板电池继续计时。 实时时钟=RTC时钟=墙上时间;RTC时钟:断电时自动更新,不断电时要Linux内核的驱动程序负责更新;在芯片没有断电和内核正在正常运行时,RTC时钟会被墙上时间更新,在定时器的中断服务程序里面RTC时间会被墙上时间覆盖更新;

墙上时间xtime:是内核的一个全局变量xtime,会在定时器的中断服务程序里被更新。它的数值会与RTC时间一 样,只不过RTC时间为硬件实时时间,而墙上时间xtime为软件实时时间。内核启动的时候会从RTC芯片中读出时间来初始化此墙上时间。

时间戳计数TSC:SOC有一个64bit的寄存器,对硬件定时器的中断信号(时钟信号)进行计算。每来一个时钟中断信号或者每来一个节拍这个寄存器的数值就会加1。在内核启动的时候这个时间戳开始从0递增,每来一个节拍计算一次。 在数值上时间戳TSC=jiffies,并不是每个SOC都有硬件时间戳的,x86系列会有,arm可能没有。

节拍总值jiffies:内核启动的时候jiffies被初始化为0,然后在硬件定时器的中断服务程序里面递增。可以理解为软件时间戳。

可编程中断定时器 PIT:就是soc里面的硬件timer,可以通过修改驱动程序去改变其的中断频率。其中可能有寄存器直接读出硬件时间戳TSC.x86体系结构中,主要采用可编程中断时钟(PIT)作为系统(硬件)定时器。Linux中,若PIT以100Hz频率向IRQ0发出定时中断,即每10ms产生1次定时中断。这个10ms间隔,就是一(tick), 以微妙为单位存放在tick变量。

7.Linux内核的时间管理流程是怎样的?
1):初始化SOC的timer,一种可编程硬件芯片,能以固定频率产生中断,如在arm中这个 频率一般为100hz,即10ms产生一次中断,一次中断为一个节拍。
2):芯片或者内核启动时,内核通过读取RTC来初始化墙上时间,该时间存放在xtime变 量中。
3):内核启动的时候节拍总值jiffies=0,HZ=100,系统时间=0;
4):在定时器的中断服务程序里面jiffies++,更新墙上时间,RTC时间,系统时间(内核运行的时间总和)。
5):程序使用时间管理的接口去获取各种时间参数来写程序;各种时间参数有(RTC时间,系统时间,节拍总值jiffies,节拍率HZ…)。

8.Linux内核的硬件定时器的中断服务程序做了哪些工作?
答:定时器的中断服务程序所做的工作主要分为两部分,一。体系结构相关部分 二。体系结构无关部分
体系相关:
获得xtime_lock锁,对访问jiffies_64和墙上时间进行保护。
需要时应答或重新设置系统时钟。
根据RTC芯片的饿特性->周期性地使用墙上时间更新实时(RTC)时钟。
体系无关:
给jiffies_64+1
更新资源消耗的统计值,如当前进程所消耗的系统时间和用户时间。
执行已经到期的动态定时器(内核定时器)。
更新墙上时间,该时间存放在xtime中。
计算平均负载值。

9.写驱动程序时或者写应用程序时有哪些接口可工程序员使用来获取RTC时间(硬实时),墙上时间(软实时),节拍
总值,节拍率等参数?
RTC时间 要根据具体的芯片型号进行驱动读取它存贮的硬实时时间;
内核态用 sys_gettimeofday(struct timeval* tv, struct timezone* tz)获取墙上时间。
用户态用 gettimeofday(struct timeval* tv, struct timezone* tz)获取墙上时间。
节拍总值 jiffies是个全局宏,可直接使用,与current一样.
节拍率:可直接用宏HZ.

在shell脚本获取墙上时间,系统运行时间可参考:https://blog.csdn.net/jks212454/article/details/120144835

10.内核提供了哪些方法来实现进程上下文延迟?
答:
1)忙等待:利用节拍总值jiffies和每个节拍的时长来延时,缺点是不精准,消耗cpu资源;
2)短延迟:利用内核提供的udelay,mdelay;缺点也是不够精准;
3)schedule_timeout(n):利用调度将当前进程睡眠n个节拍之后自动唤醒;
4)设置超时时间,在等待队列上睡眠:利用等待队列实现定时睡眠;跟3)有点像;

Linux内核时间管理与定时器

节拍率:HZ(一秒钟产生多少次节拍)
jiffies:(节拍总值,从内核启动开始算起)
jiffies内部表示
jiffies回绕:(jiffies是个32bit的数据,或者64bit,计数满了之后会回到0状态)
用户空间和HZ
硬时钟和定时器:硬时钟是指(RTC硬件时钟,定时器是中Linux内核的动态定时器)
时钟中断处理程序:
墙上时间(实际时间)
time, ftime, gettimeofday关系
定时器:
定时器竞争条件
实现定时器
延迟执行:
忙等待
短延迟
schedule_timeout() 睡眠到指定延迟时间
设置超时时间,在等待队列上睡眠

基本概念

系统(soc系统,硬件概念)定时器:一种可编程硬件芯片,能以固定频率产生中断,是指SOC硬件系统的定时器timer,可编程,为硬件定时器。
定时器中断:系统定时器固定时间周期产生的中断,其中断处理程序负责更新系统时间(linux系统的时间,软件概念),执行周期性任务。
动态定时器:一种用来推迟执行程序的工具。内核可以动态创建、销毁动态定时器,这是一种内核定时器,软件定时器。
节拍率(HZ):一秒钟产生多少次节拍,系统定时器频率,系统定时器以某种频率自行触发(又称为击中(hitting)或射中(popping))时钟中断,该频率可以通过编程设定。
节拍总值(jiffies):预编的节拍率对内核来说是可知的,因此内核知道连续2次时钟中断间隔时间。这个时间称为节拍。节拍 = 1/ 节拍率。常用来计算墙上时间和系统运行时间,是指内核启动开始计算的节拍总数。
墙上时间(walk clock time):RTC芯片记录的实时时间,实际时间,对用户空间的应用程序来说很重要。代表从进程开始运行到结束,系统时钟走过的时间(时钟数),包含了进程阻塞的时间。每秒滴答数(节拍率)可通过sysconf(_SC_CLK_TCK)获取。
系统时间:自系统(soc系统,硬件概念)启动开始所经过的时间,对用户空间和内核都很有用。
墙上时间 = 阻塞时间 + 就绪时间 + 运行时间,运行时间 = 用户CPU实际 + 系统CPU时间。
系统定时器中断周期性执行的任务:

更新系统时间。->更新SOC运行了多久
更新实际时间。->更新RTC实时时间
在smp系统上,均衡调度程序中各处理器上的运行队列。如果运行队列负载不均衡的话,尽量使它们均衡。
检查当前进程是否用尽了自己的时间片。如果用尽,就重新调度。
运行超时的动态定时器。
更新资源消耗和处理器时间的统计值。
[======]

节拍率:HZ
系统定时器(节拍率)通过静态预处理定义,系统启动时按HZ值对硬件进行设置。HZ值取决于体系结构。如i386体系结构,HZ值为1000(Hz),代表每秒钟产生1000次节拍

#include <asm/param.h>

#define HZ 1000 /* 内核时钟频率 */
其他体系结构节拍率:
在这里插入图片描述

系统(SOC硬件)定时器使用高频率优缺点
优点:
内核定时器能以更高频度和准确度运行。
依赖定时值执行的系统调用,如select,poll,能以更高精度运行。
对诸如资源消耗和系统运行时间等的测量会有精细的解析度。
提高进程抢占的准确度。

缺点:
节拍率越高,系统(硬件)时钟中断频率越高,意味着系统负担越重,即中断处理处理程序占用的处理器时间越多,减少了处理其他工作的时间。
[======]

jiffies
全局变量jiffies用来记录自系统启动以来产生的节拍总数。启动时,初值0;之后,每次时钟中断处理程序都会让jiffies+1。

jiffies定义:

#include <linux/jiffies.h>

extern unsigned long volatile jiffies;
jiffies内部表示
32bit体系结构上,jiffies是32bit,如果时钟频率100Hz,497天后会溢出;频率1000Hz,49.7天后溢出。
64bit体系结构上,几乎不可能会看到它溢出。

除了前面定义,jiffies还有第二个变量定义:

#include <linux/jiffies.h>

extern u64 jiffies_64;
ld(1) 脚本用于连接主内核映像,然后用jiffies_64初值覆盖jiffies变量:

// x86, arch/i386/kernel/vmlinux.lds.S
jiffies = jiffies_64;
也就是说,jiffies只取jiffies_64低32bit。因为大多数代码只使用jiffies存放流失的时间,二时间管理代码使用整个64bit的jiffies_64,以避免溢出。

在32bit体系结构上,jiffies 读取jiffies_64低32bit值;get_jiffies_64()读取jiffies_64整个64bit值。
周64bit体系结构上,jiffies 等价于get_jiffies_64(),和jiffies_64是同一个变量。

jiffies回绕
jiffies 溢出后,会绕回(wrap around)到0。内核提供4个宏函数,用于比较节拍计数,以避免回绕问题:
#include <linux/jiffies.h>
// unknown是jiffies, known是需要对比的值
#define timer_after(unknown, known) ((long)(known) - (long)(unknown) < 0) //后面的比前面的小则返回真
#define timer_before(unknown, known) ((long)(unknown) - (long)(known) < 0)//前面的比后面的小则返回真
#define timer_after_eq(unknown, known) ((long)(unknown) - (long)(known) >= 0)//后面的比前面的小
#define timer_before_eq(unknown, known) ((long)(known) - (long)(unknown) >= 0)//前面的比后面的小

用户空间和HZ
Linux内核2.6以前,如果改变内核中HZ值,会给用户空间中某些程序造成异常结果,因为应用程序已经依赖这个特定HZ值。要避免上面错误,内核需要更改所有导出的jiffies值。因此,内核定义USER_HZ代表用户空间看到的HZ值。

例如,x86体系结构上,HZ值原来一直是100,因此USER_HZ值定义为100。
内核使用宏jiffies_to_clock_t() 将一个由HZ表示的节拍计数转换成一个由USER_HZ表示的节拍数。
当USER_HZ是HZ的整数倍时。

#define jiffies_to_clock_t(x) ((x) / (HZ/USER_HZ))
另外,jiffies_64_to_clock_t()将64位jiffies值单位从HZ转换为USER_HZ。

[======]

硬时钟和定时器
体系结构提供3种硬时钟用于计时:实时时钟,时间戳计数,可编程中断定时器。

实时时钟 RTC
RTC是用来持久存放系统时间的设备,即使PC关掉电源,RTC还能依靠主板电池继续计时。
主要作用:
1)系统启动时,内核通过读取RTC来初始化墙上时间,该时间存放在xtime变量中。
2)Linux只用RTC来获得当前时间和日期。

时间戳计数 TSC
x86包含一个64位的时间戳计数器(寄存器),对每个时钟信号进行计数。例如,如果时钟节拍400MHz,那么TSC每2.5ns计数+1。而时钟信号频率没有在预编译时指定,必须在Linux初始化时确定。通过calibrate_tsc(),在系统初始化阶段完成时钟信号频率计算。

可编程中断定时器 PIT
x86体系结构中,主要采用可编程中断时钟(PIT)作为系统(硬件)定时器。
Linux中,若PIT以100Hz频率向IRQ0发出定时中断,即每10ms产生1次定时中断。这个10ms间隔,就是一个节拍(tick),以微妙为单位存放在tick变量。

TSC与PIT相比,拥有更高的精度。PIT针对编写软件而言,更加灵活。

[======]

时钟中断处理程序
时钟中断处理程序可划分2个部分:体系结构相关部分,体系结构无关部分。
与体系结构相关的例程作为系统定时器(PIT)的中断处理程序而注册到内核,以便产生时钟中断时能运行。
处理程序主要执行以下工作:

获得xtime_lock锁,对访问jiffies_64和墙上时间进行保护。
需要时应答或重新设置系统时钟。
周期性地使用墙上时间更新实时时钟。
调用体系结构无关的时钟例程:do_timer()。
中断服务程序主要通过调用与体系结构无关的do_timer()执行工作:

给jiffies_64 + 1。
更新资源消耗的统计值,如当前进程所消耗的系统时间和用户时间。
执行已经到期的动态定时器。
更新墙上时间,该时间存放在xtime变量中。
计算平均负载值。
do_timer()看起来像:

void do_timer(struct pt_regs* regs)
{
jiffies_64++;

update_process_times(user_mode(regs)); // 对用户或系统进行时间更新
update_times(); // 更新墙上时钟

}
user_mode()宏查询处理器寄存器regs的状态。如果时钟中断发生在用户空间,它返回1;如果发生在内核,则返回0。update_process_times()函数根据时钟中断产生的位置(用户态 or 内核态),对用户或对系统进行相应的时间更新。

void update_process_times(int user_tick)
{
struct task_struct *p = current;
int cpu = smp_processor_id();
int system = user_tick ^ 1; // user_tick和system只会有一个变量为1,另一个必为0

update_one_process(p, user_tick, system, cpu); // 更新进程时间
run_local_timers();  // 标记一个软中断处理所有到期的定时器
scheduler_tick(user_tick, system); // 负责减少当前运行进程的时间片计数值,并在需要时设置need_resched标志

}
update_one_process() 通过判断分支,将user_tick和system加到进程相应的计数上:

/* 更新恰当的时间计数器,给其加一个jiffy */
p->utime += user;
p->stime += system;

update_times()负责更新墙上时钟:

void update_times(void)
{
unsigned long ticks; // 记录最近一次更新后新产生的节拍数

ticks = jiffies - wall_jiffies;
if (ticks) {
    wall_jiffies += ticks;
    update_wall_time(ticks); // 更新存储墙上时间的xtime
}
last_time_offset = 0;
calc_load(ticks); // 更新载入平均值

}
ticks记录最近一次更新后新产生的节拍数。通常,ticks应为1,但时钟中断可能丢失,导致节拍丢失。中断长时间被禁止时,就会出现这种情况(虽然很可能是bug)。

cal_load(0更新载入平均值,到此,update_times()执行完毕。do_timer()亦执行完毕并返回与体系结构相关的中断处理程序,继续执行后面的工作,释放xtime_lock锁,然后退出。

墙上时间(软件RTC时间)
墙上时间定义在kernel/timer.c中

struct timespec xtime;
timespec结构定义:

#include <linux/time.h>
struct timespec {
time_t tv_sec; /* 秒 /
long tv_nsec; /
纳秒 */
};
xtime.tv_sec 存放着自1970年7月1日(UTC)以来经过的时间。1970年7月1日被称为纪元,Unix墙上时间都是基于该纪元的。
xtime.ntv_sec记录着自上一秒开始经过的纳秒数。

读写xtime变量需要用xtime_lock锁,这是一个seqlock锁。
更新xtime:

write_seqlock(&xtime_lock);

/* 更新xtime… */

write_sequnlock(&time_lock);
读取xtime:

/* 循环更新xtime, 直到确认循环期间没有时钟中断处理程序更新xtime */
do {
unsigned long lost;
seq = read_seqbegin(&xtime_lock);

usec = timer->get_offset();
lost = jiffies->wall_jiffies;
if (lost) 
    usec += lost * (1000000/HZ);
sec = xtime.tv_sec;
usec += (xtime.tv_nsec/1000);

} while(read_seqretry(&xtime_lock, seq));
如果循环期间有时钟中断处理程序更新xtime,read_seqretry()会返回无效序列号,继续循环等待。

从用户空间取得墙上时间的主要接口:gettimeofday(),内核中对应系统调用sys_gettimeofday():

asmlinkage long sys_gettimeofday(struct timeval* tv, struct timezone* tz)
{
if (likely(tv))
{ // <=> if (tv)
struct timeval ktv;
do_gettimeofday(&ktv); // 循环读取xtime操作
}

if (copy_to_user(tv, &ktv, sizeof(ktv))) // 在给用户空间拷贝墙上时间或时区
    return -EFAULT; // 拷贝时发生错误
    
if (unlikely(tz))
 { // <=> if (!tz)
    if (copy_to_user(tz, &sys_tz, sizeof(sys_tz))) 
    			return -EFAULT;
}
return 0;

}

/* 宏likely和unlikey在内核中定义, 便于编译器优化, 以提升性能 */
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
time, ftime, gettimeofday关系
内核也实现了time(), ftime()系统调用,但都被gettimeofday()所取代。为保持向后兼容,Linux还保留着。

time() 返回从1970年1月1日午夜开始所走过的秒数。
ftime() 返回一个类型为timeb的数据结构,该结构包含从1970年1月1日午夜开始所走过的秒数;在最后1秒内所走过的毫秒数;时区以及夏令时当前的状态。
gettimeofday() 返回的值存放在2个数据结构timeval和timezone,其中包含的信息与ftime相同。
[======]

定时器
定时器被称为动态定时器或内核定时器,是管理内核时间的基础。

定时器使用
思路:先进行一些初始化工作,设置一个超时时间,指定超时后执行的函数,然后激活定时器。指定的函数将在定时器到期时自动执行。
定时器不会周期运行,超时后自行销毁。这是这种定时器被称为动态定时器的一个原因。因此,动态定时器是在不断的创建和销毁,而且运行次数不受限制。在内核中应用非常普遍。

定时器由结构timer_list表示,定义于<linux/timer.h>

struct timer_list {
struct list_head entry; /* 定时器链表入口 /
unsigned long expires; /
以jiffies为单位的定时值 /
spinlock_t lock; /
保护定时器的锁 */
void (function)(unsigned long); / 定时器处理函数 /
unsigned long data; /
传给处理函数的长整型参数 */
struct tvec_t_base_s base; / 定时器内部值, 用户不要使用 */
};
使用定时器不用深入了解timer_list结构。内核提供一组接口简化管理定时器的操作。

1)定义定时器

struct timer_list my_timer;
2)初始化定时器

init_timer(&my_timer);
3)填充定时器结构中需要的值

my_timer.expires = jiffies + delay; /* 定时器超时节拍数 /
my_timer.data = 0; /
给定时器处理函数传入值0 /
my_timer.function = my_function; /
定时器超时调用的处理函数 */
超时处理函数必须是这种原型:

void my_timer_function(unsigned long data);
4)激活定时器

add_timer(&my_timer);
定时器工作条件:当前节拍计数jiffies >= my_timer.expires
定时器会在超时后马上执行,但也可能推迟到下一个时钟节拍,因此不能用于硬实时任务。

5)修改定时器
改变超时时间

mod_timer(&my_timer, jiffies + new_delay); /* new expiration */
mod_timer可用于已经初始化但未激活的定时器;如果定时器未被激活,mod_timer会激活之。
如果调用时,定时器未被激活,函数返回0;否则,返回1.

6)删除定时器
在定时器超时前定制定时器

del_timer(&my_timer);
激活或未被激活的定时器都可以用该函数,如果未被激活,函数返回0;否则,返回1。
已超时的定时器不需要调用该函数,因为会自动被删除。

del_timer只能保证定时器将来不会被激活,不保证当前在其他处理器上已运行时会停止。此时,需要用del_timer_sync,等待其他处理器上运行的超时处理函数退出。

del_timer_sync(&my_timer); /* 如果有并发访问可能性, 推荐优先使用 */
del_timer_sync() 不能在中断上下文中使用,因为会阻塞。
定时器竞争条件
定时器与当前执行(设置定时器的)代码是异步的,因此可能存在潜在竞争条件。因此,不能用如下方式替代mod_timer(),来改变定时器的超时时间,因为在多处理器上是不安全的:

/* 用下面代码替换mod_timer, 修改定时器超时时间是错误的 */
del_timer(&my_timer);
my_timer->expires = jiffies + new_delay;
add_timer(&my_timer);
通常,用过用del_timer_sync() 取代del_timer()删除定时器,避免并发访问的问题,因为无法确定删除定时器的时候,它是否在其他处理器上运行。

实现定时器
定时器作为软中断在下半部上下文中执行。
时钟中断处理程序会执行update_process_timers(),该函数会随即调用run_local_timers()。

void run_local_timers(void)
{
raise_softirq(TIMER_SOFTIRQ);
}
run_timer_softirq()处理软件中断TIMER_SOFTIRQ,从而在当前处理器上运行所有的超时定时器。

内核定时器是以链表形式存放,但并没有遍历链表以寻找超时定时器,也没有在链表中插入和删除定时器。
而是,将定时器按超时时间分为五组。当定时器超时时间接近时,定时器将随组一起下移。采用分组定时器的方法可以在执行软中断的多数情况下,可以确保内核尽可能减少搜索超时定时器所带来的负担。

[======]

延迟执行
内核代码(尤其驱动程序)除了用定时器或下半部机制外,还需要其他方法来推迟执行任务。
常适用于:短时间等待硬件完成某些工作,比如,重新设计网卡的以太网模式(2ms)。

内核提供多种延迟方法处理各种延迟要求:
1)忙等待
2)短延迟
3)schedule_timeout()
4)设置超时时间,在等待队列上睡眠

忙等待
忙等待(或称忙循环),是最简单的延迟方法,也是最不理想的。
方法仅适用于想要延迟的时间是节拍的整数倍,或者精确度要求不高时使用。

忙循环使用示例:在循环中不断旋转直到希望的时钟节拍数耗尽

unsigned long delay = jiffies + 10; /* 10个节拍 */

while (time_before(jiffies, delay)) /* CPU循环等待 jiffies > delay (自动处理定时器值回绕) */
;
上面循环不断旋转,等待10个节拍。HZ值为1000的x86体系结构上,每个节拍1ms,10个节拍总共耗时10ms。

unsigned long delay = jiffies + 2 * HZ; /* 2秒 */

while (time_before(jiffies, delay))
;
上面循环自旋时,并不会放弃CPU。下面cond_resched()将调度一个新程序投入运行,不过只有在设置完need_resched标志后,才能生效。因为cond_resched方法会调用调度程序,因此不能在中断上下文中使用,而只能在进程上下文中使用。

unsigned long delay = jiffies + 5 * HZ;

while (time_before(jiffies, delay))
cond_resched(); /* 调度一个新程序投入运行 */
注意:
1)所有延迟方法都只能在进程上下文使用,不能在中断上下文使用。因为中处理程序应尽快执行。
2)延迟执行 不应在持有锁或者禁止中断的时候发生。

短延迟
有时驱动程序不但需要很短的延迟(比时钟节拍typ.为1ms还短),而且要求延迟的时间很精确。不可能使用精度为1ms的jiffies节拍用于延迟。
此时,可以用内核提供的另外2个函数,用于处理微妙和毫秒级延迟。
头文件:<linux/delay.h>

void udelay(unsigned long usecs);
void mdelay(unsigned long msecs);
mdelay是通过udelay实现的。

如,延迟150微秒,延迟200毫秒

udelay(150); /* 延迟150us /
mdelay(200); /
延迟200ms */
注意:
1)延迟超过1ms时,不要用udelay,应该用mdelay。
2)能不用则不用mdelay,尽量少用。
3)不要在持有锁或者禁止中断时,使用忙等待,因为类似于忙等待,会让系统响应速度和性能大打折扣。

schedule_timeout() 睡眠到指定延迟时间
该方法会让需要延迟执行的任务睡眠到指定的延迟时间耗尽后,再重新运行。不能保证睡眠时间刚好等于指定的延迟时间,只能是尽量接近。当指定时间到期后,内核唤醒被延迟的任务并将其重新放回运行队列。

典型用法:

/* 将任务设置为可中断睡眠状态 */
set_current_state(TASK_INTERRUPTIBLE);

unsigned long S = 10;
/* 小睡一会儿,S秒后唤醒 */
schedule_timeout(s * HZ);
唯一的参数是延迟的相对时间,单位jiffies。

如果睡眠时,想接收信号,可将任务状态设置为TASK_INTERRUPTIBLE;如果不想,可以将任务状态设置为TASK_UNINTERRUPTIBLE。
注意:调用schedule_timeout()前,必须将任务设置为上面两种状态之一,否则任务不会睡眠。

schedule_timeout的简单实现:

signed long schedule_timeout(singed long timeout)
{
timer_t timer;
unsigned long expire;

switch(timeout)
{ /* 处理特殊情况 */
case MAX_SCHEDULE_TIMEOUT: /* 无限期睡眠 */
    schedule(); /* 调度进程: 从就绪队列中选一个优先级最高的进程来替代当前进程运行 */
    goto out;
default:
    if (timeout < 0) {
        printk(KERN_ERR"schedule_timeout: wrong timeout value %lx from %p\n", timeout, __builtin_return_address(0));
        goto out;
    }
}

expire = timeout + jiffies;
init_timer(&timer); /* 初始化动态定时器 */
timer.expires = expire; 
timer.data = (unsigned long)current;
timer.funtion = process_timeout;

add_timer(&timer); /* 激活定时器 */
schedule();
del_timer_sync(&timer); /* 同步删除定时器 */

timeout = expire - jiffies;

out:
return timeout < 0 ? 0 : timeout;
}

/* 定时器超时处理函数 */
void process_timeout(unsigned long data)
{
wake_up_progress((task_t )data); / 唤醒进程, 将任务设置为TASK_RUNNING */
}
因为任务被标识为TASK_INTERRUPTIBLE或TASK_UNINTERRUPTIBLE(在调用schedule_timeout之前),所以调度程序不会再选择该任务投入运行,而会选择其他新任务运行。

设置超时时间,在等待队列上睡眠
进程上下文中为了等待特定事件发生,会将自己放入等待队列,然后调用调度程序执行新任务。一旦事件发生,内核可调用wake_up()唤醒在睡眠队列上的任务,使其重新投入运行。

schedule_timeout用在什么地方?
当等待队列上的某个任务可能既在等待一个特定事件到来,又在等待一个特定时间到期,看谁先来。此时,可以用schedule_timeout替换schedule(),因为schedule()只是简单的阻塞等待唤醒事件,而schedule_timeout除了可以等待IO事件,还会等待超时。

[======]

小结
1)讲述了时间的基本概念,如墙上时间,时钟中断,时钟节拍,HZ,jiffies等。
2)定时器的实现,应用方法等。
3)开发者用于延迟的方法:忙等待、短延迟、schedule_timeout。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值