linux内核-中断请求队列的初始化

在前一篇博客中,我们讲到中断向量表(更确切地,应该说中段描述表)IDT有两种表项,一种是为保留专用于CPU本身的中断门,主要用于由CPU产生到的异常,如除数为0、页面错等等,以后由用户程序通过INT指令产生的中断(或称陷阱),主要用来产生系统调用(另外还有个用于debug的INT 3)。这些中断门的向量除用于系统调用的0x80外都在0x20以下。从0x20开始就是第二种表项,共224项,都是用于外设的通用中断门。这二者的区别在于通用中断门可以为多个中断源所共享,而专用中断门则是为特定的中断源所专用。

由于通用中断门是让多个中断源共享,而且允许这种共用的结构在系统运行的过程中动态地变化,所以在IDT的初始化阶段只是为每个中断向量,也即每个表项准备下一个中断请求队列,从而形成一个中断请求队列的数组,这就是数组irq_desc。中断请求队列头部的数据结构时在include/linux/irq.h中定义的:

/*
 * Interrupt controller descriptor. This is all we need
 * to describe about the low-level hardware. 
 */
struct hw_interrupt_type {
	const char * typename;
	unsigned int (*startup)(unsigned int irq);
	void (*shutdown)(unsigned int irq);
	void (*enable)(unsigned int irq);
	void (*disable)(unsigned int irq);
	void (*ack)(unsigned int irq);
	void (*end)(unsigned int irq);
	void (*set_affinity)(unsigned int irq, unsigned long mask);
};

typedef struct hw_interrupt_type  hw_irq_controller;

/*
 * This is the "IRQ descriptor", which contains various information
 * about the irq, including what kind of hardware handling it has,
 * whether it is disabled etc etc.
 *
 * Pad this out to 32 bytes for cache and indexing reasons.
 */
typedef struct {
	unsigned int status;		/* IRQ status */
	hw_irq_controller *handler;
	struct irqaction *action;	/* IRQ action list */
	unsigned int depth;		/* nested irq disables */
	spinlock_t lock;
} ____cacheline_aligned irq_desc_t;

extern irq_desc_t irq_desc [NR_IRQS];

每个队列头部中除指针action用来维持一个由中断服务程序描述项构成的单链表队列中,还有个指针handler指向另一个数据结构,即hw_irq_controller数据结构。那里主要是一些函数指针,用于该队列,或者说该共用中断通道的控制(而并不是对具体中断源的服务)。具体的函数则取决于所用的中断控制器(通常是i8259A)。例如,函数指针enable和disable用来开启和关闭其所属的通道,ack用于对中断控制器的响应。而end则用于每次中断服务返回的前夕。这些函数都是在init_IRQ中调用init_ISA_irqs设置好的,如下:



void __init init_ISA_irqs (void)
{
	int i;

	init_8259A(0);

	for (i = 0; i < NR_IRQS; i++) {
		irq_desc[i].status = IRQ_DISABLED;
		irq_desc[i].action = 0;
		irq_desc[i].depth = 1;

		if (i < 16) {
			/*
			 * 16 old-style INTA-cycle interrupts:
			 */
			irq_desc[i].handler = &i8259A_irq_type;
		} else {
			/*
			 * 'high' PCI IRQs filled in on demand
			 */
			irq_desc[i].handler = &no_irq_type;
		}
	}
}

程序先调用init_8259A对8259A中断控制器进行初始化,然后将开头16个中断请求队列的handler指针设置成指向数据结构i8259A_irq_type,定义入下:

static struct hw_interrupt_type i8259A_irq_type = {
	"XT-PIC",
	startup_8259A_irq,
	shutdown_8259A_irq,
	enable_8259A_irq,
	disable_8259A_irq,
	mask_and_ack_8259A,
	end_8259A_irq,
	NULL
};

用于具体中断服务程序描述项的数据结构irqaction,定义如下:

struct irqaction {
	void (*handler)(int, void *, struct pt_regs *);
	unsigned long flags;
	unsigned long mask;
	const char *name;
	void *dev_id;
	struct irqaction *next;
};

其中最主要的就是函数指针handler,指向具体的中断服务程序。

在IDT表的初始化完成之初,每个中断服务队列都是空的。此时即使打开中断并且某个外设中断真的发生了,也得不到实际的服务。虽然从中断源的硬件以及中断控制器的角度来看似乎已经得到服务了,因为形式上CPU确实通过中断门进入了某个中断向量的总服务程序,例如IRQ0x01_interrupt,并且按要求执行了对中断控制器的ack以及end,然后执行iret指令从中断返回。但是,从逻辑的角度、功能的角度来看,则其实并没有得到实质的服务,因为并没有执行具体的中断服务程序。所以,真正的中断服务要到具体设备的初始化程序将其中断服务程序通过request_irq向系统登记,挂入某个中断请求队列以后才会发生。

函数request_irq的代码定义如下:


/**
 *	request_irq - allocate an interrupt line
 *	@irq: Interrupt line to allocate
 *	@handler: Function to be called when the IRQ occurs
 *	@irqflags: Interrupt type flags
 *	@devname: An ascii name for the claiming device
 *	@dev_id: A cookie passed back to the handler function
 *
 *	This call allocates interrupt resources and enables the
 *	interrupt line and IRQ handling. From the point this
 *	call is made your handler function may be invoked. Since
 *	your handler function must clear any interrupt the board 
 *	raises, you must take care both to initialise your hardware
 *	and to set up the interrupt handler in the right order.
 *
 *	Dev_id must be globally unique. Normally the address of the
 *	device data structure is used as the cookie. Since the handler
 *	receives this value it makes sense to use it.
 *
 *	If your interrupt is shared you must pass a non NULL dev_id
 *	as this is required when freeing the interrupt.
 *
 *	Flags:
 *
 *	SA_SHIRQ		Interrupt is shared
 *
 *	SA_INTERRUPT		Disable local interrupts while processing
 *
 *	SA_SAMPLE_RANDOM	The interrupt can be used for entropy
 *
 */
 
int request_irq(unsigned int irq, 
		void (*handler)(int, void *, struct pt_regs *),
		unsigned long irqflags, 
		const char * devname,
		void *dev_id)
{
	int retval;
	struct irqaction * action;

#if 1
	/*
	 * Sanity-check: shared interrupts should REALLY pass in
	 * a real dev-ID, otherwise we'll have trouble later trying
	 * to figure out which interrupt is which (messes up the
	 * interrupt freeing logic etc).
	 */
	if (irqflags & SA_SHIRQ) {
		if (!dev_id)
			printk("Bad boy: %s (at 0x%x) called us without a dev_id!\n", devname, (&irq)[-1]);
	}
#endif

	if (irq >= NR_IRQS)
		return -EINVAL;
	if (!handler)
		return -EINVAL;

	action = (struct irqaction *)
			kmalloc(sizeof(struct irqaction), GFP_KERNEL);
	if (!action)
		return -ENOMEM;

	action->handler = handler;
	action->flags = irqflags;
	action->mask = 0;
	action->name = devname;
	action->next = NULL;
	action->dev_id = dev_id;

	retval = setup_irq(irq, action);
	if (retval)
		kfree(action);
	return retval;
}

参数irq为中断请求队列的序号,也就是人们通常所说的中断请求号,对应于中断控制器中的一个通道,有时候要在接口卡上通过微型开关或跳线来设置。但是要注意,这样的中断请求号与CPU所用的中断号或中断向量是不同的,中断请求号IRQ0相当于中断向量0x20。也许,可以把这种中断请求号看成逻辑中断向量,而后者则为物理中断向量。通常,前16个中断请求通道IRQ0值IRQ15是由中断控制器i8259A控制的。参数irqflags是一些标志位,其中的SA_SHIRQ标志表示与其他中断源公用该中断请求通道。此时必须提供一个非零的dev_id以供区别。当中断发生时,参数dev_id会被作为调用参数传回所指定的服务程序。至于dev_id到底是什么,request_irq和中断服务程序并不在乎,只要各个具体的中断服务程序自己能够辨别和使用即可,所以这里dev_id的类型是void *。而request_irq中则对此进行检查。顺便提一下,printk产生一个出错信息,通常是写入文件/var/log/messages或者屏幕上显示,取决于守护神syslogd和klogd是否已经在运行。这里有趣的是语句中的参数 (&irq)[-1]。这里irq是第一个调用参数,所以是最后压入堆栈的,&irq就是参数irq在堆栈中的位置。那么,在&irq下面是什么呢?那就是函数的返回地址。所以,这个printk语句显示该request_irq函数是从什么地方调用的,使程序员可以根据这个地址发现是哪个函数中调用的。

在分配并设置了一个irqaction数据结构action以后,便调用setup_irq,将其链入相应的中断请求队列。其代码在同一个文件(irq.c)中:


/* this was setup_x86_irq but it seems pretty generic */
int setup_irq(unsigned int irq, struct irqaction * new)
{
	int shared = 0;
	unsigned long flags;
	struct irqaction *old, **p;
	irq_desc_t *desc = irq_desc + irq;

	/*
	 * Some drivers like serial.c use request_irq() heavily,
	 * so we have to be careful not to interfere with a
	 * running system.
	 */
	if (new->flags & SA_SAMPLE_RANDOM) {
		/*
		 * This function might sleep, we want to call it first,
		 * outside of the atomic block.
		 * Yes, this might clear the entropy pool if the wrong
		 * driver is attempted to be loaded, without actually
		 * installing a new handler, but is this really a problem,
		 * only the sysadmin is able to do this.
		 */
		rand_initialize_irq(irq);
	}

	/*
	 * The following block of code has to be executed atomically
	 */
	spin_lock_irqsave(&desc->lock,flags);
	p = &desc->action;
	if ((old = *p) != NULL) {
		/* Can't share interrupts unless both agree to */
		if (!(old->flags & new->flags & SA_SHIRQ)) {
			spin_unlock_irqrestore(&desc->lock,flags);
			return -EBUSY;
		}

		/* add new interrupt at end of irq queue */
		do {
			p = &old->next;
			old = *p;
		} while (old);
		shared = 1;
	}

	*p = new;

	if (!shared) {
		desc->depth = 0;
		desc->status &= ~(IRQ_DISABLED | IRQ_AUTODETECT | IRQ_WAITING);
		desc->handler->startup(irq);
	}
	spin_unlock_irqrestore(&desc->lock,flags);

	register_irq_proc(irq);
	return 0;
}

计算机系统在使用中常常有产生随机数的要求,但是要产生真正的随机数是不可能的(所以由计算机产生的随机数称为伪随机数)。为了达到尽可能的随机,需要在系统的运行中引入一些随机的因素,称为熵(entropy)。由各种中断源产生的中断请求在时间上大多是相当随机的,可以用来作为这样的随机因素。所以linux内核提供了一种手段,使得可以根据中断发生的时间来引入一点随机性。需要在某个中断请求队列,或者说中断请求通道中引入这种随机性时,可以在调用参数irqflags中将标志位SA_SAMPLE_RANDOM设成1。而这里调用的rand_initialize_irq就据此为该中断请求队列初始化一个数据结构,用来记录该中断的时序。

可想而知,对于中断请求队列的操作当然不允许受到干扰,必须要在临界区内进行,不光中断要关闭,还要防止可能来自其他处理器的干扰。代码中986行的spin_lock_irqsave就使CPU进入了这样的临界区。我们将在后面的多处理器SMP结构的博客中介绍和讨论spin_lock_irqsave,与之相对的spin_unlock_irqrestore则是临界区的出口。

对第一个加入队列的irqaction结构的处理比较简单(1003行),不过此时要对队列头部进行一些初始化(1006-1008行),包括调用本队列的startup函数。对于后来加入队列的irqaction结构则要稍加检查,检查的内容为是否允许共用一个中断通道,只有在新加入的结构以及队列中的第一个结构都允许共用时才将其链入队列的尾部。

在内核中,设备驱动程序一般都要通过request_irq向系统登记其中断服务程序。

Supongo que te gusta

Origin blog.csdn.net/guoguangwu/article/details/121062507
Recomendado
Clasificación