Linux中断体系结构

一、linux 异常结构
1、start_kernel 会调用trap_init 和init_IRQ设置异常处理函数
2、trap_init (位于arch/arm/kernel/traps.c中),linux异常向量表位于0xffff_0000,异常处理程序位于0xffff_0000+0x200(一级处理)

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

3、异常向量、其跳去执行的代码位于arch/arm/kernel/entry-armv.S

//异常向量表,如发生未定义异常,则执行b   vector_und + stubs_offset
    .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:

//异常处理(一级处理),如未定义异常,确定是那种模式下的异常,ARM有7中模式,4位表示没有用到的定义为__und_invalid          
    vector_stub und, UND_MODE

    .long   __und_usr           @  0 (USR_26 / USR_32)
    .long   __und_invalid           @  1 (FIQ_26 / FIQ_32)
    .long   __und_invalid           @  2 (IRQ_26 / IRQ_32)
    .long   __und_svc           @  3 (SVC_26 / SVC_32)
    .long   __und_invalid           @  4
    .long   __und_invalid           @  5
    .long   __und_invalid           @  6
    .long   __und_invalid           @  7
    .long   __und_invalid           @  8
    .long   __und_invalid           @  9
    .long   __und_invalid           @  a
    .long   __und_invalid           @  b
    .long   __und_invalid           @  c
    .long   __und_invalid           @  d
    .long   __und_invalid           @  e
    .long   __und_invalid           @  f

3、各种异常处理具体代码(二级处理)
(1)do_undefinstr (arch/arm/kernel/traps.c)
(2)do_DataAbort, do_PerfetchAbort (arch/arm/mm/fault.c)
(3)中断处理函数总入口asm_do_IRQ (arch/arm/mm/irq.c)
(4)swi处理函数指针被处理成一个表格,swi指令机器码位[23:0]作为索引,处理函数为系统调用以sys开头,如sys_open ……。swi处理与具体开发板无关

二、linux中断体系
1、数据结构
Linux内核把所有中断统一编号,使用irq_desc (include/linux/irq.h)结构数组描述

struct irq_desc {
    //这个或这组(可能多个中断共享一个中断号)中断处理函数的入口。发生中断时总入口为asm_do_IRQ 再根据中断号调用相应的handle_irq
    irq_flow_handler_t  handle_irq;

    //结构chip中存放清除、使能中断的函数
    struct irq_chip     *chip;
    struct msi_desc     *msi_desc;
    void            *handler_data;
    void            *chip_data;

第二级中断处理函数组成的链表(如果有多个的话)
    struct irqaction    *action;    /* IRQ action list */
    unsigned int        status;     /* IRQ status */

    unsigned int        depth;      /* nested irq disables */
    unsigned int        wake_depth; /* nested wake enables */
    unsigned int        irq_count;  /* For detecting broken IRQs */
    unsigned int        irqs_unhandled;
    spinlock_t      lock;
#ifdef CONFIG_SMP
    cpumask_t       affinity;
    unsigned int        cpu;
#endif
#if defined(CONFIG_GENERIC_PENDING_IRQ) || defined(CONFIG_IRQBALANCE)
    cpumask_t       pending_mask;
#endif
#ifdef CONFIG_PROC_FS
    struct proc_dir_entry   *dir;
#endif
    const char      *name;
} ____cacheline_internodealigned_in_smp;

2、中断处理流程
(1)发生中断,CPU执行一次向量vector_irq中代码
(2)一些操作后,调用中断总入口asm_do_IRQ
(3)根据中断号调用irq_desc 结构中的 handle_irq
(4)handle_irq 使用chip里面函数设置硬件
(5)handle_irq 逐个调用用户在action中注册的处理函数。
所以中断初始化就是构造数据结构:irq_desc数组中的 handle_irq 、chip;注册中断就是构造action 链表,卸载中断就是去除action 中的项
3、代码分析
(1)开发板中断初始化

void __init init_IRQ(void)
{
    int irq;

    for (irq = 0; irq < NR_IRQS; irq++)
        irq_desc[irq].status |= IRQ_NOREQUEST | IRQ_NOPROBE;

#ifdef CONFIG_SMP
    bad_irq_desc.affinity = CPU_MASK_ALL;
    bad_irq_desc.cpu = smp_processor_id();
#endif

    //如,s3c24xx_init_irq就指向init_arch_irq()
    init_arch_irq();
}

s3c24xx_init_irq在 arch/arm/plat-s3c24xx/irq.c, 为所有中断设置相关数据结构,如外部中断

    for (irqno = IRQ_EINT4; irqno <= IRQ_EINT23; irqno++) {
        irqdbf("registering irq %d (extended s3c irq)\n", irqno);
        //设置chip成员
        set_irq_chip(irqno, &s3c_irqext_chip);
        //设置该中断号入口处理函数
        set_irq_handler(irqno, handle_edge_irq);
        //是否使用
        set_irq_flags(irqno, IRQF_VALID);
    }

使用request_irq 注册中断处理函数,该函数在kernel/irq/manage.c中定义

//根据中断号找到irq_desc数组项, 在它的action链表中加入一个表项。
int request_irq(unsigned int irq, irq_handler_t handler,
unsigned long irqflags, const char *devname, void *dev_id)

    action->handler = handler;
    action->flags = irqflags;
    cpus_clear(action->mask);
    action->name = devname;
    action->next = NULL;
    action->dev_id = dev_id;

(2)初始化后的实际中断过程

中断C语言总入口

asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
    struct pt_regs *old_regs = set_irq_regs(regs);
    struct irq_desc *desc = irq_desc + irq;

    /*
     * Some hardware gives randomly wrong interrupts.  Rather
     * than crashing, do something sensible.
     */
    if (irq >= NR_IRQS)
        desc = &bad_irq_desc;

    irq_enter();

//根据中断号调用irq_desc相关结构的处理函数
    desc_handle_irq(irq, desc);

    /* AT91 specific workaround */
    irq_finish(irq);

    irq_exit();
    set_irq_regs(old_regs);
}

//asm_do_IRQ根据中断号调用irq_desc[irq].handle_irq,这是中断处理函数入口。对于电平触发中断通常是handle_level_irq, 边沿触发中断是handle_edge_irq, 它们都会逐个调用用户在irq_desc[irq].action中注册的中断处理函数。

4、例子:按键中断,除了应用程序,其他都在s3c24xx_buttons.c 中

1)加载和卸载,即从内核中注册或注销
// 执行“insmod s3c24xx_buttons”加载一个模块命令时就会调用这个函数
static int __init s3c24xx_buttons_init(void)
{
    int ret;

    /* 注册字符设备驱动程序
     * 参数为主设备号、设备名字、file_operations结构;
     * 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”卸载命令时就会调用这个函数 
 */
static void __exit s3c24xx_buttons_exit(void)
{
    /* 卸载驱动程序 */
    unregister_chrdev(BUTTON_MAJOR, DEVICE_NAME);
}

/* 这两行指定驱动程序的初始化函数和卸载函数 */
module_init(s3c24xx_buttons_init);
module_exit(s3c24xx_buttons_exit);
2)定义设备描述结构file_operations 

static struct file_operations s3c24xx_buttons_fops = {
    .owner   =   THIS_MODULE,    /* 这是一个宏,指向编译模块时自动创建的__this_module变量 */
    .open    =   s3c24xx_buttons_open,
    .release =   s3c24xx_buttons_close, 
    .read    =   s3c24xx_buttons_read,
};
3)open注册中断、 relaese 释放已经注册的中断、 read读按键次数数据
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函数
 buff存读到的数据,count是数据长度*/
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;
}
4)中断处理函数,dev_id是前面数组press_cnt,按键按一次对应数据加一
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);
}

(5)把s3c24xx_buttons放到源码目录drivers/char下,在同目录Makefile中加一行
obj-m += s3c24xx_buttons.o。 在内核根目录下执行 make modules 则可在drivers/char下生成s3c24xx_buttons.ko。把其放到开发板根文件系统/lib/modules/2.6.22.6/下就可以使用insmod s3c24xx_buttons 进行加载了。
    加载之间先的在开发板中建立设备文件 mknod /dev/buttons c 232 0
    cat /proc/devices 可以看到内核中已有的设备
6)测试文件,输入button_test &让它在后台运行
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;    
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值