《linux设备驱动开发详解》笔记——10中断与时钟

 

10.1 中断与定时器

  中断一般有如下类型:

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

#include <linux/interrupt.h>

extern int irq_set_affinity( unsigned int irq, const struct cpumask *m );

irq_set_affinity( irq, cpumask_of(i));    //把中断irq设置到core i上

 

10.2 linux中断处理程序架构

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

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

  

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

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

~$ cat /proc/interrupts 
            CPU0       
   0:        131   IO-APIC    2-edge      timer
   1:        268   IO-APIC    1-edge      i8042
   8:          1   IO-APIC    8-edge      rtc0
   9:          0   IO-APIC    9-fasteoi   acpi
  12:      13131   IO-APIC   12-edge      i8042
  14:          0   IO-APIC   14-edge      ata_piix
  15:          0   IO-APIC   15-edge      ata_piix
  16:       4449   IO-APIC   16-fasteoi   vmwgfx, snd_ens1371
  17:      38218   IO-APIC   17-fasteoi   ehci_hcd:usb1, ioc0
  18:        196   IO-APIC   18-fasteoi   uhci_hcd:usb2
  19:      58527   IO-APIC   19-fasteoi   ens33
  24:          0   PCI-MSI 344064-edge      PCIe PME, pciehp
  25:          0   PCI-MSI 346112-edge      PCIe PME, pciehp

 

10.3 linux中断编程

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

10.3.1 申请和释放中断

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

#include <linux/interrupt.h>

int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
	    const char *name, void *dev);
  参数:irq,硬件中断号
     handler,顶半部回调函数,发生中断以后,调用此函数
     dev,handler的参数,一般设置为这个设备的设备结构体
     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
    返回值: 0,成功
       -EINVAL,irq无效或handler为空
       -EBUSY,中断已经被占用且不能共享 
void free_irq(unsigned int, void *);

申请接口的变体,带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 使能和屏蔽中断

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

#include <linux/interrupt.h>

1. 单个使能/屏蔽
extern void disable_irq_nosync(unsigned int irq);    // 立即返回
extern void disable_irq(unsigned int irq);        // 等待中断处理完成后返回
extern void enable_irq(unsigned int irq);    
    注意:在顶半部调用disable_irq()会导致死锁,因为
    void disable_irq(unsigned int irq)
    {
        disable irq;            // 关了中断
        while( irq is not finish )    // 等不到中断结束了,死锁
        {
            ;
        }    
    }

2.全局使能屏蔽
#define local_irq_enable()         // 全局使能
#define local_irq_disable()         // 全局关闭
#define local_irq_save(flags)        // 全局关闭,同时保存目前中断状态到flag,flag时unsigned long,不是指针
#define local_irq_restore(flags)    // 全局使能,恢复中断状态

10.3.3  底半部机制

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

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

!10.3.3.1 tasklet

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

  

#include <interrupt.h>

#define DECLARE_TASKLET(name, func, data) \    // 定义名字为name的tasklet,回调函数为func,参数为data
    struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }

void tasklet_schedule(struct tasklet_struct *t); // 调度,在顶半部里调用

 

使用模板:

#include <linux/interrupt.h>

void xxx_do_tasklet( unsigned long );
DECLARE_TASKLET( xxx_tasklet, xxx_do_tasklet, 0 );

/*  中断处理底半部 */
void xxx_do_tasklet( unsigned long )
{
    ...
}
/* 中断处理顶半部 */ irqreturn_t xxx_interrupt( int irq, void * dev_id ) { ... tasklet_schedule( &xxx_tasklet ); // 顶半部调用 ...
  return IRQ_HANDLED } __init xxx_init(
void ) { ... result = request_irq( xxx_irq, xxx_interrupt,0,"xxx", NULL ); // 注册的是顶半部的执行函数 ... } __exit xxx_exit( void ) { ... free_irq( xxx_irq, xxx_interrupt ); ... }

 

!10.3.3.2 工作队列

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

#include <linux/interrupt.h >    // 在次头文件里包含了linux/workqueue.h

1.定义工作队列
struct work_struct my_wq;    // 定义工作队列
2.把工作队列和处理函数初始化及绑定
void my_wq_func( struct work_struct * work );  // 定义一个处理函数,注意形参
INIT_WORK( &my_wq, my_wq_func );          // 初始化工作队列,并绑定处理函数
3.调度
schedule_work( &my_wq );

 

 

  使用模板:

#include <linux/interrupt.h>

struct work_struct xxx_wq; void xxx_do_work( struct work_struct * work );/* 中断处理底半部 */ void xxx_do_work( struct work_struct * work ) { ... } /* 中断处理顶半部 */ irqreturn_t xxx_interrupt( int irq, void * dev_id ) { ... schedule_work( &xxx_wq ); // 顶半部调用 ... } __init xxx_init( void ) { ... result = request_irq( xxx_irq, xxx_interrupt,0,"xxx", NULL ); // 注册的是顶半部的执行函数 ...
  INIT_WORK( &xxx_wq, xxx_do_work );
return IRQ_HANDLED; } __exit xxx_exit( void ) { ... free_irq( xxx_irq, xxx_interrupt ); ... }

10.3.3.3 软中断

  •   tasklet就是基于软中断的,一般来说,驱动编写者不宜直接使用软中断,linux封装了例如tasklet这类友好的接口,还有其他场合是用软中断实现的,见后面描述。
  •        优先级: 中断>软中断>线程,当某个时间段内软中断过多时,操作系统会将后面的软中断放入ksoftirq内核线程中执行,软中断适度线程化有利于缓解高负荷情况下的系统响应。
#include  <linux/interrupt.h>


struct softirq_action
{
    void    (*action)(struct softirq_action *);
};


extern void open_softirq(int nr, void (*action)(struct softirq_action *));    // 注册中断函数
extern void raise_softirq(unsigned int nr);                      // 触发软中断


static inline void local_bh_disable(void);                       // 关闭底半部
extern void local_bh_enable(void);                            // 使能底半部


/* PLEASE, avoid to allocate new softirqs, if you need not _really_ high
   frequency threaded job scheduling. For almost all the purposes
   tasklets are more than enough. F.e. all serial device BHs et
   al. should be converted to tasklets, not to softirqs.
 */

enum
{
    HI_SOFTIRQ=0,
    TIMER_SOFTIRQ,
    NET_TX_SOFTIRQ,
    NET_RX_SOFTIRQ,
    BLOCK_SOFTIRQ,
    BLOCK_IOPOLL_SOFTIRQ,
    TASKLET_SOFTIRQ,
    SCHED_SOFTIRQ,
    HRTIMER_SOFTIRQ,
    RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */

    NR_SOFTIRQS
};

10.3.3.4 threaded_irq

  • 同时注册顶半部和底半部
#include <linux/interrupt.h>

extern int request_threaded_irq(unsigned int irq, irq_handler_t handler,
             irq_handler_t thread_fn,
             unsigned long flags, const char *name, void *dev);

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

enum irqreturn {
    IRQ_NONE        = (0 << 0),
    IRQ_HANDLED        = (1 << 0),
    IRQ_WAKE_THREAD        = (1 << 1),
};
!!! 如果顶半部返回IRQ_WAKE_THREAD,则顶半部执行结束后,内核会调度对应线程执行thread_fn对应的函数
如果参数handler为NULL,则系统使用如下默认顶半部处理函数

/*

* 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;
}
!!!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.

 

 

使用模板:

#include <linux/interrupt.h>

irqreturn_t xxx_interrupt( int irq, void * dev_id )
{
  ...
  int status = read_int_status();  
  if( !is_myint( dev_id, status) )    // 判断是不是自己需要处理的中断源
    return IRQ_NONE;

  ...
  return IRQ_HANDLE;    
}

int xxx_init( void )
{
  ...
  result = request( sh_irq, xxx_interrupt, IRQF_SHARED, "xxx", xxx_dev );  
  ...
}

int xxx_exit( void )
{
  ...
  free( sh_irq, xxx_interrupt );
}

10.5 内核定时器

10.5.1 内核定时器编程

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

10.5.1.1 普通定时器编程

#include <linux/timer.h>

/* 1.定义timer_list结构体*/
struct timer_list {
    /*
     * All fields that change during normal runtime grouped to the
     * same cacheline
     */
    struct list_head entry;
    unsigned long expires;            // !定时器到期时间,单位jiffies
    struct tvec_base *base;

    void (*function)(unsigned long);    // !定时器期满后执行此函数
    unsigned long data;            // !传入function的参数

    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
};

struct timer_list my_timer;

/* 2.初始化定时器 */
#define init_timer(timer) __init_timer((timer), 0)    
// 对timer进行初始化,对entry和base赋值

/* 3.给timer_list关键成员赋值 */
#define TIMER_INITIALIZER(_function, _expires, _data)        \
    __TIMER_INITIALIZER((_function), (_expires), (_data), 0)

#define __TIMER_INITIALIZER(_function, _expires, _data, _flags) { \
        .entry = { .prev = TIMER_ENTRY_STATIC },    \
        .function = (_function),            \
        .expires = (_expires),                \
        .data = (_data),                \
        .base = (void *)((unsigned long)&boot_tvec_bases + (_flags)), \
        .slack = -1,                    \
        __TIMER_LOCKDEP_MAP_INITIALIZER(        \
            __FILE__ ":" __stringify(__LINE__))    \
    }

/*
 *  "1+3"的快捷方式,定义+赋关键值
 */
#define DEFINE_TIMER(_name, _function, _expires, _data)        \
    struct timer_list _name =                \
        TIMER_INITIALIZER(_function, _expires, _data)


/* 4.增加定时器,注册到内核里,定时器就开始工作了 */
extern void add_timer(struct timer_list *timer);

/* 5.删除定时器 */
extern int del_timer(struct timer_list * timer);
extern int del_timer_sync(struct timer_list *timer);    // 同步版,等待定时器结束,不能在中断上下文

/* 6.修改定时器 */
extern int mod_timer(struct timer_list *timer, unsigned long expires);
// 原来的expires无效了,改为这个新的值

使用模板:

#include <linux/interrupt.h>

struct xxx_dev{
  struct cdev cdev;
  ...
  struct timer_list xxx_timer;  // 定义定时器
};

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);
  ...
}

xxx_func2(...)
{
  ...
  del_timer(&dev->timer);
  ...
}

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级别的精度。

 

#include <linux/hrtimer.h>

/*
 * Mode arguments of xxx_hrtimer functions:
 */
enum hrtimer_mode {
    HRTIMER_MODE_ABS = 0x0,        /* Time value is absolute */
    HRTIMER_MODE_REL = 0x1,        /* Time value is relative to now */
    HRTIMER_MODE_PINNED = 0x02,    /* Timer is bound to CPU */
    HRTIMER_MODE_ABS_PINNED = 0x02,
    HRTIMER_MODE_REL_PINNED = 0x03,
};

/*
 * Return values for the callback function
 */
enum hrtimer_restart {
    HRTIMER_NORESTART,    /* Timer is not restarted */
    HRTIMER_RESTART,    /* Timer must be restarted */
};

union ktime {
    s64    tv64;
#if BITS_PER_LONG != 64 && !defined(CONFIG_KTIME_SCALAR)
    struct {
# ifdef __BIG_ENDIAN
    s32    sec, nsec;
# else
    s32    nsec, sec;
# endif
    } tv;
#endif
};

/**
 * struct hrtimer - the basic hrtimer structure
 * @node:    timerqueue node, which also manages node.expires,
 *        the absolute expiry time in the hrtimers internal
 *        representation. The time is related to the clock on
 *        which the timer is based. Is setup by adding
 *        slack to the _softexpires value. For non range timers
 *        identical to _softexpires.
 * @_softexpires: the absolute earliest expiry time of the hrtimer.
 *        The time which was given as expiry time when the timer
 *        was armed.
 * @function:    timer expiry callback function
 * @base:    pointer to the timer base (per cpu and per clock)
 * @state:    state information (See bit values above)
 * @start_site:    timer statistics field to store the site where the timer
 *        was started
 * @start_comm: timer statistics field to store the name of the process which
 *        started the timer
 * @start_pid: timer statistics field to store the pid of the task which
 *        started the timer
 *
 * The hrtimer structure must be initialized by hrtimer_init()
 */
struct hrtimer {
    struct timerqueue_node        node;
    ktime_t                _softexpires;
    enum hrtimer_restart        (*function)(struct hrtimer *);
    struct hrtimer_clock_base    *base;
    unsigned long            state;
#ifdef CONFIG_TIMER_STATS
    int                start_pid;
    void                *start_site;
    char                start_comm[16];
#endif
};


static inline void hrtimer_set_expires(struct hrtimer *timer, ktime_t time)
{
    timer->node.expires = time;
    timer->_softexpires = time;
}

static inline int hrtimer_start_expires(struct hrtimer *timer,
                        enum hrtimer_mode mode)
{
    unsigned long delta;
    ktime_t soft, hard;
    soft = hrtimer_get_softexpires(timer);
    hard = hrtimer_get_expires(timer);
    delta = ktime_to_ns(ktime_sub(hard, soft));
    return hrtimer_start_range_ns(timer, soft, delta, mode);
}


/* Forward a hrtimer so it expires after the hrtimer's current now */
static inline u64 hrtimer_forward_now(struct hrtimer *timer,
                      ktime_t interval)
{
    return hrtimer_forward(timer, timer->base->get_time(), interval);
}


static inline int hrtimer_restart(struct hrtimer *timer)
{
    return hrtimer_start_expires(timer, HRTIMER_MODE_ABS);
}

/* 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:

static enum hrtimer_restart snd_hrtimer_callback(struct hrtimer *hrt)
{
  ...
  hrtimer_forward_now(hrt, ns_to_ktime(iprtd->poll_time_ns));    // 继续装载,以便下一次触发
  return HRTIMER_RESTART;
}

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;

  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);     // 启动,相对时间模式
    ...
}

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;
}

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值可能不通,即节拍不同。

常用函数和宏:

#include <linux/jiffies.h>

get_jiffies_64():获取64bit值,考虑了原子操作
extern unsigned long msecs_to_jiffies(const unsigned int m); 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 实例: 秒字符设备

  

#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <linux/wait.h>
#include <linux/mutex.h>
#include <linux/timer.h>

#define DEV_NAME    "second"

struct second_dev_t
{
    struct cdev cdev;
    struct class * class;
    dev_t  dev_no;
    struct timer_list timer;
}second_dev;


static void sec_timer_isr( unsigned long arg) 
{
    struct second_dev_t * second_devp  = (struct second_dev_t *)arg;

    printk("\r\n1.jiffies = %d.",jiffies);
    second_devp->timer.expires = jiffies + HZ;
    add_timer(&second_devp->timer);

    printk("\r\n2.jiffies = %d.",jiffies);
}


int second_open(struct inode * inode, struct file * filp)
{
    filp->private_data = &second_dev;

    printk("\r\nsecond open.");
    printk("\r\nsecond open.");

    init_timer( &second_dev.timer );
    second_dev.timer.function = &sec_timer_isr;
    second_dev.timer.data = (unsigned long)(&second_dev);
    second_dev.timer.expires = jiffies + HZ;
    add_timer(&second_dev.timer);

    return 0;
}

ssize_t second_read(struct file *filp, char __user * buf, size_t len, loff_t * pos)
{
    struct second_dev_t * second_devp;
    int ret;

    second_devp = filp->private_data;


    return ret;
}

int second_release(struct inode * inode, struct file * filp)
{
    struct second_dev_t * second_devp = filp->private_data; 

    del_timer(&second_devp->timer);
    return 0;
}


struct file_operations second_fops = {
    .owner = THIS_MODULE,
    .open = second_open,
    .read = second_read,
    .release = second_release,
};

static int __init second_init( void )
{
    int ret;
    
    cdev_init(&second_dev.cdev,&second_fops);
    second_dev.cdev.owner=THIS_MODULE;

    if( (ret=alloc_chrdev_region(&second_dev.dev_no,0,1,DEV_NAME))<0 )
    {
        printk("alloc_chrdev_region err.\r\n");    
        return ret;
    }
    ret = cdev_add(&second_dev.cdev,second_dev.dev_no,1);
    if( ret )
    {
        printk("cdev_add err.\r\n");    
        return ret;
    }

    /*
         * $ sudo insmod second.ko    如果使用class_create,insmod时会报错,dmesg查看内核log信息发现找不到class相关函数
     *   insmod: ERROR: could not insert module second.ko: Unknown symbol in module
     *   $ dmesg   
     *    [ 5495.606920] second: Unknown symbol __class_create (err 0)
     *    [ 5495.606943] second: Unknown symbol class_destroy (err 0)
     *    [ 5495.607027] second: Unknown symbol device_create (err 0)
     */    

    second_dev.class = class_create( THIS_MODULE, DEV_NAME );
    device_create(second_dev.class,NULL,second_dev.dev_no,NULL,DEV_NAME);

    return 0;
}


static void __exit second_exit( void )
{
    unregister_chrdev_region(second_dev.dev_no, 1);
    cdev_del(&second_dev.cdev);
    device_destroy(second_dev.class,second_dev.dev_no);
    class_destroy(second_dev.class);
}


module_init(second_init);
module_exit(second_exit);

MODULE_LICENSE("GPL");    // 不加此声明,会报上述Unknown symbol问题
应用层测试代码:
#include <stdio.h> // printf #include <stdlib.h> // exit #include <unistd.h> #include <fcntl.h> // open #define FILE_NAME "/dev/second" int main(int args, char *argv[]) { int fd; printf("\r\nstart."); // open file fd=open(FILE_NAME,O_RDONLY|O_NONBLOCK); if( fd<0 ) { printf("\r\nopen file err,fd=%d",fd); exit(-1); } while(1); close(fd); exit(0); }

 

 执行结果

sudo ./app

[109127.309807] 
                second open.
                jiffies = 27207252.
                jiffies = 27207502.
                jiffies = 27207752.
                jiffies = 27208002.
                jiffies = 27208253.
                jiffies = 27208503.
                jiffies = 27208753.
                jiffies = 27209003.
                jiffies = 27209253.

 

10.6 内核延时 

10.6.1 短延时

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

#include <linux/delay.h>

void ndelay(unsigned long x);
void udelay(unsigned long x);
void mdelay(unsigned long x);

 

10.6.2 长延时

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

/* 延迟 100 个 jiffies */
unsigned long delay = jiffies + 100;
while(time_before(jiffies, delay));

/* 再延迟 2s */
unsigned long delay = jiffies + 2*Hz;
while(time_before(jiffies, delay));

10.6.3 睡眠延迟

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

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

 

10.7 总结

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

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

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

 

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

 

posted @ 2017-08-03 15:30  liuwanpeng  阅读(4680)  评论(0编辑  收藏  举报