您的位置:首页 > 运维架构 > Linux

《Linux4.0设备驱动开发详解》笔记--第十章:Linux设备驱动中的中断与时钟

2016-05-20 13:44 441 查看

10.1 中断与定时器

分类

中断来源

内部中断

来源于CPU的内部(软件中断的指令、溢出、除法错误等)

外部中断

来源于外设请求

是否可屏蔽

可屏蔽中断

不可屏蔽中断

中断的入口方式

向量中断

CPU给每个不同的中断分配不同的中断号,中断发生时会自动跳到该中断号对应的地址执行

非向量中断

多个中断共享一个入口地址,进入该入口后通过软件判断中具体哪个中断

ARM渡河处理器里最常用的中断控制器是GIC,它支持三种类型的中断

SGI:软件产生的中断,可用于多核间通信,一个CPU可以通过写GIC寄存器给另一个CPU产生中断

PPI:某个CPU私有外设的中断,这类外设中断只能发给一个CPU

SPI:共享外设中断,这类外设的中断可以路由到任何一个CPU

ARM Linux默认情况下,中断都是由CPU0上产生的

10.2 Linux的中断处理程序架构

中断分层

顶半部

简单地读取寄存器中中断状态,并处理中断标志后就进行“登记中断”的工作

“登记中断”:将底半部放在该设备的底半部执行队列中,以加快顶半部的执行速度

底半部

处理中断程序的所有事情,可被中断打断

中断要处理的任务较少时可以所有的处理任务放到顶部处理

cat /proc/interrupts

获得中断的统计信息,并且能统计出每个中断号发生的次数

10.3 Linux中断编程

10.3.1 申请和释放中断

1、申请irq

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


request_irq()函数

参数

irq:申请的硬件中断号

handler:向系统登记的中断处理函数,是一个回调函数

irqflags:中断处理属性

中断触发方式

IRQ_TRIGGER_RISING

IRQF_TRIGGER_FALLING

IRQF_TRIGGER_HIGH

IRQF_TRIGGER_LOW

中断处理方式

IRQF_shared:多个设备共享中断

dev:传递给中断服务程序的私有数据,一般为设备结构体或者NULL

返回值

成功:返回0

失败:

返回-EINVAL:中断号无效或者中断处理函数为NULL

返回-EBUSY:中断已经被占用且不能共享

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


devm_request_irq()函数

与request_irq()函数区别:devm_开头的API申请的是内核“managed”的资源,一般不需要在出错的地方处理和在remove()接口里面在显示的释放

10.3.2 使能和屏蔽中断

屏蔽一个中断源

disable_irq_nosync():立即返回

disable_irq():等待目前的中断处理完成

因为desable_irq()函数会等待指定的中断被处理完成,因此如果在n号中断的顶半部调用disable_irq(n),会引起系统的死锁,这种情况下只能调用disable_irq_nosync(n)

void disable_irq_irq(int irq);
void disable_irq_nosync(int irq);
void enable_irq(int irq);


屏蔽本CPU上的全部中断

local_irq_save()函数:会将中断的状态保留在flags中

flags为unsigned long类型,被直接传递,而不是通过指针

local_irq()函数直接禁止中断而不保存状态

#define local_irq_save(flag) ...
void local_irq_disable(void);


恢复本CPU上的全部中断

#define local_irq-restore(flags) ...
void local_irq_enable(void);


10.3.3 底半部机制

Linux实现底半部机制主要有tasklet、工作队列、软中断和线程化irq

tasklet

tasklet执行的上下文是软中断,执行的时机是顶半部返回的时候

实际的操作是:定义tasklet及其处理函数,并将两者关联

DECLARE_TASKLET:实现了定义名称为my_tasklet的tasklet,并将其与my_tasklet_func()这个函数绑定,而传入这个函数的参数为data

在调度tasklet的时候引用一个tasklet_schedule()函数

void my_tasklet_func(unsigned long);//定义一个处理函数

//定义一个tasklet结构my_tasklet, 与my_tasklet_func(data)函数相关联
DECLARE_TASKLET(my_tasklet, my_tasklet_func, data);

tasklet_schedule(&my_tasklet);


工作队列

使用方法和tasklet相似,如下:

struct work_struct my_wq; //定义一个工作队列

void my_wq_func(unsigned long);  //定义一个处理函数

INIT_WORK(&my_wq, (void (*)(void *))my_wq_func, NULL);  //初始化工作队列并将其与处理函数绑定

schedule_work(&my_irq);//系统在适当的时候需要调度时使用运行


软中断

使用软件方式模拟硬件中断的概念,实现宏观上的异步执行效果

软中断和tasklet仍然运行与中断上下文,而工作队列则运行于进程上下文,因此软中断和tasklet的处理函数不能休眠,但工作队列是可以的。

softirq_action结构体表征一个软中断

这个结构体中包含软中断处理函数指针和传递给函数的参数

open_softirq()可以注册软中断对应的处理函数

raise_softirq()函数可以触发一个中断

local_bh_disable()和local_bh_enable()是内核用于禁止和使能软中断和tasklet底半部机制的函数。

threaded_irq

内核申请中断的新函数

request_threaded_irq();

devm_request_threaded_irq();

新的函数比request_irq()函数和devm_request_irq()函数多了个thread_fn参数

这两个函数申请中断的时候,内核会为相应的中断号分配一个对应的内核线程,这个线程指针对这个中断号

这两个函数支持在irqflags中设置IRQF_ONESHOT标志,这样内核会自动在中断上下文中屏蔽对应的中断号,而在内核调度thread_fn执行后,重新是能该中断号

参数

handler对应的函数执行于中断上下文

thread_fn参数对应的函数执行于内核线程

如果handler结束的时候,返回值是IRQ_WAKE_THREAD,内核会调度线程执行thread_fn对应的函数

handler参数可以设置为NULL,此时内核会默认的irq_default_primary_handler()代替handler,并会使用IRQF_ONESHOT标志

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

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

static irqreturn_t irq_default_primary_handler(int irq, void *dev_id)
{
return IRQ_WAKE_THREAD;
}


10.4 中断共享

共享中断的多个设备申请中断时,都应该使用IRQF_SHARED标志,申请成功的前提:

该中断未被申请

该中断虽然被申请了,但是之前申请的该中断的所有设备也都以IRQF_SHARED标志申请该中断

尽管内核模块可以访问的全局地址都可以作为request_irq(…,void *dev_id)的最后一个参数dev_id,但是设备结构体指针显示可传入的最佳参数

中断到来时,会遍历执行共享此中断的所有中断处理程序,直到某个函数返回IRQ_HANDLED

在中断处理程序顶半部中,应根据硬件寄存器中的信息比照传入的dev_id参数迅速判断是否为本设备的中断,若不是,应迅速返回IRQ_NONE

10.5 内核定时器

10.5.1 内核定时器编程

1、timer_list结构体

内核定时器的数据结构

定时器期满后,function()成员将被执行

data成员是其传入的参数

expires成员是定时器的到期时间(jiffies)

struct timer_list {
struct list_head entry;

unsigned long expires;
void (*function)(unsigned long);
unsigned long data;

struct tvec_base *base;
/* ... */
};


定义一个名为my_timer的定时器

struct timer_list my_timer;


2、初始化定时器

init_timer是一个宏,用来初始化timer_list的entry的next为NULL,并给base指针赋值

void init_timer(struct timer_list *timer);


TIMER_INITALIZER(_function,_expires, _data)宏用于赋值定时器结构体的function、expires、data和base成员

DEFINE_TIMER(_name,_function,_expires,_data)宏是定义并初始化定时器成员的“快捷方式”

setup_timer()也可以用于初始化定时器并赋值其成员

3、增加定时器

注册内核定时器,将定时器加入到内核的动态定时器链表中

void add_timer(struct timer_list *timer);


4、删除定时器

del_timer_sync()是del_timer的同步版,在删除一个定时器时需要等待其被处理完,因此该函数的调用不能发生在中断上下文中

int del_timer(struct timer_list *timer);


5、修改定时器

mod_timer()函数修改定时器的到期时间,在新的被传入的expires到来后才会执行定时器函数

int mod_timer(struct timer_list *timer, unsigned long expires);


6、定时器的使用流程

-----------使用定时器的步骤--------------
struct timer_list  my_timer_list;//定义一个定时器,可以把它放在你的设备结构中 struct{定义一个定时器}
init_timer(&my_timer_list);//初始化一个定时器
my_timer_list.expire=jiffies+HZ;//定时器1s后运行服务程序
my_timer_list.function=timer_function;//定时器服务函数
add_timer(&my_timer_list);//添加定时器
void timer_function(unsigned long)//写定时器服务函数
del_timer(&my_timer_list);//当定时器不再需要时删除定时器
del_timer_sync(&my_timer_list);//基本和del_timer一样,比较适合在多核处理器使用,一般推荐使用del_timer_sync


10.6 内核延时

10.6.1 短延时

内核提供的纳秒、微妙和毫秒的延时

忙等待,根据CPU的频率进行一定次数的循环

毫秒延时对内核来说是很大的,最好不要直接用,可以用替代性函数

void ndelay(unsigned long nsecs);
void udelay(unsigned long usecs);
void mdelay(unsigned long msecs);

//毫秒延时的替代性函数
vooid msleep(unsigned int millisecs);
unsigned long msleep_interruptible(unsigned int millisecs);
void ssleep(unsigned int seconds);


10.6.2 长延时

比较当前的jiffies和目标jiffies,直到未来的jiffies到达目标jiffies

time_before()与time_after()函数:将传入的未来时间jiffies和被调用时的jiffies进行一个简单的比较

为了防止在比较过程中编译器对jiffies的优化,内核将其定义为volatile变量,这将保证每次都会重新读取这个变量

//延迟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 睡着延时

睡着延时:是在等待的时间到来之前进程处于睡眠状态,CPU资源被其他的进程使用

schedule_timeout()可以使当前任务休眠至指定的jiffies之后再重新被调用执行

schedule_timeout的实现原理是向系统添加一个定时器,在定时器处理函数中唤醒与参数对应的进程

下面函数可以将当前进程添加到等待队列中,从而在等待队列上睡眠,当超时发生时,进程将它们唤醒

sleep_on_timeout(wait_queue_head_t *q, unsigned long timeout);
interruptible_sleep_on_timeout(wait_queue_head_t *q, unsignd long timeout);
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  驱动开发