歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> 關於Linux >> Linux的fasync驅動異步通知詳解

Linux的fasync驅動異步通知詳解

日期:2017/3/1 11:43:26   编辑:關於Linux

工作項目用有個需求是監測某個GPIO輸入方波的頻率!通俗的講就是一個最最簡單的測方波頻率的示波器!不過只是測方波的頻率!頻率范圍是0~200HZ,而且頻率方波不是一直都是200HZ,大多數的時候可能一直是0或者一個更低頻率的方波!同時要考慮到方波有可能一直維持在200HZ ,同時保持效率和性能的情況下,fasync驅動異步通知是個不錯的選擇,當初寫demo的時候實測1K的方波完全沒有問題!應用到項目中也是完全能滿足需求!驅動很簡單!業余時間把自己之前學到的知識總結一下!對自己也是個提高!

根據需求,驅動中實現比較簡單!自己只實現open、close、fasync和read函數 ,這裡只需要讀取方波的頻率即可!

驅動大概實現原理:方波每產生一個下降沿,產生一個中斷,然後根據中斷在通過異步通知應用程序,以此來測定輸入方波的頻率!

fansync機制的優勢是能使驅動的讀寫和應用程序的讀寫分開,使得應用程序可以在驅動讀寫的時候去做別的事情!

下面是驅動的源碼:

**-------File Info---------------------------------------------------------------------------------------
** File Name:               gpioInt.c
** Latest modified Data:    2015_11_16
** Latest Version:          v1.0
** Description:             NOME
**
**--------------------------------------------------------------------------------------------------------
** Create By:               K
** Create date:             20015-11-16
** Version:                 v1.0
** Descriptions:            混雜設備驅動程序 GPIO中斷驅動 下降沿觸發GPIO 內核會向用戶空間發送一個鍵值
**						    用戶態的應用程序通過讀取鍵值來判斷GPIO中斷狀況
**
**--------------------------------------------------------------------------------------------------------
*********************************************************************************************************/
#include
#include
#include                                                  
#include                                                 
#include"mach/../../mx28_pins.h"
#include 
#include "mach/mx28.h"
#include
#include 
#include                                     
#include                          
#include                          
#include                   
#include              
#include
#include 
#include 
#include 
#include 

/* 
*中斷事件標志,中斷服務程序將它置1,在gpio_drv_read將它清0 
*/
static volatile int ev_press = 0;

/*
*異步結構體指針 用於讀
*/
static struct fasync_struct *b_async;

/*
*中斷引腳描述結構體
*/
struct pin_desc_s{				
	unsigned int pin;
	unsigned int key_val;
	unsigned int irq;
};
static unsigned char key_val;

struct pin_desc_s pin_desc[5] = {
	{MXS_PIN_TO_GPIO(PINID_LCD_ENABLE),0x03,},    /* IO1 rain GPIO1_31     */  
	{MXS_PIN_TO_GPIO(PINID_LCD_HSYNC),0x05,},     /* IO2 windspeed GPIO1_29*/
	{MXS_PIN_TO_GPIO(PINID_LCD_DOTCK),0x0A,},     /* 機箱門             */
	{MXS_PIN_TO_GPIO(PINID_AUART3_RX),0x07,},     /* key1 GPIO3_12         */ 
	{MXS_PIN_TO_GPIO(PINID_AUART3_TX),0x09,},     /* key2 GPIO3_13         */ 
};


static DECLARE_MUTEX(b_lock);     
static DECLARE_WAIT_QUEUE_HEAD(b_waitq);


static irqreturn_t b_irq(int irq, void *dev_id)
{
	struct pin_desc_s * pindesc = (struct pin_desc_s *)dev_id;
	unsigned int pinval;
	
	pinval = gpio_get_value(pindesc->pin);

	if (pinval)
	{
		key_val = 1;
	}
	else
	{
		key_val = pindesc->key_val;
	}
        ev_press = 1;
        wake_up_interruptible(&b_waitq);		//喚醒等待隊列裡面的進程
		kill_fasync(&b_async, SIGIO, POLL_IN);	//異步通知
	//printk("interrupt occur..........\n");
	return IRQ_RETVAL(IRQ_HANDLED);
}

static int gpio_drv_open(struct inode *inode, struct file *file)
{
	int iRet[5]={0};
	int i = 0;

	if (file->f_flags & O_NONBLOCK)
	{
		if (down_trylock(&b_lock))
			return -EBUSY;
	}
	else
	{
		down(&b_lock);
	}
	
	
	for(i = 0; i < 5; i++)
	{
		gpio_direction_input((pin_desc[i]).pin);
		(pin_desc[i]).irq = gpio_to_irq((pin_desc[i]).pin); 
		if ((pin_desc[i]).irq) 
			disable_irq((pin_desc[i]).irq);
		set_irq_type((pin_desc[i]).irq, IRQF_TRIGGER_FALLING);	//下降沿中斷
		iRet[i] = request_irq((pin_desc[i]).irq, buttons_irq, IRQF_SHARED, "gpio_int", &pin_desc[i]);
		if (iRet[i] != 0){
			printk("request irq failed!! ret: %d  irq:%d \n", iRet[i],(pin_desc[i]).irq);
		return -EBUSY;}
	
	}
	
	
	return 0;
}

ssize_t gpio_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
	if (size != 1)
		return -EINVAL;

	if (file->f_flags & O_NONBLOCK)
	{
		if (!ev_press)
			return -EAGAIN;
	}
	else
	{
		wait_event_interruptible(b_waitq, ev_press);
	}
	copy_to_user(buf, &key_val, 1);
	ev_press = 0;
	
	return 1;
}

int gpio_drv_close(struct inode *inode, struct file *file)
{
	int i = 0;
	
	for( i = 0; i < 5; i++)
	{
		free_irq((pin_desc[i]).irq, &pin_desc[i]);
	}

	up(&b_lock);
	return 0;
}

static int gpio_drv_fasync (int fd, struct file *filp, int on)
{
	printk("driver: gpio_drv_successful\n");
	return fasync_helper (fd, filp, on, &b_async);
}

static struct file_operations gpio_drv_fops = {
	.owner		= THIS_MODULE,
	.open		= gpio_drv_open,
	.read		= gpio_drv_read,
	.release	= gpio_drv_close,
	.fasync	 	= gpio_drv_fasync,
};

static struct miscdevice b_miscdev = 
{
	.minor	        = MISC_DYNAMIC_MINOR,
   	.name	        = "magic-gpio",
    .fops	        = &gpio_drv_fops,
};

static int __init gpio_drv_init(void)
{
	int iRet=0;
	printk("gpio_miscdev module init!\n");
	iRet = misc_register(&b_miscdev);
	if (iRet) {
		printk("register failed!\n");
	} 
	return 0;
}

static void __exit gpio_drv_exit(void)
{
	printk("gpio_miscdev module exit!\n");
	misc_deregister(&b_miscdev);
}

module_init(gpio_drv_init);
module_exit(gpio_drv_exit);

MODULE_AUTHOR("HEHAI & RK");
MODULE_LICENSE("Dual BSD/GPL");
MODULE_DESCRIPTION("gpio interrupt module");

首先還是先從init函數來總結:該驅動是一混雜設備驅動模型來寫的,這個主要是借鑒網上的好多資料都是一這種模式來寫的,Linux裡面misc混雜設備驅動的主設備號是為10的驅動設備,init模塊首先是用 misc_register()函數注冊一個一個混雜設備驅動,參數一個混雜設備驅動裡面非常重要的一個數據結構 struct miscdevice,下面把原型貼出來:

struct miscdevice  {
    int minor;
    const char *name;
    const struct file_operations *fops;
    struct list_head list;
    struct device *parent;
    struct device *this_device;
    const char *nodename;
    mode_t mode;
};
當然我上面的驅動代碼只初始化了前面的關鍵三項:

static struct miscdevice b_miscdev = 
{
	.minor	        = MISC_DYNAMIC_MINOR,
   	.name	        = "magic-gpio",
        .fops	        = &gpio_drv_fops,
};

這裡先說說 .minor這個成員:定義次設備號的,這裡使用了一個MISC_DYNAMIC_MINOR宏! 這個宏的意思就是動態分配次設備號!而且這個次設備號不會超過64!實現的方法比較巧妙!這裡貼出一篇相關的文章:

剩下的兩個name 和 fops成員對驅動開發來說就最熟悉不過了!驅動的名字和驅動的接口函數這裡就不說了!

注冊混雜設備驅動後就是接口函數的表演了!

這裡和內核硬件相關的就是struct pin_desc_s 結構了,硬件的初始化工作比較簡單,放在open函數裡面了!

struct pin_desc_s pin_desc[5] = {
	{MXS_PIN_TO_GPIO(PINID_LCD_ENABLE),0x03,},    /* IO1 rain GPIO1_31     */  
	{MXS_PIN_TO_GPIO(PINID_LCD_HSYNC),0x05,},     /* IO2 windspeed GPIO1_29*/
	{MXS_PIN_TO_GPIO(PINID_LCD_DOTCK),0x0A,},     /* 機箱門             */
	{MXS_PIN_TO_GPIO(PINID_AUART3_RX),0x07,},     /* key1 GPIO3_12         */ 
	{MXS_PIN_TO_GPIO(PINID_AUART3_TX),0x09,},     /* key2 GPIO3_13         */ 
};

這裡把好幾個gpio接口都放到這一個裡面了!都是後邊加進去的!上面的是直接根據文檔在內核頭文件中找到GPIO引腳對應的宏定義的!後邊是給GPIO設置的鍵值!就是當應用程序收到一個signal後,根據讀取到的鍵值來區分是哪一個GPIO發生了中斷或是有信號傳過來!看看open函數:

static int gpio_drv_open(struct inode *inode, struct file *file)
{
	int iRet[5]={0};
	int i = 0;

	if (file->f_flags & O_NONBLOCK)//非阻塞
	{
		if (down_trylock(&b_lock))
			return -EBUSY;
	}
	else
	{
		down(&b_lock);
	}
	
	
	for(i = 0; i < 5; i++)
	{
		gpio_direction_input((pin_desc[i]).pin);//設置對應的GPIO輸入
		(pin_desc[i]).irq = gpio_to_irq((pin_desc[i]).pin);//把GPIO對應的pin值轉換為相應的IRQ值並返回
		if ((pin_desc[i]).irq) 
			disable_irq((pin_desc[i]).irq);//先關閉中斷並等待中斷處理完
		set_irq_type((pin_desc[i]).irq, IRQF_TRIGGER_FALLING);	//設置下降沿中斷
		iRet[i] = request_irq((pin_desc[i]).irq, b_irq, IRQF_SHARED, "gpio_int", &pin_desc[i]);
		if (iRet[i] != 0){
			printk("request irq failed!! ret: %d  irq:%d \n", iRet[i],(pin_desc[i]).irq);
		return -EBUSY;}
	
	}
	
	
	return 0;
}

request_irq函數
說說上面的request_irq函數了:

int request_irq(unsigned int irq, irq_handler_t handler,
unsigned long irqflags, const char *devname, void *dev_id)
irq是要申請的硬件中斷號。
handler是向系統注冊的中斷處理函數,是一個回調函數,中斷發生時,系統調用這個函數,dev_id參數將被傳遞給它。
irqflags是中斷處理的屬性,SA_SHARED表示多個設備共享中斷,
devname設置中斷名稱,通常是設備驅動程序的名稱 在cat /proc/interrupts中可以看到此名稱。
dev_id在中斷共享時會用到,一般設置為這個設備的設備結構體或者NULL。
request_irq()返回0表示成功,返回-INVAL表示中斷號無效或處理函數指針為NULL,返回-EBUSY表示中斷已經被占用且不能共享。

這裡用到回調函數b_irq函數就是根據響應的GPIO中斷返回設置好的相應的值,這樣應用程序在得到這個值的時候就可以知道是哪個GPIO發送的中斷!

b_irq函數:

static irqreturn_t b_irq(int irq, void *dev_id)
{
	struct pin_desc_s * pindesc = (struct pin_desc_s *)dev_id;
	unsigned int pinval;
	
	pinval = gpio_get_value(pindesc->pin);

	if (pinval)
	{
		key_val = 1;
	}
	else
	{
		key_val = pindesc->key_val;
	}
        ev_press = 1;
        wake_up_interruptible(&b_waitq);		//喚醒等待隊列裡面的進程
		kill_fasync(&b_async, SIGIO, POLL_IN);	//異步通知
	//printk("interrupt occur..........\n");
	return IRQ_RETVAL(IRQ_HANDLED);
}
其中上面的b_waitq是這樣定義的:

static DECLARE_WAIT_QUEUE_HEAD(b_waitq);//生成一個等待隊列的頭 名字為b_waitq

其實這裡有一個很關鍵的地方就是kill_fasync異步通知應用程序。這裡有很關鍵的一步,可以說是整個驅動程序的核心:kill_fasync 及 fasync_helper用於異步通知中,其中 kill_fasync(&b_async,SIGIO,POLL_IN)函數的功能是向應用程序發送可讀信號,還有那個進程調用fasync_helper函數就向誰發!這個可以結合應用程序是如何拿到信號的對比著看,關於應用程序這裡就不說了!網上的資料也比較多講解的也很詳細!例程代碼還有理論分析都有!

fansync_helpr函數內部實現:

int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
{
    struct fasync_struct *fa, **fp;
    struct fasync_struct *new = NULL;
    int result = 0;

    if (on) {
        new = kmem_cache_alloc(fasync_cache, GFP_KERNEL);
        if (!new)
            return -ENOMEM;
    }
    write_lock_irq(&fasync_lock);
    for (fp = fapp; (fa = *fp) != NULL; fp = &fa->fa_next) {
        if (fa->fa_file == filp) {
            if(on) {
                fa->fa_fd = fd;   //區分向誰發
                kmem_cache_free(fasync_cache, new);
            } else {
                *fp = fa->fa_next;
                kmem_cache_free(fasync_cache, fa);
                result = 1;
            }
            goto out;
        }
    }

    if (on) {
        new->magic = FASYNC_MAGIC;
        new->fa_file = filp;
        new->fa_fd = fd;
        new->fa_next = *fapp;
        *fapp = new;
        result = 1;
    }
out:
    write_unlock_irq(&fasync_lock);
    return result;
}
kill_fasync函數裡面的b_async參數:struct fasync_struct類型定義:

struct   fasync_struct   {
    int magic;
    int fa_fd;
    struct fasync_struct *fa_next;  
    struct file   *fa_file;
};
這個參數在下面中也被調用:實現的fasync成員函數
static int gpio_drv_fasync (int fd, struct file *filp, int on)
{
	printk("driver: gpio_drv_successful\n");
	return fasync_helper (fd, filp, on, &b_async);
}
這也是應用程序和內核之間傳參的一個關鍵:

要實現傳參,我們需要把一個結構體struct fasync_struct添加到內核的異步隊列中,這個結構體用來存放對應設備文件的信息(如fd, filp)並交給內核來管理。一但收到信號,內核就會在這個所謂的異步隊列頭找到相應的文件(fd),並在filp->owner中找到對應的進程PID,並且調用對應的sig_handler了。

關於剩下的程序中用到的down() 、up() 還有 DECILARE_MUTEX(b_lock)這裡簡單的用到了信號量的兩個簡單的操作,主要是用於保護臨界資源,保證中斷不被丟失!

剩下的read和close都比較簡單,驅動裡面的函數基本都是對應的,close裡面一把是釋放所有申請的資源!這也是模塊化驅動的一個好處!雖然這個驅動很簡單!但是要仔細深究起來,裡面所涉及的知識量也不小!上面也只是簡單的分析總結一下!做個筆記算是對自己的一個提高,也別人在參考的時候能有一點點的幫助!

最近住的地方沒網!感覺好長時間沒寫博客了!現在業余時間看linux驅動設備詳解,哈哈,比一年多前看的效果好多了,至少書上的好多知識多多少少都接觸過!而且看起來還比較有收獲,就是看了就忘!看來總結還是相當重要的!好記性不如爛筆頭!

Copyright © Linux教程網 All Rights Reserved