1.異步通知機制的實現理論:
最基本的read()和write()函數實現的數據最基本的交互.等待隊列實現了讀寫阻塞策略,針對一些比較長時間纔可以獲取資源的設備進程進行休眠處理進而優化了系統資源的利用率;poll機制可以實現設備的監聽,可以進一步提高讀寫機制的命中時機.而異步通知則類似於硬件上的中斷,當設備資源準備就緒,向指定進程發送信號,從而喚醒進程對此信號進行處理.poll的方式是用戶主動式,當用戶偵聽到設備信息的情況從而裁決下一步的動作,而異步通知是設備主動式,當設備準備就緒的情況下,會自動通知用戶.
總而言之,讀寫是必須的核心動作,而wait_queue、poll和fasync都是這個核心動作的優化,從而使系統資源得以高效利用.
因此,從異步通知的實質機制的理論上講,要實現異步通知需要實現下面三步:
1).要有信號;
2).綁定信號;
3).處理信號.
2.實現異步通知的細則:
根據異步通知機制可知,信號源是由驅動發出,用戶進程負責捕捉接收由驅動發出的信號而裁決下一步的動作.但是內核有很多處可能發出信號,而上層也有可能有多個進程在等待信號.爲了讓特定的進程接收到特定的設備的信號,這過程需要一個"綁定"的過程.下面從用戶空間到底層看一下如何實現這個過程.
2-1.處理信號:
和純粹的用戶空間操作一樣,向一個進程發送指定的信號,當此進程接收到此信號時,引發對此信號的處理.示意代碼如下:
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
void sigterm_handler(int signo)
{
printf("Have Caught Sig %d\n",signo);
}
int main(int argc,char **argv)
{
signal(SIGINT,sigterm_handler);
while(1);
return 0;
}
執行結果如下:
root@seven-laptop:~/learn/LddDemo/linuxdriver_code_tool/09# gcc signal.c -o signal
root@seven-laptop:~/learn/LddDemo/linuxdriver_code_tool/09# ./signal
查看signal進程:
root@seven-laptop:~# ps -e | grep signal
23570 pts/1 00:00:29 signal
向進程signal發送SIGINT(2)信號:
root@seven-laptop:~# kill -s 2 23570
進程接收到SIGINT信號的表現:root@seven-laptop:~/learn/LddDemo/linuxdriver_code_tool/09# ./signal
Have Caught Sig 2
所以,用戶空間必須得實現捕捉信號並處理信號這一步.只不過這裏的信號純粹的用戶空間發起的而不是由驅動發起的.
2-2.綁定信號:
如果信號的發源地是設備驅動的時候,爲了進程唯一地能處理到此設備驅動的信號,需要一個綁定的過程.實現步驟如下:
1).通過F_SETOWN IO控制命令設置設備文件的擁有者爲本進程;F:表示設備文件;SETOWN:設置擁有者(OWN);
2).通過F_SETFL IO控制命令設置設備文件支持FASYNC,即異步通知模式;
3).當然也是必須的,捕捉並處理信號的例程函數.
下面的代碼來自宋寶華<<LINUX設備驅動開發詳解第二版>>.
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
#define MAX_LEN 100
void input_handler(int num)
{
char data[MAX_LEN];
int len;
len = read(STDIN_FILENO,&data,MAX_LEN);
data[len] = 0;
printf("input available:%s\n",data);
}
int main(int argc,char **argv)
{
int oflags;
signal(SIGIO,input_handler);
fcntl(STDIN_FILENO,F_SETOWN,getpid());
oflags = fcntl(STDIN_FILENO,F_GETFL);
fcntl(STDIN_FILENO,F_SETFL,oflags | FASYNC);
while(1);
return 0;
}
執行結果如下:
root@seven-laptop:~/learn/LddDemo/linuxdriver_code_tool/09# gcc syncsignal.c -o syncsignal
root@seven-laptop:~/learn/LddDemo/linuxdriver_code_tool/09# ./syncsignal
Do you think you are sth?
input available:Do you think you are sth?
No,You are nothing!
input available:No,You are nothing!
這裏的設備文件爲標準的輸入流STDIN_FILENO.下面是關於函數fcntl()的簡要分析.
[附:]
fcntl()函數針對文件描述符提供控制.其原型如下:
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd, struct flock *lock);
各參數意義如下:
fd:文件描述詞;
cmd:操作命令;
arg:供命令使用的參數;
lock:同上.
在這裏用到的fcntl()參數說明如下:
fd:
標準輸入終端--STDIN_FILENO;
cmd:
F_SETOWN:設置將要在文件描述詞fd上接收SIGIO 或 SIGURG事件信號的進程或進程組標識,這將決定第三個參數的形式,如getpid();F_GETFL:讀取文件狀態標誌;
F_SETFL:設置文件狀態標誌.其中O_RDONLY,O_WRONLY,O_RDWR,O_CREAT, O_EXCL,O_NOCTTY 和O_TRUNC不受影響,可以更改的標誌有 O_APPEND,O_ASYNC,O_DIRECT,O_NOATIME 和 O_NONBLOCK.此參數影響第三個參數.如oflags | FASYNC.
下面語句:
fcntl(STDIN_FILENO,F_SETFL,oflags | FASYNC);
對設備設置了FASYNC標誌,啓用異步通知機制.
2-3.設備驅動發出信號
異步通知的信號源來自底層設備驅動.要實現底層設備驅動向指定的進程發送信號,主要操作的對象爲一個數據結構和兩個函數.
其中,數據結構是:
struct fasync_struct;
兩個函數是:
int fasync_helper(int fd,struct file *filp,int mode,struct fasync_struct **fa);
void kill_fasync(struct fasync_struct **fa,int sig,int band);
其中第一個函數是從相關進程列表增刪文件.是文件操作集域fasync的核心組成部分.如下:
static int scull_p_fasync(int fd,struct file *filp,int mode)
{
struct scull_pipe *dev = filp->private_data;
return fasync_helper(fd,filp,mode,&dev->async_queue);
}
前三個參數均由用戶空間的open動作加工而成,最後一個參數則由具體驅動程序提供.因此,驅動要實現異步通知,則必須實現這樣的一個結構體,這類似於等待隊列要實現一個隊列頭一樣,然後輔助函數fasync_helper()進行相應的關聯.
而第二個函數則是負責發出信號.舉例如下:
if(dev->async_queue)
kill_fasync(&dev->async_queue,SIGIO,POLL_IN);
第一個參數是具體的驅動定義的,第二個是信號源;第三個參數表示數據可讀.
[注:]POLL_IN、POLL_OUT是在用戶空間的角度看的.IN表示數據可讀,OUT表示可寫.
當用戶進行最後次的close()時,異步通知還需要調用下面的函數來實現異步通知資源的釋放:
scull_p_fasync(-1,filp,0);
3.實例:
UserSpace:
/*======================================================================
A test program to access /dev/second
This example is to help understand async IO
The initial developer of the original code is Baohua Song
<[email protected]>. All Rights Reserved.
======================================================================*/
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
void input_handler(int signum)
{
printf("receive a signal from globalfifo,signalnum:%d\n",signum);
}
main()
{
int fd, oflags;
fd = open("/dev/globalfifo", O_RDWR, S_IRUSR | S_IWUSR);
if (fd != - 1)
{
signal(SIGIO, input_handler);
fcntl(fd, F_SETOWN, getpid());
oflags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, oflags | FASYNC);
while(1)
{
sleep(100);
}
}
else
{
printf("device open failure\n");
}
}
KernelSpace:
/*======================================================================
A globalfifo driver as an example of char device drivers
This example is to introduce asynchronous notifier
The initial developer of the original code is Baohua Song
<[email protected]>. All Rights Reserved.
======================================================================*/
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include <linux/poll.h>
#define GLOBALFIFO_SIZE 0x1000 /*全局fifo最大4K字節*/
#define FIFO_CLEAR 0x1 /*清0全局內存的長度*/
#define GLOBALFIFO_MAJOR 251 /*預設的globalfifo的主設備號*/
static int globalfifo_major = GLOBALFIFO_MAJOR;
/*globalfifo設備結構體*/
struct globalfifo_dev
{
struct cdev cdev; /*cdev結構體*/
unsigned int current_len; /*fifo有效數據長度*/
unsigned char mem[GLOBALFIFO_SIZE]; /*全局內存*/
struct semaphore sem; /*併發控制用的信號量*/
wait_queue_head_t r_wait; /*阻塞讀用的等待隊列頭*/
wait_queue_head_t w_wait; /*阻塞寫用的等待隊列頭*/
struct fasync_struct *async_queue; /* 異步結構體指針,用於讀 */
};
struct globalfifo_dev *globalfifo_devp; /*設備結構體指針*/
/*文件打開函數*/
int globalfifo_open(struct inode *inode, struct file *filp)
{
/*將設備結構體指針賦值給文件私有數據指針*/
filp->private_data = globalfifo_devp;
return 0;
}
/*文件釋放函數*/
int globalfifo_release(struct inode *inode, struct file *filp)
{
/* 將文件從異步通知列表中刪除 */
globalfifo_fasync(-1, filp, 0);
return 0;
}
/* ioctl設備控制函數 */
static int globalfifo_ioctl(struct inode *inodep, struct file *filp, unsigned
int cmd, unsigned long arg)
{
struct globalfifo_dev *dev = filp->private_data;/*獲得設備結構體指針*/
switch (cmd)
{
case FIFO_CLEAR:
down(&dev->sem); //獲得信號量
dev->current_len = 0;
memset(dev->mem,0,GLOBALFIFO_SIZE);
up(&dev->sem); //釋放信號量
printk(KERN_INFO "globalfifo is set to zero\n");
break;
default:
return - EINVAL;
}
return 0;
}
static unsigned int globalfifo_poll(struct file *filp, poll_table *wait)
{
unsigned int mask = 0;
struct globalfifo_dev *dev = filp->private_data; /*獲得設備結構體指針*/
down(&dev->sem);
poll_wait(filp, &dev->r_wait, wait);
poll_wait(filp, &dev->w_wait, wait);
/*fifo非空*/
if (dev->current_len != 0)
{
mask |= POLLIN | POLLRDNORM; /*標示數據可獲得*/
}
/*fifo非滿*/
if (dev->current_len != GLOBALFIFO_SIZE)
{
mask |= POLLOUT | POLLWRNORM; /*標示數據可寫入*/
}
up(&dev->sem);
return mask;
}
/* globalfifo fasync函數*/
static int globalfifo_fasync(int fd, struct file *filp, int mode)
{
struct globalfifo_dev *dev = filp->private_data;
return fasync_helper(fd, filp, mode, &dev->async_queue);
}
/*globalfifo讀函數*/
static ssize_t globalfifo_read(struct file *filp, char __user *buf, size_t count,
loff_t *ppos)
{
int ret;
struct globalfifo_dev *dev = filp->private_data; //獲得設備結構體指針
DECLARE_WAITQUEUE(wait, current); //定義等待隊列
down(&dev->sem); //獲得信號量
add_wait_queue(&dev->r_wait, &wait); //進入讀等待隊列頭
/* 等待FIFO非空 */
if (dev->current_len == 0)
{
if (filp->f_flags &O_NONBLOCK)
{
ret = - EAGAIN;
goto out;
}
__set_current_state(TASK_INTERRUPTIBLE); //改變進程狀態爲睡眠
up(&dev->sem);
schedule(); //調度其他進程執行
if (signal_pending(current))
//如果是因爲信號喚醒
{
ret = - ERESTARTSYS;
goto out2;
}
down(&dev->sem);
}
/* 拷貝到用戶空間 */
if (count > dev->current_len)
count = dev->current_len;
if (copy_to_user(buf, dev->mem, count))
{
ret = - EFAULT;
goto out;
}
else
{
memcpy(dev->mem, dev->mem + count, dev->current_len - count); //fifo數據前移
dev->current_len -= count; //有效數據長度減少
printk(KERN_INFO "read %d bytes(s),current_len:%d\n", count, dev->current_len);
wake_up_interruptible(&dev->w_wait); //喚醒寫等待隊列
ret = count;
}
out: up(&dev->sem); //釋放信號量
out2:remove_wait_queue(&dev->w_wait, &wait); //從附屬的等待隊列頭移除
set_current_state(TASK_RUNNING);
return ret;
}
/*globalfifo寫操作*/
static ssize_t globalfifo_write(struct file *filp, const char __user *buf,
size_t count, loff_t *ppos)
{
struct globalfifo_dev *dev = filp->private_data; //獲得設備結構體指針
int ret;
DECLARE_WAITQUEUE(wait, current); //定義等待隊列
down(&dev->sem); //獲取信號量
add_wait_queue(&dev->w_wait, &wait); //進入寫等待隊列頭
/* 等待FIFO非滿 */
if (dev->current_len == GLOBALFIFO_SIZE)
{
if (filp->f_flags &O_NONBLOCK)
//如果是非阻塞訪問
{
ret = - EAGAIN;
goto out;
}
__set_current_state(TASK_INTERRUPTIBLE); //改變進程狀態爲睡眠
up(&dev->sem);
schedule(); //調度其他進程執行
if (signal_pending(current))
//如果是因爲信號喚醒
{
ret = - ERESTARTSYS;
goto out2;
}
down(&dev->sem); //獲得信號量
}
/*從用戶空間拷貝到內核空間*/
if (count > GLOBALFIFO_SIZE - dev->current_len)
count = GLOBALFIFO_SIZE - dev->current_len;
if (copy_from_user(dev->mem + dev->current_len, buf, count))
{
ret = - EFAULT;
goto out;
}
else
{
dev->current_len += count;
printk(KERN_INFO "written %d bytes(s),current_len:%d\n", count, dev
->current_len);
wake_up_interruptible(&dev->r_wait); //喚醒讀等待隊列
/* 產生異步讀信號 */
if (dev->async_queue)
kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
ret = count;
}
out: up(&dev->sem); //釋放信號量
out2:remove_wait_queue(&dev->w_wait, &wait); //從附屬的等待隊列頭移除
set_current_state(TASK_RUNNING);
return ret;
}
/*文件操作結構體*/
static const struct file_operations globalfifo_fops =
{
.owner = THIS_MODULE,
.read = globalfifo_read,
.write = globalfifo_write,
.ioctl = globalfifo_ioctl,
.poll = globalfifo_poll,
.open = globalfifo_open,
.release = globalfifo_release,
.fasync = globalfifo_fasync,
};
/*初始化並註冊cdev*/
static void globalfifo_setup_cdev(struct globalfifo_dev *dev, int index)
{
int err, devno = MKDEV(globalfifo_major, index);
cdev_init(&dev->cdev, &globalfifo_fops);
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &globalfifo_fops;
err = cdev_add(&dev->cdev, devno, 1);
if (err)
printk(KERN_NOTICE "Error %d adding LED%d", err, index);
}
/*設備驅動模塊加載函數*/
int globalfifo_init(void)
{
int ret;
dev_t devno = MKDEV(globalfifo_major, 0);
/* 申請設備號*/
if (globalfifo_major)
ret = register_chrdev_region(devno, 1, "globalfifo");
else /* 動態申請設備號 */
{
ret = alloc_chrdev_region(&devno, 0, 1, "globalfifo");
globalfifo_major = MAJOR(devno);
}
if (ret < 0)
return ret;
/* 動態申請設備結構體的內存*/
globalfifo_devp = kmalloc(sizeof(struct globalfifo_dev), GFP_KERNEL);
if (!globalfifo_devp) /*申請失敗*/
{
ret = - ENOMEM;
goto fail_malloc;
}
memset(globalfifo_devp, 0, sizeof(struct globalfifo_dev));
globalfifo_setup_cdev(globalfifo_devp, 0);
init_MUTEX(&globalfifo_devp->sem); /*初始化信號量*/
init_waitqueue_head(&globalfifo_devp->r_wait); /*初始化讀等待隊列頭*/
init_waitqueue_head(&globalfifo_devp->w_wait); /*初始化寫等待隊列頭*/
return 0;
fail_malloc: unregister_chrdev_region(devno, 1);
return ret;
}
/*模塊卸載函數*/
void globalfifo_exit(void)
{
cdev_del(&globalfifo_devp->cdev); /*註銷cdev*/
kfree(globalfifo_devp); /*釋放設備結構體內存*/
unregister_chrdev_region(MKDEV(globalfifo_major, 0), 1); /*釋放設備號*/
}
MODULE_AUTHOR("Song Baohua");
MODULE_LICENSE("Dual BSD/GPL");
module_param(globalfifo_major, int, S_IRUGO);
module_init(globalfifo_init);
module_exit(globalfifo_exit);
4.疑惑
在異步通知機制中,我們只定義了一個數據結構和兩個函數,但是內核卻可以實現向驅動對應的用戶進程發送信號.這過程和等待隊列機制一樣,只定義了一個隊列頭和操作一些相關的關鍵函數,卻實現了進程的休眠.邏輯上感覺有點"虛無縹緲".爲了保持邏輯上的連貫性.下面對異步通知機制的兩個關鍵函數進行宏觀的邏輯分析.
4-1.int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
此函數承擔了操作集fops裏面fasync域的核心工作.其中,參數fapp是我們具體驅動定義的,其餘的均爲根據用戶空間的信息構建起來的:
int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
{
if (!on)
return fasync_remove_entry(filp, fapp);
return fasync_add_entry(fd, filp, fapp);
}
從字面上理解,此函數是完成fasync隊列的增/刪.
/*
* Add a fasync entry. Return negative on error, positive if
* added, and zero if did nothing but change an existing one.
*
* NOTE! It is very important that the FASYNC flag always
* match the state "is the filp on a fasync list".
*/
static int fasync_add_entry(int fd, struct file *filp, struct fasync_struct **fapp)
{
struct fasync_struct *new, *fa, **fp;
int result = 0;
new = kmem_cache_alloc(fasync_cache, GFP_KERNEL);
if (!new)
return -ENOMEM;
spin_lock(&filp->f_lock);
write_lock_irq(&fasync_lock);
for (fp = fapp; (fa = *fp) != NULL; fp = &fa->fa_next) {
if (fa->fa_file != filp)
continue;
fa->fa_fd = fd;
kmem_cache_free(fasync_cache, new);
goto out;
}
new->magic = FASYNC_MAGIC;
new->fa_file = filp;
new->fa_fd = fd;
new->fa_next = *fapp;
*fapp = new;
result = 1;
filp->f_flags |= FASYNC;
out:
write_unlock_irq(&fasync_lock);
spin_unlock(&filp->f_lock);
return result;
}
其中語句:
new = kmem_cache_alloc(fasync_cache, GFP_KERNEL);
new->magic = FASYNC_MAGIC;
new->fa_file = filp;
new->fa_fd = fd;
new->fa_next = *fapp;
*fapp = new;
和等待隊列一樣(動態生成隊列),這裏動態生成(從slab池摘取)了一個new的fasync_struct結構體,並把我們具體驅動實現的結構體指針fapp掛在它後面的節點,而我們的fapp又回指向new,形成一個雙向鏈表.後續的異步通知的機制裏面,new變成了其核心流竄的數據體.
4-2.void kill_fasync(struct fasync_struct **fp, int sig, int band)
此函數完成信號源的發出:
void kill_fasync(struct fasync_struct **fp, int sig, int band)
{
/* First a quick test without locking: usually
* the list is empty.
*/
if (*fp) {
read_lock(&fasync_lock);
/* reread *fp after obtaining the lock */
__kill_fasync(*fp, sig, band);
read_unlock(&fasync_lock);
}
}
其絕大部分工作由函數__kill_fasync()完成,如下:
void __kill_fasync(struct fasync_struct *fa, int sig, int band)
{
while (fa) {
struct fown_struct * fown;
if (fa->magic != FASYNC_MAGIC) {
printk(KERN_ERR "kill_fasync: bad magic number in "
"fasync_struct!\n");
return;
}
fown = &fa->fa_file->f_owner;
/* Don't send SIGURG to processes which have not set a
queued signum: SIGURG has its own default signalling
mechanism. */
if (!(sig == SIGURG && fown->signum == 0))
send_sigio(fown, fa->fa_fd, band);
fa = fa->fa_next;
}
}
其中,fa便是我們具體驅動實現的並被函數fasync_helper()初始化的.下面語句便是獲取對應進程的接收者:
fown = &fa->fa_file->f_owner;
然後向對應進程發送信號:
send_sigio(fown, fa->fa_fd, band);
這裏還有一個疑問,我們傳遞進來的信號在這裏只是用來作判斷是不是SIGURG信號,它並沒有發送到上層進程.難道是遍歷把所有信號都發一遍?