目录
一、引言
二、时间概念
三、定时器
四、POSIX中的定时器
五、延迟执行
六、进程休眠
一、引言
时间管理在内核中十分重要,我们常用的延时、定时等功能,包括实际的系统运行时间,都由其来实现。
(ps:杭州linux嵌入式方面的新人,目前从事android开发,欢迎相关方面的同学,希望能交个朋友)
二、时间概念
硬件为内核一共了一个系统定时器来计算流逝的时间,该定时器以某种频率自行触发,可以通过编程预定,称为节拍(tick),它等于1/ticket (节拍率)秒。
同时,内核也为用户空间提供了一组系统调用来获取实际日期和实际时间
节拍率 HZ
系统定时器频率(节拍率),是通过静态预处理定义的,称为HZ,每个机器的HZ都有可能不同。
比如X86的默认频率为100,每秒钟时钟会中断100次,10ms产生一次
常见处理器的系统时钟频率如下
jiffies
全局变量jiffies用来记录自系统启动后产生节拍的总数,每次时钟中断都会增加该变量的值
jiffies/HZ 就是系统运行的秒数
内核中可以使用该变量设置超时时间
extern unsigned long volatile jiffies;
//可以利用jiffies设置超时等,譬如:
unsigned long timeout = jiffies + tick_rate * 2; // 2秒钟后超时
if(time_before(jiffies, timeout){
// 还没有超时
}
else{
// 已经超时
}
常用法
三、定时器
定时器使用 timer_list 结构体,在’kernel\include\linux\timer.h’中定义
其接口大多数在’kernel\kernel\timer.c’中实现
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
};
定时器的使用
1) 定义定时器:
struct timer_list my_timer
2)初始化定时器:
初始化定时器的到期节拍数
my_timer.expires = jiffies +delay ;该设置让定时器的触发时间设置为 激活定时器后的delay个节拍点
my_timer.function = 处理函数的名称 该设置设置定时器触发时处理的函数
my_timer.data 初始化处理函数的参数值,若处理函数没有参数则可简单设置为0或任意其他数值
3)激活定时器:即内核会开始定时,直到my_timer.expires
使用函数add_timer 即 add_timer(&my_timer);
4)删除定时器:如果需要在定时器到期之前停止定时器,则可以使用该函数,若是定时器已经过期则不需调用该函数,因为它们会自动删除
del_timer(&my_timer);
5)删除定时器以及其他处理器上的定时器处理程序,在多处理器的终端上通常使用这个接口来删除定时器
del_timer_sync(&my_timer);
6)修改定时器超时时间:如果想要修改定时器的超时时间,并且重新开始计时,可以使用 mod_timer(&stimer, jiffies + HZ);
#include< linux/module.h >
#include< linux/init.h >
#include< linux/sched.h >
#include < linux/timer.h >
#include < linux/kernel.h >
struct timer_list stimer; //定义定时器
static void time_handler(unsigned long data){ //定时器处理函数
mod_timer(&stimer, jiffies + HZ); //修改设置到期时间,并重新激活定时器
printk(“current jiffies is %ld\n”, jiffies);
}
static int __init timer_init(void){ //定时器初始化过程
printk(“My module worked!\n”);
init_timer(&stimer);
stimer.data = 0;
stimer.expires = jiffies + HZ; //设置到期时间
stimer.function = time_handler;
add_timer(&stimer); //添加一个定时器,并开始计时
return 0;
}
static void __exit timer_exit(void){
printk(“Unloading my module.\n”);
del_timer(&stimer);//删除定时器
return;
}
module_init(timer_init);//加载模块
module_exit(timer_exit);//卸载模块
MODULE_LICENSE(“GPL”);
看一下add_timer(&stimer); 中做了什么事
void add_timer(struct timer_list *timer)
{
BUG_ON(timer_pending(timer));
mod_timer(timer, timer->expires);
}
最后还是调用mod_timer,更新超时时间,并开始计时
四、POSIX中的定时器
在需要移植到不同平台的程序中,通常会使用POSIX中的接口
POSIX创建、初始化以及删除一个定时器的行动被分为三个不同的函数
1、timer_create()(创建定时器)
2、timer_settime()(初始化定时器)
3、timer_delete(销毁它)
五、延迟执行
linux中延迟执行有以下几种方式
1、忙等待
在延时的时间是节拍的整数倍且精确度要求不高的情况下可以使用
而对应的该进方式如下
允许在等待的时候执行其他程序
2、短延时
当我们需要很短暂的延时(比时钟节拍还短),而且要求很精确的时候,不能再使用jiffies的延迟方法
在linux内核中提供了三个函数来分别实现纳秒,微秒,毫秒延迟,原理上是忙等待,它根据CPU频率进行一定次数的循环(不会进行进程切换)
void ndelay(unsigned long nsecs);
void udelay(unsigned long usecs);
void mdelay(unsigned long msecs);
毫秒延迟已经相当大了,当然更秒延迟当然要小一些,在内核中,为了性能,最好不要用mdelay,这会耗费大量cpu资源
void msleep(unsigned int millisecs);
unsigned long msleep_interruptible(unsigned int millisecs);
void ssleep(unsigned int seconds);
这三个是内核专门提供该我们用来处理毫秒以上的延迟。上述函数将使得调用它的进程睡眠参数指定的秒数,其中第二个是可以被打断的,其余的两个是不可以的。
3、睡着延时
睡着延迟:这显然是比忙等待好的方法,因为在未到来之前,进程会处于睡眠状态,把CPU空出来,让CPU可以做别的事情,等时间到了,调用schedule_timeout()就可以唤醒它并重新调度执行。msleep和msleep_interruptible本质上都是依靠包含了schedule_timeout的schedule_timeout_uninterruptible()和schedule_
timeout_interruptible()实现。就像下边这样:
void msleep(unsigned int msecs)
{
unsigned long timeout = msecs_to_jiffies(msecs) + 1;
while(timeout)
timeout = schedule_timeout_uninterruptible(timeout);
}
unsigned long msleep_interruptible(unsigned int msecs)
{
unsigned long timeout = msecs_to_jiffies(msecs) + 1;
while(timeout && !signal_pending(current))
timeout = schedule_timeout_interruptible(timeout);
return jiffies_to_msecs(timeout);
}
signed long __sched schedule_timeout_interruptible()signed long timeout)
{
__set_current_state(TASK_INTERRUPTIBLE);
return schedule_timeout(timeout);
}
signed long __sched schedule_timeout_uninterruptible()signed long timeout)
{
__set_current_state(TASK_UNINTERRUPTIBLE);
return schedule_timeout(timeout);
}
另外还有如下:
time_on_timeout(wait_queue_head_t *q, unsigned long timeout);
interruptible_sleep_on_timeout(wait_queue_head_t *q, unsigned long timeout);
这两个将当前进程添加到等待队列,从而在等待队列上睡眠,当超时发生时,进程将被唤醒。
六、进程休眠
在Linux中,仅等待CPU时间的进程称为就绪进程,它们被放置在一个运行队列中,一个就绪进程的状态标志位为TASK_RUNNING。一旦一个运行中的进程时间片用完, Linux内核的调度器会剥夺这个进程对CPU的控制权,并且从运行队列中选择一个合适的进程投入运行。
当然,一个进程也可以主动释放CPU的控制权。函数schedule()是一个调度函数,它可以被一个进程主动调用,从而调度其它进程占用CPU。一旦这个主动放弃CPU的进程被重新调度占用CPU,那么它将从上次停止执行的位置开始执行,也就是说它将从调用schedule()的下一行代码处开始执行。
可中断的睡眠状态的进程会睡眠直到某个条件变为真,比如说产生一个硬件中断、释放进程正在等待的系统资源或是传递一个信号都可以是唤醒进程的条件。不可中断睡眠状态与可中断睡眠状态类似,但是它有一个例外,那就是把信号传递到这种睡眠状态的进程不能改变它的状态,也就是说它不响应信号的唤醒。不可中断睡眠状态一般较少用到,但在一些特定情况下这种状态还是很有用的,比如说:进程必须等待,不能被中断,直到某个特定的事件发生。
在现代的Linux操作系统中,进程一般都是用调用schedule()的方法进入睡眠状态的
而上面的睡着延时,本质也是休眠,只不过有一个超时时间,到达这个时间后自动被唤醒
手动唤醒
我们可以使用下面的这个函数将刚才那个进入睡眠的进程唤醒。
wake_up_process(sleeping_task);
在调用了wake_up_process()以后,这个睡眠进程的状态会被设置为TASK_RUNNING,而且调度器会把它加入到运行队列中去。当然,这个进程只有在下次被调度器调度的时候才能真正地投入运行