160 软中断和tasklet基础概念

本文详细介绍了Linux中断处理机制,包括中断函数的设计原则、中断上下半部的概念及作用,以及软中断和tasklet机制。中断上半部执行紧急、时间敏感的任务,下半部处理耗时任务,避免中断嵌套。软中断是一种中断下半部机制,用于执行非中断处理函数,tasklet是基于软中断的另一种机制,适用于轻量级任务。文中还给出了驱动源文件和测试app的示例,展示了如何在实际操作中应用这些机制。

一、中断函数

1、设计原则

执行越快越好

2、根源

中断会打断其他重要代码的执行
中断函数通常在关中断情况下执行,在执行期间cpu无法处理其它的中断函数

二、中断上下半部机制

比较紧急、运行时间短的代码放在中断函数的上半部来执行。
比较耗时、执行速度慢的代码放到中断下半部

内核将中断分为上半部和下半部的目的就是为了实现中断处理函数的快进快出。

1、上半部

申请中断(request_irq)时注册的中断服务函数就是上半部
用来完成一些比较重要,对时间敏感的任务
比如:
1、表明中断已经被系统捕获
2、对响应时间有苛刻要求的操作
3、执行速度快的操作
4、硬件信号控制

2、下半部

非中断处理函数,由上半部设置工作任务(就是设置中断下半部的处理函数)
如:
1、数据复制
2、数据包装和转发
3、计算时间长的数据处理
4、读取外部数据

三、软中断

中断下半部的一种机制

1、softirq_action 结构体

表示一个软中断
include/linux/interrupt.h

struct softirq_action
{
	// 只有一个成员变量,函数指针
	// 负责处理中断下半部的具体工作
	void (*action)(struct softirq_action *);
};
softirq_vec数组

linux系统默认有10种软中断,每个软中断的处理函数都放在softirq_vec数组中
定义了是个软中断
kernel/softirq.c

static struct softirq_action softirq_vec[NR_SOFTIRQS];
NR_SOFTIRQS变量
enum
{
    HI_SOFTIRQ=0, /* 最高优先级软中断 */
    TIMER_SOFTIRQ, /* 定时器软中断 */
    NET_TX_SOFTIRQ, /* 网络数据发送软中断 */
    NET_RX_SOFTIRQ, /* 网络数据接收软中断 */
    BLOCK_SOFTIRQ,	/*块设备软中断*/
    BLOCK_IOPOLL_SOFTIRQ,
    // TASKLET_SOFTIRQ是kernel开放出来给开发者使用的软中断机制
    TASKLET_SOFTIRQ, /* tasklet 软中断 */
    SCHED_SOFTIRQ, /* 调度软中断 */
    HRTIMER_SOFTIRQ, /* 高精度定时器软中断  */
    RCU_SOFTIRQ, /* RCU服务软中断  */
    NR_SOFTIRQS //10
};

内核已经规定了所有软中断的用法

2、软中断相关的api

(1)open_softirq()函数

include/linux/interrupt.h

注册一个软中断

void open_softirq(int nr, void (*action)(struct softirq_action *));
  • 参数:
    nr:要开启的软中断序号,详见上面的枚举类型,10选1
    action:相应的处理函数
(2)raise_softirq()函数

include/linux/interrupt.h

主动触发(激活)指定软中断

void raise_softirq(unsigned int nr);
  • 参数:
    nr:指定激活的软中断序号

四、tasklet 机制

基于软中断实现的另一种下半部机制,运行在软中断环境下
本质上是软中断的一种变体

1、softirq_init()函数

开机自动运行
kernel/softirq.c

void __init softirq_init(void)
{
	int cpu;

	for_each_possible_cpu(cpu) {
		per_cpu(tasklet_vec, cpu).tail =
			&per_cpu(tasklet_vec, cpu).head;
		per_cpu(tasklet_hi_vec, cpu).tail =
			&per_cpu(tasklet_hi_vec, cpu).head;
	}
	// 分别给指定的软中断序号注册一个处理函数
	open_softirq(TASKLET_SOFTIRQ, tasklet_action);
	open_softirq(HI_SOFTIRQ, tasklet_hi_action);
}

tasklet_struct 结构体

include/linux/interrupt.h

struct tasklet_struct
{
	// 链表
	struct tasklet_struct *next;
	// tasklet状态:初始化,运行,调度,...
	unsigned long state;
	// 计数器,记录对tasket的引用数
	atomic_t count;
	// tasklet执行的函数
	void (*func)(unsigned long);
	// 上面的函数指针的参数
	unsigned long data;
};

tasklet相关的api

tasklet_init()函数

初始化一个tasket对象
tasklet_struct中的状态被设置为初始化状态
include/linux/interrupt.h

void tasklet_init(struct tasklet_struct *t,
		  void (*func)(unsigned long), unsigned long data)
  • 参数:
    t:要初始化的tasket
    func:tasklet执行的函数
    data:函数参数
tasklet_schedule()函数

调度执行tasket对象
主要就是将tasklet_struct中的状态设置为调度状态,此时tasklet_action在遍历tasklet_struct链表时,处于调度状态的节点所对应的函数会被执行
include/linux/interrupt.h
在上半部中调用此函数,会触发tasklet_struct 中的func成员

static inline void tasklet_schedule(struct tasklet_struct *t)
  • 参数:
    t:要调度的tasket对象
tasklet_kill()函数

注销tasket对象

include/linux/interrupt.h

void tasklet_kill(struct tasklet_struct *t);
  • 参数:
    t:要注销的tasket对象

五、例程

1、驱动源文件


#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/io.h>
#include <linux/device.h>
#include <linux/irq.h>
#include <linux/of_irq.h>


/*------------------字符设备内容----------------------*/
#define DEV_NAME "button"
#define DEV_CNT (1)

static dev_t button_devno;		 //定义字符设备的设备号
static struct cdev button_chr_dev; //定义字符设备结构体chr_dev
struct class *class_button;		 //保存创建的类
struct device *device_button;		 // 保存创建的设备


struct device_node	*button_device_node = NULL;  //定义按键设备节点结构体
unsigned  button_GPIO_number = 0;  //保存button使用的GPIO引脚编号
u32  interrupt_number = 0;         // button 引脚中断编号
atomic_t   button_status = ATOMIC_INIT(0);  //定义整型原子变量,保存按键状态 ,设置初始值为0

struct tasklet_struct button_tasklet;

void tasklet_hander(unsigned long data)
{

	printk(KERN_EMERG "tasklet_hander is running!");

}

static irqreturn_t button_irq_hander(int irq, void *dev_id)
{
	// printk_green("button on \n");
	/*按键状态加一*/
	printk(KERN_EMERG "button_irq_hander is running!");
	atomic_inc(&button_status);
	tasklet_schedule(&button_tasklet);
	printk(KERN_EMERG "button_irq_hander is end!");
	return IRQ_HANDLED;
}

static int button_open(struct inode *inode, struct file *filp)
{
	int error = -1;
	
	
	/*添加初始化代码*/
	// printk_green("button_open");

	/*获取按键 设备树节点*/
	button_device_node = of_find_node_by_path("/button_interrupt");
	if(NULL == button_device_node)
	{
		printk("of_find_node_by_path error!");
		return -1;
	}

	/*获取按键使用的GPIO*/
	button_GPIO_number = of_get_named_gpio(button_device_node ,"button_gpio", 0);
	if(0 == button_GPIO_number)
	{
		printk("of_get_named_gpio error");
		return -1;
	}

	/*申请GPIO  , 记得释放*/
	error = gpio_request(button_GPIO_number, "button_gpio");
	if(error < 0)
	{
		printk("gpio_request error");
		gpio_free(button_GPIO_number);
		return -1;
	}

	error = gpio_direction_input(button_GPIO_number);//设置引脚为输入模式

	/*获取中断号*/
	interrupt_number = irq_of_parse_and_map(button_device_node, 0);
	printk("\n irq_of_parse_and_map! =  %d \n",interrupt_number);


	/*申请中断, 记得释放*/
	error = request_irq(interrupt_number,button_irq_hander,IRQF_TRIGGER_RISING,"button_interrupt",device_button);
	if(error != 0)
	{
		printk("request_irq error");
		free_irq(interrupt_number, device_button);
		return -1;
	}

	/*申请之后已经开启了,切记不要再次打开,否则运行时报错*/
	// // enable_irq(interrupt_number);

	return 0;

}

static int button_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
	int error = -1;
	int button_countervc = 0;

	/*读取按键状态值*/
	button_countervc = atomic_read(&button_status);

	/*结果拷贝到用户空间*/
	error = copy_to_user(buf, &button_countervc, sizeof(button_countervc));
	if(error < 0)
	{
		printk("copy_to_user error");
		return -1;
	}

	/*清零按键状态值*/
	atomic_set(&button_status,0);
	return 0;
}

/*字符设备操作函数集,.release函数实现*/
static int button_release(struct inode *inode, struct file *filp)
{
	/*释放申请的引脚,和中断*/
	gpio_free(button_GPIO_number);
	free_irq(interrupt_number, device_button);
	return 0;
}



/*字符设备操作函数集*/
static struct file_operations button_chr_dev_fops = {
	.owner = THIS_MODULE,
	.open = button_open,
	.read = button_read,
	.release = button_release};

/*
*驱动初始化函数
*/
static int __init button_driver_init(void)
{
	int error = -1;

	/*采用动态分配的方式,获取设备编号,次设备号为0,*/
	error = alloc_chrdev_region(&button_devno, 0, DEV_CNT, DEV_NAME);
	if (error < 0)
	{
		printk("fail to alloc button_devno\n");
		goto alloc_err;
	}

	/*关联字符设备结构体cdev与文件操作结构体file_operations*/
	button_chr_dev.owner = THIS_MODULE;
	cdev_init(&button_chr_dev, &button_chr_dev_fops);

	/*添加设备至cdev_map散列表中*/ 
	error = cdev_add(&button_chr_dev, button_devno, DEV_CNT);
	if (error < 0) 
	{
		printk("fail to add cdev\n");
		goto add_err;
	}

	class_button = class_create(THIS_MODULE, DEV_NAME);                         //创建类
	device_button = device_create(class_button, NULL, button_devno, NULL, DEV_NAME);//创建设备 DEV_NAME 指定设备名,

	tasklet_init(&button_tasklet,tasklet_hander,0);

	return 0;

add_err:
	unregister_chrdev_region(button_devno, DEV_CNT);    // 添加设备失败时,需要注销设备号
	printk("\n error! \n");
	
alloc_err:
	return -1;
}



/*
*驱动注销函数
*/
static void __exit button_driver_exit(void)
{
	pr_info("button_driver_exit\n");
	/*删除设备*/
	tasklet_kill(&button_tasklet);
	device_destroy(class_button, button_devno);		   //清除设备
	class_destroy(class_button);					   //清除类
	cdev_del(&button_chr_dev);					       //清除设备号
	unregister_chrdev_region(button_devno, DEV_CNT);   //取消注册字符设备
}



module_init(button_driver_init);
module_exit(button_driver_exit);

MODULE_LICENSE("GPL");

2、测试app

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
    int error = -20;
    int button_status = 0;

    /*打开文件*/
    int fd = open("/dev/button", O_RDWR);
    if (fd < 0)
    {
        printf("open file : /dev/button error!\n");
        return -1;
    }

    printf("wait button down... \n");
    printf("wait button down... \n");

    do
    {
        /*读取按键状态*/
        error = read(fd, &button_status, sizeof(button_status));
        if (error < 0)
        {
            printf("read file error! \n");
        }
        usleep(1000 * 100); //延时100毫秒
    } while (0 == button_status);
    printf("button Down !\n");

    /*关闭文件*/
    error = close(fd);
    if (error < 0)
    {
        printf("close file error! \n");
    }
    return 0;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值