加入星計(jì)劃,您可以享受以下權(quán)益:

  • 創(chuàng)作內(nèi)容快速變現(xiàn)
  • 行業(yè)影響力擴(kuò)散
  • 作品版權(quán)保護(hù)
  • 300W+ 專業(yè)用戶
  • 1.5W+ 優(yōu)質(zhì)創(chuàng)作者
  • 5000+ 長期合作伙伴
立即加入
  • 正文
    • 6.3  底層文件I/O操作
  • 相關(guān)推薦
  • 電子產(chǎn)業(yè)圖譜
申請入駐 產(chǎn)業(yè)圖譜

文件I/O編程之: 底層文件I/O操作

2013/09/13
1
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點(diǎn)資訊討論

?

6.3??底層文件I/O操作

本節(jié)主要介紹文件I/O操作的系統(tǒng)調(diào)用,主要用到5個函數(shù):open()、read()、write()、lseek()和close()。這些函數(shù)的特點(diǎn)是不帶緩存,直接對文件(包括設(shè)備)進(jìn)行讀寫操作。這些函數(shù)雖然不是ANSI?C的組成部分,但是是POSIX的組成部分。

6.3.1??基本文件操作

(1)函數(shù)說明。

open()函數(shù)用于打開或創(chuàng)建文件,在打開或創(chuàng)建文件時可以指定文件的屬性及用戶的權(quán)限等各種參數(shù)。

close()函數(shù)用于關(guān)閉一個被打開的文件。當(dāng)一個進(jìn)程終止時,所有被它打開的文件都由內(nèi)核自動關(guān)閉,很多程序都使用這一功能而不顯示地關(guān)閉一個文件。

read()函數(shù)用于將從指定的文件描述符中讀出的數(shù)據(jù)放到緩存區(qū)中,并返回實(shí)際讀入的字節(jié)數(shù)。若返回0,則表示沒有數(shù)據(jù)可讀,即已達(dá)到文件尾。讀操作從文件的當(dāng)前指針位置開始。當(dāng)從終端設(shè)備文件中讀出數(shù)據(jù)時,通常一次最多讀一行。

write()函數(shù)用于向打開的文件寫數(shù)據(jù),寫操作從文件的當(dāng)前指針位置開始。對磁盤文件進(jìn)行寫操作,若磁盤已滿或超出該文件的長度,則write()函數(shù)返回失敗。

lseek()函數(shù)用于在指定的文件描述符中將文件指針定位到相應(yīng)的位置。它只能用在可定位(可隨機(jī)訪問)文件操作中。管道、套接字和大部分字符設(shè)備文件是不可定位的,所以在這些文件的操作中無法使用lseek()調(diào)用。

(2)函數(shù)格式。

open()函數(shù)的語法格式如表6.1所示。

表6.1 open()函數(shù)語法要點(diǎn)

所需頭文件

#include?<sys/types.h>?/*?提供類型pid_t的定義?*/

#include?<sys/stat.h>
#include?<fcntl.h>

函數(shù)原型

int?open(const?char?*pathname,?int?flags,?int?perms)

函數(shù)傳入值

pathname

被打開的文件名(可包括路徑名)

flag:文件打開的方式

O_RDONLY:以只讀方式打開文件

O_WRONLY:以只寫方式打開文件

O_RDWR:以讀寫方式打開文件

O_CREAT:如果該文件不存在,就創(chuàng)建一個新的文件,并用第三個參數(shù)為其設(shè)置權(quán)限

O_EXCL:如果使用O_CREAT時文件存在,則可返回錯誤消息。這一參數(shù)可測試文件是否存在。此時open是原子操作,防止多個進(jìn)程同時創(chuàng)建同一個文件

O_NOCTTY:使用本參數(shù)時,若文件為終端,那么該終端不會成為調(diào)用open()的那個進(jìn)程的控制終端

O_TRUNC:若文件已經(jīng)存在,那么會刪除文件中的全部原有數(shù)據(jù),并且設(shè)置文件大小為0。

O_APPEND:以添加方式打開文件,在打開文件的同時,文件指針指向文件的末尾,即將寫入的數(shù)據(jù)添加到文件的末尾

perms

被打開文件的存取權(quán)限

可以用一組宏定義:S_I(R/W/X)(USR/GRP/OTH)

其中R/W/X分別表示讀/寫/執(zhí)行權(quán)限

USR/GRP/OTH分別表示文件所有者/文件所屬組/其他用戶

例如,S_IRUSR?|?S_IWUSR表示設(shè)置文件所有者的可讀可寫屬性。八進(jìn)制表示法中600也表示同樣的權(quán)限

函數(shù)返回值

成功:返回文件描述符
失?。?1

在open()函數(shù)中,flag參數(shù)可通過“|”組合構(gòu)成,但前3個標(biāo)志常量(O_RDONLY、O_WRONLY以及O_RDWR)不能相互組合。perms是文件的存取權(quán)限,既可以用宏定義表示法,也可以用八進(jìn)制表示法。

close()函數(shù)的語法格式表6.2所示。

表6.2 close()函數(shù)語法要點(diǎn)

所需頭文件

#include?<unistd.h>

函數(shù)原型

int?close(int?fd)

函數(shù)輸入值

fd:文件描述符

函數(shù)返回值

0:成功
-1:出錯

read()函數(shù)的語法格式如表6.3所示。

表6.3 read()函數(shù)語法要點(diǎn)

所需頭文件

#include?<unistd.h>

函數(shù)原型

ssize_t?read(int?fd,?void?*buf,?size_t?count)

函數(shù)傳入值

fd:文件描述符

buf:指定存儲器讀出數(shù)據(jù)的緩沖區(qū)

count:指定讀出的字節(jié)數(shù)

函數(shù)返回值

成功:讀到的字節(jié)數(shù)
0:已到達(dá)文件尾
-1:出錯

在讀普通文件時,若讀到要求的字節(jié)數(shù)之前已到達(dá)文件的尾部,則返回的字節(jié)數(shù)會小于希望讀出的字節(jié)數(shù)。

write()函數(shù)的語法格式如表6.4所示。

表6.4 write()函數(shù)語法要點(diǎn)

所需頭文件

#include?<unistd.h>

函數(shù)原型

ssize_t?write(int?fd,?void?*buf,?size_t?count)

函數(shù)傳入值

fd:文件描述符

buf:指定存儲器寫入數(shù)據(jù)的緩沖區(qū)

count:指定讀出的字節(jié)數(shù)

函數(shù)返回值

成功:已寫的字節(jié)數(shù)
-1:出錯

在寫普通文件時,寫操作從文件的當(dāng)前指針位置開始。

lseek()函數(shù)的語法格式如表6.5所示。

表6.5 lseek()函數(shù)語法要點(diǎn)

所需頭文件

#include?<unistd.h>

#include?<sys/types.h>

函數(shù)原型

off_t?lseek(int?fd,?off_t?offset,?int?whence)

函數(shù)傳入值

fd:文件描述符

offset:偏移量,每一讀寫操作所需要移動的距離,單位是字節(jié),可正可負(fù)(向前移,向后移)

whence:

當(dāng)前位置的基點(diǎn)

SEEK_SET:當(dāng)前位置為文件的開頭,新位置為偏移量的大小

SEEK_CUR:當(dāng)前位置為文件指針的位置,新位置為當(dāng)前位置加上偏移量

SEEK_END:當(dāng)前位置為文件的結(jié)尾,新位置為文件的大小加上偏移量的大小

函數(shù)返回值

成功:文件的當(dāng)前位移
-1:出錯

?

(3)函數(shù)使用實(shí)例。

下面實(shí)例中的open()函數(shù)帶有3個flag參數(shù):O_CREAT、O_TRUNC和O_WRONLY,這樣就可以對不同的情況指定相應(yīng)的處理方法。另外,這里對該文件的權(quán)限設(shè)置為0600。其源碼如下所示:

下面列出文件基本操作的實(shí)例,基本功能是從一個文件(源文件)中讀取最后10KB數(shù)據(jù)并到另一個文件(目標(biāo)文件)。在實(shí)例中源文件是以只讀方式打開,目標(biāo)文件是以只寫方式打開(可以是讀寫方式)。若目標(biāo)文件不存在,可以創(chuàng)建并設(shè)置權(quán)限的初始值為644,即文件所有者可讀可寫,文件所屬組和其他用戶只能讀。

讀者需要留意的地方是改變每次讀寫的緩存大?。▽?shí)例中為1KB)會怎樣影響運(yùn)行效率。

/*?copy_file.c?*/

#include?<unistd.h>

#include?<sys/types.h>

#include?<sys/stat.h>

#include?<fcntl.h>

#include?<stdlib.h>

#include?<stdio.h>

#define?BUFFER_SIZE????1024??????????????/*?每次讀寫緩存大小,影響運(yùn)行效率*/

#define?SRC_FILE_NAME??"src_file"??/*?源文件名?*/

#define?DEST_FILE_NAME?"dest_file"?/*?目標(biāo)文件名文件名?*/

#define?OFFSE????????10240?????????????/*?復(fù)制的數(shù)據(jù)大小?*/

?????

int?main()

{

?????int?src_file,?dest_file;

?????unsigned?char?buff[BUFFER_SIZE];

?????int?real_read_len;

?????

?????/*?以只讀方式打開源文件?*/

?????src_file?=?open(SRC_FILE_NAME,?O_RDONLY);

?????

?????/*?以只寫方式打開目標(biāo)文件,若此文件不存在則創(chuàng)建該文件,?訪問權(quán)限值為644?*/

?????dest_file?=?open(DEST_FILE_NAME,?

?????????????????????O_WRONLY|O_CREAT,?S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);

?????if?(src_file?<?0?||?dest_file?<?0)

?????{

??????????printf("Open?file?errorn");

??????????exit(1);

?????}

?????

?????/*?將源文件的讀寫指針移到最后10KB的起始位置*/

?????lseek(src_file,?-OFFSET,?SEEK_END);

?????

?????/*?讀取源文件的最后10KB數(shù)據(jù)并寫到目標(biāo)文件中,每次讀寫1KB?*/

?????while?((real_read_len?=?read(src_file,?buff,?sizeof(buff)))?>?0)

?????{

??????????write(dest_file,?buff,?real_read_len);

?????}

?????close(dest_file);

?????close(src_file);?

?????return?0;

}

$?./copy_file

$?ls?-lh??dest_file

-rw-r--r--?1?david?root?10K?14:06?dest_file

注意

open()函數(shù)返回的文件描述符一定是最小的未用文件描述符。由于一個進(jìn)程在啟動時自動打開了0、1、2三個文件描述符,因此,該文件運(yùn)行結(jié)果中返回的文件描述符為3。讀者可以嘗試在調(diào)用open()函數(shù)之前,加一句close(0),則此后在調(diào)用open()函數(shù)時返回的文件描述符為0(若關(guān)閉文件描述符1,則在程序執(zhí)行時會由于沒有標(biāo)準(zhǔn)輸出文件而無法輸出)。

6.3.2??文件鎖

(1)fcntl()函數(shù)說明。

前面的這5個基本函數(shù)實(shí)現(xiàn)了文件的打開、讀寫等基本操作,本小節(jié)將討論的是,在文件已經(jīng)共享的情況下如何操作,也就是當(dāng)多個用戶共同使用、操作一個文件的情況,這時,Linux通常采用的方法是給文件上鎖,來避免共享的資源產(chǎn)生競爭的狀態(tài)。

文件鎖包括建議性鎖和強(qiáng)制性鎖。建議性鎖要求每個上鎖文件的進(jìn)程都要檢查是否有鎖存在,并且尊重已有的鎖。在一般情況下,內(nèi)核和系統(tǒng)都不使用建議性鎖。強(qiáng)制性鎖是由內(nèi)核執(zhí)行的鎖,當(dāng)一個文件被上鎖進(jìn)行寫入操作的時候,內(nèi)核將阻止其他任何文件對其進(jìn)行讀寫操作。采用強(qiáng)制性鎖對性能的影響很大,每次讀寫操作都必須檢查是否有鎖存在。

在Linux中,實(shí)現(xiàn)文件上鎖的函數(shù)有l(wèi)ockf()和fcntl(),其中l(wèi)ockf()用于對文件施加建議性鎖,而fcntl()不僅可以施加建議性鎖,還可以施加強(qiáng)制鎖。同時,fcntl()還能對文件的某一記錄上鎖,也就是記錄鎖。

記錄鎖又可分為讀取鎖和寫入鎖,其中讀取鎖又稱為共享鎖,它能夠使多個進(jìn)程都能在文件的同一部分建立讀取鎖。而寫入鎖又稱為排斥鎖,在任何時刻只能有一個進(jìn)程在文件的某個部分上建立寫入鎖。當(dāng)然,在文件的同一部分不能同時建立讀取鎖和寫入鎖。

注意

fcntl()是一個非常通用的函數(shù),它可以對已打開的文件描述符進(jìn)行各種操作,不僅包括管理文件鎖,還包括獲得和設(shè)置文件描述符和文件描述符標(biāo)志、文件描述符的復(fù)制等很多功能。在本節(jié)中,主要介紹建立記錄鎖的方法。

?

(2)fcntl()函數(shù)格式。

用于建立記錄鎖的fcntl()函數(shù)格式如表6.6所示。

表6.6 fcntl()函數(shù)語法要點(diǎn)

所需頭文件

#include?<sys/types.h>

#include?<unistd.h>

#include?<fcntl.h>

函數(shù)原型

int?fcnt1(int?fd,?int?cmd,?struct?flock?*lock)

函數(shù)傳入值

fd:文件描述符

cmd

F_DUPFD:復(fù)制文件描述符

F_GETFD:獲得fd的close-on-exec標(biāo)志,若標(biāo)志未設(shè)置,則文件經(jīng)過exec()函數(shù)之后仍保持打開狀態(tài)

F_SETFD:設(shè)置close-on-exec標(biāo)志,該標(biāo)志由參數(shù)arg的FD_CLOEXEC位決定

F_GETFL:得到open設(shè)置的標(biāo)志

F_SETFL:改變open設(shè)置的標(biāo)志

F_GETLK:根據(jù)lock參數(shù)值,決定是否上文件鎖

F_SETLK:設(shè)置lock參數(shù)值的文件鎖

F_SETLKW:這是F_SETLK的阻塞版本(命令名中的W表示等待(wait))。

在無法獲取鎖時,會進(jìn)入睡眠狀態(tài);如果可以獲取鎖或者捕捉到信號則會返回

lock:結(jié)構(gòu)為flock,設(shè)置記錄鎖的具體狀態(tài)

函數(shù)返回值

0:成功
-1:出錯

這里,lock的結(jié)構(gòu)如下所示:

struct?flock

{

?????short?l_type;

?????off_t?l_start;

?????short?l_whence;

?????off_t?l_len;

?????pid_t?l_pid;

}

lock結(jié)構(gòu)中每個變量的取值含義如表6.7所示。

表6.7 lock結(jié)構(gòu)變量取值

l_type

F_RDLCK:讀取鎖(共享鎖)

F_WRLCK:寫入鎖(排斥鎖)

F_UNLCK:解鎖

l_stat

相對位移量(字節(jié))

l_whence:相對位移量的起點(diǎn)(同lseek的whence)

SEEK_SET:當(dāng)前位置為文件的開頭,新位置為偏移量的大小

SEEK_CUR:當(dāng)前位置為文件指針的位置,新位置為當(dāng)前位置加上偏移量

SEEK_END:當(dāng)前位置為文件的結(jié)尾,新位置為文件的大小加上偏移量的大小

l_len

加鎖區(qū)域的長度

小技巧

為加鎖整個文件,通常的方法是將l_start設(shè)置為0,l_whence設(shè)置為SEEK_SET,l_len設(shè)置為0。

(3)fcntl()使用實(shí)例

下面首先給出了使用fcntl()函數(shù)的文件記錄鎖功能的代碼實(shí)現(xiàn)。在該代碼中,首先給flock結(jié)構(gòu)體的對應(yīng)位賦予相應(yīng)的值。接著使用兩次fcntl()函數(shù),分別用于判斷文件是否可以上鎖和給相關(guān)文件上鎖,這里用到的cmd值分別為F_GETLK和F_SETLK(或F_SETLKW)。

用F_GETLK命令判斷是否可以進(jìn)行flock結(jié)構(gòu)所描述的鎖操作:若可以進(jìn)行,則flock結(jié)構(gòu)的l_type會被設(shè)置為F_UNLCK,其他域不變;若不可行,則l_pid被設(shè)置為擁有文件鎖的進(jìn)程號,其他域不變。

用F_SETLK和F_SETLKW命令設(shè)置flock結(jié)構(gòu)所描述的鎖操作,后者是前者的阻塞版。

文件記錄鎖功能的源代碼如下所示:

/*?lock_set.c?*/

int?lock_set(int?fd,?int?type)

{

?????struct?flock?old_lock,?lock;

?????lock.l_whence?=?SEEK_SET;

?????lock.l_start?=?0;

?????lock.l_len?=?0;

?????lock.l_type?=?type;

?????lock.l_pid?=?-1;

?????

?????/*?判斷文件是否可以上鎖?*/

?????fcntl(fd,?F_GETLK,?&lock);

?????if?(lock.l_type?!=?F_UNLCK)

?????{

??????????/*?判斷文件不能上鎖的原因?*/

??????????if?(lock.l_type?==?F_RDLCK)?/*?該文件已有讀取鎖?*/

??????????{

???????????????printf("Read?lock?already?set?by?%dn",?lock.l_pid);

??????????}

??????????else?if?(lock.l_type?==?F_WRLCK)?/*?該文件已有寫入鎖?*/

??????????{

???????????????printf("Write?lock?already?set?by?%dn",?lock.l_pid);

??????????}???????????????

?????}

?????

?????/*?l_type?可能已被F_GETLK修改過?*/

?????lock.l_type?=?type;

?????

?????/*?根據(jù)不同的type值進(jìn)行阻塞式上鎖或解鎖?*/

?????if?((fcntl(fd,?F_SETLKW,?&lock))?<?0)

?????{

??????????printf("Lock?failed:type?=?%dn",?lock.l_type);

??????????return?1;

?????}

??????????

?????switch(lock.l_type)

?????{

??????????case?F_RDLCK:

??????????{

??????????printf("Read?lock?set?by?%dn",?getpid());

??????????}

??????????break;

??????????case?F_WRLCK:

??????????{

??????????printf("Write?lock?set?by?%dn",?getpid());

??????????}

??????????break;

??????????case?F_UNLCK:

??????????{

???????????????printf("Release?lock?by?%dn",?getpid());

???????????????return?1;

??????????}

??????????break;

??????????default:

??????????break;

?????}/*?end?of?switch??*/

?????return?0;

}

?

下面的實(shí)例是文件寫入鎖的測試用例,這里首先創(chuàng)建了一個hello文件,之后對其上寫入鎖,最后釋放寫入鎖,代碼如下所示:

/*?write_lock.c?*/

#include?<unistd.h>

#include?<sys/file.h>

#include?<sys/types.h>

#include?<sys/stat.h>

#include?<stdio.h>

#include?<stdlib.h>

#include?"lock_set.c"

int?main(void)

{

?????int?fd;

?????

?????/*?首先打開文件?*/

?????fd?=?open("hello",O_RDWR?|?O_CREAT,?0644);

?????if(fd?<?0)

?????{

??????????printf("Open?file?errorn");

??????????exit(1);

?????}

?????

?????/*?給文件上寫入鎖?*/

?????lock_set(fd,?F_WRLCK);

?????getchar();

?????/*?給文件解鎖?*/

?????lock_set(fd,?F_UNLCK);

?????getchar();

?????close(fd);

?????exit(0);

}

為了能夠使用多個終端,更好地顯示寫入鎖的作用,本實(shí)例主要在PC機(jī)上測試,讀者可將其交叉編譯,下載到目標(biāo)板上運(yùn)行。下面是在PC機(jī)上的運(yùn)行結(jié)果。為了使程序有較大的靈活性,筆者采用文件上鎖后由用戶鍵入一任意鍵使程序繼續(xù)運(yùn)行。建議讀者開啟兩個終端,并且在兩個終端上同時運(yùn)行該程序,以達(dá)到多個進(jìn)程操作一個文件的效果。在這里,筆者首先運(yùn)行終端一,請讀者注意終端二中的第一句。

終端一:

$?./write_lock

write?lock?set?by?4994

release?lock?by?4994

終端二:

$?./write_lock

write?lock?already?set?by?4994

write?lock?set?by?4997

release?lock?by?4997

由此可見,寫入鎖為互斥鎖,同一時刻只能有一個寫入鎖存在。

接下來的程序是文件讀取鎖的測試用例,原理和上面的程序一樣。

/*?fcntl_read.c?*/

#include?<unistd.h>

#include?<sys/file.h>

#include?<sys/types.h>

#include?<sys/stat.h>

#include?<stdio.h>

#include?<stdlib.h>

#include?"lock_set.c"

int?main(void)

{

?????int?fd;

?????fd?=?open("hello",O_RDWR?|?O_CREAT,?0644);

?????if(fd?<?0)

?????{

??????????printf("Open?file?errorn");

??????????exit(1);

?????}

?????

?????/*?給文件上讀取鎖?*/

?????lock_set(fd,?F_RDLCK);

?????getchar();

?????/*?給文件解鎖?*/

?????lock_set(fd,?F_UNLCK);

?????getchar();

?????close(fd);

?????exit(0);

}

同樣開啟兩個終端,并首先啟動終端一上的程序,其運(yùn)行結(jié)果如下所示:

終端一:

$?./read_lock

read?lock?set?by?5009

release?lock?by?5009

終端二:

$?./read_lock

read?lock?set?by?5010

release?lock?by?5010

讀者可以將此結(jié)果與寫入鎖的運(yùn)行結(jié)果相比較,可以看出,讀取鎖為共享鎖,當(dāng)進(jìn)程5009已設(shè)置讀取鎖后,進(jìn)程5010仍然可以設(shè)置讀取鎖。

思考

如果在一個終端上運(yùn)行設(shè)置讀取鎖的程序,則在另一個終端上運(yùn)行設(shè)置寫入鎖的程序,會有什么結(jié)果呢??

6.3.3??多路復(fù)用

(1)函數(shù)說明。

前面的fcntl()函數(shù)解決了文件的共享問題,接下來該處理I/O復(fù)用的情況了。

總的來說,I/O處理的模型有5種。

n 阻塞I/O模型:在這種模型下,若所調(diào)用的I/O函數(shù)沒有完成相關(guān)的功能,則會使進(jìn)程掛起,直到相關(guān)數(shù)據(jù)到達(dá)才會返回。對管道設(shè)備、終端設(shè)備和網(wǎng)絡(luò)設(shè)備進(jìn)行讀寫時經(jīng)常會出現(xiàn)這種情況。

n 非阻塞模型:在這種模型下,當(dāng)請求的I/O操作不能完成時,則不讓進(jìn)程睡眠,而且立即返回。非阻塞I/O使用戶可以調(diào)用不會阻塞的I/O操作,如open()、write()和read()。如果該操作不能完成,則會立即返回出錯(例如:打不開文件)或者返回0(例如:在緩沖區(qū)中沒有數(shù)據(jù)可以讀取或者沒有空間可以寫入數(shù)據(jù))。

n I/O多路轉(zhuǎn)接模型:在這種模型下,如果請求的I/O操作阻塞,且它不是真正阻塞I/O,而是讓其中的一個函數(shù)等待,在這期間,I/O還能進(jìn)行其他操作。本節(jié)要介紹的select()和poll函數(shù)()就是屬于這種模型。

n 信號驅(qū)動I/O模型:在這種模型下,通過安裝一個信號處理程序,系統(tǒng)可以自動捕獲特定信號的到來,從而啟動I/O。這是由內(nèi)核通知用戶何時可以啟動一個I/O操作決定的。

n 異步I/O模型:在這種模型下,當(dāng)一個描述符已準(zhǔn)備好,可以啟動I/O時,進(jìn)程會通知內(nèi)核?,F(xiàn)在,并不是所有的系統(tǒng)都支持這種模型。

可以看到,select()和poll()的I/O多路轉(zhuǎn)接模型是處理I/O復(fù)用的一個高效的方法。它可以具體設(shè)置程序中每一個所關(guān)心的文件描述符的條件、希望等待的時間等,從select()和poll()函數(shù)返回時,內(nèi)核會通知用戶已準(zhǔn)備好的文件描述符的數(shù)量、已準(zhǔn)備好的條件等。通過使用select()和poll()函數(shù)的返回結(jié)果,就可以調(diào)用相應(yīng)的I/O處理函數(shù)。

?

(2)函數(shù)格式。

select()函數(shù)的語法格式如表6.8所示。

表6.8 select()函數(shù)語法要點(diǎn)

所需頭文件

#include?<sys/types.h>

#include?<sys/time.h>

#include?<unistd.h>

函數(shù)原型

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

fd_set?*exeptfds,?struct?timeval?*timeout)

函數(shù)傳入值

numfds:該參數(shù)值為需要監(jiān)視的文件描述符的最大值加1

readfds:由select()監(jiān)視的讀文件描述符集合

writefds:由select()監(jiān)視的寫文件描述符集合

exeptfds:由select()監(jiān)視的異常處理文件描述符集合

timeout?

NULL:永遠(yuǎn)等待,直到捕捉到信號或文件描述符已準(zhǔn)備好為止

具體值:struct?timeval類型的指針,若等待了timeout時間還沒有檢測到任何文件描符準(zhǔn)備好,就立即返回

0:從不等待,測試所有指定的描述符并立即返回

函數(shù)返回值

大于0:成功,返回準(zhǔn)備好的文件描述符的數(shù)目
0:超時;-1:出錯

思考

請讀者考慮一下如何確定被監(jiān)視的文件描述符的最大值?

可以看到,select()函數(shù)根據(jù)希望進(jìn)行的文件操作對文件描述符進(jìn)行了分類處理,這里,對文件描述符的處理主要涉及4個宏函數(shù),如表6.9所示。

表6.9 select()文件描述符處理函數(shù)

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)

如果文件描述符fd為fd_set集中的一個元素,則返回非零值,可以用于調(diào)用select()之后測試文件描述符集中的文件描述符是否有變化

一般來說,在使用select()函數(shù)之前,首先使用FD_ZERO()和FD_SET()來初始化文件描述符集,在使用了select()函數(shù)時,可循環(huán)使用FD_ISSET()來測試描述符集,在執(zhí)行完對相關(guān)文件描述符的操作之后,使用FD_CLR()來清除描述符集。

另外,select()函數(shù)中的timeout是一個struct?timeval類型的指針,該結(jié)構(gòu)體如下所示:

struct?timeval?

{

????????long?tv_sec;?/*?秒?*/

????????long?tv_unsec;?/*?微秒?*/

}

可以看到,這個時間結(jié)構(gòu)體的精確度可以設(shè)置到微秒級,這對于大多數(shù)的應(yīng)用而言已經(jīng)足夠了。

poll()函數(shù)的語法格式如表6.10所示。

表6.10 poll()函數(shù)語法要點(diǎn)

所需頭文件

#include?<sys/types.h>

#include?<poll.h>

函數(shù)原型

int?poll(struct?pollfd?*fds,?int?numfds,?int?timeout)

函數(shù)傳入值

fds:struct?pollfd結(jié)構(gòu)的指針,用于描述需要對哪些文件的哪種類型的操作進(jìn)行監(jiān)控。

struct?pollfd

{

int????fd;???????/*?需要監(jiān)聽的文件描述符?*/

short??events;????/*?需要監(jiān)聽的事件?*/

short??revents;???/*?已發(fā)生的事件?*/

}

events成員描述需要監(jiān)聽哪些類型的事件,可以用以下幾種標(biāo)志來描述。

POLLIN:文件中有數(shù)據(jù)可讀,下面實(shí)例中使用到了這個標(biāo)志

POLLPRI::文件中有緊急數(shù)據(jù)可讀

POLLOUT:可以向文件寫入數(shù)據(jù)

POLLERR:文件中出現(xiàn)錯誤,只限于輸出

POLLHUP:與文件的連接被斷開了,只限于輸出

POLLNVAL:文件描述符是不合法的,即它并沒有指向一個成功打開的文件

numfds:需要監(jiān)聽的文件個數(shù),即第一個參數(shù)所指向的數(shù)組中的元素數(shù)目

timeout:表示poll阻塞的超時時間(毫秒)。如果該值小于等于0,則表示無限等待

函數(shù)返回值

成功:返回大于0的值,表示事件發(fā)生的pollfd結(jié)構(gòu)的個數(shù)?

0:超時;-1:出錯

(3)使用實(shí)例。

由于多路復(fù)用通常用于I/O操作可能會被阻塞的情況,而對于可能會有阻塞I/O的管道、網(wǎng)絡(luò)編程,本書到現(xiàn)在為止還沒有涉及。這里通過手動創(chuàng)建(用mknod命令)兩個管道文件,重點(diǎn)說明如何使用兩個多路復(fù)用函數(shù)。

在本實(shí)例中,分別用select()函數(shù)和poll()函數(shù)實(shí)現(xiàn)同一個功能,以下功能說明是以select()函數(shù)為例描述的。

本實(shí)例中主要實(shí)現(xiàn)通過調(diào)用select()函數(shù)來監(jiān)聽3個終端的輸入(分別重定向到兩個管道文件的虛擬終端以及主程序所運(yùn)行的虛擬終端),并分別進(jìn)行相應(yīng)的處理。在這里我們建立了一個select()函數(shù)監(jiān)視的讀文件描述符集,其中包含3個文件描述符,分別為一個標(biāo)準(zhǔn)輸入文件描述符和兩個管道文件描述符。通過監(jiān)視主程序的虛擬終端標(biāo)準(zhǔn)輸入來實(shí)現(xiàn)程序的控制(例如:程序結(jié)束);以兩個管道作為數(shù)據(jù)輸入,主程序?qū)膬蓚€管道讀取的輸入字符串寫入到標(biāo)準(zhǔn)輸出文件(屏幕)。

為了充分表現(xiàn)select()調(diào)用的功能,在運(yùn)行主程序的時候,需要打開3個虛擬終端:首先用mknod命令創(chuàng)建兩個管道in1和in2。接下來,在兩個虛擬終端上分別運(yùn)行cat>in1和cat>in2。同時在第三個虛擬終端上運(yùn)行主程序。在程序運(yùn)行之后,如果在兩個管道終端上輸入字符串,則可以觀察到同樣的內(nèi)容將在主程序的虛擬終端上逐行顯示。如果想結(jié)束主程序,只要在主程序的虛擬終端下輸入以‘q’或‘Q’字符開頭的字符串即可。如果三個文件一直在無輸入狀態(tài)中,則主程序一直處于阻塞狀態(tài)。為了防止無限期的阻塞,在select程序中設(shè)置超時值(本實(shí)例中設(shè)置為60s),當(dāng)無輸入狀態(tài)持續(xù)到超時值時,主程序主動結(jié)束運(yùn)行并退出。而poll程序中依然無限等待,當(dāng)然poll()函數(shù)也可以設(shè)置超時參數(shù)。

該程序的流程圖如圖6.2所示。

圖6.2??select實(shí)例流程圖

?

使用select()函數(shù)實(shí)現(xiàn)的代碼如下所示:

/*?multiplex_select?*/

#include?<fcntl.h>

#include?<stdio.h>

#include?<unistd.h>

#include?<stdlib.h>

#include?<time.h>

#include?<errno.h>

#define?MAX_BUFFER_SIZE?????1024???????????????/*?緩沖區(qū)大小*/

#define?IN_FILES??????????????3??????????????????/*?多路復(fù)用輸入文件數(shù)目*/

#define?TIME_DELAY????????????60?????????????????/*?超時值秒數(shù)?*/

#define?MAX(a,?b)?????????????((a?>?b)?(a):(b))

int?main(void)

{

?????int?fds[IN_FILES];

?????char?buf[MAX_BUFFER_SIZE];

?????int?i,?res,?real_read,?maxfd;

?????struct?timeval?tv;

?????fd_set?inset,tmp_inset;

?????

?????/*首先以只讀非阻塞方式打開兩個管道文件*/

?????fds[0]?=?0;

?????

?????if((fds[1]?=?open?("in1",?O_RDONLY|O_NONBLOCK))?<?0)

?????{

??????????printf("Open?in1?errorn");

??????????return?1;

?????}

??????????????

?????if((fds[2]?=?open?("in2",?O_RDONLY|O_NONBLOCK))?<?0)

?????{

??????????printf("Open?in2?errorn");

??????????return?1;

?????}

?????/*取出兩個文件描述符中的較大者*/

?????maxfd?=?MAX(MAX(fds[0],?fds[1]),?fds[2]);

?????/*初始化讀集合inset,并在讀集合中加入相應(yīng)的描述集*/

?????FD_ZERO(&inset);?

?????for?(i?=?0;?i?<?IN_FILES;?i++)

?????{

?????????FD_SET(fds[i],?&inset);

?????}

?????FD_SET(0,?&inset);

?????tv.tv_sec?=?TIME_DELAY;

?????tv.tv_usec?=?0;

?????

?????/*循環(huán)測試該文件描述符是否準(zhǔn)備就緒,并調(diào)用select函數(shù)對相關(guān)文件描述符做對應(yīng)操作*/

?????while(FD_ISSET(fds[0],&inset)?

????????????||?FD_ISSET(fds[1],&inset)?||?FD_ISSET(fds[2],?&inset))

?????{?

??????????/*?文件描述符集合的備份,?這樣可以避免每次進(jìn)行初始化?*/

??????????tmp_inset?=?inset;?

??????????res?=?select(maxfd?+?1,?&tmp_inset,?NULL,?NULL,?&tv);

??????????

??????????switch(res)

??????????{

??????????case?-1:

??????????????{

???????????????????printf("Select?errorn");

???????????????????return?1;

??????????????}

??????????????break;

???????????????

???????????????case?0:?/*?Timeout?*/

???????????????{

???????????????????printf("Time?outn");

???????????????????return?1;

??????????????}??????????

??????????????break;

???????????????

???????????????default:

???????????????{

???????????????????for?(i?=?0;?i?<?IN_FILES;?i++)

???????????????????{

????????????????????????f?(FD_ISSET(fds[i],?&tmp_inset))

????????????????????????{

????????????????????????????memset(buf,?0,?MAX_BUFFER_SIZE);

????????????????????????????real_read?=?read(fds[i],?buf,?MAX_BUFFER_SIZE);

????????????????????

????????????????????????????if?(real_read?<?0)

????????????????????????????{

?????????????????????????????????if?(errno?!=?EAGAIN)

?????????????????????????????????{

?????????????????????????????????????return?1;

?????????????????????????????????}

????????????????????????????}

?????????????????????????????else?if?(!real_read)

?????????????????????????????{

?????????????????????????????????close(fds[i]);

?????????????????????????????????FD_CLR(fds[i],?&inset);

????????????????????????????}

????????????????????????????else

????????????????????????????{

?????????????????????????????????if?(i?==?0)

?????????????????????????????????{/*?主程序終端控制?*/

??????????????????????????????????????if?((buf[0]?==?'q')?||?(buf[0]?==?'Q'))

??????????????????????????????????????{

???????????????????????????????????????????return?1;

??????????????????????????????????????}

?????????????????????????????????}

?????????????????????????????????else

?????????????????????????????????{/*?顯示管道輸入字符串?*/

??????????????????????????????????????buf[real_read]?=?'