一、异步通知简介
异步通知:驱动程序可以主动发送信号给应用程序,通知应用程序自己可以访问了,应用程序获取到信号以后就可以从驱动设备中读取或者写入数据。
异步通知的核心是信号,在 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;
}