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

  • 創(chuàng)作內(nèi)容快速變現(xiàn)
  • 行業(yè)影響力擴(kuò)散
  • 作品版權(quán)保護(hù)
  • 300W+ 專業(yè)用戶
  • 1.5W+ 優(yōu)質(zhì)創(chuàng)作者
  • 5000+ 長(zhǎng)期合作伙伴
立即加入
  • 正文
    • 6.3  底層文件I/O操作
  • 相關(guān)推薦
  • 電子產(chǎn)業(yè)圖譜
申請(qǐng)入駐 產(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個(gè)函數(shù):open()、read()、write()、lseek()和close()。這些函數(shù)的特點(diǎn)是不帶緩存,直接對(duì)文件(包括設(shè)備)進(jìn)行讀寫(xiě)操作。這些函數(shù)雖然不是ANSI?C的組成部分,但是是POSIX的組成部分。

6.3.1??基本文件操作

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

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

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

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

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

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

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

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

表6.1 open()函數(shù)語(yǔ)法要點(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

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

flag:文件打開(kāi)的方式

O_RDONLY:以只讀方式打開(kāi)文件

O_WRONLY:以只寫(xiě)方式打開(kāi)文件

O_RDWR:以讀寫(xiě)方式打開(kāi)文件

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

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

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

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

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

perms

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

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

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

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

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

函數(shù)返回值

成功:返回文件描述符
失敗:-1

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

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

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

所需頭文件

#include?<unistd.h>

函數(shù)原型

int?close(int?fd)

函數(shù)輸入值

fd:文件描述符

函數(shù)返回值

0:成功
-1:出錯(cuò)

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

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

所需頭文件

#include?<unistd.h>

函數(shù)原型

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

函數(shù)傳入值

fd:文件描述符

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

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

函數(shù)返回值

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

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

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

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

所需頭文件

#include?<unistd.h>

函數(shù)原型

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

函數(shù)傳入值

fd:文件描述符

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

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

函數(shù)返回值

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

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

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

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

所需頭文件

#include?<unistd.h>

#include?<sys/types.h>

函數(shù)原型

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

函數(shù)傳入值

fd:文件描述符

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

whence:

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

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

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

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

函數(shù)返回值

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

?

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

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

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

讀者需要留意的地方是改變每次讀寫(xiě)的緩存大?。▽?shí)例中為1KB)會(huì)怎樣影響運(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??????????????/*?每次讀寫(xiě)緩存大小,影響運(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;

?????

?????/*?以只讀方式打開(kāi)源文件?*/

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

?????

?????/*?以只寫(xiě)方式打開(kāi)目標(biāo)文件,若此文件不存在則創(chuàng)建該文件,?訪問(wèn)權(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);

?????}

?????

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

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

?????

?????/*?讀取源文件的最后10KB數(shù)據(jù)并寫(xiě)到目標(biāo)文件中,每次讀寫(xiě)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ù)返回的文件描述符一定是最小的未用文件描述符。由于一個(gè)進(jìn)程在啟動(dòng)時(shí)自動(dòng)打開(kāi)了0、1、2三個(gè)文件描述符,因此,該文件運(yùn)行結(jié)果中返回的文件描述符為3。讀者可以嘗試在調(diào)用open()函數(shù)之前,加一句close(0),則此后在調(diào)用open()函數(shù)時(shí)返回的文件描述符為0(若關(guān)閉文件描述符1,則在程序執(zhí)行時(shí)會(huì)由于沒(méi)有標(biāo)準(zhǔn)輸出文件而無(wú)法輸出)。

6.3.2??文件鎖

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

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

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

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

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

注意

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

?

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

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

表6.6 fcntl()函數(shù)語(yǔ)法要點(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)過(guò)exec()函數(shù)之后仍保持打開(kāi)狀態(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))。

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

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

函數(shù)返回值

0:成功
-1:出錯(cuò)

這里,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)中每個(gè)變量的取值含義如表6.7所示。

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

l_type

F_RDLCK:讀取鎖(共享鎖)

F_WRLCK:寫(xiě)入鎖(排斥鎖)

F_UNLCK:解鎖

l_stat

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

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

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

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

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

l_len

加鎖區(qū)域的長(zhǎng)度

小技巧

為加鎖整個(gè)文件,通常的方法是將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)體的對(duì)應(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會(huì)被設(shè)置為F_UNLCK,其他域不變;若不可行,則l_pid被設(shè)置為擁有文件鎖的進(jìn)程號(hào),其他域不變。

用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)?/*?該文件已有寫(xiě)入鎖?*/

??????????{

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

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

?????}

?????

?????/*?l_type?可能已被F_GETLK修改過(guò)?*/

?????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í)例是文件寫(xiě)入鎖的測(cè)試用例,這里首先創(chuàng)建了一個(gè)hello文件,之后對(duì)其上寫(xiě)入鎖,最后釋放寫(xiě)入鎖,代碼如下所示:

/*?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;

?????

?????/*?首先打開(kāi)文件?*/

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

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

?????{

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

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

?????}

?????

?????/*?給文件上寫(xiě)入鎖?*/

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

?????getchar();

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

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

?????getchar();

?????close(fd);

?????exit(0);

}

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

終端一:

$?./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

由此可見(jiàn),寫(xiě)入鎖為互斥鎖,同一時(shí)刻只能有一個(gè)寫(xiě)入鎖存在。

接下來(lái)的程序是文件讀取鎖的測(cè)試用例,原理和上面的程序一樣。

/*?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);

}

同樣開(kāi)啟兩個(gè)終端,并首先啟動(dòng)終端一上的程序,其運(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é)果與寫(xiě)入鎖的運(yùn)行結(jié)果相比較,可以看出,讀取鎖為共享鎖,當(dāng)進(jìn)程5009已設(shè)置讀取鎖后,進(jìn)程5010仍然可以設(shè)置讀取鎖。

思考

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

6.3.3??多路復(fù)用

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

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

總的來(lái)說(shuō),I/O處理的模型有5種。

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

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

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

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

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

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

?

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

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

表6.8 select()函數(shù)語(yǔ)法要點(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)視的寫(xiě)文件描述符集合

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

timeout?

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

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

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

函數(shù)返回值

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

思考

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

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

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

FD_ZERO(fd_set?*set)

清除一個(gè)文件描述符集

FD_SET(int?fd,?fd_set?*set)

將一個(gè)文件描述符加入文件描述符集中

FD_CLR(int?fd,?fd_set?*set)

將一個(gè)文件描述符從文件描述符集中清除

FD_ISSET(int?fd,?fd_set?*set)

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

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

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

struct?timeval?

{

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

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

}

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

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

表6.10 poll()函數(shù)語(yǔ)法要點(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)的指針,用于描述需要對(duì)哪些文件的哪種類型的操作進(jìn)行監(jiān)控。

struct?pollfd

{

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

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

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

}

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

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

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

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

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

POLLHUP:與文件的連接被斷開(kāi)了,只限于輸出

POLLNVAL:文件描述符是不合法的,即它并沒(méi)有指向一個(gè)成功打開(kāi)的文件

numfds:需要監(jiān)聽(tīng)的文件個(gè)數(shù),即第一個(gè)參數(shù)所指向的數(shù)組中的元素?cái)?shù)目

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

函數(shù)返回值

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

0:超時(shí);-1:出錯(cuò)

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

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

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

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

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

?????

?????/*首先以只讀非阻塞方式打開(kāi)兩個(gè)管道文件*/

?????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;

?????}

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

?????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)測(cè)試該文件描述符是否準(zhǔn)備就緒,并調(diào)用select函數(shù)對(duì)相關(guān)文件描述符做對(duì)應(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]?=?'