當(dāng)前位置:首頁 > 嵌入式培訓(xùn) > Linux學(xué)習(xí) > linux入門 > Linux串口編程select詳解
今天要和大家分享的Linux學(xué)習(xí)知識點(diǎn)是Linux串口編程select。select系統(tǒng)調(diào)用的的用途是:在一段指定的時(shí)間內(nèi),監(jiān)聽用戶感興趣的文件描述符上可讀、可寫和異常等事件。
Linux串口編程select詳解
①select機(jī)制的優(yōu)勢
為什么會(huì)出現(xiàn)select模型?
先看一下下面的這句代碼:
int iResult = recv(s, buffer,1024);
這是用來接收數(shù)據(jù)的,在默認(rèn)的阻塞模式下的套接字里,recv會(huì)阻塞在那里,直到套接字連接上有數(shù)據(jù)可讀,把數(shù)據(jù)讀到buffer里后recv函數(shù)才會(huì)返回,不然就會(huì)一直阻塞在那里。在單線程的程序里出現(xiàn)這種情況會(huì)導(dǎo)致主線程(單線程程序里只有一個(gè)默認(rèn)的主線程)被阻塞,這樣整個(gè)程序被鎖死在這里,如果永 遠(yuǎn)沒數(shù)據(jù)發(fā)送過來,那么程序就會(huì)被永遠(yuǎn)鎖死。這個(gè)問題可以用多線程解決,但是在有多個(gè)套接字連接的情況下,這不是一個(gè)好的選擇,擴(kuò)展性很差。
再看代碼:
int iResult = ioctlsocket(s, FIOBIO, (unsigned long *)&ul);
iResult = recv(s, buffer,1024);
這一次recv的調(diào)用不管套接字連接上有沒有數(shù)據(jù)可以接收都會(huì)馬上返回。原因就在于我們用ioctlsocket把套接字設(shè)置為非阻塞模式了。不過你跟蹤一下就會(huì)發(fā)現(xiàn),在沒有數(shù)據(jù)的情況下,recv確實(shí)是馬上返回了,但是也返回了一個(gè)錯(cuò)誤:WSAEWOULDBLOCK,意思就是請求的操作沒有成功完成。
看到這里很多人可能會(huì)說,那么就重復(fù)調(diào)用recv并檢查返回值,直到成功為止,但是這樣做效率很成問題,開銷太大。
select模型的出現(xiàn)就是為了解決上述問題。
select模型的關(guān)鍵是使用一種有序的方式,對多個(gè)套接字進(jìn)行統(tǒng)一管理與調(diào)度 。
如上所示,用戶首先將需要進(jìn)行IO操作的socket添加到select中,然后阻塞等待select系統(tǒng)調(diào)用返回。當(dāng)數(shù)據(jù)到達(dá)時(shí),socket被激活,select函數(shù)返回。用戶線程正式發(fā)起read請求,讀取數(shù)據(jù)并繼續(xù)執(zhí)行。
從流程上來看,使用select函數(shù)進(jìn)行IO請求和同步阻塞模型沒有太大的區(qū)別,甚至還多了添加監(jiān)視socket,以及調(diào)用select函數(shù)的額外操作,效率更差。但是,使用select以后大的優(yōu)勢是用戶可以在一個(gè)線程內(nèi)同時(shí)處理多個(gè)socket的IO請求。用戶可以注冊多個(gè)socket,然后不斷地調(diào)用select讀取被激活的socket,即可達(dá)到在同一個(gè)線程內(nèi)同時(shí)處理多個(gè)IO請求的目的。而在同步阻塞模型中,必須通過多線程的方式才能達(dá)到這個(gè)目的。
select流程偽代碼如下:
{
select(socket);
while(1)
{
sockets = select();
for(socket in sockets)
{
if(can_read(socket))
{
read(socket, buffer);
process(buffer);
}
}
}
}
②select相關(guān)API介紹與使用
#include #include #include #include int select(int maxfdp, fd_set *readset, fd_set *writeset, fd_set *exceptset,struct timeval *timeout);
參數(shù)說明:
maxfdp:被監(jiān)聽的文件描述符的總數(shù),它比所有文件描述符集合中的文件描述符的大值大1,因?yàn)槲募枋龇菑?開始計(jì)數(shù)的;
readfds、writefds、exceptset:分別指向可讀、可寫和異常等事件對應(yīng)的描述符集合。
timeout:用于設(shè)置select函數(shù)的超時(shí)時(shí)間,即告訴內(nèi)核select等待多長時(shí)間之后就放棄等待。timeout == NULL 表示等待無限長的時(shí)間
timeval結(jié)構(gòu)體定義如下:
struct timeval
{
long tv_sec; /*秒 */
long tv_usec; /*微秒 */ };
返回值:超時(shí)返回0;失敗返回-1;成功返回大于0的整數(shù),這個(gè)整數(shù)表示就緒描述符的數(shù)目。
以下介紹與select函數(shù)相關(guān)的常見的幾個(gè)宏:
#include int FD_ZERO(int fd, fd_set *fdset); //一個(gè) fd_set類型變量的所有位都設(shè)為 0int FD_CLR(int fd, fd_set *fdset); //清除某個(gè)位時(shí)可以使用int FD_SET(int fd, fd_set *fd_set); //設(shè)置變量的某個(gè)位置位int FD_ISSET(int fd, fd_set *fdset); //測試某個(gè)位是否被置位
select使用范例:
當(dāng)聲明了一個(gè)文件描述符集后,必須用FD_ZERO將所有位置零。之后將我們所感興趣的描述符所對應(yīng)的位置位,操作如下:
fd_set rset; int fd; FD_ZERO(&rset); FD_SET(fd, &rset); FD_SET(stdin, &rset);
然后調(diào)用select函數(shù),擁塞等待文件描述符事件的到來;如果超過設(shè)定的時(shí)間,則不再等待,繼續(xù)往下執(zhí)行。
select(fd+1, &rset, NULL, NULL,NULL);
select返回后,用FD_ISSET測試給定位是否置位:
if(FD_ISSET(fd, &rset)
{
...
//do something
}
下面是一個(gè)簡單的select的使用例子:
#include #include #include #include #include int main(){
fd_set rd; struct timeval tv; int err;
FD_ZERO(&rd);
FD_SET(0,&rd);
tv.tv_sec = 5;
tv.tv_usec = 0;
err = select(1,&rd,NULL,NULL,&tv);
if(err == 0) //超時(shí)
{ printf("select time out!\n");
} else if(err == -1) //失敗
{ printf("fail to select!\n");
} else //成功
{ printf("data is available!\n");
}
return 0;
}
我們運(yùn)行該程序并且隨便輸入一些數(shù)據(jù),程序就提示收到數(shù)據(jù)了。
以上就是Linux串口編程select的相關(guān)知識點(diǎn),更多Linux知識學(xué)習(xí),請關(guān)注Linux系統(tǒng)入門學(xué)習(xí)欄目。