首页 > 系统相关 >Linux驱动开发之Linux内核中的中断处理以及相关API和例程分析

Linux驱动开发之Linux内核中的中断处理以及相关API和例程分析

时间:2023-12-30 11:32:02浏览次数:224  
标签:函数 例程 irq 中断 int API Linux include struct

中断是计算机中实现异步事件处理的一种关键机制。当中断发生时,CPU会暂停当前的任务,转去运行中断服务例程。中断处理完成后,CPU再返回到原来的任务。这使得中断处理具有很高的实时性和响应速度。在Linux内核中,充分利用了中断机制来响应各种硬件和软件事件。

在Linux操作系统中,中断的本质就是一个数字,要想使用中断,就是对这个数字进行操作,但是内核不允许直接操作这个数字,可以简介的通过函数进行操作,这个函数对中断进行一个转换,转换的对象就是引脚的编号,引脚的编号转换完成之后就可以得到一个中断号,用来进行操作。

中断的特点

  • 快进快出:中断处理必须很快地完成,避免长时间阻塞正常任务。
  • 随机发生:中断可能在任何时候发生,需要能够保存和恢复处理前的上下文。
  • 高优先级:中断处理具有很高的优先级,可以打断正在运行的进程。
  • 准确识别:需要正确区分不同的中断源,调用对应 的服务程序。

Linux中的中断类型

Linux将中断分为以下三类:

  • PPI - 私有外设中断,特定的外设专用中断。
  • SPI - 共享外设中断,可以服务多个外设,数量有限。
  • SGI - 软件生成中断,通过编程的方式触发。

中断号唯一标识一个中断源,用于注册和处理中断。

相关API函数

gpio_to_irq()

功能
    通过gpio_to_irq函数将引脚编号转换为中断号,用于后续的操作
头文件
    #include<linux/gpio.h>
原型
    int gpio_to_irq(unsigned int gpio)
参数
   unsigned int gpio    引脚编号
返回值
    成功    中断号
    失败    负数           

enable_irq()

功能
    使用enable_irq函数使能中断号,使得该中断可以被触发和处理
头文件
    #include<linux/interrupt.h>
原型
    void enable_irq(unsigned int irq);
参数
    unsigned int irq    中断号
返回值
    无

disable_irq()

功能
   失能中断号
头文件
    #include<linux/interrupt.h>
原型
    void disable_irq(unsigned int irq);
参数
    unsigned int irq    中断号
返回值
    无

request_irq()

功能
	使用request_irq函数向内核注册中断号,指定中断服务函数、中断的触发方式、中断的名字等参数
头文件
     #include<linux/interrupt.h>
原型
    static inline int __must_check request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char * name, void * dev)    
参数
    unsigned int irq,         中断号
    irq_handler_t handler,   typedefirqreturn_t (*irq_handler_t)(int, void *);
                                                 中断服务函数
    unsigned long flags,     中断的触发的方式
        IRQF_TRIGGER_RISING      上升沿
        IRQF_TRIGGER_FALLING     下降沿
        IRQF_TRIGGER_HIGH         高电平
        IRQF_TRIGGER_LOW          低电平
    const char *name,       中断的名字
    void *dev              传给中断服务函数的参数 一般写 NULL   
返回值
    成功 0
    失败 负数

free_irq()

功能
	使用free_irq函数取消中断的注册,释放中断号和中断服务函数
头文件
    #include<linux/interrupt.h>
原型
    void free_irq(unsigned int irq, void *dev_id)
参数
    undigned int irq    中断号
    void *dev_id    跟注册中断的函数的最后一位保持一致
返回值
    无

中断的使用

在Linux中使用中断的一般流程是:

  1. 将GPIO引脚号转换为中断号。
  2. 注册中断服务函数。
  3. 使能中断号。
  4. 在中断服务函数中编写处理逻辑。
  5. 根据需要禁用或重新启用中断。
  6. 当不再需要时,取消中断号注册。

采用这种方式,可以很好地响应异步外部事件,提高系统实时性。

等待队列

由于中断具有异步性和随机性,有时需要利用等待队列使内核进入睡眠,等待某事件发生后再被唤醒继续执行。常用的等待队列相关函数有:

DECLARE_WAIT_QUEUE_HEAD()

功能
	定义等待队列的结构体
原型
	#define DECLARE_WAIT_QUEUE_HEAD(name) \
         wait_queue_head_tname = __WAIT_QUEUE_HEAD_INITIALIZER(name)
参数
	name:    等待队列的结构体的名字

wait_event_interruptible()

功能
    阻塞内核,直到满足指定的条件才解除阻塞
头文件
    #include <linux/sched.h>
原型
    #define wait_event_interruptible(wq,condition)                            \
    ({                                                                            \
                              int__ret = 0;                                                        \
                              if(!(condition))                                                     \
                                       __wait_event_interruptible(wq,condition, __ret);        \
                              __ret;                                                                    \
    })    
参数
    wq    等待队列的头
    condition    条件    0  休眠  1 唤醒
返回值
    无              

wake_up_interruptible()

功能
    解除等待队列的阻塞,唤醒等待队列中的进程
头文件
    #include<linux/sched.h>
原型
    #define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)
参数
    x    等待队列的结构体指针
返回值
    无

等待队列可以防止空轮询,减少CPU资源占用,是Linux内核编程中非常重要的机制。

中断相关例程

例程分析

该例程使用了中断来监控按键状态变化,通过定时器和等待队列在按键按下时解除阻塞并通知用户空间。同时,还提供了对应的文件操作函数供用户空间使用。

全局变量
  • dev_t mydev: 设备号。
  • struct cdev mycdev: 字符设备结构体。
  • struct file_operations myfops: 文件操作结构体。
  • struct class *myclass: 类结构体。
  • unsigned int irq_num[4], myirq: 中断号和当前中断号。
  • int cond: 条件变量,用于在读取函数中阻塞解除。
函数声明
  • myfunc: 中断服务函数,当中断触发时调用。
  • myopenmyclosemyreadmypoll: 对应打开、关闭、读取和轮询操作的函数。
myopen
  • 将GPIO引脚转换为中断号。
  • 使能中断号。
  • 注册中断号并设置中断服务函数。
  • 打印日志表示设备已打开。
myclose
  • 取消中断的注册并禁用中断。
myread
  • 读取GPIO引脚的状态,如果按键按下,则将按键号传递给用户空间。
  • 当条件符合时解除阻塞并唤醒等待队列。
定时器函数 my_timer_fun
  • 当定时器激活时,检查中断号,如果对应的按键按下,则设置条件变量为1,唤醒等待队列。
mypoll
  • 使用poll系统调用进行轮询等待。
  • 当条件满足时,返回POLLIN
模块初始化和退出
  • mykey_init: 初始化函数,在模块加载时执行,包括设备号分配、字符设备注册、类结构体创建以及设备文件创建等操作。
  • mykey_exit: 退出函数,在模块卸载时执行,注销设备文件、类结构体,删除字符设备等操作。

源码分享

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/uaccess.h>
#include <linux/sched.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
#include <linux/poll.h>

dev_t mydev;
struct cdev mycdev;
struct file_operations myfops;
struct class *myclass;
unsigned int irq_num[4], myirq;
int cond;
struct timer_list mytimer;

//创建一个等待队列
DECLARE_WAIT_QUEUE_HEAD(mywait);

irqreturn_t myfunc(int num, void *args)
{
	myirq = num;
	//更新激活定时器
	mod_timer(&mytimer, jiffies + msecs_to_jiffies(15));
	return 0;
}

int myopen (struct inode *inode, struct file *file)
{
	int ret, i;
	//gpio转中断号
	for(i = 2; i < 6; i++)
	{
		irq_num[i - 2] = gpio_to_irq(EXYNOS4_GPX3(i));
		if(irq_num[i - 2] < 0)
		{
			printk("中断号转换失败\n");
			return -1;
		}
	}
	

	//使能中断号
	for(i = 0; i < 4; i++)
	{
		enable_irq(irq_num[i]);
	}
	

	//向内核注册一个中断号
	for(i = 0; i < 4; i++)
	{
		ret = request_irq(irq_num[i], myfunc, IRQF_TRIGGER_FALLING, "keytest", NULL);
		if(ret < 0)
		{
			printk("中断号注册失败\n");
			return -1;
		}
	}
	
	printk("open执行完毕\n");
	return 0;
}

int myclose (struct inode *inode, struct file *file)
{
	int i;
	//取消中断的注册
	for(i = 0; i < 4; i++)
	{
		free_irq(irq_num[i], NULL);
		disable_irq(irq_num[i]);
	}

	

	return 0;
}

ssize_t myread (struct file *file, char __user *buf, size_t size, loff_t *loff)
{
	int value, ret, i;
//	wait_event_interruptible(mywait, cond);
	printk("阻塞解除\n");
	for(i = 2; i < 6; i++)
	{
		value = gpio_get_value(EXYNOS4_GPX3(i));
		if(value == 0)
		{
			i = i - 1;
			ret = copy_to_user(buf, &i, 4);
			break;
		}
	}
	cond = 0;
	return 0;
}

//定时器回调函数
void my_timer_fun(unsigned long data){

	int i;
	for(i = 0; i < 4; i++)
	{
		if(myirq == irq_num[i])
		{
			if(gpio_get_value(EXYNOS4_GPX3(i + 2)) == 0){
				printk("按键 %d 按下\n", i + 1);
				cond = 1;
				wake_up_interruptible(&mywait);
				break;
			}
			
		}
	}
}

//poll轮询函数
unsigned int mypoll (struct file *file, struct poll_table_struct *p){

	poll_wait(file, &mywait, p);
	if(cond == 1){
		cond = 0;
		return POLLIN;
	}
	return 0;
}

static int __init mykey_init(void)
{
	//初始化定时器
	mytimer.expires  = jiffies + 2 * HZ;
	mytimer.function = my_timer_fun;
	init_timer(&mytimer);

	//申请设备号
	alloc_chrdev_region(&mydev, 0, 1, "mykey");
	printk("申请到的设备号 %d\n", mydev);
	printk("主设备号 %d\n", MAJOR(mydev));
	printk(
"次设备号 %d\n", MINOR(mydev));
	
	//初始化核心结构体
	myfops.owner = THIS_MODULE;
	myfops.open = myopen;
	myfops.release = myclose;
	myfops.read = myread;
	myfops.poll = mypoll;
	cdev_init(&mycdev, &myfops);

	//向设备注册核心结构体
	cdev_add(&mycdev, mydev, 1);

	//创建类结构体
	myclass = class_create(THIS_MODULE, "mykey");
	if(myclass == NULL)
	{
		printk("创建类结构体失败\n");
		return -1;
	}

	//创建设备文件
	device_create(myclass, NULL, mydev, NULL, "mykey");
	return 0;
}

static void __exit mykey_exit(void)
{
	//注销设备文件
	device_destroy(myclass, mydev);

	//注销类结构体
	class_destroy(myclass);

	//取消注册
	cdev_del(&mycdev);

	//释放设备好
	unregister_chrdev_region(mydev, 1);
}

module_init(mykey_init);
module_exit(mykey_exit);
MODULE_LICENSE("GPL");

总结

中断机制和等待队列在Linux内核中发挥着重要作用,它们的合理利用可以构建出高实时性和高效性的系统。中断编程涉及内核低层操作,需要谨慎处理,但掌握后可以大大提升系统的异步处理能力。

标签:函数,例程,irq,中断,int,API,Linux,include,struct
From: https://blog.51cto.com/u_16158769/9040306

相关文章

  • linux 中 ls -F选项
     linux中ls-F选项,F表示文件类型。文件末尾追加*表示是可执行文件;文件末尾/表示是目录文件末尾是@表示是软链接文件 001、[root@pc1test]#ls##测试目录a.txtb.txtdir1dir2dir3dir4file1file2file3file4[root@pc1test]#ls-l......
  • linux 中实现仅对指定目录下的目录或者文件单独进行迭代
     001、测试目录如下,分别包含目录、文件[root@pc1test]#ls##测试目录dir1dir2dir3dir4file1file2file3file4 002、仅对目录进行迭代 a、[root@pc1test]#ls##测试目录dir1dir2di......
  • CentOS For Linux搭建过程
    在搭建CentOSLinux的过程中,首先需要准备的软件是VMwareWorkstation虚拟机软件,它是在搭建CentOSLinux的过程中,首先需要准备的软件是VMwareWorkstation虚拟机软件,它是用于创建和运行虚拟机的常用工具。然后你需要下载CentOS7的镜像文件,它是一款免费开源的Linux操作系统,广泛应用......
  • linux初始
    1.linux诞生linux由林纳斯托瓦丝在1991年创立并发展至今成服务器操作系统领域的核心系统2.什么是linux系统的内核内核提供了linux系统的主要功能,如硬件调度管理能力linux内核是免费开源的,任何人都可以查看内核的源代码,甚至是贡献源代码3.什么是linux系统发行版内核无法被......
  • kube-apiserver开启审计
    k8sv1.19.0#/etc/kubernetes/pki/audit-policy.yamlapiVersion:audit.k8s.io/v1beta1kind:Policyrules:-level:Requestresources:-group:""-level:RequestResponseresources:-group:""-level:Metadatares......
  • .Net Core WebAPI 缓存
    Asp.NetCoreWebAPI缓存 一、缓存缓存指在中间层中存储数据的行为,该行为可使后续数据检索更快。从概念上讲,缓存是一种性能优化策略和设计考虑因素。缓存可以显著提高应用性能,方法是提高不常更改(或检索成本高)的数据的就绪性。二、RFC9111在最新的缓存控制规范文件RFC91......
  • linux 中取文本的最后一列
     001、测试数据,awk实现[root@pc1test]#lsa.txt[root@pc1test]#cata.txt##测试数据01020304050607080910111213141516171819202122232425262728293031323334353637383940......
  • Linux常用工具:grep/awk/sed
    Linux常用工具grep文本过滤sedsteameditor文本编辑工具awk格式化文本Ⅰ.grepgrep(globalregularexpression)命令用于查找文件里符合条件的字符串或正则表达式。命令组成grep[options]pattern[files]逐个解释grep命令的各部分pattern:表示要查找的字符串或......
  • 【APIO2016】烟火表演
    先前的题目对slopetrick的认识还不深刻,这题可以看出一个完整的构建过程。题目描述给定一棵有根树,根为\(1\),边带权,修改边权的代价时修改值与原值差的绝对值,求让所有叶子到根距离相等的最小代价。\(1\leqn\leq3\times10^5,1\leqw\leq10^9\)。算法解析首先有朴......
  • Apipost一键压测参数化功能详解
    最近更新中Apipost对UI页面进行了一些调整,另外一键压测功能支持参数化!本篇文章将详细介绍这些改动!API调试页面的细节改动在请求区填入请求参数或脚本时会有相应的标识如在Query中填入多个参数时上方会展示数量在预、后执行脚本中写入脚本上方会有绿色小点标识一键压测参数化一键压......