linux基礎(chǔ)復(fù)習(xí)(6)文件I/O操作
不帶緩存的文件I/O 操作,主要用到5 個函數(shù):open、read、write、lseek和close。這里的不帶緩存是指每一個函數(shù)都只調(diào)用系統(tǒng)中的一個函數(shù)(不理解這句話的含義)。這些函數(shù)雖然不是ANSI C的組成部分,但是是POSIX 的組成部分。
open函數(shù)語法要點(diǎn)
|-- #i nclude // 提供類型pid_t的定義
所需頭文件----|-- #i nclude
|-- #i nclude
函數(shù)原型 int open(const char *pathname,flags,int perms)
pathname 被打開的文件名(可包括路徑名)
O_RDONLY:只讀方式打開文件
O_WRONLY:可寫方式打開文件
O_RDWR:讀寫方式打開文件
O_CREAT:如果該文件不存在,就創(chuàng)建一個新的文件,并用第三個參數(shù)為其設(shè)置權(quán)限
O_EXCL:如果使用O_CREAT時文件存在,則可返回錯誤消息。這一
函數(shù)傳入值 參數(shù)可測試文件是否存在
O_NOCTTY:使用本參數(shù)時,如文件為終端
終端不可用open()系統(tǒng)調(diào)用的那個進(jìn)程的控制終端
O_TRUNC:如文件已經(jīng)存在,并且以只讀或只寫成功打開,那么會先
全部刪除文件中原有數(shù)據(jù)
O+APPEND:以添加方式打開文件,在打開文件的同時,文件指針指
向文件的末尾
perms 被打開文件的存取權(quán)限,為8進(jìn)制表示法
函數(shù)返回值 成功:返回文件描述符
失?。?1
補(bǔ)述:文件描述符
對于Linux 而言,所有對設(shè)備和文件的操作都使用文件描述符來進(jìn)行的。文件描述符
是一個非負(fù)的整數(shù),它是一個索引值,并指向內(nèi)核中每個進(jìn)程打開文件的記錄表。當(dāng)打開一
個現(xiàn)存文件或創(chuàng)建一個新文件時,內(nèi)核就向進(jìn)程返回一個文件描述符;當(dāng)需要讀寫文件時,
也需要把文件描述符作為參數(shù)傳遞給相應(yīng)的函數(shù)。
通常,一個進(jìn)程啟動時,都會打開3 個文件:標(biāo)準(zhǔn)輸入、標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)出錯處理。這
3 個文件分別對應(yīng)文件描述符為0、1 和2(也就是宏替換STDIN_FILENO、STDOUT_FILENO
和STDERR_FILENO)。
基于文件描述符的I/O 操作雖然不能移植到類Linux 以外的系統(tǒng)上去(如Windows),但它
往往是實(shí)現(xiàn)某些I/O操作的惟一途徑,如Linux中低級文件操作函數(shù)、多路I/O、TCP/IP套接字
編程接口等。同時,它們也很好地兼容POSIX標(biāo)準(zhǔn),因此,可以很方便地移植到任何POSIX平
臺上。基于文件描述符的I/O操作是Linux中最常用的操作之一。
read函數(shù)語法要點(diǎn)
所需頭文件 #i nclude
函數(shù)原型 ssize_t read(int fd,void *buf,size_t count)
fd:文件描述符
函數(shù)傳入值 buf:指定存儲器讀出數(shù)據(jù)的緩函數(shù)傳入值 沖區(qū)
count:指定讀出的字節(jié)數(shù)
成功:讀到的字節(jié)數(shù)
函數(shù)返回值 0:已到達(dá)文件尾
-1:出錯
write函數(shù)語法要點(diǎn)
所需頭文件 #i nclude
函數(shù)原型 ssize_t write(int fd,void *buf,size_t count)
fd:文件描述符
函數(shù)傳入值 buf:指定存儲器寫入數(shù)據(jù)的緩函數(shù)傳入值 沖區(qū)
count:指定讀出的字節(jié)數(shù)
函數(shù)返回值 成功:已寫的字節(jié)數(shù)
-1:出錯
lseek函數(shù)語法要點(diǎn)
所需頭文件 #i nclude
#i nclude
函數(shù)原型 off_t lseek(int fd,off_t offset,int whence)
fd:文件描述符
函數(shù)傳入值 offset:偏移量,每一讀寫操作所需要移動的距離
單位是字節(jié)的數(shù)量,可正可負(fù)(向前移,向后移)
whence: SEEK_SET:當(dāng)前位置為文件的開頭,新位置為偏移量的大小
當(dāng)前位置 SEEK_CUR:當(dāng)前位置為文件指針的位置,新位置為當(dāng)前位置加上偏移
的基點(diǎn) SEEK_END:當(dāng)前位置為文件的結(jié)尾,新位置為文件的大小加上偏移
函數(shù)返回值 成功:文件的當(dāng)前位移
-1:出錯
/*打開,關(guān)閉,讀寫文件.c*/
#i nclude unistd.h>
#i nclude sys/types.h>
#i nclude sys/stat.h>
#i nclude fcntl.h>
#i nclude stdlib.h>
#i nclude stdio.h>
int main(void)
{
int fd; //文件描述符
int i,size,len;
char *buf="Writing to this file!";
char buf_r[10];
len = strlen(buf);
/*調(diào)用open函數(shù),以可讀寫的方式打開,注意選項(xiàng)可以用“|”符號連接*/
if((fd = open("/tmp/hello.c", O_CREAT | O_TRUNC | O_WRONLY , 0600 ))0){
perror("open:");
exit(1);
}
else{
printf("Open file: hello.c %d\n",fd);
}
/*調(diào)用write函數(shù),將buf中的內(nèi)容寫入到打開的文件中*/
if((size = write( fd, buf, len)) 0){
perror("write:");
exit(1);
}
else
printf("Write:%s\n",buf);
/*調(diào)用lsseek函數(shù)將文件指針移到文件起始,并讀出文件中的10個字節(jié)*/
lseek( fd, 0, SEEK_SET );
if((size = read( fd, buf_r, 10))0){
perror("read:");
exit(1);
}
else
printf("read form file:%s\n",buf_r);
if( close(fd) 0 ){
perror("close:");
exit(1);
}
else
printf("Close hello.c\n");
exit(0);
}
當(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)ock和fcntl,其中flock用于對文件施加建議性鎖,而fcntl不僅可以施加建議性鎖,還可以施加強(qiáng)制鎖。同時,fcntl還能對文件的某一記錄進(jìn)行[!--empirenews.page--]
上鎖,也就是記錄鎖。記錄鎖又可分為讀取鎖和寫入鎖,其中讀取鎖又稱為共享鎖,它能夠使多個進(jìn)程都能在
文件的同一部分建立讀取鎖。而寫入鎖又稱為排斥鎖,在任何時刻只能有一個進(jìn)程在文件的某個部分上建立寫入鎖。當(dāng)然,在文件的同一部分不能同時建立讀取鎖和寫入鎖。
fcntl函數(shù)格式
fcntl函數(shù)可以改變已經(jīng)打開文件的性質(zhì)。
#i nclude
#i nclude
#i nclude
int fcntl(int filedes, int cmd, ... ) ;
返回:若成功則依賴于cmd(見下),若出錯為- 1。
f c n t l函數(shù)有五種功能:
n 復(fù)制一個現(xiàn)存的描述符, 新文件描述符作為函數(shù)值返(c m d=F_DUPFD)。
n 獲得/設(shè)置文件描述符標(biāo)記,對應(yīng)于filedes 的文件描述符標(biāo)志作為函數(shù)值返回.(c m d = F_GETFD或F_SETFD)。
n 獲得/設(shè)置文件狀態(tài)標(biāo)志,對應(yīng)于filedes 的文件狀態(tài)標(biāo)志作為函數(shù)值返回。(c m d = F_GETFL或F_SETFL)。
n 獲得/設(shè)置異步I / O有權(quán)(c m d = F_GETOWN或F_SETOWN)。
n 獲得/設(shè)置記錄鎖(c m d = F_SETLK , F_SETLKW)。
關(guān)于加鎖和解鎖區(qū)域的說明還要注意下列各點(diǎn):
l 該區(qū)域可以在當(dāng)前文件尾端處開始或越過其尾端處開始,但是不能在文件起始位置之前開始或越過該起始位置。
l 如若l_len為0,則表示鎖的區(qū)域從其起點(diǎn)(由l_start和l_whence決定)開始直至最大可能位置為止。也就是不管添寫到該文件中多少數(shù)據(jù),它都處于鎖的范圍。
l 為了鎖整個文件,通常的方法是將l_start說明為0,l_whence說明為SEEK_SET,l_len說明為0。
實(shí)例:
/*fcntl_write.c測試文件寫入鎖主函數(shù)部分*/
#i nclude unistd.h>
#i nclude sys/file.h>
#i nclude sys/types.h>
#i nclude sys/stat.h>
#i nclude stdio.h>
#i nclude stdlib.h>
/*lock_set函數(shù)*/
void lock_set(int fd, int type)
{
struct flock lock;
lock.l_whence = SEEK_SET;//賦值lock結(jié)構(gòu)體
lock.l_start = 0;
lock.l_len =0;
while(1)
{
lock.l_type = type;
/*根據(jù)不同的type值給文件上鎖或解鎖*/
if((fcntl(fd, F_SETLK, &lock)) == 0)
{
if( lock.l_type == F_RDLCK )
printf("read lock set by %d\n",getpid());
else if( lock.l_type == F_WRLCK )
printf("write lock set by %d\n",getpid());
else if( lock.l_type == F_UNLCK )
printf("release lock by %d\n",getpid());
return;
}
/*判斷文件是否可以上鎖*/
fcntl(fd, F_GETLK,&lock);
/*判斷文件不能上鎖的原因*/
if(lock.l_type != F_UNLCK)
{
/*/該文件已有寫入鎖*/
if( lock.l_type == F_RDLCK )
printf("read lock already set by %d\n",lock.l_pid);
/*該文件已有讀取鎖*/
else if( lock.l_type == F_WRLCK )
printf("write lock already set by %d\n",lock.l_pid);
getchar();
}
}
}
int main(void)
{
int fd;
/*首先打開文件*/
fd=open("hello",O_RDWR | O_CREAT, 0666);
if(fd 0)
{
perror("open");
exit(1);
}
/*給文件上寫入鎖*/
lock_set(fd, F_WRLCK);
getchar();
/*給文件接鎖*/
lock_set(fd, F_UNLCK);
getchar();
close(fd);
exit(0);
}
開兩個終端分別運(yùn)行,可看到先運(yùn)行的那個終端,成功上鎖,后運(yùn)行的那個無效??梢妼懭腈i是互斥鎖,一個時候只能有一個寫入鎖存在
select 實(shí)現(xiàn)I/O復(fù)用
I/O處理的五種模型
① 阻塞I/O模型:若所調(diào)用的I/O函數(shù)沒有完成相關(guān)的功能就會使進(jìn)程掛起,直到相關(guān)數(shù)據(jù)到達(dá)才會返回。如:終端、網(wǎng)絡(luò)設(shè)備的訪問。
② 非阻塞模型:當(dāng)請求的I/O操作不能完成時,則不讓進(jìn)程休眠,而且返回一個錯誤。如:open、read、write訪問。
③ I/O多路轉(zhuǎn)接模型:如果請求的I/O 操作阻塞,且他不是真正阻塞I/O,而且讓其中的一個函數(shù)等待,在這期間, I/O還能進(jìn)行其他操作。如:select函數(shù)。
④ 信號驅(qū)動I/O模型:在這種模型下,通過安裝一個信號處理程序,系統(tǒng)可以自動捕獲特定信號的到來,從而啟動I/O。
⑤ 異步I/O模型:在這種模型下,當(dāng)一個描述符已準(zhǔn)備好,可以啟動I/O時,進(jìn)程會通知內(nèi)核。由內(nèi)核進(jìn)行后續(xù)處理,這種用法現(xiàn)在較少。
select函數(shù)
傳向select的參數(shù)告訴內(nèi)核:
(1) 我們所關(guān)心的描述符。
(2) 對于每個描述符我們所關(guān)心的條件(是否讀一個給定的描述符?是否想寫一個給定的描述符?是否關(guān)心一個描述符的異常條件?)。
(3) 希望等待多長時間(可以永遠(yuǎn)等待,等待一個固定量時間,或完全不等待)。
從s e l e c t返回時,內(nèi)核告訴我們:
(1) 已準(zhǔn)備好的描述符的數(shù)量。
(2) 哪一個描述符已準(zhǔn)備好讀、寫或異常條件。
#i nclude /* fd_set data type */
#i nclude /* struct timeval */
#i nclude /* function prototype might be here */
int select (int numfds, fd_set *readfds,
fd_set *writefds, fd_set *exceptfds, struct timeval * timeout) ;
返回:準(zhǔn)備就緒的描述符數(shù),若超時則為0,若出錯則為- 1。
timeout值:
n NULL:永遠(yuǎn)等待,直到捕捉到信號或文件描述符已準(zhǔn)備好為止;
n 具體值: struct timeval 類型的指針,若等待為timeout時間還沒有文件描述符準(zhǔn)備好,就立即返回;
n 0:從不等待,測試所有指定 的描述符并立即返回;
先說明最后一個參數(shù),它指定愿意等待的時間。[!--empirenews.page--]
struct timeval
{
long tv_sec; /* seconds */
long tv_usec; /* and microseconds */
};
select函數(shù)根據(jù)希望進(jìn)行的文件操作對文件描述符進(jìn)行分類處理,這里,對文件描述符的處理主要設(shè)計(jì)4個宏函數(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) 測試該集中的一個給定位是否有變化;
在使用select函數(shù)之前,首先使用FD_ZERO和FD_SET來初始化文件描述符集,并使用select函數(shù)時,可循環(huán)使用FD_ISSET測試描述符集, 在執(zhí)行完成對相關(guān)的文件描述符后, 使用FD_CLR來清除描述符集。
實(shí)例
/*select.c*/
#i nclude fcntl.h>
#i nclude stdio.h>
#i nclude unistd.h>
#i nclude stdlib.h>
#i nclude sys/time.h>
int main(void)
{
int fds[2];
char buf[7];
int i,rc,maxfd;
fd_set inset1,inset2;
struct timeval tv;
if((fds[0] = open ("hello1", O_RDWR|O_CREAT,0666))0)
perror("open hello1");
if((fds[1] = open ("hello2", O_RDWR|O_CREAT,0666))0)
perror("open hello2");
if((rc = write(fds[0],"Hello!\n",7)))
printf("rc=%d\n",rc);
lseek(fds[0],0,SEEK_SET);
maxfd = fds[0]>fds[1] ? fds[0] : fds[1];
//初始化讀集合 inset1,并在讀集合中加入相應(yīng)的描述集
FD_ZERO(&inset1);
FD_SET(fds[0],&inset1);
//初始化寫集合 inset2,并在寫集合中加入相應(yīng)的描述集
FD_ZERO(&inset2);
FD_SET(fds[1],&inset2);
tv.tv_sec=2;
tv.tv_usec=0;
// 循環(huán)測試該文件描述符是否準(zhǔn)備就緒,并調(diào)用 select 函數(shù)對相關(guān)文件描述符做相應(yīng)操作
while(FD_ISSET(fds[0],&inset1)||FD_ISSET(fds[1],&inset2))
{
if(select(maxfd+1,&inset1,&inset2,NULL,&tv)0)
perror("select");
else{
if(FD_ISSET(fds[0],&inset1))
{
rc = read(fds[0],buf,7);
if(rc>0)
{
buf[rc]='\0';
printf("read: %s\n",buf);
}else
perror("read");
}
if(FD_ISSET(fds[1],&inset2))
{
rc = write(fds[1],buf,7);
if(rc>0)
{
buf[rc]='\0';
printf("rc=%d,write: %s\n",rc,buf);
}else
perror("write");
sleep(10);
}
}
}
exit(0);
}