13_linux异步通知

一、异步通知简介

​ 异步通知:驱动程序可以主动发送信号给应用程序,通知应用程序自己可以访问了,应用程序获取到信号以后就可以从驱动设备中读取或者写入数据。

​ 异步通知的核心是信号,在 arch/arm/include/uapi/asm/signal.h 文件中定义了 linux 所支持的所有信号:

#define SIGHUP		 1
#define SIGINT		 2
#define SIGQUIT		 3
#define SIGILL		 4
#define SIGTRAP		 5
#define SIGABRT		 6
#define SIGIOT		 6
#define SIGBUS		 7
#define SIGFPE		 8
#define SIGKILL		 9
#define SIGUSR1		10
#define SIGSEGV		11
#define SIGUSR2		12
#define SIGPIPE		13
#define SIGALRM		14
#define SIGTERM		15
#define SIGSTKFLT	16
#define SIGCHLD		17
#define SIGCONT		18
#define SIGSTOP		19
#define SIGTSTP		20
#define SIGTTIN		21
#define SIGTTOU		22
#define SIGURG		23
#define SIGXCPU		24
#define SIGXFSZ		25
#define SIGVTALRM	26
#define SIGPROF		27
#define SIGWINCH	28
#define SIGIO		29
#define SIGPOLL		SIGIO
/*
#define SIGLOST		29
*/
#define SIGPWR		30
#define SIGSYS		31
#define	SIGUNUSED	31

​ 这些信号除了 SIGKILL(9)SIGSTOP(19) 这两个信号不能被忽略,其他信号都可以忽略。

二、驱动中的信号处理

1、fasync_struct结构体

​ 首先需要在驱动程序中定义一个 fasync_struct 结构体指针变量,fasync_struct 结构体定义如下:

struct fasync_struct { 
    spinlock_t fa_lock; 
    int magic; 
    int fa_fd; 
    struct fasync_struct *fa_next; 
    struct file *fa_file; 
    struct rcu_head fa_rcu; 
};

2、fasync函数

​ 使用异步通知,需要在 file_operations 操作集中添加 fasync 函数,当应用程序调用 fcntl 函数时,fasync 函数就会执行,函数格式如下:

int (*fasync) (int fd, struct file *filp, int on)

​ fasync 函数内一般调用 fasync_helper 函数来初始化前面定义的 fasync_struct 结构体指针,fasync_helper 函数定义如下:

int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)

前三个参数是 fasync 函数的三个参数,第四个参数是需要初始化的 fasync_struct 结构体指针。

3、kill_fasync函数

​ 当设备可以访问的时候,驱动程序需要向应用程序发出信号, kill_fasync 函数负责发送指定的信号, kill_fasync 函数原型如下所示:

void kill_fasync(struct fasync_struct **fp, int sig, int band)

p:要操作的 fasync_struct。

sig:要发送的信号。

band:可读时设置为 POLL_IN,可写时设置为 POLL_OUT。

4、驱动程序参考示例

struct xxx_dev { 
    ...... 
    struct fasync_struct *async_queue; /* 异步相关结构体 */ 
};

static int xxx_release(struct inode *inode, struct file *filp) 
{ 
    return xxx_fasync(-1, filp, 0); /* 删除异步通知 */ 
}

static int xxx_fasync(int fd, struct file *filp, int on) 
{ 
    struct xxx_dev *dev = (xxx_dev)filp->private_data; 
    
    if (fasync_helper(fd, filp, on, &dev->async_queue) < 0) //初始化异步通知结构体指针
        return -EIO; 
    return 0; 
}

static struct file_operations xxx_ops = { 
    ...... 
    .release = xxx_release,
    .fasync = xxx_fasync, 
    ...... 
};

​ 在关闭应用程序前,要在 release 函数释放 fasync_struct,使用 fasync 函数释放。

三、应用程序对异步通知的处理

1、使用signal函数指定信号处理函数

​ signal 函数原型如下:

sighandler_t signal(int signum, sighandler_t handler)

signum:要设置处理函数的信号。

handler:信号的处理函数。

返回值:设置成功的话返回信号的前一个处理函数,设置失败的话返回 SIG_ERR

​ 信号处理函数原型如下:

typedef void (*sighandler_t)(int)

2、将本应用程序的进程号告诉内核

​ 使用 fcntl(fd, F_SETOWN, getpid()) 将本应用程序的进程号告诉给内核。

3、开启异步通知

​ 使用如下两行程序开启异步通知:

flags = fcntl(fd, F_GETFL); /* 获取当前的进程状态 */ 
fcntl(fd, F_SETFL, flags | FASYNC); /* 开启当前进程异步通知功能 */

​ 重点就是通过 fcntl 函数设置进程状态为 FASYNC,经过这一步,驱动程序中的 fasync 函数就会执行。

四、异步通知实验

​ 按键中断后启动定时器,在定时中断中再次读取按键状态,以此实现消抖。

1、添加设备树节点

1)添加设备节点

key_input {
		compatible = "key_test";
		status = "okay";
		pinctrl-names = "default";
		pinctrl = <&keyinput>;
		gpio_key = <&gpio1 18 GPIO_ACTIVE_LOW>;
		interrupt-parent = <&gpio1>;
		interrupt = <18 IRQ_TYPE_EDGE_BOTH>;
	};

​ 第 7 行,设置 interrupt-parent 属性值为“gpio1”,因为 KEY0 所使用的 GPIO 为 GPIO1_IO18,也就是设置 KEY0 的 GPIO 中断控制器为 gpio1。

​ 第 8 行,设置 interrupts 属性,也就是设置中断源,第一个 cells 的 18 表示 GPIO1 组的 18 号 IO。 IRQ_TYPE_EDGE_BOTH 定义在文件 include/linux/irq.h 中。

2)添加pinctrl节点

​ 在 iomuxc 节点下的子节点 imx6ul-evk 中添加 pinctrl 节点:

keyinput: keygrp {
			fsl,pins = <
				MX6UL_PAD_UART1_CTS_B__GPIO1_IO18   0xf080
			>;
		};

2、添加设备结构体

/* 设备结构体 */
struct fasync_dev{
	dev_t devid;		//设备号
	int major;			//主设备号
	int minor;			//次设备号
	struct cdev cdev;	//字符设备
	struct class *class;	//类
	struct device *device;	//设备
	int key_gpio;		//按键gpio编号
	struct device_node *key_nd;	//按键节点
	int irq_num;		//中断号
	struct timer_list timer;	//定时器
	struct fasync_struct *key_fasync;	//异步通知结构体指针
	atomic_t key_state;	//按键状态
};
struct fasync_dev fasync;

3、编写加载和卸载注册函数

​ 加载和卸载注册函数如下:

module_init(fasync_init);
module_exit(fasync_exit);

入口函数:

/* 入口函数 */
static int __init fasync_init(void)
{
	int ret = 0;
	atomic_set(&fasync.key_state, KEY_LOOSE);
	/* 处理设备号 */
	fasync.major = 0;
	if(fasync.major){	//设置了主设备号
		fasync.minor = 0;
		fasync.devid = MKDEV(fasync.major, fasync.minor);
		ret = register_chrdev_region(fasync.devid, DEVICE_CNT, DEVICE_NAME);
		if(ret < 0){	//注册失败
			printk("fail to register devid\r\n");
			return -EINVAL;
		}
	}else{	//没有设置主设备号
		ret = alloc_chrdev_region(&fasync.devid, 0, DEVICE_CNT, DEVICE_NAME);
		if(ret < 0){	//申请设备号失败
			printk("fail to register devid\r\n");
			return -EINVAL;
		}
		fasync.major = MAJOR(fasync.devid);
		fasync.minor = MINOR(fasync.devid);
	}
	printk("major = %d\r\nminor = %d\r\n", fasync.major, fasync.minor);

	/* 注册字符设备 */
	fasync.cdev.owner = THIS_MODULE;
	cdev_init(&fasync.cdev, &fasync_fops);
	ret = cdev_add(&fasync.cdev, fasync.devid, DEVICE_CNT);
	if(ret < 0){
		ret = -EINVAL;
		printk("fail too add cdev\r\n");
		goto fail_add_cdev;
	}

	/* 自动注册设备节点 */
	fasync.class = NULL;
	fasync.device = NULL;
	fasync.class = class_create(THIS_MODULE, DEVICE_NAME);
	if(fasync.class == NULL){
		ret = -EINVAL;
		printk("fail to create class\r\n");
		goto fail_create_class;
	}
	fasync.device = device_create(fasync.class, NULL, fasync.devid, NULL, DEVICE_NAME);
	if(fasync.device == NULL){
		ret = -EINVAL;
		printk("fail to create device\r\n");
		goto fail_create_device;
	}
	printk("driver init\r\n");

	return 0;

fail_add_cdev:
	unregister_chrdev_region(fasync.devid, DEVICE_CNT);
fail_create_class:
	cdev_del(&fasync.cdev);
	unregister_chrdev_region(fasync.devid, DEVICE_CNT);
fail_create_device:
	class_destroy(fasync.class);
	cdev_del(&fasync.cdev);
	unregister_chrdev_region(fasync.devid, DEVICE_CNT);

	return ret;
}

出口函数:

​ 如果激活了定时器,在卸载模块时,一定要先删除定时器,否则将无法卸载模块。

/* 出口函数 */
static void __exit timer_exit(void)
{
	gpio_free(fasync.key_gpio);		//注销gpio
	del_timer_sync(&fasync.timer);		//删除定时器
	free_irq(fasync.irq_num, &fasync);	//注销中断
	device_destroy(fasync.class, fasync.devid);	//删除设备
	class_destroy(fasync.class);		//删除类
	cdev_del(&fasync.cdev);			//删除字符设备
	unregister_chrdev_region(fasync.devid, DEVICE_CNT);	//注销设备号
}

4、编写按键中断初始化函数

/* 按键初始化函数 */
static int Key_init(void)
{
	int ret = 0;

	/* 获取设备节点 */
	fasync.key_nd = of_find_node_by_name(NULL, "key_input");
	if(fasync.key_nd == NULL){
		printk("fail to get node\e\n");
		return -EINVAL;
	}

	/* 获取gpio编号 */
	fasync.key_gpio = of_get_named_gpio(fasync.key_nd, "gpio_key", 0);
	if(fasync.key_gpio < 0){
		printk("fail to get gpio num\r\n");
		return -EINVAL;
	}

	/* 设置gpio属性 */
	gpio_request(fasync.key_gpio, "key");
	gpio_direction_input(fasync.key_gpio);

	/* 获取中断号 */
	fasync.irq_num = gpio_to_irq(fasync.key_gpio);

	/* 申请中断 */
	ret = request_irq(fasync.irq_num, key_handler, IRQF_TRIGGER_FALLING, "key_irq", &fasync);

	return 0;
}

5、编写中断回调函数

/* 中断处理函数 */
static irqreturn_t key_handler(int irq_num, void *dev_id)
{
	struct fasync_dev *dev = (struct fasync_dev *)dev_id;
	mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));	//开启定时器
	return IRQ_RETVAL(IRQ_HANDLED);
}

6、编写定时器初始化函数

/* 定时器初始化函数 */
void timer_init(void)
{
	init_timer(&fasync.timer);	//初始化定时器
	fasync.timer.function = timer_function;	//注册定时回调函数
	fasync.timer.data = (unsigned long)&fasync;	//设置回调函数参数
}

7、编写定时回调函数

​ linux 内核的定时器启动后只会运行一次,如果要连续定时,需要在回调函数中重新启动定时器。

/* 定时回调函数 */
void timer_function(unsigned long arg)
{
	struct fasync_dev *dev = (struct fasync_dev *)arg;

	if(gpio_get_value(dev->key_gpio) == 0){
		atomic_set(&dev->key_state, KEY_PRESS);
		kill_fasync(&dev->key_fasync, SIGIO, POLL_IN);	//发送信号给应用程序数据可读
	}
}

8、编写设备的具体操作函数

/* open函数 */
static int fasync_open(struct inode *inode, struct file *filp)
{
	int ret = 0;

	filp->private_data = &fasync;	//设置私有数据

	timer_init();
	ret = Key_init();

	return 0;
}

/* read函数 */
static ssize_t fasync_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
	
	int key_state,ret;
	struct fasync_dev *dev = filp->private_data;

	key_state = atomic_read(&dev->key_state);

	if(key_state == KEY_PRESS){
		ret = copy_to_user(buf, &key_state, cnt);
	}
	atomic_set(&dev->key_state, KEY_LOOSE);

	return 0;
}

/* fasync函数 */
int fasync_key(int fd, struct file *filp, int on)
{
	struct fasync_dev *dev = filp->private_data;

	if(fasync_helper(fd, filp, on, &dev->key_fasync) < 0)
		return -EIO;

	return 0;
}

/* release函数 */
static int fasync_release(struct inode *inode, struct file *filp)
{
	fasync_key(-1, filp, 0); //删除异步通知
	return 0;
}

/* 操作函数集合 */
static const struct file_operations fasync_fops = {
	.owner = THIS_MODULE,
	.open  = fasync_open,
	.read  = fasync_read,
	.fasync = fasync_key,
	.release = fasync_release,
};

4、添加头文件

​ 参考 linux 内核的驱动代码时,找到可能用到的头文件,添加进工程。在调用系统调用函数库函数时,在终端使用 man 命令可查看调用的函数需要包含哪些头文件。
​ man 命令数字含义:1:标准命令 2:系统调用 3:库函数
​ 添加以下头文件:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <asm/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/atomic.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
#include <linux/of_irq.h> 
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>

#define DEVICE_NAME "fasync"
#define DEVICE_CNT 1
#define KEY_PRESS 0
#define KEY_LOOSE 1

5、添加 License 和作者信息

​ 驱动的 License 是必须的,缺少的话会报错,在文件最末端添加以下代码:

MODULE_LICENSE("GPL");
MODULE_AUTHOR("lzk");

6、编写测试应用

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include "linux/ioctl.h"
#include "poll.h"
#include "sys/select.h"
#include "signal.h"
#include "fcntl.h"

static int fd = 0;

static void signal_func(int arg)
{
    static key_state = 1;

    read(fd, &key_state, sizeof(key_state));
    if(key_state == 0)
            printf("key press\r\n");
        key_state = 1;
}

int main(int argc, char *argv[])
{
    int flags = 0;
    char *filename;
    int key_state = 1;

    
    if(argc != 2){
        printf("missing parameter!\r\n");
        return -1;
    }

    filename = argv[1];
    fd = open(filename, O_RDWR | O_NONBLOCK);   //非阻塞方式打开驱动
    if(fd < 0){
        printf("open file %s failed\r\n", filename);
        return -1;
    }

    signal(SIGIO, signal_func); //指定信号处理函数
    fcntl(fd, F_SETOWN, getpid());  //将应用程序的进程号告诉内核
    flags = fcntl(fd, F_GETFL); /* 获取当前的进程状态 */ 
    fcntl(fd, F_SETFL, flags | FASYNC); /* 开启当前进程异步通知功能 */

    while(1){
        sleep(2);
    }

    close(fd);

    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值