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

CPU00: 131 IO-APIC 2-edge timer1: 268 IO-APIC 1-edge i80428: 1 IO-APIC 8-edge rtc09: 0 IO-APIC 9-fasteoi acpi12: 13131 IO-APIC 12-edge i804214: 0 IO-APIC 14-edge ata_piix15: 0 IO-APIC 15-edge ata_piix16: 4449 IO-APIC 16-fasteoi vmwgfx, snd_ens137117: 38218 IO-APIC 17-fasteoi ehci_hcd:usb1, ioc018: 196 IO-APIC 18-fasteoi uhci_hcd:usb219: 58527 IO-APIC 19-fasteoi ens3324: 0 PCI-MSI 344064-edge PCIe PME, pciehp25: 0 PCI-MSI 346112-edge PCIe PME, pciehp

10.3 linux中断编程

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

10.3.1 申请和释放中断

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

#include intrequest_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_FALLING0x00000002

#define IRQF_TRIGGER_HIGH0x00000004

#define IRQF_TRIGGER_LOW0x00000008

处理方式:

#define IRQF_SHARED0x00000080

返回值: 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 1. 单个使能/屏蔽extern void disable_irq_nosync(unsigned int irq); //立即返回

extern void disable_irq(unsigned int irq); //等待中断处理完成后返回

extern void enable_irq(unsigned intirq);

注意:在顶半部调用disable_irq()会导致死锁,因为void disable_irq(unsigned intirq)

{

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 #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 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/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 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 );returnIRQ_HANDLED;

}

__exit xxx_exit(void)

{

...

free_irq( xxx_irq, xxx_interrupt );

...

}

10.3.3.3 软中断

tasklet就是基于软中断的,一般来说,驱动编写者不宜直接使用软中断,linux封装了例如tasklet这类友好的接口,还有其他场合是用软中断实现的,见后面描述。

优先级: 中断>软中断>线程,当某个时间段内软中断过多时,操作系统会将后面的软中断放入ksoftirq内核线程中执行,软中断适度线程化有利于缓解高负荷情况下的系统响应。

#include structsoftirq_action

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

};extern void open_softirq(int nr, void (*action)(struct softirq_action *));    // 注册中断函数extern void raise_softirq(unsigned intnr);                      // 触发软中断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 extern int request_threaded_irq(unsigned intirq, irq_handler_t handler,

irq_handler_t thread_fn,

unsignedlong flags, const char *name, void *dev);extern int devm_request_threaded_irq(struct device *dev, unsigned intirq,

irq_handler_t handler, irq_handler_t thread_fn,

unsignedlong irqflags, const char *devname,void *dev_id);

enumirqreturn {

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)

{returnIRQ_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 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 /*1.定义timer_list结构体*/

structtimer_list {/** All fields that change during normal runtime grouped to the

* same cacheline*/

structlist_head entry;

unsignedlong expires; //!定时器到期时间,单位jiffies

struct tvec_base *base;void (*function)(unsigned long); //!定时器期满后执行此函数

unsigned long data; //!传入function的参数

intslack;

#ifdef CONFIG_TIMER_STATSintstart_pid;void *start_site;char start_comm[16];#endif#ifdef CONFIG_LOCKDEPstructlockdep_map lockdep_map;#endif};structtimer_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 longexpires);//原来的expires无效了,改为这个新的值

使用模板:

#include structxxx_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 /** Mode arguments of xxx_hrtimer functions:*/

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

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

#elses32 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()*/

structhrtimer {structtimerqueue_node node;

ktime_t _softexpires;enum hrtimer_restart (*function)(struct hrtimer *);struct hrtimer_clock_base *base;

unsignedlongstate;

#ifdef CONFIG_TIMER_STATSintstart_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,enumhrtimer_mode mode)

{

unsignedlongdelta;

ktime_t soft, hard;

soft=hrtimer_get_softexpires(timer);

hard=hrtimer_get_expires(timer);

delta=ktime_to_ns(ktime_sub(hard, soft));returnhrtimer_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)

{returnhrtimer_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 get_jiffies_64():获取64bit值,考虑了原子操作extern unsigned long msecs_to_jiffies(const unsigned intm);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#include#include#include#include#include#include#include#define DEV_NAME "second"

structsecond_dev_t

{structcdev cdev;struct class * class;

dev_t dev_no;structtimer_list timer;

}second_dev;static void sec_timer_isr( unsigned longarg)

{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;intret;

second_devp= filp->private_data;returnret;

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

{intret;

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");returnret;

}

ret= cdev_add(&second_dev.cdev,second_dev.dev_no,1);if( ret )

{

printk("cdev_add err.\r\n");returnret;

}/** $ 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 //printf

#include //exit

#include #include//open

#define FILE_NAME "/dev/second"

int main(int args, char *argv[])

{intfd;

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 void ndelay(unsigned longx);void udelay(unsigned longx);void mdelay(unsigned long x);

10.6.2 长延时

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

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

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

10.6.3 睡眠延迟

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

#include

void msleep(unsigned intmillisecs);

unsignedlong msleep_interruptible(unsigned intmillisecs);void ssleep(unsigned int seconds);

10.7 总结

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

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

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

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

c16语言延时函数delay,《linux设备驱动开发详解》笔记——10中断与时钟相关推荐

  1. 《Linux设备驱动开发详解(第2版)》隆重出版

    Linux设备驱动开发详解(第2版)(前一版狂销3万册,畅销书最新升级) [新品] 点击看大图     基本信息 * 作者: 宋宝华       * 出版社:人民邮电出版社     * ISBN:97 ...

  2. 《Linux设备驱动开发详解》学习笔记一

    Linux设备驱动开发详解学习笔记<一> 书名:<Linux设备驱动开发详解>第二版 主机环境:Linux version 2.6.25-14.fc9.i686@Fedora ...

  3. 《Linux 设备驱动开发详解(第2版)》——1.4 Linux设备驱动

    本节书摘来自异步社区<Linux 设备驱动开发详解(第2版)>一书中的第1章,第1.1节,作者:宋宝华著,更多章节内容可以访问云栖社区"异步社区"公众号查看 1.4 L ...

  4. linux 设备驱动 ppt,linux设备驱动开发详解讲座ppt

    PPT内容 这是linux设备驱动开发详解讲座ppt下载,主要介绍了设备驱动简介:建立和运行模块:字符驱动:调试技术:并发和竞争:分配内存:硬件通讯:中断处理:块设备驱动,欢迎点击下载. 嵌入式Lin ...

  5. linux设备驱动开发详解孔夫子,Linux设备驱动开发详解

    [内容简介] <Linux设备驱动开发详解(第2版)>是一本介绍linux设备驱动开发理论.框架与实例的书,<Linux设备驱动开发详解(第2版)>基于ldd6410开发板,以 ...

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

    <linux设备驱动开发详解>笔记--15 linux i2c驱动 15.1 总体结构 如下图,i2c驱动分为如下几个重要模块 核心层core,完成i2c总线.设备.驱动模型,对用户提供s ...

  7. linux设备驱动总结,《Linux设备驱动开发详解(第3版)》海量更新总结

    本博实时更新<Linux设备驱动开发详解(第3版)>的最新进展. 2015.2.26 几乎完成初稿. [F]是修正或升级:[N]是新增知识点:[D]是删除的内容 第1章 <Linux ...

  8. linux设备驱动开发详解 第三版,《Linux设备驱动开发详解(第3版)》进展同步更新...

    2014.8.25 目前初步完成2-11章以及第22章 <Linux设备驱动的调试>,相对于第2版,这几章主要的变更. 本博实时更新<Linux设备驱动开发详解(第3版)>的最 ...

  9. 《Linux设备驱动开发详解(第3版)》(即《Linux设备驱动开发详解:基于最新的Linux 4.0内核》)进展同步更新

    本博实时更新<Linux设备驱动开发详解(第3版)>的最新进展. 目前已经完成稿件. 2015年8月9日,china-pub开始上线预售: http://product.china-pub ...

  10. 分享《Linux设备驱动开发详解》第2版高清电子版

    新浪微博:@宋宝华Barry 在@微盘 分享了<linux设备驱动开发详解>第2版1080P电子版,拟升级为第3版,3.16内核,Cortex-A9 SMP,Device tree, DV ...

最新文章

  1. 字节跳动《算法中文手册》完整版 PDF 开放下载!
  2. svm 支持向量机 回归 预测_机器学习:简单理解支持向量机SVM
  3. ssh 三者集合的思想
  4. Java中获取当前函数名
  5. ubuntu 远程桌面
  6. 判断是否为两位数(信息学奥赛一本通-T1044)
  7. 文件处理命令,目录处理命令,链接命令
  8. 手把手教你使用python爬取网络图片并存入本地
  9. 建立微带天线阵列与散射仿真
  10. jvm分析工具JProfiler详解
  11. NOIP2016排名(919~1419)
  12. 索尼公布电池召回计划 不局限于笔记本
  13. 小米 信号测试软件,小米WiFi测试版
  14. 巧用CUDA中的pinned memory
  15. canvas画奥运五环 2019/10/29
  16. 利用Python you-get 下载网页视频
  17. 【CTF题解NO.00008】mini-LCTF 2021 official write up by arttnba3
  18. MVC、MVP、MVI、MVVM 和 VIPER 设计模式
  19. 从 RxBus 这辆兰博基尼深入进去
  20. 我的世界java边境之地_我的世界:前往边境之地的9个“诡异”现象!阳光也会被吞噬?...

热门文章

  1. ANSI C typedef
  2. windows下安装gcc编译器(c/c++/fortran)
  3. Django:Web框架,WSGI,WSGI实现浏览器与服务器通信,路由route,WSGI实现页面访问
  4. rust程序设计语言第二版_C语言程序设计(山东联盟青岛大学版)
  5. android-6.0 新功能介绍(Marshmallow)
  6. netty的channel介绍
  7. 计算机组成i1-i8,2016年软考程序员例题分析之计算机组成原理
  8. tomcat中request对象是被创建的_Python中对象的创建与引用
  9. OpenShift 4 之Knative(3) - 通过事件触发Serverless服务
  10. (十)DeepFaceLab:预包装的DIY深度伪造替代品