由起,
中断的使用是CPU对系统外围设备进行管理的一个自然的选择。通常来说,处理器的速度和外围硬件设备的速度通常不在一个数量级上。如果使用轮询的方式进行协同工作,整体性能通常会差强人意(仅仅是通常情况,某些情况下,轮询的效率会很高,尤其在高速网络情况下)。故采用中断方式。中断本质上是一种特殊的电信号,有硬件设备产生。处理器接收到中断后,会马上向操作系统反映次信号的到来,然后就由操作系统负责处理这些新到来的数据。
类型,
1.同步中断(异常)
由CPU自身产生,针对当前执行的程序。。异常可能由种种原因触发:如编程失误(除0),执行期间出现特殊情况(缺页),因为许多体系结构处理异常与处理中断的方法类似,因此,内核对它们的处理也很类似。
2.异步中断
经典的中断类型,由外部设备产生,可能发生在任意时间。
每个中断都有一个编号。如果将中断号分配给不同的设备,那么内核即可区分两个设备,并在中断发生时调用对应的ISR来执行特定与设备的操作。
中断控制器,负责IRQ的信号的电工处理之外,还会对IRQ编号和中断号进行一个转换。
处理中断
中断处理划分为3部分。首先,必须建立一个适当的环境,使得处理程序函数能够在其中执行,接下来调用处理程序自身,最后将系统复原(在当前任务看来)到中断之前的状态。进入和退出任务还负责确保处理器从用户态切换到核心态。进入路径的一个关键任务是,从用户态栈切换到核心态栈。在退出路径中,内核会检查下列事项:1,是否要进行新的调度。2,是否有信号投递到原进程。
中断处理程序的实现要求和组成划分
ISR必须满足:1,实现(尤其是禁用其他中断时)必须包含尽可能少的代码,以支持快速处理。2,可以在其他ISR执行期间调用的中断处理例程,不能彼此干扰。
ISR组成划分:1,关键操作必须在中断发生后立即执行,在此类操作期间,必须禁用其他中断。2.非关键操作也应该尽快执行,但允许启用中断。3,可延期操作不是特别重要,不必在中断处理程序中实现。
中断处理框架实现
1: struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
2: [0 ... NR_IRQS-1] = {
3: .handle_irq = handle_bad_irq,
4: .depth = 1,
5: .lock = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
6: }
7: };
IRQ相关信息管理的关键点是一个全局数组,每个数组项对应于一个IRQ号。尽管数组使用的是一个体系结构无关的数据类型,但IRQ的最大可能数目是通过一个平台相关的常数NR_IRQ指定的。IA-32连同经典的8256A控制器,提供16个IRQ。如果使用IO-APIC扩展,中断数可以增加到224个。
1: struct irq_desc {
2: struct irq_data irq_data;
3: struct timer_rand_state *timer_rand_state;
4: unsigned int __percpu *kstat_irqs;
5: irq_flow_handler_t handle_irq;
6: #ifdef CONFIG_IRQ_PREFLOW_FASTEOI
7: irq_preflow_handler_t preflow_handler;
8: #endif
9: struct irqaction *action; /* IRQ action list */
10: unsigned int status_use_accessors;
11: unsigned int core_internal_state__do_not_mess_with_it;
12: unsigned int depth; /* nested irq disables */
13: unsigned int wake_depth; /* nested wake enables */
14: unsigned int irq_count; /* For detecting broken IRQs */
15: unsigned long last_unhandled; /* Aging timer for unhandled count */
16: unsigned int irqs_unhandled;
17: raw_spinlock_t lock;
18: struct cpumask *percpu_enabled;
19: #ifdef CONFIG_SMP
20: const struct cpumask *affinity_hint;
21: struct irq_affinity_notify *affinity_notify;
22: #ifdef CONFIG_GENERIC_PENDING_IRQ
23: cpumask_var_t pending_mask;
24: #endif
25: #endif
26: unsigned long threads_oneshot;
27: atomic_t threads_active;
28: wait_queue_head_t wait_for_threads;
29: #ifdef CONFIG_PROC_FS
30: struct proc_dir_entry *dir;
31: #endif
32: struct module *owner;
33: const char *name;
34: } ____cacheline_internodealigned_in_smp;
由于中断处理是高度体系结构相关的,故该数据结构含有大量体系结构相关的内容。对此我们略过。只讨论上层ISR相关的部分。整个中断处理的框架如下图所示,其中irq_chip是IRQ控制器:
软件真正关心的是struct irqaction(中断动作描述符):
1: struct irqaction {
2: irq_handler_t handler;//中断处理函数
3: unsigned long flags;//标志位,request_irq函数介绍
4: void *dev_id;//设备ID,中断共享时标示中断具体属于哪个设备
5: void __percpu *percpu_dev_id;
6: struct irqaction *next;//单链表
7: int irq;//中断号
8: irq_handler_t thread_fn;
9: struct task_struct *thread;
10: unsigned long thread_flags;
11: unsigned long thread_mask;
12: const char *name;//设备名称
13: struct proc_dir_entry *dir;//proc/irq/NN
14: } ____cacheline_internodealigned_in_smp;
中断处理程序是管理硬件的驱动程序的组成部分。如果设备使用中断,那么相应的驱动程序就要注册一个中断处理程序,通过request_irq()函数进行注册。
1: static inline int __must_check
2: request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
3: const char *name, void *dev)
4: typedef irqreturn_t (*irq_handler_t)(int, void *);
第一个参数表示要分配的中断号。对于某些设备,这个值通常是预先确定的。而对于大多数其他设备而言,这个值要么是可以通过探测获取,要么可以通过编程动态确定(PCI总线)。第二个参数指向处理这个中断的实际中断处理程序,只要操作系统已接收到中断,该函数就调用。第三个标志参数(~/include/linux/interrupt.h),重要的有如下几个:
IRQF_DISABLED,该标志设置后,在中断处理工程中,禁用其他的所用中断。
IRQF_SAMPLE_RANDOM,表明该设备产生的中断对于内核熵值有贡献
IRQF_TIMER,表明这是一个定时器中断
IRQF_SHARED,表明在多个中断程序之间共享中断线。
第四个参数,用于/proc/irq和/proc/interrupts文件中显示名称。第五个参数,共享中断线时使用。函数成功时返回0,非0为函数未注册成功,中断处理程序未注册。
1: void free_irq(unsigned int, void *);
卸载驱动程序时,需要注销相应的中断处理程序,并释放中断线。如果指定的中断线不是共享的,那么,该函数删除处理程序的同时将禁用这条中断线。如果中断线时共享的,则仅仅删除dev所对应的处理程序,而这条中断线本身只用在删除了最后一个处理程序时才禁用。
Linux中的中断处理程序是无须重入的。当一个给定的中断处理程序正在执行时,相应的中断线在所有处理器上都会被屏蔽掉,以防止在同一中断线上接收另一个新的中断。通常情况下,所有其他的中断都是打开的,所以这些不同中断线上的其他中断都能处理,但当前中断线总是被禁止的。由此可以看出,同一个中断处理程序绝对不会被同时调用以处理嵌套的中断。(通过irq_des的status字段进行处理,当有中断在进行处理时,该值被设置为IRQ_INPROGRESS,当发现被设置为该标志是,等待)
在实现中断处理例程时,主要的问题是它们在所谓的中断上下文中执行。中断上下文与普通上下文的不同之处主要有3点:
1),中断是异步执行的。因而从用户空间来看,处理程序例程并不是在一个明确定义的环境中执行(禁止访问用户空间)。
2),中断上下文中不能调用调度器
3),中断处理程序不能进入睡眠状态。
中断控制
Linux内核提供了一组接口用于操作机器上的中断状态,控制中断系统的原因归根结底是需要提供同步。
local_irq_save() //禁止本地中断
local_irq_restore() //激活中断
disable_irq() //中断指定的一条中断线
enable_irq() //激活指定的中断线
实例()
1: static int e100_up(struct nic *nic)
2: {
3: int err;
4:
5: if ((err = e100_rx_alloc_list(nic)))
6: return err;
7: if ((err = e100_alloc_cbs(nic)))
8: goto err_rx_clean_list;
9: if ((err = e100_hw_init(nic)))
10: goto err_clean_cbs;
11: e100_set_multicast_list(nic->netdev);
12: e100_start_receiver(nic, NULL);
13: mod_timer(&nic->watchdog, jiffies);
14: if ((err =
16: goto err_no_irq;
17: netif_wake_queue(nic->netdev);
18: napi_enable(&nic->napi);
19: /* enable ints _after_ enabling poll, preventing a race between
20: * disable ints+schedule */
21: e100_enable_irq(nic);
22: return 0;
23:
24: err_no_irq:
25: del_timer_sync(&nic->watchdog);
26: err_clean_cbs:
27: e100_clean_cbs(nic);
28: err_rx_clean_list:
29: e100_rx_clean_list(nic);
30: return err;
31:
1: static void e100_down(struct nic *nic)
2: {
3: /* wait here for poll to complete */
4: napi_disable(&nic->napi);
5: netif_stop_queue(nic->netdev);
6: e100_hw_reset(nic);
7:
8: del_timer_sync(&nic->watchdog);
9: netif_carrier_off(nic->netdev);
10: e100_clean_cbs(nic);
11: e100_rx_clean_list(nic);
12: }
1: static irqreturn_t e100_intr(int irq, void *dev_id)
2: {
3: struct net_device *netdev = dev_id;
4: struct nic *nic = netdev_priv(netdev);
5: u8 stat_ack = ioread8(&nic->csr->scb.stat_ack);
6:
7: netif_printk(nic, intr, KERN_DEBUG, nic->netdev,
8: "stat_ack = 0x%02X\n", stat_ack);
9:
10: if (stat_ack == stat_ack_not_ours || /* Not our interrupt */
11: stat_ack == stat_ack_not_present) /* Hardware is ejected */
12:
13:
14: /* Ack interrupt(s) */
15: iowrite8(stat_ack, &nic->csr->scb.stat_ack);
16:
17: /* We hit Receive No Resource (RNR); restart RU after cleaning */
18: if (stat_ack & stat_ack_rnr)
19: nic->ru_running = RU_SUSPENDED;
20:
21: if (likely(napi_schedule_prep(&nic->napi))) {
22:
24: }
25:
26: return IRQ_HANDLED;
27: }