1. 简介:

中断,根据中断入口跳转方法的不同,分为向量中断和非向量中断。
采用向量中断的 CPU 通常为不同的中断分配不同的中断号,当检测到某中断号的中断到来后,就自动跳转到与该中断号对应的地址执行。不同中断号的中断有不同的入口地址。
非向量中断的多个中断共享一个入口地址,进入该入口地址后再通过软件判断中断标志来识别具体是哪个中断。也就是说,向量中断由硬件提供中断服务程序入口地址,非向量中断由软件提供中断服务程序入口地址。

为了平衡中断处理程序时间要求短和工作量要大的问题,linux将中断处理程序分为顶半部(top half)底半部(bottom half)

  • 顶半部完成的一般是紧急的硬件操作,一般包括读取寄存的中断状态,清除中断标志,将底半部处理程序挂到底半部的执行队列中去
  • 底半部执行大部分的耗时操作,并且可以被新的中断打断

当然,如果一个程序足够简单,可以不用分成两部分,直接都在顶半部的部分执行完就可以了

2. 函数:

申请 IRQ

int request_irq(unsigned int irq, irq_handler_t handler, 
                unsigned long irqflags, const char *devname, void *dev_id)

函数中的参数:
①irq,要申请的硬件中断号
②handler,中断处理函数,一个回调函数,发生中断进入

irq_handler_t 的定义如下:
typedef irqreturn_t (*irq_handler_t)(int, void *); 
typedef int irqreturn_t;` 

③irqflags,中断处理属性,可以是中断触发方式,或者处理方式,
触发方式如下:

IRQF_TRIGGER_RISING,上升沿触发
IRQF_TRIGGER_FALLING
IRQF_TRIGGER_HIGH,高电平触发
IRQF_TRIGGER_LOW

处理方式如下:

IRQF_DISABLED,表明中断处理程序是快速处理程序,被调用时会屏蔽所有中断
IRQF_SHARED,表示多个设备共享中断

④devname,申请中断的设备的名字

 gpio_key 的一个例子如下
 button->desc ? button->desc : "gpio_keys"

⑤dev_id,中断共享时用到,一般设置为这个设备的设备结构体,或者 NULL
返回值

0,成功
-EINVAL,中断号无效,或者处理函数指针为 NULL
-EBUSY,中断已经被占用并且不能共享

释放 IRQ :

void free_irq(unsigned int irq,void *dev_id);

使能 IRQ:

void enable_irq(int irq); 

屏蔽一个中断源:

void disable_irq(int irq); –> 等待目前的中断处理完成
void disable_irq_nosync(int irq); –> 立即返回
如果在 n 号中断的顶半部调用 disable_irq(n),会引起系统的死锁,这种情况下,只能调用 disable_irq_nosync(n)。

屏蔽本 CPU 内的所有中断:

#define local_irq_save(flags) ... –> 将目前的中断状态保留在 flags 中(注意 flags 为 unsigned long 类型,被直接传递,而不是通过指针)
void local_irq_disable(void); –> 直接禁止中断,不保存状态
与之相应的中断恢复:
#define local_irq_restore(flags) ...
void local_irq_enable(void);

3. 底半部机制:

实现底半部的机制主要有

  • tasklet
  • 工作队列
  • 软中断

3.1 tasklet:

定义 tasklet 及其处理函数:

void my_tasklet_func(unsigned long); /*定义一个处理函数*/ 
DECLARE_TASKLET(my_tasklet, my_tasklet_func, data); 
或者是 tasklet_init(&hello_device.tle
   /*定义一个 tasklet 结构 my_tasklet,与 my_tasklet_func(data)函数相关联 */ 

关联:

DECLARE_TASKLET(my_tasklet,my_tasklet_func,data)

实现定义名称为 my_tasklet 的 tasklet 并将其与 my_tasklet_func() 这个函数绑定,而传入这个函数的参数为 data。

调度:

tasklet_schedule(&my_tasklet); 
在需要调度 tasklet 的时候引用能使系统在适当的时候进行调度运行

使用 tasklet 作为底半部处理中断的设备驱动程序模板

 /*定义 tasklet 和底半部函数并关联*/ 
 void xxx_do_tasklet(unsigned long); 
 DECLARE_TASKLET(xxx_tasklet, xxx_do_tasklet, 0); 

 /*中断处理底半部*/ 
 void xxx_do_tasklet(unsigned long) 
 { 
   ... 
 } 
  /*中断处理顶半部*/ 
irqreturn_t xxx_interrupt(int irq, void *dev_id) 
{ 
  ... 
  tasklet_schedule(&xxx_tasklet); 
  ... 
} 

/*设备驱动模块加载函数*/ 
int _ _init xxx_init(void) 
{ 
  ... 
  /*申请中断*/ 
  result = request_irq(xxx_irq, xxx_interrupt, 
    IRQF_DISABLED, "xxx", NULL); 
  ...   
  return IRQ_HANDLED; 
} 

/*设备驱动模块卸载函数*/ 
void _ _exit xxx_exit(void) 
{ 
  ... 
  /*释放中断*/ 
  free_irq(xxx_irq, xxx_interrupt); 
  ... 
} 

一个使用tasklet 的例子:

Makefile 参考前边的例子中的 Makefile,用户层的测试代码也没有写,函数中的自己自动注册设备号也未实现,希望测试的读者,可以查看前面的几篇文章。

tasklet.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#include <linux/sched.h>
#include <linux/semaphore.h>
#include <linux/interrupt.h>

MODULE_LICENSE ("GPL");

int hello_major = 250;
int hello_minor = 0;
int number_of_devices = 1;

struct hello_device
{
    char data[128];
    int len;
    wait_queue_head_t rq, wq;
    struct semaphore sem;
    struct cdev cdev;
    struct tasklet_struct tlet;/**********关键点********/
} hello_device;

static int hello_open (struct inode *inode, struct file *filp)
{
    filp->private_data = container_of(inode->i_cdev, struct hello_device, cdev);
    printk (KERN_INFO "Hey! device opened\n");

    return 0;
}

static int hello_release (struct inode *inode, struct file *filp)
{
    printk (KERN_INFO "Hmmm... device closed\n");

    return 0;
}

ssize_t hello_read (struct file *filp, char *buff, size_t count, loff_t *offp)
{
    ssize_t result = 0;
    struct hello_device *dev = filp->private_data;

    down(&dev->sem);
    while (dev->len == 0)
    {
        up(&dev->sem);
        if (filp->f_flags & O_NONBLOCK)
            return -EAGAIN;
        if (wait_event_interruptible(dev->rq, (dev->len != 0))) return -ERESTARTSYS;
        down(&dev->sem);
    }

    if (count > dev->len) count = dev->len;
    if (copy_to_user (buff, dev->data, count)) 
    {
        result = -EFAULT;
    }
    else
    {
        printk (KERN_INFO "wrote %d bytes\n", count);
        dev->len -= count;
        result = count;
        memcpy(dev->data, dev->data+count, dev->len);
    }
    up(&dev->sem);
    wake_up(&dev->wq);

    return result;
}

ssize_t hello_write (struct file *filp, const char  *buf, size_t count, loff_t *f_pos)
{
    ssize_t ret = 0;
    struct hello_device *dev = filp->private_data;

    if (count > 128) return -ENOMEM;
    down(&dev->sem);
    while (dev->len == 128)
    {
        up(&dev->sem);
        if (filp->f_flags & O_NONBLOCK)
            return -EAGAIN;
        if (wait_event_interruptible(dev->wq, (dev->len != 128))) return -ERESTARTSYS;
        down(&dev->sem);
    }

    if (count > (128 - dev->len)) count = 128 - dev->len;
    if (copy_from_user (dev->data+dev->len, buf, count)) {
        ret = -EFAULT;
    }
    else {
        dev->len += count;
        ret = count;
    }

    tasklet_schedule(&dev->tlet); /*************关键点*****/
    printk("in write jiffies=%ld\n",jiffies);

    up(&dev->sem);
    wake_up(&dev->rq);

    return ret;
}


struct file_operations hello_fops = {
    .owner = THIS_MODULE,
    .open  = hello_open,
    .release = hello_release,
    .read  = hello_read,
    .write = hello_write
};

static void char_reg_setup_cdev (void)
{
    int error;
    dev_t devno;

    devno = MKDEV (hello_major, hello_minor);
    cdev_init (&hello_device.cdev, &hello_fops);
    hello_device.cdev.owner = THIS_MODULE;
    error = cdev_add (&hello_device.cdev, devno , 1);
    if (error)
        printk (KERN_NOTICE "Error %d adding char_reg_setup_cdev", error);
}

void jit_tasklet_fn(unsigned long arg)
{
    printk("in jit_tasklet_fn  jiffies=%ld\n",jiffies);
}

static int __init hello_2_init (void)
{
    int result;
    dev_t devno;

    devno = MKDEV (hello_major, hello_minor);
    result = register_chrdev_region (devno, number_of_devices, "hello");

    if (result < 0) {
        printk (KERN_WARNING "hello: can't get major number %d\n", hello_major);
        return result;
    }

    char_reg_setup_cdev ();
    init_waitqueue_head(&hello_device.rq);
    init_waitqueue_head(&hello_device.wq);
    sema_init(&hello_device.sem, 1);
    memset(hello_device.data, 0, 128);
    hello_device.len = 0;

/*************关键点******/
    tasklet_init(&hello_device.tlet, jit_tasklet_fn, (unsigned long)&hello_device);
    printk (KERN_INFO "char device registered\n");

    return 0;
}

static void __exit hello_2_exit (void)
{
    dev_t devno = MKDEV (hello_major, hello_minor);

    cdev_del (&hello_device.cdev);

    unregister_chrdev_region (devno, number_of_devices);
}

module_init (hello_2_init);
module_exit (hello_2_exit);

3.2 工作队列:

工作队列(work queue)是Linux kernel中将工作推后执行的一种机制。这种机制和BH或Tasklets不同之处在于工作队列是把推后的工作交由一个内核线程去执行,因此工作队列的优势就在于它允许重新调度甚至睡眠。

定义一个工作队列和一个底半部执行函数:

struct work_struct my_wq; /*定义一个工作队列*/ 
void my_wq_func(unsigned long); /*定义一个处理函数*/ 

初始化这个工作队列并将工作队列与处理函数绑定:

    INIT_WORK(&my_wq, (void (*)(void *)) my_wq_func, NULL); 
  /*初始化工作队列并将其与处理函数绑定*/ 

调度:

schedule_work(&my_wq);/*调度工作队列执行*/ 

使用模板:

/*定义工作队列和关联函数*/ 
 struct work_struct xxx_wq; 
 void xxx_do_work(unsigned long); 

 /*中断处理底半部*/ 
 void xxx_do_work(unsigned long) 
 { 
   ... 
 } 

 /*中断处理顶半部*/ 
 irqreturn_t xxx_interrupt(int irq, void *dev_id, struct pt_regs *regs) 
 { 
   ... 
   schedule_work(&xxx_wq); 
   ... 
   return IRQ_HANDLED; 
 } 

 /*设备驱动模块加载函数*/ 
 int xxx_init(void) 
 { 
   ... 
   /*申请中断*/ 
   result = request_irq(xxx_irq, xxx_interrupt, 
      IRQF_DISABLED, "xxx", NULL); 
  ... 
  /*初始化工作队列*/ 
   INIT_WORK(&xxx_wq, (void (*)(void *)) xxx_do_work, NULL); 
  ... 
 } 

 /*设备驱动模块卸载函数*/ 
 void xxx_exit(void) 
 { 
   ... 
   /*释放中断*/ 
   free_irq(xxx_irq, xxx_interrupt); 
   ... 
 } 

尽管 Linux 社区多建议在设备第一次打开时才申请设备的中断并在最后一次关闭时释放中断以尽量减少中断被这个设备占用的时间,但是,许多情况下,驱动工程师还是将中断申请和释放的工作放在了设备驱动的模块加载和卸载函数中。

3.3 软中断:

一般来说,驱动的编写者不会也不宜直接使用 softirq

  • 软中断和 tasklet 运行于软中断上下文,不能睡眠,另外,tasklet的代码必须是原子的
  • 工作队列运行于进程上下文,工作队列处理函数中允许睡眠

4. 中断共享:

处理的是多个设备共用一个中断号的情况

使用方法:
1. 共享中断的多个设备在申请中断时,都应该使用 IRQF_SHARED 标志,而且一个设备以IRQF_SHARED 申请某中断成功的前提是该中断未被申请,或该中断虽然被申请了,但是之前申请该中断的所有设备也都以 IRQF_SHARED 标志申请该中断。
2. 尽管内核模块可访问的全局地址都可以作为request_irq(…, void *dev_id)的最后一个参数dev_id,但是设备结构体指针显然是可传入的最佳参数。
3. 在中断到来时,会遍历执行共享此中断的所有中断处理
程序,直到某一个函数返回 IRQ_HANDLED。在中断处理程序顶半部中,应迅速地根据硬件寄存器中的信息比照传入的 dev_id 参数判断是否是本设备的中断,若不是,应迅速返回 IRQ_NONE

模板:

 /*中断处理顶半部*/ 
 irqreturn_t xxx_interrupt(int irq, void *dev_id, struct pt_regs *regs) 
 { 
   ... 
  int status = read_int_status();/*获知中断源*/ 
  if(!is_myint(dev_id,status)) /*判断是否是本设备中断*/ 
         return IRQ_NONE;  /*不是本设备中断,立即返回*/ 
  /* 是本设备中断,进行处理 */ 
  ...  
  return IRQ_HANDLED;  /* 返回 IRQ_HANDLED 表明中断已被处理 */ 
 } 
   /*设备驱动模块加载函数*/ 
 int xxx_init(void) 
 { 
   ... 
   /*申请共享中断*/ 
   result = request_irq(sh_irq, xxx_interrupt, 
     IRQF_SHARED, "xxx", xxx_dev); 
  ...  
 } 

 /*设备驱动模块卸载函数*/ 
 void xxx_exit(void) 
 { 
   ... 
   /*释放中断*/ 
   free_irq(xxx_irq, xxx_interrupt); 
   ... 
 } 
GitHub 加速计划 / li / linux-dash
10.39 K
1.2 K
下载
A beautiful web dashboard for Linux
最近提交(Master分支:2 个月前 )
186a802e added ecosystem file for PM2 4 年前
5def40a3 Add host customization support for the NodeJS version 4 年前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐