精华内容
下载资源
问答
  • 文章目录中断中断上下文中断处理程序中断处理下半部与下半部机制 中断 硬件在需要时候向内核发出信号 硬件 -电信号-> 中断控制器输入引脚 -中断号->处理器 不考虑时钟同步 (异常考虑时钟同步,处理方式和...

    中断

    硬件在需要的时候向内核发出信号
    硬件 -电信号-> 中断控制器输入引脚 -中断号->处理器
    不考虑时钟同步
    (异常考虑时钟同步,处理方式和中断相似)

    中断上下文

    也称原子上下文,与进程无关。不可阻塞

    中断处理程序

    interrupt handler、interrupt service routine, ISR 是驱动程序的一部分
    Linux内核中是C函数
    驱动request_irq() 注册中断处理程序,激活中断线。包含参数:isq 中断号 handler 指针,实际操作函数 flags 标志 name 设备 dev 共享中断线(该函数可能会休眠,因为调用了kmalloc()函数)
    卸载驱动、注销中断处理程序、释放中断线 free_irq(irq, dev)

    中断处理

    分为上半部和下半部,
    上半部:立即执行的任务,简洁迅速。时间敏感、硬件相关、不被其他中断打断的工作 放在中断处理程序
    下半部:允许稍后执行的工作,中断处理程序不执行的工作

    下半部与下半部机制

    下半部:指代中断处理流程中推后处理的部分;由链表组成,不允许打断
    下半部机制:所有推后执行的内核机制。软中断(性能最高, 无顺序保障)、tasklet(同类型tasklet不能同时处理)、任务队列(是进程,可休眠,无顺序保障)

    展开全文
  • 中断挂起的概念: 1. 因为某种原因,中断不能马上执行,所以“挂起”等待。 2. 等程序可以中断,在执行“响应挂起的中断” 比如有高、低级别的中断同时发生,就挂起低级别中断,等高级别中断程序执行完,在执行低...
  • 中断处理程序

    2014-08-20 16:47:17
    1、安装中断处理程序 系统中中断信号线很有限,有时只有15或16根。内核维护了一个类似于I/O端口注册表中断信号线注册表。一个模块可以申请一个中断请求IRQ,处理完以后也可以释放掉它。相关函数: 头文件  ...

    一)、基本概念
    1、安装中断处理程序
    系统中中断信号线很有限,有时只有15或16根。内核维护了一个类似于I/O端口注册表的中断信号线的注册表。一个模块可以申请一个中断请求IRQ,处理完以后也可以释放掉它。相关函数:
    头文件 
     
    原型  1)int request_irq(unsigned int irq, void (*handler)(int, void*, struct pt_regs *), 
    unsigned long flags, const char *device, void *dev_id);
    2)void free_irq(unsigned int irq, void *dev_id); 
    功能 1)申请中断
    2)释放中断 
    返回值 申请中断的函数的返回值为0时表示成功,或者返回一个负的错误码。函数返回-EBUSY通知另一个设备驱动程序已经使用了要申请的中断信号线,这种情况并不常见 
    参数表 unsigned int irq
    中断号。某些平台上Linux中断号到硬件中断号的映射并不是一对一的。
    void (*handler)(int,void *,struct pt_regs *)
    指向要安装的中断处理函数的指针。
    unsigned long flags
    与中断管理有关的各种选项的字节掩码。
    const char *device
    传递给request_irq的字符串,在/proc/interrupts中用于显示中断的拥有者。
    void *dev_id
    共享中断信号线时用于区别的唯一的标志符,类似于C++中的this指针。设备驱动程序可以自由地任意使用dev_id。除非强制使用中断共享,dev_id通常被置为NULL。
    unsigned long flags
    标志位,在flags中可以设置的位是:
    SA_INTERRUPT
    设置该位表示这是一个“快速”中断处理程序;否则就是一个“慢速”中断处理程序。
    SA_SHIRQ
    该位表明中断可以在设备间共享。
    SA_SAMPLE_RANDOM
    该位表明用于中断作于/dev/random和/dev/urandom设备使用熵池(entropy pool)的时候。可以读这些设备返回的真正的随机数,用来帮助应用软件选取用于加密的安全钥匙。这些随机数是从一个熵池中取得的,各种随机事件都会对系统的熵池(无序度)有贡献。需要设备真正随机地产生中断时才需要置上这个标志。

    <!--[if !supportEmptyParas]--> <!--[endif]-->
    中断处理程序可以在驱动程序初始化时或者在设备第一次打开时安装。在init_module函数中申请了一个中断、安装了中断处理程序,会阻碍其它驱动程序使用这个中断,可能形成浪费。
    所以应该在打开设备时调用request_irq申请中断,在关闭设备时调用free_irq释放中断将允许资源有限的共享。该技术的缺点是你必须为每个设备维护一个记录其打开次数的计数器。如果在同一个模块中控制两个以上的设备,那么仅仅使用模块计数器那还不够。
    下面这段代码要申请的中断是short_irq。对这个变量的赋值将在后面再给出,因为它与现在的讨论无关。short_base是使用的并口的I/O基地址;写接口的2号寄存器打开中断报告。
    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 {
    outb(0x10, short_base+2);
    }
    }
    这段代码显示安装的处理程序是个快速中断处理程序(SA_INTERRUPT),不支持中断共享(没有设置SA_SHIRQ),并且对系统熵池无贡献(没有设置SA_SAMPLE_RANDOM)。然后调用outb打开并口的中断报告。
    2、自动检测中断号
    驱动程序初始化时需要确设备要使用哪条中断信号线。驱动程序需要以此来安装正确的处理程序。
    有时自动检测依赖于设备使用的缺省值。例如: 
    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;
    }
    这段代码根据选定的I/O地址来分配中断号,但也允许用户在装载驱动程序时通过调用insmod short short_irq=x来覆盖缺省值。short_base缺省为0x378,因此short_irq缺省为7。
    有时驱动程序可以通过读设备的某个I/O端口的一个状态字节来获得中断号。这时自动检测中断号就是探测设备,不需要额外工作来探测中断。PCI标准就要求外围设备声明要使用的中断信号线。
    有些设备需要自动检测:驱动程序使用设备产生中断,然后观察哪一条中断信号线被激活了。
    自动检测函数在实现时有两种方法:调用内核定义的帮助函数和实现我们自己的版本。
    1)核心帮助下的检测
    主流的内核版本都提供两个函数用于探测中断号。都在头文件 中声明:
    unsigned long probe_irq_on(void);
    函数返回未被分配的中断的位掩码。这个位掩码需要保留并需要它传递给probe_irq_off函数。
    int probe_irq_off(unsigned long);
    这个函数在申请了中断后使用,参数是上一函数获得的中断位掩码;函数将使设备产生中断,并返回“启动探测”后发出的中断次数。没有中断就返回0。产生了多次中断将返回一个负值。
    处理时要在调用probe_irq_on后启动设备,并在调用probe_irq_off后关闭。此外,在调用probe_irq_off之后,不要忘了处理你的设备尚未处理的那些中断。如下例:
    int count=0;
    do {
    unsigned long mask;
    mask=probe_irq_on();
    outb_p(0x10, short_base+2); /* 启动中断报告 */
    outb_p(0x00,short_base); /* 清位 */
    outb_p(0xFF, short_base); /* 置位:中断!*/
    outb_p(0x00, short_base+2); /* 关闭中断报告 */
    short_irq=probe_irq_off(mask);
    if (short_irq==0){ /* 没有探测到中断报告?*/
    printk(KERN_INFO "short: no irq reported by probe/n");
    short_irq=-1;
    }
    /*
    * 如果激活了一个以上的中断,结果就是负的。我们将为中断提供服务(除非是lpt
    * 端口)并且再次进行循环。最多循环5次,然后放弃
    */
    } while (short_irq<0 && count++<5);
    if (short_irq<0)
    printk("short: probe failed %i times, giving up/n",count);
    探测很耗时,最好就只在模块初始化时探测中断信号线一次。
    2)DIY(Do It Yourself自己做)检测
    探测也可以有驱动程序自己较容易地实现。
    实现机制和内核帮助下的检测是一样的,一般情况下,有些中断号已经被占用,只需要探测其它一些中断号。
    例:下面的代码实现自动测试。trials数组列出所有要尝试的中断号,0是该列表的结束标志;trials数组用于记录实际上哪个处理程序被驱动程序注册了。
    int trials[]={3,5,7,9,0};
    int tried[]={0,0,0,0,0};
    int i,count=0;
    /**为所有可能的中断信号线安装探测处理程序。记录下结果(0表示成功,-EBUSY
    *表示失败)以便只释放申请的中断
    */
    for (i=0; trials[i]; i++)
    tried[i]=request_irq(trials[i], short_probing, SA_INTERRUPT, "short probe", NULL);
    do {
    short_irq=0; /*尚末取得中断号 */
    outb_p(0x10, short_base+2); /* 启动 */
    outb_p(0x00, short_base);
    outb_p(0xFF, short_base); /* 置位 */
    outb_p(0x10, short_base+2); /* 关闭 */
    /* 处理程序已经设置了这个值 */
    if (short_irq==0) { /*
    printk(KERN_INFO "short: no irq reported by probe/n");
    }
    /*
    * 如果激活了一个以上的中断,结果就是负的。我们将为中断提供服务(除非是lpt
    * 端口)并且再次进行循环。最多这样做5次
    */
    } while(short_irq<=0 && count++<5);
    /* 循环结束,卸载处理程序 */
    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);
    在事先不知道哪些中断号已经被占用时。就需要探测所有空闲的中断,即从0号中断到NR_IRQS-1号中断,NR_IRQS是在头文件中定义的与平台无关的常数。
    例:处理程序的功能是根据实际接收到的中断号来更新short_irq变量。0表示无,负数表示二义检测。
    void short_probing(int irq, void *dev_id, struct pt_regs *regs)
    {
    if (short_irq == 0) short_irq = irq; /* 找到 */
    if (short_irq != irq) short_irq = -irq; /* 有二义性 */
    }
    3、快速和慢速中断处理
    快速中断处理程序在处理时设置了处理器标志位(IF),表示不允许被中断,这保证了中断的原子处理,而调用慢速中断处理时,其它中断仍可以得到服务。
    但在中断处理前,不管是快速还是慢速中断处理程序,内核都要关闭刚才发出报告的那个中断信号线。当处理程序还在处理上一个中断时,如果设备又发出新的中断,新的中断将会丢失。中断控制器并不缓存被屏蔽的中断,但是处理器会进行缓存,快速中断处理程序运行时会关闭微处理器的中断报告,中断控制器禁止了被服务这个中断。中断处理程序在处理后可以通过调用sti来启动处理器的中断报告,微处理器就会处理被缓存的中断。sti函数是“置中断标志位”处理器指令。慢速处理程序运行时是启动了处理器的中断报告的,但中断控制器也禁止了正在被服务这个中断。
    两种中断处理给内核带来的额外开销也不同。慢速中断处理程序会给内核带来的一些管理开销。因此此较频繁(每秒大于100次)的中断最好由快速中断处理程序为之提供服务。
    4、x86平台上中断处理的内幕
    最底层的中断处理是在头文件irq.h中的声明为宏的一些汇编代码,这些宏在文件irq.h中被扩展。为每个中断声明了三种处理函数:慢速,快速和伪处理函数。
    “伪”处理程序最小,是在没有为中断安装C语言的处理程序时的汇编入口点。它将中断转交给PIC(可编程的中断控制器)设备的同时禁止它。在驱动程序处理完中断信号后调用free_irq时又会重新安装伪处理程序。伪处理程序不会将/proc/stat中的计数器加1。
    在x86上的自动探测依赖于伪处理程序的这种行为。probe_irq_on启动所有的伪中断,而不安装处理程序;probe_irq_off只是简单地检查自调用probe_irq_on以来那些中断被禁止了。
    慢速中断的汇编入口点会将所有寄存器保存到堆栈中,并将数据段(DS和ES处理器寄存器)指向核心地址空间(处理器已经设置了CS寄存器)。然后代码将将中断转交给PIC,禁止在相同的中断信号线上触发新的中断,并发出一条sti指令(set interrupt flag,置中断标志位)。处理器在对中断进行服务时会自动清除该标志位。接着慢速中断处理程序就将中断号和指向处理器寄存器的一个指针传递给do_IRQ,这是一个C函数,由它来调用相应的C语言处理程序。驱动程序传递给中断处理程序参数struct pt_regs *是一个指向存放着各个寄存器的堆栈的指针。
    do_IRQ结束后,会发出cli指令,打开PIC中指定的中断,并调用ret_from_sys_call。最后这个入口点(arch/i386/kernel/entry.S)从堆栈中恢复所有的寄存器,处理所有待处理的下半部处理程序,如果需要的话,重新调度处理器。
    快入口点不同的是,在跳转到C代码之前并不调用sti指令,并且在调用do_fast_IRQ前并不保存所有的机器寄存器。当驱动程序中的处理程序被调用时,regs参数是NULL(空指针,因为寄存器没有保存到堆栈中)并且中断仍被屏蔽。最后,快速中断处理程序会重新打开8259芯片上的所有中断,恢复先前保存的所有寄存器,并且不经过ret_from_sys_call就返回了。待处理的下半部处理程序也不运行。
    5、实现中断处理程序
    处理程序是在中断时间内运行的,它不在任何进程的上下文中执行,就不能向用户空间发送或接受数据。快速中断处理程序,是原子地执行的,当访问共享的数据项时并不需要避免竞争条件。而慢速处理程序不是原子的,在运行慢速处理程序时也能为其它处理程序提供服务。
    中断处理程序的功能就是将有关中断接收的信息反馈给设备,并根据要服务的中断的不同含义相应地对数据进行读写。对于大部分硬件设备第一步通常要先清除接口卡上“中断待处理”位,这样硬件在该位被清除前就不会产生任何中断。而没有“中断待处理”位的设备不需要这一步,如并口。
    典型中断处理程序是唤醒在设备上睡眠的那些进程,比如,新数据到达了。
    老式的帧捕获卡,进程可以通过连续地对设备读来获取一系列的图像;每读一帧后read调用都被阻塞,而新的帧一到达后中断处理程序都会唤醒该进程。。
    不论是快速还是慢速中断处理程序,处理例程的执行时间必须尽可能短。如果要进行长时间的计算,最好使用任务队列。
    short范例使用中断来调用do_gettimeofday并把当前时间打印到大小为一页的循环缓冲区。然后它唤醒所有的读进程。
    void short_interrupt(int irq, void *dev_id, struct pt_regs *regs)
    {
    struct timeval tv;
    do_gettimeofday(&tv);
    /* 写一个16个字节的记录。假设 PAGE_SIZE是16的倍数 */
    short_head += sprintf((char *)short_head,"%08u.%06u/n",
    (int)(tv.tv_sec % 100000000), (int)(tv.tv_usec));
    if (short_head == short_buffer + PAGE_SIZE)
    short_head = short_buffer; /* 绕回来 */
    wake_up_interruptible(&short_queue); /* 唤醒所有的读进程 */
    }
    用来读取在中断时间里填满的缓冲区的节点是/dev/shortint,它内部的实现为中断产生和报告作了特别的处理。每向设备写入一个字节都会产生一个中断;而读设备时则给出每次中断报告的时间。
    如果你将并口插座的第9和第10引脚相连,那么拉高并行数据字节的最高位就可以产生中断。这可以通过向/dev/short0写二进制数据或者向/dv/shortint写入任意数据来实现。
    下面的代码是/dev/shortint的read和write的实现:
    read_write_t short_i_read (struct inode *inode, struct file *filp,char *buf, count_t count)
    {
    int count0;
    while (short_head == short_tail) {
    interruptible_sleep_on(&short_queue);
    if (current->signal & ~current->blocked) /* 有信号到达 */
    return -ERESTARTSYS; /* 通知fs层去处理它 */
    /* 否则,再次循环 */
    }
    /* count0 是可以读进来的数据字节个数 */
    count0 = short_head - short_tail;
    if (count0 < 0) /* wrapped */
    count0 = short_buffer + PAGE_SIZE - short_tail;
    if (count0 < count) count = count0;
    memcpy_tofs(buf, (char *)short_tail, count);
    short_tail += count;
    if (short_tail == short_buffer + PAGE_SIZE)
    short_tail = short_buffer;
    return count;
    }
    read_write_t short_i_write (struct inode *inode, struct file *filp, const char *buf, count_t count)
    {
    int written = 0, odd = filp->f_pos & 1;
    unsigned port = short_base; /* 输出到并口数据锁存器 */
    while (written < count)
    outb(0xff * ((++written + odd) & 1), port);
    filp->f_pos += count;
    return written;
    }
    上面的函数中有三个参数被传给了中断处理函数:irq,dev_id和regs。
    当用一个处理程序来同时对若干个设备进行处理并且使用不同的中断信号线,那么中断号(int irq)就可以用来通知处理程序是哪个设备发出了中断。
    例如,如果驱动程序声明了一个设备结构的数组hwinfo,每个元素都有一个irq域,那么下面的代码可以在中断到达时选取出正确的设备。这段代码的设备前缀是cx。
    static void cx_interrupt(int irq)
    {
    /* “Cxg_Board”是硬件信息的数据类型 */
    Cxg_Board *board; int i;
    for (i=0, board=hwinfo; i>cxg_boards; board++,i++)
    if (board->irq==irq)
    break;
    /* 现在'board' 指向了正确的硬件描述 */
    /* .... */
    }
    第二个参数,void *dev_id,是一种ClientData;是传递给request_irq函数的一个void *类型的指针,并且当中断发生时这个设备ID还会作为参数传回给处理程序。参数dev_id是可以用来处理共享中断,但即使不共享它也很有用。
    假定我们例子中的设备是象下面这样注册它的中断的(这里board->irq是要申请的中断,board是ClientData)
    static void cx_open(struct inode *inode, struct file *filp)
    {
    Cxg_Board *board=hwinfo+MINOR(inode->i_rdev);
    Request_irq(board->irq, cx_interrupt, 0, "cx100", board /* dev_id */);
    /* .... */
    return 0;
    }
    这样处理程序的代码就可以缩减如下:
    static void cx_interrupt(int irq, void *dev_id, struct pt_regs *regs)
    {
    Cxg_Board *board=dev_id;
    /* 现在'board' 指向了正确的硬件项 */
    /* .... */
    }
    参数struct pt_regs *regs,很少使用。它存放着在处理器进入中断代码前的一个处理器上下文的快照。这些寄存器可用于监控和调试,show_regs函数(按下RightAlt-PrScr键时由键盘中断启动的调试函数就是使用它们来实现监控和调试的。
    6、打开和禁止中断
    有时驱动程序要打开和禁止它相应IRQ信号的中断报告。内核为此提供了两个函数,都在头文件中声明:
    void disable_irq(int irq);
    void enable_irq(int irq);
    调用其中任一函数都会更新PIC中对指定的irq的掩码。
    当中断被禁止后,处理器将得不到中断报告。
    但要注意的是,因为处理程序本身无法打开和禁止中断信号。内核在调用处理程序前会禁止中断,而在处理程序结束后又会重新打开它。但打开和禁止中断仍可以做到,只要在下半部处理程序中作就可以了。
    7、使用/proc查看
    当处理器被硬件中断时,一个内部计数器会被加1,这为检查设备是否正常工作提供了一个方法。报告的中断显示在文件/proc/interrupts中。下面是该文件的一个快照:
    0: 537598 timer
    1: 23070 keyboard
    2: 0 cascade
    3: 7930 + serial
    5: 4568 NE2000
    7: 15920 + short
    13: 0 math error
    14: 48163 + ide0
    15: 1278 + ide1
    第一列是IRQ中断号。该文件只显示已经安装了驱动程序的那些中断。出现在各记录中的加号标志该行中断采用了快速中断处理程序。
    /proc树中另一个与中断有关的文件是/proc/stat;这个文件记录了系统活动的一些底层的统计信息,包括系统启动以来接收到的中断次数。
    stat文件的每一行都以一个字符串表示的关键字开始;其中intr标记表示中断相关记录,例如:
    intr 947102 540971 23346 0 8795 4907 4568 0 15920 0 0 0 0 0 0 48317 1278
    第一个数(947102)是总的中断次数,后面16个数字表示0~15共16个中断各自的使用次数。
    interrupts文件与体系结构无关,而stat文件则与体系结构有关:其字段的个数取决于内核之下的硬件。比如在Atari(M68k处理器)上则中断号可以多达72个。
    <!--[if !supportEmptyParas]--> <!--[endif]-->
    (二)关于下半部
    Linux将中断处理程序分成两部分:“上半部”即request_irq函数注册的处理例程,“下半部”则是由上半部调度到以后在更安全的时间内执行的那部分例程,这种机制有助于处理程序中比较耗时的任务。
    两部分处理程序最大的不同就在于在执行bh时所有的中断都是打开的。典型的情况是,上半部处理程序将设备数据存放进一个设备指定的缓冲区,再标记它的下半部,然后退出;这样处理得就非常快。由bh将新到的数据再分派给各个进程,必要时再唤醒它们。这种设置允许上半部处理程序在下半部还在运行时就能为新的中断提供服务。但是,如果在上半部处理程序结束前有新的数据到了,由于中断控制器禁止了中断信号,这些数据仍会丢失。
    所有实际的中断处理程序都作了这样的划分。如,当网络接口卡报告新的数据包到达了,处理程序只是取得数据并将它推进协议层中;对数据包的实际处理是在下半部中完成的。
    实际上,任务队列就是从下半部的一个较老的实现演变而来的。与动态的任务队列不同,下半部的个数有限,并由内核预定义了;下半部的静态特性并不是个问题,因为有些下半部可以通过运行任务队列演变为动态对象。在头文件 中,你可以看到下半部的一张列表; 
    1、下半部的设计
    下半部由一个函数指针数组和一个位掩码组成,它们不超过32个。当内核准备处理异步事件时,它就调用do_bottom_half,如从系统调用返回和退出慢速处理程序时;而这两类事件都发生得很频繁。而使用掩码主要出于性能的考虑。
    当代码需要调度运行下半部处理时,只要调用mark_bh,该函数设置了掩码变量的一个位以将相应的函数进入执行队列。下半部可以由中断处理程序或其它函数来调度。执行下半部时,它会自动去除标记。
    标记下半部的函数是在头文件 中定义的:
    void mark_bh(int nr);
    nr是激活的bh的“数目”。这个数是在头文件 中定义的一个符号常数,它标记位掩码中要设置哪个位。每个下半部bh相应的处理函数由拥有它的那个驱动程序提供。例如,当调用mark_bh(KEYBOARD_BH)时,要调度执行的函数是kbd_bh,它是键盘驱动程序的一部分。
    因为下半部是静态对象,模块化的驱动程序无法注册自己的下半部。但此时可以使用立即队列。
    一些比较常见的下半部:
    IMMEDIATE_BH
    对设备驱动程序来说这是最重要的bh。被调度执行的函数处理任务队列tq_immediate。没有下半部的驱动程序可以通过使用立即队列来取得和tq_immediate同样的效果。将任务等记到队列中后,驱动程序必须标记bh以使得它的代码真正得到执行; 
    TQUEUE_BH
    如果任务登记在tq_timer队列中,每次时钟都会激活这个bh。驱动程序也可以使用tq_timer来实现自己的下半部;但不必为定时器队列调用mark_bh。TQUEUE_BH总是在IMMEDIATE_BH后执行的。
    NET_BH
    网络驱动程序通过标记这个队列来将事件通知上面的网络层。bh本身是网络层的一部分,模块无法访问。
    CONSOLE_BH
    控制台是在下半部中进行终端tty切换的。这个操作要包含进程控制。例如,在X Window系统和字符模式间切换就是由X 服务器控制的。而且,如果键盘驱动程序请求控制台的切换,那么控制台切换不能在中断时进行。也不能在进程向控制台写的时候进行。使用bh就能满足这些要求,因为驱动程序可以任意禁止下半部;如果发生了前面情况,在写控制台时禁止console_bh即可。
    TIMER_BH
    这个bh由do_timer函数标记;do_timer函数管理着时钟滴答。这个bh要执行的函数正是驱动内核定时器的那个函数。不使用add_timer的驱动程序是无法使用这种功能的。
    <!--[if !supportEmptyParas]--> <!--[endif]-->
    其余的下半部是有特定的内核驱动程序使用的。bh一旦被激活,当在return_from_sys_call中调用do_bottom_half(kernel/softirq.c)时它就会得到执行。当进程退出系统调用或慢速中断处理程序退出时都会执行return_from_sys_call过程。快速中断处理程序退出时就不会执行下半部; 
    时钟滴答总要执行ret_from_sys_call的;如果快速中断处理程序标记了一个bh,实际的bh处理函数最多10ms后就会被执行。
    下半部运行后,如果设置了need_resched变量,就会调用调度器;各种wake_up函数都会设置这个变量。因此,上半部可以将任何与被唤醒的进程有关的任务放到下半部去做,并通过设置need_resched调度这些任务。
    2、编写下半部
    下半部代码是在安全时间内运行的。但是,bh还是在“中断时间”内处理的。intr_count不为0,因为下半部是在进程上下文之外执行的。因此,针对“任务队列”的各种限制也适用于在下半部中执行的代码。
    下半部通过暂时禁止中断或使用锁来与上半部中断处理程序共享数据结构,且避免竞争条件。
    新编写的实现了下半部的驱动程序应该通过使用立即队列来将它的代码挂在IMMEDIATE_BH上。共有三个函数可用于管理自己私有的下半部:init_bh,enable_bh和disable_bh。
    实际上,立即队列也是一种下半部。当标记了IMMEDIATE_BH后,处理下半部的函数实际上就是去处理立即队列。如果你的中断处理函数将它的bh处理函数排进tq_immediate队列并且标记了下半部,那么队列中的这个任务会正确地被执行。内核都可以将相同的任务多次排队而不破坏任务队列,每次运行上半部处理函数时都可以将下半部排队。
    一些需要特殊配置的驱动程序需要多个下半部或不能简单地用tq_immediate来设置,就可以使用定制的任务队列。中断处理函数将任务排进自己的队列中,并准备运行这些任务时,就将一个简单的对队列进行处理的函数插入立即队列。
    例子:装载时如果指定bh=1,那么模块就会安装一个使用了下半部的中断处理函数。short是这样对中断处理进行划分的:上半部(中断处理函数)将当前时间保存到一个循环缓冲区中并调度下半部。而bh将累积的各个时间值打印到一个字符缓冲区,然后唤醒所有的读进程。最后上半部非常简单:
    void short_bh_interrupt(int irq, void *dev_id, struct pt_regs *regs)
    {
    do_gettimeofday(tv_head);
    tv_head++;
    if (tv_head == (tv_data + NR_TIMEVAL) )
    tv_head = tv_data; /* wrap */
    /* 将bh排队。即使被多次排队也没有关系 */
    queue_task_irq_off(&short_task, &tq_immediate);
    mark_bh(IMMEDIATE_BH);
    short_bh_count++; /* 记录一个新的中断到了 */
    }
    这段代码调用queue_task却不会检查任务是否已被排进队列。
    然后,下半部记录下在调度下半部前上半部被激活的次数(savecount)。如果上半部是一个“慢速”处理函数,那么这个数总为1,当慢速处理函数退出时,总会运行待处理的下半部。
    void short_bottom_half(void *unused)
    {
    int savecount = short_bh_count;
    short_bh_count = 0; /* 我们已经从队列中删去*/
    /*
    * 下半部读入由上半部填充的tv数组,并将它打印入循环的字符缓冲区,该缓冲区是
    * 由读进程处理的
    */
    /* 首先写入在这个bh 前发生的中断的次数*/
    short_head += sprintf((char *)short_head,"bh after %6i/n",savecount);
    if (short_head == short_buffer + PAGE_SIZE)
    short_head = short_buffer; /* 绕回来 */
    /*
    *然后,写入时间值。每次写16个字节。因此与PAGE_SIZE是对齐的
    */
    do {
    short_head += sprintf((char *)short_head,"%08u.%06u/n",
    (int)(tv_tail->tv_sec % 100000000),
    (int)(tv_tail->tv_usec));
    if (short_head == short_buffer + PAGE_SIZE)
    short_head = short_buffer; /* 绕回来 */
    tv_tail++;
    if (tv_tail == (tv_data + NR_TIMEVAL) )
    tv_tail = tv_data; /* 绕回来 */
    } while (tv_tail != tv_head);
    wake_up_interruptible(&short_queue); /* 唤醒所有读进程 */
    }
    使用下半部,两个中断间的时间间将减少,但处理中断的总的工作量不变,更快的上半部的优点是禁止中断的时间较短,但对真正的硬件中断来说,这个时间还是很有关系的。
    下面是当装载short时指定bh=1你可能看到的输出结果:
    morgana%echo 1122334455 > /dev/shortint; cat /dev/shortint
    bh after 5
    50588804.876653
    50588804.876693
    50588804.876720
    50588804.876747
    50588804.876774
    <!--[if !supportEmptyParas]--> <!--[endif]-->
    (三)共享中断
    在PC机上不能将不同的设备挂到同一个中断信号线上。但Linux可以共享中断,Linux软件对共享的支持是为PCI设备做的,也可用于ISA卡。
    1、安装共享的处理程序
    和其它中断一样,要与它共享的中断也是通过request_irq函数来安装的,但它们有两处不同:
    <!--[if !supportLists]-->l<!--[endif]-->申请共享中断时,必须在flags参数中指定SA_SHIRQ位
    <!--[if !supportLists]-->l<!--[endif]-->dev_id参数必须是唯一的。任何指向模块的地址空间的指针都可以,当然dev_id一定不能设为NULL。
    内核为每个中断维护了一张共享处理函数的列表,并且这些处理函数的dev_id各不相同。如果两个驱动程序都将dev_id注册为NULL,那么在卸载时就会混淆,且当中断到达时内核就会出现oops消息。
    满足这些条件之后,如果中断信号线空闲或者满足下面两个条件,request_irq就会成功:
    <!--[if !supportLists]-->l<!--[endif]-->前面注册的处理函数的flags参数指定了SA_SHIRQ位。
    <!--[if !supportLists]-->l<!--[endif]-->两个处理函数同为快速/慢速处理函数,快速和慢速处理函数处于不同的环境,不能互相混淆。
    当两个或两个以上的驱动程序共享同一根中断信号线,而硬件又通过这根信号线中断了处理器时,内核激活这个中断注册的所有处理函数,并将自己的dev_id传递给它们。因此,共享处理函数必须能够识别出它对应于哪个中断。
    内核没有共享中断的探测函数。仅当使用的中断信号线空闲时,标准的探测机制才能奏效;DIY探测时驱动程序必须为所有可能的中断信号线申请共享处理函数,然后观察中断在何处报告。这和前面的DIY探测之间的差别在于,此时探测处理函数必须检查是否真的发生了中断。
    释放处理函数同样是通过执行release_irq来实现的。这里dev_id参数用于从该中断的共享处理函数列表中正确地选出要释放的那个处理函数。
    使用共享处理程序的驱动程序时不能使用enable_irq和disable_irq。因为使用后共享中断信号线的其它设备就无法正常工作了。
    2、运行处理函数
    当内核接收到中断时,所有注册过的处理函数都会被激活。共享中断处理程序必须能将需要处理的中断和其它设备产生的中断区分开来。
    例:装载short时指定shared=1将安装下面的处理程序而不是缺省的处理程序:
    void short_sh_interrupt(int irq, void *dev_id, struct pt_regs *regs)
    {
    int value;
    struct timeval tv;
    /* 如果不是short,立即返回 */
    value = inb(short_base);
    if (!(value & 0x80)) return;
    /* 清除中断位 */
    outb(value & 0x7F, short_base);
    /* 其余不变 */
    do_gettimeofday(&tv);
    short_head += sprintf((char *)short_head,"%08u.%06u/n",
    (int)(tv.tv_sec % 100000000), (int)(tv.tv_usec));
    if (short_head == short_buffer + PAGE_SIZE)
    short_head = short_buffer; /* 绕回来 */
    wake_up_interruptible(&short_queue); /* 唤醒所有读进程 */
    }
    并口没有 “待处理的中断”位可供检查,为此处理函数使用了ACK位。如果该位为高,报告的中断就是送给short的,然后处理函数通过将并口的数据端口的高位清零来清除中断位。如果与short共享同一中断的设备产生了一个中断,short会知道它的信号线并未激活。
    3、/proc接口
    系统中安装的共享中断处理程序不会影响/proc/stat文件,但是,会影响/proc/interrupts文件。
    为同一个中断号安装的处理程序会出现在/proc/interrupts文件的同一行上。例如下面的快照是将short和帧捕捉卡装载为共享中断处理程序之后:
    0: 1153617 timer
    1: 13637 keyboard
    2: 0 cascade
    3: 14697 + serial 
    5: 190762 NE2000 
    7: 2094 + short, + cx100
    13: 0 math error
    14: 47995 + ide0
    15: 12207 + ide1
    这里共享中断信号是IRQ7号中断;激活的处理程序列在同一行,用逗号隔开。
    4、缓冲与中断驱动的I/O
    有时驱动程序的写函数必须实现缓冲。数据缓冲可以将数据的发送和接收与write及read系统调用分离开来,提高系统的整体性能。
    “中断驱动的I/O”使写设备的进程填充一个输入缓冲区并在中断时间内由读设备的进程将其取空;或由读进程读空数据然后在中断时间内由写进程填充输入缓冲区。
    这种机制要正确运行,就要求硬件必须按下面的语义产生中断:
    <!--[if !supportLists]-->l<!--[endif]-->对输入而言,当新数据到达,系统处理器准备读取它时,设备就中断处理器。实际执行的动作取决于设备是否使用了I/O端口,内存映射或者DMA。
    <!--[if !supportLists]-->l<!--[endif]-->对输出而言,当设备准备好接收新数据或对成功的数据传输进行确认时都会发出中断。内存映射和能进行DMA的设备通常是通过产生中断来通知系统它们的对缓冲区的处理已经结束。
    <!--[if !supportLists]-->5、<!--[endif]-->竞争条件与处理
    当操作不是原子地执行时或可能有代码会被同时执行时,会发生资源竞争,会导致当变量或其它数据项在中断时间内被修改时,由于竞争条件的存在,驱动程序的操作就有可能造成它们的不一致。但在执行时仍会假定数据会保持一致性。典型的竞争条件会在三种情况下发生:在函数内隐式地调用schedule、阻塞操作、由中断代码或系统调用访问共享数据。
    最好处理方法是不允许并发访问。一般用于避免竞争条件的技术是在驱动程序的方法中实现的,但中断处理函数并不需要特别的处理,因为相对设备驱动,它的操作是原子性的。
    最常用的防止数据被并发地访问的方法有:
    <!--[if !supportLists]-->l<!--[endif]-->使用循环缓冲区和避免使用共享变量。
    <!--[if !supportLists]-->l<!--[endif]-->在访问共享变量的方法里暂时禁止中断。
    <!--[if !supportLists]-->l<!--[endif]-->使用锁变量,它是原子地增加和减少的。
    可能在中断时间内被修改了的变量时可以声明为volatile的,来阻止编译器对该值的访问进行优化(例如,它阻止编译器在整个函数的运行期内将这个值放进一个寄存器中)。但是,使用volatile变量后,编译器产生的代码会很糟糕;也可以使用cli和sti,Linux实现这些函数时使用了gcc的制导来保证在中断标志位被修改之前处理器处于安全状态。
    1)使用循环缓冲区
    循环缓冲区使一个进程将数据放进缓冲区中,另一个则将它取出来。有多种情况,如读进程等待读取在中断时间里生产的数据;或,下半部读取上半部产生的数据。
    head和tail指针用于对循环缓冲区进行寻址。head是数据的写入位置,由数据的写进程更新。数据从tail处读出,它是由读进程更新。如果数据是在中断时间内写的,那么必须将head定义成volatile的或者在进入竞争条件前将中断禁止。
    如果缓冲区满了,有多种方式可以处理,简单地丢弃数据(如果并不检查溢出,如果head超过了tail,那么整个缓冲区中的数据都丢失了)、丢弃最后那个数据项、覆盖缓冲区的tail、、阻塞写进程、分配一个临时的附加的缓冲区作为主力缓冲区的候补等。选择解决方案取决于数据的重要性。
    虽然循环缓冲区看来解决了并发访问的问题,但当read函数进入睡眠时可能出现竞争条件。如:
    while (short_head==short_tail) {
    interruptible_sleep_on(&short_queue);
    /* ... */
    }
    新数据有可能在while条件被测试是否为真后和进程进入睡眠前到达。中断中携带的信息就无法被进程及时读取;即使此时head != tail,进程也将进入睡眠,直到下一项数据到达时它才会被唤醒。
    2)禁止中断
    通常调用cli禁止处理器的中断报告以获得对共享数据独占访问。当数据在中断时间内要被修改并且是被生存于正常的计算流中的函数修改时,那么随后的函数在访问这些数据前就必须先禁止中断。
    这种情况下,竞争条件会发生在读共享数据项的指令和使用刚获得与数据有关的信息的指令之间。例如,如果链接表在中断时间内被修改过了,那么下面的循环在读这个表时就可能会失败。
    for (ptr=listHead; ptr; ptr=ptr->next)
    /* do somthing */;
    在ptr已经被读取后但在使用它之前,一个中断可能会改变了ptr的值。
    以下代码在整个关键循环期间将中断禁止:
    unsigned long flags;
    save_flags(flags);
    cli();
    /* 临界区代码 */
    restore_flags(flags);
    在驱动程序的方法中,可以认为当进程进入系统调用时中断会被打开,就可以用简单的cli/sti对来替代save_flags/restore_flags。但是有时候无法确定中断标志位(IF) 当前的值,就不得不使用更安全的save_flags/restore_flags解决方法。
    3)使用锁变量
    当两个无关的实体(比如象中断处理程序和read系统调用,或者是SMP对称多处理器计算机中的两个处理器)需要并发地对共享的数据项进行访问时,它们必须先申请锁。如果得不到锁,它就必须等待。
    Linux内核开放了两套函数来对锁进行处理:位操作和对“原子性”数据类型的访问。
    3-1)位操作
    可能我们要在进程可能正在访问时,使有单个位的锁变量或者要在中断时间内更新设备状态位,内核为此提供了一套原子地修改和测试位的函数用于这一操作。
    原子性的位操作通过单条机器指令来完成,运行的很快,这些函数与体系结构相关,在头文件中声明。即使在SMP机器上它们也能保证是原子的, 
    但是这些函数的数据类型也是体系结构相关的。nr参数和返回值在Alpha上是unsigned long类型,而在其它体系结构上是int类型。
    set_bit(nr, void *addr);
    这个函数用于设置addr指向的数据项的第nr个位。该函数作用在一个unsigned long上,即使addr指向void。返回的是该位原先的取值,0或非零。
    clear_bit(nr, void *addr);
    这个函数用于清除addr指向的unsigned long数据中的指定位。它的语义和set_bit类似。
    change_bit(nr, void *addr);
    这个函数用于切换指定位,其它方面和前面的set_bit和clear_bit函数类似。
    test_bit(nr, void *addr);
    这个函数不必是原子的伪操作;它只是简单地返回该位当前的值。
    要访问共享数据项的代码段可以使用set_bit或clear_bit来试着原子地获取锁。通常是象下面的代码段这样实现的;假定锁位于地址addr的第nr位上。并且假定当锁空闲时该位为0,锁忙时该位非零。
    /* 试着设置锁 */
    while (set_bit(nr,addr)!=0)
    wait_for_a_while();
    /* 做你的工作 */
    /* 释放锁,并检查... */
    if (clear_bit(nr,addr)==0)
    something_wnt_wrong(); /* 已经被释放了:出错 */
    这种访问共享数据的方式的毛病是竞争双方都必须要等待。如果其中一方是中断处理程序,那么这一点就较难保证了。
    3-2)原子性的整数操作
    。中定义了一种新的数据类型atomic_t,只能通过原子操作来访问它。这些函数比位操作功能更强大。
    atomic_t目前在所有支持的体系结构上都被定义为int。下面的操作能保证SMP机器上的所有处理器是原子地对它进行访问。这些操作都非常快,因为它们都尽可能编译成单条的机器指令。
    void atomic_add(atomic_t i, atomic_t *v);
    将v指向的原子变量加上i。返回值是void类型。网络部分的代码使用这个函数来更新套接字在内存使用上的统计信息。
    void atomic_sub(atomic_t i, atomic_t *v);
    从*v里减去i。
    void atomic_inc(atomic_t *v); 
    void atomic_dec(atomic_t *v);
    对原子变量加减1。
    int atomic_dec_and_test(atomic_t *v);
    该函数用于跟踪引用计数。仅当变量*v在减1后取值为0时返回值为0。
    如果将原子数据项传递给了一个要求参数类型为整型的函数,编译时就会得到警告。可以读取原子数据项的当前值并将它强制转换成其它数据类型。
    4)无竞争地进入睡眠
    有种特别的竞争条件发生在检查进入睡眠的条件和对sleep_on的实际调用之间。如下面的测试代码:
    while (short_head==short_tail){
    interruptible_sleep_on(&short_queue);
    /* ... */
    }
    如果要安全地进行比较和进入睡眠,你必须先禁止中断报告,然后测试条件并进入睡眠。比较中被测试的变量不能被修改。内核允许进程在发出cli指令后就进入睡眠。而在将进程插入它的等待队列之后,在调用shcedule之前,内核只要简单地重新打开中断报告。
    这里例子使用了while循环来处理信号。如果有阻塞的信号向进程发出报告,interruptible_sleep_on就返回,再次进行while语句中的测试。
    下面是一种可能的实现:
    while (short_head==short_tail){
    cli();
    if (short_head==short_tail)
    interuptible_sleep_on(&short_queue);
    sti();
    /* ... 信号解码 .... */
    }
    如果中断是在cli后发生的,那么这个中断在当前进程进入睡眠前都会处于待处理状态。而当中断最终报告给处理器时,进程已经进入了睡眠,可以被安全地唤醒。
    在这个例子中,可以使用cli/sti是因为代码存在于read方法内的;否则我们必须使用更为安全的save_flags,和restore_flags函数。
    如果在进入睡眠之前你不想禁止中断,有一种方法,其基本想法是,进程可以把自己排进等待队列,声明自己的状态为睡眠状态,然后执行它的测试代码。
    典型的实现如下:
    struct wait_queue wait = {current, NULL};
    add_wait(&short_queue, &wait);
    current->state=TASK_INTERRUPTIBLE;
    while (short_head==short_tail){
    schedule();
    /* ... 信号解码 ... */
    }
    remove_wait_queue(&short_queue, &wait);
    这段代码看起来有点象将sleep_on的内部实现展开了。显式地声明了wait变量,因为需要用它来使进程进入睡眠; 
    其中current->state字段是给调度器用的提示。调度器被激活后,它将通过观察所有进程的state字段来决定接着作些什么。所有进程都可以任意修改自己的state字段,但在调度器运行之前这种改变还不会生效。
    #include 
    TASK_RUNNING
    TASK_INTERRUPTIBLE
    TASK_UNINTERRUPTIBLE
    这些符号名是current->state常用的取值。
    void add_wait_queue(struct wait_queue ** p, struct wait_queue *wait)
    void remove_wait_queue(struct wait_queue ** p, struct wait_queue *wait)
    void __add_wait_queue(struct wait_queue ** p, struct wait_queue *wait)
    void __ remove_wait_queue(struct wait_queue ** p, struct wait_queue *wait)
    这些函数用于从等待队列中插入和删除进程。wait参数必须指向进程堆栈所在的页(临时变量)。以下划线开头的函数运行的更快些,但它们在禁止中断后才能被调用。
    当中断到达时处理程序将调用wake_up_interruptible(&short_queue);对Linux而言,这意味着“将state置为TASK_RUNNING”。因此,如果在while条件和schedule调用间有中断报告的话,该任务的state字段将会又被标记为TASK_RUNNING的,因此不会丢失数据。
    而如果进程仍是“可中断的”(TASK_INTERRUPTIBLE),schedule将保持它的睡眠状态。
    wake_up系统调用并不会将进程从等待队列中删去。是由sleep_on来对等待队列进行进程的添加和删除的。因此程序代码必须显式地调用add_wait_queue和remove_wait_queue。

    展开全文
  • 中断处理程序&中断服务例程

    千次阅读 2016-06-03 13:58:23
    中断处理程序(Interrupt Handler)和中断服务例程ISR(Inerrupt Service Routine)是两个不同的概念;一条中断线对应一个中断处理程序,而一个中断处理程序再对应若干个中断服务例程,如下: 所有的中断服务例程...
    中断处理程序(Interrupt Handler)和中断服务例程ISR(Inerrupt Service Routine)是两个不同的概念;一条中断线对应一个中断处理程序,而一个中断处理程序再对应若干个中断服务例程,如下:


    所有的中断服务例程挂在中断请求队列中,这个工作是由request_irq()函数来完成的,其实也就是对中断服务例程进行注册,关于这个函数的具体实现在include/linux/interrupt.h中。而中断处理程序就相当于某个中断向量的总的处理程序,比如上图中IRQ0x09_interrupt()是中断号为9(向量为47)的总处理程序,假如这个9号中断由5个设备共享,那么这5个设备都分别有其对应的中断服务例程。
     

    当有多个设备需要共享某个中断线时,中断处理程序必须要调用ISR,此时会调用handle_IRQ_event()来运行挂在该中断线上的所有中断服务例程,下图给出了具体的调用关系:

    这其实就是中断处理程序的执行过程。其中IRQn_interrupt表示从IRQ0x00_interrupt到IRQ0x0f_interrupt的任意一个中断处理程序。这个中断处理程序需要调用do_IRQ()函数,而do_IRQ()函数对收到的中断请求进行应答,并禁止这条中断线,然后要确保这条中断线上有一个有效的中断服务例程,而且目前这个这个中断服务例程已经启动但未执行。这时do_IRQ()调用handle_IRQ_event()来运行挂在这条中断线上的所有中断服务例程。
     

    补充说明一下在内核中用于让多个设备共享一条中断线而设置的数据结构irqaction(3.7版本的内核):
    typedef irqreturn_t (*irq_handler_t)(int, void *);    //声明一个中断服务例程的钩子函数,返回值为irqreturn_tx型
    struct irqaction {
                  irq_handler_t handler;                             //指向一个具体的I/O设备的中断服务程序 
                  void *dev_id;                                           //指定的I/O设备的主设备号和次设备号
                  void __percpu *percpu_dev_id;              //用于识别不同cpu设备的资料
                  struct irqaction *next;                              //指向用于共享中断线的下一个irqaction结构
                  irq_handler_t thread_fn;                         //指向一个具体的线程化中断的中断服务例程 
                  struct task_struct *thread;                       //指向线程中断的线程指针
                  unsigned int irq;                                      //所申请的中断号
                  unsigned int flags;                                  //一组用于描述中断线与I/O设备之间关系的中断标志 
                  unsigned long thread_flags;                   //用于描述线程中断的中断标志
                  unsigned long thread_mask;                  //中断掩码
                  const char *name;                                  //中断设备名称 
                  struct proc_dir_entry *dir;                       //指向IRQn相关的/proc/irq/n目录的描述符
    ____cacheline_internodealigned_in_smp;                

    展开全文
  • 1,中断处理程序中不能使用有睡眠功能的函数,如ioremap,kmalloc,msleep等,理由是中断程序并不是进程,没有进程的概念,因此就没有休眠的概念; 2,中断处理程序中的延时可以用忙等待函数来代替,如ndelay,...

    1,中断处理程序中不能使用有睡眠功能的函数,如ioremap,kmalloc,msleep等,理由是中断程序并不是进程,没有进程的概念,因此就没有休眠的概念;

    2,中断处理程序中的延时可以用忙等待函数来代替,如ndelay,udelay,mdelay等,这些函数在实现上本质是根据CPU频率进行一定次数的循环;最好不要使用mdelay,因为毫秒延时对内核来说已经是非常大了。但是在中断处理程序中使用msleep却不行。(见linux设备驱动开发详解第二版p210页)

    3,printk函数在中断处理函数中可以使用,但是会占用较多时间,降低效率。在调IIC驱动的时候,由于IIC读取写入处理时必须进行一定延时,在我没有使用udelay的时候,竟然用printk就使IIC中断正常运行,当时在调试的时候,发现有些printk加上程序就正常,去掉就不正常,当时真是匪夷所思,但现在明白了,因此printk占用时间比较大,正好充当了IIC延时的功能。最后我把printk全部去掉,在需要延时的地方加入udelay,才使程序正常运行。

    4,使用for和while等的空循环在中断处理函数中进行延时操作,在实际测试中发现并不能起到延时的功能,linux内核处理这种循环速度很快,并没有延时的效果。这跟裸板程序使用循环来延时的用法不相同。

     

    以上是我的总结,然后摘抄一些别人的总结,摘自:http://cache.baidu.com/c?m=9d78d513d9d437ab4f9b96697d12c0176d4381132ba6db020ea08439e7732a41501794ac56240704a2873c3c5de91048adb0687d6d4566f58cc9fb57c0ebcc757a9f27437318875612a448f2945b7b966bc306b6f445bcefa72595acd1d3db49&p=93759a40d59910ea0be290221508&user=baidu&fm=sc&query=%D6%D0%B6%CF%C9%CF%CF%C2%CE%C4+printk&qid=f445575e01162846&p1=1

     

    1、中断是一种电信号,由硬件设备生成,并直接送入中断控制器的输入引脚上。然后再由中断控制器向处理器发送相应的信号。处理器一经检测到此信号,便中断自己的当前工作转而处理中断。此后,处理器会通知操作系统已经产生中断,这样,操作系统就可以对这个中断进行适当的处理了。

       

       2、不同的设备对应的中断不同,而每个中断都通过一个唯一的数字标识。中断值通常被称为中断请求(IRQ)线。有些中断值是指定的,有些是动态分配的。特定的中断总与特定的设备相关联。

     

       3、异常与中断不同,它在产生时必须考虑与处理器时钟同步。异常也常常称为同步中断。许多处理器体系结构处理异常与中断的方式类似,因此内核对它们的处理也很类似。

      

       4、在响应一个特定中断的时候,内核会执行一个函数,该函数叫做中断处理程序或中断服务例程。产生中断的每个设备都有一个相应的中断处理程序,如果一个设备可以产生多种不同的中断,那么该设备就可以对应多个中断处理程序。一个设备的中断处理程序是它设备驱动程序的一部分。

       5、中断处理程序与其他内核函数的真正区别在于:中断处理程序是被内核调用来响应中断的,而它们运行于我们称之为中断上下文的特殊上下文中。

     

       6、中断处理一般分为两个部分,中断处理程序是上半部-接收到一个中断就立即执行,但只做有严格时限的工作,这些工作都是在所有中断被禁止的情况下完成的。能够被允许稍后完成的工作被推迟到下半部去。通常情况下,下半部会在中断处理程序返回时立即执行。

     

       7、Linux中的中断处理程序是无需重入的。当一个给定的中断处理程序正在执行时,相应的中断线在所有处理器上都会被屏蔽掉,以防止在同一中断线上接收另一个新的中断。通常情况下,所有其他的中断都是打开的,所以这些不同中断线上的其它中断都能被处理,但当前中断线总是被禁止的。由此可以看出,同一个中断处理程序绝对不会被同时调用以处理嵌套的中断。

       8、共享的中断处理程序与非共享的在注册和运行方式上比较类似,但差异主要有以下三处:

    •    注册中断处理程序函数request_irq()的参数flags必须设置SA_SHIRQ标志。
    •    对每个注册的中断处理程序来说,dev_id参数必须唯一。不能给共享的处理程序传递NULL值。
    •    中断处理程序必须能够区分它的设备是否真的产生了中断。否则它根本无法知道是它对应的设备发出了这个中断还是共享这条中断线的其它设备发出了这个中断。

       9、当执行一个中断处理程序或下半部时,内核处于中断上下文中。中断上下文和进程并没有什么瓜葛。因为没有进程的背景,所以中断上下文不可以睡眠。因此,不能从中断上下文中调用某些函数。如果一个函数睡眠,就不能在中断处理函数中使用它。中断上下文具有较为严格的时间限制,因为它打断了其他代码。中断上下文中的代码应当迅速简洁,尽量不要使用循环去处理繁重的工作。尽量把工作从中断处理程序中分离出来,放在下半部执行。中断处理程序并不具有自己的栈。相反,它共享被中断进程的内核栈。如果没有正在运行的进程,就使用idle进程的栈。中断处理程序共享别人的堆栈,所以它在栈中获取空间时必须非常节省。内核栈本就很有限,所有的内核代码都应该谨慎利用它。

       10、Linux内核提供了一组接口用于操作机器上的中断状态。这些接口为我们提供了能够禁止当前处理器的中断系统,或屏蔽掉整个机器的一条中断线的能力,这些例程都是与体系结构相关的,可以在<asm/system.h>和<asm/irq.h>中找到。

       11、控制中断系统的原因归根结底是需要提供同步。通过禁止中断,可以确保某个中断处理程序不会抢占当前代码,还可以禁止内核抢占。但它们都没有提供任何保护机制来防止来自其他处理器的并发访问。锁提供保护机制来防止来自其他处理器的并发访问。禁止中断提供保护机制来防止来自其他中断处理程序的并发访问。

    转自:http://blog.csdn.net/samantha_sun/article/details/6790492
    展开全文
  • Linux中断处理程序设计 中断概念   中断指当出现需要时,CPU暂时停止当前程序的执行转而执行处理新情况的程序和执行过程。  为什么需要中断? 1、 外设的处理速度一般慢于CPU 2、 CPU不能一直等待外部事件 ...
  • 中断的概念中断处理过程

    千次阅读 2013-05-30 22:17:06
    中断的概念中断处理过程 (1)中断: 在运行一个程序的过程中,断续地以“插入”方式执行一些完成特定处理功能的程序段,这种处理方式称为中断。 (2)中断的作用: ◎并行操作 ◎硬件故障报警与处理 ◎...
  • 首先中断处理程序(Interrupt Handler)和中断服务例程ISR(Inerrupt Service Routine)是两个不同的概念.简单来说就是,一条中断线对应一个中断处理程序,而一个中断处理程序再对应若干个中断服务例程,具体看下图所示...
  • 一个设备的中断处理程序是它设备驱动程序的一部分--设备驱动程序是用于对设备进行管理的内核代码。中断处理程序与其他内核函数的真正区别在于,中断处理程序是被内核调用来响应中断的,而它们运行于我们称之为中断上...
  • 五、中断处理程序实例 一、中断: 定义:中断就是使CPU暂时挂起当前正在进行工作并转向某紧急事件服务与处理程序(该服务与处理程序称为中断服务程序),在执行完中断服务程序后再返回到被中止
  • 中断处理程序、中断上下文中处理...1,中断处理程序中不能使用有睡眠功能的函数,如ioremap,kmalloc,msleep等,理由是中断程序并不是进程,没有进程的概念,因此就没有休眠的概念; 2,中断处理程序中的延时可以...
  • 8086汇编0号中断处理程序

    千次阅读 2016-12-06 20:44:59
    1.中断的基本概念中断是指在计算机执行期间,CPU收到某个信号(来自软件或硬件),暂时保存正在执行的程序的上下文,转而去执行相应的中断处理程序. 2.8086CPU内部有内部有下面的情况发生时,将产生相应的中断信息. 除法...
  • 1,中断处理程序中不能使用有睡眠功能的函数,如ioremap,kmalloc,msleep等,理由是中断程序并不是进程,没有进程的概念,因此就没有休眠的概念; 2,中断处理程序中的延时可以用忙等待函数来代替,如ndelay,udelay,...
  • 如何用PHP编写一个信号中断处理程序 什么是中断信号? 从字面意义来讲就是指可以使软件中断运行信号。中断信号处理程序完程序后,就会返回继续执行主程序。具体概念请自行百度 有哪些中断信号? 在linux系统上...
  • 中断的概念

    2020-09-28 10:11:37
    中断必须将控制转移到合适的中断处理程序,处理转移的简单方法是调用一个通用子程序,接着这个子程序调用相应的中断处理程序,由于只有少量预先定义的中断,所以可以使用中断处理子程序的指针表,这个指针表记录了...
  • Linux中断处理驱动程序编写

    千次阅读 2014-12-23 21:54:52
    Linux中断处理驱动程序编写 中断与定时器: 中断的概念:指CPU在执行过程中,出现某些突发事件急待处理,CPU暂停执行当前程序,转去处理突发事件,处理完后CPU又返回原程序被中断的位置继续执行 中断的分类:...
  • 1.中断的概念  中断本质上是一种特殊的电信号,由硬件设备发向处理器。处理器接收到中断后,会马上向操作系统反映此信号的到来,然后就由OS负责处理这些新到来的信号。  从物理学的角度看,中断是一种电信号,有...
  • 中断以及相关的概念

    2020-04-23 22:47:09
    中断是指程序执行过程中,遇到急需处理的事件时,暂时中止CPU上现行程序的运行转而执行相应的事件处理程序,待处理完成后再返回原程序被中断处或调度其他程序执行的过程 二、中断的分类 1、外中断:是指来自...
  • 中断的基本概念 中断机制实现 中断的上半部,下半部 中断下半部实现方式

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,155
精华内容 862
关键字:

中断处理程序的概念