簡介
前面使用阻塞和非阻塞的方式來讀取驅動中的按鍵值都是應用程序主動讀取的,對於非阻塞的方式還需要應用程序通過poll函數不斷的輪詢
Linux內核提供了異步通知這個機制來實現驅動程序主動向應用程序發出通知,報告自己可以訪問,然後應用程序再從驅動程序中讀取或寫入數據,軟件中斷的方式
”信號“類似於硬件上的中斷,是軟件層面上對中斷的模擬,設備可以被讀寫時發出信號
阻塞、非阻塞、異步通知是針對不同的場合提出來的不同的解決方法,沒有優劣之分,選擇合適的
除了SIGKILL(9)和SIGSTOP(19)這兩個信號不能被忽略外,其他的信號都可以被忽略
如果要在應用程序中使用信號,必須設置信號所使用的信號處理函數,使用如下的函數來註冊信號處理函數
sighandler_t signal(int signum, sighandler_t handler)
signum信號標號,handler處理函數指針
處理函數的原型:typedef void (*sighandler_t)(int)
驅動中的信號處理
重點關注一個數據結構兩個函數
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函數
如果要使用異步通知,需要在設備驅動中實現fops中的fasync函數
int (*fasync) (int fd, struct file *filp, int on)
fasync函數中一般通過fasync_helper函數來初始化fasync_struct結構體
int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
前三個參數就是fasync的三個參數
第四個參數就是要初始化fasync_struct結構體指針變量
當應用程序通過fcntl(fd,F_SETEL,flags | FASYNC)改變fasync標記時,驅動程序fops操作集中的fasync函數就會執行
關閉驅動文化的時候需要在file_operations操作集中的release函數中釋放fasync_struct,釋放時同樣調用fasync_helper
kill_fasync函數
當設備可以訪問的時候,驅動程序需要嚮應用程序發出信號,相當於產生”中斷“。kill_fastync函數負責發送指定的信號,kill_fastync函數原型如下
void kill_fasync(struct fasync_struct **fp, int sig, int band)
- fp:要操作的 fasync_struct
- sig:要發送的信號
- band:可讀時設置爲POLL_IN,可寫時設置爲POLL_OUT
應用程序的處理
1、註冊信號處理函數
2、將本應用程序的進程號告訴內核
3、開啓異步通知
flags = fcntl(fd,F_GETEL);//獲取當前的進程狀態
fcntl(fd,F_SETEL,flags | FASYNC);//開啓當前進程異步通知功能
重點就是通過fcntl函數設置進程狀態爲FASYNC,經過這一步,驅動程序中的fasync函數就會執行
實驗代碼及分析
實驗代碼
驅動代碼
struct irqkey_dev
{
dev_t devid;
...
struct fasync_struct *async_queue;
};
void timer_function(unsigned long arg)
{
unsigned char value;
...
//一次完成的按鍵過程
if(atomic_read(&dev->releasekey))
{
if(dev->async_queue)
kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
}
}
static int key_fasync(int fd, struct file *filp, int on)
{
printk(KERN_EMERG "key_fasync enter!\n");
struct irqkey_dev *dev = (struct irqkey_dev *)filp->private_data;
return fasync_helper(fd, filp, on, &dev->async_queue);
}
static int key_release(struct inode *inode,struct file *filp)
{
printk(KERN_EMERG "key_release enter!\n");
return key_fasync(-1, filp, 0);
}
應用程序代碼
static void sigio_signal_func(int signum)
{
int err = 0;
unsigned int keyvalue = 0;
err = read(fd, &keyvalue, sizeof(keyvalue));
if(err < 0)
{
}
else
{
printf("SIGIO signal! key value = %d\r\n",keyvalue);
}
}
int main(int argc, char *argv[])
{
...
/*open device*/
fd = open(filename, O_RDWR);
if(fd < 0)
{
printf("Can't open file %s\r\n", filename);
return -1;
}
printf("Open file %s OK!\r\n", filename);
signal(SIGIO, sigio_signal_func);
fcntl(fd, F_SETOWN, getpid());
flags = fcntl(fd, F_GETFD);
fcntl(fd, F_SETFL, flags | FASYNC);
while(1)
{
sleep(2);
}
}
代碼分析
驅動部分
在timer_function中,當經過按鍵防抖確實按鍵被按下後使用kill_fasync()釋放SIGIO信號,第三個參數爲POLL_IN表示資源可讀
在驅動的fasync函數中使用fasync_helper函數初始化fasync,包括分配內存和設置屬性
在驅動的release函數中調用設備驅動的fasync函數(key_fasync())將文件從異步通知的列表中刪除
使用如下的語句
return key_fasync(-1, filp, 0);
應用程序部分
在應用程序中使用signal來爲SIGIO安裝sigio_signal_func()作爲信號處理函數
signal(SIGIO, sigio_signal_func);
使用fcntl來設置本進程爲文件的擁有者,沒有這一步內核不會知道應該將信號發給哪個進程
fcntl(fd, F_SETOWN, getpid());
因爲啓用了異步通知機制還需對設備設置FASYNC機制
flags = fcntl(fd, F_GETFD);
fcntl(fd, F_SETFL, flags | FASYNC);
總結
爲了能在用戶空間中處理一個設備釋放的信號,它必須完成3項工作
- 通過F_SETOWN IO控制命令設置設備文件的擁有者爲本進程,這樣從設備驅動發出的信號才能被本進程接收到
- 通過F_SETOWN IO控制命令設置設備文件以支持FASYNC,即異步通知模式
- 通過signal()函數連接信號和信號處理函數
爲了使設備支持異步通知機制,驅動程序中涉及3項工作
- 支持F_SETOWN 命令,能在這個控制命令處理中設置filp->f_owner爲對應進程ID。不過此項工作已由內核完成,設備驅動無需處理
- 支持F_SETFL命令,沒放FASYNC標誌改變時,驅動程序中的fasync()函數將得以執行。因此,驅動中應該事先fasync()函數
- 在設備資源可獲得時,調用kill_fasync()函數激發相應的信號
整個機制的框圖如下