分析openmoko的源碼的時候遇到fd_set和select相關的問題。網上摘錄整理如下。以備以後查看。
fd_set以及select和poll的用法-驅動程序的阻塞與非阻塞
select()函數主要是建立在fd_set類型的基礎上的。
fd_set(它比較重要所以先介紹一下)是一組文件描述字(fd)的集合,它用一位來表示一個fd(下面會仔細介紹),對於fd_set類型通過下面四個宏來操作:
fd_set set;
FD_ZERO(&set); /* 將set清零使集合中不含任何fd*/
FD_SET(fd, &set); /* 將fd加入set集合 */
FD_CLR(fd, &set); /* 將fd從set集合中清除 */ WS
FD_ISSET(fd, &set); /* 測試fd是否在set集合中*/
過去,一個fd_set通常只能包含<32的fd(文件描述字),因爲fd_set其實只用了一個32位矢量來表示fd;現在,UNIX系統通常會在頭文件<sys/select.h>中定義常量
FD_SETSIZE,它是數據類型fd_set的描述字數量,其值通常是1024,這樣就能表示<1024的fd。根據fd_set的位矢量實現,我們可以重新理解操作fd_set的四個宏
:
fd_set set;
FD_ZERO(&set); /*將set的所有位置0,如set在內存中佔8位則將set置爲 00000000*/
FD_SET(0, &set); /* 將set的第0位置1,如set原來是00000000,則現在變爲10000000,這樣fd==1的文件描述字就被加進set中了 */
FD_CLR(4, &set); /*將set的第4位置0,如set原來是10001000,則現在變爲10000000,這樣fd==4的文件描述字就被從set中清除了 */
FD_ISSET(5, &set); /* 測試set的第5位是否爲1,如果set原來是10000100,則返回非零,表明fd==5的文件描述字在set中;否則返回0*/
―――――――――――――――――――――――――――――――――――――――
注意fd的最大值必須<FD_SETSIZE。
―――――――――――――――――――――――――――――――――――――――
select函數的接口比較簡單:
int select(int nfds, fd_set *readset, fd_set *writeset, fd_set* exceptset, struct timeval *timeout);
功能:
測試指定的fd可讀?可寫?有異常條件待處理?
參數:
nfds
需要檢查的文件描述字個數(即檢查到fd_set的第幾位),數值應該比三組fd_set中所含的最大fd值更大,一般設爲三組fd_set中所含的最大fd值加1(如在
readset,writeset,exceptset中所含最大的fd爲5,則nfds=6,因爲fd是從0開始的)。設這個值是爲提高效率,使函數不必檢查fd_set的所有1024位。
readset
用來檢查可讀性的一組文件描述字。
writeset
用來檢查可寫性的一組文件描述字。
exceptset
用來檢查是否有異常條件出現的文件描述字。(注:錯誤不包括在異常條件之內)
timeout
有三種可能:
1. timeout=NULL(阻塞:直到有一個fd位被置爲1函數才返回)
2. timeout所指向的結構設爲非零時間(等待固定時間:有一個fd位被置爲1或者時間耗盡,函數均返回)
3. timeout所指向的結構,時間設爲0(非阻塞:函數檢查完每個fd後立即返回)
返回值:
返回對應位仍然爲1的fd的總數。
Remarks:
三組fd_set均將某些fd位置0,只有那些可讀,可寫以及有異常條件待處理的fd位仍然爲1。
使用select函數的過程一般是:
先調用宏FD_ZERO將指定的fd_set清零,然後調用宏FD_SET將需要測試的fd加入fd_set,接着調用函數select測試fd_set中的所有fd,最後用宏FD_ISSET檢查某個
fd在函數select調用後,相應位是否仍然爲1。
以下是一個測試單個文件描述字可讀性的例子:
int isready(int fd)
{
int rc;
fd_set fds;
struct timeval tv;
FD_ZERO(&fds);
FD_SET(fd,&fds);
tv.tv_sec = tv.tv_usec = 0;
rc = select(fd+1, &fds, NULL, NULL, &tv);
if (rc < 0) //error
return -1;
return FD_ISSET(fd,&fds) ? 1 : 0;
}
下面還有一個複雜一些的應用:
//這段代碼將指定測試Socket的描述字的可讀可寫性,因爲Socket使用的也是fd
uint32 SocketWait(TSocket *s,bool rd,bool wr,uint32 timems)
{
fd_set rfds,wfds;
#ifdef _WIN32
TIMEVAL tv;
#else
struct timeval tv;
#endif /* _WIN32 */
FD_ZERO(&rfds);
FD_ZERO(&wfds);
if (rd) //TRUE
FD_SET(*s,&rfds); //添加要測試的描述字
if (wr) //FALSE
FD_SET(*s,&wfds);
tv.tv_sec=timems/1000; //second
tv.tv_usec=timems%1000; //ms
for (;;) //如果errno==EINTR,反覆測試緩衝區的可讀性
switch(select((*s)+1,&rfds,&wfds,NULL, (timems==TIME_INFINITE?NULL:&tv))) //測試在規定的時間內套接口接收緩衝區中是否有數據可讀
{
//0--超時,-1--出錯
case 0: /* time out */
return 0;
case (-1): /* socket error */
if (SocketError()==EINTR)
break;
return 0; //有錯但不是EINTR
default:
if (FD_ISSET(*s,&rfds)) //如果s是fds中的一員返回非0,否則返回0
return 1;
if (FD_ISSET(*s,&wfds))
return 2;
return 0;
};
}
+++++++++++++++
原創:http://jiaxingkui123.blog.163.com/blog/static/8756468420091534254862/
select系統調用是用來讓我們的程序監視多個文件句柄(file descriptor)的狀態變化的。程序會停在select這裏等待,直到被監視的文件句柄有某一個或多個發生了狀態改
變。 文件在句柄在Linux裏很多,如果你man某個函數,在函數返回值部分說到成功後有一個文件句柄被創建的都是的,如man socket可以看到“On success, a file
descriptor for the new socket is returned.”而man 2 open可以看到“open() and creat() return the new file descriptor”,其實文件句柄就是一個整數,看
socket函數的聲明就明白了:
int socket(int domain, int type, int protocol);
當然,我們最熟悉的句柄是0、1、2三個,0是標準輸入,1是標準輸出,2是標準錯誤輸出。0、1、2是整數表示的,對應的FILE *結構的表示就是stdin、stdout、
stderr,0就是stdin,1就是stdout,2就是stderr。比如下面這兩段代碼都是從標準輸入讀入9個字節字符:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char ** argv)
{
char buf[10] = "";
read(0, buf, 9); /* 從標準輸入 0 讀入字符 */
fprintf(stdout, "%s/n", buf); /* 向標準輸出 stdout 寫字符 */
return 0;
}
/* **上面和下面的代碼都可以用來從標準輸入讀用戶輸入的9個字符** */
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char ** argv)
{
char buf[10] = "";
fread(buf, 9, 1, stdin); /* 從標準輸入 stdin 讀入字符 */
write(1, buf, strlen(buf));
return 0;
}
繼續上面說的select,就是用來監視某個或某些句柄的狀態變化的。
select函數原型如下:
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
函 數的最後一個參數timeout顯然是一個超時時間值,其類型是struct timeval *,即一個struct timeval結構的變量的指針,所以我們在程序裏要申明一個struct
timeval tv;然後把變量tv的地址&tv傳遞給select函數。struct timeval結構如下:
struct timeval
{
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
第2、 3、4三個參數是一樣的類型: fd_set *,即我們在程序裏要申明幾個fd_set類型的變量,比如rdfds, wtfds, exfds,然後把這個變量的地址&rdfds, &wtfds,
&exfds 傳遞給select函數。
這三個參數都是一個句柄的集合,第一個rdfds是用來保存這樣的句柄的:當句柄的狀態變成可讀的時系統就會告訴select函數返回,同理第二個wtfds是指有句柄狀
態變成可寫的時系統就會告訴select函數返回,同理第三個參數exfds是特殊情況,即句柄上有特殊情況發生時系統會告訴select函數返回。特殊情況比如對方通過一個
socket句柄發來了緊急數據。如果我們程序裏只想檢測某個socket是否有數據可讀,我們可以這樣:
fd_set rdfds; /* 先申明一個 fd_set 集合來保存我們要檢測的 socket句柄 */
struct timeval tv; /* 申明一個時間變量來保存時間 */
int ret; /* 保存返回值 */
FD_ZERO(&rdfds); /* 用select函數之前先把集合清零 */
FD_SET(socket, &rdfds); /* 把要檢測的句柄socket加入到集合裏 */
tv.tv_sec = 1;
tv.tv_usec = 500; /* 設置select等待的最大時間爲1秒加500微秒 */
ret = select(socket + 1, &rdfds, NULL, NULL, &tv); /* 檢測我們上面設置到集合rdfds裏的句柄是否有可讀信息 */
if(ret < 0)
perror("select");/* 這說明select函數出錯 */
else if(ret == 0)
printf("超時/n"); /* 說明在我們設定的時間值1秒加500毫秒的時間內,socket的狀態沒有發生變化 */
else
{ /* 說明等待時間還未到1秒加500毫秒,socket的狀態發生了變化 */
printf("ret=%d/n", ret); /* ret這個返回值記錄了發生狀態變化的句柄的數目,由於我們只監視了socket這一個句柄,所以這裏一定ret=1,如果同時有多個句柄發
生變化返回的就是句柄的總和了 */
/* 這裏我們就應該從socket這個句柄裏讀取數據了,因爲select函數已經告訴我們這個句柄裏有數據可讀 */
if(FD_ISSET(socket, &rdfds)) { /* 先判斷一下socket這外被監視的句柄是否真的變成可讀的了 */
/* 讀取socket句柄裏的數據 */
recv(...);
}
}
注意select函數的第一個參數,是所有加入集合的句柄值的最大那個值還要加1。比如我們創建了3個句柄:
int sa, sb, sc;
sa = socket(...); /* 分別創建3個句柄並連接到服務器上 */
connect(sa,...);
sb = socket(...);
connect(sb,...);
sc = socket(...);
connect(sc,...);
FD_SET(sa, &rdfds);/* 分別把3個句柄加入讀監視集合裏去 */
FD_SET(sb, &rdfds);
FD_SET(sc, &rdfds);
在使用select函數之前,一定要找到3個句柄中的最大值是哪個,我們一般定義一個變量來保存最大值,取得最大socket值如下:
int maxfd = 0;
if(sa > maxfd)
maxfd = sa;
if(sb > maxfd)
maxfd = sb;
if(sc > maxfd)
maxfd = sc;
然後調用select函數:
ret = select(maxfd + 1, &rdfds, NULL, NULL, &tv); /* 注意是最大值還要加1 */
同樣的道理,如果我們要檢測用戶是否按了鍵盤進行輸入,我們就應該把標準輸入0這個句柄放到select裏來檢測,如下:
FD_ZERO(&rdfds);
FD_SET(0, &rdfds);
tv.tv_sec = 1;
tv.tv_usec = 0;
ret = select(1, &rdfds, NULL, NULL, &tv); /* 注意是最大值還要加1 */
if(ret < 0)
perror("select");/* 出錯 */
else if(ret == 0)
printf("超時/n"); /* 在我們設定的時間tv內,用戶沒有按鍵盤 */
else { /* 用戶有按鍵盤,要讀取用戶的輸入 */
scanf("%s", buf); }
+++++++++++++++++++++++++++++++++++++++++++++
Linux設備驅動編程之阻塞與非阻塞
時間:2006-10-23 11:04:02 來源:天極軟件 作者:宋寶華
阻塞操作是指,在執行設備操作時,若不能獲得資源,則進程掛起直到滿足可操作的條件再進行操作。非阻塞操作的進程在不能進行設備操作時,並不掛起。被掛起的進程
進入sleep狀態,被從調度器的運行隊列移走,直到等待的條件被滿足。
關於上述例程,我們補充說一點,如果將驅動程序中的read函數改爲:
static ssize_t globalvar_read(struct file *filp, char *buf, size_t len, loff_t *off)
{
//獲取信號量:可能阻塞
if (down_interruptible(&sem))
{
return - ERESTARTSYS;
}
//等待數據可獲得:可能阻塞
if (wait_event_interruptible(outq, flag != 0))
{
return - ERESTARTSYS;
}
flag = 0;
//臨界資源訪問
if (copy_to_user(buf, &global_var, sizeof(int)))
{
up(&sem);
return - EFAULT;
}
//釋放信號量
up(&sem);
return sizeof(int);
}
即交換wait_event_interruptible(outq, flag != 0)和down_interruptible(&sem)的順序,這個驅動程序將變得不可運行。實際上,當兩個可能要阻塞的事件同
時出現時,即兩個wait_event或down擺在一起的時候,將變得非常危險,死鎖的可能性很大,這個時候我們要特別留意它們的出現順序。當然,我們應該儘可能地避免
這種情況的發生!
還有一個與設備阻塞與非阻塞訪問息息相關的論題,即select和poll,select和poll的本質一樣,前者在BSD Unix中引入,後者在System V中引入。poll和select用
於查詢設備的狀態,以便用戶程序獲知是否能對設備進行非阻塞的訪問,它們都需要設備驅動程序中的poll函數支持。
驅動程序中poll函數中最主要用到的一個API是poll_wait,其原型如下:
void poll_wait(struct file *filp, wait_queue_heat_t *queue, poll_table * wait);
poll_wait函數所做的工作是把當前進程添加到wait參數指定的等待列表(poll_table)中。下面我們給globalvar的驅動添加一個poll函數:
static unsigned int globalvar_poll(struct file *filp, poll_table *wait)
{
unsigned int mask = 0;
poll_wait(filp, &outq, wait);
//數據是否可獲得?
if (flag != 0)
{
mask |= POLLIN | POLLRDNORM; //標示數據可獲得
}
return mask;
}
需要說明的是,poll_wait函數並不阻塞,程序中poll_wait(filp, &outq, wait)這句話的意思並不是說一直等待outq信號量可獲得,真正的阻塞動作是上層的
select/poll函數中完成的。select/poll會在一個循環中對每個需要監聽的設備調用它們自己的poll支持函數以使得當前進程被加入各個設備的等待列表。若當前沒有任何
被監聽的設備就緒,則內核進行調度(調用schedule)讓出cpu進入阻塞狀態,schedule返回時將再次循環檢測是否有操作可以進行,如此反覆;否則,若有任意一個設
備就緒,select/poll都立即返回。
我們編寫一個用戶態應用程序來測試改寫後的驅動。程序中要用到BSD Unix中引入的select函數,其原型爲:
int select(int numfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
其中readfds、writefds、exceptfds分別是被select()監視的讀、寫和異常處理的文件描述符集合,numfds的值是需要檢查的號碼最高的文件描述符加1。
timeout參數是一個指向struct timeval類型的指針,它可以使select()在等待timeout時間後若沒有文件描述符準備好則返回。struct timeval數據結構爲:
struct timeval
{
int tv_sec; /* seconds */
int tv_usec; /* microseconds */
};
除此之外,我們還將使用下列API:
FD_ZERO(fd_set *set)――清除一個文件描述符集;
FD_SET(int fd,fd_set *set)――將一個文件描述符加入文件描述符集中;
FD_CLR(int fd,fd_set *set)――將一個文件描述符從文件描述符集中清除;
FD_ISSET(int fd,fd_set *set)――判斷文件描述符是否被置位。
下面的用戶態測試程序等待/dev/globalvar可讀,但是設置了5秒的等待超時,若超過5秒仍然沒有數據可讀,則輸出"No data within 5 seconds":
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
main()
{
int fd, num;
fd_set rfds;
struct timeval tv;
fd = open("/dev/globalvar", O_RDWR, S_IRUSR | S_IWUSR);
if (fd != - 1)
{
while (1)
{
//查看globalvar是否有輸入
FD_ZERO(&rfds);
FD_SET(fd, &rfds);
//設置超時時間爲5s
tv.tv_sec = 5;
tv.tv_usec = 0;
select(fd + 1, &rfds, NULL, NULL, &tv);
//數據是否可獲得?
if (FD_ISSET(fd, &rfds))
{
read(fd, &num, sizeof(int));
printf("The globalvar is %d/n", num);
//輸入爲0,退出
if (num == 0)
{
close(fd);
break;
}
}
else
printf("No data within 5 seconds./n");
}
}
else
{
printf("device open failure/n");
}
}
開兩個終端,分別運行程序:一個對globalvar進行寫,一個用上述程序對globalvar進行讀。當我們在寫終端給globalvar輸入一個值後,讀終端立即就能輸出該值
,當我們連續5秒沒有輸入時,"No data within 5 seconds"在讀終端被輸出.