天天看點

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 函數就會執行,函數格式如下:

​ fasync 函數内一般調用 fasync_helper 函數來初始化前面定義的 fasync_struct 結構體指針,fasync_helper 函數定義如下:

​ 前三個參數是 fasync 函數的三個參數,第四個參數是需要初始化的 fasync_struct 結構體指針。

3、kill_fasync函數

​ 當裝置可以通路的時候,驅動程式需要向應用程式發出信号, kill_fasync 函數負責發送指定的信号, kill_fasync 函數原型如下所示:

​ 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 函數原型如下:

​ signum:要設定處理函數的信号。

​ handler:信号的處理函數。

​ 傳回值:設定成功的話傳回信号的前一個處理函數,設定失敗的話傳回 SIG_ERR。

​ 信号處理函數原型如下:

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