Linux驱动 | Linux中断下半部机制

1、Linux中断

Linux中断分为两部分:上半部和下半部,上半部完成紧急且能很快完成的任务,下半部完成不紧急且比较耗时的任务。

1.1、特征

1,上半部和下半部都处于中断上下文中,不能调用任何可能调度的函数(意味着不能睡眠)。
2,下部分的执行必须保证其原子性。
硬件中断发生时,内核禁止了抢占,中断上半部被执行。上半部执行完成后,在执行下部分前,重新启用了中断,意味着在下部分执行时,可以被新的硬件中断抢占,意味着需要对中断上半部与下半部的涉及的共享资源加锁。
3,实现下半部的机制:tasket和工作队列。
tasket是软中断的一种,运行中断上下文中,工作队列运行在进程上下文中。

2、tasket

2.1、特征

1、tasket是软中断,运行在中断上下文中。
2、tasket始终运行在调度它的同一CPU执行。
3、tasket可以被启用或禁止,但必须保证启用次数和禁止次数的相等,tasket才被执行。
4、在tasket函数中可以注册本身。
5、高优先级的tasket会被首先执行。
6、tasket可以被多次调度,但tasket的调度不会累计,实际只会运行一次。若一个tasket被调度,但是还没有被执行,那么新的调度被忽略。
7、每个tasket在所在CPU串行执行,但多个不同tasket可以并行在多个处理器。

2.2、tasket相关结构体和函数

2.2.1、tasklet_struct结构体

  struct tasklet_struct
  {
      struct tasklet_struct *next;
      unsigned long state;
      atomic_t count;
      void (*func)(unsigned long);
      unsigned long data;
  };

state : tasklet被调度的状态
count : tasklet大于0表示tasklet被禁用
func : tasklet的下半部函数
data : 常作为指针传递数据给func函数

2.2.2、tasklet函数

DECLARE_TASKLET(name, func, data) : 静态初始化一个tasklet结构体
DECLARE_TASKLET_DISABLED(name, func, data) : 跟DECLARE_TASKLET一样,将tasklet结构体的count成员初始化为1,禁用tasklet
tasklet_enable(tasklet) : count-1,使能tasklet
tasklet_disable(tasklet) : count+1,禁用tasklet。若tasklet正在运行,该函数进入忙等待,返回时说明已经禁用。
tasklet_disable_nosync(tasklet) : count++,禁用tasklet。不等待tasklet退出,直接禁用,但tasklet可能还在运行。
tasklet_init(tasklet,func,data) : 动态初始化一个tasklet结构体
tasklet_schedule(tasklet) : 将tasklet结构体添加到内核的tasklet链表,下半部函数在未来的某个时间被调度。
tasklet_kill(tasklet) : 在注销函数调用,删除tasklet

3、工作队列

  • 内核在启动时,创建1个或多个内核工作线程,工作线程取出工作队列中的每一个工作,然后执行,当工作队列中没有工作时,工作线程睡眠。而我们驱动的目的就是创建一个或多个工作队列,或者使用内核共享的工作队列,将要执行的工作链接到相应的工作队列中。工作队列的类型为workqueue_struct,工作队列的结点为work_struct。
  • 工作队列运行在工作线程上下文中,所以可以睡眠,但因为工作线程没有用户空间可言,所以不能调用用户空间。
  • 工作队列还能指定在一段jiffies后调用下半部函数。

3.1、创建工作队列

工作队列类型:struct workqueue_struct *work_queue;
在驱动中,我们不需要给该结构体分配内存,使用create_workqueue或者create_singlethread_workqueue,自动为其分配内存,创建一个队列。函数的原型如下:

create_workqueue(char *name)
create_singlethread_workqueue(char *name)

使用create_workqueue会为每个CPU创建该工作队列专有的线程,使用create_singlethread_workqueue则只创建一个线程,一般使用create_singlethread_workqueue。

3.2、创建工作队列结点

类型:struct work_struct,详细信息如下:

struct work_struct {                                                                                                    
	atomic_long_t data;
	struct list_head entry;
	work_func_t func;
#ifdef CONFIG_LOCKDEP
	struct lockdep_map lockdep_map;
#endif
};
typedef void (*work_func_t)(struct work_struct *work);

func : 是工作队列的下半部函数。
采用下面函数定义work_struct。
DECLARE_WORK(name,func) : 静态定义
INIT_WORK(name,func) : 动态定义

3.3、将工作提交到工作队列

3.3.1、提交到自定义工作队列

若是自定义工作队列,使用queue_work和queue_delayed_work将工作提交到自定义工作队列,queue_delayed_work延迟delay个jiffies时间,才会调用func函数。

 queue_work(struct workqueue_struct *wq,struct work_struct *work))
 queue_delayed_work(struct workqueue_struct *wq,
                        struct delayed_work *dwork,
                        unsigned long delay)

3.3.2、提交到共享队列

schedule_work(work);
schedule_delayed_work(work,delay);

3.4、删除工作队列

destroy_workqueue(struct workqueue_struct *wq);

4、实操代码编写

本驱动的目的是:学习tasklet、自定义工作队列、共享队列的下半部机制,使用不同的机制来实现下半部,按下wps按键,在下半部函数中创建一个随机的uuid数据到/proc/uuid_table中。
本驱动在insmod时,传入参数0或1或2给choice参数,参数0代表使用tasklet,参数1代表使用自定义工作队列,参数2代表使用共享队列。

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/proc_fs.h>
#include <linux/spinlock.h>
#include <linux/slab.h>
#include <linux/uuid.h>
#include <linux/seq_file.h>
#include <linux/list.h>
#include <linux/workqueue.h>
#include <linux/moduleparam.h>

MODULE_AUTHOR("Javier Huang");
MODULE_LICENSE("GPL");

#define WPS_GPIO 38

enum intr_choice{
    INTR_TASKLET_CHOICE = 0,
    INTR_WORKQUEUE_CHOICE1,
    INTR_WORKQUEUE_CHOICE2
};

static int choice = INTR_TASKLET_CHOICE;

module_param(choice,int,S_IRUGO);

static int intr_open(struct inode *inode, struct file *filp);
static void * intr_seq_start(struct seq_file *m, loff_t *pos);
static void * intr_seq_next(struct seq_file *m, void *v, loff_t *pos);
static void intr_seq_stop(struct seq_file *m, void *v);
static int intr_seq_show(struct seq_file *m, void *v);

struct my_intr_info_entry{
    char *uuid_buf;
    struct list_head node;
};

struct my_intr_device{
    int irq;
    struct proc_dir_entry *dir_entry;
    struct tasklet_struct tasklet;
    struct workqueue_struct *work_queue;
    struct work_struct work;
    spinlock_t list_lock;
    struct list_head head;
};

static struct my_intr_device *dev = NULL;

static const struct file_operations intr_fops = {
    .owner = THIS_MODULE,
    .open = intr_open,
    .read = seq_read,
    .llseek = seq_lseek,
    .release = seq_release
};

static const struct seq_operations intr_seq_ops = {
    .start = intr_seq_start,
    .next = intr_seq_next,
    .stop = intr_seq_stop,
    .show = intr_seq_show
};

static int intr_open(struct inode *inode, struct file *filp)
{
    return seq_open(filp,&intr_seq_ops);
}

static void * intr_seq_start(struct seq_file *m, loff_t *pos)
{
    //lock
    spin_lock(&dev->list_lock);
    return seq_list_start(&dev->head,*pos);
}

static int intr_seq_show(struct seq_file *m, void *v)
{
    struct my_intr_info_entry *dev = container_of(v, struct my_intr_info_entry, node);
    switch(choice){
    case INTR_TASKLET_CHOICE:
        seq_printf(m,"tasklet : %s\n",dev->uuid_buf);
        break;
    case INTR_WORKQUEUE_CHOICE1:
        seq_printf(m,"workqueue1: %s\n",dev->uuid_buf);
        break;
    case INTR_WORKQUEUE_CHOICE2:
        seq_printf(m,"workqueue2: %s\n",dev->uuid_buf);
        break;
    }
    return 0;
}

static void * intr_seq_next(struct seq_file *m, void *v, loff_t *pos)
{
    return seq_list_next(v,&dev->head,pos);
}

static void intr_seq_stop(struct seq_file *m, void *v)
{
    //unlock
    spin_unlock(&dev->list_lock);
}

static void intr_list_add(struct list_head *head,struct my_intr_info_entry *entry)
{
    list_add_tail(&entry->node,head);
}

static void intr_list_free_all(struct list_head *head)
{
    struct list_head *pos = NULL;
    struct my_intr_info_entry *entry;
    list_for_each(pos,head){
        entry = container_of(pos,struct my_intr_info_entry,node);
        kfree(entry->uuid_buf);
        kfree(entry);
    }
}

static char * gen_uuid(void)
{
    int i;
    char *buf = NULL,*buf2 = NULL;
    uuid_le u;
    int uuid_size = ARRAY_SIZE(u.b) * 2 + 1;
    buf = kzalloc(uuid_size,GFP_ATOMIC);
    if(!buf)
        return NULL;
    buf2 = buf;
    uuid_le_gen(&u);
    for(i=0;i<ARRAY_SIZE(u.b);i++){
        if(u.b[i]<=0x0f)
            buf2 += sprintf(buf2,"0%x",u.b[i]);
        else
            buf2 += sprintf(buf2,"%x",u.b[i]);
    }
    return buf;
}

static void run_common(unsigned long data)
{
    struct my_intr_device *device = (struct my_intr_device *)data;
    //分配节点内存
    struct my_intr_info_entry *item = kzalloc(sizeof(struct my_intr_info_entry),GFP_ATOMIC);
    if(!item){
        printk(KERN_ERR "no enough memory left");
        return;
    }

    //获取uuid
    item->uuid_buf = gen_uuid();

    spin_lock(&device->list_lock);
    //插入结点
    intr_list_add(&device->head,item);
    spin_unlock(&device->list_lock);
}

static void intr_tasklet_func(unsigned long data)
{
    run_common(data);
}

static void intr_workqueue_func(struct work_struct *work)
{
    struct my_intr_device *dev = container_of(work,struct my_intr_device,work);
    run_common((unsigned long)dev);
}

static irqreturn_t my_interrupt_handler(int irq,void *dev_id)
{
    if(dev_id && dev_id==dev){
        switch(choice){
        case INTR_TASKLET_CHOICE:
            tasklet_schedule( &((struct my_intr_device *)dev_id)->tasklet );
            break;
        case INTR_WORKQUEUE_CHOICE1:
            queue_work(  ((struct my_intr_device *)dev_id)->work_queue,
                         &((struct my_intr_device *)dev_id)->work );
            break;
        case INTR_WORKQUEUE_CHOICE2:
            schedule_work( &((struct my_intr_device *)dev_id)->work );
            break;
        }
        return IRQ_HANDLED;
    }
    return IRQ_NONE;
}

static int __init my_intr_init(void)
{
    int ret = 0;
    dev = kzalloc(sizeof(struct my_intr_device),GFP_KERNEL);
    if(!dev){
        printk(KERN_ERR "kmalloc failed\n");
        ret = -ENOMEM;
        goto end;
    }

    //初始化锁
    spin_lock_init(&dev->list_lock);

    //初始化链表
    INIT_LIST_HEAD(&dev->head);

    //初始化tasklet,workqueue
    switch(choice){
    case INTR_TASKLET_CHOICE:
        tasklet_init(&dev->tasklet,intr_tasklet_func,(unsigned long)dev);
        break;
    case INTR_WORKQUEUE_CHOICE1:
        dev->work_queue = create_workqueue("my_workqueue");
    case INTR_WORKQUEUE_CHOICE2:
        INIT_WORK(&dev->work,intr_workqueue_func);
        break;
    }
    
    //申请proc接口
    dev->dir_entry = proc_create("uuid_table",0,NULL,&intr_fops);
    if(!dev->dir_entry){
        printk(KERN_ERR "proc_create failed.\n");
        ret=-EIO;
        goto proc_fail;
    }

    //申请中断
    dev->irq = gpio_to_irq(WPS_GPIO);
    if( (ret=request_irq(dev->irq,my_interrupt_handler,IRQF_TRIGGER_FALLING,"my_irq",dev)) < 0){
        printk(KERN_ERR "driver : request_irq failed\n");
        goto req_fail;
    }

    return 0;

req_fail:
    remove_proc_entry("uuid_table",NULL);
proc_fail:
    if(choice==INTR_WORKQUEUE_CHOICE2)
        destroy_workqueue(dev->work_queue);
    if(choice==INTR_TASKLET_CHOICE)
        tasklet_kill(&dev->tasklet);
    kfree(dev);
end:
    return ret;
}

static void __exit my_intr_exit(void)
{
    remove_proc_entry("uuid_table",NULL);
    intr_list_free_all(&dev->head);
    free_irq(dev->irq,dev);
    if(choice==INTR_WORKQUEUE_CHOICE2)
        destroy_workqueue(dev->work_queue);
    if(choice==INTR_TASKLET_CHOICE)
        tasklet_kill(&dev->tasklet);
    kfree(dev);
}

module_init(my_intr_init);
module_exit(my_intr_exit);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值