10.1 中断与定时器

  中断一般有如下类型:

  • 内部中断和外部中断:内部中断来自CPU,例如软件中断指令、溢出、除0错误等;外部中断有外部设备触发
  • 可屏蔽中断和不可屏蔽中断
  • 向量中断和非向量中断,ARM一般是非向量中断,因为现在的中断源很多,如果做成向量,那中断向量表会很大。不过ARM的总异常还是按照向量的方式组织的。
  1. ARM cortex-A9 中断体系举例:

向量表:发生异常后,CPU直接跳转到响应地址执行。

.section .vectors
_vector_table:
B _boot
B Undefined
B SVCHandler
B PrefetchAbortHandler
B DataAbortHandler
NOP /* Placeholder for address exception vector*/
B IRQHandler    // 发生中断后,进入此处,后面的中断服务程序再去判断发生了哪个中断,属于非向量中断方式
B FIQHandler

  ARM处理器常用的中断控制器GIC如下图。

  有若干中断源:

  SGI:software generated interrupt,

  PPI:private peripheral interrupt,各个core私有的中断,包括定时器等

  SPI:shared peripheral interrupt,共享外设中断。对于SPI类型的中断,默认都是在core0上产生的,可以通过接口设置到背的core。

  1. #include <linux/interrupt.h>
  2.  
  3. extern int irq_set_affinity( unsigned int irq, const struct cpumask *m );
  4.  
  5. irq_set_affinity( irq, cpumask_of(i)); //把中断irq设置到core i上

10.2 linux中断处理程序架构

  中断会打断内核的正常进程调度,所以尽量短小精悍。不过实际系统,中断中要的事又比较多。 为了解决这一矛盾,Linux把中断分为两部分:

  •   顶半部,top half,紧急且量小任务,一般为读取寄存器中的中断状态,并清除中断标记。总之,完成必要的硬件操作。处于中断上下文,可会被打断。
  •   底半部,bottom half,完成主要任务,非中断上下文,可以被打断。

  

  注:不一定所有的中断都分两部分,如果要干的事很少,完全可以不要底半部。

  linux查看中断统计信息的方法:/proc/interrupts文件

  1. ~$ cat /proc/interrupts
  2. CPU0
  3. : IO-APIC -edge timer
  4. : IO-APIC -edge i8042
  5. : IO-APIC -edge rtc0
  6. : IO-APIC -fasteoi acpi
  7. : IO-APIC -edge i8042
  8. : IO-APIC -edge ata_piix
  9. : IO-APIC -edge ata_piix
  10. : IO-APIC -fasteoi vmwgfx, snd_ens1371
  11. : IO-APIC -fasteoi ehci_hcd:usb1, ioc0
  12. : IO-APIC -fasteoi uhci_hcd:usb2
  13. : IO-APIC -fasteoi ens33
  14. : PCI-MSI -edge PCIe PME, pciehp
  15. : PCI-MSI -edge PCIe PME, pciehp

10.3 linux中断编程

10.3.1和10.3.2对应顶半部如何实现,10.3.3对应底半部如何实现。

10.3.1 申请和释放中断

本质上是把回调函数设置到内核里,以便发生中断是调用;同时,把一些配置,例如触发方式传递给内核,便于初始化中断。

  1. #include <linux/interrupt.h>
  2.  
  3. int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
  4. const char *name, void *dev);
  5.   参数:irq,硬件中断号
         handler,顶半部回调函数,发生中断以后,调用此函数
         devhandler的参数,一般设置为这个设备的设备结构体
         flags,中断处理的属性,可以指定中断的触发方式和处理方式,常用宏定义:
             触发方式:
             #define IRQF_TRIGGER_RISING 0x00000001
             #define IRQF_TRIGGER_FALLING 0x00000002
             #define IRQF_TRIGGER_HIGH 0x00000004
             #define IRQF_TRIGGER_LOW 0x00000008
    处理方式:
            #define IRQF_SHARED 0x00000080
  1. 返回值: 0,成功
           -EINVAL,irq无效或handler为空
           -EBUSY,中断已经被占用且不能共享 
  2. void free_irq(unsigned int, void *);
  3.  
  4. 申请接口的变体,带devm_前缀,内核管理的资源,不用在出错处理和remove接口里显式的释放。

  int devm_request_irq(struct device *dev, unsigned int irq, irq_handler_t handler,
      unsigned long irqflags, const char *devname, void *dev_id);

10.3.2 使能和屏蔽中断

  光注册中断还不行,还要使能,使能以后,发生中断后才能调用对应的回调函数。

  1. #include <linux/interrupt.h>
  2.  
  3. . 单个使能/屏蔽
  4. extern void disable_irq_nosync(unsigned int irq); // 立即返回
  5. extern void disable_irq(unsigned int irq); // 等待中断处理完成后返回
  6. extern void enable_irq(unsigned int irq);
  7. 注意:在顶半部调用disable_irq()会导致死锁,因为
  8. void disable_irq(unsigned int irq)
  9. {
  10. disable irq; // 关了中断
  11. while( irq is not finish ) // 等不到中断结束了,死锁
  12. {
  13. ;
  14. }
  15. }
  16.  
  17. .全局使能屏蔽
  18. #define local_irq_enable() // 全局使能
  19. #define local_irq_disable() // 全局关闭
  20. #define local_irq_save(flags) // 全局关闭,同时保存目前中断状态到flag,flag时unsigned long,不是指针
  21. #define local_irq_restore(flags) // 全局使能,恢复中断状态

10.3.3  底半部机制

  实际上就是在顶半部,利用这些机制调度中断底半部。

  这里的底半部机制与阻塞时用到的等待队列不是一个概念,等待队列用于进程睡眠等待,并在一定的地方唤醒。

!10.3.3.1 tasklet

  tasklet的执行上下文是软中断,通常是顶半部返回的时候执行。 由于利用软中断,所以不能有阻塞操作

  

  1. #include <interrupt.h>
  2.  
  3. #define DECLARE_TASKLET(name, func, data) \ // 定义名字为name的tasklet,回调函数为func,参数为data
  4. struct tasklet_struct name = { NULL, , ATOMIC_INIT(), func, data }
  5.  
  6. void tasklet_schedule(struct tasklet_struct *t); // 调度,在顶半部里调用

使用模板:

  1. #include <linux/interrupt.h>
  2.  
  3. void xxx_do_tasklet( unsigned long );
  4. DECLARE_TASKLET( xxx_tasklet, xxx_do_tasklet, );
  5.  
  6. /* 中断处理底半部 */
  7. void xxx_do_tasklet( unsigned long )
  8. {
  9. ...
  10. }
  11. /* 中断处理顶半部 */
  12. irqreturn_t xxx_interrupt( int irq, void * dev_id )
  13. {
  14. ...
  15. tasklet_schedule( &xxx_tasklet ); // 顶半部调用
  16. ...
      return IRQ_HANDLED
  17. }
  18.  
  19. __init xxx_init( void )
  20. {
  21. ...
  22. result = request_irq( xxx_irq, xxx_interrupt,,"xxx", NULL ); // 注册的是顶半部的执行函数
  23. ...
  24. }
  25.  
  26. __exit xxx_exit( void )
  27. {
  28. ...
  29. free_irq( xxx_irq, xxx_interrupt );
  30. ...
  31. }

!10.3.3.2 工作队列

  使用与tasklet相似,区别是执行上线文为内核线程,可以调度和睡眠。内核维护一个工作队列池。

  1. #include <linux/interrupt.h > // 在次头文件里包含了linux/workqueue.h
  2.  
  3. .定义工作队列
    struct work_struct my_wq;    // 定义工作队列
  4. .把工作队列和处理函数初始化及绑定
    void my_wq_func( struct work_struct * work );  // 定义一个处理函数,注意形参
    INIT_WORK( &my_wq, my_wq_func );          // 初始化工作队列,并绑定处理函数
  5. .调度
    schedule_work( &my_wq );

  使用模板:

  1. #include <linux/interrupt.h>

  2. struct work_struct xxx_wq;
  3. void xxx_do_work( struct work_struct * work );/* 中断处理底半部 */
  4. void xxx_do_work( struct work_struct * work )
  5. {
  6. ...
  7. }
  8.  
  9. /* 中断处理顶半部 */
  10. irqreturn_t xxx_interrupt( int irq, void * dev_id )
  11. {
  12. ...
  13. schedule_work( &xxx_wq ); // 顶半部调用
  14. ...
  15. }
  16.  
  17. __init xxx_init( void )
  18. {
  19. ...
  20. result = request_irq( xxx_irq, xxx_interrupt,,"xxx", NULL ); // 注册的是顶半部的执行函数
  21. ...
      INIT_WORK( &xxx_wq, xxx_do_work );
  22. return IRQ_HANDLED;
  23. }
  24.  
  25. __exit xxx_exit( void )
  26. {
  27. ...
  28. free_irq( xxx_irq, xxx_interrupt );
  29. ...
  30. }

10.3.3.3 软中断

  •   tasklet就是基于软中断的,一般来说,驱动编写者不宜直接使用软中断,linux封装了例如tasklet这类友好的接口,还有其他场合是用软中断实现的,见后面描述。
  • 优先级: 中断>软中断>线程,当某个时间段内软中断过多时,操作系统会将后面的软中断放入ksoftirq内核线程中执行,软中断适度线程化有利于缓解高负荷情况下的系统响应。
  1. #include <linux/interrupt.h>
  2.  
  3. struct softirq_action
  4. {
  5. void (*action)(struct softirq_action *);
  6. };
  7.  
  8. extern void open_softirq(int nr, void (*action)(struct softirq_action *));    // 注册中断函数
  9. extern void raise_softirq(unsigned int nr);                      // 触发软中断
  10.  
  11. static inline void local_bh_disable(void);                       // 关闭底半部
  12. extern void local_bh_enable(void);                            // 使能底半部
  13.  
  14. /* PLEASE, avoid to allocate new softirqs, if you need not _really_ high
  15. frequency threaded job scheduling. For almost all the purposes
  16. tasklets are more than enough. F.e. all serial device BHs et
  17. al. should be converted to tasklets, not to softirqs.
  18. */
  19.  
  20. enum
  21. {
  22. HI_SOFTIRQ=,
  23. TIMER_SOFTIRQ,
  24. NET_TX_SOFTIRQ,
  25. NET_RX_SOFTIRQ,
  26. BLOCK_SOFTIRQ,
  27. BLOCK_IOPOLL_SOFTIRQ,
  28. TASKLET_SOFTIRQ,
  29. SCHED_SOFTIRQ,
  30. HRTIMER_SOFTIRQ,
  31. RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */
  32.  
  33. NR_SOFTIRQS
  34. };

10.3.3.4 threaded_irq

  • 同时注册顶半部和底半部
  1. #include <linux/interrupt.h>
  2.  
  3. extern int request_threaded_irq(unsigned int irq, irq_handler_t handler,
  4. irq_handler_t thread_fn,
  5. unsigned long flags, const char *name, void *dev);
  6.  
  7. extern int devm_request_threaded_irq(struct device *dev, unsigned int irq,
  8. irq_handler_t handler, irq_handler_t thread_fn,
  9. unsigned long irqflags, const char *devname,
  10. void *dev_id);
  11.  
  12. enum irqreturn {
  13. IRQ_NONE = ( << ),
  14. IRQ_HANDLED = ( << ),
  15. IRQ_WAKE_THREAD = ( << ),
  16. };
  1. !!! 如果顶半部返回IRQ_WAKE_THREAD,则顶半部执行结束后,内核会调度对应线程执行thread_fn对应的函数
    如果参数handlerNULL,则系统使用如下默认顶半部处理函数
  2.  
  3. /*
    * Default primary interrupt handler for threaded interrupts. Is
    * assigned as primary handler when request_threaded_irq is called
    * with handler == NULL. Useful for oneshot interrupts. */
    static irqreturn_t irq_default_primary_handler(int irq, void *dev_id)
    {
      return IRQ_WAKE_THREAD;
    }
  1. !!!flag使用IRQ_ONESHOT标记,内核会在中断上下文屏蔽对应中断号,在内核调度thread_fn执行后,重新使能该中断号。避免中断程序一退出,马上又进入中断的情况。
    #define IRQF_ONESHOT 0x00002000

10.3.4 实例:GPIO按键的中断

10.4 linux中断共享

  • 共享中断申请时,都要使用IRQF_SHARED标志,申请时使用IRQF_SHARED,成功的前提是该中断没有被申请或者之前的申请全部为IRQF_SHARED;
  • 最后一个参数dev_id,最好传入设备结构体;
  • 处理流程如下,顶半部迅速判断是不是自己要处理的中断,如果不是,赶紧返回IRQF_NONE;如果是自己的,返回IRQ_HANDLE.
  • 发生中断后,会遍历已注册到此共享中断的所有处理函数,直到有人返回IRQ_HANDLE.

使用模板:

  1. #include <linux/interrupt.h>
  2.  
  3. irqreturn_t xxx_interrupt( int irq, void * dev_id )
    {
      ...
      int status = read_int_status();  
      if( !is_myint( dev_id, status) )    // 判断是不是自己需要处理的中断源
        return IRQ_NONE;
  4.  
  5.   ...
      return IRQ_HANDLE;    
    }
  6.  
  7. int xxx_init( void )
    {
      ...
      result = request( sh_irq, xxx_interrupt, IRQF_SHARED, "xxx", xxx_dev );  
      ...
    }
  8.  
  9. int xxx_exit( void )
    {
      ...
      free( sh_irq, xxx_interrupt );
    }

10.5 内核定时器

10.5.1 内核定时器编程

  • 利用软中断机制实现底半部,顶半部为时钟中断,软中断为TIMER_SOFTIRQ;
  • linux提供了一套进一步操作定时器的接口,一般不用关心底层行为。

10.5.1.1 普通定时器编程

  1. #include <linux/timer.h>
  2.  
  3. /* 1.定义timer_list结构体*/
  4. struct timer_list {
  5. /*
  6. * All fields that change during normal runtime grouped to the
  7. * same cacheline
  8. */
  9. struct list_head entry;
  10. unsigned long expires; // !定时器到期时间,单位jiffies
  11. struct tvec_base *base;
  12.  
  13. void (*function)(unsigned long); // !定时器期满后执行此函数
  14. unsigned long data; // !传入function的参数
  15.  
  16. int slack;
  17.  
  18. #ifdef CONFIG_TIMER_STATS
  19. int start_pid;
  20. void *start_site;
  21. char start_comm[];
  22. #endif
  23. #ifdef CONFIG_LOCKDEP
  24. struct lockdep_map lockdep_map;
  25. #endif
  26. };
  27.  
  28. struct timer_list my_timer;
  29.  
  30. /* 2.初始化定时器 */
  31. #define init_timer(timer) __init_timer((timer), 0)
  32. // 对timer进行初始化,对entry和base赋值
  33.  
  34. /* 3.给timer_list关键成员赋值 */
  35. #define TIMER_INITIALIZER(_function, _expires, _data) \
  36. __TIMER_INITIALIZER((_function), (_expires), (_data), )
  37.  
  38. #define __TIMER_INITIALIZER(_function, _expires, _data, _flags) { \
  39. .entry = { .prev = TIMER_ENTRY_STATIC }, \
  40. .function = (_function), \
  41. .expires = (_expires), \
  42. .data = (_data), \
  43. .base = (void *)((unsigned long)&boot_tvec_bases + (_flags)), \
  44. .slack = -, \
  45. __TIMER_LOCKDEP_MAP_INITIALIZER( \
  46. __FILE__ ":" __stringify(__LINE__)) \
  47. }
  48.  
  49. /*
  50. * "1+3"的快捷方式,定义+赋关键值
  51. */
  52. #define DEFINE_TIMER(_name, _function, _expires, _data) \
  53. struct timer_list _name = \
  54. TIMER_INITIALIZER(_function, _expires, _data)
  55.  
  56. /* 4.增加定时器,注册到内核里,定时器就开始工作了 */
  57. extern void add_timer(struct timer_list *timer);
  58.  
  59. /* 5.删除定时器 */
  60. extern int del_timer(struct timer_list * timer);
  61. extern int del_timer_sync(struct timer_list *timer); // 同步版,等待定时器结束,不能在中断上下文
  62.  
  63. /* 6.修改定时器 */
  64. extern int mod_timer(struct timer_list *timer, unsigned long expires);
  65. // 原来的expires无效了,改为这个新的值

使用模板:

  1. #include <linux/interrupt.h>
  2.  
  3. struct xxx_dev{
  4.   struct cdev cdev;
      ...
      struct timer_list xxx_timer;  // 定义定时器
    };
  5.  
  6. xxx_func1(...)
    {
      struct xxx_dev *dev=filp->pricate_data;
      ...
      init_timer(&dev->xxx_timer);
      dev->xxx_timer.function = & xxx_do_timer;
      dev->xxx_timer.data =(unsigned long)dev;
      dev->xxx_timer.expires = jiffies + delay;
      add_timer(&dev->xxx_timer);
      ...
    }
  7.  
  8. xxx_func2(...)
    {
      ...
      del_timer(&dev->timer);
      ...
    }
  9.  
  10. static void xxx_do_timer( unsigned long arg )
    {
      struct xxx_device *dev=(struct xxx_device*)(arg);
      ...
      dev->xxx_timer.expires = jiffies + delay;
      add_timer(&dev->xxx_timer);          // 需要再次填入,以便下一次触发
      ...    
    }

10.5.1.2 高精度定时器hrtimer编程

  hrtimer,high resolution kernel timers,可支持到us级别的精度。

  1. #include <linux/hrtimer.h>
  2.  
  3. /*
  4. * Mode arguments of xxx_hrtimer functions:
  5. */
  6. enum hrtimer_mode {
  7. HRTIMER_MODE_ABS = 0x0, /* Time value is absolute */
  8. HRTIMER_MODE_REL = 0x1, /* Time value is relative to now */
  9. HRTIMER_MODE_PINNED = 0x02, /* Timer is bound to CPU */
  10. HRTIMER_MODE_ABS_PINNED = 0x02,
  11. HRTIMER_MODE_REL_PINNED = 0x03,
  12. };
  13.  
  14. /*
  15. * Return values for the callback function
  16. */
  17. enum hrtimer_restart {
  18. HRTIMER_NORESTART, /* Timer is not restarted */
  19. HRTIMER_RESTART, /* Timer must be restarted */
  20. };
  21.  
  22. union ktime {
  23. s64 tv64;
  24. #if BITS_PER_LONG != 64 && !defined(CONFIG_KTIME_SCALAR)
  25. struct {
  26. # ifdef __BIG_ENDIAN
  27. s32 sec, nsec;
  28. # else
  29. s32 nsec, sec;
  30. # endif
  31. } tv;
  32. #endif
  33. };
  34.  
  35. /**
  36. * struct hrtimer - the basic hrtimer structure
  37. * @node: timerqueue node, which also manages node.expires,
  38. * the absolute expiry time in the hrtimers internal
  39. * representation. The time is related to the clock on
  40. * which the timer is based. Is setup by adding
  41. * slack to the _softexpires value. For non range timers
  42. * identical to _softexpires.
  43. * @_softexpires: the absolute earliest expiry time of the hrtimer.
  44. * The time which was given as expiry time when the timer
  45. * was armed.
  46. * @function: timer expiry callback function
  47. * @base: pointer to the timer base (per cpu and per clock)
  48. * @state: state information (See bit values above)
  49. * @start_site: timer statistics field to store the site where the timer
  50. * was started
  51. * @start_comm: timer statistics field to store the name of the process which
  52. * started the timer
  53. * @start_pid: timer statistics field to store the pid of the task which
  54. * started the timer
  55. *
  56. * The hrtimer structure must be initialized by hrtimer_init()
  57. */
  58. struct hrtimer {
  59. struct timerqueue_node node;
  60. ktime_t _softexpires;
  61. enum hrtimer_restart (*function)(struct hrtimer *);
  62. struct hrtimer_clock_base *base;
  63. unsigned long state;
  64. #ifdef CONFIG_TIMER_STATS
  65. int start_pid;
  66. void *start_site;
  67. char start_comm[];
  68. #endif
  69. };
  70.  
  71. static inline void hrtimer_set_expires(struct hrtimer *timer, ktime_t time)
  72. {
  73. timer->node.expires = time;
  74. timer->_softexpires = time;
  75. }
  76.  
  77. static inline int hrtimer_start_expires(struct hrtimer *timer,
  78. enum hrtimer_mode mode)
  79. {
  80. unsigned long delta;
  81. ktime_t soft, hard;
  82. soft = hrtimer_get_softexpires(timer);
  83. hard = hrtimer_get_expires(timer);
  84. delta = ktime_to_ns(ktime_sub(hard, soft));
  85. return hrtimer_start_range_ns(timer, soft, delta, mode);
  86. }
  87.  
  88. /* Forward a hrtimer so it expires after the hrtimer's current now */
  89. static inline u64 hrtimer_forward_now(struct hrtimer *timer,
  90. ktime_t interval)
  91. {
  92. return hrtimer_forward(timer, timer->base->get_time(), interval);
  93. }
  94.  
  95. static inline int hrtimer_restart(struct hrtimer *timer)
  96. {
  97. return hrtimer_start_expires(timer, HRTIMER_MODE_ABS);
  98. }

/* Initialize timers: */
extern void hrtimer_init(struct hrtimer *timer, clockid_t which_clock,
enum hrtimer_mode mode);

// 参数which_clock可以是CLOCK_REALTIME、CLOCK_MONOTONIC、CLOCK_BOOTTIME中的一种,涉及到linux的时间,见10.5.1.4节描述

extern int hrtimer_cancel(struct hrtimer *timer);

使用模板,sound/soc/fsl/imx-pcm-fiq.c:

  1. static enum hrtimer_restart snd_hrtimer_callback(struct hrtimer *hrt)
    {
      ...
      hrtimer_forward_now(hrt, ns_to_ktime(iprtd->poll_time_ns));    // 继续装载,以便下一次触发
      return HRTIMER_RESTART;
    }
  2.  
  3. static int snd_imx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
    {
      struct snd_pcm_runtime *runtime = substream->runtime;
      struct imx_pcm_runtime_data *iprtd = runtime->private_data;
  4.  
  5.   switch (cmd) {
      case SNDRV_PCM_TRIGGER_START:
      case SNDRV_PCM_TRIGGER_RESUME:
      case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
        ...
        hrtimer_start(&iprtd->hrt, ns_to_ktime(iprtd->poll_time_ns),HRTIMER_MODE_REL);     // 启动,相对时间模式
        ...
  1. }
  2.  
  3. static int snd_imx_open(struct snd_pcm_substream *substream)
    {
      ...
      hrtimer_init(&iprtd->hrt, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
      iprtd->hrt.function = snd_hrtimer_callback;
      ...
      return 0;
    }
  4.  
  5. static int snd_imx_close(struct snd_pcm_substream *substream)
    {
      ...
      hrtimer_cancel(&iprtd->hrt);
      ...
    }

10.5.1.3 jiffies说明

  jiffies是系统的节拍,内核1个jiffy进入一次中断。定时器的延时单位为jiffies,Hz的值(ZYNQ LINUX配置为1000)表示 jiffies / s, 即1秒钟有1000个jiffies,1ms对应1个jiffy。 不同系统Hz值可能不通,即节拍不同。

常用函数和宏:

  1. #include <linux/jiffies.h>
  2.  
  3. get_jiffies_64():获取64bit值,考虑了原子操作
  4. extern unsigned long msecs_to_jiffies(const unsigned int m);
  5. extern unsigned long usecs_to_jiffies(const unsigned int u);

/*
* These inlines deal with timer wrapping correctly. You are
* strongly encouraged to use them
* 1. Because people otherwise forget
* 2. Because if the timer wrap changes in future you won't have to
* alter your driver code.
*
* time_after(a,b) returns true if the time a is after time b.
*
* Do this with "<0" and ">=0" to only test the sign of the result. A
* good compiler would generate better code (and a really good compiler
* wouldn't care). Gcc is currently neither.
*/
#define time_after(a,b) \
(typecheck(unsigned long, a) && \
typecheck(unsigned long, b) && \
((long)((b) - (a)) < 0))
#define time_before(a,b) time_after(b,a)

#define time_after_eq(a,b) \
(typecheck(unsigned long, a) && \
typecheck(unsigned long, b) && \
((long)((a) - (b)) >= 0))
#define time_before_eq(a,b) time_after_eq(b,a)

10.5.1.4 linux时间说明

内核管理着多种时间,它们分别是:

  • RTC时间
  • wall time:墙上时间
  • monotonic time
  • raw monotonic time
  • boot time:总启动时间

RTC时间  在PC中,RTC时间又叫CMOS时间,它通常由一个专门的计时硬件来实现,软件可以读取该硬件来获得年月日、时分秒等时间信息,而在嵌入式系统中,有使用专门的RTC芯片,也有直接把RTC集成到Soc芯片中,读取Soc中的某个寄存器即可获取当前时间信息。一般来说,RTC是一种可持续计时的,也就是说,不管系统是否上电,RTC中的时间信息都不会丢失,计时会一直持续进行,硬件上通常使用一个后备电池对RTC硬件进行单独的供电。因为RTC硬件的多样性,开发者需要为每种RTC时钟硬件提供相应的驱动程序,内核和用户空间通过驱动程序访问RTC硬件来获取或设置时间信息。

xtime  xtime和RTC时间一样,都是人们日常所使用的墙上时间,只是RTC时间的精度通常比较低,大多数情况下只能达到毫秒级别的精度,如果是使用外部的RTC芯片,访问速度也比较慢,为此,内核维护了另外一个wall time时间:xtime,取决于用于对xtime计时的clocksource,它的精度甚至可以达到纳秒级别,因为xtime实际上是一个内存中的变量,它的访问速度非常快,内核大部分时间都是使用xtime来获得当前时间信息。xtime记录的是自1970年1月1日24时到当前时刻所经历的纳秒数。

monotonic time  该时间自系统开机后就一直单调地增加,它不像xtime可以因用户的调整时间而产生跳变,不过该时间不计算系统休眠的时间,也就是说,系统休眠时,monotoic时间不会递增。

raw monotonic time  该时间与monotonic时间类似,也是单调递增的时间,唯一的不同是:raw monotonic time“更纯净”,他不会受到NTP时间调整的影响,它代表着系统独立时钟硬件对时间的统计。

boot time  与monotonic时间相同,不过会累加上系统休眠的时间,它代表着系统上电后的总时间。

时间种类 精度(统计单位) 访问速度 累计休眠时间 受NTP调整的影响
RTC Yes Yes
xtime Yes Yes
monotonic No Yes
raw monotonic No No
boot time Yes Yes

10.5.2 内核中延迟的工作delayed_work

  linux提供了一套用 “定时器+工作队列”封装好的机制,可以实现周期调度。

  感觉不怎么常用,不看了。

10.5.3 实例: 秒字符设备

  

  1. #include <linux/module.h>
  2. #include <linux/cdev.h>
  3. #include <linux/fs.h>
  4. #include <linux/device.h>
  5. #include <asm/uaccess.h>
  6. #include <linux/wait.h>
  7. #include <linux/mutex.h>
  8. #include <linux/timer.h>
  9.  
  10. #define DEV_NAME "second"
  11.  
  12. struct second_dev_t
  13. {
  14. struct cdev cdev;
  15. struct class * class;
  16. dev_t dev_no;
  17. struct timer_list timer;
  18. }second_dev;
  19.  
  20. static void sec_timer_isr( unsigned long arg)
  21. {
  22. struct second_dev_t * second_devp = (struct second_dev_t *)arg;
  23.  
  24. printk("\r\n1.jiffies = %d.",jiffies);
  25. second_devp->timer.expires = jiffies + HZ;
  26. add_timer(&second_devp->timer);
  27.  
  28. printk("\r\n2.jiffies = %d.",jiffies);
  29. }
  30.  
  31. int second_open(struct inode * inode, struct file * filp)
  32. {
  33. filp->private_data = &second_dev;
  34.  
  35. printk("\r\nsecond open.");
  36. printk("\r\nsecond open.");
  37.  
  38. init_timer( &second_dev.timer );
  39. second_dev.timer.function = &sec_timer_isr;
  40. second_dev.timer.data = (unsigned long)(&second_dev);
  41. second_dev.timer.expires = jiffies + HZ;
  42. add_timer(&second_dev.timer);
  43.  
  44. return ;
  45. }
  46.  
  47. ssize_t second_read(struct file *filp, char __user * buf, size_t len, loff_t * pos)
  48. {
  49. struct second_dev_t * second_devp;
  50. int ret;
  51.  
  52. second_devp = filp->private_data;
  53.  
  54. return ret;
  55. }
  56.  
  57. int second_release(struct inode * inode, struct file * filp)
  58. {
  59. struct second_dev_t * second_devp = filp->private_data;
  60.  
  61. del_timer(&second_devp->timer);
  62. return ;
  63. }
  64.  
  65. struct file_operations second_fops = {
  66. .owner = THIS_MODULE,
  67. .open = second_open,
  68. .read = second_read,
  69. .release = second_release,
  70. };
  71.  
  72. static int __init second_init( void )
  73. {
  74. int ret;
  75.  
  76. cdev_init(&second_dev.cdev,&second_fops);
  77. second_dev.cdev.owner=THIS_MODULE;
  78.  
  79. if( (ret=alloc_chrdev_region(&second_dev.dev_no,,,DEV_NAME))< )
  80. {
  81. printk("alloc_chrdev_region err.\r\n");
  82. return ret;
  83. }
  84. ret = cdev_add(&second_dev.cdev,second_dev.dev_no,);
  85. if( ret )
  86. {
  87. printk("cdev_add err.\r\n");
  88. return ret;
  89. }
  90.  
  91. /*
  92. * $ sudo insmod second.ko 如果使用class_create,insmod时会报错,dmesg查看内核log信息发现找不到class相关函数
  93. * insmod: ERROR: could not insert module second.ko: Unknown symbol in module
  94. * $ dmesg
  95. * [ 5495.606920] second: Unknown symbol __class_create (err 0)
  96. * [ 5495.606943] second: Unknown symbol class_destroy (err 0)
  97. * [ 5495.607027] second: Unknown symbol device_create (err 0)
  98. */
  99.  
  100. second_dev.class = class_create( THIS_MODULE, DEV_NAME );
  101. device_create(second_dev.class,NULL,second_dev.dev_no,NULL,DEV_NAME);
  102.  
  103. return ;
  104. }
  105.  
  106. static void __exit second_exit( void )
  107. {
  108. unregister_chrdev_region(second_dev.dev_no, );
  109. cdev_del(&second_dev.cdev);
  110. device_destroy(second_dev.class,second_dev.dev_no);
  111. class_destroy(second_dev.class);
  112. }
  113.  
  114. module_init(second_init);
  115. module_exit(second_exit);
  116.  
  117. MODULE_LICENSE("GPL"); // 不加此声明,会报上述Unknown symbol问题
  1. 应用层测试代码:
    #include <stdio.h> // printf
  2. #include <stdlib.h> // exit
  3. #include <unistd.h>
  4. #include <fcntl.h> // open
  5.  
  6. #define FILE_NAME "/dev/second"
  7.  
  8. int main(int args, char *argv[])
  9. {
  10. int fd;
  11.  
  12. printf("\r\nstart.");
  13.  
  14. // open file
  15. fd=open(FILE_NAME,O_RDONLY|O_NONBLOCK);
  16. if( fd< )
  17. {
  18. printf("\r\nopen file err,fd=%d",fd);
  19. exit(-);
  20. }
  21.  
  22. while();
  23.  
  24. close(fd);
  25. exit();
  26. }

执行结果

  1. sudo ./app
  2.  
  3. [109127.309807]
  4. second open.
  5. jiffies = .
  6. jiffies = .
  7. jiffies = .
  8. jiffies = .
  9. jiffies = .
  10. jiffies = .
  11. jiffies = .
  12. jiffies = .
  13. jiffies = .

10.6 内核延时

10.6.1 短延时

忙等待,占用CPU,一般用于驱动里的短延时

  1. #include <linux/delay.h>
  2.  
  3. void ndelay(unsigned long x);
  4. void udelay(unsigned long x);
  5. void mdelay(unsigned long x);

10.6.2 长延时

可用jiffies实现,也是忙等待,比较准确

  1. /* 延迟 100 个 jiffies */
  2. unsigned long delay = jiffies + ;
  3. while(time_before(jiffies, delay));
  4.  
  5. /* 再延迟 2s */
  6. unsigned long delay = jiffies + *Hz;
  7. while(time_before(jiffies, delay));

10.6.3 睡眠延迟

ms级别以上的延时,一般使用睡眠延迟。精度有限。

  1. #include <linux/delay.h>
  1. void msleep(unsigned int millisecs);
  2. unsigned long msleep_interruptible(unsigned int millisecs);
  3. void ssleep(unsigned int seconds);

10.7 总结

中断分为顶半部和低半部;

底半部的机制包括:tasklet、工作队列、软中断等。tasklet基于软中断;

内核定时器也依赖软中断实现。

内核的延迟分为忙等待和睡眠等待,对精度要求不高的情况下,可用睡眠等待,如果要求高,可用长延时(while+timer_after);短延时一般用于底层驱动与硬件交互。

《linux设备驱动开发详解》笔记——10中断与时钟的更多相关文章

  1. linux设备驱动开发详解 笔记

      在目录的 Makefile 中关于 RTC_DRV_S3C 的编译脚本为: obj -$(CONFIG_RTC_DRV_S3C) += rtc-s3c.o 上述脚本意味着如果 RTC_DRV_S3 ...

  2. Linux设备驱动开发详解

    Linux设备驱动开发详解 http://download.csdn.net/detail/wuyouzi067/9581380

  3. 《Linux设备驱动开发详解(第2版)》配套视频登录51cto教育频道

    http://edu.51cto.com/course/course_id-379-page-1.html http://edu.51cto.com/course/course_id-379-page ...

  4. 《linux设备驱动开发详解》笔记——14 linux网络设备驱动

    14.1 网络设备驱动结构 网络协议接口层:硬件无关,标准收发函数dev_queue_xmit()和netif_rx();  注意,netif_rx是将接收到的数据给上层,有时也在驱动收到数据以后调用 ...

  5. 《linux设备驱动开发详解》笔记——6字符设备驱动

    6.1 字符设备驱动结构 先看看字符设备驱动的架构: 6.1.1 cdev cdev结构体是字符设备的核心数据结构,用于描述一个字符设备,cdev定义如下: #include <linux/cd ...

  6. 《linux设备驱动开发详解》笔记——15 linux i2c驱动

    结合实际代码和书中描述,可能跟书上有一定出入.本文后续芯片相关代码参考ZYNQ. 15.1 总体结构 如下图,i2c驱动分为如下几个重要模块 核心层core,完成i2c总线.设备.驱动模型,对用户提供 ...

  7. 《linux设备驱动开发详解》笔记——18 ARM linux设备树

    18.1 设备树的起源 linux 2.6及之前,大量板级信息被硬编码到内核里,十分庞大,大量冗余代码: linux 2.6之前,引入了设备树: 设备树源于OpenFirmware,描述硬件的数据结构 ...

  8. 《linux设备驱动开发详解》笔记——12linux设备驱动的软件架构思想

    本章重点讲解思想.思想.思想. 12.1 linux驱动的软件架构 下述三种思想,在linux的spi.iic.usb等复杂驱动里广泛使用.后面几节分别对这些思想进行详细说明. 思想1:驱动与设备分离 ...

  9. Linux设备驱动开发详解-Note(11)--- Linux 文件系统与设备文件系统(3)

    Linux 文件系统与设备文件系统(3) 成于坚持,败于止步 sysfs 文件系统与 Linux 设备模型 1.sysfs 文件系统 Linux 2.6 内核引入了 sysfs 文件系统,sysfs ...

  10. Linux设备驱动开发详解-Note(5)---Linux 内核及内核编程(1)

    Linux 内核及内核编程(1) 成于坚持,败于止步 Linux 2.6 内核的特点 Linux 2.6 相对于 Linux 2.4 有相当大的改进,主要体现在如下几个方面. 1.新的调度器 2.6 ...

随机推荐

  1. NetCore上传多文件

    .NetCore上传多文件的几种示例   本章和大家分享的是.NetCore的MVC框架上传文件的示例,主要讲的内容有:form方式提交上传,ajax上传,ajax提交+上传进度效果,Task并行处理 ...

  2. CSS3 - CheakBox 开关效果

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  3. 1137 - Sin your life sin公式 + 枚举

    http://www.ifrog.cc/acm/problem/1137 和差化积公式, 变成2 * sin((x + y) / 2) * cos((x - y) / 2) + sin(n - (x ...

  4. SSIS父子维度

    1.数据仓库结构: 2.区域的AttributeHierarchyVisible设置为False 3.Parent ID的Usage设置为Parent 4.级别命名: 5.结果:

  5. spring和springmvc是单例还是多例

    这么说其实不规范 spring的bean    默认是单例 springmvc的controller    默认是单例 所以最好不要在controller里定义成员变量 都可通过注解 @scope=p ...

  6. RHEL6.4 安装 highpoint RocketRAID 2720 阵列卡驱动

    step1:下载驱动程序. http://www.highpoint-tech.com/USA_new/series_rr272x_configuration.htm step2:上传驱动程序至服务器 ...

  7. webpack.config.js====插件html-webpack-plugin

    1. 安装 cnpm install html-webpack-plugin --save-dev 2. webpack.config.js中使用 const htmlWebpackPlugin = ...

  8. 有关在python中使用Redis(一)

    python作为一种处理数据的脚本语言本身有许多方法函数供大家使用,有时候为了提升数据处理速度(如海量数据的访问或者海量数据的读取),涉及分布式管理架构,可能需要用到Redis,Redis是一个开源的 ...

  9. 我们为什么要看《超实用的HTML代码段》

    不知道自己HTML水平如何,不知道HTML5如何进化?看这张图 如果一半以上的你都不会,必须看这本书,阿里一线工程师用代码和功能页面来告诉你每一个技术点. 都会一点,但不知道如何检验自己,看看本书提供 ...

  10. put_user

    1. put_user Name put_user --    Write a simple value into user space. Synopsis put_user ( x, ptr); A ...