lv14 中断上半部与下半部 15

前言:为了解决处理中断处理程序时间过长的问题

如果一个中断处理程序的执行时间超过1分钟,那么对用户来说非常不友好。

处理方式:

  • tasklet(基于软中断,属于异常上下文)
  • workqueue(基于内核线程,属于任务上下文)
  • 软中断也可以(需要懂汇编,属于异常上下文)
  • 定时器(基于软中断,属于异常上下文)

一、上半部与下半部

起源:

  1. 中断处理程序执行时间过长引起的问题

  2. 有些设备的中断处理程序必须要处理一些耗时操作

二、下半部机制之tasklet ---- 基于软中断

6.1 结构体

struct tasklet_struct
{

    struct tasklet_struct *next; //链表

    unsigned long state;  //tasklet的状态,用于标识tasklet是否正在执行或者已经被禁用等信息

    atomic_t count; //原子计数器,用于记录tasklet被调度的次数,避免重复执行。

    void (*func)(unsigned long);  //指向tasklet的处理函数,当tasklet被调度时会调用该函数。

    unsigned long data;  //传递给处理函数的参数。

};

6.2 定义tasklet的中断底半部处理函数

void tasklet_func(unsigned long data);

6.3 初始化tasklet

内核定义的宏,一般很少用,常用下面的初始化函数

DECLARE_TASKLET(name, func, data);
/*
定义变量并初始化
参数:name:中断底半部tasklet的名称
     Func:中断底半部处理函数的名字
     data:给中断底半部处理函数传递的参数
*/
void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long), unsigned long data)

6.4 调度tasklet

一般在上半部的最后调用

void tasklet_schedule(struct tasklet_struct *t)
//参数:t:tasklet的结构体

三、按键驱动之tasklet版

目的:按键程序一般不需要tasklet,借用按键中断处理来熟悉tasklet用法

改写fs4412_key_tasklet.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include "fs4412_key.h"


int major = 11;
int minor = 0;
int fs4412key2_num  = 1;

struct fs4412key2_dev
{
	struct cdev mydev;
	
	unsigned int gpio;
	unsigned int irqno;

	struct keyvalue data;
	unsigned int newflag;
	
	spinlock_t lock;

	wait_queue_head_t rq;

	struct tasklet_struct tsk;
};

struct fs4412key2_dev * pgmydev = NULL;

int fs4412key2_open(struct inode *pnode,struct file *pfile)
{
	pfile->private_data =(void *) (container_of(pnode->i_cdev,struct fs4412key2_dev,mydev));
	//暂不处理重复设备打开的情况
	return 0;
}

ssize_t fs4412key2_read(struct file *pfile,char __user *puser,size_t count,loff_t *p_pos)
{
	struct fs4412key2_dev *pmydev = (struct fs4412key2_dev *)pfile->private_data;
	int size = 0;
	int ret = 0;

	if(count < sizeof(struct keyvalue))
	{
		printk("expect read size is invalid\n");
		return -1;
	}

	spin_lock(&pmydev->lock);
	if(!pmydev->newflag)
	{
		if(pfile->f_flags & O_NONBLOCK)
		{//非阻塞
			spin_unlock(&pmydev->lock);
			printk("O_NONBLOCK No Data Read\n");
			return -1;
		}
		else
		{//阻塞
			spin_unlock(&pmydev->lock);
			ret = wait_event_interruptible(pmydev->rq,pmydev->newflag == 1);
			if(ret)
			{
				printk("Wake up by signal\n");
				return -ERESTARTSYS;
			}
			spin_lock(&pmydev->lock);
		}
	}

	if(count > sizeof(struct keyvalue))
	{
		size = sizeof(struct keyvalue);
	}
	else
	{
		size = count;
	}

	ret = copy_to_user(puser,&pmydev->data,size);
	if(ret)
	{
		spin_unlock(&pmydev->lock);
		printk("copy_to_user failed\n");
		return -1;
	}

	pmydev->newflag = 0;

	spin_unlock(&pmydev->lock);

	return size;
}

unsigned int fs4412key2_poll(struct file *pfile,poll_table *ptb)
{
	struct fs4412key2_dev *pmydev = (struct fs4412key2_dev *)pfile->private_data;
	unsigned int mask = 0;

	poll_wait(pfile,&pmydev->rq,ptb);

	spin_lock(&pmydev->lock);
	if(pmydev->newflag)
	{
		mask |= POLLIN | POLLRDNORM;
	}
	spin_unlock(&pmydev->lock);

	return mask;
}

int fs4412key2_close(struct inode *pnode,struct file *pfile)
{
	struct fs4412key2_dev *pmydev = (struct fs4412key2_dev *)pfile->private_data;

	spin_lock(&pmydev->lock);
	pmydev->newflag = 0;
	spin_unlock(&pmydev->lock);
	return 0;
}

irqreturn_t key2_irq_handle(int no,void *arg)
{
	struct fs4412key2_dev *pmydev = (struct fs4412key2_dev *)arg;

	tasklet_schedule(&pmydev->tsk);

	return IRQ_HANDLED;
}

void bottom_irq_func(unsigned long arg)
{
	struct fs4412key2_dev *pmydev = (struct fs4412key2_dev *)arg;
	int status = 0;

	status = gpio_get_value(pmydev->gpio);
	mdelay(1); //消抖
	if(status != gpio_get_value(pmydev->gpio))
	{
		return;
	}

	spin_lock(&pmydev->lock);
	if(status == pmydev->data.status)
	{
		spin_unlock(&pmydev->lock);
		return;
	}

	pmydev->data.code = KEY2;
	pmydev->data.status = status;
	pmydev->newflag = 1;

	spin_unlock(&pmydev->lock);
	wake_up(&pmydev->rq);

	return;
}

struct file_operations myops = {
	.owner = THIS_MODULE,
	.open = fs4412key2_open,
	.release = fs4412key2_close,
	.read = fs4412key2_read,
	.poll = fs4412key2_poll,
};

int __init fs4412key2_init(void)
{
	int ret = 0;
	dev_t devno = MKDEV(major,minor);

	struct device_node *pnode = NULL;

	pnode = of_find_node_by_path("/fs4412-key2");
	if(NULL == pnode)
	{
		printk("find node failed\n");
		return -1;
	}


	pgmydev = (struct fs4412key2_dev *)kmalloc(sizeof(struct fs4412key2_dev),GFP_KERNEL);
	if(NULL == pgmydev)
	{
		printk("kmallc for struct fs4412key2_dev failed\n");
		return -1;
	}

	pgmydev->gpio = of_get_named_gpio(pnode,"key2-gpio",0);

	pgmydev->irqno = irq_of_parse_and_map(pnode,0);

	/*申请设备号*/
	ret = register_chrdev_region(devno,fs4412key2_num,"fs4412key2");
	if(ret)
	{
		ret = alloc_chrdev_region(&devno,minor,fs4412key2_num,"fs4412key2");
		if(ret)
		{
			kfree(pgmydev);
			pgmydev = NULL;
			printk("get devno failed\n");
			return -1;
		}
		major = MAJOR(devno);//容易遗漏,注意
	}

	/*给struct cdev对象指定操作函数集*/	
	cdev_init(&pgmydev->mydev,&myops);

	/*将struct cdev对象添加到内核对应的数据结构里*/
	pgmydev->mydev.owner = THIS_MODULE;
	cdev_add(&pgmydev->mydev,devno,fs4412key2_num);


	init_waitqueue_head(&pgmydev->rq);

	spin_lock_init(&pgmydev->lock);

	tasklet_init(&pgmydev->tsk, bottom_irq_func,(unsigned long)pgmydev);

	ret = request_irq(pgmydev->irqno,key2_irq_handle,IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,"fs4412key2",pgmydev);
	if(ret)
	{
		printk("request_irq failed\n");
		cdev_del(&pgmydev->mydev);
		kfree(pgmydev);
		pgmydev = NULL;
		unregister_chrdev_region(devno,fs4412key2_num);
		return -1;
	}
	return 0;
}

void __exit fs4412key2_exit(void)
{
	dev_t devno = MKDEV(major,minor);
	
	free_irq(pgmydev->irqno,pgmydev);

	cdev_del(&pgmydev->mydev);

	unregister_chrdev_region(devno,fs4412key2_num);

	kfree(pgmydev);
	pgmydev = NULL;
}


MODULE_LICENSE("GPL");

module_init(fs4412key2_init);
module_exit(fs4412key2_exit);

修改Makefile

ifeq ($(KERNELRELEASE),)

ifeq ($(ARCH),arm)
KERNELDIR ?= /home/linux/Linux_4412/kernel/linux-3.14
ROOTFS ?= /opt/4412/rootfs
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
endif
PWD := $(shell pwd)


modules:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules

modules_install:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules INSTALL_MOD_PATH=$(ROOTFS) modules_install

clean:
	rm -rf  *.o  *.ko  .*.cmd  *.mod.*  modules.order  Module.symvers   .tmp_versions

else

CONFIG_MODULE_SIG=n
obj-m += mychar.o
obj-m += mychar_poll.o
obj-m += openonce_atomic.o
obj-m += openonce_spinlock.o
obj-m += mychar_sema.o
obj-m += mychar_mutex.o
obj-m += second.o
obj-m += leddrv.o
obj-m += leddrv_dt.o
obj-m += fs4412_key2.o
obj-m += fs4412_key2_tasklet.o

endif

测试同上一章节

四、下半部机制之workqueue ----- 基于内核线程

8.1 工作队列结构体:

typedef void (*work_func_t)(struct work_struct *work)  //是一个函数指针类型,指向一个形参为 struct work_struct 指针的返回类型为 void 的函数

struct work_struct {

    atomic_long_t data;  //用于存储工作项的数据,可以根据需要进行自定义

    struct list_head entry;  //用于将工作项组织成链表结构,方便管理和调度
 
    work_func_t func;  //指向工作项的处理函数,当工作项被调度时会调用该函数进行处理

#ifdef CONFIG_LOCKDEP

    struct lockdep_map lockdep_map;  //用于支持内核锁依赖性分析的相关信息,在配置了 CONFIG_LOCKDEP 选项时才会编译

#endif

};

8.2 定义工作队列底半部处理函数

void work_queue_func(struct work_struct *work);

8.3 初始化工作队列

struct work_struct work_queue;

初始化:绑定工作队列及工作队列的底半部处理函数

INIT_WORK(struct work_struct * pwork, _func) ;

参数:

  • pwork:工作队列
  • func:工作队列的底半部处理函数

8.4 工作队列的调度函数

bool schedule_work(struct work_struct *work);

五、按键驱动之workqueue版

改写fs4412_key_workqueue.c

其中与tasklet的主要区别在于,一个传参是传入自己本身的结构体,需要通过通过container_of来获取需要的参数指针  

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include "fs4412_key.h"


int major = 11;
int minor = 0;
int fs4412key2_num  = 1;

struct fs4412key2_dev
{
	struct cdev mydev;
	
	unsigned int gpio;
	unsigned int irqno;

	struct keyvalue data;
	unsigned int newflag;
	
	spinlock_t lock;

	wait_queue_head_t rq;

	struct work_struct wk;
};

struct fs4412key2_dev * pgmydev = NULL;

int fs4412key2_open(struct inode *pnode,struct file *pfile)
{
	pfile->private_data =(void *) (container_of(pnode->i_cdev,struct fs4412key2_dev,mydev));
	//暂不处理重复设备打开的情况
	return 0;
}

ssize_t fs4412key2_read(struct file *pfile,char __user *puser,size_t count,loff_t *p_pos)
{
	struct fs4412key2_dev *pmydev = (struct fs4412key2_dev *)pfile->private_data;
	int size = 0;
	int ret = 0;

	if(count < sizeof(struct keyvalue))
	{
		printk("expect read size is invalid\n");
		return -1;
	}

	spin_lock(&pmydev->lock);
	if(!pmydev->newflag)
	{
		if(pfile->f_flags & O_NONBLOCK)
		{//非阻塞
			spin_unlock(&pmydev->lock);
			printk("O_NONBLOCK No Data Read\n");
			return -1;
		}
		else
		{//阻塞
			spin_unlock(&pmydev->lock);
			ret = wait_event_interruptible(pmydev->rq,pmydev->newflag == 1);
			if(ret)
			{
				printk("Wake up by signal\n");
				return -ERESTARTSYS;
			}
			spin_lock(&pmydev->lock);
		}
	}

	if(count > sizeof(struct keyvalue))
	{
		size = sizeof(struct keyvalue);
	}
	else
	{
		size = count;
	}

	ret = copy_to_user(puser,&pmydev->data,size);
	if(ret)
	{
		spin_unlock(&pmydev->lock);
		printk("copy_to_user failed\n");
		return -1;
	}

	pmydev->newflag = 0;

	spin_unlock(&pmydev->lock);

	return size;
}

unsigned int fs4412key2_poll(struct file *pfile,poll_table *ptb)
{
	struct fs4412key2_dev *pmydev = (struct fs4412key2_dev *)pfile->private_data;
	unsigned int mask = 0;

	poll_wait(pfile,&pmydev->rq,ptb);

	spin_lock(&pmydev->lock);
	if(pmydev->newflag)
	{
		mask |= POLLIN | POLLRDNORM;
	}
	spin_unlock(&pmydev->lock);

	return mask;
}

int fs4412key2_close(struct inode *pnode,struct file *pfile)
{
	struct fs4412key2_dev *pmydev = (struct fs4412key2_dev *)pfile->private_data;

	spin_lock(&pmydev->lock);
	pmydev->newflag = 0;
	spin_unlock(&pmydev->lock);
	return 0;
}

irqreturn_t key2_irq_handle(int no,void *arg)
{
	struct fs4412key2_dev *pmydev = (struct fs4412key2_dev *)arg;

	schedule_work(&pmydev->wk);

	return IRQ_HANDLED;
}

//void bottom_irq_func(unsigned long arg)
void bottom_irq_func(struct work_struct *pwk)
{
	//struct fs4412key2_dev *pmydev = (struct fs4412key2_dev *)arg;
	struct fs4412key2_dev *pmydev = container_of(pwk,struct fs4412key2_dev,wk);  
	int status = 0;

	status = gpio_get_value(pmydev->gpio);
	mdelay(1); //消抖
	if(status != gpio_get_value(pmydev->gpio))
	{
		return IRQ_NONE;
	}

	spin_lock(&pmydev->lock);
	if(status == pmydev->data.status)
	{
		spin_unlock(&pmydev->lock);
		return IRQ_NONE;
	}

	pmydev->data.code = KEY2;
	pmydev->data.status = status;
	pmydev->newflag = 1;

	spin_unlock(&pmydev->lock);
	wake_up(&pmydev->rq);

	return IRQ_HANDLED;
}

struct file_operations myops = {
	.owner = THIS_MODULE,
	.open = fs4412key2_open,
	.release = fs4412key2_close,
	.read = fs4412key2_read,
	.poll = fs4412key2_poll,
};

int __init fs4412key2_init(void)
{
	int ret = 0;
	dev_t devno = MKDEV(major,minor);

	struct device_node *pnode = NULL;

	pnode = of_find_node_by_path("/fs4412-key2");
	if(NULL == pnode)
	{
		printk("find node failed\n");
		return -1;
	}


	pgmydev = (struct fs4412key2_dev *)kmalloc(sizeof(struct fs4412key2_dev),GFP_KERNEL);
	if(NULL == pgmydev)
	{
		printk("kmallc for struct fs4412key2_dev failed\n");
		return -1;
	}

	pgmydev->gpio = of_get_named_gpio(pnode,"key2-gpio",0);

	pgmydev->irqno = irq_of_parse_and_map(pnode,0);

	/*申请设备号*/
	ret = register_chrdev_region(devno,fs4412key2_num,"fs4412key2");
	if(ret)
	{
		ret = alloc_chrdev_region(&devno,minor,fs4412key2_num,"fs4412key2");
		if(ret)
		{
			kfree(pgmydev);
			pgmydev = NULL;
			printk("get devno failed\n");
			return -1;
		}
		major = MAJOR(devno);//容易遗漏,注意
	}

	/*给struct cdev对象指定操作函数集*/	
	cdev_init(&pgmydev->mydev,&myops);

	/*将struct cdev对象添加到内核对应的数据结构里*/
	pgmydev->mydev.owner = THIS_MODULE;
	cdev_add(&pgmydev->mydev,devno,fs4412key2_num);


	init_waitqueue_head(&pgmydev->rq);

	spin_lock_init(&pgmydev->lock);

	INIT_WORK(&pgmydev->wk,bottom_irq_func);
	
	ret = request_irq(pgmydev->irqno,key2_irq_handle,IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,"fs4412key2",pgmydev);
	if(ret)
	{
		printk("request_irq failed\n");
		cdev_del(&pgmydev->mydev);
		kfree(pgmydev);
		pgmydev = NULL;
		unregister_chrdev_region(devno,fs4412key2_num);
		return -1;
	}
	return 0;
}

void __exit fs4412key2_exit(void)
{
	dev_t devno = MKDEV(major,minor);
	
	free_irq(pgmydev->irqno,pgmydev);

	cdev_del(&pgmydev->mydev);

	unregister_chrdev_region(devno,fs4412key2_num);

	kfree(pgmydev);
	pgmydev = NULL;
}


MODULE_LICENSE("GPL");

module_init(fs4412key2_init);
module_exit(fs4412key2_exit);

Makefile

ifeq ($(KERNELRELEASE),)

ifeq ($(ARCH),arm)
KERNELDIR ?= /home/linux/Linux_4412/kernel/linux-3.14
ROOTFS ?= /opt/4412/rootfs
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
endif
PWD := $(shell pwd)


modules:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules

modules_install:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules INSTALL_MOD_PATH=$(ROOTFS) modules_install

clean:
	rm -rf  *.o  *.ko  .*.cmd  *.mod.*  modules.order  Module.symvers   .tmp_versions

else

CONFIG_MODULE_SIG=n
obj-m += mychar.o
obj-m += mychar_poll.o
obj-m += openonce_atomic.o
obj-m += openonce_spinlock.o
obj-m += mychar_sema.o
obj-m += mychar_mutex.o
obj-m += second.o
obj-m += leddrv.o
obj-m += leddrv_dt.o
obj-m += fs4412_key2.o
obj-m += fs4412_key2_tasklet.o
obj-m += fs4412_key2_workqueue.o

endif

测试同上一节略 

六、下半部机制比较

任务机制

        workqueue ----- 内核线程 能睡眠 运行时间无限制

异常机制 ------- 不能睡眠 下半部执行时间不宜太长( < 1s)

        软中断 ---- 接口不方便

        tasklet ----- 无具体延后时间要求时

        定时器 -----有具体延后时间要求时(如延时5ms处理)

重点要知道代码是属于任务上下文还是异常上下文

你可能感兴趣的:(嵌入式开发,arm开发,linux)