中断与定时器
内部中断,源自CPU内部(软件中断指令、溢出、除法错误等),外部中断来自CPU外部,由外设提出请求。
中断又根据是否可以屏蔽中断分为可屏蔽中断与不可屏蔽中断(NMI)。通过屏蔽字屏蔽可屏蔽中断。
根据中断入口跳转方法,分为向量国断和非向量中断。向量中断由硬件提供中断服务程序入口地址,非向量中断由软件提供中断服务程序入口地址。
Linux中断处理程序程序架构
Linux将相对比较耗时的中断处理程序分解为两个半部:tophalt和bottom half.
顶半部完成尽可能少的比较紧急的功能,往往只是简单地读取寄存器中的中断状态并清除中断标志后就进行“登记中断”的工作。登记中断“”意味将底半部处理程序挂到该设备的底半部执行队列中去。这样,顶半部执行速度就会很快,可以服务更多的中断请求。
底半部几乎做了中断处理程序所有事情,而且可以被新的中断打断,而顶半部不可被中断。底半部相对而言,并不非常紧急,相对耗时,不在硬件中断服务程序中执行。
许多操作系统都提供了中断上下文和非中断上下文相结合的机制,将中断的耗时工作保留到非中断上下文去执行。
Linux中断编程
1.申请IRQ
int request_irq(unsigned int irq,
void (*handler)(int irq, void *dev_id, struct pt_regs *regs),
unsigned long irqflags,
const char *devname,
void *dev_id);
irq 申请的中断号。
handler中断处理函数。dev_id传给此回调函数的参数。
irqflags中断处理的属性。SA_INTERRUPT,表示中断处理程序是快速处理程序,屏蔽所有中断;SA_SHIRQ,表示多个设备共享中断,根据dev_id判定。
return 0,成功;-INVAL,中断号无效或handler为NULL;-EBUSY,中断已被占用且不能共享。
2.释放IRQ
void free_irq(unsigned int irq, void *dev_id);
3.使能和屏蔽中断
void disable_irq(int irq);
void disable_irq_nosync(int irq);//立即返回
void enable_irq(int irq);//等待中断处理完成
以上三个函数作用于可编程中断控制器,对系统内的所有CPU都有效。
void local_irq_save(unsigned long flags);//保留状态在flags中
void local_irq_disable(void);
以上两个函数屏蔽本CPU内的所有中断。
void local_irq_restore(unsigned long flags);
void local_irq_enable(void);
对应local_开头的两个屏蔽中断的函数。
底半部机制
4.tasklet
void my_tasklet_func(unsigned long);//定义一个处理函数
DECLARE_TASKLET(my_tasklet,my_tasklet_func, data);/*定义一个tasklet结构my_tasklet,与my_tasklet_func(data)函数相关联*/
DECLARE_TASKLET(my_tasklet,my_tasklet_func,data)实现了定义名称为my_tasklet的tasklet 并将其与my_tasklet_func()函数绑定,传入参数data.
tasklet调度:
tasklet_schedule(&my_tasklet);
5.工作队列
struct work_struct my_wq;//定义一个工作队列
void my_wq_func(unsigned log);//定义一个处理函数
INIT_WORK(&my_wq, (void(*)(void*))my_wq_func, NULL);//初始化工作队列并将其与处理函数绑定
schedule_work(&my_wq);//调度工作队列执行
6.软中断
软中断是用软件方式模拟硬件中断的概念,实现宏观上的异步执行效果,tasklet孔明基于软中断实现的。
Linux内核中,用softirq_action结构表征一个软中断,这个结构体中包含软中断处理函数指针和传递给该函数的参数。使用softirq_action()函数可以注册软中断对应的处理函数,而raise_softirq()函数可以触发一个软中断。
软中断和tasklet仍然运行于中断上下文,而工作队列则运行于进程上下文。因此,软中断和tasklet处理函数中不能睡眠,而工作队列处理函数中允许睡眠。
local_bh_disable()和local_bh_enable()是内核中用于禁止和使能软中断和tasklet底半部机制的函数。
- 共享中断的多个设备在申请中断时都应该使用SA_SHIRQ标志,而且一个设备以SA_SHIRQ申请某个中断成功的前提是之前申请该中断的所有设备也都以SA_SHIRQ标志申请该中断。
- 尽管内核模块可访问的全局地址都可以作为request_irq(..,void *dev_id)的最后一个参数dev_id,但设备结构指针是可传入的最佳参数。
- 在中断到来时,所有共享此中断的中断处理程序 会被执行,在中断处理程序顶半部中应迅速根据硬件寄存器中的此信息比照传入的dev_id参数来判断是否晒设备的中断,若不是,就迅速返回。
Linux内核所提供的用于操作定时器的数据结构和函数如下:
1. timer_list
struct timer_list{
struct list_head entry;//定时器列表
unsigned long expires;//定时器到期时间
void (*function)(unsigned long);//定时器处理函数
unsigned long data;//作为参数被传入定时器处理函数
struct timer_base_s *base;
};
2.初始化定时器
void init_timer(struct timer_list *timer);
初始化timer_list的entry的next为NULL。
TIMER_INITIALIZER(_function, _expires, _data)宏用于赋值定时器结构体的function、expires、data和base成员。
#define TIMER_INITIALIZER(_function, _expires, _data) { \
.function = (_function), \
expires = (_expires), \
.data = (_data), \
.base = &__init_timer_base, \
}
DEFINE_TIMER(_name, _function, expires, _data)宏是定义并初始化定时器成员的快捷方式。
#define DEFINE_TIMER(_name, _function, _expires, data) \
struct timer_list _name = \
TIMER_INITIALIZER(_function, _expires, _data) \
setup_timer()也可用于初始化定时器并赋值其成员。
static inline void setup_timer(struct timer_list *timer,
void (*function)(unsigned long),
unsigned long data
{
timer->function = function;
timer->data =data;
init_timer(timer);
}
3. 增加定时器
void add_timer(struct timer_list *timer);//注册内核定时器,将定时器加入到内核动态定时器链表中。
4.删除定时器
int del_timer(struct timer_list *timer);
del_timer_sync()是del_timer()同步版,主要用于多处理哭器系统中使用,内核不支持SMP,del_timer_sync()等同于del_timer()
5.修改定时器的expires
int mod_timer(struct timer_list *timer, unsigned long expires);
void ndelay(unsigned long nsecs);//纳秒
void udelay(unsigned long nsecs);//微秒
void mdelay(unsigned long nsecs);//毫秒
以上3个延迟的实现本质是忙等待,根据CPU频率进行一定次数的循环。
内核中最好不要直接使用mdelay()函数,将无谓地耗费CPU资源。内核提供以下函数用于毫秒级以上的延迟
void msleep(unsigned int millisecs);
unsigned long msleep_interruptible(unsigned int millisecs);//可被唤醒
void ssleep(unsigned int seconds);
上述函数将使得商用它的进程睡眠指定的时间。
长延迟
#define time_after(a, b) \
#define time_before(a, b) time_before(b, a)(typecheck(unsigned long ,a) && \
typecheck(unsigned long ,b) && \
((long)(b) -(long)(a) < 0))
为了防止time_before()和time_after()的比较过程中编译器对jiffies的优化,内核将其定义为volatile变量,保证它每次都被重新读取。
睡眠
睡眠延迟在时间到来之前让出CPU资源
schedule_timeout(unsigned long timeout);实现原理是向系统添加一个定时器,在定时器处理函数中唤醒参数对应的进程
schedule_timeout_interruptible(unsigned long timeout);//置进程状态为TAK_INTERRUPTIBLE
schedule_timeout_uninterruptible(unsigned long timeout);//置进程状态为TAK_UNINTERRUPTIBLE
sleep_on_timeout(wait_queue_head_t *q, unsigned long time);
inerruptible_sleep_on_timeout(wait_queue_head_t *q, unsigned long time);//timeout之前可被唤醒
文章评论(0条评论)
登录后参与讨论