尽管device的IO region可以用来控制device,但是还不够。一个device,往往会有外界有交互,当外界发生了某个事件,需要device做出某种响应,driver也需要做处理。CPU不可能一直等device的某个event,而应该在event发生时,由device通知CPU。因此那就要有一种机制,让device能够通知到driver,这就是中断的作用。

所谓中断,就是硬件在需要CPU做出反应的时候发出的signal,kernel处理硬件中断的方式方法和user space处理signal是类似的。

driver能做的,其实就是实现一个interrupt handler,并注册给kernel,当device中断产生时,handler能够正确处理这个中断就可以了。在讲中断之前,需要强调一点,interrupt handler是和其他的code并发运行的,因此handler里一定要考虑竞争和并发的问题。

10.1. Preparing the Parallel Port


准备并口设备这里不讲了,因为没有并口设备。

10.2. Installing an Interrupt Handler


如果想在device driver中接收并处理设备产生的中断,得需要device driver告诉kernel怎么把中断传递给driver。如果没有匹配的handler,设备的中断会被忽略。

中断线(interrupt line)是kernel里很宝贵又很稀有的资源,一共只有15/16个,driver在使用中断之前要申请中断线资源,使用完后释放。在很多情况下,kernel中有些module driver可能要与别的drivershare同样的中断线。

获取中断资源的函数:

<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);void free_irq(unsigned int irq, void *dev_id);

request_irq的返回值,如果是0,表明成功,如果返回负值,说明获取irq失败。下面是参数的说明:

unsigned int irq : driver请求的设备中断号

irqreturn_t (*handler)(int, void *, struct pt_regs *) : 中断发生时用来处理中断的handler

unsigned long flags  : 和中断管理相关的一些bitmask,包含SA_INTERRUPT,SA_SHIRQ,SA_SAMPLE_RANDOM,在下面会详细说明。

const char *dev_name  : 中断的owner,设备名字,在/proc/interrupts下会显示。

void *dev_id : 对于shared  interrupt line有用。一般是device的structure,或者一些private data,用来表明是谁的中断。如果不是shared interrupt line,可以设为NULL。

关于参数flags,有SA_INTERRUPT,SA_SHIRQ,SA_SAMPLE_RANDOM这几种:

SA_INTERRUPT: 如果设这个flag,说明是一个Fast的handler,Fast的handler运行的时候会关闭当前CPU的中断。

SA_SHIRQ:如果设这个flag,说明中断可以在设备之间share。

SA_SAMPLE_RANDOM: 如果你的device产生的中断比较随机,那么可以设这个flag,这样的话,OS上层需要产生随机数的时候,就可以把你的中断作为参考因素之一。

interrupt的handler可以在module driver初始化的时候注册,也可以在device被open的时候注册。LDD3推荐后者,也就是在device被open的时候才注册,因为中断线是有限的资源,有些device driver初始化的时候注册,但是后续可能并不会使用,这就造成了浪费,因此推荐在driver被open的时候才去请求中断,并注册handler。

一般来说,在device被open,但是还没有让device产生中断之前request_irq,在所有的device都被close之后再free_irq,这种方式的缺点在于,需要device driver自己保存device的计数,来确定什么时候关闭了所有的device。

一个使用request_irq的例子:

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);}
}

有些架构比如X86,有一个query irq是否可用的接口:

int can_request_irq(unsigned int irq, unsigned long flags);

如果irq当前没有人使用,就返回非零值。

10.2.1. The /proc Interface

每当hardware产生中断,并被CPU接收时,kernel就会把中断计数加1,这个计数可以用来check hardware是否正常工作。中断的计数可以通过/proc/interrupts来读取。另外,/proc/stat也可以出去interrupt的数量,区别在于stat里是自系统起来获取的中断数;而/proc/interrupts只统计当前active的中断数。比如这里有一个读取中断计数的例子:

root@montalcino:/bike/corbet/write/ldd3/src/short# m /proc/interruptsCPU0       CPU1       0:    4848108         34    IO-APIC-edge  timer2:          0          0          XT-PIC  cascade8:          3          1    IO-APIC-edge  rtc10:       4335          1   IO-APIC-level  aic7xxx11:       8903          0   IO-APIC-level  uhci_hcd12:         49          1    IO-APIC-edge  i8042
NMI:          0          0
LOC:    4848187    4848186
ERR:          0
MIS:          0

第一类是中断数IRQ number。从上面可以看出,CPU0一直在处理中断,而CPU1就很少,因为在同一个CPU上处理中断容易缓存命中,有助于提高performance。最后两列,第一个是interrupt controller,以及注册这个中断的设备名字。

从/proc/stat读取的中断信息:

intr 5167833 5154006 2 0 2 4907 0 2 68 4 0 4406 9291 50 0 0

第一个值表示所有中断的总和,后面的每一个都代表一个interrupt line(从0开始)。这里统计的是所有曾经产生的中断,即便你的device被关闭了,这里仍然可以看到它之前产生的中断,因此有时候,/proc/stat里的信息要比/proc/interrupts里的更有参考意义。

这两个文件的 另一个区别是:interrupts是与架构无关的,而stat是架构相关的。系统中可用的IRQ line根据架构的不同而不同,比如SPARC上只有16个IRQ line,而在IA-64上则有256个IRQ line。

下面这个是在IA-64上的/proc/interrupts的输出实例:

           CPU0       CPU1       27:       1705      34141  IO-SAPIC-level  qla128040:          0          0           SAPIC  perfmon43:        913       6960  IO-SAPIC-level  eth047:      26722        146  IO-SAPIC-level  usb-uhci64:          3          6   IO-SAPIC-edge  ide080:          4          2   IO-SAPIC-edge  keyboard89:          0          0   IO-SAPIC-edge  PS/2 Mouse
239:    5606341    5606052           SAPIC  timer
254:      67575      52815           SAPIC  IPI
NMI:          0          0
ERR:          0

10.2.2. Autodetecting the IRQ Number

因为driver必须要request irq,但是怎么知道该用哪个irq number呢?答案是auto detect。

某些设备可以按照经验值先指定中断号,然后尝试获取。比如:

if (short_irq < 0) /* not yet specified: force the default on */switch(short_base) {case 0x378: short_irq = 7; break;case 0x278: short_irq = 2; break;case 0x3bc: short_irq = 5; break;}

有些则是由device本身design的时候就决定使用哪个中断号,然后通过它的I/O port或者PCI configure space,driver可以读到device的中断号,像这类设备,所谓的auto detect就只需要probe device,并不需要做额外的工作来probe interrupt,目前大多数的设别其实都是按照这种方式工作的。比如PCI的标准已经支持这种操作,PCI总线可以知道PCI设备将要使用什么中断。

10.2.2.1 Kernel-assisted probing

对于某些没那么高级的设备,需要driver自己获取device使用的中断好,可以使用kernel提供的一些helper函数。

unsigned long probe_irq_on(void);

这个函数返回当前没有被使用的IRQ number的bitmask。device driver必须保存这个bitmask,并在probe_irq_off的时候传递进去。在调用完这个函数之后,device driver可以安排它的device产生中断了。

int probe_irq_off(unsigned long);

当device已经产生了中断,device driver调用上面的函数,把之前从probe_irq_on拿到的bitmask再传递进去。这个函数返回被probe on的IRQ number。如果没有interrupt被probe on,返回0;如果有多个被probe on(ambiguous detection),返回负值。

device driver需要在调用probe_irq_on之后才enable device的中断,在调用probe_irq_off之前就要disable device的中断。

这里是一个例子:

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 interrupt (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);

10.2.2.2 Do-it-yourself probing

极少数情况下,需要device driver完全自己probe IRQ。一般的解决办法都是,先enable所有的IRQ number,然后等着,看哪个IRQ number是有中断产生的,那个IRQ number就是自己device的IRQ number。然而,有些情况下,不需要测试所有的IRQ number,只需要测试个别的几个就可以了。比如这个例子,就是假设3,5,7,9里面的一个会被使用:

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 port* doesn'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);// handler
irqreturn_t short_probing(int irq, void *dev_id, struct pt_regs *regs)
{if (short_irq =  = 0) short_irq = irq;    /* found */if (short_irq != irq) short_irq = -irq; /* ambiguous */return IRQ_HANDLED;
}

当然,如果你事先不知道到底哪些IRQ number会被使用,你只能从0到NR_IRQS-1,这样一个一个去试一试了。

10.2.3. Fast and Slow Handlers

就是字面的意思,有的handler处理的快,有的处理的慢,快的handler就是fast handler,慢的handler就是slow handler。

fast handler因为很快处理完,所以在处理期间保持当前CPU中断关闭就可以;如果是slow handler,就要注意了,不能一直关闭中断,因为很多别的device也要产生中断并等待CPU处理,slow的handler可能会导致这些device等待时间过长。在现在的kernel里,几乎没有fast和slow的明显界限,简单说,在处理期间需要关闭当前CPU中断的就是fast handler。

一般的device driver不推荐使用SA_INTERRUPT,这个flag主要给timer的interrupt来用。

10.2.3.1 The internals of interrupt handling on the x86

这里大概讲了一下x86系统上kernel内部中断机制的实现,参考文件:arch/i386/kernel/irq.c, arch/i386/kernel/apic.c, arch/i386/kernel/entry.S, arch/i386/kernel/i8259.c, and include/asm-i386/hw_irq.h。

首先是在entry.S里,这里是kernel中处理中断的最底层,用汇编做的实现。这里主要是把IRQ number压栈,然后跳到do_IRQ(在irq.c里)继续执行。

然后do_IRQ要做的第一步就是搞清楚这个IRQ number,这样后面的interrupt controller才知道怎么处理。先获取这个IRQ number的spinlock,防止别的CPU再处理这个IRQ,然后找到这个IRQ number对应的interrupt handler。如果这个IRQ number没有handler,do_IRQ后面就啥也不干,release这个IRQ number的spinlock,然后处理所有的software中断,再直接返回。

如果这个IRQ number有对应的handler,handle_IRQ_event就会调用它的handler。如果发现这个handler是slow的(没有设置SA_INTERRUPT),hardware的中断会被enable,然后handler被调用,之后就是cleanup,然后处理所有的软件中断,然后该干嘛干嘛去了(比如因为中断产生,需要wake up别的process)。

这里提到了之前probe_irq_on/probe_irq_off的工作原理:当调用了probe_irq_on的时候,kernel会把所有目前没有handler的IRQ number设置IRQ_WAITING,然后device driver打开了device的中断以后,do_IRQ会搜索所有的IRQ nubmber的handler,没有handler的就会把IRQ_WAITING这个flag清掉;probe_irq_off这个函数的功能就简单了,只需要搜索所有不带IRQ_WAITING这个标的就行了,不带这个标的IRQ number,是产生了中断,但是没有注册handler,一般就是device driver要找的那个IRQ number了。

10.3. Implementing a Handler


handler在实现的时候,因为是运行在中断时期,有一些限制条件需要注意,这些限制条件和kernel timer的限制条件类似:

1,不能和user space交换数据,因为运行在interrupt context,而不是process context。

2, 不能调用任何可能导致sleep的函数,比如wait_event,或者不是GFP_ATOMIC的kmalloc。

3, 不能通过semaphore加锁,因为semaphore会导致休眠。

4, handler里不能调用schedule函数,因为会放弃CPU。

中断的handler的角色,就是对设备的状态更新作出反应,比如根据中断的类型,读写数据等。很多device driver可能需要根据device的情况,在handler处理完当前的中断之前,把device的中断关闭,比如设一个寄存器等。

在handler的处理过程中,典型的任务就是唤醒某些等待event的线程,比如设备产生了数据,可能需要唤醒读写数据线程等。需要注意的是,handler占用的时间越少越好,如果中间需要做费时的操作,应该把费时的操作放在tasklet或者workqueue来做,由kernel的thread来负责完成这些操作,而handler应该尽快返回。

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;
}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;
}

以及对应的read/write实现:

ssize_t short_i_read (struct file *filp, char _ _user *buf, size_t count, loff_t *f_pos)
{int count0;DEFINE_WAIT(wait);while (short_head =  = short_tail) {prepare_to_wait(&short_queue, &wait, TASK_INTERRUPTIBLE);if (short_head =  = short_tail)schedule(  );finish_wait(&short_queue, &wait);if (signal_pending (current))  /* a signal arrived */return -ERESTARTSYS; /* tell the fs layer to handle it */} /* count0 is the number of readable data bytes */count0 = short_head - short_tail;if (count0 < 0) /* wrapped */count0 = short_buffer + PAGE_SIZE - short_tail;if (count0 < count) count = count0;if (copy_to_user(buf, (char *)short_tail, count))return -EFAULT;short_incr_bp (&short_tail, count);return count;
}ssize_t short_i_write (struct file *filp, const char _ _user *buf, size_t count,loff_t *f_pos)
{int written = 0, odd = *f_pos & 1;unsigned long port = short_base; /* output to the parallel data latch */void *address = (void *) short_base;if (use_mem) {while (written < count)iowrite8(0xff * ((++written + odd) & 1), address);} else {while (written < count)outb(0xff * ((++written + odd) & 1), port);}*f_pos += count;return written;
}

10.3.1. Handler Arguments and Return Value

irq 的handler有三个参数:irq, dev_id,和 regs。

irq就是注册handler的时候使用的中断号,dev_id就是之前driver注册handler的时候传递的dev_id,这个是void *,一般driver都会传递private data,比如device structure的指针,然后在handler里根据这个data,就能知道是哪个device的interrupt,从而进行处理。比如这样的例子:

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;
}

regs(struct pt_regs *regs)是CPU调handler之前,当前CPU的process context的snapshot,device driver很少使用。

device driver的interrupt handler需要一个返回值,让kernel直到handler的中断处理是否完成。如果handler可以处理,就返回IRQ_HANDLED,否则返回IRQ_NONE。kernel也提供了宏生成返回值:

IRQ_RETVAL(handled)

10.3.2. Enabling and Disabling Interrupts

有时候driver里需要暂时block住interrupt的delivery,比如为了防止死锁,在拿了spinlock之后,device driver可能需要关闭硬件中断,无论是哪种用法,device driver中关闭硬件中断都是很少用的。

10.3.2.1 Disabling a single interrupt

kernel提供了专门的方式来disable、enable中断,再次强调,device driver中很少需要关闭中断,如果这个IRQ number还是share的,那就更加麻烦。

#include <asm/irq.h>
void disable_irq(int irq);
void disable_irq_nosync(int irq);
void enable_irq(int irq);

如果调用了上面的函数,kernel会更新PIC中对应IRQ number的mask,从而把所有CPU上的IRQ number中断打开或者关闭。需要注意的是,调用了多少次disable,就要调用多少次enable才能真正把interrupt enable起来。另外,当IRQ number被disable的时候,如果当前有IRQ number对应的handler正在执行,它会等到handler执行完成才会disable IRQ number。

10.3.2.2 Disabling all interrupts

上面的函数只能关闭某一个中断,下面的函数可以把当前CPU的中断全部关掉。

#include <asm/system.h>
//关闭当前CPU的中断,并保存之前的中断状态到flags里。
void local_irq_save(unsigned long flags);
//关闭当前CPU的中断,但是不保存任何状态。
void local_irq_disable(void);//打开当前CPU的中断,并恢复中断状态位flags。
void local_irq_restore(unsigned long flags);
//无条件打开当前CPU的中断状态。
void local_irq_enable(void);

10.4. Top and Bottom Halves


也就是中断上半部和下半部。

通常driver可能需要在interrupt做很多费时的处理,比如读写数据,但是interrupt handler又不能执行太久,因为hardware的中断在hanlder执行的过程中是关闭的,handler不执行完,中断不会被打开。既要做很多事情,又要很快的做完,本身是冲突的,因此kernel的设计是把整个中断处理过程分为两个部分,也就是上半部和下半部。

上半部就是interrupt handler,也就driver调用request_irq注册给kernel里的部分,是中断发生时由kernel负责调用的部分;而下半部是driver自己的实现,通过上半部的调度来让下半部执行。

上半部和下半部一个很重要的区别是,上半部是关中断的,下半部中断是打开的,这样可以大大缩短中断被关闭的时间,从而提高性能。通常来说,上半部干的事情就是把device产生的数据保存起来,然后把下半部调度起来去处理这些数据,下半部的执行就很自由了,比如唤醒线程,做IO等;这样分开执行的好处是下半部在处理数据的同时,上半部可以继续处理新产生的中断。

下半部的实现方式有两种:tasklet和work queue。tasklet比较快速,但是要求driver的下半部是atomic context,work queue没有这些限制,但是延迟较高。

10.4.1. Tasklets

tasklet是运行在软中断上下文中,并且和其他的thread后者tasklet是并发的关系,所以如果driver有不止一个tasklet,那就要做好竞争条件的处理。

tasklet和调度它的handler是运行在同一个CPU上的,因此只有handler执行完以后,才会调用tasklet。但是考虑到tasklet执行的时候还会有新的中断产生,导致handler运行,tasklet和handler之前还是要做好资源保护。

//kernel定义tasklet原型
DECLARE_TASKLET(name, function, data);//LDD3 sample code定义tasklet
void short_do_tasklet(unsigned long);
DECLARE_TASKLET(short_tasklet, short_do_tasklet, 0);

这里是LDD3 sample的interrupt handler:

irqreturn_t short_tl_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{do_gettimeofday((struct timeval *) tv_head); /* cast to stop 'volatile' warning */short_incr_tv(&tv_head);tasklet_schedule(&short_tasklet);short_wq_count++; /* record that an interrupt arrived */return IRQ_HANDLED;
}

可以看到,interrupt handler里会调度tasklet,tasklet就会执行short_do_tasklet这个函数:

void short_do_tasklet (unsigned long unused)
{int savecount = short_wq_count, written;short_wq_count = 0; /* we have already been removed from the queue *//** The bottom half reads the tv array, filled by the top half,* and prints it to the circular text buffer, which is then consumed* by reading processes*//* First write the number of interrupts that occurred before this bh */written = sprintf((char *)short_head,"bh after %6i\n",savecount);short_incr_bp(&short_head, written);/** Then, write the time values. Write exactly 16 bytes at a time,* so it aligns with PAGE_SIZE*/do {written = sprintf((char *)short_head,"%08u.%06u\n",(int)(tv_tail->tv_sec % 100000000),(int)(tv_tail->tv_usec));short_incr_bp(&short_head, written);short_incr_tv(&tv_tail);} while (tv_tail != tv_head);wake_up_interruptible(&short_queue); /* awake any reading process */
}

10.4.2. Workqueues

workqueue运行在kernel一个特殊的context里, 不是普通的process context,所以workqueue里可以休眠,但是不能访问user space,因为不是process context,就没有对应的user space。

这里是一个使用work queue的例子:

static struct work_struct short_wq;/* this line is in short_init(  ) */INIT_WORK(&short_wq, (void (*)(void *)) short_do_tasklet, NULL);

注意,这个work queue的sample里,work struct里的function仍然是short_do_tasklet。schedule的实现:

irqreturn_t short_wq_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{/* Grab the current time information. */do_gettimeofday((struct timeval *) tv_head);short_incr_tv(&tv_head);/* Queue the bh. Don't worry about multiple enqueueing */schedule_work(&short_wq);short_wq_count++; /* record that an interrupt arrived */return IRQ_HANDLED;
}

从code上看,workqueue的实现和tasklet的实现差别很小,主要就是schedule的方式不一样,这里使用了schedule_work,而不是schdule_tasklet。

10.5. Interrupt Sharing


在最开始的时候,IRQ line是不能share的。后来,现代的硬件都支持IRQ line share,这样同一个IRQ line可以支持多个device的中断。大多数情况下,share IRQ line比较容易。

10.5.1. Installing a Shared Handler

shared interrupt的申请也是通过request_irq,只是和之前的方式有一些区别:

1. SA_SHIRQ这个bit在request_irq的flag中一定要设置。

2. dev_id这个pointer一定是unique的,但是一定不能是NULL。

kernel保存了share interrupt的所有handlers,并且拿dev_id作为区分各个handler的签名,因此这个值必须是global 唯一的。当满足以下两个条件时,request_irq会成功:

1. 这个interrupt line目前没有人使用。

2. interrupt line已经被人使用,但是别人也设置了share。

等被share的interrupt line发生了中断时,kernel会loop调用这个interrupt line上所有的handler,并且把他们对应的dev_id传递过去。当handler被调用时,需要检查是否是自己的device发生了中断,如果不是,handler必须返回IRQ_NONE。

如果handler需要从kernel里移除,仍然调用free_irq即可,kernel会根据传递进来的dev_id把对应的handler移除,因此dev_id必须是唯一的。

对于share的IRQ,有一点要注意,因为不止一个device在使用这个IRQ line,所以任何一个device driver都不要轻易的disable某个IRQ,否则对别的device可能产生比较大的影响。

10.5.2. Running the Handler

这里是一个例子:

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;
}

所有share IRQ的handler都会被调用,当handler被调用时,要自己判断是不是自己的device产生的中断,上面的例子是通过检查base address读出来的值是否有0x80来实现的。

真正的driver仍然需要把中断处理拆分为上半部和下半部,share IRQ对此没有影响。而且,driver可以使用dev_id来帮助判断是否是自己的device产生的中断。

10.5.3. The /proc Interface and Shared Interrupts

share interrupt对/proc/stat的输出没有影响,但是对/proc/interrupts是有影响的,看一下/proc/interrupts:

           CPU0       0:  892335412         XT-PIC  timer1:     453971         XT-PIC  i80422:          0         XT-PIC  cascade5:          0         XT-PIC  libata, ehci_hcd8:          0         XT-PIC  rtc9:          0         XT-PIC  acpi10:   11365067         XT-PIC  ide2, uhci_hcd, uhci_hcd, SysKonnect SK-98xx, EMU10K111:    4391962         XT-PIC  uhci_hcd, uhci_hcd12:        224         XT-PIC  i804214:    2787721         XT-PIC  ide015:     203048         XT-PIC  ide1
NMI:      41234
LOC:  892193503
ERR:        102
MIS:          0

可以看到,凡是share的IRQ,在最后一列会列出来哪些handler share了这个interrupt,从上面的输出就能看出来IRQ10,11等都是被share的IRQ。

10.6. Interrupt-Driven I/O


当user mode和device的读写交互可能发生延迟时,应该使用buffer。buffer的好处在于把read、write系统调用解放出来,不用block在数据交互的过程中,这样可以提高performance。

所谓interrupt-driver I/O,意思就是在interrupt发生时,发生的I/O操作。比如input buffer在中断发生时被填满,当user process把数据读走时被清空;output buffer在中断发生时被device读走清空,在user process在写时被填满。

为了实现上述的步骤,硬件应该按照下面的语法产生中断:

1, 对于input data,当device有新的数据时,要能够产生中断通知CPU。实际的行为取决于device使用了IO port,memory mapping,还是DMA。

2, 对于output data,当device准备好从buffer中读数据或者完成了data transfer时产生中断,通常只有memory mapping和DMA会在buffer传输完成之后产生中断。

10.6.1. A Write-Buffering Example

这里是一个例子:

    while (written < count) {/* Hang out until some buffer space is available. */space = shortp_out_space(  );if (space <= 0) {if (wait_event_interruptible(shortp_out_queue,(space = shortp_out_space(  )) > 0))goto out;}/* Move data into the buffer. */if ((space + written) > count)space = count - written;if (copy_from_user((char *) shortp_out_head, buf, space)) {up(&shortp_out_sem);return -EFAULT;}shortp_incr_out_bp(&shortp_out_head, space);buf += space;written += space;/* If no output is active, make it active. */spin_lock_irqsave(&shortp_out_lock, flags);if (! shortp_output_active)shortp_start_output(  );spin_unlock_irqrestore(&shortp_out_lock, flags);}out:*f_pos += written;
static void shortp_start_output(void)
{if (shortp_output_active) /* Should never happen */return;/* Set up our 'missed interrupt' timer */shortp_output_active = 1;shortp_timer.expires = jiffies + TIMEOUT;add_timer(&shortp_timer);/*  And get the process going. */queue_work(shortp_workqueue, &shortp_work);
}

Interrupt Handling [LDD3 10]相关推荐

  1. ARM通用中断控制器GIC之中断处理状态机 Interrupt handling state machine

    中断有四种状态:inactive,pending,active 和active and pending.而产生中断的方式有两种,一种是通过写pending寄存器,让中断进入pending状态,可以忽略 ...

  2. 21英里法则_穿着自动鞋带走一英里

    21英里法则 From owning my own mobile phone at age 11, I got used to charging my phone, as we all are acc ...

  3. 21英里法则_一英里的跑道将带您到任何地方

    21英里法则 Did anyone else go through a phase when they were young when they wanted to become a pilot? I ...

  4. Linux内核深入理解中断和异常(8):串口驱动程序

    Linux内核深入理解中断和异常(8):串口驱动程序 rtoax 2021年3月 /*** start_kernel()->setup_arch()->idt_setup_early_tr ...

  5. KVM Interrupt Virtualization

    对于x86架构,KVM为每个虚拟机维护一个pic主中断控制器.一个pic从中断控制器以及一个ioapic控制器(使用pic 加 ioapic 的两种芯片的模拟组合,来控制中断信息),ioapic根据自 ...

  6. linux内核tor03,Linux内核x86架构引导协议4(翻译)

    字段名称:    type_of_loader 类型:           write (obligatory) 偏移/大小:   0x210/1 协议:    2.00+ 内核加载程序的ID号.这个 ...

  7. 记一次lwip中 遇到 pcb == pcb-next 的pcb死循环debug过程

    amoBBS 阿莫电子论坛 标题: 记一次lwip中 遇到 pcb == pcb->next 的pcb死循环debug过程 [打印本页] 作者: kayatsl 时间: 2013-10-11 1 ...

  8. [ATF]-ATF文档和代码的深度解读

    组件(components) 1. SecureOS的调度器 术语: (路径:services/spd) SPD:Secure Payload Dispatcher opteed:OP-TEE Dis ...

  9. arm Linux 中断管理机制

    关键词:GIC.IAR.EOI.SGI/PPI/SPI.中断映射.中断异常向量.中断上下文.内核中断线程.中断注册. 1.1 ARM支持中断类型 ARM GIC-v2支持三种类型的中断: SGI:软件 ...

最新文章

  1. Hadoop Streaming二次排序
  2. 2014年 第5届 蓝桥杯 Java B组 省赛解析及总结
  3. Python __dict__和vars()
  4. 发布CodeBuild.Net代码自动生成器 V2008 2.01(Vs2008)和架构实例源码Demo
  5. ttc error oracle,ORA-03137: TTC protocol internal error : [12333]错误一例
  6. 前端:QuickJS到底能干什么
  7. 10月31日,面试题小结
  8. 【Html】Html基本标记
  9. 不会写代码也能当程序员?无代码来了,是福还是祸?
  10. 材料成型及控制工程学计算机吗,材料成型及控制工程 硕士以后 工资多少,
  11. 《操作系统》课程设计任务书
  12. python实现根据前序序列和中序序列求二叉树的后序序列
  13. 微信/支付宝扫码支付流程
  14. 【子网划分两个实例】通过子网数来划分子网和通过计算主机数来划分子网
  15. “四通一达”本一家,这家人是如何“承包”中国快递半壁江山的?
  16. 派克宇航获得AVIC涡轮螺旋桨支线飞机MA700的飞控作动系统合同
  17. Hadoop的体系结构
  18. 老板发公告:11月成功程序员脱单,奖15天年假!
  19. 一线城市的游戏建模师大概是什么收入水平?
  20. pandas速学系列四:修改dataframe的六大方法

热门文章

  1. 腾讯云搭载frp服务端-映射本地客户端到外网(小米路由pro内网穿透)
  2. 免费优质的网页设计素材网站推荐
  3. 计算机网络与通信缩读,网络课件子讲稿规范.ppt
  4. C\C++ 输出8位,不足时全补0
  5. 远程计算机用户端口号,如何修改远程桌面端口号(手把手教你修改桌面端口号)...
  6. 白盒测试哪种测试效果好_软件测试白盒测试时需要考虑哪些问题?
  7. 人力外派月入4万的操作方法,唤醒你身边沉睡的财富,建议收藏哦
  8. 慰问敬老院活动方案3
  9. git 报错git-upload-pack 解决方法
  10. 行到水穷处,坐看云起时。忽然明白