今天寫的是Linux設(shè)備驅(qū)動中的阻塞和非阻塞I/0,何謂阻塞與非阻塞I/O,?簡單來說就是對I/O操作的兩種不同的方式,驅(qū)動程序可以靈活的支持用戶空間對設(shè)備的這兩種訪問方式,。
一,、基本概念:
- 阻塞操作 : 是指在執(zhí)行設(shè)備操作時,若不能獲得資源,,則掛起進(jìn)程直到滿足操作條件后再進(jìn)行操作,。被掛起的進(jìn)程進(jìn)入休眠, 被從調(diào)度器移走,,直到條件滿足,。
- 非阻塞操作 :在不能進(jìn)行設(shè)備操作時,并不掛起,,它或者放棄,,或者不停地查詢,直到可以進(jìn)行操作,。非阻塞應(yīng)用程序通常使用select系統(tǒng)調(diào)用查詢是否可以對設(shè)備進(jìn)行無阻塞的訪問最終會引發(fā)設(shè)備驅(qū)動中 poll函數(shù)執(zhí)行,。
二、輪詢操作
阻塞的讀取一個字符:
1 2 3 4 5 6 7 8 | char buf;
fd = open( "/dev/ttyS1" ,O_RDWR);
.....
res = read(fd,&buf,1); //當(dāng)串口上有輸入時才返回,沒有輸入則進(jìn)程掛起睡眠
if (res == 1)
{
printf ( "%c/n" ,buf);
}
|
非阻塞的讀一個字符:
1 2 3 4 5 | char buf;
fd = open( "/dev/ttyS1" ,O_RDWR|O_NONBLOCK); //O_NONBLOCK 非阻塞標(biāo)識
.....
while (read(fd,&buf,1)!=1); //串口上沒有輸入則返回,,所以循環(huán)讀取
printf ( "%c/n" ,buf);
|
阻塞操作常常用等待隊列來實現(xiàn),,而非阻塞操作用輪詢的方式來實現(xiàn)。非阻塞I/O的操作在應(yīng)用層通常會用到select()和poll()系統(tǒng)調(diào)用查詢是否可對設(shè)備進(jìn)行無阻塞訪問,。select()和poll()系統(tǒng)調(diào)用最終會引發(fā)設(shè)備驅(qū)動中的poll()函數(shù)被調(diào)用,。這里對隊列就不多介紹了,大家可以看看數(shù)據(jù)結(jié)構(gòu)里面的知識點,。
應(yīng)用層的select()原型為:
1 2 3 | int select( int numfds,fd_set *readfds,fd_set *writefds,fd_set *exceptionfds,
struct timeval *timeout); numfds 的值為需要檢查的號碼最高的文件描述符加1,,若select()在等待timeout時間后,若沒有文件描述符準(zhǔn)備好則返回,。
|
應(yīng)用程序為:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | #inlcude------
main()
{
int fd,num;
char rd_ch[BUFFER_LEN];
fd_set rfds,wfds; //讀寫文件描述符集
//以非阻塞方式打開/dev/globalfifo設(shè)備文件
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);
}
}
}
|
下面說說設(shè)備驅(qū)動中的poll()函數(shù),,函數(shù)原型如下:
1 | static unsigned int poll( struct file *file, struct socket *sock,poll_table *wait) //第一個參數(shù)是file結(jié)構(gòu)體指針,,第三個參數(shù)是輪詢表指針,這個函數(shù)應(yīng)該進(jìn)行兩項工作
|
- 對可能引起設(shè)備文件狀態(tài)變化的等待隊列調(diào)用poll_wait()函數(shù),,將對應(yīng)的等待隊列頭添加到poll_table
- 返回表示是否能對設(shè)備進(jìn)行無阻塞讀,,寫訪問的掩碼
這里還要提到poll_wait()函數(shù),很多人會以為是和wait_event()一樣的函數(shù),,會阻塞的等待某件事情的發(fā)生,,其實這個函數(shù)并不會引起阻塞,它的工作是把當(dāng)前的進(jìn)程增添到wait參數(shù)指定的等待列表poll_table中去,,poll_wait()函數(shù)原型如下:
1 2 | static inline void poll_wait( struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
從中可以看出是將等待隊列頭wait_address添加到p所指向的結(jié)構(gòu)體中(poll_table)
|
驅(qū)動函數(shù)中的poll()函數(shù)典型模板如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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; //獲得設(shè)備結(jié)構(gòu)體指針
...
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驅(qū)動
在globalfifo的poll()函數(shù)中,首先將設(shè)備結(jié)構(gòu)體重的r_wait和w_wait等待隊列頭加到等待隊列表,,globalfifo設(shè)備驅(qū)動的poll()函數(shù)如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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;
}
|
四,、總結(jié)
阻塞與非阻塞操作:
- 定義并初始化等待對列頭;
- 定義并初始化等待隊列,;
- 把等待隊列添加到等待隊列頭
- 設(shè)置進(jìn)程狀態(tài)(TASK_INTERRUPTIBLE(可以被信號打斷)和TASK_UNINTERRUPTIBLE(不能被信號打斷))
- 調(diào)用其它進(jìn)程
poll機制:
- 把等待隊列頭加到poll_table
- 返回表示是否能對設(shè)備進(jìn)行無阻塞讀,,寫訪問的掩碼
|