主題:linux驅動之異步通知

最近今天是2018-12-31日,即將跨年,希望在2018年最後的幾個小時內對今天的東西進行一次總結。

編譯系統   :ubuntu 16.04

內       核   :linux-2.6.22.6

硬件平臺   :jz2240

交叉編譯器:arm-linux-gcc 3.4.5

我們知道,讀取一個按鍵狀態有幾種方法。

  1. 輪詢方式,用一個死循環一直讀按鍵狀態。
  2. 採樣中斷方式,有按鍵就觸發中斷,可以讀按鍵狀態。在沒有按鍵被按下的情況下代表沒有數據可以讀,除非選擇非阻塞狀態,否則會使用戶程序處於休眠狀態,當有按鍵中斷髮生,就會喚醒用戶程序。
  3. 採樣poll或者select機制,定時掃描按鍵狀態,如果還沒到掃描時間有按鍵被按下就會通知select、poll。
  4. 採樣異步通知,驅動主動通知用戶程序。

有人可能會問,方式2和4看樣子沒啥區別呀!其實有很多的區別,因爲方式2在沒有按鍵按下的情況下會讓用戶進程處於休眠狀態。而方式4不會,其用戶程序可以一直處理,當有按鍵中斷來臨時,纔會跳轉到具體的處理函數。方式4很像單片機的裸機程序,沒有中斷,處理正常的事物,有中斷就去處理中斷事物。

方式1,是個循環模式,太佔cpu資源,是不切實際的。也有場合需要這種。在沒有按鍵被按下,不能進行任何處理。

方式3,select/poll是查詢文件狀態,當在一次掃描時間之內被改變時,就返回一直狀態。當在設置的時間被耗盡後任然沒有改變,返回另外一種狀態。根據返回狀態可以選擇執行不同的代碼功能。

下面將詳細介紹方式4,異步通知機制

先上代碼,驅動代碼如下

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <asm/irq.h>
#include <asm/uaccess.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <asm/io.h>
#include <asm/signal.h>

/* 這是一個按鍵驅動
 * 如果沒有按鍵按下,就進行休眠
 * 爲什麼要休眠,因爲應用程序會一直在讀或者等到按鍵被按下來,
 * 如果說一直沒有按下按鍵,那麼久需要讓用戶空間進行休眠,不然會一直處於讀阻塞
 */

static struct class  *g_buttons_class;
static struct class_device *g_my_buttons_classdevice;

/* -------------------------------------------------------------------------  */
/* ################## button 進行配置 ################## */

struct buttondesc
 {
 	unsigned int key_gpio; //引腳
	unsigned int irq ;	  //中斷號
	unsigned int flag;	  //觸發條件
	const char   *name;	  //名字
	char         keyval;//自定義鍵值
 };

static struct buttondesc g_buttons[4] = 
{
	{S3C2410_GPF0,  IRQ_EINT0,  IRQT_FALLING, "S2",0x11},
	{S3C2410_GPF2,  IRQ_EINT2,  IRQT_FALLING, "S3",0x12},
	{S3C2410_GPG3,  IRQ_EINT11, IRQT_FALLING, "S4",0x13},
	{S3C2410_GPG11, IRQ_EINT19, IRQT_FALLING, "S5",0x14},
};

static char g_keyval;//用於返回給用戶空間

/* -------------------------------------------------------------------------  */
/* ################## 休眠相關變量 ################## */
#define      SLEEP      1
#define      NO_SLEEP   0 
static DECLARE_WAIT_QUEUE_HEAD(g_buttons_waitq);
static char g_ev_press = SLEEP;//用於標誌中斷休眠

/* -------------------------------------------------------------------------  */
/* ################## 原子操作相關變量 ################## */
static atomic_t g_atomic = ATOMIC_INIT(1);//定義一個原子,並且初始化爲1

/* -------------------------------------------------------------------------  */
/* ################## 異步相關變量 ################## */
struct fasync_struct *fasync;


/* @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*/
//實際的中斷處理函數
static irq_handler_t buttons_handler(int irq, void *dev_id)
{
	struct buttondesc *temp_desc = (struct buttondesc *)dev_id;//根據傳遞參數可以獲得對應中斷號數組
	unsigned int temp_key  = s3c2410_gpio_getpin(temp_desc->key_gpio);//讀取gpio引腳狀態

	if(!temp_key)//如果有按鍵按下就是0
		g_keyval = 0x80 | temp_desc->keyval;
	else
		g_keyval = temp_desc->keyval;

	//訪問全局變量這裏要進行原子操作
	if(!atomic_dec_and_test(&g_atomic))//測試其是否爲0,爲 0 則返回 true,否則返回 false 
	{
		//已經打開,不能再次打開
		atomic_inc(&g_atomic);
		return -1;
	}
	g_ev_press = NO_SLEEP;//喚醒休眠
	atomic_inc(&g_atomic);//恢復原子值
	wake_up_interruptible(&g_buttons_waitq);//喚醒掛在 g_buttons_waitq 上的進程
	kill_fasync(&fasync,SIGIO,POLL_IN);//發送異步信號 POLL_IN可讀
	
	return 0;
}

//驅動讀函數
static ssize_t buttons_read(struct file *fp, char __user *buf, size_t size, loff_t *fops)
{
	if(g_ev_press == SLEEP)//處於睡眠狀態,此時沒有數據
	{	
		if(fp->f_flags & O_NONBLOCK)//處於阻塞讀取
		{
			return -1;
		}
		else
		{
			wait_event_interruptible(g_buttons_waitq, (g_ev_press == NO_SLEEP));//當g_ev_press == NO_SLEEP條件符合是 會被喚醒,繼續執行
		}
	}				
	
	//執行到這裏說明被中斷喚醒了
	if(copy_to_user(buf,&g_keyval, 1))//返回到用戶空間
		printk("copy_to_user not complete\n");
	//訪問全局變量這裏要進行原子操作
	if(!atomic_dec_and_test(&g_atomic))//測試其是否爲0,爲 0 則返回 true,否則返回 false 
	{
		//已經打開,不能再次打開
		atomic_inc(&g_atomic);
		return -1;
	}
	g_ev_press = SLEEP;//重新進行休眠
	atomic_inc(&g_atomic);//恢復原子值
	return 0;
}


//驅動poll,供應用程序selec和poll使用
static unsigned int buttons_poll(struct file *filp, struct poll_table_struct *wait)
{
	unsigned int mask = 0;

	poll_wait(filp, &g_buttons_waitq, wait);//並不是表示阻塞,而是代表g_buttons_waitq喚醒可喚醒select

	if (g_ev_press == NO_SLEEP)/* 可讀 */
		mask |= POLLIN | POLLRDNORM;//POLLIN表示可以無阻塞讀

	return mask;
}

static int button_fasync(int fd, struct file *filp, int on)
{
	printk(KERN_INFO "--------- button_fasync --------- \n");
	fasync_helper(fd,filp,on,&fasync);

	return 0;
}


//驅動打開函數
static int buttons_open(struct inode *inodep, struct file *fp)
{
	int i;
	//註冊中斷
	for(i = 0; i < sizeof(g_buttons)/sizeof(struct buttondesc); i++)
		request_irq(g_buttons[i].irq,buttons_handler,g_buttons[i].flag,g_buttons[i].name,(void *)(&g_buttons[i]));
	//訪問全局變量這裏要進行原子操作
	if(!atomic_dec_and_test(&g_atomic))//測試其是否爲0,爲 0 則返回 true,否則返回 false 
	{
		//已經打開,不能再次打開
		atomic_inc(&g_atomic);
		return -1;
	}
	g_ev_press = SLEEP;//進行休眠
	atomic_inc(&g_atomic);//恢復原子值
	printk(KERN_INFO "open buttons success\n");
	
	return 0;
}

//關閉
static int buttons_close(struct inode *inodep, struct file *fp)
{
	int i;
	for(i = 0; i < sizeof(g_buttons)/sizeof(struct buttondesc); i++)
			free_irq(g_buttons[i].irq,&g_buttons[i]);

	button_fasync(-1,fp,0);//釋放異步,將文件從異步通知列表中取消
	return 0;
}


//操作函數集
static struct file_operations g_buttons_op = 
{
	.owner   = THIS_MODULE,
	.open    = buttons_open,
	.read    = buttons_read,
	.release = buttons_close,
	.poll    = buttons_poll,
	.fasync  = button_fasync,
};

int g_buttons_major;
static int __init buttons_init(void)
{	
	g_buttons_major = register_chrdev(0, "my_buttons", &g_buttons_op);	    //註冊設備 
	g_buttons_class = class_create(THIS_MODULE, "HBUT_class");	   //創建類
	g_my_buttons_classdevice = class_device_create(g_buttons_class, NULL, MKDEV(g_buttons_major, 0), NULL, "buttons");	// /dev/led %d

	return 0;
}

static void __exit buttons_exit(void)
{
	unregister_chrdev(g_buttons_major,"my_buttons");

	class_device_destroy(g_buttons_class,MKDEV(g_buttons_major, 0));	

	class_destroy(g_buttons_class);
}

MODULE_AUTHOR("大白菜");
MODULE_DESCRIPTION("buttons drive with sleep and interrupt");
MODULE_LICENSE("GPL");
module_init(buttons_init);
module_exit(buttons_exit);






用戶程序代碼如下:

#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <error.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/select.h>
#include <sys/time.h>
#include <fcntl.h>
/* 在用戶程序中,select和poll也是與阻塞和非阻塞訪問息息相關,使用非阻塞的應用程序通常
 * 會使用select和poll系統調用用於查詢是否對設備進行無阻賽訪問
 * select 和 poll最終都會調用驅動中的poll
 *
 * 以按鍵驅動爲例進行說明,用阻塞的方式打開按鍵驅動文件/dev/buttons,
 * 應用程序使用read()函數來讀取按鍵的鍵值。這樣做的效果是:如果有按鍵按下了,調用該read()函數的進程,就成功讀取到數據,應用程序得到繼續執行;倘若沒有按鍵按下,則要一直處於休眠狀態,等待這有按鍵按下這樣的事件發生。
 * 這種功能在一些場合是適用的,但是並不能滿足我們所有的需要,有時我們需要一個時間節點。倘若沒有按鍵按下,那麼超過多少時間之後,也要返回超時錯誤信息,進程能夠繼續得到執行,而不是沒有按鍵按下,就永遠休眠。
 */
 
/*
	ifconfig eth0 192.168.0.11
	mount -t nfs -o nolock,vers=2 192.168.0.104:/home/book/wangruo /mnt
	cd /mnt/
*/

int g_fd;//文件描述符

void my_handler(int sign_num)
{
	int readval;//read函數讀返回值
	char keyval = 0;//按鍵值
	static int count=0;//記錄發生多少次按鍵
	
	readval = read(g_fd,&keyval,sizeof(char));//沒有中斷時,休眠
	if(readval < 0)
	{			
		printf("no data to read\n");
		exit(-1);
	}
	printf("keyval = 0x%x count = %d\n",keyval,++count);

}

int main(int argc,int **argv)
{
	int get_flag;//fcntl讀取的文件狀態標誌

	//一般來說使用select或者poll機制時,都是使用非阻塞訪問
	g_fd = open("/dev/buttons",O_RDONLY|O_NONBLOCK);
	if(g_fd < 0)
	{
		perror("open /dev/buttons");
		return -1;
	}
	
	signal(SIGIO,my_handler);//接收到信號
	fcntl(g_fd,F_SETOWN,getpid());//通過命令將設置設備文件的擁有者爲本進程
	get_flag = fcntl(g_fd,F_GETFD);
	fcntl(g_fd,F_SETFD,get_flag|FASYNC);//通過命令將設置設備文件支持FASYNC,即異步機制
	
	for(;;)
	{
		sleep(1000*10);
	}

	close(g_fd);		
	return 0;
}

用戶程序中,首先把通過fcntl吧fd和進程綁定,即fcntl(g_fd,F_SETOWN,getpid)

然後,獲取當前的fd標誌。get_flag = fcntl(g_fd,F_GETFL);

再把fd設置成支持FASYNC,即異步模式 fcntl(g_fd,F_SETFL,get_flag|FASYNC);

在此之前,需要用信號捕獲函數接收SIGIO(一般來說驅動程序和用戶程序之間通過SIGIO信號傳輸,應該也可以使用其他信號,有興趣可以試試)。signal(SIGIO,my_handler)

代碼運行如下:可以看出key進程僅僅佔據cpu的0.0%,而且是處於休眠狀態,但是隨機按下按鍵都會有反應。達到目標。


具體代碼就不分析了,因爲驅動程序中的註釋寫的很清楚。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章