LINUX中时间相关的概念与操作

来源:互联网 发布:windows msu x64 编辑:程序博客网 时间:2024/06/16 08:50

    一切东西离开了时间便失去了其存在的意义,内核也不例外.下面是经常在内核驱动中经常谈及到的时间的概念和具体的操作应用.


1.HZ:

    HZ在内核中表征时钟中断间隔.与具体体系结构相关.可以理解为"秒".经常用它来派生更短的延时,如毫秒.如下:

   

#define TIMEOUT_WIEGAND         HZ/100
    表示10毫秒.

    它也常和jiffies配合使用来实现延后:

#define TIMEOUT_WIEGAND         (HZ/100)jiffies + TIMEOUT_WIEGAND
    上述代码表示延后10毫秒.


2.jiffies:

    每当时钟中断发生时,内核内部计数器的值就增加一,这个计数值就是jiffies.系统引导过程中,这个值初始化为0.它通常配合HZ使用.如下:

#include <linux/jiffies.h>unsigned long j,stamp_1,stamp_half,stamp_n;j = jiffies;    //读取当前值stamp_1 = j + HZ;    //未来1秒stamp_half = j + HZ/2;  //未来半秒stamp_n = j + n * HZ /1000;  //未来n毫秒
   

3.内核获取当前时间:

    我们在驱动中也许会用到获取当前时间.内核提供了此功能实现的API:

#include <linux/time.h>void do_gettimeofday(struct timeval *tv);
    其中,struct timeval有两个成员,一个表征秒,另一个表征微秒.如下:

struct timespec{    __kernel_time_t tv_sec;    __kernel_suseconds_t tv_usec;}
    另外,当前时间也可以通过直接获取全局可见变量xtime,只不过精度稍微差了点而已.如下:

    wall_to_monotonic.tv_sec += xtime.tv_sec - tv->tv_sec;    wall_to_monotonic.tv_nsec += xtime.tv_nsec - tv->tv_nsec;

 

4.延时:

    实际的驱动编程中,我们经常会将某些特定的代码延迟一段时间后执行--通常是为了让硬件能完成某些任务,比如初始化设备、等待数据.

 

    4-1.长延迟:

    长延心内核推荐使用两种方式:一是等待队列;二是显式让出CPU一段时间内再回来执行原来的代码.

    4-1-1:借助等待队列:

#include <linux/wait.h>#define wait_event_timeout(wq, condition, timeout)#define wait_event_interruptible_timeout(wq, condition, timeout)

    这两个函数会在给定的等待队列上休眠,并且会在超时timeout后返回.如下:

wait_event_timeout(mdc800->irq_wait, mdc800->irq_woken, msec*HZ/1000);

    4-1-2:schedule_timeout():

#include <linux/sched.h>signed long __sched schedule_timeout(signed long timeout)

    timeout之后,该进程会重新得到被CPU继续执行的机会.在实际使用过程中,这还需要手动设置该进程的进程状态.如下:

set_current_state(TASK_INTERRUPTIBLE);schedule_timeout(delay);

    如果要实现不可中断,可使用TASK_UNINTERRUPTIBLE标志.如下:

set_current_state(TASK_UNINTERRUPTIBLE);schedule_timeout(ABIT_UGURU_RETRY_DELAY);

    4-1-3.扩展:

    在长延迟的机制里面,等待队列其实内部的核心功能是借助schedule_timeout()来实现.具体查看内核wait_event_timeout(wq, condition, timeout)和wait_event_interruptible_timeout(wq, condition, timeout)源码.这里有这样的一个疑问,上述两种长延迟机制,均会使当前进程让出CPU,并标志自己状态为不可运行状态.如果没有存在第三方机制,那么又是怎么使当前进程复活得以继续执行呢?答案就是定时器!见schedule_timeout()源码:

/** * schedule_timeout - sleep until timeout * @timeout: timeout value in jiffies * * Make the current task sleep until @timeout jiffies have * elapsed. The routine will return immediately unless * the current task state has been set (see set_current_state()). * * You can set the task state as follows - * * %TASK_UNINTERRUPTIBLE - at least @timeout jiffies are guaranteed to * pass before the routine returns. The routine will return 0 * * %TASK_INTERRUPTIBLE - the routine may return early if a signal is * delivered to the current task. In this case the remaining time * in jiffies will be returned, or 0 if the timer expired in time * * The current task state is guaranteed to be TASK_RUNNING when this * routine returns. * * Specifying a @timeout value of %MAX_SCHEDULE_TIMEOUT will schedule * the CPU away without a bound on the timeout. In this case the return * value will be %MAX_SCHEDULE_TIMEOUT. * * In all cases the return value is guaranteed to be non-negative. */signed long __sched schedule_timeout(signed long timeout){struct timer_list timer;unsigned long expire;switch (timeout){case MAX_SCHEDULE_TIMEOUT:/* * These two special cases are useful to be comfortable * in the caller. Nothing more. We could take * MAX_SCHEDULE_TIMEOUT from one of the negative value * but I' d like to return a valid offset (>=0) to allow * the caller to do everything it want with the retval. */schedule();goto out;default:/* * Another bit of PARANOID. Note that the retval will be * 0 since no piece of kernel is supposed to do a check * for a negative retval of schedule_timeout() (since it * should never happens anyway). You just have the printk() * that will tell you if something is gone wrong and where. */if (timeout < 0) {printk(KERN_ERR "schedule_timeout: wrong timeout ""value %lx\n", timeout);dump_stack();current->state = TASK_RUNNING;goto out;}}expire = timeout + jiffies;setup_timer_on_stack(&timer, process_timeout, (unsigned long)current);__mod_timer(&timer, expire, false, TIMER_NOT_PINNED);schedule();del_singleshot_timer_sync(&timer);/* Remove the timer from the object tracker */destroy_timer_on_stack(&timer);timeout = expire - jiffies; out:return timeout < 0 ? 0 : timeout;}

    引起CPU重新选出一个合理的进程执行的核心函数是schedule().很显然,这里用了定时器使当前不可执行的进程得到重新执行的机会.

   

    4-2.短延迟:

    为实现比较短暂的延时,内核提供了三组API:

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

    这三个API和平台相关.实际使用过程中注意下面两点:

1).这三个延迟函数其实是忙等待,延迟过程中无法运行其他任务(根据笔者工作经验,中断还是会影响的,如果追求实时性比较强的情况下,短延时的情况下还需要禁止本地中断);2).延迟上千微秒时,使用udelay而不使用ndelay;延迟上千纳秒时,使用mdelay而不使用ndelay.

     对于毫秒级以上的延迟,内核提供了API去完成非忙等待:  

void msleep(unsigned int millisecs);unsigned long msleep_interruptible(unsigned int millisecs);void ssleep(unsigned int seconds);

 

5.内核定时器:

    内核定时器存在的意义,是为了帮助解决未来某时间点执行调度某个动作.而且在这时间点到来之间不会引起当前进程的阻塞.借助定时器,对于一些无法产生中断的硬件进行轮询.

    5-1.内核定时器的特点:

    在实际使用过程中,内核定时器应该明确下面三点:

1).内核定时器是异步执行的;2).它运行于原子上下文,即需要考虑非进程上下文的一些限制,如不能访问用户空间、不能访问current指针、不能休眠或重新调度;3).内核定时器运行过程中也是可以被硬件中断打断的,因此,如果定时器和外部硬件中断有共享资源的,一定要考虑其并发性.

    5-2.内核定时器的实质:

    内核定时器形式上是纯软件,实质最终还是依赖于硬件定时器来实现--内核在时钟中断发生后会检测各定时器是否到期,到期的话就去执行相应的定时器处理函数.而内核对硬件定时器中断的处理机制是软中断,并且是在下半部去执行的.见内核定时器初始函数源码:

void __init init_timers(void){int err = timer_cpu_notify(&timers_nb, (unsigned long)CPU_UP_PREPARE,(void *)(long)smp_processor_id());init_timer_stats();BUG_ON(err == NOTIFY_BAD);register_cpu_notifier(&timers_nb);open_softirq(TIMER_SOFTIRQ, run_timer_softirq);}

    其中TIMER_SOFTIRQ是软中断的标志.如下:

enum{HI_SOFTIRQ=0,TIMER_SOFTIRQ,    //内核定时器NET_TX_SOFTIRQ,NET_RX_SOFTIRQ,BLOCK_SOFTIRQ,BLOCK_IOPOLL_SOFTIRQ,TASKLET_SOFTIRQ,  //TaskletSCHED_SOFTIRQ,HRTIMER_SOFTIRQ,RCU_SOFTIRQ,/* Preferable RCU should always be the last softirq */NR_SOFTIRQS};

    以S3C2410为例:

static void __init s3c2410_timer_init(void){s3c2410_timer_resources();s3c2410_timer_setup();setup_irq(IRQ_TIMER4, &s3c2410_timer_irq);}

-->

static struct irqaction s3c2410_timer_irq = {.name= "S3C2410 Timer Tick",.flags= IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL,.handler= s3c2410_timer_interrupt,};

-->

static irqreturn_ts3c2410_timer_interrupt(int irq, void *dev_id){timer_tick();return IRQ_HANDLED;}

-->

void timer_tick(void){profile_tick(CPU_PROFILING);do_leds();do_set_rtc();write_seqlock(&xtime_lock);do_timer(1);write_sequnlock(&xtime_lock);#ifndef CONFIG_SMPupdate_process_times(user_mode(get_irq_regs()));#endif}

-->

void update_process_times(int user_tick){struct task_struct *p = current;int cpu = smp_processor_id();/* Note: this timer irq context must be accounted for as well. */account_process_tick(p, user_tick);run_local_timers();rcu_check_callbacks(cpu, user_tick);printk_tick();scheduler_tick();run_posix_cpu_timers(p);}

-->

void run_local_timers(void){hrtimer_run_queues();raise_softirq(TIMER_SOFTIRQ);softlockup_tick();}

    见最后的函数raise_softirq(TIMER_SOFTIRQ)

    5-3.内核定时器API:

    内核中经常用某一结构表征一种设备抽象.内核定时器用下面的结构体表征:

   

#include <linux/timer.h>struct timer_list{    unsigned long expires;    void (*function)(unsigned long);    unsigned long data;}
    这里需要注意的是data是函数function的参数,如果data是指向一个多项数据传递的时候,可以将数据打包成数据结构,然后将该数据结构的指针强制转化换成unsigned long传入.在实际用到此数据结构的时候再强制转换回来.


    定义一个内核定时器:

struct timer_list my_timer;
    初始化内核定时器:

void init_timer(struct timer_list *timer);
    我们还可以通过函数setup_timer()来初始化内核定时器:   
#define setup_timer(timer,fn,data)
    这是一体化的函数,它把init_timer()函数额外手动做的事情帮我们做了.具体见此宏源码.

    将内核定时器注册进内核动态定时器链表中:

add_timer(struct timer_list *timer);
    移除定时器:

int del_timer(struct timer_list * timer);
    内核定时器有一个特性就是“一次性执行”.内核定时器到指定的时间点执行完一次之后,它是不会再执行的.如果需要继续执行的话,需要在指定的时间点更新内核定时器.这个功能可以通过下面这个函数实现:
mod_timer(struct timer_list *timer, unsigned long expires);


    5-4.实例:

    下面代码来自宋宝华<<LINUX设备开发详解第二版>>:

    UserSpace:

/*======================================================================    A test program to access /dev/second    This example is to help understand kernel timer         The initial developer of the original code is Baohua Song    <author@linuxdriver.cn>. All Rights Reserved.======================================================================*/#include <sys/types.h>#include <sys/stat.h>#include <stdio.h>#include <fcntl.h>#include <unistd.h>#include <sys/time.h>main(){  int fd;  int counter = 0;  int old_counter = 0;    fd = open("/dev/second", O_RDONLY);  if (fd !=  - 1)  {    while (1)    {      read(fd,&counter, sizeof(unsigned int));      if(counter!=old_counter)      {      printf("seconds after open /dev/second :%d\n",counter);      old_counter = counter;      }    }      }  else  {    printf("Device open failure\n");  }}


    KernelSpace:

/*======================================================================    A "seond" device driver as an example of kernel timer        The initial developer of the original code is Baohua Song    <author@linuxdriver.cn>. All Rights Reserved.======================================================================*/#include <linux/module.h>#include <linux/types.h>#include <linux/fs.h>#include <linux/errno.h>#include <linux/mm.h>#include <linux/sched.h>#include <linux/init.h>#include <linux/cdev.h>#include <asm/io.h>#include <asm/system.h>#include <asm/uaccess.h>#include <linux/timer.h> #include <asm/atomic.h> #define SECOND_MAJOR 252    static int second_major = SECOND_MAJOR;struct second_dev{  struct cdev cdev;   atomic_t counter;  struct timer_list s_timer; };struct second_dev *second_devp; static void second_timer_handle(unsigned long arg){  mod_timer(&second_devp->s_timer,jiffies + HZ);  atomic_inc(&second_devp->counter);    printk(KERN_NOTICE "current jiffies is %ld\n", jiffies);}int second_open(struct inode *inode, struct file *filp){  init_timer(&second_devp->s_timer);  second_devp->s_timer.function = &second_timer_handle;  second_devp->s_timer.expires = jiffies + HZ;    add_timer(&second_devp->s_timer);     atomic_set(&second_devp->counter,0);   return 0;}int second_release(struct inode *inode, struct file *filp){  del_timer(&second_devp->s_timer);    return 0;}static ssize_t second_read(struct file *filp, char __user *buf, size_t count,  loff_t *ppos){    int counter;    counter = atomic_read(&second_devp->counter);  if(put_user(counter, (int*)buf))  return - EFAULT;  else  return sizeof(unsigned int);  }static const struct file_operations second_fops ={  .owner = THIS_MODULE,   .open = second_open,   .release = second_release,  .read = second_read,};static void second_setup_cdev(struct second_dev *dev, int index){  int err, devno = MKDEV(second_major, index);  cdev_init(&dev->cdev, &second_fops);  dev->cdev.owner = THIS_MODULE;  dev->cdev.ops = &second_fops;  err = cdev_add(&dev->cdev, devno, 1);  if (err)    printk(KERN_NOTICE "Error %d adding LED%d", err, index);}int second_init(void){  int ret;  dev_t devno = MKDEV(second_major, 0);  if (second_major)    ret = register_chrdev_region(devno, 1, "second");  else{    ret = alloc_chrdev_region(&devno, 0, 1, "second");    second_major = MAJOR(devno);  }  if (ret < 0)    return ret;  second_devp = kmalloc(sizeof(struct second_dev), GFP_KERNEL);  if (!second_devp)      {    ret =  - ENOMEM;    goto fail_malloc;  }  memset(second_devp, 0, sizeof(struct second_dev));  second_setup_cdev(second_devp, 0);  return 0;  fail_malloc: unregister_chrdev_region(devno, 1);}void second_exit(void){  cdev_del(&second_devp->cdev);     kfree(second_devp);       unregister_chrdev_region(MKDEV(second_major, 0), 1); }MODULE_AUTHOR("Song Baohua");MODULE_LICENSE("Dual BSD/GPL");module_param(second_major, int, S_IRUGO);module_init(second_init);module_exit(second_exit);







    
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          


0 0
原创粉丝点击