《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
- : IO-APIC -edge timer
- : IO-APIC -edge i8042
- : IO-APIC -edge rtc0
- : IO-APIC -fasteoi acpi
- : IO-APIC -edge i8042
- : IO-APIC -edge ata_piix
- : IO-APIC -edge ata_piix
- : IO-APIC -fasteoi vmwgfx, snd_ens1371
- : IO-APIC -fasteoi ehci_hcd:usb1, ioc0
- : IO-APIC -fasteoi uhci_hcd:usb2
- : IO-APIC -fasteoi ens33
- : PCI-MSI -edge PCIe PME, pciehp
- : PCI-MSI -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>
- . 单个使能/屏蔽
- 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 ) // 等不到中断结束了,死锁
- {
- ;
- }
- }
- .全局使能屏蔽
- #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, , ATOMIC_INIT(), 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, );
- /* 中断处理底半部 */
- 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,,"xxx", NULL ); // 注册的是顶半部的执行函数
- ...
- }
- __exit xxx_exit( void )
- {
- ...
- free_irq( xxx_irq, xxx_interrupt );
- ...
- }
!10.3.3.2 工作队列
使用与tasklet相似,区别是执行上线文为内核线程,可以调度和睡眠。内核维护一个工作队列池。
- #include <linux/interrupt.h > // 在次头文件里包含了linux/workqueue.h
- .定义工作队列
struct work_struct my_wq; // 定义工作队列- .把工作队列和处理函数初始化及绑定
void my_wq_func( struct work_struct * work ); // 定义一个处理函数,注意形参
INIT_WORK( &my_wq, my_wq_func ); // 初始化工作队列,并绑定处理函数- .调度
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,,"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=,
- 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 = ( << ),
- IRQ_HANDLED = ( << ),
- IRQ_WAKE_THREAD = ( << ),
- };
- !!! 如果顶半部返回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[];
- #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), )
- #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 = -, \
- __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[];
- #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 ;
- }
- 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 ;
- }
- 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,,,DEV_NAME))< )
- {
- printk("alloc_chrdev_region err.\r\n");
- return ret;
- }
- ret = cdev_add(&second_dev.cdev,second_dev.dev_no,);
- 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 ;
- }
- static void __exit second_exit( void )
- {
- unregister_chrdev_region(second_dev.dev_no, );
- 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< )
- {
- printf("\r\nopen file err,fd=%d",fd);
- exit(-);
- }
- while();
- close(fd);
- exit();
- }
执行结果
- sudo ./app
- [109127.309807]
- second open.
- jiffies = .
- jiffies = .
- jiffies = .
- jiffies = .
- jiffies = .
- jiffies = .
- jiffies = .
- jiffies = .
- jiffies = .
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 + ;
- while(time_before(jiffies, delay));
- /* 再延迟 2s */
- unsigned long delay = jiffies + *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);短延时一般用于底层驱动与硬件交互。
《linux设备驱动开发详解》笔记——10中断与时钟的更多相关文章
- linux设备驱动开发详解 笔记
在目录的 Makefile 中关于 RTC_DRV_S3C 的编译脚本为: obj -$(CONFIG_RTC_DRV_S3C) += rtc-s3c.o 上述脚本意味着如果 RTC_DRV_S3 ...
- Linux设备驱动开发详解
Linux设备驱动开发详解 http://download.csdn.net/detail/wuyouzi067/9581380
- 《Linux设备驱动开发详解(第2版)》配套视频登录51cto教育频道
http://edu.51cto.com/course/course_id-379-page-1.html http://edu.51cto.com/course/course_id-379-page ...
- 《linux设备驱动开发详解》笔记——14 linux网络设备驱动
14.1 网络设备驱动结构 网络协议接口层:硬件无关,标准收发函数dev_queue_xmit()和netif_rx(); 注意,netif_rx是将接收到的数据给上层,有时也在驱动收到数据以后调用 ...
- 《linux设备驱动开发详解》笔记——6字符设备驱动
6.1 字符设备驱动结构 先看看字符设备驱动的架构: 6.1.1 cdev cdev结构体是字符设备的核心数据结构,用于描述一个字符设备,cdev定义如下: #include <linux/cd ...
- 《linux设备驱动开发详解》笔记——15 linux i2c驱动
结合实际代码和书中描述,可能跟书上有一定出入.本文后续芯片相关代码参考ZYNQ. 15.1 总体结构 如下图,i2c驱动分为如下几个重要模块 核心层core,完成i2c总线.设备.驱动模型,对用户提供 ...
- 《linux设备驱动开发详解》笔记——18 ARM linux设备树
18.1 设备树的起源 linux 2.6及之前,大量板级信息被硬编码到内核里,十分庞大,大量冗余代码: linux 2.6之前,引入了设备树: 设备树源于OpenFirmware,描述硬件的数据结构 ...
- 《linux设备驱动开发详解》笔记——12linux设备驱动的软件架构思想
本章重点讲解思想.思想.思想. 12.1 linux驱动的软件架构 下述三种思想,在linux的spi.iic.usb等复杂驱动里广泛使用.后面几节分别对这些思想进行详细说明. 思想1:驱动与设备分离 ...
- Linux设备驱动开发详解-Note(11)--- Linux 文件系统与设备文件系统(3)
Linux 文件系统与设备文件系统(3) 成于坚持,败于止步 sysfs 文件系统与 Linux 设备模型 1.sysfs 文件系统 Linux 2.6 内核引入了 sysfs 文件系统,sysfs ...
- Linux设备驱动开发详解-Note(5)---Linux 内核及内核编程(1)
Linux 内核及内核编程(1) 成于坚持,败于止步 Linux 2.6 内核的特点 Linux 2.6 相对于 Linux 2.4 有相当大的改进,主要体现在如下几个方面. 1.新的调度器 2.6 ...
随机推荐
- NetCore上传多文件
.NetCore上传多文件的几种示例 本章和大家分享的是.NetCore的MVC框架上传文件的示例,主要讲的内容有:form方式提交上传,ajax上传,ajax提交+上传进度效果,Task并行处理 ...
- CSS3 - CheakBox 开关效果
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- 1137 - Sin your life sin公式 + 枚举
http://www.ifrog.cc/acm/problem/1137 和差化积公式, 变成2 * sin((x + y) / 2) * cos((x - y) / 2) + sin(n - (x ...
- SSIS父子维度
1.数据仓库结构: 2.区域的AttributeHierarchyVisible设置为False 3.Parent ID的Usage设置为Parent 4.级别命名: 5.结果:
- spring和springmvc是单例还是多例
这么说其实不规范 spring的bean 默认是单例 springmvc的controller 默认是单例 所以最好不要在controller里定义成员变量 都可通过注解 @scope=p ...
- RHEL6.4 安装 highpoint RocketRAID 2720 阵列卡驱动
step1:下载驱动程序. http://www.highpoint-tech.com/USA_new/series_rr272x_configuration.htm step2:上传驱动程序至服务器 ...
- webpack.config.js====插件html-webpack-plugin
1. 安装 cnpm install html-webpack-plugin --save-dev 2. webpack.config.js中使用 const htmlWebpackPlugin = ...
- 有关在python中使用Redis(一)
python作为一种处理数据的脚本语言本身有许多方法函数供大家使用,有时候为了提升数据处理速度(如海量数据的访问或者海量数据的读取),涉及分布式管理架构,可能需要用到Redis,Redis是一个开源的 ...
- 我们为什么要看《超实用的HTML代码段》
不知道自己HTML水平如何,不知道HTML5如何进化?看这张图 如果一半以上的你都不会,必须看这本书,阿里一线工程师用代码和功能页面来告诉你每一个技术点. 都会一点,但不知道如何检验自己,看看本书提供 ...
- put_user
1. put_user Name put_user -- Write a simple value into user space. Synopsis put_user ( x, ptr); A ...