淘先锋技术网

首页 1 2 3 4 5 6 7

一、内核的时间

(1)Tick(滴答)

内核采用了一个新的时间单位来进行计时。该时间单位称为tick(滴答),一个tick对应硬件定时器两次中断之间的时间间隔。当前内核每秒钟硬件定时器会发生HZ次中断。tick和秒的换算关系为: 1 tick = 1/HZ秒。
HZ是在内核make menuconfig(内核的.config文件)时确定,如果要修改HZ值,需要重新编译内核。

(2)相对时间

内核从开机开始记录一个相对时间。内核利用全局变量jiffies来记录从开机到当前时间所经过的tick的总量。
jiffies++的工作是由硬件定时器的中断处理函数完成的。jiffies的类型是unsigned long,在32位平台上最大值为4G。因此,当HZ为1000时,大约49.7天会溢出一次。为了避免溢出,可以使用64位的变量jiffies_64,大约2000亿天会溢出一次。

内核里的很多延迟和定时,都是用jiffies进行判断的。如果要访问jiffies,应包含头文件”linux/sched.h”。

(3)绝对时间

1969 UNIX诞生,绝对时间为从1970年1月1日0时0分0秒开始到现在的时间。在内核中用两个结构体来记录绝对时间:timeval和timespec,在头文件“linux/time.h”中。

#include <linux/time.h>
struct timeval tval;
struct timespec tspec;
调用内核的函数来获得绝对时间
do_gettimeofday(&tval);
getnstimeofday(&tspec);
打印绝对时间
printk("%lds : %ldus\n", tval.tv_sec, tval.tv_usec);
printk("%lds : %ldns\n", tspec.tv_sec, tspec.tv_nsec);

二、内核的延迟和定时

延迟:当前程序停下来,等待某个条件满足。延迟是不得已的,如果程序可以运行,就不应该延迟;
定时:启动定时器,由内核(硬件定时器的中断处理函数)在未来的某个时间为启动定时器者完成某项工作。

(1)基于忙循环的延迟
如果是比较短时间的延迟,则可以通过在cpu上运行一段循环来延迟,如果要延迟较长的时间,则需要将当前进程置入睡眠来延迟。

#include <linux/delay.h>
ndelay(10); //延迟10ns
udelay(20); //延迟20us
mdelay(30); //延迟30ms

内核用for循环实现上述3个函数。其中udelay用的最多,一般用于寄存器间设置的间隔。延迟时间和for循环次数的转换是一个经验值。内核在开机时会运行1s(或1个tick)的循环,然后记录循环的次数。udealy等的时间就通过该次数进行转换。这个次数记录在/proc/cpuinfo文件的变量bogomips中。

(2)基于等待队列的延迟(基于睡眠)

A、进程状态和运行队列

进程的核心结构体为“linux/sched.h”中的task_struct,区分进程的3个核心状态:
TASK_RUNNING
TASK_INTERRUPTIBLE
TASK_UNINTERRUPTIBLE

处于TASK_RUNNING状态的进程会组织在一个运行队列中,2.6.23内核以后,通过CFS调度器(Completely Fair Scheduler, 完全公平调度器)来调度,运行队列中的进程用rb_tree组织在一起。

B、直接睡眠延时(基于等待队列)

set_current_state(state);
state可以用TASK_INTERRUPT或TASK_UNINTERRUPTIBLE
schedule_timeout(delaytime);
delaytime以tick为单位,例如睡眠3s,则dalaytime可设置为3*HZ。

C、等待队列延时

每个要等待的条件都可以分配对应的等待队列,每个队列有一个等待队列头(wait_queue_head_t),等待队列定义在“linux/wait.h”头文件中。

#include <linux/wait.h>

//声明等待队列头
wait_queue_head_t mywait;

//队列头使用前要初始化
init_waitqueue_head(&mywait);

//在char驱动的write函数中,如果缓冲区满则睡眠
ssize_t my_write(...)
{
    int ret;

    if (dev->wp == dev->buf_size) {
        //wait_event(mywait, dev->wp < dev->buf_size);
        ret = wait_event_interruptible(mywait, dev-wp < dev->buf_size);
        if (ret != 0)
            return -ERESTARTSYS;
    }

    //如果缓冲区非满,则可写
    ...
}

//在ioctl函数中,可以让缓冲区复位,此时可以唤醒等待队列中的睡眠进程
int my_ioctl(xxx)
{
    //如果复位缓冲区,则唤醒整个队列
    memset(xxx);
    dev->wp = 0;
    //wake_up(&mywait);
    wake_up_interruptible(&mywait);
    ...
}

(3)定时器

定时器的特征:
A、启动定时器的人和执行定时器的人不一样
一般启动定时器的常常是某个进程或者中断,而内核中负责执行定时器的是硬件定时器的中断处理函数

B、定时器的执行时间一定是未来
内核利用jiffies来确定定时器的执行时间

C、定时器对应的函数只执行一次
一般谁准备定时器,谁提供执行函数;执行函数的人是内核的硬件定时器中断。如果希望实现一个循环的定时器,则需要在执行函数中自行将定时器再重新启动定时器。

D、定时器的函数在中断上下文(context)执行
因此,执行函数中不能睡眠

定时器的核心结构体定义在头文件“linux/timer.h”中,为timer_list。timer_list由启动者准备,当启动定时器后,timer_list会形成一个链表,由内核的硬件定时器中断来检查链表,看有没有定时器到时。

#include <linux/timer.h>

//声明定时器
struct timer_list mytimer;

//定时器的执行函数
//当定时器到期后,由硬件定时器中断执行一次
static void 
my_timer_func(unsigned long data)
{
    ...//不可睡眠
}

//初始化定时器
setup_timer(&mytimer, my_timer_func, data);
初始化定时器时传入的参数为timer_list的指针;执行函数;传给执行函数的参数

//启动定时器
mod_timer(&mytimer, jiffies+HZ);
//定时器一旦启动,就会加入一个timer_list的链表,一旦到时,就会被执行。
//启动定时器的人和执行的人不是一个。即使启动者退出,定时器仍然执行。

//删除定时器
del_timer(&mytimer);
如果模块要rmmod,在卸载之前,必须删除所有没执行的定时器。