1. 程式人生 > >深入淺出:Linux裝置驅動中的阻塞和非阻塞I/O

深入淺出:Linux裝置驅動中的阻塞和非阻塞I/O

今天寫的是Linux裝置驅動中的阻塞和非阻塞I/0,何謂阻塞與非阻塞I/O?簡單來說就是對I/O操作的兩種不同的方式,驅動程式可以靈活的支援使用者空間對裝置的這兩種訪問方式。

一、基本概念:

阻塞操作 : 是指在執行裝置操作時,若不能獲得資源,則掛起程序直到滿足操作條件後再進行操作。被掛起的程序進入休眠, 被從排程器移走,直到條件滿足。
非阻塞操作 :在不能進行裝置操作時,並不掛起,它或者放棄,或者不停地查詢,直到可以進行操作。非阻塞應用程式通常使用select系統呼叫查詢是否可以對裝置進行無阻塞的訪問最終會引發裝置驅動中 poll函式執行。

二、輪詢操作

阻塞的讀取一個字元:

char buf;
fd = open
("/dev/ttyS1",O_RDWR); ..... res = read(fd,&buf,1); //當串列埠上有輸入時才返回,沒有輸入則程序掛起睡眠 if(res == 1) { printf("%c/n",buf); }
char buf;
fd = open("/dev/ttyS1",O_RDWR);
.....
res = read(fd,&buf,1); //當串列埠上有輸入時才返回,沒有輸入則程序掛起睡眠
if(res == 1)
{
 printf("%c/n",buf);
}

非阻塞的讀一個字元:

char buf;
fd = open("/dev/ttyS1",O_RDWR|O_NONBLOCK);//
O_NONBLOCK 非阻塞標識 ..... while(read(fd,&buf,1)!=1);//串列埠上沒有輸入則返回,所以迴圈讀取 printf("%c/n",buf);
char buf;
fd = open("/dev/ttyS1",O_RDWR|O_NONBLOCK);//O_NONBLOCK 非阻塞標識
.....
while(read(fd,&buf,1)!=1);//串列埠上沒有輸入則返回,所以迴圈讀取
printf("%c/n",buf);

阻塞操作常常用等待佇列來實現,而非阻塞操作用輪詢的方式來實現。非阻塞I/O的操作在應用層通常會用到select()和poll()系統呼叫查詢是否可對裝置進行無阻塞訪問。select()和poll()系統呼叫最終會引發裝置驅動中的poll()函式被呼叫。這裡對佇列就不多介紹了,大家可以看看資料結構裡面的知識點。

應用層的select()原型為:

int select(int numfds,fd_set *readfds,fd_set *writefds,fd_set *exceptionfds,struct timeval *timeout);

numfds 的值為需要檢查的號碼最高的檔案描述符加1,若select()在等待timeout時間後,若沒有檔案描述符準備好則返回。

int select(int numfds,fd_set *readfds,fd_set *writefds,fd_set *exceptionfds,

struct timeval *timeout); numfds 的值為需要檢查的號碼最高的檔案描述符加1,若select()在等待timeout時間後,若沒有檔案描述符準備好則返回。

應用程式為:

#inlcude------
main()
{
 int fd,num;
 char rd_ch[BUFFER_LEN];
 fd_set rfds,wfds; //讀寫檔案描述符集
 //以非阻塞方式開啟/dev/globalfifo裝置檔案
 fd=open("/dev/globalfifo",O_RDWR|O_NONBLOCK);
 if(fd != -1)
 {
 //FIFO 清零
 if(ioctl(fd,FIFO_CLEAR,0) < 0)
 {
 printf("ioctl cmd failed /n");
 }
 while(1)
 {
 FD_ZERO(&rfds);
 FD_ZERO(&wfds);
 FD_SET(fd,&rfds);
 FD_SET(fd,&wfds);
 select(fd+1,&rfds,&wfds,null,null);

 }
 }
}
#inlcude------
main()
{
 int fd,num;
 char rd_ch[BUFFER_LEN];
 fd_set rfds,wfds; //讀寫檔案描述符集
 //以非阻塞方式開啟/dev/globalfifo裝置檔案
 fd=open("/dev/globalfifo",O_RDWR|O_NONBLOCK);
 if(fd != -1)
 {
 //FIFO 清零
 if(ioctl(fd,FIFO_CLEAR,0) < 0)
 {
 printf("ioctl cmd failed /n");
 }
 while(1)
 {
 FD_ZERO(&rfds);
 FD_ZERO(&wfds);
 FD_SET(fd,&rfds);
 FD_SET(fd,&wfds);
 select(fd+1,&rfds,&wfds,null,null);

 }
 }
}

下面說說裝置驅動中的poll()函式,函式原型如下:

static unsigned int poll(struct file *file, struct socket *sock,poll_table *wait) //第一個引數是file結構體指標,第三個引數是輪詢表指標,這個函式應該進行兩項工作
static unsigned int poll(struct file *file, struct socket *sock,poll_table *wait) //第一個引數是file結構體指標,第三個引數是輪詢表指標,這個函式應該進行兩項工作
對可能引起裝置檔案狀態變化的等待佇列呼叫poll_wait()函式,將對應的等待佇列頭新增到poll_table
返回表示是否能對裝置進行無阻塞讀,寫訪問的掩碼

這裡還要提到poll_wait()函式,很多人會以為是和wait_event()一樣的函式,會阻塞的等待某件事情的發生,其實這個函式並不會引起阻塞,它的工作是把當前的程序增添到wait引數指定的等待列表poll_table中去,poll_wait()函式原型如下:

static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)

從中可以看出是將等待佇列頭wait_address新增到p所指向的結構體中(poll_table)

static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)

從中可以看出是將等待佇列頭wait_address新增到p所指向的結構體中(poll_table)

驅動函式中的poll()函式典型模板如下:

static unsigned int xxx_poll(struct file *filp,struct socket *sock,
 poll_table *wait)
{
unsigned int mask = 0;
struct xxx_dev *dev = filp->private_data;//獲得裝置結構體指標
...
poll_wait(filp,&dev->r_wait,wait);//加讀等待佇列頭到poll_table
poll_wait(filp,&dev->w_wait,wait);//加寫等待佇列頭到poll_table
...
if(...)//可讀
mask |= POLLIN | POLLRDNORM;
if(...)//可寫
mask |= POLLOUT | POLLRDNORM;
...
return mask;

}
static unsigned int xxx_poll(struct file *filp,struct socket *sock,
 poll_table *wait)
{
unsigned int mask = 0;
struct xxx_dev *dev = filp->private_data;//獲得裝置結構體指標
...
poll_wait(filp,&dev->r_wait,wait);//加讀等待佇列頭到poll_table
poll_wait(filp,&dev->w_wait,wait);//加寫等待佇列頭到poll_table
...
if(...)//可讀
mask |= POLLIN | POLLRDNORM;
if(...)//可寫
mask |= POLLOUT | POLLRDNORM;
...
return mask;

}

三、支援輪詢操作的globalfifo驅動

在globalfifo的poll()函式中,首先將裝置結構體重的r_wait和w_wait等待佇列頭加到等待隊列表,globalfifo裝置驅動的poll()函式如下:

static unsigned int gloablfif0_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->r_wait , wait) ;

 if(dev->current_len != 0)
 {
 mask |= POLLIN | POLLRDNORM; 
 }

 if(dev->current_len != GLOBALFIFO_SIZE)
 {
 mask |= POLLOUT | POLLWRNORM;
 }

 up(&dev->sem);
 return mask;
}
static unsigned int gloablfif0_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->r_wait , wait) ;

 if(dev->current_len != 0)
 {
 mask |= POLLIN | POLLRDNORM; 
 }

 if(dev->current_len != GLOBALFIFO_SIZE)
 {
 mask |= POLLOUT | POLLWRNORM;
 }

 up(&dev->sem);
 return mask;
}

四、總結

阻塞與非阻塞操作:

定義並初始化等待對列頭;
定義並初始化等待佇列;
把等待佇列新增到等待佇列頭
設定程序狀態(TASK_INTERRUPTIBLE(可以被訊號打斷)和TASK_UNINTERRUPTIBLE(不能被訊號打斷))
呼叫其它程序

poll機制:

把等待佇列頭加到poll_table
返回表示是否能對裝置進行無阻塞讀,寫訪問的掩碼