非同步通知:
使用非同步通知機制可以提高查詢裝置的效率。通過使用非同步通知,應用程式可以在資料可用時收到一個訊號,而無需不停地輪詢。
設定非同步通知的步驟(針對應用程式層來說的):
1.首先制定一個進程作為檔案的屬主。通過使用fcntl系統調用執行F_SETOWN命令時,屬主進程的ID號就會儲存在filp->f_owner中,目的是為了讓核心知道應該通知哪個進程。
2.在裝置中設定FASYNC標誌。通過fcntl調用的F_SETFL來完成。
設定晚以上兩步後,輸入檔案就可以在新資料到達時請求發送一個SIGIO訊號,該訊號被發送到存放在filp->f_owner中的進程。
執行個體:啟用stdin輸入檔案到當前進程的非同步通知機制
signal(SIGIO,&input_handler);
fcntl(STDIN_FILENO,F_SETOWN,getpid());//設定STDIN_FILENO的屬主為當前進程
oflags=fcntl(STDIN_FILENO,F_GETFL);//獲得STDIN_FILENO的描述符
fcntl(STDIN_FILENO,F_SETFL,oflags|FASYNC);//從新設定描述符
注意的問題:
1.應用程式通常只假設通訊端和終端具備非同步通知功能。
2.當進程受到SIGIO訊號時,它並不知道哪個輸入檔案有了新的輸入。如果有多於一個檔案可以非同步通知輸入的進程,則應用程式必須藉助於poll或select來確定輸入的來源。
驅動程式的實現:
1、F_SETOWN被調用時對filp->f_owner賦值。
2.在執行F_SETFL啟用FASYNC時,調用驅動程式的fasync方法。只要filp->f_owner中的FASYNC標誌發生了變化,就會調用該方法,以便把這個變化通知驅動程式,使其正確響應。檔案開啟時,預設fasync標誌是被清除的。
3.當資料到達時,所有註冊為非同步通知的進程都會收到一個SIGIO訊號。
linux 這種調用方法基於一個資料結構和兩個函數,對於驅動開發而言主要關注兩個函數,核心會自己維護該資料結構,為驅動程式服務(書上對該資料結構也並未給出多少解釋)。包含在標頭檔<linux/fs.h>中。
struct fasync_struct {
int magic;
int fa_fd;
struct fasync_struct *fa_next; /* singly linked list */
struct file *fa_file;
};
兩個函數如下:
int fasnyc_helper(int fd,struct file *filp,int mode,struct fasync_struct **fa);
void kill_fasync(struct fasync_struct **fa,int sig,int band);
當一個開啟的檔案的FASYNC標誌被修改時,調用fasync_helper以便從相關的進程列表中增加或刪除檔案。當資料到達時,可使用kill_fasync通知所有的相關進程。它的參數是要發送的訊號(sig)和頻寬(band)。
注意的地方:1.對於struct fasync_struct這個資料結構在編寫驅動時並不需要特別關注,它會由核心來維護,驅動程式中調用它即可。如同poll的底層實現上poll_table這個記憶體頁鏈表也是由核心來維護,驅動程式使用它即可。
2.對於用來通知可讀的非同步通知:band幾乎總是為poll_in
對於用來通知可寫的非同步通知:band幾乎總是為poll_out
3.wait_enevt_interruptible(dev->inq,(dev->rp != dev->wp));
wake_up_interruptible(&dev->inq);
在使進程休眠的時候使用的是值傳遞,但在喚醒進程的時候使用的指標傳遞。
實現:
static int scull_p_fasync(int fd,struct file *filp,int mode)
{
struct scull_pipe *dev = filp->private;
fasync_helper(fd,filp,mod,&dev->async_queue);
}
接著當資料到達時,必須執行下面的語句來通知非同步讀取進程。由於提供scullpipe的讀取進程的新資料是在某個進程調用wirte產生的,所以這條語句在scullpipe的write方法中實現:
if(dev->async_queue)
kill_fasync(&dev->async_queue,SIGIO,POLL_IN);
最後注意在檔案關閉之前,必須調用fasync方法,以便從活動的非同步讀取進程列表中刪除檔案。
scull_p_fasync(-1,filp,0);
下面是自己敲了一下scull_pipe的程式,順便對前邊學的複習一下:
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/proc.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <linux/poll.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/scull.h> //包含一些局部變數
int scull_major = SCULL_MAJOR;
int scull_minor = 0;
int scull_p_nr_devs = SCULL_P_NR_DEVS;
int scull_p_buffer = SCULL_P_BUFFER;
module_param(scull_major,int,S_IRUGO);
module_param(scull_minor,int,S_IRUGO);
module_param(scull_p_nr_devs,int,0);
module_param(scull_p_buffer,int,0);
struct scull_pipe {
wait_queue_head_t inq,outq; //定義讀寫等待隊列
char *buffer,*end; //緩衝區開始和結束指標
int buffersize;
char *rp,*wp; //當前的讀寫位置
int nreaders,nwriters; //讀寫者計數
sttuct fasync_struct *asynv_queue;
struct semphore sem;
struct cdev cdev;
};
dev_t scull_devno;
static struct scull_pipe *scull_p_devices;
static int scull_p_fasync(int fd,struct file *filp,int mode);
static int spacefree(struct scull_pipe *dev);
static int scull_p_open(struct inode *inode,struct file *filp)
{
struct scull_pipe *dev;
dev = container_of(inode->i_cdev,struct scull_pipe,cdev);
filp->private_data = dev;
if(down_interuptible(&dev->sem))
return -ERESTARTSYS;
if(!dev->buffer){
dev->buffer = kmalloc(scull_p_buffer,GFP_KERNEL);
if(!dev->buffer){
up(&dev->sem);
return -ENOMEM;
}
}
dev->buffersize = scull_p_buffersize;
dev->end = scull_p_buffer + scull_p_buffersize;
dev->rp = dev->wp = dev->buffer;
if(filp->f_mode & FMODE_READ)
dev->nreaders++;
if(filp->f_mode & FMODE_WRITE)
dev->nwriters++;
up(&dev->sem);
return nonseekable_open(inode,filp);
}
static int scull_p_release(struct inode *inode,struct file *filp)
{
struct scull_pipe *dev = filp->private_data;
scull_p_fasync(-1,filp,0);
down_interruptible(&dev->sem);
if(filp->f_mode & FMODE_READ)
dev->nreaders--;
if(filp->f_mode & FMODE_WRITE)
dev->nwriters--;
if(dev->nreaders + dev->nwriters == 0){
kfree(dev->buffer);
dev->buffer =NULL;
}
up(&dev->sem);
return 0;
}
static scull_p_read(struct file *filp,char __user *buf,size_t count,loof_t *f_pos)
{
struct scull_pipe *dev = filp->private_data;
if(down_interruptible(&dev->sem))
renturn -ERESTARTSYS;
while(dev->rp == dev->wp){
up(&dev->sem);
if(filp->f_flags & O_NONBLOCK)
return -EAGAIN;
PDEBUG(....);
if(wait_event_intertuptible(dev->inq,(dev->rp != dev->wp)))
return -ERESTARTSYS;
if(down_interruptible(&dev->sem))
return -ERESTARTSYS;
}
....
if(copy_to_user(buf,dev->rp,count){
up(&dev->sem);
return -EFAULT;
}
dev->rp += count;
if(dev->rp == dev->end)
dev->rp = dev->end;
up(&dev->sem);
wake_up_interrputible(&dev->outq); //在讀走資料以後,喚醒寫等待隊列
return count;
}
static int scull_p_ioctl(struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg)
{
int err = 0;
if(_IOC_TYPE(cmd) != SCULL_IOC_MAGIC) return -ENOTTY;
if(_IOC_NR(cmd) > SCULL_IOC_MAXNR) return -ENOTTY;
if(_IOC_DIR(cmd) & _IOC_READ)
err = !access_ok(VERIFY_WRITE,(void __user *)arg,_IOC_SIZE(cmd));
else if(_IOC_DIR(cmd) & _IOC_WRITE)
err = !access_ok(VERIFY_READ,(void __user *)arg,_IOC_SIZE(cmd)):
if(err) return -EFAULT;
這裡的access_ok函數,成功返回1,失敗返回零。上述程式碼完成了對cmd的檢查確保其唯一性,access_ok是面向核心的 ,所以傳輸方向與其剛好相反。
switch(cmd){
case SCULL_P_IOCTSIZE: //分別是在標頭檔中定義的宏命令
scull_p_buffer = arg;
break;
case SCULL_P_IOCQSIZE:
return scull_p_buffer;
default: /* redundant, as cmd was checked against MAXNR */
return -ENOTTY;
}
return 0;
}
static int scull_getwritespace(struct scull_pipe *dev, struct file *filp)
{
while (spacefree(dev) == 0) { /* full */
DEFINE_WAIT(wait); 設定等待隊列入口
up(&dev->sem);
if (filp->f_flags & O_NONBLOCK)
return -EAGAIN;
PDEBUG("/"%s/" writing: going to sleep/n",current->comm);
prepare_to_wait(&dev->outq, &wait, TASK_INTERRUPTIBLE); //將dev->outq加入到等待隊列,並設定其為可中斷狀態。
if (spacefree(dev) == 0) //這個檢查狠重要,確保仍需要休眠
schedule();
finish_wait(&dev->outq, &wait);//schedule()晚以後,馬上調用finish_wait
if (signal_pending(current))
return -ERESTARTSYS; /* signal: tell the fs layer to handle it */
if (down_interruptible(&dev->sem))
return -ERESTARTSYS;
}
return 0;
}
這裡結合scull程式碼分析下非同步通知實現的過程:
非同步機制在應用程式層設計FASYNC標誌的置位,驅動層涉及到一個資料結構和兩個函數的調用,首先來看驅動層,在scull的驅動代碼中用scull_p_fasync函數實現了對fasync_helper的調用,如下:
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);
}
在scull_p_write函數中實現了對kill_fasync的調用,如下:
static ssize_t scull_p_write(struct file *filp, const char __user *buf, size_t count,loff_t *f_pos)
{
struct scull_pipe *dev = filp->private_data;
int result;
if (down_interruptible(&dev->sem))
return -ERESTARTSYS;
/* Make sure there's space to write */
result = scull_getwritespace(dev, filp);
if (result)
return result; /* scull_getwritespace called up(&dev->sem) */
/* ok, space is there, accept something */
count = min(count, (size_t)spacefree(dev));
if (dev->wp >= dev->rp)
count = min(count, (size_t)(dev->end - dev->wp)); /* to end-of-buf */
else /* the write pointer has wrapped, fill up to rp-1 */
count = min(count, (size_t)(dev->rp - dev->wp - 1));
PDEBUG("Going to accept %li bytes to %p from %p/n", (long)count, dev->wp, buf);
if (copy_from_user(dev->wp, buf, count)) {
up (&dev->sem);
return -EFAULT;
}
dev->wp += count;
if (dev->wp == dev->end)
dev->wp = dev->buffer; /* wrapped */
up(&dev->sem);
/* finally, awake any reader */
wake_up_interruptible(&dev->inq); /* blocked in read() and select() */
/* and signal asynchronous readers, explained late in chapter 5 */
if (dev->async_queue)
kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
PDEBUG("/"%s/" did write %li bytes/n",current->comm, (long)count);
return count;
}
在程式的最後調用了kill_fasync,在調用kill_fasync後會向檔案的屬主進程發送SIGIO訊號,從而在驅動層完成了非同步機制的設定。下面來看應用程式:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
int gotdata=0;
void sighandler(int signo)
{
if (signo==SIGIO)
gotdata++;
return;
}
char buffer[21];
int main(int argc, char **argv)
{
int pipetest0;
int count;
struct sigaction action;
if ((pipetest0 = open("/dev/scullpipe0",O_RDONLY)) < 0) {
printf("open scullpipe0 error! /n");
exit(1);
}
memset(&action, 0, sizeof(action));
action.sa_handler = sighandler;
action.sa_flags = 0;
sigaction(SIGIO, &action, NULL);
fcntl(pipetest0, F_SETOWN, getpid());
fcntl(pipetest0, F_SETFL, fcntl(pipetest0, F_GETFL) | FASYNC);
while(1) {
/* this only returns if a signal arrives */
sleep(86400); /* one day */
if (!gotdata)
continue;
count=read(pipetest0, buffer, 21);
/* buggy: if avail data is more than 4kbytes... */
write(1,buffer,count);
gotdata=0;
break;
}
close(pipetest0 );
printf("close pipetest0 ! /n");
printf("exit !/n");
exit(0);
}
這個程式運行在後台,首先在主程式中通過
fcntl(pipetest0, F_SETOWN, getpid());
fcntl(pipetest0, F_SETFL, fcntl(pipetest0, F_GETFL) | FASYNC);
完成了應用程式層非同步通知的設定,然後只有在驅動層調用到了kill_fasync從而發出SIGIO訊號後,啟用sigaction的處理函數,設定了gotdata的值,並得到了POLL_IN狀態表明可讀了,這是調用read函數讀出資料,並通過write將讀出的資料發往到終端。以上就是非同步交換的整個過程。