linux文件编程(1)—— open、write、read、lseek、阻塞问题
參考:linux文件編程(1)—— 常用API之open、write、read、lseek
作者:丶PURSUING
發布時間: 2021-04-08 22:19:28
網址:https://blog.csdn.net/weixin_44742824/article/details/115209134
目錄
- 一、open、creat、close
- 參數與返回值
- creat
- 二、write
- 參數與返回值
- 使用示例
- 三、read
- 參數與返回值
- 使用示例
- 四、lseek
- 參數與返回值
- 應用示例
- 阻塞問題
一、open、creat、close
文件打開、創建、關閉。
參數與返回值
int open(const char *pathname, int flags)/** 返回值 ** fd>0,文件打開成功且fd為文件標識符 fd<0,文件打開失敗*//** 參數 ** pathname:文件路徑+文件名(若不包含路徑,則默認為當前路徑) flags: (1)O_RDONLY、O_WRONLY、O_RDWR、O_CREAT(2)O_CREAT、O_APPEND、O_TRUNC*/下面對flags(2)這類使用(|)進行附加使用的參數進行額外說明:
(1)O_CREAT:文件若不存在則創建
注意:需要額外說明文件操作權限參數mode
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h>int main() {//嘗試打開當前路徑下的文件file1.cint fd = open("./file1.c",O_RDWR);//打開失敗了if(fd < 0){printf("打開失敗:沒有這個文件\n");//嘗試以可讀可寫的方式創建并打開文件fd = open("./file1.c",O_RDWR|O_CREAT,0600); //可讀4 可寫4 4+2=6if(fd > 0){printf("創建文件并且打開成功\n");}}//關閉文件close(fd);return 0; }對權限0600的解釋:
運行結果:可以看到已經成功創建了file1.c
(2)O_APPEND:寫時加到文件末端(另起一行)
如果不使用這個參數,因為文件打開后光標是位于文件頭的,寫入數據會把原來的數據按長度覆蓋。(本質上就是光標的問題)
(3)O_TRUNC清空原內容后寫入
在每次打開文件寫入之前,先把原有的內容清空后寫入
creat
創建文件的另一種方法(不可用于打開)
int creat(const char *pathname, mode_t mode);/** mode ** S_IRUSR 可讀 宏:4 S_IWUSR 可寫 宏:2 S_IXUSR 可執行 宏:1 S_IRWXU 可讀可寫可執行 宏:7 */簡單使用示例:
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h>int main() {//可讀可寫int fd = creat("./file1.c",S_IRUSR|S_IWUSR);if(fd > 0){printf("文件創建成功\n");}else{printf("同名文件已經存在\n");}close(fd);return 0; }運行結果:
當文件存在時創建失敗,反之成功。可用ls -l查看文件權限:
二、write
參數與返回值
ssize_t write(int fd, const void *buf, size_t count)/** 返回值 ** 成功返回寫入字節數,失敗返回-1*//** 參數 ** fd 被寫入的目標文件對應的文件描述符 buf 將緩沖區buf里面內容寫到文件里面去 count 寫入的字節數*/使用示例
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <string.h>int main() {char* writeBuf = (char* )malloc(128);memset(writeBuf,'\0',128);printf("要往文件里寫什么?\n");//為了避免棧溢出,使用fgets限制獲取的內容長度fgets(writeBuf,128,stdin);//創建文件int fd = open("./test",O_RDWR|O_CREAT,0600);if(fd < 0){printf("文件打開失敗\n");exit(-1);}int retWrite = write(fd,writeBuf,strlen(writeBuf));//此處不能用sizeof(計算的是指針所占用的字節數 //Linux環境下指針占8個字節 只能寫8個字節/字母)if(retWrite > 0){printf("成功寫入字節數:%d\n",retWrite);}else{printf("寫入失敗\n");}close(fd);return 0; }運行結果:成功創建文件并寫入內容
三、read
參數與返回值
ssize_t read(int fd, void *buf, size_t count)/** 文件內容讀到緩沖區buf里面去**/使用示例
下面的程序中簡化對open和write的操作,重點突出read。讀取上面write例子中創建的文件內容。
#include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <string.h>int main() {int fd = open("./test",O_RDWR|O_CREAT,0600);char* readBuf = (char* )malloc(128);memset(readBuf,'\0',128);int retRead = read(fd,readBuf,128);if(retRead < 0){printf("read error\n");}else{printf("讀取的字節數是%d\n內容是%s\n",retRead,readBuf);} //讀取的字節數并不是128個 而是根據實際的文件內容確定close(fd);return 0; }運行結果:
read最需要注意的就是光標的位置,尤其是在write操作后,光標已經到達文件尾部,直接read,就會讀個寂寞。解決辦法是重新打開文件(不建議)使光標回到文件頭。或者使用后面所提到的lseek操作光標。
配合光標操作舉個例子(具體光標介紹在第四節):
#include <stdio.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdlib.h>int main() {int fd;int n_open;int n_write;int n_read;char *Buf="hello ubuntu";fd=open("./file2",O_RDWR);if(fd == -1){printf("沒有該文件,等待創建.....\n");fd=open("./file2",O_RDWR|O_CREAT,0600);if(fd > 0){printf("創建成功并打開,fd=%d\n",fd);}}n_write=write(fd,Buf,strlen(Buf));if(n_write != -1){printf("寫入成功,寫了%d個字節\n",n_write);}//lseek(int fd, off_t offset, int whence);lseek(fd,0,SEEK_SET);//光標指向頭部也可以寫成lseek(fd,-12,SEEK_END/CUR);char *readBuf;//定義一個緩沖區readBuf=(char *)malloc(sizeof(char)*n_write+1);//n_write是寫入的字節數,readBuf大小就是寫入了多少個字節數,加1保險點// ssize_t read(int fd, void *buf, size_t count);n_read=read(fd,readBuf,n_write);//把fd的內容放到readBuf里面進行讀取,讀了n_write個if(n_read != 0){printf("read %d個字節,內容是:%s\n",n_read,readBuf);}close(fd); return 0; }運行結果:
四、lseek
參數與返回值
offt lseek(int fd, offt offset, int whence)/** 參數 ** whence:SEEK_SET 使光標位于文件頭部SEEK_CUR 光標的當前位置SEEK_END 使光標位于文件尾部 offset:相對whence的偏移值,可以是負數表向前移動光標。 *//** 返回值 ** 成功操作時返回從文件開始到到光標位置的字節數,錯誤則-1并且設置error */應用示例
使得光標移動到文件的開始位置:
lseek(fd,0,SEEK_SET);//相對于文件開始位置偏移0,就是在文件頭部咯。lseek也常用于計算文件的大小:返回文件開始到偏移位置(文件尾部)的字節大小
size = lseek(fd,0,SEEK_END);阻塞問題
摘自:https://blog.csdn.net/zjhkobe/article/details/6633446?utm_source=app&app_version=5.1.1&code=app_1562916241&uLinkId=usr1mkqgl919blen
read
函數從打開的設備或文件中讀取數據。
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
返回值:成功返回讀取的字節數,出錯返回-1并設置errno,如果在調read之前已到達文件末尾,則這次read返回0
參數
count
是請求讀取的字節數,讀上來的數據保存在緩沖區buf中,同時文件的當前讀寫位置向后移。注意這個讀寫位置和使用C標準I/O庫時的讀寫位置有可能不同,這個讀寫位置是記在內核中的,而使用C標準I/O庫時的讀寫位置是用戶空間I/O緩沖區中的位置。比如用fgetc讀一個字節,fgetc有可能從內核中預讀1024個字節到I/O緩沖區中,再返回第一個字節,這時該文件在內核中記錄的讀寫位置是1024,而在FILE結構體中記錄的讀寫位置是1。注意返回值類型是ssize_t,表示有符號的size_t,這樣既可以返回正的字節數、0(表示到達文件末尾)也可以返回負值-1(表示出錯)。
read函數返回時,返回值說明了buf中前多少個字節是剛讀上來的。有些情況下,實際讀到的字節數(返回值)會小于請求讀的字節數count,例如:讀常規文件時,在讀到count個字節之前已到達文件末尾。例如,距文件末尾還有30個字節而請求讀100個字節,則read返回30,下次read將返回0。
從終端設備讀,通常以行為單位,讀到換行符就返回了。
從網絡讀,根據不同的傳輸層協議和內核緩存機制,返回值可能小于請求的字節數,后面socket編程部分會詳細講解。
write
函數向打開的設備或文件中寫數據。
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
返回值:成功返回寫入的字節數,出錯返回-1并設置errno寫常規文件時,write的返回值通常等于請求寫的字節數
count,而向終端設備或網絡寫則不一定。
讀常規文件是不會阻塞的,不管讀多少字節,read一定會在有限的時間內返回。從終端設備或網絡讀則不一定,如果從終端輸入的數據沒有換行符,調用read讀終端設備就會阻塞,如果網絡上沒有接收到數據包,調用read從網絡讀就會阻塞,至于會阻塞多長時間也是不確定的,如果一直沒有數據到達就一直阻塞在那里。同樣,寫常規文件是不會阻塞的,而向終端設備或網絡寫則不一定。
現在明確一下阻塞(Block)這個概念。當進程調用一個阻塞的系統函數時,該進程被置于睡眠(Sleep)狀態,這時內核調度其它進程運行,直到該進程等待的事件發生了(比如網絡上接收到數據包,或者調用sleep指定的睡眠時間到了)它才有可能繼續運行。與睡眠狀態相對的是運行(Running)狀態,在Linux內核中,處于運行狀態的進程分為兩種情況:
正在被調度執行。CPU處于該進程的上下文環境中,程序計數器(eip)里保存著該進程的指令地址,通用寄存器里保存著該進程運算過程的中間結果,正在執行該進程的指令,正在讀寫該進程的地址空間。
就緒狀態。該進程不需要等待什么事件發生,隨時都可以執行,但CPU暫時還在執行另一個進程,所以該進程在一個就緒隊列中等待被內核調度。系統中可能同時有多個就緒的進程,那么該調度誰執行呢?內核的調度算法是基于優先級和時間片的,而且會根據每個進程的運行情況動態調整它的優先級和時間片,讓每個進程都能比較公平地得到機會執行,同時要兼顧用戶體驗,不能讓和用戶交互的進程響應太慢。
下面這個小程序從終端讀數據再寫回終端。
例 28.2. 阻塞讀終端
#include <unistd.h>
#include <stdlib.h>
int main(void) {
char buf[10];
int n;
n = read(STDIN_FILENO, buf, 10);
if (n < 0) {
perror(“read STDIN_FILENO”);
exit(1);
}
write(STDOUT_FILENO, buf, n);
return 0; }
執行結果如下:
$ ./a.out hello(回車) hello
$ ./a.out hello world(回車) hello
worl$ d bash: d: command not found
第一次執行a.out的結果很正常,而第二次執行的過程有點特殊,現在分析一下:
Shell進程創建a.out進程,a.out進程開始執行,而Shell進程睡眠等待a.out進程退出。
a.out調用read時睡眠等待,直到終端設備輸入了換行符才從read返回,read只讀走10個字符,剩下的字符仍然保存在內核的終端設備輸入緩沖區中。
a.out
進程打印并退出,這時Shell進程恢復運行,Shell繼續從終端讀取用戶輸入的命令,于是讀走了終端設備輸入緩沖區中剩下的字符d和換行符,把它當成一條命令解釋執行,結果發現執行不了,沒有d這個命令。
如果在open一個設備時指定了O_NONBLOCK標志,read/write就不會阻塞。以read為例,如果設備暫時沒有數據可讀就返回-1,同時置errno為EWOULDBLOCK(或者EAGAIN,這兩個宏定義的值相同),表示本來應該阻塞在這里(would block,虛擬語氣),事實上并沒有阻塞而是直接返回錯誤,調用者應該試著再讀一次(again)。這種行為方式稱為輪詢(Poll),調用者只是查詢一下,而不是阻塞在這里死等,這樣可以同時監視多個設備:
while(1) {undefined
非阻塞read(設備1);
if(設備1有數據到達)
處理數據;
非阻塞read(設備2);
if(設備2有數據到達)
處理數據;
…
}
如果
read(設備1)
是阻塞的,那么只要設備1沒有數據到達就會一直阻塞在設備1的
read
調用上,即使設備2有數據到達也不能處理,使用非阻塞I/O就可以避免設備2得不到及時處理。
非阻塞I/O有一個缺點,如果所有設備都一直沒有數據到達,調用者需要反復查詢做無用功,如果阻塞在那里,操作系統可以調度別的進程執行,就不會做無用功了。在使用非阻塞I/O時,通常不會在一個while循環中一直不停地查詢(這稱為Tight Loop),而是每延遲等待一會兒來查詢一下,以免做太多無用功,在延遲等待的時候可以調度其它進程執行。
while(1) {
非阻塞read(設備1);
if(設備1有數據到達) 處理數據;
非阻塞read(設備2);
if(設備2有數據到達) 處理數據;
… sleep(n);
}
這樣做的問題是,設備1有數據到達時可能不能及時處理,最長需延遲n秒才能處理,而且反復查詢還是做了很多無用功。以后要學習的select(2)函數可以阻塞地同時監視多個設備,還可以設定阻塞等待的超時時間,從而圓滿地解決了這個問題。
以下是一個非阻塞I/O的例子。目前我們學過的可能引起阻塞的設備只有終端,所以我們用終端來做這個實驗。程序開始執行時在0、1、2文件描述符上自動打開的文件就是終端,但是沒有O_NONBLOCK標志。所以就像例 28.2 “阻塞讀終端”一樣,讀標準輸入是阻塞的。我們可以重新打開一遍設備文件/dev/tty(表示當前終端),在打開時指定
O_NONBLOCK標志。
例 28.3. 非阻塞讀終端
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#define MSG_TRY “try again\n”
int main(void) {
char buf[10];
int fd, n;
fd = open("/dev/tty", O_RDONLY|O_NONBLOCK);
if(fd<0) {
perror(“open /dev/tty”);
exit(1);
}
tryagain:
n = read(fd, buf, 10);
if (n < 0) {
if (errno == EAGAIN) {
sleep(1);
write(STDOUT_FILENO, MSG_TRY, strlen(MSG_TRY));
goto tryagain;
}
perror(“read /dev/tty”);
exit(1);
}
write(STDOUT_FILENO, buf, n);
close(fd);
return 0;
}
以下是用非阻塞I/O實現等待超時的例子。既保證了超時退出的邏輯又保證了有數據到達時處理延遲較小。
例 28.4. 非阻塞讀終端和等待超時
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#define MSG_TRY “try again\n”
#define MSG_TIMEOUT “timeout\n”
int main(void) {
char buf[10];
int fd, n, i;
fd = open("/dev/tty", O_RDONLY|O_NONBLOCK);
if(fd<0) {
perror(“open /dev/tty”);
exit(1);
}
for(i=0; i<5; i++) {
n = read(fd, buf, 10);
if(n>=0) break;
if(errno!=EAGAIN) {
perror(“read /dev/tty”);
exit(1);
}
sleep(1);
write(STDOUT_FILENO, MSG_TRY, strlen(MSG_TRY));
}
if(i==5)
write(STDOUT_FILENO, MSG_TIMEOUT, strlen(MSG_TIMEOUT));
else
write(STDOUT_FILENO, buf, n);
close(fd);
return 0;
}
總結
以上是生活随笔為你收集整理的linux文件编程(1)—— open、write、read、lseek、阻塞问题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: redis 学习笔记(1)-编译、启动、
- 下一篇: Linux进程编程(PS: exec族函