国产久操视频-国产久草视频-国产久热精品-国产久热香蕉在线观看-青青青青娱乐-青青青青在线成人视99

  • 正文
    • 6.3  底層文件I/O操作
  • 相關推薦
申請入駐 產業(yè)圖譜

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

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

?

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

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

6.3.1??基本文件操作

(1)函數說明。

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

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

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

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

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

(2)函數格式。

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

表6.1 open()函數語法要點

所需頭文件

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

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

函數原型

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

函數傳入值

pathname

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

flag:文件打開的方式

O_RDONLY:以只讀方式打開文件

O_WRONLY:以只寫方式打開文件

O_RDWR:以讀寫方式打開文件

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

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

O_NOCTTY:使用本參數時,若文件為終端,那么該終端不會成為調用open()的那個進程的控制終端

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

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

perms

被打開文件的存取權限

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

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

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

例如,S_IRUSR?|?S_IWUSR表示設置文件所有者的可讀可寫屬性。八進制表示法中600也表示同樣的權限

函數返回值

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

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

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

表6.2 close()函數語法要點

所需頭文件

#include?<unistd.h>

函數原型

int?close(int?fd)

函數輸入值

fd:文件描述符

函數返回值

0:成功
-1:出錯

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

表6.3 read()函數語法要點

所需頭文件

#include?<unistd.h>

函數原型

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

函數傳入值

fd:文件描述符

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

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

函數返回值

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

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

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

表6.4 write()函數語法要點

所需頭文件

#include?<unistd.h>

函數原型

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

函數傳入值

fd:文件描述符

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

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

函數返回值

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

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

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

表6.5 lseek()函數語法要點

所需頭文件

#include?<unistd.h>

#include?<sys/types.h>

函數原型

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

函數傳入值

fd:文件描述符

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

whence:

當前位置的基點

SEEK_SET:當前位置為文件的開頭,新位置為偏移量的大小

SEEK_CUR:當前位置為文件指針的位置,新位置為當前位置加上偏移量

SEEK_END:當前位置為文件的結尾,新位置為文件的大小加上偏移量的大小

函數返回值

成功:文件的當前位移
-1:出錯

?

(3)函數使用實例。

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

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

讀者需要留意的地方是改變每次讀寫的緩存大?。▽嵗袨?KB)會怎樣影響運行效率。

/*?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??????????????/*?每次讀寫緩存大小,影響運行效率*/

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

#define?DEST_FILE_NAME?"dest_file"?/*?目標文件名文件名?*/

#define?OFFSE????????10240?????????????/*?復制的數據大小?*/

?????

int?main()

{

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

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

?????int?real_read_len;

?????

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

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

?????

?????/*?以只寫方式打開目標文件,若此文件不存在則創(chuàng)建該文件,?訪問權限值為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數據并寫到目標文件中,每次讀寫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()函數返回的文件描述符一定是最小的未用文件描述符。由于一個進程在啟動時自動打開了0、1、2三個文件描述符,因此,該文件運行結果中返回的文件描述符為3。讀者可以嘗試在調用open()函數之前,加一句close(0),則此后在調用open()函數時返回的文件描述符為0(若關閉文件描述符1,則在程序執(zhí)行時會由于沒有標準輸出文件而無法輸出)。

6.3.2??文件鎖

(1)fcntl()函數說明。

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

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

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

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

注意

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

?

(2)fcntl()函數格式。

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

表6.6 fcntl()函數語法要點

所需頭文件

#include?<sys/types.h>

#include?<unistd.h>

#include?<fcntl.h>

函數原型

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

函數傳入值

fd:文件描述符

cmd

F_DUPFD:復制文件描述符

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

F_SETFD:設置close-on-exec標志,該標志由參數arg的FD_CLOEXEC位決定

F_GETFL:得到open設置的標志

F_SETFL:改變open設置的標志

F_GETLK:根據lock參數值,決定是否上文件鎖

F_SETLK:設置lock參數值的文件鎖

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

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

lock:結構為flock,設置記錄鎖的具體狀態(tài)

函數返回值

0:成功
-1:出錯

這里,lock的結構如下所示:

struct?flock

{

?????short?l_type;

?????off_t?l_start;

?????short?l_whence;

?????off_t?l_len;

?????pid_t?l_pid;

}

lock結構中每個變量的取值含義如表6.7所示。

表6.7 lock結構變量取值

l_type

F_RDLCK:讀取鎖(共享鎖)

F_WRLCK:寫入鎖(排斥鎖)

F_UNLCK:解鎖

l_stat

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

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

SEEK_SET:當前位置為文件的開頭,新位置為偏移量的大小

SEEK_CUR:當前位置為文件指針的位置,新位置為當前位置加上偏移量

SEEK_END:當前位置為文件的結尾,新位置為文件的大小加上偏移量的大小

l_len

加鎖區(qū)域的長度

小技巧

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

(3)fcntl()使用實例

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

用F_GETLK命令判斷是否可以進行flock結構所描述的鎖操作:若可以進行,則flock結構的l_type會被設置為F_UNLCK,其他域不變;若不可行,則l_pid被設置為擁有文件鎖的進程號,其他域不變。

用F_SETLK和F_SETLKW命令設置flock結構所描述的鎖操作,后者是前者的阻塞版。

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

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

?????

?????/*?根據不同的type值進行阻塞式上鎖或解鎖?*/

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

}

?

下面的實例是文件寫入鎖的測試用例,這里首先創(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);

}

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

終端一:

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

}

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

終端一:

$?./read_lock

read?lock?set?by?5009

release?lock?by?5009

終端二:

$?./read_lock

read?lock?set?by?5010

release?lock?by?5010

讀者可以將此結果與寫入鎖的運行結果相比較,可以看出,讀取鎖為共享鎖,當進程5009已設置讀取鎖后,進程5010仍然可以設置讀取鎖。

思考

如果在一個終端上運行設置讀取鎖的程序,則在另一個終端上運行設置寫入鎖的程序,會有什么結果呢??

6.3.3??多路復用

(1)函數說明。

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

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

n 阻塞I/O模型:在這種模型下,若所調用的I/O函數沒有完成相關的功能,則會使進程掛起,直到相關數據到達才會返回。對管道設備、終端設備和網絡設備進行讀寫時經常會出現這種情況。

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

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

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

n 異步I/O模型:在這種模型下,當一個描述符已準備好,可以啟動I/O時,進程會通知內核?,F在,并不是所有的系統都支持這種模型。

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

?

(2)函數格式。

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

表6.8 select()函數語法要點

所需頭文件

#include?<sys/types.h>

#include?<sys/time.h>

#include?<unistd.h>

函數原型

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

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

函數傳入值

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

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

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

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

timeout?

NULL:永遠等待,直到捕捉到信號或文件描述符已準備好為止

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

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

函數返回值

大于0:成功,返回準備好的文件描述符的數目
0:超時;-1:出錯

思考

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

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

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

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集中的一個元素,則返回非零值,可以用于調用select()之后測試文件描述符集中的文件描述符是否有變化

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

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

struct?timeval?

{

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

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

}

可以看到,這個時間結構體的精確度可以設置到微秒級,這對于大多數的應用而言已經足夠了。

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

表6.10 poll()函數語法要點

所需頭文件

#include?<sys/types.h>

#include?<poll.h>

函數原型

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

函數傳入值

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

struct?pollfd

{

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

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

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

}

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

POLLIN:文件中有數據可讀,下面實例中使用到了這個標志

POLLPRI::文件中有緊急數據可讀

POLLOUT:可以向文件寫入數據

POLLERR:文件中出現錯誤,只限于輸出

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

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

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

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

函數返回值

成功:返回大于0的值,表示事件發(fā)生的pollfd結構的個數?

0:超時;-1:出錯

(3)使用實例。

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

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

本實例中主要實現通過調用select()函數來監(jiān)聽3個終端的輸入(分別重定向到兩個管道文件的虛擬終端以及主程序所運行的虛擬終端),并分別進行相應的處理。在這里我們建立了一個select()函數監(jiān)視的讀文件描述符集,其中包含3個文件描述符,分別為一個標準輸入文件描述符和兩個管道文件描述符。通過監(jiān)視主程序的虛擬終端標準輸入來實現程序的控制(例如:程序結束);以兩個管道作為數據輸入,主程序將從兩個管道讀取的輸入字符串寫入到標準輸出文件(屏幕)。

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

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

圖6.2??select實例流程圖

?

使用select()函數實現的代碼如下所示:

/*?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??????????????????/*?多路復用輸入文件數目*/

#define?TIME_DELAY????????????60?????????????????/*?超時值秒數?*/

#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,并在讀集合中加入相應的描述集*/

?????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)測試該文件描述符是否準備就緒,并調用select函數對相關文件描述符做對應操作*/

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

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

?????{?

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

??????????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]?=?'

内丘县| 江达县| 含山县| 百色市| 甘洛县| 嵊泗县| 阿图什市| 白水县| 黄骅市| 蚌埠市| 大竹县| 潞城市| 福泉市| 昌都县| 寻乌县| 丹巴县| 岳西县| 汾阳市| 黄龙县| 西藏| 孝义市| 石家庄市| 政和县| 阿克陶县| 蒙自县| 汶上县| 姚安县| 民丰县| 景德镇市| 朝阳市| 广丰县| 保山市| 勐海县| 嵊泗县| 枝江市| 陆良县| 鄂伦春自治旗| 阿拉善右旗| 十堰市| 海盐县| 虞城县|