2016-11-22 17:38:50 viewsky11 阅读数 528

一、什么是下半部

中断是一个很霸道的东西,处理器一旦接收到中断,就会打断正在执行的代码,调用中断处理函数。如果在中断处理函数中没有禁止中断,该中断处理函数执行过程中仍有可能被其他中断打断。出于这样的原因,大家都希望中断处理函数执行得越快越好。
另外,中断上下文中不能阻塞,这也限制了中断上下文中能干的事。
基于上面的原因,内核将整个的中断处理流程分为了上半部和下半部。上半部就是之前所说的中断处理函数,它能最快的响应中断,并且做一些必须在中断响应之后马上要做的事情。而一些需要在中断处理函数后继续执行的操作,内核建议把它放在下半部执行。
拿网卡来举例,在linux内核中,当网卡一旦接受到数据,网卡会通过中断告诉内核处理数据,内核会在网卡中断处理函数(上半部)执行一些网卡硬件的必要设置,因为这是在中断响应后急切要干的事情。接着,内核调用对应的下半部函数来处理网卡接收到的数据,因为数据处理没必要在中断处理函数里面马上执行,可以将中断让出来做更紧迫的事情。

可以有三种方法来实现下半部:软中断、tasklet和等待队列。

二、软中断

软中断一般很少用于实现下半部,但tasklet是通过软中断实现的,所以先介绍软中断。字面理解,软中断就是软件实现的异步中断,它的优先级比硬中断低,但比普通进程优先级高,同时,它和硬中断一样不能休眠。

软中断是在编译时候静态分配的,要用软中断必须修改内核代码。

在kernel/softirq.c中有这样的一个数组:

 static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;

内核通过一个softirq_action数组来维护的软中断,NR_SOFTIRQS是当前软中断的个数,待会再看他在哪里定义。

先看一下softirq_action结构体:

struct softirq_action
{
    void    (*action)(struct softirq_action *);
};

结构体里面就一个软中断函数,他的参数就是本身结构体的指针。之所以这样设计,是为了以后的拓展,如果在结构体中添加了新成员,也不需要修改函数接口。在以前的内核,该结构体里面还有一个data的成员,用于传参,不过现在没有了。

接下来看一下如何使用软中断实现下半部
一、要使用软中断,首先就要静态声明软中断:

enum
{
    HI_SOFTIRQ=0,
    TIMER_SOFTIRQ,
    NET_TX_SOFTIRQ,
    NET_RX_SOFTIRQ,
    BLOCK_SOFTIRQ,
    BLOCK_IOPOLL_SOFTIRQ,
    TASKLET_SOFTIRQ,
    SCHED_SOFTIRQ,
    HRTIMER_SOFTIRQ,
    RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */

    NR_SOFTIRQS
};

上面通过枚举定义了NR_SOFTIRQS(10)个软中断的索引号,优先级最高是0(HI_SOFTIRQ

二、定义了索引号后,还要注册处理程序。
通过函数open_softirq来注册软中断处理函数,使软中断索引号与中断处理函数对应。该函数在kernel/softirq.c中定义:

void open_softirq(int nr, void (*action)(struct softirq_action *))
{
    /* softirq_vec是个struct softirq_action类型的数组 */
    softirq_vec[nr].action = action;
}

 系统一般使用open_softirq()函数进行软中断描述符的初始化,主要就是将action函数指针指向该软中断应该执行的函数。在tart_kernel()进行系统初始化中,就调用了softirq_init()函数对HI_SOFTIRQTASKLET_SOFTIRQ两个软中断进行了初始化

void __init softirq_init(void)
{
    int cpu;

    for_each_possible_cpu(cpu) {
        per_cpu(tasklet_vec, cpu).tail =
            &per_cpu(tasklet_vec, cpu).head;
        per_cpu(tasklet_hi_vec, cpu).tail =
            &per_cpu(tasklet_hi_vec, cpu).head;
    }

    /* 开启常规tasklet */
    open_softirq(TASKLET_SOFTIRQ, tasklet_action);
    /* 开启高优先级tasklet */
    open_softirq(HI_SOFTIRQ, tasklet_hi_action);
}


/* 开启软中断 */
void open_softirq(int nr, void (*action)(struct softirq_action *))
{
    softirq_vec[nr].action = action;
}

可以看到,TASKLET_SOFTIRQ的action操作使用了tasklet_action()函数,HI_SOFTIRQ的action操作使用了tasklet_hi_action()函数,这两个函数我们需要结合tasklet进行说明。我们也可以看看其他的软中断使用了什么函数:

    open_softirq(TIMER_SOFTIRQ, run_timer_softirq);
    open_softirq(NET_TX_SOFTIRQ, net_tx_action);
    open_softirq(NET_RX_SOFTIRQ, net_rx_action);
    open_softirq(BLOCK_SOFTIRQ, blk_done_softirq);
    open_softirq(BLOCK_IOPOLL_SOFTIRQ, blk_iopoll_softirq);
    open_softirq(SCHED_SOFTIRQ, run_rebalance_domains);
    open_softirq(HRTIMER_SOFTIRQ, run_hrtimer_softirq);
    open_softirq(RCU_SOFTIRQ, rcu_process_callbacks);

除了TASKLET_SOFTIRQHI_SOFTIRQ,其他的软中断更多地是用于特定的设备和环境,对于我们普通的IO驱动和设备而已,使用的软中断几乎都是TASKLET_SOFTIRQHI_SOFTIRQ,而系统为了对这些不同IO设备进行统一的处理,就在TASKLET_SOFTIRQHI_SOFTIRQ的action函数中使用到了tasklet。

  对于每个CPU,都有一个irq_cpustat_t的数据结构,里面有一个__softirq_pending变量,这个变量很重要,用于表示该CPU的哪个软中断处于挂起状态,在软中断处理时可以根据此值跳过不需要处理的软中断,直接处理需要处理的软中断。内核使用local_softirq_pending()获取此CPU的__softirq_pending的值。

  当使用open_softirq设置好某个软中断的action指针后,该软中断就会开始可以使用了,其实更明了地说,从中断初始化完成开始,即使所有的软中断都没有使用open_softirq()进行初始化,软中断都已经开始使用了,只是所有软中断的action都为空,系统每次执行到软中断都没有软中断需要执行罢了。

在每个CPU上一次软中断处理的一个典型流程是:

1, 硬中断执行完毕,开中断。
2, 检查该CPU是否处于嵌套中断的情况,如果处于嵌套中,则不执行软中断,也就是在最外层中断才执行软中断。
3, 执行软中断,设置一个软中断执行最多使用时间和循环次数(10次)。
4, 进入循环,获取CPU的__softirq_pending的副本。
5,执行此__softirq_pending副本中所有需要执行的软中断。
6,如果软中断执行完毕,退出中断上下文。
7,如果还有软中断需要执行(在软中断期间又发发生了中断,产生了新的软中断,新的软中断记录在CPU的__softirq_pending上,而我们的__softirq_pending只是个副本)。
8 , 检查此次软中断总共使用的时间和循环次数,条件允许继续执行软中断,循环次数减一,并跳转到第4步。

具体看一下代码,首先在irq_exit()中会检查是否需要进行软中断处理:

void irq_exit(void)
{
#ifndef __ARCH_IRQ_EXIT_IRQS_DISABLED
    local_irq_disable();
#else
    WARN_ON_ONCE(!irqs_disabled());
#endif

    account_irq_exit_time(current);
    /* 减少preempt_count的硬中断计数器 */
    preempt_count_sub(HARDIRQ_OFFSET);

    /* in_interrupt()会检查preempt_count上的软中断计数器和硬中断计数器来判断是否处于中断嵌套中 */
    /* local_softirq_pending()则会检查该CPU的__softirq_pending变量,是否有软中断挂起 */
    if (!in_interrupt() && local_softirq_pending())
        invoke_softirq();

    tick_irq_exit();
    rcu_irq_exit();
    trace_hardirq_exit(); /* must be last! */
}

再进入到invoke_softirq():

static inline void invoke_softirq(void)
{

    if (!force_irqthreads) {
#ifdef CONFIG_HAVE_IRQ_EXIT_ON_IRQ_STACK
        /*
         * We can safely execute softirq on the current stack if
         * it is the irq stack, because it should be near empty
         * at this stage.
         */
        /* 软中断处理函数 */
        __do_softirq();
#else
        /*
         * Otherwise, irq_exit() is called on the task stack that can
         * be potentially deep already. So call softirq in its own stack
         * to prevent from any overrun.
         */
        do_softirq_own_stack();
#endif
    } else {
        /* 如果强制使用软中断线程进行软中断处理,会通知调度器唤醒软中断线程ksoftirqd */
        wakeup_softirqd();
    }
}

 重点就在__do_softirq()中:

asmlinkage __visible void __do_softirq(void)
{
    /* 为了防止软中断执行时间太长,设置了一个软中断结束时间 */
    unsigned long end = jiffies + MAX_SOFTIRQ_TIME;
    /* 保存当前进程的标志 */
    unsigned long old_flags = current->flags;
    /* 软中断循环执行次数: 10次 */
    int max_restart = MAX_SOFTIRQ_RESTART;
    /* 软中断的action指针 */
    struct softirq_action *h;
    bool in_hardirq;
    __u32 pending;
    int softirq_bit;

    /*
     * Mask out PF_MEMALLOC s current task context is borrowed for the
     * softirq. A softirq handled such as network RX might set PF_MEMALLOC
     * again if the socket is related to swap
     */
    current->flags &= ~PF_MEMALLOC;

    /* 获取此CPU的__softirq_pengding变量值 */
    pending = local_softirq_pending();
    /* 用于统计进程被软中断使用时间 */
    account_irq_enter_time(current);

    /* 增加preempt_count软中断计数器,也表明禁止了调度 */
    __local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET);
    in_hardirq = lockdep_softirq_start();

/* 循环10次的入口,每次循环都会把所有挂起需要执行的软中断执行一遍 */
restart:
    /* 该CPU的__softirq_pending清零,当前的__softirq_pending保存在pending变量中 */
    /* 这样做就保证了新的软中断会在下次循环中执行 */
    set_softirq_pending(0);

    /* 开中断 */
    local_irq_enable();

    /* h指向软中断数组头 */
    h = softirq_vec;

    /* 每次获取最高优先级的已挂起软中断 */
    while ((softirq_bit = ffs(pending))) {
        unsigned int vec_nr;
        int prev_count;
        /* 获取此软中断描述符地址 */
        h += softirq_bit - 1;

        /* 减去软中断描述符数组首地址,获得软中断号 */
        vec_nr = h - softirq_vec;
        /* 获取preempt_count的值 */
        prev_count = preempt_count();

        /* 增加统计中该软中断发生次数 */
        kstat_incr_softirqs_this_cpu(vec_nr);

        trace_softirq_entry(vec_nr);
        /* 执行该软中断的action操作 */
        h->action(h);
        trace_softirq_exit(vec_nr);

        /* 之前保存的preempt_count并不等于当前的preempt_count的情况处理,也是简单的把之前的复制到当前的preempt_count上,这样做是防止最后软中断计数不为0导致系统不能够执行调度 */
        if (unlikely(prev_count != preempt_count())) {
            pr_err("huh, entered softirq %u %s %p with preempt_count %08x, exited with %08x?\n",
                   vec_nr, softirq_to_name[vec_nr], h->action,
                   prev_count, preempt_count());
            preempt_count_set(prev_count);
        }
        /* h指向下一个软中断,但下个软中断并不一定需要执行,这里只是配合softirq_bit做到一个处理 */
        h++;
        pending >>= softirq_bit;
    }

    rcu_bh_qs();
    /* 关中断 */
    local_irq_disable();

    /* 循环结束后再次获取CPU的__softirq_pending变量,为了检查是否还有软中断未执行 */
    pending = local_softirq_pending();
    /* 还有软中断需要执行 */
    if (pending) {
        /* 在还有软中断需要执行的情况下,如果时间片没有执行完,并且循环次数也没到10次,继续执行软中断 */
        if (time_before(jiffies, end) && !need_resched() &&
            --max_restart)
            goto restart;
        /* 这里是有软中断挂起,但是软中断时间和循环次数已经用完,通知调度器唤醒软中断线程去执行挂起的软中断,软中断线程是ksoftirqd,这里只起到一个通知作用,因为在中断上下文中是禁止调度的 */
        wakeup_softirqd();
    }

    lockdep_softirq_end(in_hardirq);
    /* 用于统计进程被软中断使用时间 */
    account_irq_exit_time(current);
    /* 减少preempt_count中的软中断计数器 */
    __local_bh_enable(SOFTIRQ_OFFSET);
    WARN_ON_ONCE(in_interrupt());
    /* 还原进程标志 */
    tsk_restore_flags(current, old_flags, PF_MEMALLOC);
}

内核代码目录/kernel/softirq.c

三、tasklet

软中断有多种,部分种类有自己特殊的处理,如从NET_TX_SOFTIRQNET_RT_SOFTIRQBLOCK_SOFTIRQ等,而如HI_SOFTIRQTASKLET_SOFTIRQ则是专门使用tasklet。它是在I/O驱动程序中实现可延迟函数的首选方法,如上一句所说,它建立在HI_SOFTIRQTASKLET_SOFTIRQ这两种软中断之上,多个tasklet可以与同一个软中断相关联,系统会使用一个链表组织他们,而每个tasklet执行自己的函数处理。而HI_SOFTIRQTASKLET_SOFTIRQ这两个软中断并没有什么区别,他们只是优先级上的不同而已,系统会先执行HI_SOFTIRQ的tasklet,再执行TASKLET_SOFTIRQ的tasklet。同一个tasklet不能同时在几个CPU上执行,一个tasklet在一个时间上只能在一个CPU的软中断链上,不能同时在多个CPU的软中断链上,并且当这个tasklet正在执行时,其他CPU不能够执行这个tasklet。也就是说,tasklet不必要编写成可重入的函数。

 系统会为每个CPU维护两个链表,用于保存HI_SOFTIRQ的tasklet和TASKLET_SOFTIRQ的tasklet,这两个链表是tasklet_vectasklet_hi_vec,它们都是双向链表,如下:

struct tasklet_head {
    struct tasklet_struct *head;
    struct tasklet_struct **tail;
};

static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec);
static DEFINE_PER_CPU(struct tasklet_head, tasklet_hi_vec);

 在softirq_init()函数中,会将每个CPU的tasklet_vec链表和tasklet_hi_vec链表进行初始化,将他们的头尾相连,实现为一个空链表。由于tasklet_vectasklet_hi_vec处理方式几乎一样,只是软中断的优先级别不同,我们只需要理解系统如何对tasklet_vec进行处理即可。需要注意的是,tasklet_vec链表都是以顺序方式执行,并不会出现后一个先执行,再到前一个先执行(在软中断期间被中断的情况),之后的代码详细说明。

tasklet简单来说,就是一个处理函数的封装,类似于硬中断中的irqaction结构。一般来说,在一个驱动中如果需要使用tasklet进行软中断的处理,只需要一个中断对应初始化一个tasklet,它可以在每次中断产生时重复使用。系统使用tasklet_struct结构进行描述一个tasklet,而且对于同一个tasklet_struct你可以选择放在tasklet_hi_vec链表或者tasklet_vec链表上。

struct tasklet_struct
{
    struct tasklet_struct *next;      /* 指向链表下一个tasklet */
    unsigned long state;              /* tasklet状态 */
    atomic_t count;                   /* 禁止计数器,调用tasklet_disable()会增加此数,tasklet_enable()减少此数 */
    void (*func)(unsigned long);      /* 处理函数 */
    unsigned long data;               /* 处理函数使用的数据 */
};

 tasklet状态主要分为以下两种:

TASKLET_STATE_SCHED:这种状态表示此tasklet处于某个tasklet链表之上(可能是tasklet_vec也可能是tasklet_hi_vec)。
TASKLET_STATE_RUN:表示此tasklet正在运行中。

  这两个状态主要就是用于防止tasklet同时在几个CPU上运行和在同一个CPU上交错执行。
  
而func指针就是指向相应的处理函数。在编写驱动时,可以使用tasklet_init()函数或者DECLARE_TASKLET宏进行一个task_struct结构的初始化,之后可以使用tasklet_schedule()或者tasklet_hi_schedule()将其放到相应链表上等待CPU运行。我们使用一张图描述一下软中断和tasklet结合运行的情况:
这里写图片描述

每个软中断都有自己的action函数,在HI_SOFTIRQTASKLET_SOFTIRQ的action函数中,就用到了它们对应的TASKLET_HI_VEC链表和TASKLET_VEC链表,并依次顺序执行链表中的每个tasklet结点。

  在SMP系统中,我们会遇到一个问题:两个CPU都需要执行同一个tasklet的情况,虽然一个tasklet只能放在一个CPU的tasklet_vec链表或者tasklet_hi_vec链表上,但是这种情况是有可能发生的,设想一下,中断在CPU1上得到了响应,并且它的tasklet放到了CPU1的tasklet_vec上进行执行,而当中断的tasklet上正在执行时,此中断再次发生,并在CPU2上进行了响应,此时CPU2将此中断的tasklet放到CPU2的tasklet_vec上,并执行到此中断的tasklet。

  实际上,为了处理这种情况,在HI_SOFTIRQTASKLET_SOFTIRQ的action函数中,会先将对应的tasklet链表取出来,并把对应的tasklet链表的head和tail清空,如果在执行过程中,某个tasklet的state为TASKLET_STATE_RUN状态,则把此tasklet加入到原先已清空的tasklet链表的末尾,然后设置__softirq_pending变量,这样,在下次循环软中断的过程中,会再次运行这个tasklet。

TASKLET_SOFTIRQ的action处理:

static void tasklet_action(struct softirq_action *a)
{
    struct tasklet_struct *list;

    local_irq_disable();
    /* 将tasklet链表从该CPU中拿出来 */
    list = __this_cpu_read(tasklet_vec.head);
    /* 将该CPU的此软中断的tasklet链表清空 */
    __this_cpu_write(tasklet_vec.head, NULL);
    __this_cpu_write(tasklet_vec.tail, this_cpu_ptr(&tasklet_vec.head));
    local_irq_enable();

    /* 链表已经处于list中,并且该CPU的tasklet_vec链表为空 */
    while (list) {
        struct tasklet_struct *t = list;

        list = list->next;

        /* 检查并设置该tasklet为TASKLET_STATE_RUN状态 */
        if (tasklet_trylock(t)) {
            /* 检查是否被禁止 */
            if (!atomic_read(&t->count)) {
                /* 清除其TASKLET_STATE_SCHED状态 */
                if (!test_and_clear_bit(TASKLET_STATE_SCHED,
                            &t->state))
                    BUG();
                /* 执行该tasklet的func处理函数 */
                t->func(t->data);
                /* 清除该tasklet的TASKLET_STATE_RUN状态 */
                tasklet_unlock(t);
                continue;
            }
            tasklet_unlock(t);
        }

        /* 以下为tasklet为TASKLET_STATE_RUN状态下的处理 */
        /* 禁止中断 */
        local_irq_disable();
        /* 将此tasklet添加的该CPU的tasklet_vec链表尾部 */
        t->next = NULL;
        *__this_cpu_read(tasklet_vec.tail) = t;
        __this_cpu_write(tasklet_vec.tail, &(t->next));
        /* 设置该CPU的此软中断处于挂起状态,设置irq_cpustat_t的__sofirq_pending变量,这样在软中断的下次执行中会再次执行此tasklet */
        __raise_softirq_irqoff(TASKLET_SOFTIRQ);
        /* 开启中断 */
        local_irq_enable();
    }
}

软中断处理线程
  当有过多软中断需要处理时,为了保证进程能够得到一个满意的响应时间,设计时给定软中断一个时间片和循环次数,当时间片和循环次数到达但软中断又没有处理完时,就会把剩下的软中断交给软中断处理线程进行处理,这个线程是一个内核线程,其作为一个普通进程,优先级是120。其核心处理函数是run_ksoftirqd(),其实此线程的处理也很简单,就是调用了上面的__do_softirq()函数:

/* 在smpboot_thread_fun的一个死循环中被调用 */
static void run_ksoftirqd(unsigned int cpu)
{
    /* 禁止中断,在__do_softirq()中会开启 */
    local_irq_disable();
    /* 检查该CPU的__softirq_pending是否有软中断被挂起 */
    if (local_softirq_pending()) {
        /*
         * We can safely run softirq on inline stack, as we are not deep
         * in the task stack here.
         */
        /* 执行软中断 */
        __do_softirq();
        rcu_note_context_switch(cpu);
        /* 开中断 */
        local_irq_enable();
        /* 检查是否需要调度 */
        cond_resched();
        return;
    }
    /* 开中断 */
    local_irq_enable();
}

四、工作队列

工作队列(work queue)是另外一种将工作推后执行的形式,他和我们前面讨论过的其他形式完全不同。工作队列可以把工作推后,交由一个内核线程去执行——这个下半部总是会在进程上下文执行。这样,通过工作队列执行的代码能占尽进程上下文的所有优势,最重要的是工作队列允许重新调度甚至是睡眠。

如果你需要用一个可以重新调度的实体来执行你的下半部处理,你应该使用工作队列,它是唯一能在进程上下文运行的下半部实现的机制,也只有它才可以睡眠,这意味着你在你需要获得大量的内存时,在你需要获取信号量时,在你需要执行阻塞式的IO操作时,它都会非常有用,如果你不需要用一个内核线程来推后执行工作,那么就考虑使用tasklet吧!

工作队列的实现

工作队列子系统是一个用于创建内核线程的接口,通过它创建的进程负责执行由内核其他部分排到队列里的其他任务。它创建的这些内核线程被称作工作者线程。工作队列可以让驱动程序创建一个专门的工作者线程来处理需要推后的工作。不过,工作队列子系统提供了一个默认的工作者线程来处理这些工作。因此,工作队列最基本的表现形式就转变成了一个把需要推后执行的任务交给特定的通用线程这样一个接口。

表示线程的数据结构

工作者线程用workqueue_struct结构表示:

struct workqueue_struct {
           struct cpu_workqueue_struct cpu_wq[NR_CPUS];
           const char *name;
           struct   list_head   list;
          };

该结构内是一个由cpu_workqueue_struct结构组成的数组,定义在kernel/workqueue.c中,数组的每一项对应一个系统中的处理器。每个工作者线程都对应这样的cpu_workqueue_struct结构体。cpu_workqueue_struct是kernel/workqueue.c中的核心数据结构:

struct cpu_workqueue_struct     {
                spinlock_t    lock;             /* 锁定以便保护该结构体 */
                long    romove_sequeue; /* 最近一个被加上的(下一个要运行的) */
                long   insert_sequeue;     /*下一个要加上的   */
                wait_queue_head_t    more_work;
                wait_queue_head_t     work_done;
                struct   workqueue_struct   *wq;           /* 有关联的workqueue_struct结构 */
                task_t    *thread;                                 /* 有关联的线程 */
               int   run_depth;                                     /* run_workqueue()循环深度   */
               };

由此可以看出,每个工作者线程类型关联一个自己的workqueue_struct。在该结构体里面,给每个线程分配一个cpu_workqueue_struct,因而也就是给每个处理器分配一个,因为每个处理器都有一个该类型的线程。

表示工作的数据结构

所有工作者线程都是用普通的内核线程实现的,它们都要执行worker_thread()函数。在它初始化完以后,这个函数(worker_thread)开始休眠。当有操作被插入到队列的时候,线程就会被唤醒,以便执行这些操作。当没有剩余的操作时,它又会继续睡眠。

工作用

struct    work_struct {
                   unsigned   long   pending;        /* 这个工作是否正在等待处理 */
                   struct   list_head entry;             /* l连接所有工作的链表 */
                   void   (* func) (void *);               /* 处理函数 */
                   void   *wq_data;                         /* 内部使用 */
                   struct timer_list timer;               /* 延迟的工作队列所用到的定时器 */
};

这些结构体被连接成链表,在每个处理器的每种类型的队列都对应这样一个链表。当一个工作者线程被唤醒时,它会执行它的链表上的所有工作。当工作完毕时,他会将相应的work_struct对象从链表中移去,当链表上不再有对象的时候,它就会继续睡眠。

使用工作队列

(1)创建推后的工作
首先要做的是实际创建一些需要推后执行的工作。可以通过DECLARE_WORK在编译时静态的创建该结构体:

DECLARE_WORK(name, void (*func) (void *), void *data);

这样就会静态的创建一个名为name,处理函数为func,参数为data的work_struct结构体。也可以在运行时通过指针创建一个工作:

INIT_WORK(struct work_struct *work, void (*func)(void *), void   *data);

这样就动态的初始化了一个由work指向的工作。

(2)工作队列的处理函数

原型是:

void   work_handler(void   *data)

这个函数会由一个工作者线程执行,因此,函数会运行在进程上下文中,默认情况下,允许响应中断,并且不持有任何锁,如果需要,函数可以睡眠,注意的是,尽管操作处理函数运行在进程上下文中,但它不能访问用户空间,因为内核线程在用户空间没有相关的内存映射,通常在系统调用发生时,内核会代表用户空间的进程运行,此时它才能访问用户空间,也只有在此时它才会映射用户空间的内存。

(3)对工作进行调度
现在工作已经创建,我们可以调度它了,要把给定工作的处理函数提交给默认的events工作线程,只需调用: schedule_work(&work); work马上就会被调度,一旦其所在的处理器上的工作者线程被唤醒,它就会被执行。

(4)刷新操作
刷新工作队列的函数就是确保在卸载模块之前,要确保一些操作已经执行完毕了,该函数如下:

void flush_scheduled_work(void);

该函数会一直等待,直到队列中所有对象都被执行以后才返回,在等待所以待处理的工作执行的时候,该函数会进入休眠状态,所以只能在进程上下文中使用它。

(5)创建新的工作队列

当缺省的队列不能满足你的需要时,你应该创建一个新的工作队列和与之对应的工作者线程。

2011-04-08 10:28:00 myspor 阅读数 370

Kernel中断处理模型

 
 

Kernel中断处理模型

内核版本: Linux 2.6.18_pro500 (Montavista)

Kernel中断处理模型结构图如下:

clip_image001

下面简单介绍一下:

1. Linux定义了名字为irq_desc的中断例程描述符表:(include/linux/irq.h)

struct irqdesc irq_desc[NR_IRQS];

NR_IRQS表示中断源的数目。

2. irq_desc[]是一个指向irq_desc_t结构的数组, irq_desc_t结构是各个设备中断服务例程的描述符。Irq_desc_t结构体中的成员action指向该中断号对应的irqaction结构体链表。Irqaction结构体定义如下:

/* include/linux/interrupt.h */
struct irqaction {
    irq_handler_t handler; /* 指向中断服务程序 */
unsigned long flags; /* 中断标志 */
unsigned long mask; /* 中断掩码 */
const char *name; /* I/O设备名
    void *dev_id;            /* 设备标识 */
struct irqaction *next; /* 指向下一个描述符 */
int irq; /* IRQ线 */
struct proc_dir_entry *dir; /* 指向IRQn相关的/proc/irq/n目录的描述符 */
};

其中关键的handler成员指向了该设备的中断服务程序,由执行request_irq时建立。

3. 在驱动程序初始化时,若使用到中断,通常调用函数request_irq()建立该驱动程序对应的irqaction结构体,并把它登记到irq_desc [irq_num]->action链表中。Iqr_num为驱动程序申请的中断号。

request_irq()函数的原型如下:

/* kernel/irq/manage.c */
int request_irq(unsigned int irq,
        irqreturn_t (*handler)(int, void *, struct pt_regs *),
unsigned long irqflags,
        const char *devname,
        void *dev_id);

参数irq是设备中断求号,在向irq_desc []数组登记时,它做为数组的下标。把中断号为irq的irqaction结构体的首地址写入irq_desc [irq]->action。这样就把设备的中断请求号与该设备的中断服务例程irqaction联系在一起了。

这样当CPU接收到中断请求后,就可以根据中断号通过irq_desc []找到该设备的中断服务程序。流程如上图所示。

4. 关于共享中断

共享中断的不同设备的iqraction结构体都会添加进该中断号对应的irq_desc结构体的action成员所指向的irqaction链表内。当内核发生中断时,它会依次调用该链表内所有的handler函数。因此,若驱动程序需要使用共享中断机制,其中断处理函数必须有能力识别是否是自己的硬件产生了中断。通常是通过读取该硬件设备提供的中断flag标志位进行判断。

2017-03-08 08:02:53 sunlei0625 阅读数 218
前面了解了软中断的主要结构,对于软中断初始化主要时调用open_softirq()函数
其对前面介绍的softirq_vec数组赋值:
void open_softirq(int nr, void (*action)(struct softirq_action *))
{
 softirq_vec[nr].action = action;
}

块设备:blk_softirq_init open_softirq(BLOCK_SOFTIRQ, blk_done_softirq);

网络设备:net_dev_init open_softirq(NET_TX_SOFTIRQ, net_tx_action); open_softirq(NET_RX_SOFTIRQ, net_rx_action);

调度中负载均衡:init_sched_fair_class open_softirq(SCHED_SOFTIRQ, run_rebalance_domains);

任务延时tasklet:softirq_init open_softirq(TASKLET_SOFTIRQ, tasklet_action); open_softirq(HI_SOFTIRQ, tasklet_hi_action);    定时器类处理:init_timers open_softirq(TIMER_SOFTIRQ, run_timer_softirq);

RCU类处理:rcu_init open_softirq(RCU_SOFTIRQ, rcu_process_callbacks);

 
 
 
2011-06-29 11:07:00 swt914 阅读数 712

Kernel 中断处理图


1. Linux 定义了名字为irq_desc 的中断例程描述符表:(include/linux/irq.h)

    struct irqdesc irq_desc[NR_IRQS];

NR_IRQS 表示中断源的数目。

2. irq_desc []是一个指向irq_desc_t 结构的数组, irq_desc_t 结构是各个设备中断服务例程的描述符。Irq_desc_t 结构体中的成员action 指向该中断号对应的irqaction 结构体链表。Irqaction 结构体定义如下:

/* include/linux/interrupt.h */
struct irqaction {
     irq_handler_t handler;    /* 指向中断服务程序 */
    unsigned long flags;     /* 中断标志 */
    unsigned long mask;      /* 中断掩码 */
    const char * name;         
/* I/O设备名
     void *dev_id;             /* 设备标识 */

    struct irqaction * next; /* 指向下一个描述符 */
    int irq;                  /* IRQ线 */
    struct proc_dir_entry * dir; /* 指向IRQn相关的/proc/irq/n目录的描述符 */
} ;

   其中关键的handler 成员指向了该设备的中断服务程序,由执行request_irq 时建立。

 

3. 在驱动程序初始化时,若使用到中断,通常调用函数 request_irq ()建立该驱动程序对应的 irqaction 结构体,并把它登记到 irq_desc [irq_num]->action 链表中。 Iqr_num 为驱动程序申请的中断号。

request_irq ()函数的原型如下:

/* kernel/irq/manage.c */
int request_irq( unsigned int irq,
         irqreturn_t ( * handler) ( int , void * , struct pt_regs * ) ,
        unsigned long irqflags,
        const char * devname,
        void * dev_id) ;

 

参数 irq 是设备中断求号,在向 irq_desc [] 数组登记时,它做为数组的下标。把中断号为 irq irqaction 结构体的首地址写入 irq_desc [irq]->action 。这样就把设备的中断请求号与该设备的中断服务例程 irqaction 联系在一起了。

这样当 CPU 接收到中断请求后,就可以根据中断号通过 irq_desc [] 找到该设备的中断服务程序。流程如上图所示。

 

4. 关于共享中断

共享中断的不同设备的 iqraction 结构体都会添加进该中断号对应的 irq_desc 结构体的 action 成员所指向的 irqaction 链表内。当内核发生中断时,它会依次调用该链表内所有的 handler 函数。因此,若驱动程序需要使用共享中断机制,其中断处理函数必须有能力识别是否是自己的硬件产生了中断。通常是通过读取该硬件设备提供的中断 flag 标志位进行判断。

2013-08-05 21:02:43 u011461299 阅读数 1058
一般来说,在一个device driver中实现中断,是比较简单的,如上面的RTC的例子。其无非就是:

1.       定义一个IRQ No。如何将Hardware中断信息map到我们的IRQ No就是get_irqnr_and_base要做得事情,get_irqnr_and_base是一个macro,后面会详细分析之。这个宏的实现往往也是我们如果要将一个标准的Linux Kernel移植到我们的SOC chip上面要做得事情。

2.       实现对应device driverirq handler

3.       device driveropen中通过request_irq()installirq handler

1.1                  RTC Device Driver中断相关code的分析

1.       IRQ No

#define IRQ_RTC        XXXX_IRQ(3)

我们将对应的Hardware Source定义到一张表中,get_irqnr_and_base就是先从SOC Interrupt Controller中获得该Interrupt硬件信息(这个信息可能就是一个interrtup hardware index,或者其它复杂点的),然后从这张表中查询得到RTC AlarmIRQ No

2.       Irq handle

其实现如下:它通过rtc_update_irqà wake_up_interruptible(&rtc->irq_queue);唤醒(通知)select等待RTC alarmprocess

static irqreturn_t xxxx_rtc_alarmirq(int irq, void *id)

{

  struct rtc_device *rdev = id;

  //clear the alarm event. 

  xxxx_rtc_clear_status(XXXX_RTC_ALARM_EVENT );

  //wake up the rtc->irq_queue

  rtc_update_irq(rdev, 1, RTC_AF | RTC_IRQF);

  return IRQ_HANDLED;

}

3.       通过request 安装irq handler

static int xxxx_rtc_open(struct device *dev)

{

  struct platform_device *pdev = to_platform_device(dev);

  struct rtc_device *rtc_dev = platform_get_drvdata(pdev);

  int ret;

  ret = request_irq(IRQ_RTC, xxxx_rtc_alarmirq,

                  IRQF_DISABLED,  "xxxx-rtc alarm", rtc_dev);

  if (ret) {

         dev_err(dev, "IRQ%d error %d\n", xxxx_rtc_alarmno, ret);

         return ret;

  }

  return ret;

}

static void xxxx_rtc_release(struct device *dev)

{

  struct platform_device *pdev = to_platform_device(dev);

  struct rtc_device *rtc_dev = platform_get_drvdata(pdev);

  //do not clear AIE here, it may be needed for alarm even after the rtc device file has been closed.

  free_irq(xxxx_rtc_alarmno, rtc_dev);

}

1.2                  常用的接口介绍

接口名称

描述

request_irq()

内核其他的driver模块调用该接口用来分配一个interrupt line。

参数描述如下:

irq: Interrupt line to allocate

handler:该中断的handler。

irqflags: Interrupt type flags。

IRQF_SHARED: Interrupt is shared

IRQF_DISABLED:   Disable local interrupts while processing devname: An ascii name for the claiming device

dev_id: A cookie passed back to the handler function,IRQF_SHARED类型的中断需要该参数,否则无法区分中断。

setup_irq()

类似request_irq,只是需要我们自己根据request_irq中的后四个参数自己建立struct irqaction intstance。

free_irq()

内核其他的driver模块调用该接口用来释放一个interrupt,在释放之后,该interrupt line可能仍然是有效的,因为可能有多个driver共享一个interrupt line。

参数描述如下:

irq: Interrupt line to free

dev_id: Device identity to free

disable_irq()

disable an irq and wait for completion,这里禁止的不是全局中断而是该interrupt line上的中断。

参数描述如下:

irq: Interrupt to disable

 

enable_irq()

enable handling of an irq。

参数描述如下:

irq: Interrupt to enable

enable_irq和disable_irq可以嵌套使用。

1.2.1             Interruptenable/disable

Interruptenabledisable有两个层次,一个是ARM Chip的全局Interruptenabledisable,二是各个Soc Interrupt Controller对于各个不同的IRQ sourcemaskunmask

1.       ARM Interrupt Enablelocal_irq_enableinclude/linux/irqflags.h中)raw_local_irq_enableinclude/asm-arm/irqflags.h

2.       ARM interrupt Disablelocal_irq_disableraw_local_irq_disable

3.       Soc Interrupt Controllerirq disabledisable_irqdisable_irqà disable_irq_nosyncà desc->chip->disable(irq);

其中desc->chip是在s3c2451_init_irq中由set_irq_chip设定的。set_irq_chipàirq_chip_set_defaults中将chip->disable设置为:default_disable,而default_disable什么也没有做。那么它是如何mask这个irq呢?这是因为:(这里看起来有点别扭)

       disable_irqà disable_irq_nosync àdesc->status |= IRQ_DISABLED;

       handle_level_irq中会:

if (unlikely(!action || (desc->status & IRQ_DISABLED)))  goto out_unlock;

注意:这里IRQ_DISABLED(用于关闭某个特定的irq)和IRQF_DISABLED(用关闭arm global irq)是两个不同的macro,其概念也是不一样的。

4.       Soc Interrupt Controllerirq unmaskenable_irq

 

1.3                  Hardware中断发生到各自定义的中断handler被执行的全过程分析

其实有了上面的基础,对于我们一般的device driverdevelopment来说似乎已经可以了,但是我们不仅要知其然而要知其所以然,更为重要的是如果我们要分析一些复杂的问题、或者我们要移植Linux Kernel到某个Hardware platform上去,我们必须对此过程非常清楚。

在这个分析的过程中,我们会不断讨论一些设计原则,这样才可以真正明白这些设计要求的背后原因,这样也可以更好的灵活应变了。

1.3.1             ARM Linuxexception vector的来龙去脉――启动过程关于IRQsetting

1.3.1.1      ARM Exception Vector介绍

ARM CPU无论在产生一个IRQ或是SWI或是reset等都会跳到ARM exception vector中相应的vector entry执行对应codeARM exception vector可以存储在零地址(Normal Exception Vectors 0x0000 0000 0x0000 001C),也可以是0xFFFF0000High Exception Vectors0xFFFF00000xFFFF001C)。初始设置为Normal Exception Vectors

但是我们知道C programming通常使用零地址NULL为非法地址,所以我们只能选择High Exception Vectors。那么Linux Kernel是如何选择使用High Exception Vectors的呢?

1.3.1.2      Linux Kernle启动时设定High Exception Vectors的全过程

首先我们需要简单说一下Linux Kernel的启动过程,我们不进行详细的分析,如果以后有需要的话,我想我们在以后的Phase2中开一个章节来讲,大概也需要3小时,这里面也是有很多的故事和技巧的。

Bootl/Loader 加载(LoadLinux Kernel imageSDRAM起始地址(这里假定SDRAM的物理地址0x2000 0000)+0x8000的位置以后,Boot/Loader通常通过执行asm("ldr pc, =0x20008000");,这样开始Linux Kernel的启动过程,由于我们的Linux Kernel image是有压缩的(这个问题后面有个讨论),所以我们的Kernel第一步就是解压缩,解压缩之后的Kernel Image至少增加了一倍,并且存储在同一个位置。这是才开始真正的Linux Kernel的启动,入口codearch/arm/kernel/head.S文件中:ENTRY(stext),我们先只看我们关心的:

ENTRY(stext) :

。。。。。。

     bl    __lookup_processor_type            @ r5=procinfo r9=cpuid

     movs      r10, r5                          @ invalid processor (r5=0)?

。。。。。。

     adr   lr, __enable_mmu         @ return address

     add  pc, r10, #PROCINFO_INITFUNC   @ initialise processor

。。。。。。

 

__lookup_processor_type查找到我们processor相关information structure,我们是ARM926,其定义在arch/arm/mm/proc-arm926.S中,如下:(至于它是如何找到了,这里不说明了,有兴趣的朋友结合我们之前的分析技巧不难分析,或者我们以后在讲)

.section ".proc.info.init", #alloc, #execinstr

     .type       __arm926_proc_info,#object

__arm926_proc_info:

     .long       0x41069260                  @ ARM926EJ-S (v5TEJ)

     .long       0xff0ffff0

     .long   PMD_TYPE_SECT | \

            PMD_SECT_BUFFERABLE | \

            PMD_SECT_CACHEABLE | \

            PMD_BIT4 | \

            PMD_SECT_AP_WRITE | \

            PMD_SECT_AP_READ

     .long   PMD_TYPE_SECT | \

            PMD_BIT4 | \

            PMD_SECT_AP_WRITE | \

            PMD_SECT_AP_READ

     b     __arm926_setup

     .long       cpu_arch_name

     .long       cpu_elf_name

     .long      HWCAP_SWP|HWCAP_HALF|HWCAP_THUMB|HWCAP_FAST_MULT|HWCAP_EDSP|HWCAP_JAVA

     .long       cpu_arm926_name

     .long       arm926_processor_functions

     .long       v4wbi_tlb_fns

     .long       v4wb_user_fns

     .long       arm926_cache_fn

     .size       __arm926_proc_info, . - __arm926_proc_info

 

此外其中PROCINFO_INITFUNC的定义在arch/arm/kernel/asm-offsets.c文件中,如下:

DEFINE(PROCINFO_INITFUNC, offsetof(struct proc_info_list, __cpu_flush));

所以      add  pc, r10, #PROCINFO_INITFUNC   @ initialise processor也就是b    __arm926_setup了。

再看:

     .type       __arm926_setup, #function

__arm926_setup:

     mov r0, #0

     mcr  p15, 0, r0, c7, c7          @ invalidate I,D caches on v4

     mcr  p15, 0, r0, c7, c10, 4            @ drain write buffer on v4

#ifdef CONFIG_MMU

     mcr  p15, 0, r0, c8, c7          @ invalidate I,D TLBs on v4

#endif

#ifdef CONFIG_CPU_DCACHE_WRITETHROUGH

     mov r0, #4                           @ disable write-back on caches explicitly

     mcr  p15, 7, r0, c15, c0, 0

#endif

     adr   r5, arm926_crval

     ldmia      r5, {r5, r6}

     mrc  p15, 0, r0, c1, c0          @ get control register v4

     bic   r0, r0, r5

     orr   r0, r0, r6

#ifdef CONFIG_CPU_CACHE_ROUND_ROBIN

     orr   r0, r0, #0x4000                    @ .1.. .... .... ....

#endif

     mov pc, lr

     .size       __arm926_setup, . - __arm926_setup

     /*

      *  R

      * .RVI ZFRS BLDP WCAM

      * .011 0001 ..11 0101

      */

     .type       arm926_crval, #object

arm926_crval:

     crval clear=0x00007f3f, mmuset=0x00003135, ucset=0x00001134

 

这段code执行的结果就是将0x00003135写入r0 regisger,当这段code完成后会执行__enable_mmu(定义在arch/arm/kernel/head.S中,代码就不贴出来了。),__enable_mmu会将r0写入ARM Coprocessor Control Registerwhy?大家不妨想一下,因为这里似乎没有直接进行函数调用或指令跳转呀?)

AAM[3] 2.4可以知道,我们的ARM926Linux Kernel设置为High Vector模式了。

 

1.3.1.3      Linunx Kernel copy Exception Vectors0xFFFF 0000

Arch/arm/kernel/traps.c中:CONFIG_VECTORS_BASE 0xFFFF0000menuconfig的时候定义的。

void __init trap_init(void)

{

  unsigned long vectors = CONFIG_VECTORS_BASE;

  extern char __stubs_start[], __stubs_end[];

  extern char __vectors_start[], __vectors_end[];

  extern char __kuser_helper_start[], __kuser_helper_end[];

  int kuser_sz = __kuser_helper_end - __kuser_helper_start;

  /*

   * Copy the vectors, stubs and kuser helpers (in entry-armv.S)

   * into the vector page, mapped at 0xffff0000, and ensure these

   * are visible to the instruction stream.

   */

  memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);

  memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);

  memcpy((void *)vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz);

  /*

   * Copy signal return handlers into the vector page, and

   * set sigreturn to be a pointer to these.

   */

  memcpy((void *)KERN_SIGRETURN_CODE, sigreturn_codes, sizeof(sigreturn_codes));

  flush_icache_range(vectors, vectors + PAGE_SIZE);

  modify_domain(DOMAIN_USER, DOMAIN_CLIENT);

}

 

那么这里还有一个问题就是:现在MMU已经被打开了,0xffff0000Virtual Address,该地址对应的physical memory什么时候被分配并建立mapping的呢?

start_kernel->setup_arch->paging_init->devicemaps_init

。。。。。。。

     vectors = alloc_bootmem_low_pages(PAGE_SIZE);

。。。。。。。

     /*

      * Create a mapping for the machine vectors at the high-vectors

      * location (0xffff0000).  If we aren't using high-vectors, also

      * create a mapping at the low-vectors virtual address.

      */

     map.pfn = __phys_to_pfn(virt_to_phys(vectors));

      map.virtual = 0xffff0000;

     map.length = PAGE_SIZE;

     map.type = MT_HIGH_VECTORS;

     create_mapping(&map);

。。。。。。

1.3.2             ARM Linux中断处理全过程分析

ArmLinux 2.6.23 exception vector table locates in arch/arm/kernel/entry-armv.S,我们之前在分析system call的时候已经见过:

     .globl      __vectors_start

__vectors_start:

     swi  SYS_ERROR0

     b     vector_und + stubs_offset

     ldr    pc, .LCvswi + stubs_offset

     b     vector_pabt + stubs_offset

     b     vector_dabt + stubs_offset

     b     vector_addrexcptn + stubs_offset

      b     vector_irq + stubs_offset

     b     vector_fiq + stubs_offset

 

     .globl      __vectors_end

__vectors_end:

 

1.3.2.1      vector_irq

下面我们就从vector_irq开始我们的分析。如果我们用查找的方法的是没有找到vector_irq的定义的,vector_irq其实在entry-armv.Smacro vector_stub来定义的:

  .macro    vector_stub, name, mode, correction=0

  .align      5

vector_\name:

  .if \correction

  sub  lr, lr, #\correction

  .endif

 

  @

  @ Save r0, lr_<exception> (parent PC) and spsr_<exception>

  @ (parent CPSR)

  @

  stmia      sp, {r0, lr}              @ save r0, lr

  mrs  lr, spsr

  str   lr, [sp, #8]             @ save spsr

 

  @

  @ Prepare for SVC32 mode.  IRQs remain disabled.

  @

  mrs  r0, cpsr

  eor   r0, r0, #(\mode ^ SVC_MODE)

  msr  spsr_cxsf, r0

 

  @

  @ the branch table must immediately follow this code

  @

  and  lr, lr, #0x0f

  mov r0, sp

  ldr    lr, [pc, lr, lsl #2]

  movs       pc, lr                     @ branch to handler in SVC mode

  .endm

 

vector_stub      irq, IRQ_MODE, 4

  .long       __irq_usr               @  0  (USR_26 / USR_32)

  .long       __irq_invalid                  @  1  (FIQ_26 / FIQ_32)

  .long       __irq_invalid                  @  2  (IRQ_26 / IRQ_32)

  .long      __irq_svc               @  3  (SVC_26 / SVC_32)

  。。。。。。

 

上面的这段assembler code不是很好看懂,需要对ARM architure以及ARM lassmebler anguage很清楚,我们这里主要的目的不是去学习ARM,有兴趣的朋友可以熟悉ARM后再回头自己来阅读好了。

现在我们只要知道这段code主要做了如下几件事情就可以了:

1.       Save r0, lr_irq, spsr_irq into the irq stack

2.       prepare entering arm SVC mode from IRQ mode. 之前我们提过IRQ mode时间很短,现在更清楚明白了。

3.       如果发生中断时ARMKernel Mode则调用 __irq_svc,反之__irq_usr.

 

下面我们仅仅以__irq_svc为例子进行说明,但是在开始之前留一个问题给大家思考一下:为什么“b      vector_irq + stubs_offset”?简单提示一下:请结合trap_init()来理解。

     .equstubs_offset, __vectors_start + 0x200 - __stubs_start

 

1.3.2.2      __irq_svc

我们先不考虑pre-emptive,事实上在U3No1pre-emptive都是disabled。这样简单来说它其实就是:

1.       svc_entry macrosave the processor context。定义在同一个文件中。

2.       irq_handler macro

我们中说明irq_handler macro

A. 通过get_irqnr_and_base获得IRQ Noget_irqnr_and_base是我们移植的时候需要实现(我们通常实现在include/asm-arm/arch-xxxx/entry-macro.S),它其实就是从HardwareInterrupt source确定IRQ No。。

B. asm_do_IRQ()

到此我们总算可以开始看到C Coder了。

1.3.2.3      asm_do_IRQ()

asm_do_IRQ定义在arch/arm/kernel/irq.c中。

首先我们要记得ARMIRQ已经被ARM chip自己Disable,详见:AAM[3] Section2.6.6。到此时,我们并没有enable它。

它主要完成了:

1.       根据前面IRQ No从系统的global tableirq_desc找到对应struct irq_desc。这张table中记录了所有的irq descriptors

Irq_desc这张table定义在kernel/irq/handle.c中:

struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {

     [0 ... NR_IRQS-1] = {

            .status = IRQ_DISABLED,

            .chip = &no_irq_chip,

            .handle_irq = handle_bad_irq,

            .depth = 1,

            .lock = __SPIN_LOCK_UNLOCKED(irq_desc->lock),

     }

};

irqaction list是由request_irq建立的:include/linux/interrupt.h

struct irqaction {

irq_handler_t handler;//这个handler就是request_irq中传入的irq handler,也就是我们在各个具的device driver中实现的irq handler。这里应该知道why request_irq了吧。这样应该很清楚我们为什么需要request_irq了吧,其实现在kernel/irq/manager.c文件中:request_irq ->setup_irq->p = &desc->action;  *p = new;

  unsigned long flags;

  cpumask_t mask;

  const char *name;

  void *dev_id;

  struct irqaction *next;

  int irq;

  struct proc_dir_entry *dir;

};

2.       asm_do_IRQ->desc_handle_irqàdesc->handle_irq(irq, desc);其实初始定义为handle_bad_irqHandle_irq的是需要我们在porting时设定,以s3c2451为例子其实现在:arch\arm\mach-s3c2451\irq.c中的s3c2451_init_irq->set_irq_handler。通常会依据是edge还是level中断被设置为handle_edge_irqhandle_level_irq

3.      handle_level_irq为例:handle_level_irqàhandle_IRQ_event

A. if (!(action->flags & IRQF_DISABLED))enable ARM global interrupt

B. ret = action->handler(irq, action->dev_id);  这就是我们在reqest_irq中设定的各个具体device driverirq handler了。

C.local_irq_disable关闭ARM global interrupt

4.      asm_do_IRQ->irq_exit->invoke_softirq,此时启动soft irq,这部分我们将在9中详述。这里我们首先要注意的是此时ARM global interrupt处于关闭的状态

 

1.3.3             关机后中断唤醒

enable_irq_wake->set_irq_wake->desc->chip->set_wake(irq, on)s3c2451中的实现见:s3c2451_irq_wake.

同时在具体Device Driver里面要enable_irq_wake设置关机后可以中断唤醒。

没有更多推荐了,返回首页