LDD读书笔记第十章-中断处理

当硬件需要处理器关注时(如硬盘读取完成,通知可以继续读取其他内容。),产生某个事件通知处理器。这种机制叫做中断。一个中断仅仅是个信号。

准备并口

在没有设定产生中断之前,设备不会产生中断。
对于并口标准来说设置端口2(0x37a:0×378+2,0x27a:0×278+2或者其他)的第四位来启用中断报告,初始化的时候用outb莱斯设置这个位。
中断启用时,每当引脚10(ACK)位的电平发生从底到高改变时,并口就会产生一个中断。

安装中断处理列程

相关函数和定义

1
2
3
4
5
6
7
8
9
10
11
#include <linux/interrupt.h>
int request_irq(unsigned int irq,
		irqreturn_t (*handler)(int, void *,
				 struct pt_regs *),
		unsigned long flags,
		const char *dev_name,
		void *dev_id);
/*函数request_irq函数返回0时表示成功,为负值时表示错误码 返回-EBUSY表示已经有另一个程序占用了中断信号线*/
void free_irq(unsigned int irq, void *dev_id);
int can_request_irq(unsigned int irq, unsigned long flags);
/*can_request_irq用于查询中断线是否可用,注意can_request_irq后request_irq之前可能发生一些事情导致中断线被其他程序占用*/

参数的具体意义如下

unsigned int irq
所请求的中断号
irqreturn_t (*handler)(int, void *, struct pt_regs *)
中断处理函数。
unsigned long flags
中断有关的位掩码选项
const char *dev_name
用来在/proc/interrupts中显示中断拥有者的字符串
void *dev_id
共享中断信号线时他是唯一标示来表示设备,驱动程序也可以使用它指向驱动程序自己的私有数据区。
在flags中设置的位选项
SA_INTERRUPT
表明是快速处理例程
SA_SHIRQ
表示中断可以在设备之间共享
SA_SAMPLE_RANDOM
是否能对/dev/random设备和/dev/urandom设备使用的熵池有贡献。

short_irq中的的例中断申请

1
2
3
4
5
6
7
8
9
10
11
12
13
if (short_irq >= 0) {
	result = request_irq(short_irq, short_interrupt,
				SA_INTERRUPT, "short", NULL);
	if (result) {
		printk(KERN_INFO "short: can't get \
			assigned irq %i\n",short_irq);
		short_irq = -1;
	}else { 
		/* actually enable it -- assume this 
			*is* a parallel port */
		outb(0x10,short_base+2);
	}
}

自动检测irq号

1
2
3
4
5
6
if (short_irq < 0) /* 未指定信号线根据基地址使用默认值*/
	switch(short_base) {
		case 0x378: short_irq = 7;break;
		case 0x278: short_irq = 2;break;
		case 0x3bc: short_irq = 5;break;
}

内核帮助下探测

内核提供了底层设施来探测中断号。它只能在非共享中断模式下工作。
相关函数:

1
2
3
4
5
6
7
#include <linux/interrupt.h>
unsigned long probe_irq_on(void);
/*返回一个未分配中断的掩码,返回值传递给probe_irq_off函数,
此函数执行后驱动程序要安排设备至少产生一次中断*/
int probe_irq_off(unsigned long);
/*请求设备产生中断以后以probe_irq_on的返回值为参数调用。
如果没有中断产生返回0,如果产生了多次中断(产生二义性)返回一个负值。*/

\
注意在probe_irq_on()之后启用中断在probe_irq_off()之前禁用中断,并且在probe_irq_off之后,需要处理设备上待处理的中断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
int count = 0;
do {
	unsigned long mask;
	mask = probe_irq_on( );
	outb_p(0x10,short_base+2); /* enable reporting */
	outb_p(0x00,short_base);
	/* clear the bit */
	outb_p(0xFF,short_base);
	/* set the bit: interrupt! */
	outb_p(0x00,short_base+2); /* disable reporting */
	udelay(5); /* give it some time */
	short_irq = probe_irq_off(mask);
	if (short_irq = = 0) { /* none of them? */
	printk(KERN_INFO "short: no irq reported by probe\n");
	short_irq = -1;
	}
	/*
	* if more than one line has been activated, the 
	*result is negative. We should service the interrup
	*(no need for lpt port) and loop over again. Loop 
	*at most five times, then give up
	*/
} while (short_irq < 0 && count++ < 5);
if (short_irq < 0)
	printk("short: probe failed %i times, 
		giving up\n", count);

DIY探测

DIY探测时测试所有可能的IRQ,来取得真正的IRQ号。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<pre lang="C">
int trials[ ] = {3, 5, 7, 9, 0};
int tried[ ] = {0, 0, 0, 0, 0};
int i, count = 0;
/*
* install the probing handler for all possible lines.
*Remember the result (0 for success, or -EBUSY) in order
*to only free what has been acquired
*/
for (i = 0; trials[i]; i++)
	tried[i] = request_irq(trials[i], short_probing,
				SA_INTERRUPT, "short probe", NULL);
do {
	short_irq = 0; /* none got, yet */
	outb_p(0x10,short_base+2); /* enable */
	outb_p(0x00,short_base);
	outb_p(0xFF,short_base); /* toggle the bit */
	outb_p(0x00,short_base+2); /* disable */
	udelay(5); /* give it some time */
	/* the value has been set by the handler */
	if (short_irq = = 0) { /* none of them? */
		printk(KERN_INFO "short: no irq reported \
					by probe\n");
	}
	/*
	* If more than one line has been activated, 
	*the result is negative. We should service the
	*interrupt (but the lpt portdoesn't need it) and 
	*loop over again. Do it at most 5 times
	*/
	} while (short_irq <=0 && count++ < 5);
	/* end of loop, uninstall the handler */
	for (i = 0; trials[i]; i++)
		if (tried[i] = = 0)
	free_irq(trials[i], NULL);
	if (short_irq < 0)
		printk("short: probe failed %i times, giving \
				up\n", count);

快速和慢速处理例程

顾名思义快速处理例程和慢速处理例程区别是速度。慢速处理例程处理时课要求处理器再次启用中断。
在现代内核中这种区别已经消失。只剩下一个快速中断(使用SA_INTERRUPT标志申请的中断,快速中断执行时当前处理器上的所有其他中断都会被禁止。

实现中断处理例程

中断处理例程的限制:

  • 不能向用户空间发送或接收数据
  • 不能做可能发生休眠的操作
  • 不能调用schdule函数

short中的示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
irqreturn_t short_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
struct timeval tv;
int written;
do_gettimeofday(&tv);
/* Write a 16 byte record. Assume PAGE_SIZE is a multiple of 16 */
written = sprintf((char *)short_head,"%08u.%06u\n",
	(int)(tv.tv_sec % 100000000), (int)(tv.tv_usec));
BUG_ON(written != 16);
short_incr_bp(&short_head, written);
wake_up_interruptible(&short_queue); 
/* awake any reading process */
return IRQ_HANDLED;
}
static inline void short_incr_bp(volatile unsigned long *index, int delta)
{
	unsigned long new = *index + delta;
	barrier( ); /* Don't optimize these two together */
	*index = (new >= (short_buffer + PAGE_SIZE)) ? 		short_buffer : new;
}

处理例程的参数及返回值

函数原型

1
2
irqreturn_t handler(int irq, void * dev_id,
			struct pt_regs *reg)

irq为中断号,
dev_id为一种客户数据类型,一般存储指向设备数据结构的指针
regs包含进入中断代码之前的处理器上下文快照
返回值,用来指明是否真正处理了一个中断。如果发现需要处理返回
IRQ_HANDLED否则为IRQ_NONE
也可以用IRQ_RETVAL来产生返回值
典型用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
static irqreturn_t sample_interrupt(int irq, void *dev_id, 		struct pt_regs *regs)
{
	struct sample_dev *dev = dev_id;
	/* now `dev' points to the right hardware item */
	/* .... */
}
static void sample_open(struct inode *inode, struct file
				*filp)
{
	struct sample_dev *dev = hwinfo + MINOR(inode->i_rdev);
	request_irq(dev->irq, sample_interrupt,
		0 /* flags */, "sample", dev /* dev_id */);
	/*....*/
	return 0;
}
 
<h4>启用和禁用中断</h4>
<h5>禁用单个中断</h5>
<pre lang="C">
void disable_irq(int irq);
void disable_irq_nosync(int irq);/*立即返回*/
void enable_irq(int irq);

禁用中断的时候只有禁用中断的次数和启用中断的次数相同的情况下才再次启用中断。

禁用所有中断
1
2
3
4
void local_irq_save(unsigned long flags);
void local_irq_disable(void);
void local_irq_restore(unsigned long flags);
void local_irq_enable(void);

顶半部和底半部

为了解决工作量和速度的冲突linux通过将中断处理例程分成两部分来解决这个问题。
顶半部:是实际响应的处理例程也就是用request_irq注册的例程
底半部:是一个被顶半部调度,并在稍候更安全的时间内延迟执行的例程。
典型操作是顶半部保存设备的数据导一个设备特定的缓冲区并调度底半部,然后退出。
稍候底半部会执行其他必要的操作。
底半部的调度方法有两种分别是tasklet和工作队列
tasklet速度块但是所有操作必须是原子的。
工作队列会有更长的延迟但是它可以休眠。
tasklet和工作队列的更多资料查看第五章
并发和竞态(一)
并发和竞态(二)

中断共享

中断共享也是通过request_irq安装的,但是有两处不同:

  • 必须指定flages参数的SA_SHIRQ位
  • dev_id参数必须是唯一的

如果满足下列条件之一request_irq就会成功。

  • 中断信号线空闲
  • 任何已经注册了该信号线的处理例程也标示了是共享的

当共享的信号线中出现信号时内核会用各自的dev_id调用各个中断处理例程。
所以驱动程序必须判断是否是自己的中断。如果不是需要迅速退出。
shrot当中的范例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
irqreturn_t short_sh_interrupt(int irq, void *dev_id, 
					struct pt_regs *regs)
{
	int value, written;
	struct timeval tv;
	/* If it wasn't short, return immediately */
	value = inb(short_base);
	if (!(value & 0x80))
		return IRQ_NONE;
	/* clear the interrupting bit */
	outb(value & 0x7F, short_base);
	/* the rest is unchanged */
	do_gettimeofday(&tv);
	written = sprintf((char *)short_head,"%08u.%06u\n",
		(int)(tv.tv_sec % 100000000), (int)(tv.tv_usec));
	short_incr_bp(&short_head, written);
	wake_up_interruptible(&short_queue); 
	/* awake any 	reading process */
	return IRQ_HANDLED;
}

中断驱动的I/O

如果与驱动程序管理的硬件之间的数据传输有延迟。驱动程序应该该实现缓冲。
一个输入缓冲区在中断时间内被填充,并由读取该设备的进程取走缓冲区内的数据。
一个输出缓冲区由写入设备的进程填充,在中断时间内被取走。
要正确进行中断驱动的数据传输,则要求设备应该能按照下面的语意来产生中断。

你可以继续围观

  • LDD读书笔记第七章-时间、延迟及延缓操作
    度量时间差 在linux/param.h中定义HZ值,表示每秒发生时钟中断的频率。 内核时钟计数器:jiffies_64。 内核使用jiffies变量是unsigned log类型。 可能是jiffies_64的值也可能是其底32位。 使用jiffies计数器 头文件:linux/jif...
  • 《深入理解linux内核》边读边说-内存寻址(一)
    内存地址 内存地址有三种类型:逻辑地址,线性地址,物理地址。 逻辑地址:常见程序所表示的地址就是逻辑地址。比如MS-DOS程序设计时使用DS:EAX,CS:IP等地址都是逻辑地址。此类地址把地址分为段偏移的模式来把程序分为若干段。在逻辑地址层面上可以把内存看为若干个段来分开的空间。 线性地址:...

发表评论