結合阻塞與非阻塞訪問、poll 函數可以較好地解決設備的讀寫,但是如果有了異步通知就更方便了。異步通知的意思是:一旦設備就緒,則主動通知應用程序,這樣應用程序根本就不需要查詢設備狀態,這一點非常類似於硬件上“中斷”地概念,比較准確的稱謂是:信號驅動(SIGIO)的異步 I/O。可以使用signal()函數來設置對應的信號的處理函數。函數原型是:
void (*signal(int signo,void (*func)(int))) (int)
我們先來看一個使用信號驅動的例子,通過signal(SIGIO,input_handler) 對打開的文件fd 啟動信號機制,輸入可獲得時inputhandler被調用,代碼如下:
/*async_io_app.c*/
#include <sys/types.h> #include <sys/stat.h> #include <stdio.h> #include <fcntl.h> #include <signal.h> #include <unistd.h> #include <stdlib.h> #define MAX_LEN 100 int fd; void input_handler(int num) { char data[MAX_LEN]; int len; //讀取並輸出 STDIN_FILENO 上的輸入 len = read(fd, &data, MAX_LEN); data[len] = 0; printf("input available:%s\n", data); } int main() { int oflags; //啟動信號驅動機制 fd = open("/dev/CDEV_ZHU", O_RDWR, S_IRUSR | S_IWUSR); if(fd == -1)
{ printf("Device Open Failure !\n"); exit(0); } signal(SIGIO, input_handler); fcntl(fd, F_SETOWN, getpid()); oflags = fcntl(fd, F_GETFL); fcntl(fd, F_SETFL, oflags | FASYNC); //最后進入一個死循環,程序什么都不干了,只有信號能激發 input_handler 的運行 //如果程序中沒有這個死循環,會立即執行完畢 while (1); return 0; }
下面來解釋一下上面的代碼。為了一個用戶在用戶空間中能處理一個設備釋放的信號,它必須完成一下3份工作:
1)通過F_SETOWN控制指令設置設備文件的擁有者為本進程,這樣從設備驅動中發出的信號才能被本進程收到。
2)通過F_SETFL 控制命令設置設備文件支持FASYNC,即異步通知模式。
3)通過signal()鏈接信號和信號處理函數。
有了信號的發送,那么就一定得有信號的釋放了:
在設備驅動和應用程序的異步通知交互中,僅僅在應用程序端捕獲信號是不夠的,因為信號沒有的源頭是在驅動端,因此要在適當的時機讓設備驅動釋放信號。
為了使設備支持異步通知機制,驅動程序中涉及三個操作:
1)支持F_SETOWN命令,能在這個控制命令處理中設置filp->f_owner為對應的進程ID。不過此項工作已由內核完成,設備驅動無須處理。
2)支持F_SETFL命令的處理,每當FASYNC標志改變時,驅動程序中fasync()函數將得以進行。因此,驅動程序必須實現fasync()函數。
3)在設備資源可獲得時,調用kill_fasync()函數激發相應的信號。
驅動程序中上面的三步是和應用程序是一一對應的。如下圖:
設備驅動中異步通知編程還是比較簡單的,主要就是一些數據結構,和兩個函數:
數據結構:fasync_struct結構體
函數:1)處理FASYNC標志變更的函數int fasync_helper(int fd, struct file *filp, int mode ,struct fasync_struct **fa);
2) 釋放信號用的函數void kill_fasync(struct fasync_struct **fa, int sig, int band);
和其他設備驅動一樣,一般將fasync_struct放到設備結構體中。
下面給出驅動程序部分實現支持異步IO的代碼:
/* async_io_driver.c */
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/wait.h>
#include <linux/semaphore.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/sched.h>
#include <linux/fcntl.h>
#include <linux/poll.h>
MODULE_LICENSE("GPL");
#define LEN 30
#define init_MUTEX(LOCKNAME) sema_init(LOCKNAME,1)
#define DEVICE_NAME "CDEV_ZHU"
static struct class *cdev_class;
struct asycIO
{
struct cdev dev_c; /*cdev結構體*/
dev_t dev;
char mem[LEN];
int flag ;
struct semaphore sem; /*並發控制用的信號量*/
wait_queue_head_t r_wait; /*阻塞讀用的等待隊列頭*/
struct fasync_struct *async_queue; /* 異步結構體指針,用於讀 */
};
struct asycIO asyc_device;
static int asyc_io_fasync(int fd, struct file *filp, int mode)
{
return fasync_helper(fd, filp, mode, &asyc_device.async_queue);
}
/*文件釋放函數*/
int asyc_io_release(struct inode *inode, struct file *filp)
{
/* 將文件從異步通知列表中刪除 */
asyc_io_fasync( - 1, filp, 0);
return 0;
}
/*寫操作*/
static ssize_t asyc_write(struct file *filp, const char __user *buf,size_t count, loff_t *ppos)
{
int ret = count;
printk("In asyc_write! \n");
down(&asyc_device.sem); //獲取信號量
memset(asyc_device.mem,0,LEN);
if (copy_from_user(asyc_device.mem, buf, count))
{
up(&asyc_device.sem);
return - EFAULT;
}
printk("kernel recieve: %s and the length is %d \n",asyc_device.mem,count);
up(&asyc_device.sem);
asyc_device.flag = 1;
if (asyc_device.async_queue)
kill_fasync(&asyc_device.async_queue, SIGIO, POLL_IN);
wake_up_interruptible(&asyc_device.r_wait);
return ret;
}
static ssize_t asyc_read(struct file *filp, char *buf, size_t len, loff_t *off)
{
int ret = len;
printk("In asyc_read \n");
if (wait_event_interruptible(asyc_device.r_wait, asyc_device.flag != 0))
{
return - ERESTARTSYS;
}
if (down_interruptible(&asyc_device.sem))
{
return - ERESTARTSYS;
}
asyc_device.flag = 0;
if (copy_to_user(buf, asyc_device.mem, len))
{
up(&asyc_device.sem);
return - EFAULT;
}
up(&asyc_device.sem);
return ret;
}
struct file_operations asyc_fops =
{
read: asyc_read,
write: asyc_write,
fasync: asyc_io_fasync,
release: asyc_io_release,
};
static int __init asyc_init(void)
{
int ret,err;
ret = alloc_chrdev_region(&(asyc_device.dev),0,1,DEVICE_NAME) ;
if (ret)
{
printk("globalvar register failure");
}
else
{
cdev_init(&(asyc_device.dev_c),&asyc_fops);
err = cdev_add(&(asyc_device.dev_c),asyc_device.dev,1);
if(err)
{
printk(KERN_NOTICE "error %d adding FC_dev\n",err);
unregister_chrdev_region(asyc_device.dev, 1);
return err;
}
else
{
printk("device register success! \n");
}
cdev_class = class_create(THIS_MODULE,DEVICE_NAME);
if(IS_ERR(cdev_class))
{
printk("ERR:cannot create a cdev_class\n");
unregister_chrdev_region(asyc_device.dev, 1);
return -1;
}
device_create(cdev_class, NULL, asyc_device.dev, 0, DEVICE_NAME);
asyc_device.flag = 0;
init_MUTEX(&(asyc_device.sem));
init_waitqueue_head(&(asyc_device.r_wait));
}
return ret;
}
static void __exit asyc_exit(void)
{
device_destroy(cdev_class,asyc_device.dev);
class_destroy(cdev_class);
unregister_chrdev_region(asyc_device.dev,1);
printk(" device exit! \n");
}
module_init(asyc_init);
module_exit(asyc_exit);
應用程序實現寫入功能:
/* async_io_app_w.c*/
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
int main()
{
int fd, num;
char buffer[100] = {0};
fd = open("/dev/CDEV_ZHU", O_RDWR, S_IRUSR | S_IWUSR);
printf("open /dev/CDEV_ZHU fd = %d \n",fd);
if (fd != -1)
{
while (1)
{
memset(buffer,0,sizeof(buffer));
printf("Please input the buffer:\n");
scanf("%s", buffer);
if (buffer[0] == '0') //如果輸入 0,退出
{
close(fd);
break;
}
write(fd, buffer, strlen(buffer));
printf("We have written: %s\n",buffer);
}
}
else
{
printf("device open failure\n");
}
return 0;
}
將上面的“async_io_app.c”、“async_io_driver.c”、“async_io_app_w.c”進行編譯,加載驅動之后,開兩個終端,分別運行async_io_app 和 async_io_app_w,當async_io_app_w有數據寫入的時候,async_io_app的終端會打印所寫入的數據,當然內核也會打印數據,下面是結果:
說明:上面圖是三個終端的打印結果,從左到右一次是async_io_app_w , async_io_app 和使用dmesg 打印內核的結果。
注:我本來也想用代碼格式,但是感覺在vim上排版很舒服的,上來用代碼格式反而還不好看了,於是就這樣了