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