2016-07-07 21:31:09 fridayLL 阅读数 1272
  • Linux视频教学从入门到精通

    不管你是Linux小白还是有linux基础,通过本课程学习都能让你掌握足够多的linux的实战经验,本课程从Linux安装开始手把手教你如何成为Linux高手,学好服务器端操作系统Linux至关重要,互联网项目离不开Linux,分布式离不开linux,大数据离不开linux,想要高薪更离不开Linux,本课程是你好的教材。

    4958 人正在学习 去看看 任亮
知识要点
一、struct irq_chip、struct irq_desc[]、struct irqaction三者之间的关系
二、Linux内核中中断的初始化流程、中断的注册流程、中断的执行流程
三、多核cpu的中断亲和力和中断负载均衡
四、中断的上半部和下半部

一、struct irq_chip、struct irq_desc[]、struct irqaction三者之间的关
include /linux/irq.h
主要的三个数据结构
struct irq_chip :中断控制器描述符, CPU所对应的一个具体的中断控制器,如早期intel对应的中断控制器为8259a,ioapic_chip。 一个cpu可以有多个irq_chip,即多个中断控制器
struct irq_desc : 中断描述符数组,每一个IRQ对应自己的struct irq_desc对象,共同组成一个
struct irqaction : 中断服务程序描述符,该IRQ对应的一系列中断程序

  





      如图所示为该三个结构体关系,一个中断控制器(irq_chip)对应着一个中断描述符数组(irq_desc),每一个成员都是一个中断号,每一个中断号下面都有具体的中断服务程序(irqaction)链表

1、/*中断描述符*/ ------>一个IRQ对应自己的struct irq_desc对象,多个irq_desc组成irq_desc[ ]数组 
struct irq_desc {/*中断描述符*/

unsigned intirq; /* 该irq_desc具体的中断号 */

irq_flow_handler_thandle_irq;/*该irq线公共中断服务程序*/
struct irq_chip*chip;/*该中断线所属的中断控制器*/
struct msi_desc*msi_desc;
void*handler_data;
void*chip_data;
struct irqaction*action;/*该中断服务程序,区别于公共中断服务程序,这里指向的是中断服务程序的队列头*/
unsigned intstatus; /* 中断线状态*/
unsigned intdepth; /* nested irq disables */
unsigned intwake_depth; /* nested wake enables */
unsigned intirq_count; /* For detecting broken IRQs */    
unsigned longlast_unhandled; /* Aging timer for unhandled count */
unsigned intirqs_unhandled;
const char*name;

} ____cacheline_internodealigned_in_smp;


2、/*中断控制器描述符*/ --->CPU所对应的一个具体的中断控制器,如早期intel对应的中断控制器为8259a etc..
struct irq_desc {
struct irq_chip*chip;/*该中断线所属的中断控制器*/
}

struct irq_chip {
const char*name; /*中断控制器名称*/
unsigned int(*startup)(unsigned int irq);
void(*shutdown)(unsigned int irq);
void(*enable)(unsigned int irq);
void(*disable)(unsigned int irq);

void(*ack)(unsigned int irq);
void(*mask)(unsigned int irq);
void(*mask_ack)(unsigned int irq);
void(*unmask)(unsigned int irq);
void(*eoi)(unsigned int irq);

void(*end)(unsigned int irq);
int(*set_affinity)(unsigned int irq,
const struct cpumask *dest);
int(*retrigger)(unsigned int irq);
int(*set_type)(unsigned int irq, unsigned int flow_type);
int(*set_wake)(unsigned int irq, unsigned int on);

void(*bus_lock)(unsigned int irq);
void(*bus_sync_unlock)(unsigned int irq);

/* Currently used only by UML, might disappear one day.*/
#ifdef CONFIG_IRQ_RELEASE_METHOD
void(*release)(unsigned int irq, void *dev_id);
#endif
/*
* For compatibility, ->typename is copied into ->name.
* Will disappear.
*/
const char*typename;
};

3、/*中断服务程序描述符*/-------> 一个中断服务程序结构体声明
struct irq_desc {
struct irqaction*action;/*中断服务程序,区别于公共中断服务程序,这里指向的是中断服务程序的队列头*/
}
struct irqaction {
irq_handler_t handler;/*中断服务程序*/
unsigned long flags;/*IRQ 中断处理标志*/
const char *name;/*设备名*/
void *dev_id;
struct irqaction *next;/*指向该IRQ向中断请求队列下一个irqaction对象*/
int irq;
struct proc_dir_entry *dir;
irq_handler_t thread_fn;
struct task_struct *thread;
unsigned long thread_flags;
};

共享同一个IRQ先的多个irqaction对象组成的队列,即所谓的中断请求队列。当该IRQ线上产生中断时,请求队列中的中断服务程序将被依次执行

二、Linux内核中中断的初始化流程、中断的注册流程、中断的执行流程
1、中断子系统的初始化 内核代码linux 2.6.30.4
start_kernel
{
trap_init/* arm 为空函数 */
early_irq_init/* irq_desc[NR_IRQS]数组基本初始化 */
init_IRQ/* irq_desc[NR_IRQS] 中断描述符数组初始化 */
{
    for (irq = 0; irq < NR_IRQS; irq++)
irq_desc[irq].status |= IRQ_NOREQUEST | IRQ_NOPROBE;

  init_arch_irq();/* 在setup_arch中init_arch_irq = mdesc->init_irq;*/
--> s3c24xx_init_irq  以s3c24400为例
{
set_irq_chip(irqno, &s3c_irq_chip);/* 根据 irqno 关联对应的irq_desc[irqno]和irq_chip */
set_irq_handler(irqno, handle_edge_irq);/* 设置irq_dest[irqno]公共中断函数 */
set_irq_flags(irqno, IRQF_VALID);/* 设置irq_dest[irqno] flags */
set_irq_chained_handler(IRQ_UART0, s3c_irq_demux_uart0);/* 设置irq_dest[irqno]公共中断函数 */
}
}
}
void __init setup_arch(char **cmdline_p)
{
mdesc = setup_machine(machine_arch_type);/*从machine_arch_type 段中获取 machine_desc结构体 */
init_arch_irq = mdesc->init_irq;/* 其中 mdesc结构体在具体的架构中定义 */
}

#define MACHINE_START(_type,_name) \---->  arch/arm/include/asm/mach/arch.h
static const struct machine_desc __mach_desc_##_type\
 __used \
 __attribute__((__section__(".arch.info.init"))) = {\
.nr = MACH_TYPE_##_type,\
.name= _name,
#define MACHINE_END\

};

MACHINE_START(S3C2440, "SMDK2440") -----> mach-smdk2440.c 
/* Maintainer: Ben Dooks <ben@fluff.org> */
.phys_io= S3C2410_PA_UART,
.io_pg_offst= (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
.boot_params= S3C2410_SDRAM_PA + 0x100,

.init_irq= s3c24xx_init_irq,
.map_io= smdk2440_map_io,
.init_machine= smdk2440_machine_init,
.timer= &s3c24xx_timer,


2、中断服务的注册 内核代码linux 2.6.30.4
request_irq
-->request_threaded_irq
{
desc = irq_to_desc(irq);/* 根据irq号取出其对应的irq_desc */
__setup_irq(irq, desc, action);/* 将struct irqaction *action 添加到desc->action链表中*/
}

3、内核中中断的执行过程 内核代码linux 2.6.30.4
entry_armv.S 
.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)内核模式下中断
.long__irq_invalid@  4
.long__irq_invalid@  5
.long__irq_invalid@  6
.long__irq_invalid@  7
.long__irq_invalid@  8
.long__irq_invalid@  9
.long__irq_invalid@  a
.long__irq_invalid@  b
.long__irq_invalid@  c
.long__irq_invalid@  d
.long__irq_invalid@  e
.long__irq_invalid@  f

__irq_svc: entry_armv.S 
irq_handler
ENDPROC(__irq_svc)

.macro irq_handlerentry_armv.S 
bne asm_do_IRQ

asm_do_IRQ ----> irq.c 
--->generic_handle_irq(irq);
----->generic_handle_irq_desc
------->__do_IRQ(irq);
{
struct irq_desc *desc = irq_to_desc(irq);
action_ret = handle_IRQ_event(irq, desc->action);/* 依次执行 irq对应的desc->action的handler服务例程 */
}

handle_IRQ_event(unsigned int irq, struct irqaction *action)/* 依次执行 irq对应的desc->action的handler服务例程 */
{
do {
ret = action->handler(irq, action->dev_id);
action = action->next;
} while (action);
}

又比如hisi芯片的IRQ执行流程

vector_stub irq, IRQ_MODE, 4 ----> entry-armv.S
.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)
.long__irq_invalid@  4
.long__irq_invalid@  5
.long__irq_invalid@  6
.long__irq_invalid@  7
.long__irq_invalid@  8
.long__irq_invalid@  9
.long__irq_invalid@  a
.long__irq_invalid@  b
.long__irq_invalid@  c
.long__irq_invalid@  d
.long__irq_invalid@  e
.long__irq_invalid@  f

__irq_svc: ----> entry-armv.S
irq_handler
ENDPROC(__irq_svc)


.macro irq_handler
#ifdef CONFIG_MULTI_IRQ_HANDLER      ----> entry-armv.S

ldr r1, =handle_arch_irq/* 在 init_IRQ中赋值, handle_arch_irq*/
mov r0, sp
adr lr, BSYM(9997f)
ldr pc, [r1]
#else
arch_irq_handler_default

#endif

==========handle_arch_irq赋值流程,handle_arch_irq=gic_handle_irq=============

void __init init_IRQ(void)
{
if (IS_ENABLED(CONFIG_OF) && !machine_desc->init_irq)
irqchip_init();
else
machine_desc->init_irq();
}

hi3536_gic_init_irq
gic_init_bases
-->set_handle_irq(gic_handle_irq);
{
if (handle_arch_irq)
return;
handle_arch_irq = handle_irq;
}

MACHINE_START(HI3536, "hi3536") ----> arch/arm/mach-hi3536/core.c
.atag_offset  = 0x100,
.map_io       = hi3536_map_io,
.init_early   = hi3536_init_early,
.init_irq     = hi3536_gic_init_irq,
#ifdef CONFIG_HI3536_SYSCNT
.init_time    = arch_timer_init,
#else
.init_time    = hi3536_timer_init,
#endif
.init_machine = hi3536_init,
.smp          = smp_ops(hi3536_smp_ops),
.reserve      = hi3536_reserve,
.restart      = hi3536_restart,
MACHINE_END

====================================================
所以handle_arch_irq=gic_handle_irq

gic_handle_irq
--->handle_IRQ
----->generic_handle_irq
{
      struct irq_desc *desc = irq_to_desc(irq);
      generic_handle_irq_desc(irq, desc);
      {
desc->handle_irq(irq, desc);/*这里根据具体的handle_irq来执行,若irq<32,则使用 handle_percpu_devid_irq,否则使用handle_fasteoi_irq*/
 handle_fasteoi_irq/* 以irq大于32的handle_fasteoi_irq为例 */
{
handle_irq_event(desc);
--->handle_irq_event_percpu/* 依次执行irq_desc上action上的服务例程 */
{
do {
ret = action->handler(irq, action->dev_id);
action = action->next;
} while (action);
}
}
      }
}

static int gic_irq_domain_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hw)
{
if (hw < 32) /*中断号小于32 */
irq_set_chip_and_handler(irq, &gic_chip, handle_percpu_devid_irq);/*赋值desc->handle_irq*/
        else  /* 中断号大于32 */
irq_set_chip_and_handler(irq, &gic_chip,handle_fasteoi_irq);/*赋值desc->handle_irq*/

return 0;
}


三、多核cpu的中断亲和力和中断负载均衡
1、处理器间中断
   在多核cpu中,中断可以通过 处理器间中断(Inter-Processor Interrupt) 传递到其他核上.
2、中断亲和力和中断负载均衡
   利用中断亲和可以来做中断的负载均衡,将负载绑定到负载较轻的cpu上,更好的优化性能.


四、中断的上半部和下半部
linux中断上半部(top half) ------> 不可中断
上半部的功能是"登记中断",当一个中断发生时,它进行相应地硬件读写后并把中断例程的下半部挂到该设备的下半部执行 工作队列/tasklet中去
linux中断的下半部(botttom half) ----->  可中断
具体的下半部几种方式
几种下半部方式 : 
1、软中断
2、tasklet
3、工作队列

当前下半部看到使用使用工作队和tasklet比较多


参考资料

Linux 2.6.30.4源码

linux 3.10.y源码

《linux内核修炼之道》






2012-12-14 10:53:56 liangxiaozhang 阅读数 2424
  • Linux视频教学从入门到精通

    不管你是Linux小白还是有linux基础,通过本课程学习都能让你掌握足够多的linux的实战经验,本课程从Linux安装开始手把手教你如何成为Linux高手,学好服务器端操作系统Linux至关重要,互联网项目离不开Linux,分布式离不开linux,大数据离不开linux,想要高薪更离不开Linux,本课程是你好的教材。

    4958 人正在学习 去看看 任亮

 如果你要禁止所有的中断该怎么办? 在2.6内核中,可以通过下面两个函数中的其中任何一个关闭当前处理器上的所有中断处理,这两个函数定义在 <asm/system.h>中:
    void local_irq_save(unsigned long flags);
    void local_irq_disable(void);
    对 local_irq_save的调用将把当前中断状态保存到flags中,然后禁用当前处理器上的中断发送。注意, flags 被直接传递, 而不是通过指针来传递。 local_irq_disable不保存状态而关闭本地处理器上的中断发送; 只有我们知道中断并未在其他地方被禁用的情况下,才能使用这个版本。
    可通过如下函数打开中断:
    void local_irq_restore(unsigned long flags); 
    void local_irq_enable(void);
    第一个版本将local_irq_save保存的flags状态值恢复, 而local_irq_enable无条件打开中断. 与 disable_irq不同, local_irq_disable不会维护对多次的调用的跟踪。 如果调用链中有多个函数需要禁止中断, 应该使用local_irq_save.
    在2.6内核, 没有方法全局禁用整个系统的所有中断。 内核开发者认为关闭所有中断的代价太高,因此没有必要提供这个能力。如果读者使用的老驱动程序调用了类似cli和sti这样的函数,为了该驱动程序能够在2.6下使用,则需要进行修改而使用正确的锁。

    in_interrupt()是判断当前进程是否处于中断上下文,这个中断上下文包括底半部和硬件中断处理过程,

函数实现:

     #define in_interrupt() ({ const int __cpu = smp_processor_id(); /
          (local_irq_count(__cpu) + local_bh_count(__cpu) != 0); })

判断中断计数和底半部计数是否〉0,如果只希望判断是否在硬件中断上下文,则可以使用:in_irq()。

2014-11-13 10:42:58 hustyangju 阅读数 1634
  • Linux视频教学从入门到精通

    不管你是Linux小白还是有linux基础,通过本课程学习都能让你掌握足够多的linux的实战经验,本课程从Linux安装开始手把手教你如何成为Linux高手,学好服务器端操作系统Linux至关重要,互联网项目离不开Linux,分布式离不开linux,大数据离不开linux,想要高薪更离不开Linux,本课程是你好的教材。

    4958 人正在学习 去看看 任亮

参考:

http://bbs.chinaunix.net/thread-2333484-1-1.html

http://liu1227787871.blog.163.com/blog/static/20536319720129210112658/


1、软中断

一般来说,一次中断服务的过程通常可以分为两个部分。开头的 部分往往必须在关中断的条件下执行,这样才能在不受干扰的条件下“原子”地完成一些关键性操作,同时这部分操作的时间性又往往很强,必须在中断请求发生后立即或至少在一定时间限制中执行,而且相继的多次中断请求也不能合并在一起来处理。而后半部分,通常可以而且应该在开中断的条件下执行,这样才不至于因中断关闭过久而造成其他中断的丢失,同时,这些操作常常允许延时到稍后才来执行,而且有可能多次中断的相关部分合并在一起处理。这些不同的性质常常使中断服务的前后两半明显地区分开来,可以而且应该分别加以不同的实现。这里的后半部分就称为"bottom half",在内核代码中往往写成bf ,而bf的这部分就可以通过软件中断来实现。因为软件中断的激活是通过代码来实现的,而不是硬件,所以就可以自己或由系统来决定激活的时机!

1.1 注册
还是以我最熟悉的两个老朋友做为开篇:


        open_softirq(NET_TX_SOFTIRQ, net_tx_action);
        open_softirq(NET_RX_SOFTIRQ, net_rx_action);


open_softirq向内核注册一个软中断,其实质是设置软中断向量表相应槽位,注册其处理函数:

void open_softirq(int nr, void (*action)(struct softirq_action *))
{
        softirq_vec[nr].action = action;
}

softirq_vec是整个软中断的向量表:

void open_softirq(int nr, void (*action)(struct softirq_action*), void *data) 
 {      softirq_vec[nr].data = data;   softirq_vec[nr].action = action;

};


static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;


NR_SOFTIRQS是最大软中断向量数,内核支持的所有软中断如下:
enum
{
        HI_SOFTIRQ=0,
        TIMER_SOFTIRQ,
        NET_TX_SOFTIRQ,
        NET_RX_SOFTIRQ,
        BLOCK_SOFTIRQ,
        TASKLET_SOFTIRQ,
        SCHED_SOFTIRQ,
        HRTIMER_SOFTIRQ,
        RCU_SOFTIRQ,        /* Preferable RCU should always be the last softirq */


        NR_SOFTIRQS
};

好像后为为RPS新增了一个,不过这我的内核版本偏低。


1.2 激活 


当需要调用软中断时,需要调用raise_softirq函数激活软中断,这里使用术语“激活”而非“调用”,
是因为在很多情况下不能直接调用软中断。所以只能快速地将其标志为“可执行”,等待未来某一时刻调用。
为什么“在很多情况下不能直接调用软中断”?试想一下下半部引入的理念,就是为了让上半部更快地执行。
如果在中断程序代码中直接调用软中断函数,那么就失去了上半部与下半部的区别,也就是失去了其存在的意义。


内核使用一个名为__softirq_pending的位图来描述软中断,每一个位对应一个软中断,位图包含在结构irq_stat中:
typedef struct {
        unsigned int __softirq_pending;
        ……
} ____cacheline_aligned irq_cpustat_t;


DECLARE_PER_CPU_SHARED_ALIGNED(irq_cpustat_t, irq_stat);

宏or_softirq_pending用于设置相应的位(位或操作):
#define or_softirq_pending(x)        percpu_or(irq_stat.__softirq_pending, (x))

local_softirq_pending用于取得整个位图(而非某一位):
#define local_softirq_pending()        percpu_read(irq_stat.__softirq_pending)

宏__raise_softirq_irqoff是or_softirq_pending的包裹:
#define __raise_softirq_irqoff(nr) do { or_softirq_pending(1UL << (nr)); } while (0)

raise_softirq_irqoff通过调用__raise_softirq_irqoff实现激活软中断,它的参数nr即位软中断对应的位图槽位:
/*
* This function must run with irqs disabled!
*/
inline void raise_softirq_irqoff(unsigned int nr)
{
        //置位图,即标记为可执行状态
        __raise_softirq_irqoff(nr);


        /*
         * If we're in an interrupt or softirq, we're done
         * (this also catches softirq-disabled code). We will
         * actually run the softirq once we return from
         * the irq or softirq.
         *
         * Otherwise we wake up ksoftirqd to make sure we
         * schedule the softirq soon.
         */
        //设置了位图后,可以判断是否已经没有在中断上下文中了,如果没有,则是一个立即调用软中断的好时机。
        //in_interrupt另一个作用是判断软中断是否被禁用。
        //wakeup_softirqd唤醒软中断的守护进程ksoftirq。
        if (!in_interrupt())
                wakeup_softirqd();
}
复制代码
现在可以来看"激活"软中断的所有含义了,raise_softirq函数完成这一操作:
void raise_softirq(unsigned int nr)
{
        unsigned long flags;


        //所有操作,应该关闭中断,避免嵌套调用
        local_irq_save(flags);
        raise_softirq_irqoff(nr);
        local_irq_restore(flags);
}

可见,激活的操作,主要是两点:
<1>、最重要的,就是置相应的位图,等待将来被处理;
<2>、如果此时已经没有在中断上下文中,则立即调用(其实是内核线程的唤醒操作),现在就是将来;


2、调度时机
是的,除了raise_softirq在,可能会(嗯,重要的是“可能”)通过wakeup_softirqd唤醒ksoftirqd外,还得明白软中断的其它调用时机。


A、当do_IRQ完成了I/O中断时调用irq_exit:
#ifdef __ARCH_IRQ_EXIT_IRQS_DISABLED
# define invoke_softirq()        __do_softirq()
#else
# define invoke_softirq()        do_softirq()
#endif


void irq_exit(void)
{
        account_system_vtime(current);
        trace_hardirq_exit();
        sub_preempt_count(IRQ_EXIT_OFFSET);
        if (!in_interrupt() && local_softirq_pending())
                invoke_softirq();                //调用软中断

B、如果系统使用I/O APIC,在处理完本地时钟中断时:
void __irq_entry smp_apic_timer_interrupt(struct pt_regs *regs)
{
        ……
        irq_exit();
        ……
}

C、local_bh_enable


local_bh_enable就是打开下半部,当然重中之中就是软中断了:
void local_bh_enable(void)
{
        _local_bh_enable_ip((unsigned long)__builtin_return_address(0));
}


static inline void _local_bh_enable_ip(unsigned long ip)
{
        ……


        if (unlikely(!in_interrupt() && local_softirq_pending()))
                do_softirq();


        ……
}

D、在SMP中,当CPU处理完被CALL_FUNCTION_VECTOR处理器间中断所触发的函数时:
唔,对多核中CPU的之间的通信不熟,不太清楚这个机制……

3、do_softirq


不论是哪种调用方式,最终都会触发到软中断的核心处理函数do_softirq,它处理当前CPU上的所有软中断。
内核将软中断设计尽量与平台无关,但是在某些情况下,它们还是会有差异,先来看一个x86 32位的do_softirq版本:

asmlinkage void do_softirq(void)
{
        unsigned long flags;
        struct thread_info *curctx;
        union irq_ctx *irqctx;
        u32 *isp;


        //软中断不能在中断上下文内嵌套调用。中断处理程序或下半部采用的是"激活"方式。
        if (in_interrupt())
                return;


        //禁止中断,保存中断标志
        local_irq_save(flags);
        //内核使用一个CPU位图,确实几个软中断可以同时在不同的CPU上运行,包括相同的软中断。例如,
        //NET_RX_SOFTIRQ可以同时跑在多个处理器上。
        //local_softirq_pending用于确定当前CPU的所有位图是否被设置。即是否有软中断等待处理。
        //回想一下经常发生的网卡接收数据处理:当网卡中断落在哪一个CPU上时,与之相应的软中断函数就会在其上执行。
        //从这里来看,实质就是哪个网卡中断落在相应的CPU上,CPU置其软中断位图,这里做相应的检测(这里local_softirq_pending只
        //是一个总的判断,后面还有按位的判断),检测到有相应的位,执行之
        if (local_softirq_pending()) {
                //取得线程描述符
                curctx = current_thread_info();
                //构造中断上下文结构,softirq_ctx是每个CPU的软中断上下文
                //static DEFINE_PER_CPU(union irq_ctx *, softirq_ctx);
                //这里先取得当前CPU的软中断上下文,然后为其赋初始值——保存当前进程和栈指针
                irqctx = __get_cpu_var(softirq_ctx);
                irqctx->tinfo.task = curctx->task;
                irqctx->tinfo.previous_esp = current_stack_pointer;


                /* build the stack frame on the softirq stack */
                //构造中断栈帧
                isp = (u32 *) ((char *)irqctx + sizeof(*irqctx));


                //call_on_stack切换内核栈,并在中断上下文上执行函数__do_softirq
                call_on_stack(__do_softirq, isp);
                /*
                 * Shouldnt happen, we returned above if in_interrupt():
                 */
                WARN_ON_ONCE(softirq_count());
        }


        //恢复之
        local_irq_restore(flags);
}

当配置了CONFIG_4KSTACKS,每个进程的thread_union只有4K,而非8K。发生中断时,内核栈将不使用进程的内核栈,而使用每个 cpu的中断请求栈。
内核栈将使用每个 cpu的中断请求栈,而非进程的内核栈来执行软中断函数:
static void call_on_stack(void *func, void *stack)
{
        asm volatile("xchgl        %%ebx,%%esp        \n"                                //交换栈指针,中断栈帧的指针stack做为传入参数(%ebx),交换后esp是irq_ctx的栈顶,ebx是进程内核栈的栈
                     "call        *%%edi                \n"                                        //调用软中断函数
                     "movl        %%ebx,%%esp        \n"                                        //恢复之,直接使用movl,而非xchgl是因为函数执行完毕,中断的栈帧指针已经没有用处了
                     : "=b" (stack)
                     : "0" (stack),
                       "D"(func)
                     : "memory", "cc", "edx", "ecx", "eax");
}

PS:所有的这些执行,应该都是在定义4K栈的基础上的:
#ifdef CONFIG_4KSTACKS
/*
* per-CPU IRQ handling contexts (thread information and stack)
*/
union irq_ctx {
        struct thread_info      tinfo;
        u32                     stack[THREAD_SIZE/sizeof(u32)];
} __attribute__((aligned(PAGE_SIZE)));


static DEFINE_PER_CPU(union irq_ctx *, hardirq_ctx);
static DEFINE_PER_CPU(union irq_ctx *, softirq_ctx);
……


static void call_on_stack(void *func, void *stack)
……

是的,这个版本相对复杂,但是如果看了复杂的,再来看简单的,就容易多了,当平台没有定义do_softirq函数时(__ARCH_HAS_DO_SOFTIRQ),
内核提供了一个通用的:
#ifndef __ARCH_HAS_DO_SOFTIRQ


asmlinkage void do_softirq(void)
{
        __u32 pending;
        unsigned long flags;


        if (in_interrupt())
                return;


        local_irq_save(flags);


        pending = local_softirq_pending();


        if (pending)
                __do_softirq();


        local_irq_restore(flags);
}


#endif

无需更多的解释,它非常的简洁。


不论是哪个版本,都将调用__do_softirq函数:
asmlinkage void __do_softirq(void)
{
        struct softirq_action *h;
        __u32 pending;
        int max_restart = MAX_SOFTIRQ_RESTART;
        int cpu;


        //保存位图
        pending = local_softirq_pending();
        //进程记帐
        account_system_vtime(current);


        //关闭本地CPU下半部。为了保证同一个CPU上的软中断以串行方式执行。
        __local_bh_disable((unsigned long)__builtin_return_address(0));
        lockdep_softirq_enter();


        //获取本地CPU
        cpu = smp_processor_id();
restart:
        /* Reset the pending bitmask before enabling irqs */
        //清除位图
        set_softirq_pending(0);


        //锁中断,只是为了保持位图的互斥,位图处理完毕。后面的代码可以直接使用保存的pending,
        //而中断处理程序在激活的时候,也可以放心地使用irq_stat.__softirq_pending。
        //所以,可以开中断了
        local_irq_enable();


        //取得软中断向量
        h = softirq_vec;


        //循环处理所有的软中断
        do {
                //逐步取位图的每一位,判断该位上是否有软中断被设置。若有,处理之
                if (pending & 1) {
                        //保存抢占计数器
                        int prev_count = preempt_count();
                        kstat_incr_softirqs_this_cpu(h - softirq_vec);


                        trace_softirq_entry(h, softirq_vec);
                        //调用软中断
                        h->action(h);
                        trace_softirq_exit(h, softirq_vec);
                        //判断软中断是否被抢占,如果是,则输出一段错误信息
                        if (unlikely(prev_count != preempt_count())) {
                                printk(KERN_ERR "huh, entered softirq %td %s %p"
                                       "with preempt_count %08x,"
                                       " exited with %08x?\n", h - softirq_vec,
                                       softirq_to_name[h - softirq_vec],
                                       h->action, prev_count, preempt_count());
                                preempt_count() = prev_count;
                        }
                        //??qsctr,这个是啥东东
                        rcu_bh_qsctr_inc(cpu);
                }
                //指向下一个软中断槽位
                h++;
                //移位,取下一个软中断位
                pending >>= 1;
        } while (pending);


        //当软中断处理完毕后,因为前面已经开了中断了,所以有可能新的软中断已经又被设置,
        //软中断调度程序会尝试重新软中断,其最大重启次数由max_restart决定。
        //所以,这里必须再次关闭中断,再来一次……
        local_irq_disable();


        //取位图
        pending = local_softirq_pending();
        //有软中断被设置,且没有超过最大重启次数,再来一次先
        if (pending && --max_restart)
                goto restart;


        //超过最大重启次数,还有软中断待处理,调用wakeup_softirqd。其任处是唤醒软中断守护进程ksoftirqd。
        if (pending)
                wakeup_softirqd();


        lockdep_softirq_exit();


        account_system_vtime(current);
        //恢复下半部
        _local_bh_enable();
}

中断跟踪
如果中断跟踪CONFIG_TRACE_IRQFLAGS被定义,lockdep_softirq_enter/lockdep_softirq_exit用于递增/递减当前进程的软中断上下文计数器softirq_context:
# define lockdep_softirq_enter()        do { current->softirq_context++; } while (0)
# define lockdep_softirq_exit()        do { current->softirq_context--; } while (0)
复制代码
trace_softirq_entry与trace_softirq_exit配合使用,可以用于判断软中断的延迟。


好像软中断不太难,没有更多的内容了。欢迎大家回贴补充。

2011-01-24 17:10:00 hao507 阅读数 2826
  • Linux视频教学从入门到精通

    不管你是Linux小白还是有linux基础,通过本课程学习都能让你掌握足够多的linux的实战经验,本课程从Linux安装开始手把手教你如何成为Linux高手,学好服务器端操作系统Linux至关重要,互联网项目离不开Linux,分布式离不开linux,大数据离不开linux,想要高薪更离不开Linux,本课程是你好的教材。

    4958 人正在学习 去看看 任亮

转载自:http://blog.csdn.net/sailor_8318/archive/2008/08/28/2841002.aspx

 

1、Linux对异常和中断的处理

1.1    异常处理

Linux利用异常来达到两个截然不同的目的:

²      进程发送一个信号以通报一个反常情况

²      管理硬件资源

 

对于第一种情况,例如,如果进程执行了一个被0除的操作,CPU则会产生一个“除法错误”异常,并由相应的异常处理程序向当前进程发送一个SIGFPE信号。当前进程接收到这个信号后,就要采取若干必要的步骤,或者从错误中恢复,或者终止执行(如果这个信号没有相应的信号处理程序)。

 

内核对异常处理程序的调用有一个标准的结构,它由以下三部分组成:

²      在内核栈中保存大多数寄存器的内容(由汇编语言实现)

²      调用C编写的异常处理函数

²      通过ret_from_exception()函数从异常退出。

 

1.2    中断处理

当一个中断发生时,并不是所有的操作都具有相同的急迫性。事实上,把所有的操作都放进中断处理程序本身并不合适。需要时间长的、非重要的操作应该推后,因为当一个中断处理程序正在运行时,相应的IRQ中断线上再发出的信号就会被忽略。另外中断处理程序不能执行任何阻塞过程,如I/O设备操作。因此,Linux把一个中断要执行的操作分为下面的三类:

²      紧急的(Critical

这样的操作诸如:中断到来时中断控制器做出应答,对中断控制器或设备控制器重新编程,或者对设备和处理器同时访问的数据结构进行修改。这些操作都是紧急的,应该被很快地执行,也就是说,紧急操作应该在一个中断处理程序内立即执行,而且是在禁用中断的状态下

²      非紧急的(Noncritical

这样的操作如修改那些只有处理器才会访问的数据结构(例如,按下一个键后,读扫描码)。这些操作也要很快地完成,因此,它们由中断处理程序立即执行,但在启用中断的状态下。

²      非紧急可延迟的(Noncritical deferrable

这样的操作如,把一个缓冲区的内容拷贝到一些进程的地址空间(例如,把键盘行缓冲区的内容发送到终端处理程序的进程)。这些操作可能被延迟较长的时间间隔而不影响内核操作,有兴趣的进程会等待需要的数据。

 

所有的中断处理程序都执行四个基本的操作:

²      在内核栈中保存IRQ的值和寄存器的内容。

²      给与IRQ中断线相连的中断控制器发送一个应答,这将允许在这条中断线上进一步发出中断请求。

²      执行共享这个IRQ的所有设备的中断服务例程(ISR)。

²      跳到ret_to_usr( )的地址后终止。

1.3    中断处理程序的执行流程

1.3.1      流程概述

现在,我们可以从中断请求的发生到CPU的响应,再到中断处理程序的调用和返回,沿着这一思路走一遍,以体会Linux内核对中断的响应及处理。

 

假定外设的驱动程序都已完成了初始化工作,并且已把相应的中断服务例程挂入到特定的中断请求队列。又假定当前进程正在用户空间运行(随时可以接受中断),且外设已产生了一次中断请求,CPU就在执行完当前指令后来响应该中断。

 

中断处理系统在Linux中的实现是非常依赖于体系结构的,实现依赖于处理器、所使用的中断控制器的类型、体系结构的设计及机器本身。

 

设备产生中断,通过总线把电信号发送给中断控制器。如果中断线是激活的,那么中断控制器就会把中断发往处理器。在大多数体系结构中,这个工作就是通过电信号给处理器的特定管脚发送一个信号。除非在处理器上禁止该中断,否则,处理器会立即停止它正在做的事,关闭中断系统,然后跳到内存中预定义的位置开始执行那里的代码。这个预定义的位置是由内核设置的,是中断处理程序的入口点。

 

对于ARM系统来说,有个专用的IRQ运行模式,有一个统一的入口地址。假定中断发生时CPU运行在用户空间,而中断处理程序属于内核空间,因此,要进行堆栈的切换。也就是说,CPUTSS中取出内核栈指针,并切换到内核栈(此时栈还为空)。

 

 

若当前处于内核空间时,对于ARM系统来说是处于SVC模式,此时产生中断,中断处理完毕后,若是可剥夺内核,则检查是否需要进行进程调度,否则直接返回到被中断的内核空间;若需要进行进程调度,则svc_preempt,进程切换。

 190        .align  5

 191__irq_svc:

 192        svc_entry

 197#ifdef CONFIG_PREEMPT

 198        get_thread_info tsk

 199        ldr     r8, [tsk, #TI_PREEMPT]          @ get preempt count

 200        add     r7, r8, #1                      @ increment it

 201        str     r7, [tsk, #TI_PREEMPT]

 202#endif

 203

 204        irq_handler

 205#ifdef CONFIG_PREEMPT

 206        ldr     r0, [tsk, #TI_FLAGS]            @ get flags

 207        tst     r0, #_TIF_NEED_RESCHED

 208        blne    svc_preempt

 209preempt_return:

 210        ldr     r0, [tsk, #TI_PREEMPT]          @ read preempt value

 211        str     r8, [tsk, #TI_PREEMPT]          @ restore preempt count

 212        teq     r0, r7

 213        strne   r0, [r0, -r0]                   @ bug()

 214#endif

 215        ldr     r0, [sp, #S_PSR]                @ irqs are already disabled

 216        msr     spsr_cxsf, r0

 221        ldmia   sp, {r0 - pc}^                  @ load r0 - pc, cpsr

 222

 223        .ltorg

 

 

当前处于用户空间时,对于ARM系统来说是处于USR模式,此时产生中断,中断处理完毕后,无论是否是可剥夺内核,都调转到统一的用户模式出口ret_to_user,其检查是否需要进行进程调度,若需要进行进程调度,则进程切换,否则直接返回到被中断的用户空间

 

 404        .align  5

 405__irq_usr:

 406        usr_entry

 407

 411        get_thread_info tsk

 412#ifdef CONFIG_PREEMPT

 413        ldr     r8, [tsk, #TI_PREEMPT]          @ get preempt count

 414        add     r7, r8, #1                      @ increment it

 415        str     r7, [tsk, #TI_PREEMPT]

 416#endif

 417

 418        irq_handler

 419#ifdef CONFIG_PREEMPT

 420        ldr     r0, [tsk, #TI_PREEMPT]

 421        str     r8, [tsk, #TI_PREEMPT]

 422        teq     r0, r7

 423        strne   r0, [r0, -r0]       @ bug()

 424#endif

 428

 429        mov     why, #0

 430        b       ret_to_user

 432        .ltorg

 

 1.3.2      保存现场

 105/*

 106 * SVC mode handlers

 107 */

 108

 115        .macro  svc_entry

 116        sub     sp, sp, #S_FRAME_SIZE

 117 SPFIX( tst     sp, #4          )

 118 SPFIX( bicne   sp, sp, #4      )

 119        stmib   sp, {r1 - r12}

 120

 121        ldmia   r0, {r1 - r3}

 122        add     r5, sp, #S_SP           @ here for interlock avoidance

 123        mov     r4, #-1                 @  ""  ""      ""       ""

 124        add     r0, sp, #S_FRAME_SIZE   @  ""  ""      ""       ""

 125 SPFIX( addne   r0, r0, #4      )

 126        str     r1, [sp]                @ save the "real" r0 copied

 127                                        @ from the exception stack

 128

 129        mov     r1, lr

 130

 131        @

 132        @ We are now ready to fill in the remaining blanks on the stack:

 133        @

 134        @  r0 - sp_svc

 135        @  r1 - lr_svc

 136        @  r2 - lr_<exception>, already fixed up for correct return/restart

 137        @  r3 - spsr_<exception>

 138        @  r4 - orig_r0 (see pt_regs definition in ptrace.h)

 139        @

 140        stmia   r5, {r0 - r4}

 141        .endm

 

1.3.3      中断处理

因为C的调用惯例是要把函数参数放在栈的顶部,因此pt- regs结构包含原始寄存器的值,这些值是以前在汇编入口例程svc_entry中保存在栈中的。

linux+v2.6.19/include/asm-arm/arch-at91rm9200/entry-macro.S

  18        .macro  get_irqnr_and_base, irqnr, irqstat, base, tmp

  19        ldr     /base, =(AT91_VA_BASE_SYS)              @ base virtual address of SYS peripherals

  20        ldr     /irqnr, [/base, #AT91_AIC_IVR]          @ read IRQ vector register: de-asserts nIRQ to processor (and clears interrupt)

  21        ldr     /irqstat, [/base, #AT91_AIC_ISR]        @ read interrupt source number

  22        teq     /irqstat, #0                            @ ISR is 0 when no current interrupt, or spurious interrupt

  23        streq   /tmp, [/base, #AT91_AIC_EOICR]          @ not going to be handled further, then ACK it now.

  24        .endm

 

  26/*

  27 * Interrupt handling.  Preserves r7, r8, r9

  28 */

  29        .macro  irq_handler

  301:      get_irqnr_and_base r0, r6, r5, lr

  31        movne   r1, sp

  32        @

  33        @ routine called with r0 = irq number, r1 = struct pt_regs *

  34        @

  35        adrne   lr, 1b

  36        bne     asm_do_IRQ

  58        .endm

 

 

中断号的值也在irq_handler初期得以保存,所以,asm_do_IRQ可以将它提取出来。这个中断处理程序实际上要调用do_IRQ(),而do_IRQ()要调用handle_IRQ_event()函数,最后这个函数才真正地执行中断服务例程(ISR)。下图给出它们的调用关系:

 

 

asm_do_IRQ

 

    do_IRQ()

 

handle_IRQ_event()

 

中断服务

例程1

 

 

 

 

 

 

例程

 

中断服务

例程2

 

 

 

 

 

 

例程

 

 

 

 


 

               

 

 

 

 

 

 

 

 

 

                               中断处理函数的调用关系

 1.3.3.1          asm_do_IRQ

 112asmlinkage void asm_do_IRQ(unsigned int irq, struct pt_regs *regs)

 113{

 114        struct pt_regs *old_regs = set_irq_regs(regs);

 115        struct irqdesc *desc = irq_desc + irq;

 116

 121        if (irq >= NR_IRQS)

 122                desc = &bad_irq_desc;

 123

 124        irq_enter(); //记录硬件中断状态,便于跟踪中断情况确定是否是中断上下文

 125

 126        desc_handle_irq(irq, desc);

///////////////////desc_handle_irq

  33static inline void desc_handle_irq(unsigned int irq, struct irq_desc *desc)

  34{

  35        desc->handle_irq(irq, desc); //通常handle_irq指向__do_IRQ

  36}

///////////////////desc_handle_irq

 130

 131        irq_exit(); //中断退出前执行可能的软中断,被中断前是在中断上下文中则直接退出,这保证了软中断不会嵌套

 132        set_irq_regs(old_regs);

 133}

 

1.3.3.2          __do_IRQ

 157 * __do_IRQ - original all in one highlevel IRQ handler

 167fastcall unsigned int __do_IRQ(unsigned int irq)

 168{

 169        struct irq_desc *desc = irq_desc + irq;

 170        struct irqaction *action;

 171        unsigned int status;

 172

 173        kstat_this_cpu.irqs[irq]++;

 186

 187        spin_lock(&desc->lock);

 188        if (desc->chip->ack) //首先响应中断,通常实现为关闭本中断线

 189                desc->chip->ack(irq);

 190       

 194        status = desc->status & ~(IRQ_REPLAY | IRQ_WAITING);

 195        status |= IRQ_PENDING; /* we _want_ to handle it */

 196

 201        action = NULL;

 202        if (likely(!(status & (IRQ_DISABLED | IRQ_INPROGRESS)))) {

 203                action = desc->action;

 204                status &= ~IRQ_PENDING; /* we commit to handling */

 205                status |= IRQ_INPROGRESS; /* we are handling it */

 206        }

 207        desc->status = status;

 208

 215        if (unlikely(!action))

 216                goto out;

 217

 218        /*

 219         * Edge triggered interrupts need to remember

 220         * pending events.

 227         */

 228        for (;;) {

 229                irqreturn_t action_ret;

 230

 231                spin_unlock(&desc->lock);//解锁,中断处理期间可以响应其他中断,否则再次进入__do_IRQ时会死锁

 233                action_ret = handle_IRQ_event(irq, action);

 237                spin_lock(&desc->lock);

 238                if (likely(!(desc->status & IRQ_PENDING)))

 239                        break;

 240                desc->status &= ~IRQ_PENDING;

 241        }

 242        desc->status &= ~IRQ_INPROGRESS;

 243

 244out:

 249        desc->chip->end(irq);

 250        spin_unlock(&desc->lock);

 251

 252        return 1;

 253}

 

 

该函数的实现用到中断线的状态,下面给予具体说明:

#define IRQ_INPROGRESS  1   /* 正在执行这个IRQ的一个处理程序*/

#define IRQ_DISABLED    2    /* 由设备驱动程序已经禁用了这条IRQ中断线 */

#define IRQ_PENDING     4    /* 一个IRQ已经出现在中断线上,且被应答,但还没有为它提供服务 */

#define IRQ_REPLAY      8    /* Linux重新发送一个已被删除的IRQ */

#define IRQ_WAITING     32   /*当对硬件设备进行探测时,设置这个状态以标记正在被测试的irq */

#define IRQ_LEVEL       64    /* IRQ level triggered */

#define IRQ_MASKED      128    /* IRQ masked - shouldn't be seen again */

#define IRQ_PER_CPU     256     /* IRQ is per CPU */

8个状态的前5个状态比较常用,因此我们给出了具体解释。

 

经验表明,应该避免在同一条中断线上的中断嵌套,内核通过IRQ_PENDING标志位的应用保证了这一点。当do_IRQ()执行到for (;;)循环时,desc->status 中的IRQ_PENDING的标志位肯定为0。当CPU执行完handle_IRQ_event()函数返回时,如果这个标志位仍然为0,那么循环就此结束。如果这个标志位变为1,那就说明这条中断线上又有中断产生(对单CPU而言),所以循环又执行一次。通过这种循环方式,就把可能发生在同一中断线上的嵌套循环化解为“串行”。

 

在循环结束后调用desc->handler->end()函数,具体来说,如果没有设置IRQ_DISABLED标志位,就启用这条中断线。

 

1.3.3.3          handle_IRQ_event

当执行到for (;;)这个无限循环时,就准备对中断请求队列进行处理,这是由handle_IRQ_event()函数完成的。因为中断请求队列为一临界资源,因此在进入这个函数前要加锁。

handle_IRQ_event执行所有的irqaction链表:

 130irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)

 131{

 132        irqreturn_t ret, retval = IRQ_NONE;

 133        unsigned int status = 0;

 134

 135        handle_dynamic_tick(action);

 136       // 如果没有设置IRQF_DISABLED,则中断处理过程中,打开中断

 137        if (!(action->flags & IRQF_DISABLED))

 138                local_irq_enable_in_hardirq();

 139

 140        do {

 141                ret = action->handler(irq, action->dev_id);

 142                if (ret == IRQ_HANDLED)

 143                        status |= action->flags;

 144                retval |= ret;

 145                action = action->next;

 146        } while (action);

 147

 150        local_irq_disable();

 151

 152        return retval;

 153}

 

       

这个循环依次调用请求队列中的每个中断服务例程。这里要说明的是,如果设置了IRQF_DISABLED,则中断服务例程在关中断的条件下进行(不包括非屏蔽中断),但通常CPU在穿过中断门时自动关闭中断。但是,关中断时间绝不能太长,否则就可能丢失其它重要的中断。也就是说,中断服务例程应该处理最紧急的事情,而把剩下的事情交给另外一部分来处理。即后半部分(bottom half)来处理,这一部分内容将在下一节进行讨论。

 

不同的CPU不允许并发地进入同一中断服务例程,否则,那就要求所有的中断服务例程必须是“可重入”的纯代码。可重入代码的设计和实现就复杂多了,因此,Linux在设计内核时巧妙地“避难就易”,以解决问题为主要目标。

 

1.3.3.4          irq_exit()

中断退出前执行可能的软中断,被中断前是在中断上下文中则直接退出,这保证了软中断不会嵌套

////////////////////////////////////////////////////////////

linux+v2.6.19/kernel/softirq.c

 285void irq_exit(void)

 286{

 287        account_system_vtime(current);

 288        trace_hardirq_exit();

 289        sub_preempt_count(IRQ_EXIT_OFFSET);

 290        if (!in_interrupt() && local_softirq_pending())

 291                invoke_softirq();

////////////

 276#ifdef __ARCH_IRQ_EXIT_IRQS_DISABLED

 277# define invoke_softirq()       __do_softirq()

 278#else

 279# define invoke_softirq()       do_softirq()

 280#endif

////////////

 292        preempt_enable_no_resched();

 293}

////////////////////////////////////////////////////////////

 

1.3.4      从中断返回

asm_do_IRQ()这个函数处理所有外设的中断请求后就要返回。返回情况取决于中断前程序是内核态还是用户态以及是否是可剥夺内核。

²      内核态可剥夺内核,只有在preempt_count0时,schedule()才会被调用,其检查是否需要进行进程切换,需要的话就切换。在schedule()返回之后,或者如果没有挂起的工作,那么原来的寄存器被恢复,内核恢复到被中断的内核代码。

²      内核态不可剥夺内核,则直接返回至被中断的内核代码。

²      中断前处于用户态时,无论是否是可剥夺内核,统一跳转到ret_to_user

 

虽然我们这里讨论的是中断的返回,但实际上中断、异常及系统调用的返回是放在一起实现的,因此,我们常常以函数的形式提到下面这三个入口点:

ret_to_user()

终止中断处理程序

ret_slow_syscall ( ) 或者ret_fast_syscall

终止系统调用,即由0x80引起的异常

ret_from_exception(  )

终止除了0x80的所有异常

 

 565/*

 566 * This is the return code to user mode for abort handlers

 567 */

 568ENTRY(ret_from_exception)

 569        get_thread_info tsk

 570        mov     why, #0

 571        b       ret_to_user

 

  57ENTRY(ret_to_user)

  58ret_slow_syscall:

 

由上可知,中断和异常需要返回用户空间时以及系统调用完毕后都需要经过统一的出口ret_slow_syscall,以此决定是否进行进程调度切换等。

 

linux+v2.6.19/arch/arm/kernel/entry-common.S

  16        .align  5

  17/*

  18 * This is the fast syscall return path.  We do as little as

  19 * possible here, and this includes saving r0 back into the SVC

  20 * stack.

  21 */

  22ret_fast_syscall:

  23        disable_irq                             @ disable interrupts

  24        ldr     r1, [tsk, #TI_FLAGS]

  25        tst     r1, #_TIF_WORK_MASK

  26        bne     fast_work_pending

  27

  28        @ fast_restore_user_regs

  29        ldr     r1, [sp, #S_OFF + S_PSR]        @ get calling cpsr

  30        ldr     lr, [sp, #S_OFF + S_PC]!        @ get pc

  31        msr     spsr_cxsf, r1                   @ save in spsr_svc

  32        ldmdb   sp, {r1 - lr}^                  @ get calling r1 - lr

  33        mov     r0, r0

  34        add     sp, sp, #S_FRAME_SIZE - S_PC

  35        movs    pc, lr                @ return & move spsr_svc into cpsr

  36

  37/*

  38 * Ok, we need to do extra processing, enter the slow path.

  39 */

  40fast_work_pending:

  41        str     r0, [sp, #S_R0+S_OFF]!          @ returned r0

  42work_pending:

  43        tst     r1, #_TIF_NEED_RESCHED

  44        bne     work_resched

  45        tst     r1, #_TIF_NOTIFY_RESUME | _TIF_SIGPENDING

  46        beq     no_work_pending

  47        mov     r0, sp                          @ 'regs'

  48        mov     r2, why                         @ 'syscall'

  49        bl      do_notify_resume

  50        b       ret_slow_syscall                @ Check work again

  51

  52work_resched:

  53        bl      schedule

  54/*

  55 * "slow" syscall return path.  "why" tells us if this was a real syscall.

  56 */

  57ENTRY(ret_to_user)

  58ret_slow_syscall:

  59        disable_irq                             @ disable interrupts

  60        ldr     r1, [tsk, #TI_FLAGS]

  61        tst     r1, #_TIF_WORK_MASK

  62        bne     work_pending

  63no_work_pending:

  64        @ slow_restore_user_regs

  65        ldr     r1, [sp, #S_PSR]                @ get calling cpsr

  66        ldr     lr, [sp, #S_PC]!                @ get pc

  67        msr     spsr_cxsf, r1                   @ save in spsr_svc

  68        ldmdb   sp, {r0 - lr}^                  @ get calling r1 - lr

  69        mov     r0, r0

  70        add     sp, sp, #S_FRAME_SIZE - S_PC

  71        movs    pc, lr                @ return & move spsr_svc into cpsr

 

进入ret_slow_syscall后,首先关中断,也就是说,执行这段代码时CPU不接受任何中断请求。然后,看调度标志是否为非0tst     r1, #_TIF_NEED_RESCHED),如果调度标志为非0,说明需要进行调度,则去调用schedule()函数进行进程调度。

2012-06-01 17:28:30 wantianpei 阅读数 2564
  • Linux视频教学从入门到精通

    不管你是Linux小白还是有linux基础,通过本课程学习都能让你掌握足够多的linux的实战经验,本课程从Linux安装开始手把手教你如何成为Linux高手,学好服务器端操作系统Linux至关重要,互联网项目离不开Linux,分布式离不开linux,大数据离不开linux,想要高薪更离不开Linux,本课程是你好的教材。

    4958 人正在学习 去看看 任亮

linux中断响应和处理过程:

首先中断属于异常的一种。异常,就是可以打断CPU正常运行流程的一些事情,比如说外部中断,未定义的指定,试图修改只读数据,执行SWI指定(software interrupt instructin,软件中断指令,比如说上层调用sys_read,sys_write就会产生swi)等。

内核启动时在start_kernel函数(init/main.c)中调用trap_init ,  init_IRQ两个函数来设置异常的处理函数。

trap_init函数(arch/arm/kernel/traps.c

void_init trap_init(void)

{

......

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

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

.......

}

上面两条定义的是异常向量的存放地方,:__stubs_start~~~~~ __stubs_end之间就是异常向量.

接下来 我们看异常向量之间的定义:(arch/arm/kernel/entry-armv.s)

.equ stubs_offset, __vectors_start + 0x200 - __stubs_start

    .globl __vectors_start

__vectors_start:

 ARM( swi SYS_ERROR0 )      //复位时.CPU交执行这条指令

 THUMB( svc #0 )

 THUMB( nop )

W(b) vector_und + stubs_offset   //未定义异常时,CPU将执行这条跳转指令

W(ldr) pc, .LCvswi + stubs_offset   //swi异常

W(b) vector_pabt + stubs_offset   //指令预取止

W(b) vector_dabt + stubs_offset   //数据访问中止

W(b) vector_addrexcptn + stubs_offset  //没有用到

W(b) vector_irq + stubs_offset         //irq中断

W(b) vector_fiq + stubs_offset         //fig中断 (快速中断)

.globl __vectors_end

__vectors_end:

各种异常的处理函数可以分为五类,分别分布在下面不同的文件中:

1、arch/arm/kernel/traps.c

   处理未定义指令异常,总入口函数为do_undefinstr

2、arch/arm/mm/fault.c

   与内存访问相关的异常,总入口函数为do_DataAbort, do_PretftchAbort

3. arch/arm/arm/irq.c

   中断处理函数在这个文件中定义,总入口函数为asm_do_IRQ

4. arch/arm/kernel/call.s

  swi异常处理  比如说:sys_read,  sys_open.

5. 没有使用的异常

除了IRQ中断外(FIG中断linux一般不使用),所有的异常内核都定义了细致而完备的处理函数.  所以我们这里关心的也只是上面红色部分,:IRQ中断.

Init_IRQ函数(arch/arm/kernel/irq.c),被用来初使化中断的处理框架,设置各种中断的默认处理函数.

Linux内核将所有中断统一编号,使用irq_desc结构来描述中断:每个数组项对应一个中断(也可能是一组中断,它们使用共同的中断号),里面记录了中断的名称,中断状态,中断标记,并提供硬件访问函数(清除,屏蔽,使能中断),提供了这个中断的处理函数的入口,通过它可以调用用户注册的中断处理函数

include/linux/irq.h

{.........

irq_flow_handler_t handle_irq;   //当前的中断处理函数入口

struct irq_chip *chip;       //底层的硬件访问

..........

struct irqaction *action; //用户提供的中断处理函数链表

unsigned int status; //IRQ状态

...........

const char *name;     //中断名称

} ____cacheline_internodealigned_in_smp;

Handle_irq是这个或者这组中断的处理函数入口.

当中断发生时总中断入口函数asm_do_IRQ将根据中断号调用相应irq_desc数组中的handle_irq函数,handle_irq使用chip结构中的函数来清除,屏蔽,使用中断,还会一一调用用户在action链表中注册的中断处理函数.

Struct irq_chip{

const char *name;           //启动中断,如果不设置,缺省为"enable"

unsigned int (*startup)(unsigned int irq); //启动中断,如果不设置,缺省为"enable"

void (*shutdown)(unsigned int irq);  //关闭中断,如果不设置,缺省为"disable"

void (*enable)(unsigned int irq);    //使能中断,如果不设置,缺省为unmask

void (*disable)(unsigned int irq);   //禁止中断如果不设置,缺省为"mask"

void  (*ack)(unsigned int irq); //响应中断,通常是清除当前中断使得可以接收下一个中断

void (*mask)(unsigned int irq); //屏蔽中断源

void (*mask_ack)(unsigned int irq); //屏蔽和响应中断

void (*unmask)(unsigned int irq); //开启中断源

..........

}

struct irqaction *action; 结构类型在include/linux/interrupt..h中定义.

用户注册的每一个中断处理函数都用一个irqaction结构表示,一个中断(比如共享中断)可以有多个处理函数,它们的irqacion结构链接成一个链表,action为表头.

struct irqaction {

irq_handler_t handler;   //用户注册的中断处理函数

unsigned long flags;//中断标志,比如是否为共享中断,电平触发还是边沿触发

const char *name;   //用户注册的中断名字

void *dev_id;       //用户供给的handle参数,还可以区分共享中断

struct irqaction *next;

int irq;            //中断号 

struct proc_dir_entry *dir;

irq_handler_t thread_fn;

struct task_struct *thread;

unsigned long thread_flags;

};

Irq_desc结构数组中:

"irq_flow_handler_thandle_irq"   ,   "struct irq_chip *chip "   ,   "struct ,irqaction *action"这三种数据结构构成了中断处理体系结构.

 

 

 

很明显,中断需要用户处理的只有最后一步,就是用户的action中断处理函数.

所以我们需要告诉内核我们相应的中断处理函数在哪里,中断注册:reguest_irq, 相对应的中断卸载:  free_irq.

int request_irq(unsigned int irq, //中申请中断的中断号,可以根据不用的引脚到irqs.h里面查找
        irqreturn_t (*handler)(int, void *, struct pt_regs *),//用户写的中断处理函数
        unsigned long irqflags,//中断触发方式(高电平触发,低电平触发,上升沿,下降沿等)
        const char *devname,//中断名字(自己取名)
        void *dev_id);     //dev_id(共享中断时,用于识别倒里是哪个硬件产生的中断)  

                   //同一中断的不同处理函数必须用dev_id来区分

//共享中断之间既可使用同一中断处理函数,也可使用不同中断处理函数,都需要dev_id区分.

中断注册做了三件事:

1.提供用户action中断处理函数链接

2.中断触发方式是什么

3.中断使能

Void free_irq(unsigned int irq, //中断号与注册时对应

void *dev_id)             //共享中断时用于区分不同硬件与注册时对应

中断卸载和注册相反:

1.根据中断号irq,dev_idaction链表中找到表项,将它删除

2.如果它是唯一表项,还要调用IRQ_DESC[IRQ].CHIP->SHUTDOWNDESC[IRQ].CHIP->DISABLE来关闭中断.

所以很显然,用户要自己写一个中断程序,只需要实现三步,

1.向内核申请注册中断

2.实现用户中断处理函数

3.在不需要该中断的时候卸载中断

附上一个例子:按键的中断程序

驱动程序:

#include <linux/module.h>

#include <linux/kernel.h>

#include <linux/fs.h>

#include <linux/init.h>

#include <linux/delay.h>

#include <asm/irq.h>

#include <linux/interrupt.h>

#include <asm/uaccess.h>

#include <asm/arch/regs-gpio.h>

#include <asm/hardware.h>

#define DEVICE_NAME     "buttons"   /* 加载模式后,执行”cat /proc/devices”命令看到的设备名称 */

#define BUTTON_MAJOR    232         /* 主设备号 */

struct button_irq_desc {

    int irq;

    unsigned long flags;

    char *name;

};

/* 用来指定按键所用的外部中断引脚及中断触发方式名字 */

static struct button_irq_desc button_irqs [] = {

    {IRQ_EINT19, IRQF_TRIGGER_FALLING, "KEY1"}, /* K1 */

    {IRQ_EINT11, IRQF_TRIGGER_FALLING, "KEY2"}, /* K2 */

    {IRQ_EINT2,  IRQF_TRIGGER_FALLING, "KEY3"}, /* K3 */

    {IRQ_EINT0,  IRQF_TRIGGER_FALLING, "KEY4"}, /* K4 */

};

/* 按键被按下的次数(准确地说,是发生中断的次数) */

static volatile int press_cnt [] = {0, 0, 0, 0};

/* 等待队列

 * 当没有按键被按下时,如果有进程调用s3c24xx_buttons_read函数,

 * 它将休眠

 */

static DECLARE_WAIT_QUEUE_HEAD(button_waitq);

/* 中断事件标志中断服务程序将它置1s3c24xx_buttons_read将它清0 */

static volatile int ev_press = 0;

static irqreturn_t buttons_interrupt(int irq, void *dev_id)

{

    volatile int *press_cnt = (volatile int *)dev_id;

    

    *press_cnt = *press_cnt + 1; /* 按键计数加1 */

    ev_press = 1;                /* 表示中断发生了 */

    wake_up_interruptible(&button_waitq);   /* 唤醒休眠的进程 */

    

    return IRQ_RETVAL(IRQ_HANDLED);

}

/* 应用程序对设备文件/dev/buttons执行open(...)时,

 * 就会调用s3c24xx_buttons_open函数

 */

static int s3c24xx_buttons_open(struct inode *inode, struct file *file)

{

    int i;

    int err;

    

    for (i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++) {

        // 注册中断处理函数

        err = request_irq(button_irqs[i].irq, buttons_interrupt, button_irqs[i].flags, 

                          button_irqs[i].name, (void *)&press_cnt[i]);

        if (err)

            break;

    }

    if (err) {

        // 释放已经注册的中断

        i--;

        for (; i >= 0; i--)

            free_irq(button_irqs[i].irq, (void *)&press_cnt[i]);

        return -EBUSY;

    }

    

    return 0;

}

/* 应用程序对设备文件/dev/buttons执行close(...)时,

 * 就会调用s3c24xx_buttons_close函数

 */

static int s3c24xx_buttons_close(struct inode *inode, struct file *file)

{

    int i;

    

    for (i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++) {

        // 释放已经注册的中断

        free_irq(button_irqs[i].irq, (void *)&press_cnt[i]);

    }

    return 0;

}

/* 应用程序对设备文件/dev/buttons执行read(...)时,

 * 就会调用s3c24xx_buttons_read函数

 */

static int s3c24xx_buttons_read(struct file *filp, char __user *buff, 

                                         size_t count, loff_t *offp)

{

    unsigned long err;

    

    /* 如果ev_press等于0,休眠 */

    wait_event_interruptible(button_waitq, ev_press);

    /* 执行到这里时,ev_press等于1,将它清0 */

    ev_press = 0;

    /* 将按键状态复制给用户,并清0 */

    err = copy_to_user(buff, (const void *)press_cnt, min(sizeof(press_cnt), count));

    memset((void *)press_cnt, 0, sizeof(press_cnt));

    return err ? -EFAULT : 0;

}

/* 这个结构是字符设备驱动程序的核心

 * 当应用程序操作设备文件时所调用的openreadwrite等函数,

 * 最终会调用这个结构中的对应函数

 */

static struct file_operations s3c24xx_buttons_fops = {

    .owner   =   THIS_MODULE,    /* 这是一个宏,指向编译模块时自动创建的__this_module变量 */

    .open    =   s3c24xx_buttons_open,

    .release =   s3c24xx_buttons_close, 

    .read    =   s3c24xx_buttons_read,

};

/*

 * 执行“insmod s3c24xx_buttons.ko”命令时就会调用这个函数

 */

static int __init s3c24xx_buttons_init(void)

{

    int ret;

    /* 注册字符设备驱动程序

     * 参数为主设备号、设备名字、file_operations结构;

     * 这样,主设备号就和具体的file_operations结构联系起来了,

     * 操作主设备为BUTTON_MAJOR的设备文件时,就会调用s3c24xx_buttons_fops中的相关成员函数

     * BUTTON_MAJOR可以设为0,表示由内核自动分配主设备号

     */

    ret = register_chrdev(BUTTON_MAJOR, DEVICE_NAME, &s3c24xx_buttons_fops);

    if (ret < 0) {

      printk(DEVICE_NAME " can't register major number\n");

      return ret;

    }

    

    printk(DEVICE_NAME " initialized\n");

    return 0;

}

/*

 * 执行”rmmod s3c24xx_buttons.ko”命令时就会调用这个函数 

 */

static void __exit s3c24xx_buttons_exit(void)

{

    /* 卸载驱动程序 */

    unregister_chrdev(BUTTON_MAJOR, DEVICE_NAME);

}

/* 这两行指定驱动程序的初始化函数和卸载函数 */

module_init(s3c24xx_buttons_init);

module_exit(s3c24xx_buttons_exit);

/* 描述驱动程序的一些信息,不是必须的 */

MODULE_AUTHOR("http://www.100ask.net");             // 驱动程序的作者

MODULE_DESCRIPTION("S3C2410/S3C2440 BUTTON Driver");   // 一些描述信息

MODULE_LICENSE("GPL");                              // 遵循的协议///

应用调用程序:

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <sys/ioctl.h>

int main(int argc, char **argv)

{

    int i;

    int ret;

    int fd;

    int press_cnt[4];

    

    fd = open("/dev/buttons", 0);  // 打开设备

    if (fd < 0) {

        printf("Can't open /dev/buttons\n");

        return -1;

    }

    // 这是个无限循环,进程有可能在read函数中休眠,当有按键被按下时,它才返回

    while (1) {

        // 读出按键被按下的次数

        ret = read(fd, press_cnt, sizeof(press_cnt));

        if (ret < 0) {

            printf("read err!\n");

            continue;

        } 

        for (i = 0; i < sizeof(press_cnt)/sizeof(press_cnt[0]); i++) {

            // 如果被按下的次数不为0,打印出来

            if (press_cnt[i])

                printf("K%d has been pressed %d times!\n", i+1, press_cnt[i]);

        }

    }

    

    close(fd);

    return 0;    

}

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