理解Linux中断 (3)【转】

转自:http://blog.csdn.net/tommy_wxie/article/details/7425712

版权声明:本文为博主原创文章,未经博主允许不得转载。
4、下半部
在中断处理过程中,不能睡眠。另外,它运行的时候,会把当前中断线在所有处理器上都屏蔽(在ack中完成屏蔽);更糟糕的情况是,如果一个处理程序是SA_INTERRUPT类型,它执行的时候会禁上所有本地中断(通过cli指令完成),所以,中断处理应该尽可能快的完成。所以Linux把中断处理分为上半部和下半部。
上半部由中断处理程序完成,它通常完成一些和硬件相关的操作,比如对中断的到达的确认。有时它还会从硬件拷贝数据,这些工作对时间非常敏感,只能靠中断处理程序自己完成。而把其它工作放到下半部实现。
下半部的执行不需要一个确切的时间,它会在稍后系统不太繁忙时执行。下半部执行的关键在于运行的时候允许响应所有的中断。最早,Linux用”bottom half”实现下半部,这种机制简称BH,但是即使属于不同的处理器,也不允许任何两个bottom half同时执行,这种机制简单,但是却有性能瓶颈。不久,又引入任务队列(task queue)机制来实现下半部,但该机制仍不够灵活,没法代替整个BH接口。
从2.3开始,内核引入软中断(softirqs)和tasklet,并完全取代了BH。2.5中,BH最终舍去,在2.6中,内核用有三种机制实现下半部:软中断,tasklet和工作队列。Tasklet是基于软中断实现的。软中断可以在多个CPU上同时执行,即使它们是同一类型的,所以,软中断处理程序必须是可重入的,或者显示的用自旋锁保护相应的数据结构。而相同的tasklet不能同时在多个CPU上执行,所以tasklet不必是可重入的;但是,不同类型的tasklet可以在多个CPU上同时执行。一般来说,tasklet比较常用,它可以处理绝大部分的问题;而软中断用得比较少,但是对于时间要求较高的地方,比如网络子系统,常用软中断处理下半部工作。

4.1、软中断
内核2.6中定义了6种软中断:

下标越低,优先级越高。

4.1.1、数据结构
(1)软中断向量
[html] view plain copy print?
//linux/interrupt.h
struct softirq_action
{
    void    (*action)(struct softirq_action *); //待执行的函数
    void    *data;  //传给函数的参数
};
//kernel/softirq.c
//软中断向量数组
static struct softirq_action softirq_vec[32] __cacheline_aligned_in_smp;
内核定义了一个包含32个软中断向量的数组,所以最多可有32个软中断,实际上,内核目前只使用了6个软中断。
(2)    preempt_count字段
位于任务描述符的preempt_count是用来跟踪内核抢占和内核控制路径嵌套关键数据。其各个位的含义如下:
位            描述
0——7    preemption counter,内核抢占计数器(最大值255)
8——15   softirq counter,软中断计数器(最大值255)
16——27  hardirq counter,硬件中断计数器(最大值4096)
28        PREEMPT_ACTIVE标志

第一个计数用来表示内核抢占被关闭的次数,0表示可以抢占。第二个计数器表示推迟函数(下半部)被关闭的次数,0表示推迟函数打开。第三个计数器表示本地CPU中断嵌套的层数,irq_enter()增加该值,irq_exit减该值。
宏in_interrupt()检查current_thread_info->preempt_count的hardirq和softirq来断定是否处于中断上下文。如果这两个计数器之一为正,则返回非零。
(3) 软中断控制/状态结构
softirq_vec是个全局量,系统中每个CPU所看到的是同一个数组。但是,每个CPU各有其自己的“软中断控制/状态”结构,这些数据结构形成一个以CPU编号为下标的数组irq_stat[](定义在include/asm-i386/hardirq.h中)
[html] view plain copy print?
typedef struct {
    unsigned int __softirq_pending;
    unsigned long idle_timestamp;
    unsigned int __nmi_count;    /* arch dependent */
    unsigned int apic_timer_irqs;    /* arch dependent */
} ____cacheline_aligned irq_cpustat_t;
//位于kernel/softirq.c
irq_cpustat_t irq_stat[NR_CPUS] ____cacheline_aligned;
4.1.2、软中断初始化
可以通过open_softirq注册软中断处理程序:
[html] view plain copy print?
<pre name="code" class="html">//位于kernel/softirq.c
//nr:软中断的索引号
// softirq_action:处理函数
//data:传递给处理函数的参数值
void open_softirq(int nr, void (*action)(struct softirq_action*), void *data)
{
    softirq_vec[nr].data = data;
    softirq_vec[nr].action = action;
}
//软中断初始化
void __init softirq_init(void)
{
    open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL);
    open_softirq(HI_SOFTIRQ, tasklet_hi_action, NULL);
}</pre>
<pre></pre>
软中断执行时,允许响应中断,但它自己不能睡眠,4.1.3、触发软中断raise_softirq会将软中断设置为挂起状态,并在下一次运行do_softirq中投入运行。
<div class="cnblogs_code"><span style="color:#008000">//</span><span style="color:#008000">位于kernel/softirq.c</span><span style="color:#008000"></span></div>
<pre name="code" class="cpp"><pre name="code" class="cpp">void fastcall raise_softirq(unsigned int nr)
{
    unsigned long flags;
    //保存IF值,并关中断
    local_irq_save(flags);  

    //调用wakeup_softirqd()
    raise_softirq_irqoff(nr);
    //恢复IF值
    local_irq_restore(flags);
}  

inline fastcall void raise_softirq_irqoff(unsigned int nr)
{
    //把软中断设置为挂起状态
    __raise_softirq_irqoff(nr);  

     //唤醒内核线程
    if (!in_interrupt())
        wakeup_softirqd();
</pre><br>
<pre></pre>
<pre name="code" class="cpp">该函数触发软中断前,先要关闭中断,之后再恢复;如果之前中断已经关闭,可以直接调用raise_softirq_irqoff()触发软中断。
在中断服务例程中触发软中断是最常见的形式。而中断服务例程通常作为设备驱动的一部分。例如,对于网络设备,当接口收到数据时,会产生一个中断,在中断服务例程中,最终会调用netif_rx函数处理接到的数据,而netif_rx作相应处理,最终以触发一个软中断结束处理。之后,内核在执行中断处理任务后,会调用do_softirq()。于是软中断就通过软中断处理函数去处理留给它的任务。
4.1.4、软中断执行
(1) do_softirq()函数  

//处理软中断,位于arch/i386/kernel/irq.c
asmlinkage void do_softirq(void)
{
    //处于中断上下文,表明软中断是在中断上下文中触发的,或者软中断被关闭
    /*这个宏限制了软中断服务例程既不能在一个硬中断服务例程内部执行,
    *也不能在一个软中断服务例程内部执行(即嵌套)。但这个函数并没有对中断服务例程的执行
    *进行“串行化”限制。这也就是说,不同的CPU可以同时进入对软中断服务例程的执行,每个CPU
    *分别执行各自所请求的软中断服务。从这个意义上说,软中断服务例程的执行是“并发的”、多序的。
    *但是,这些软中断服务例程的设计和实现必须十分小心,不能让它们相互干扰(例如通过共享的全局变量)。
    */
    if (in_interrupt())
        return;
    //保存IF值,并关中断
    local_irq_save(flags);
//调用__do_softirq
        asm volatile(
            "       xchgl   %%ebx,%%esp     \n"
            "       call    __do_softirq    \n"
            "       movl    %%ebx,%%esp     \n"
            : "=b"(isp)
            : "0"(isp)
            : "memory", "cc", "edx", "ecx", "eax"
        );
    //恢复IF值
    local_irq_restore(flags);  

(2)__do_softirq()函数  

//执行软中断,位于kernel/softirq.c
asmlinkage void __do_softirq(void)
{
    struct softirq_action *h;
    __u32 pending;
    /*最多迭代执行10次.在执行软中断的过程中,由于允许中断,所以新的软中断可能产生.为了使推迟函数能够在
    *较短的时间延迟内执行,__do_softirq会执行所有挂起的软中断,这可能会执行太长的时间而大大延迟返回用户
    *空间的时间.所以,__do_softirq最多允许10次迭代.剩下的软中断在软中断内核线程ksoftirqd中处理.
    */
    int max_restart = MAX_SOFTIRQ_RESTART;
    int cpu;  

    //用局部变量保存软件中断位图
    pending = local_softirq_pending();
    /*增加softirq计数器的值.由于执行软中断时允许中断,当do_IRQ调用irq_exit时,另一个__do_softirq实例可能
    *开始执行.这是不允许的,推迟函数必须在CPU上串行执行.
    */
    local_bh_disable();
    cpu = smp_processor_id();
restart:
    /* Reset the pending bitmask before enabling irqs */
    //重置软中断位图,使得新的软中断可以发生
    local_softirq_pending() = 0;
    //开启本地中断,执行软中断时,允许中断的发生
    local_irq_enable();  

    h = softirq_vec;  

    do {
        if (pending & 1) {
            //执行软中断处理函数
            h->action(h);
            rcu_bh_qsctr_inc(cpu);
        }
        h++;
        pending >>= 1;
    } while (pending);
       //关闭中断
    local_irq_disable();
    //再一次检查软中断位图,因为在执行软中断处理函数时,新的软中断可能产生.
    pending = local_softirq_pending();
    if (pending && --max_restart)
        goto restart;
    /*如果还有多的软中断没有处理,通过wakeup_softirqd唤醒内核线程处理本地CPU余下的软中断.
    */
    if (pending)
        wakeup_softirqd();
    //减softirq counter的值
    __local_bh_enable();
}
(3)软中断执行点
内核会周期性的检查是否有挂起的软中断,它们位于内核代码的以下几个点:
(1)内核调用local_bh_enable()函数打开本地CPU的软中断:  

//位于kernel/softirq.c
void local_bh_enable(void)
{
    preempt_count() -= SOFTIRQ_OFFSET - 1;  

    if (unlikely(!in_interrupt() && local_softirq_pending()))
        do_softirq(); //软中断处理
    //……
}  

</pre><br>
<br>
<pre></pre>
<pre></pre>
<pre></pre>  

</pre>  

 

时间: 2024-10-04 19:19:21

理解Linux中断 (3)【转】的相关文章

理解Linux中断 (1)【转】

转自:http://blog.csdn.net/tommy_wxie/article/details/7425685 版权声明:本文为博主原创文章,未经博主允许不得转载. 一直认为,理解中断是理解内核的开始.中断已经远远超过仅仅为外围设备服务的范畴,它是现代体系结构的重要组成部分. 1.基本输入输出方式 现代体系结构的基本输入输出方式有三种: (1)程序查询: CPU周期性询问外部设备是否准备就绪.该方式的明显的缺点就是浪费CPU资源,效率低下. 但是,不要轻易的就认为该方式是一种不好的方式(漂

理解Linux中断 (2)【转】

转自:http://blog.csdn.net/tommy_wxie/article/details/7425692 版权声明:本文为博主原创文章,未经博主允许不得转载. 3.内核的中断处理 3.1.中断处理入口 由上节可知,中断向量的对应的处理程序位于interrupt数组中,下面来看看interrupt: [html] view plain copy print? 341 .data #数据段 342 ENTRY(interrupt) 343 .text 344 345 vector=0 3

深入理解Linux内存寻址的分段机制

一.前言 最近在学习Linux内核,读到<深入理解Linux内核>的内存寻址一章.原本以为自己对分段分页机制已经理解了,结果发现其实是一知半解.于是,查找了很多资料,最终理顺了内存寻址的知识.现在把我的理解记录下来,希望对内核学习者有一定帮助,也希望大家指出错误之处. 二.分段到底是怎么回事 相信学过操作系统课程的人都知道分段分页,但是奇怪的是书上基本没提分段分页是怎么产生的,这就导致我们知其然不知其所以然.下面我们先扒一下分段机制产生的历史. 实模式的诞生(16位处理器及寻址) 在8086处

LINUX中断学习笔记【转】

转自:http://blog.chinaunix.net/uid-14825809-id-2381330.html 1.中断的注册与释放: 在 , 实现中断注册接口: 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(unsigne

linux中断--中断嵌套&amp;amp;中断请求丢失

  关于中断嵌套: 在linux内核里,如果驱动在申请注册中断的时候没有特别的指定,do_irq在做中断响应的时候,是开启中断的,如果在驱动的中断处理函数正在执行的过程中,出现同一设备的中断或者不同设备的中断,这时候新的中断会被立即处理,还是被pending,等当前中断处理完成后,再做处理. 在2.4和2.6内核里,关于这一块是否有什么不同. 一般申请中断的时候都允许开中断,即不使用SA_INTERRUPT标志.如果允许共享则加上 SA_SHIRQ,如果可以为内核熵池提供熵值(譬如你写的驱动是i

linux中断--内核中断编程

Linux中断内核编程 前言 在前面分析了中断的基本原理后,就可以写一个内核中断程序来体验以下,也可以借此程序继续深入来了解内核中断的执行过程 一.内核中断程序: 我们还是来看一看成程序: 在看程序之前,要熟悉如何进行模块编程,和了解module_pararm()的用法.如果不熟悉的话请大家看,module_param()的学习和Linux内核模块编程,在此不作解释. 1.程序interrupt.c [c-sharp] view plaincopy 1 /* 2 *file name :inte

深入理解Linux内存管理机制(一)

深入理解Linux内存管理机制(一)通过本文,您即可以: 1. 存储器硬件结构: 2.分段以及对应的组织方式: 3.分页以及对应的组织方式. 注1:本文以Linux内核2.6.32.59本版为例,其对应的代码可以在http://www.kernel.org/pub/linux/kernel/v2.6/longterm/v2.6.32/linux-2.6.32.59.tar.bz2找到. 注2:本文所有的英文专有名词都是我随便翻译的,请对照英文原文进行理解. 注3:推荐使用Source Insig

深入理解linux互斥锁(mutex)

                                      深入理解linux互斥锁(mutex)     锁机制,可以说是linux整个系统的精髓所在,linux内核都是围绕着同步在运转.在多进程和多线程编程中,锁起着极其重要的作用.我这里说的是互斥锁,其实是泛指linux中所有的锁机制.我在这里不讲如果创建锁,关于锁的创建,网上代码很多,我在这里就不多说了.我要谈一谈一个让所有刚刚接触锁机制的程序员都很困惑的问题:如何使用以及锁机制在程序中是如何运作的.     为什么要使用

深入理解linux内核之(二)进程

                                      深入理解linux内核之(二)进程       程序是静态的,进程是正在执行的程序的一个实例,一个程序可以由多个进程组成.进程是资源分配的实体.在进程被创建出来之后,该子进程几乎和父进程一样.子进程复制了父进程的地址空间,从fork()之后的第一条指令开始执行,和父进程有同样的程序可执行代码(exec调用除外).尽管子进程和父进程具有同样的程序执行代码,但是子进程拥有自己的stack和heap,因此,子进程对数据的修改对