字元裝置驅動------非同步通知 按鍵之使用非同步通知(詳解)
引入:
按鍵驅動方式對比
- 查詢:一直讀,耗資源
- 中斷: 沒有超時機制,當沒有中斷,read函式一直休眠
- poll機制,加入超時機制
以上3種,都是讓應用程式主動去讀,本節我們學習非同步通知,它的作用就是當驅動層有資料時,主動告訴應用程式,然後應用程式再來讀, 這樣,應用程式就可以幹其它的事情,不必一直讀
比如:kill -9 pid ,其實就是通過發訊號殺死程序,kill發資料9給指定id號程序
程序間發訊號
使用 man signal
檢視需要標頭檔案 #include <signal.h>
測試程式如下如果用在gcc下編譯需要為sleep
#include <unistd.h>
例項函式如下arm-linux-gcc -o test test.c
1 #include <stdio.h> 2 #include <signal.h> 3 #include <unistd.h> 4 5 //typedef void (*sighandler_t)(int); 6 void my_signal_fun(int signum) 7 { 8 static int cnt=0; 9 printf("signum=%d ,%d times\n",signum,++cnt ); 10 11 } 12 13 int main(int argc, char const *argv[]) 14 { 15 signal(SIGUSR1,my_signal_fun); 16 while(1) 17 { 18 19 sleep(100); 20 } 21 return 0; 22 }
測試使用kill -USR1 pid
後會列印訊號
目標
實現非同步通知:驅動程式使用訊號通知應用程式去讀取按鍵
要求:
- 1.應用程式要實現,有:註冊訊號處理函式,使用signal函式
- 2.誰來發?驅動來發
- 3.發給誰?驅動發給應用程式,但應用程式必須告訴驅動PID,
- 4.怎麼發?驅動程式呼叫kill_fasync函式
看下別人怎麼寫的:
搜尋下發送訊號的函式kill_fasync
,尋找一個字元裝置是怎麼使用的,在drivers/char/rtc.c
中有如下呼叫
1 kill_fasync (&rtc_async_queue, SIGIO, POLL_IN); 2 3 //結構體定義如下: 4 static struct fasync_struct *rtc_async_queue; 5 struct fasync_struct { 6 int magic; 7 int fa_fd; 8 struct fasync_struct *fa_next; /* singly linked list */ 9 struct file *fa_file; 10 };
訊號的接受者被定義在fasync_struct
中,搜尋下其初始化函式,發現函式rtc_fasync
被呼叫如下形式,也就是類似於open、close
的形式了
1 static const struct file_operations rtc_fops = { 2 .owner = THIS_MODULE, 3 .llseek = no_llseek, 4 .read = rtc_read, 5 #ifdef RTC_IRQ 6 .poll = rtc_poll, 7 #endif 8 .ioctl = rtc_ioctl, 9 .open = rtc_open, 10 .release = rtc_release, 11 .fasync = rtc_fasync, 12 };
函式的原型如下,這個函式用來初始化結構體,應用程式會最終呼叫他告知驅動應該訊號給哪個pid
1 static int rtc_fasync (int fd, struct file *filp, int on) 2 { 3 return fasync_helper (fd, filp, on, &rtc_async_queue); 4 }
也就是是說應用程式通過fasync
來呼叫驅動具體的rtc_fasync
,這個函式會設定fasync_struct
這個結構體,這個結構體會被當做驅動傳送訊號函式的引數,也就是說應用程式告訴驅動程式其目標。
步驟:
1.先寫驅動程式,在之前的中斷程式上修改
1.1定義 非同步訊號結構體 變數:
1 static struct fasync_struct * button_async;
1.2在file_operations結構體新增成員.fasync函式,並寫函式
1 static int fourth_fasync (int fd, struct file *file, int on) 2 { 3 return fasync_helper(fd, file, on, & button_async); //初始化button_async結構體,就能使用kill_fasync()了 4 } 5 6 static struct file_operations third_drv_fops={ 7 .owner = THIS_MODULE, 8 .open = fourth_drv_open, 9 .read = fourth _drv_read, 10 .release= fourth _drv_class, 11 .poll = fourth _poll, 12 .fasync = fourth_fasync //新增初始化非同步訊號函式 13 };
3.3在buttons_irq中斷服務函式裡傳送訊號:
1 static irqreturn_t buttons_irq(int irq, void *dev_id) 2 { 3 4 ...... 5 ev_press = 1; /*表示中斷髮生了*/ 6 wake_up_interruptible(&button_waitq); /*去佇列button_waitq裡面喚醒休眠的程序*/ 7 8 /*kill_fasync: 傳送訊號,發給誰?-->在&button_async中定義*/ 9 /*定義之後,要在fasync_helper中初始化,才可使用*/ 10 kill_fasync (&button_async, SIGIO, POLL_IN); /*SIGIO表有資料可供讀寫*/ 11 return IRQ_HANDLED; 12 }
驅動程式程式碼如下:
/*所需標頭檔案,參考其他類似的驅動*/ #include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/init.h> #include <linux/delay.h> #include <asm/uaccess.h> #include <asm/irq.h> #include <linux/irq.h> #include <asm/io.h> #include <asm/arch/regs-gpio.h> #include <asm/hardware.h> static struct class *forthdrv_class; static struct class_device *forthdrv_class_dev; volatile unsigned long *gpfcon = NULL; volatile unsigned long *gpfdat = NULL; volatile unsigned long *gpgcon = NULL; volatile unsigned long *gpgdat = NULL; // 定義一個名為`button_waitq`的佇列 static DECLARE_WAIT_QUEUE_HEAD(button_waitq); static struct fasync_struct *button_async; /*由結構fasync_stuct定義, */ /*中斷事件標誌,中斷服務程式將它置 1 ,forth_drv_read 將它清0 */ static volatile int ev_press = 0; struct pin_desc{ unsigned int pin; unsigned int key_val; }; static unsigned char key_val; /*鍵值: 按下時,0x01, 0x02, 0x03, 0x04*/ /*鍵值: 鬆開時,0x81, 0x82, 0x83, 0x84*/ struct pin_desc pins_desc[4] = { /* pin ,key_val */ {s3c2410_GPF0, 0x01}, {s3c2410_GPF2, 0x02}, {s3c2410_GPG3, 0x03}, {s3c2410_GPG11,0x04}, }; /*確定按鍵值*/ static irqreturn_t buttons_irq(int irq, void *dev_id) { /*在中斷處理函式中如何使用pins_desc[4] */ struct pin_desc *pin = (struct pin_desc *)dev_id; unsigned int pinval; /*系統函式,可讀出單個引腳狀態值,高低電平*/ pinval = s3c2410_gpio_getpin(pin_desc->pin); /*確定按鍵值*/ if (pinval) { /* 為1--鬆開 */ key_val = 0x80 | pin_desc->key_val; } else { /* 0--按下 */ key_val = pin_desc->key_val; } ev_press = 1; /*表示中斷髮生了*/ wake_up_interruptible(&button_waitq); /*去佇列button_waitq裡面喚醒休眠的程序*/ /*kill_fasync: 傳送訊號,發給誰?-->在&button_async中定義*/ /*定義之後,要在fasync_helper中初始化,才可使用*/ kill_fasync (&button_async, SIGIO, POLL_IN); /*SIGIO表有資料可供讀寫*/ return IRQ_HANDLED; } static int forth_drv_open(struct inode *inode, struct file *file) { //GPF0 GPF2 GPG3 GPG11,使用虛擬地址 /*設定GPF0,2 GPG3,11為中斷引腳*/ request_irq(IRQ_EINT0, buttons_irq, IRQT_BOTHEDGE, "s2", &pins_desc[0]); request_irq(IRQ_EINT2, buttons_irq, IRQT_BOTHEDGE, "s3", &pins_desc[1]); request_irq(IRQ_EINT11, buttons_irq, IRQT_BOTHEDGE, "s4", &pins_desc[2]); request_irq(IRQ_EINT19, buttons_irq, IRQT_BOTHEDGE, "s5", &pins_desc[3]); return 0; } ssize_t forth_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos) { if(size != 1) return -EINVAL; /*如果沒有按鍵動作,ev_press = 0, 將程序掛在佇列裡面等待,休眠, 不會執行下面的函式*/ wait_event_interruptible(button_waitq, ev_press); //誰來喚醒程序?-> 中斷髮生的時候,就喚醒程序 /*/*如果有按鍵動作,ev_press = 1,會執行到此函式,並返回鍵值,將變數清零*/*/ copy_to_user(buf, &key_val, 1); ev_press = 0; return 1; } int forth_drv_close(struct inode *inode, struct file *file) { free_irq(IRQ_EINT0, &pins_desc[0]); free_irq(IRQ_EINT2, &pins_desc[1]); free_irq(IRQ_EINT11, &pins_desc[2]); free_irq(IRQ_EINT19, &pins_desc[3]); return 0; } /*這個函式用來初始化結構體&button_async,應用程式會最終呼叫他告知驅動應該訊號給哪個pid*/ static int forth_drv_fasync (int fd, struct file *filp, int on) { return fasync_helper (fd, filp, on, &button_async); /**/ } static struct file_operations forth_drv_fops = { .owner = THIS_MODULE, .open = forth_drv_open, .read = forth_drv_read, .release = forth_drv_close, .fasync = forth_drv_fasync, }; int major; /*定義全域性變數,儲存主裝置號*/ static int forth_drv_init(void) { major = register_chrdev(0, "forth_dev", &forth_drv_fops); forthdrv_class = class_create(THIS_MODULE, "forthdrv"); /*先建立一個類class,再在class下面建立一個裝置(如下)*/ forthdrv_class_dev = class_device_create(forthdrv_class, NULL, MKDEV(major, 0), NULL, "buttons"); /* /dev/xyz */ /*建立地址對映*/ gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16); gpfdat = gpfcon + 1; gpgcon = (volatile unsigned long *)ioremap(0x56000060, 16); gpgdat = gpgcon + 1; return 0; } static int forth_drv_exit(void) { unregister_chrdev(major, "forth_dev"); class_device_unregister(forthdrv_class_dev); /*解除安裝裝置*/ class_destroy(forthdrv_class); /*銷燬建立的類*/ /*解除地址對映*/ iounmap(gpfcon); iounmap(gpfcon); return 0; } module_init(forth_drv_init); module_exit(forth_drv_init); MODULE_LICENSE("GPL");forth_drv.c
2.寫應用測試程式
步驟如下:
1) signal(SIGIO, my_signal_fun);
呼叫signal函式,當接收到SIGIO訊號就進入my_signal_fun函式,讀取驅動層的資料
2) fcntl(fd,F_SETOWN,getpid());
指定程序做為fd檔案的”屬主”,核心收到F_SETOWN命令,就會設定pid(驅動無需處理),這樣fd驅動程式就知道發給哪個程序
3) oflags=fcntl(fd,F_GETFL);
獲取fd的檔案狀態標誌
4) fcntl(fd,F_SETFL, oflags| FASYNC );
新增FASYNC狀態標誌,會呼叫驅動中成員.fasync函式,執行fasync_helper()來初始化非同步訊號結構體
這4個步驟執行後,一旦有驅動層有SIGIO訊號時,程序就會收到
應用層程式碼如下:
1 #include <sys/types.h> 2 #include <sys/stat.h> 3 #include <stdio.h> 4 #include <string.h> 5 #include <poll.h> 6 #include <signal.h> 7 #include <unistd.h> 8 #include <fcntl.h> 9 10 int fd,ret; 11 void my_signal_fun(int signame) //有訊號來了 12 { 13 read( fd, &ret, 1); //讀取驅動層資料 14 printf("key_vale=0X%x\r\n",ret); 15 16 } 17 18 /*useg: fourthtext */ 19 int main(int argc,char **argv) 20 { 21 int oflag; 22 unsigned int val=0; 23 fd=open("/dev/buttons",O_RDWR); 24 if(fd<0) 25 {printf("can't open!!!\n"); 26 return -1;} 27 28 signal(SIGIO,my_signal_fun); //指定的訊號SIGIO與處理函式my_signal_run對應 29 fcntl( fd, F_SETOWN, getip()); //指定程序作為fd 的屬主,傳送pid給驅動 30 oflag=fcntl( fd, F_GETFL); //獲取fd的檔案標誌狀態 31 fcntl( fd, F_SETFL, oflag|FASYNC); //新增FASYNC狀態標誌,呼叫驅動層.fasync成員函式 32 33 while(1) 34 { 35 sleep(1000); //做其它的事情 36 } 37 return 0; 38 39 }forth_test.c
3.測試
可以發現開啟檔案的時候會有提示driver: drv_fasync
,說明App會主動呼叫.fasync
1 # insmod dri.ko 2 # 後臺執行 3 # ./test & 4 # driver: drv_fasync 5 #實際休眠狀態的 6 7 # ps 8 PID Uid VSZ Stat Command 9 798 0 1308 S ./test 10 .... 11 12 # 按鍵按下正常列印 13 # irq55 14 key_val: 0x3 15 irq55 16 key_val: 0x83 17 irq18
參考:
字元裝置驅動(七)按鍵非同步通知
按鍵之使用非同步通知(詳解)