53 异步通知

  • 在前面使用 阻塞 或者 非阻塞 的方式来读取驱动中按键值都是应用程序主动读取
    对于 非阻塞方式 来说还需要应用程序通过 poll 函数不断的轮询。
    最好的方式就是驱动程序能主动向应用程序发出通知,报告自己可以访问,然后应用程序在从驱动程序中读取或写入数据,类似于我们在裸机例程中讲解的中断。 Linux 提供了 异步通知 这个机制来完成此功能。

  • 异步通知简介
    我们首先来回顾一下中断,中断是处理器提供的一种异步机制,我们配置好中断以后就可以让处理器去处理其他的事情了,当中断发生以后会触发我们事先设置好的中断服务函数,在中断服务函数中做具体的处理。
    比如我们在裸机篇里面编写的 GPIO 按键中断实验,我们通过按键去开关蜂鸣器,采用中断以后处理器就不需要时刻的去查看按键有没有被按下,因为按键按下以后会自动触发中断。
    同样的, Linux 应用程序可以通过 阻塞 或者 非阻塞 这两种方式来访问驱动设备:
    通过 阻塞方式 访问的话应用程序会处于休眠态,等待驱动设备可以使用。
    通过 非阻塞方式 的话会通过 poll 函数来不断的轮询,查看驱动设备文件是否可以使用。
    这两种方式都需要应用程序主动的去查询设备的使用情况,如果能提供一种类似中断的机制,当驱动程序可以访问的时候主动告诉应用程序那就最好了。

  • 信号为此应运而生,信号类似于硬件上使用的“中断”,只不过信号是软件层次上的。
    算是在软件层次上对中断的一种模拟,驱动可以通过主动向应用程序发送信号的方式来报告自己可以访问了,应用程序获取到信号以后就可以从驱动设备中读取或者写入数据了。
    整个过程就相当于应用程序收到了驱动发送过来了的一个中断,然后应用程序去响应这个中断,在整个处理过程中应用程序并没有去查询驱动设备是否可以访问,一切都是由驱动设备自己告诉给应用程序的。

  • 阻塞、非阻塞、异步通知,这三种是针对不同的场合提出来的不同的解决方法:
    阻塞io是等待设备可以访问后再访问
    非阻塞io是查询设备是否可以访问
    异步通知是设备通知自身可以访问

  • 异步通知的核心就是信号,在 arch/xtensa/include/uapi/asm/signal.h 文件中定义了 Linux 所支持的所有信号,这些信号如下所示:

#define SIGHUP 1 /* 终端挂起或控制进程终止 */
#define SIGINT 2 /* 终端中断(Ctrl+C 组合键) */
#define SIGQUIT 3 /* 终端退出(Ctrl+\组合键) */
#define SIGILL 4 /* 非法指令 */
#define SIGTRAP 5 /* debug 使用,有断点指令产生 */
#define SIGABRT 6 /* 由 abort(3)发出的退出指令 */
#define SIGIOT 6 /* IOT 指令 */
#define SIGBUS 7 /* 总线错误 */
#define SIGFPE 8 /* 浮点运算错误 */
#define SIGKILL 9 /* 杀死、终止进程 */
#define SIGUSR1 10 /* 用户自定义信号 1 */
#define SIGSEGV 11 /* 段违例(无效的内存段) */
#define SIGUSR2 12 /* 用户自定义信号 2 */
#define SIGPIPE 13 /* 向非读管道写入数据 */
#define SIGALRM 14 /* 闹钟 */
#define SIGTERM 15 /* 软件终止 */
#define SIGSTKFLT 16 /* 栈异常 */
#define SIGCHLD 17 /* 子进程结束 */
#define SIGCONT 18 /* 进程继续 */
#define SIGSTOP 19 /* 停止进程的执行,只是暂停 */
#define SIGTSTP 20 /* 停止进程的运行(Ctrl+Z 组合键) */
#define SIGTTIN 21 /* 后台进程需要从终端读取数据 */
#define SIGTTOU 22 /* 后台进程需要向终端写数据 */
#define SIGURG 23 /* 有"紧急"数据 */
#define SIGXCPU 24 /* 超过 CPU 资源限制 */
#define SIGXFSZ 25 /* 文件大小超额 */
#define SIGVTALRM 26 /* 虚拟时钟信号 */
#define SIGPROF 27 /* 时钟信号描述 */
#define SIGWINCH 28 /* 窗口大小改变 */
#define SIGIO 29 /* 可以进行输入/输出操作 */
#define SIGPOLL SIGIO
/* #define SIGLOS 29 */
#define SIGPWR 30 /* 断点重启 */
#define SIGSYS 31 /* 非法的系统调用 */
#define SIGUNUSED 31 /* 未使用信号 */

每个信号都有默认的信号处理函数,也可以由用户去修改
除了 ·SIGKILL(9)SIGSTOP(19) 这两个信号不能被忽略外,其他的信号都可以忽略。这些信号就相当于中断号,不同的中断号代表了不同的中断,不同的中断所做的处理不同,因此,驱动程序可以通过向应用程序发送不同的信号来实现不同的功能。
shell下的信号名称要去掉 SIG:如 c 中的 SIGKILL 应变为 shell 下的 KILL

  • 我们使用中断的时候需要设置中断处理函数,同样的,如果要在应用程序中使用信号,那么就必须设置信号所使用的信号处理函数,在应用程序中使用 signal 函数来设置指定信号的处理函数, signal 函数原型如下所示:
sighandler_t signal(int signum, sighandler_t handler)
函数参数和返回值含义如下:
signum:要设置处理函数的信号。
handler: 信号的处理函数。
返回值: 设置成功的话返回信号的前一个处理函数,设置失败的话返回 SIG_ERR。

信号处理函数原型如下所示:

typedef void (*sighandler_t)(int)

我们前面讲解的使用 kill -9 PID 杀死指定进程的方法就是向 指定的进程(PID) 发送 SIGKILL 信号
当按下键盘上的 CTRL+C 组合键 以后会向当前正在占用终端的应用程序发出 SIGINT 信号 , SIGINT 信号默认的动作是关闭当前应用程序。

  • 这里我们修改一下 SIGINT 信号的默认处理函数,当按下 CTRL+C 组合键以后先在终端上打印出“SIGINT signal!”这行字符串,然后再关闭当前应用程序。新建 signaltest.c 文件,然后输入如下所示内容:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<signal.h>

void sigint_handler(int num)
{
    printf("\r\nSIGINT signal\r\n");
    exit(0);
}

int main(int argc, char** argv)
{
    signal(SIGINT, sigint_handler);
    while(1);
    return 0;
}

在示例代码 53.1.1.2 中我们设置 SIGINT 信号的处理函数为 sigint_handler,当按下 CTRL+C向 signaltest 发送 SIGINT 信号以后 sigint_handler 函数就会执行,此函数先输出一行“SIGINT signal!”字符串,然后调用 exit 函数关闭 signaltest 应用程序。
当按下 CTRL+C 组合键以后 sigint_handler 这个 SIGINT 信号处理函数执行了,并且输出了“SIGINT signal!”这行字符串。

驱动中的信号处理

  • 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;
};

一般将 fasync_struct 结构体指针变量定义到设备结构体中,比如在上一章节的 imx6uirq_dev
结构体中添加一个 fasync_struct 结构体指针变量,结果如下所示:

struct imx6uirq_dev {
struct device *dev;
struct class *cls;
struct cdev cdev;
......
struct fasync_struct *async_queue; /* 异步相关结构体 */
};
  • fasync 函数
    如果要使用异步通知,需要在设备驱动中实现 file_operations 操作集中的 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_helper 函数的前三个参数就是 fasync 函数的那三个参数
第四个参数就是要初始化的 fasync_struct 结构体指针变量。
当应用程序通过“fcntl(fd, F_SETFL, flags | FASYNC)”改变fasync 标记的时候,
	驱动程序 file_operations 操作集中的 fasync 函数就会执行。
  • 驱动程序中的 fasync 函数参考示例如下:
struct xxx_dev {
	......
	struct fasync_struct *async_queue; /* 异步相关结构体 */
};

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 = {
	......
	.fasync = xxx_fasync,
	......
};

在关闭驱动文件的时候需要在 file_operations 操作集中的 release 函数中释放 fasync_struct
fasync_struct 的释放函数同样为 fasync_helper, release 函数参数参考实例如下:

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

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

第 3 行通过调用示例代码 53.1.2.3 中的 xxx_fasync 函数来完成 fasync_struct 的释放工作,但是,其最终还是通过 fasync_helper 函数完成释放工作。

  • kill_fasync 函数
    当设备可以访问的时候,驱动程序需要向应用程序发出信号,相当于产生“中断”。 kill_fasync 函数负责发送指定的信号
    kill_fasync 函数原型如下所示
void kill_fasync(struct fasync_struct **fp, int sig, int band)
函数参数和返回值含义如下:
fp:要操作的 fasync_struct。
sig: 要发送的信号。本次实验用到 SIGIO 信号
band: 可读时设置为 POLL_IN,可写时设置为 POLL_OUT。
返回值: 无
  • 应用程序 对 异步通知的处理
    应用程序对异步通知的处理包括以下三步
    1、注册信号处理函数
    应用程序根据驱动程序所使用的信号来设置信号的处理函数,应用程序使用 signal 函数来设置信号的处理函数。前面已经详细的讲过了,这里就不细讲了。
    2、将本应用程序的进程号告诉给内核
    使用 fcntl(fd, F_SETOWN, getpid())将本应用程序的进程号告诉给内核。
    3、开启异步通知
    使用如下两行程序开启异步通知:
flags = fcntl(fd, F_GETFL); /* 设置将要在文件描述符fd上接收SIGIO 或 SIGURG事件信号的进程或进程组标识 。 */
fcntl(fd, F_SETFL, flags | FASYNC); /* 开启当前进程异步通知功能 */

重点就是通过 fcntl 函数设置进程状态为 FASYNC,经过这一步,驱动程序中的 fasync 函数就会执行
fcntl的详细使用
Linux系统调用–fcntl函数详解
FD_CLOEXEC 详解

驱动

  • 1、async.c驱动
#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/init.h>
#include <linux/fs.h>
#include<linux/slab.h>
#include<linux/io.h>
#include<linux/uaccess.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/gpio.h>
#include<linux/of_gpio.h>
#include<linux/atomic.h>
#include<linux/timer.h>
#include<linux/string.h>
#include<linux/jiffies.h>
#include<linux/irq.h>
#include<asm/mach/map.h>
#include<asm/uaccess.h>
#include<asm/io.h>
#include<linux/interrupt.h>
#include<linux/fcntl.h>
#include<linux/poll.h>
#include<linux/ide.h>

#define     IRQ_CNT             1
#define     IMX6ULIRQ_NAME      "imx6ulirq"
#define     KEY_NUM             1
#define     KEY0_ACTIVE         1
#define     KEY0_INACTIVE       2

// 按键结构体
struct irq_keydesc
{
    int gpio;   // gpio号
    int irqnum; // 中断号
    u8 value;   // 键值
    char name[8];//名字
    irqreturn_t (*handler)(int, void *);// 按键中断处理函数
};

struct imx6ulirq_dev
{
    dev_t devid;
    u32 major;
    u32 minor;
    struct cdev cdev;
    struct class *class;
    struct device *device;
    struct device_node *nd;
    int led_gpio;
    int beep_gpio;
    struct timer_list timer;
    int timer_period;
    atomic_t keyvalue;
    atomic_t releasekey;
    struct irq_keydesc irqkey;
    struct fasync_struct *fasync; // 异步通知所使用的结构体

};

struct imx6ulirq_dev imx6ulirq;

static int imx6ulirq_open(struct inode *inode, struct file *filp);
static int imx6ulirq_release(struct inode *inode, struct file *filp);
static ssize_t imx6ulirq_read(struct file *file, char __user *buf, size_t count,
			            		loff_t *ppos);
static int imx6ulirq_fasync(int fd, struct file *file, int on);

static const struct file_operations imx6ulirq_fops = 
{
    .owner = THIS_MODULE,
    .open = imx6ulirq_open,
    .read = imx6ulirq_read,
    .release = imx6ulirq_release,
    .fasync = imx6ulirq_fasync, // 新增函数
};

static int imx6ulirq_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &imx6ulirq;
    return 0;
}

static ssize_t imx6ulirq_read(struct file *file, 
                                char __user *buf, 
                                size_t count,
			                    loff_t *ppos)
{
    int ret = 0;
    u8 keyvalue = 0;
    u8 releasekey = 0;
    struct imx6ulirq_dev *dev = file->private_data;

    keyvalue = atomic_read(&dev->keyvalue);
    releasekey = atomic_read(&dev->releasekey);
    printk("%s(%d):\n", __FILE__, __LINE__);
    if(releasekey)
    {
        if (keyvalue == KEY0_INACTIVE)
        {
            printk("%s(%d):\n", __FILE__, __LINE__);
            ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
            keyvalue = KEY0_INACTIVE;
        }
        atomic_set(&dev->releasekey, 0);
    }

    return ret;    
}

static int imx6ulirq_release(struct inode *inode, struct file *filp)
{
	// 释放 fasync_struct 结构体
    return imx6ulirq_fasync(-1, filp, 0);
}

static int imx6ulirq_fasync(int fd, struct file *file, int on)
{
    struct imx6ulirq_dev *dev = file->private_data;
    return fasync_helper(fd, file, on, &dev->fasync);
}

// 按键中断处理函数
// 第二个参数是由 request_irq 最后一个参数传递进来的
static irqreturn_t key0_handler(int irq, void *dev_id)
{
    
    struct imx6ulirq_dev *dev = dev_id;
    // 开启定时器
    dev->timer.data = (u32 )dev_id;
    mod_timer(&dev->timer, jiffies+msecs_to_jiffies(10));

    return IRQ_HANDLED;
}

// timer_list 结构体中的一个成员变量做为此函数的参数
static void timer_func(unsigned long arg)
{
    int value = 0;
    struct imx6ulirq_dev *dev = (void *)arg;

    value = gpio_get_value(dev->irqkey.gpio);
    if(value == 0) 
    {
        printk("%s(%d):KEY0 PUSHED \n", __FILE__, __LINE__);
        atomic_set(&dev->keyvalue, KEY0_ACTIVE);
    }
    else if(value == 1)
    {
        printk("%s(%d):KEY0 RELEASED \n", __FILE__, __LINE__);
        atomic_set(&dev->keyvalue, KEY0_INACTIVE);
        atomic_set(&dev->releasekey, 1);
    }

    if(atomic_read(&dev->releasekey))
    {
    	// 向 app 发送 POLL_IN 信号
        kill_fasync(&dev->fasync, SIGIO, POLL_IN);
    }
}

static int keygpio_init(struct imx6ulirq_dev *dev)
{
    int ret = 0;
    int i, j;
   // 1.按键gpio初始化
    dev->nd = of_find_node_by_path("/gpiokey");
    if(dev->nd == NULL)
    {
        ret = -1;
        printk("%s(%d):fail\n", __FILE__, __LINE__);
        goto fail_nd;
    }

    
     dev->irqkey.gpio = of_get_named_gpio(dev->nd, 
     										"key-gpios",
	 										i );
     if(dev->irqkey.gpio < 0)
     {
     	ret = -1;
     	printk("%s(%d):fail,i = %d\n", __FILE__, __LINE__, i);
     	goto fail_gpio;
     }


    
    memset(dev->irqkey.name, 0, sizeof(dev->irqkey.name));
    sprintf(dev->irqkey.name, "key_%d", i);
    ret = gpio_request(dev->irqkey.gpio, dev->irqkey.name);
    if(ret)
    {
    	ret = -1;
    	printk("%s(%d):fail,i = %d\n", __FILE__, __LINE__, i);
    	goto fail_request;
    }
    	gpio_direction_input(dev->irqkey.gpio);

    // 获取中断号
#if 1
	dev->irqkey.irqnum = gpio_to_irq(dev->irqkey.gpio);
#else
	dev->irqkey.irqnum = irq_of_parse_and_map(dev->nd, i);
#endif
	dev->irqkey.handler = key0_handler;
	dev->irqkey.value = KEY0_ACTIVE;
  

  	 // 2.按键中断初始化
     ret = request_irq(dev->irqkey.irqnum, 
                            dev->irqkey.handler,
                            IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING,
                            dev->irqkey.name, // 中断名字,设置以后可以在/proc/interrupts 文件中看到对应的中断名字。
                            &imx6ulirq ); // 此参数传给中断处理函数的第二个参数
     if(ret)
     {
      	free_irq(dev->irqkey.irqnum, NULL);
      	ret = -1;
        goto fail_irq;
     }

   	 return 0;

fail_irq:
	gpio_free(dev->irqkey.gpio);

fail_request:
fail_gpio:
fail_nd:
    return ret;
}

static int __init imx6ulirq_init(void)
{
    int ret = 0;

    //1
    imx6ulirq.major = 0;
    if(imx6ulirq.major)
    {
        imx6ulirq.devid = MKDEV(imx6ulirq.major, 0);
        ret = register_chrdev_region(imx6ulirq.devid, 1, IMX6ULIRQ_NAME);
    }
    else
    {
        ret = alloc_chrdev_region(&imx6ulirq.devid, 0, 1, IMX6ULIRQ_NAME);
        imx6ulirq.major = MAJOR(imx6ulirq.devid);
        imx6ulirq.minor = MAJOR(imx6ulirq.devid);
    }
    if(ret < 0)
    {
        printk("%s(%d):fail\n", __FILE__, __LINE__);
        goto fail_devid;
    }

    //2
    imx6ulirq.cdev.owner = THIS_MODULE;
    cdev_init(&imx6ulirq.cdev, &imx6ulirq_fops);
    ret = cdev_add(&imx6ulirq.cdev, imx6ulirq.devid, 1);
    if(ret < 0)
    {
        printk("%s(%d):fail\n", __FILE__, __LINE__);
        goto fail_cdev;
    }

    //3
    imx6ulirq.class = class_create(THIS_MODULE, IMX6ULIRQ_NAME);
    if(IS_ERR(imx6ulirq.class))
    {
        printk("%s(%d):fail\n", __FILE__, __LINE__);
        ret = PTR_ERR(imx6ulirq.class);
        goto fail_class;
    }

    imx6ulirq.device = device_create(imx6ulirq.class, NULL, imx6ulirq.devid, NULL, IMX6ULIRQ_NAME);
    if(IS_ERR(imx6ulirq.device))
    {
        printk("%s(%d):fail\n", __FILE__, __LINE__);
        ret = PTR_ERR(imx6ulirq.device);
        goto fail_device;
    }

    //4
    printk("%s(%d):\n", __FILE__, __LINE__);
    ret = keygpio_init(&imx6ulirq);
    if(ret < 0)
    {
        goto fail_keyinit;
    }

    init_timer(&imx6ulirq.timer);
    imx6ulirq.timer.function = timer_func;

    atomic_set(&imx6ulirq.keyvalue, KEY0_INACTIVE);
    atomic_set(&imx6ulirq.keyvalue, 0);

    return 0;

    printk("%s(%d):fail\n", __FILE__, __LINE__);
fail_keyinit:
fail_device:
    class_destroy(imx6ulirq.class);
fail_class:
    cdev_del(&imx6ulirq.cdev);
fail_cdev:
    unregister_chrdev_region(imx6ulirq.devid, 1);
fail_devid:
    return ret;
}

static void __exit imx6ulirq_exit(void)
{
    int i;
    // 释放中断
    free_irq(imx6ulirq.irqkey.irqnum, NULL);
    // 释放 io
    gpio_free(imx6ulirq.irqkey.gpio);
    del_timer_sync(&imx6ulirq.timer);
    cdev_del(&imx6ulirq.cdev);
    unregister_chrdev_region(imx6ulirq.devid, IRQ_CNT);
    device_destroy(imx6ulirq.class, imx6ulirq.devid);
    class_destroy(imx6ulirq.class);

}

module_init(imx6ulirq_init);
module_exit(imx6ulirq_exit);
MODULE_LICENSE("GPL");
  • 测试APP
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include "linux/ioctl.h"
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <poll.h>
#include <signal.h>
#include <fcntl.h>

int fd;

static void sigio_func(int num)
{
	int err;
	unsigned char keyvalue = 0;
	err = read(fd, &keyvalue, sizeof(keyvalue));
	printf("%s(%d)\r\n", __FILE__, __LINE__);
	if(err < 0)
	{
		printf("%s(%d):error\r\n", __FILE__, __LINE__);
	}
	else
	{
		printf("keyvalue = %d\r\n", keyvalue);
	}
}

int main(int argc, char *argv[])
{
	int ret = 0;
	char *filename;
	struct pollfd fds;
	unsigned char data;
	unsigned int flags = 0;
	
	if (argc != 2) {
		printf("Error Usage!\r\n");
		return -1;
	}

	filename = argv[1];
	fd = open(filename, O_RDWR | O_NONBLOCK);
	if (fd < 0) {
		printf("Can't open file %s\r\n", filename);
		return -1;
	}
    printf("%s(%d):\n", __FILE__, __LINE__);
	
	signal(SIGIO, sigio_func);
	// 设置将要在文件描述词fd上接收 SIGIO 或 SIGURG事件信号
	// 的进程或进程组标识 
	fcntl(fd, F_SETOWN, getpid());
	// 读取文件状态标志
	flags = fcntl(fd, F_GETFL);
	// 通过 fcntl 函数设置进程状态为 FASYNC,
	// 经过这一步,驱动程序中的 fasync 函数就会执行。
	fcntl(fd, F_SETFL, flags|FASYNC);
	while(1);
	close(fd);
	return ret;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值