第七章: Interrupt and Interrupt Handler
1. 驱动调用 request_irq()来注册中断,声明在 <linux/interrupt.h>
/* request_irq: allocate a given interrupt line */
int request_irq(unsigned int irq,
irq_handler_t handler,
unsigned long flags,
const char *name,
void *dev)
中断处理函数原型:
typedef irqreturn_t (*irq_handler_t)(int, void *);
成功放回0,失败返回非0.

2. Interrupt Handler Flags
request_irq的第三个参数.主要有:
IRQF_DISABLED: 禁止所有中断.
IRQF_SAMPLE_RANDOM: 通知内核将该中断发生时间输入熵池.用于不定时间发生的中断.
IRQF_TIMER: System Timer产生的中断.
IRQF_SHARED: 该中断可被多个中断处理函数共享.

3. An Interrupt Example
if (request_irq(irqn, my_interrupt, IRQF_SHARED, "my_device", my_dev)) {
printk(KERN_ERR "my_device: cannot register IRQ %d\n", irqn);
return -EIO;
}

4.Freeing an Interrupt Handler
void free_irq(unsigned int irq, void *dev);

5. 声明interrupt handler:
static irqreturn_t intr_handler(int irq, void *dev);
返回2种类型数据, IRQ_NONE或IRQ_HANDLED
Linux中中断处理函数是不能重入的.

6.Shared Handlers
与一般nonshared handlers的区别:
(1)  request_irq()时必须设置标志 IRQF_SHARED;
(2) 参数dev对于每个注册的handler是唯一的;
(3) 中断处理函数必须能够区分设备是否触发了中断.

7. RTC interrupt例子
/* register rtc_interrupt on rtc_irq */ 
if (request_irq(rtc_irq, rtc_interrupt, IRQF_SHARED, "rtc", (void *)&rtc_port)) {
printk(KERN_ERR "rtc: cannot register IRQ %d\n", rtc_irq); 
return -EIO;
}

static irqreturn_t rtc_interrupt(int irq, void *dev) 
{
/*
* Can be an alarm interrupt, update complete interrupt,
* or a periodic interrupt. We store the status in the
* low byte and the number of interrupts received since
* the last read in the remainder of rtc_irq_data.
*/
spin_lock(&rtc_lock);
rtc_irq_data += 0x100; 
rtc_irq_data &= ~0xff; 
rtc_irq_data |= (CMOS_READ(RTC_INTR_FLAGS) & 0xF0);
if (rtc_status & RTC_TIMER_ON) 
mod_timer(&rtc_irq_timer, jiffies + HZ/rtc_freq + 2*HZ/100);
spin_unlock(&rtc_lock);
/*
* Now do the rest of the actions
*/ 
spin_lock(&rtc_task_lock); 
if (rtc_callback)
rtc_callback->func(rtc_callback->private_data); 
spin_unlock(&rtc_task_lock); 
wake_up_interruptible(&rtc_wait);
kill_fasync(&rtc_async_queue, SIGIO, POLL_IN);
return IRQ_HANDLED; 
}

8.


9. /proc/interrupts  file
该文件保存系统中断的统计信息.
中断控制:在<asm/system.h>  and <asm/irq.h>中定义了控制中断的接口(平台相关),如禁止屏蔽系统中断.
在当前处理器上禁止和使能中断:
local_irq_disable();
/* interrupts are disabled .. */
local_irq_enable();
中断保存和恢复:
unsigned long flags;
local_irq_save(flags);    /* interrupts are now disabled */
/* ... */
local_irq_restore(flags); /* interrupts are restored to their previous state */
禁止特定的中断:
void disable_irq(unsigned int irq); //等待中断结束
void disable_irq_nosync(unsigned int irq); //不等待中断结束
void enable_irq(unsigned int irq);
void synchronize_irq(unsigned int irq);//等待特定中断程序完成

宏irqs_disabled()(定义在 <asm/system.h>)返回非0表示中断被禁止,反之返回0.
宏in_interrupt():返回非0表示系统正在运行中断服务程序或者bottom half Handler.
宏in_irq():返回非0表示系统正在运行中断服务程序.

10. 中断控制方法


第八章 Bottom Halves and Deferring Work

1. 中断处理分为2部分,中断处理例程 (top halves),内核立即响应异步处理硬件中断.接下来的工作由bottom halves来处理.

2. 时间敏感的工作放在interrupt handler执行;硬件相关的工作放在interrupt handler;需要确保其他中断不会打断的工作放在interrupt handler;其他的工作都可以放在bottom half.

3. Linux2.6使用3种bottom halves机制, softirqs, tasklets, and work queues.

4. Softirqs
(1) Softirq表现为 softirq_action结构,定义在 <linux/interrupt.h>
struct softirq_action {
void (*action)(struct softirq_action *);
};
32位系统中,声明在kernel/softirq.c
static struct softirq_action softirq_vec[NR_SOFTIRQS];
内核强制32个注册softirq,当前系统只使用了9个.
(2) softirq handler:
原型: void softirq_handler(struct softirq_action *);
一个softirq从不抢占另一个softirq.
(3) 注册的softirq在执行之前必须被标记.
softirq执行发生在__do_softirq(),被do_softirq()调用.
(4)softirq用于最时间关键和重要的bottom-half处理,当前只有网络和块设备使用了softirq,其他场合可以使用tasklets.
(5)softirq值低的被先执行. HI_SOFTIRQ是第一个,RCU_SOFTIRQ总是最后一个.
(6)调用open_softirq()注册用户softirq handler, 如网络(net/core/dev.c):
open_softirq(NET_TX_SOFTIRQ, net_tx_action);
open_softirq(NET_RX_SOFTIRQ, net_rx_action);
一个softirq handler在执行时,该处理器上的softirq被禁止,其他处理器可以执行其他的softirq.也可以执行相同的softirq.
(7)标记挂起:调用raise_softirq(),如网络中的:
raise_softirq(NET_TX_SOFTIRQ);
如果中断被关闭,可以调用优化版本 raise_softirq_irqoff();
如:raise_softirq_irqoff(NET_TX_SOFTIRQ);

5.Tasklets
(1) Tasklets是基于softirq上层的bottom-half机制,与概念task无关.与softirq相似但有更简单的接口和松散的lock机制.
(2) 大部分情况选择Tasklets,只有在高频率和高度线程化的场合才使用Tasklets.
(3) Tasklets由2种softirq代表:HI_SOFTIRQ和TASKLET_SOFTIRQ.两者唯一的区别是基于HI_SOFTIRQ的tasklets优先级高于 TASKLET_SOFTIRQ的tasklets.
(4) tasklet_struct定义在 <linux/interrupt.h>,原型为:
struct tasklet_struct { 
struct tasklet_struct *next;  /* next tasklet in the list */ 
unsigned long state;          /* state of the tasklet */ 
atomic_t count;               /* reference counter */ 
void (*func)(unsigned long);  /* tasklet handler function */ 
unsigned long data;           /* argument to the tasklet function */
};
(5) 调度的Tasklets被保存在每处理器相关的结构tasklet_vec(普通tasklets)和tasklet_hi_vec(高优先级tasklets).
(6) Tasklets通过tasklet_schedule()和 tasklet_hi_schedule()来调度.这两个函数要确保相应的Tasklets没有被调度,然后调用相应的__tasklet_schedule()  and __tasklet_hi_schedule().
(7)  tasklet_action()和tasklet_hi_action()是Tasklets处理的核心.
(8) 静态声明Tasklets(定义在<linux/interrupt.h>:
DECLARE_TASKLET(name, func, data)
DECLARE_TASKLET_DISABLED(name, func, data);
Example:
DECLARE_TASKLET(my_tasklet, my_tasklet_handler, dev);
等同于:
struct tasklet_struct my_tasklet = { NULL, 0, ATOMIC_INIT(0),
my_tasklet_handler, dev };
动态声明:
tasklet_init(t, tasklet_handler, dev);  /* dynamically as opposed to statically */
(9) Tasklets handler
void tasklet_handler(unsigned long data)
Tasklets处理函数中不能使用信号量等阻塞函数.2个相同的Tasklets不能同时运行,不同的Tasklets则可以同时运行.共享数据需要用locking机制.
(10) 将 tasklet_struct传递给tasklet_schedule()来调度执行.如:
tasklet_schedule(&my_tasklet);    /* mark my_tasklet as pending */
(11) tasklet_disable();//禁止Tasklets,等待执行完
tasklet_disable_nosync();//不等执行完直接禁止
tasklet_enable();//使能Tasklets
tasklet_kill();//从pending queue中移除Tasklets

6. Work Queues
(1) Work Queues工作在内核线程,运行在process context,可以被调度,也能够睡眠.
(2) 如果延时工作需要睡眠,则使用Work Queues;如果不需要睡眠,softirqs或者tasklets都可以使用.如果处理bottom-half需要调度,使用Work Queues.在需要分配大量内存,得到信号量,或者进行I/O操作时非常有用.
(3) Work Queues创建的kernel thread叫worker threads.默认的worker threads叫作events/n.n是处理器编号.如果是单处理器系统,为events/0
(4) Worker threads用workqueue_struct结构来表示(定义在kernel/workqueue.c):
/*
* The externally visible workqueue abstraction is an array of
* per-CPU workqueues:
*/
struct workqueue_struct { 
struct cpu_workqueue_struct cpu_wq[NR_CPUS]; 
struct list_head list; 
const char *name; 
int singlethread; 
int freezeable; 
int rt;
};
cpu_workqueue_struct(kernel/workqueue.c)是其核心数据,表示每个处理器上的数据.
struct cpu_workqueue_struct { 
spinlock_t lock;             /* lock protecting this structure */
struct list_head worklist;   /* list of work */ 
wait_queue_head_t more_work; 
struct work_struct *current_struct;
struct workqueue_struct *wq; /* associated workqueue_struct */
task_t *thread;              /* associated thread */
};
(5) work_struct结构来表示work:定义在<linux/workqueue.h>:
struct work_struct {
atomic_long_t data;
struct list_head entry;
work_func_t func;
};
这些结构形成一个链表,表示一个处理器上的一个队列类型.当work tread唤醒时,运行链表中的每个work,完成后从链表中移除.
(6) run_workqueue()函数实际执行deferred work.
(7) Work Queue Implementation Summary

(8) 静态声明work:
DECLARE_WORK(name, void (*func)(void *), void *data);
动态声明work:
INIT_WORK(struct work_struct *work, void (*func)(void *), void *data);
work handler原型:
void work_handler(void *data);
调度创建的work:
schedule_work(&work);
带延时的调度:
schedule_delayed_work(&work, delay);
flush work queue:
void flush_scheduled_work(void);
等待队列中所有的work执行完才返回.不cancel任何delayed work,调用cancel_delayed_work()来取消:
int cancel_delayed_work(struct work_struct *work);

(9)创建新的work queue
struct workqueue_struct *create_workqueue(const char *name);
参数为kernel thread名称,如默认的events queue:
struct workqueue_struct *keventd_wq;
keventd_wq = create_workqueue(“events”);

int queue_work(struct workqueue_struct *wq, struct work_struct *work)
int queue_delayed_work(struct workqueue_struct *wq,
struct work_struct *work,
unsigned long delay)
类同于schedule_work()和schedule_delayed_work().
flush_workqueue(struct workqueue_struct *wq);
等同于flush_scheduled_work().

(10) 三种Bottom-half接口的比较:

(11) bottom Halves的控制方法:

这两种方法只针对softirq和tasklets,对work queue无效,因为work queue是被调度的,没有必要禁止或使能.
调用第一次local_bh_disable(),softirqs被禁止,在调用3次,才真正禁止.调用local_bh_enable()也是一样.


Logo

更多推荐