linux中文件IO
一. linux常用文件IO接口
1.1. 文件描述符
1.1.1. 文件描述符的本質是一個數字,這個數字本質上是進程表中文件描述符表的一個表項,進程通過文件描述符作為index去索引查表得到文件表指針,再間接訪問得到這個文件對應的文件表。
1.1.2. 文件描述符這個數字是open系統調用內部由操作系統自動分配的,操作系統分配這個fd時也不是隨意分配,也是遵照一定的規律的:fd從0開始依次增加。fd也是有最大限制的,在linux的早期版本中(0.11)fd最大是20,所以當時一個進程最多允許打開20個文件。linux中文件描述符表是個數組(不是鏈表),所以這個文件描述符表其實就是一個數組,fd是index,文件表指針是value。當我們去open時,內核會從文件描述符表中挑選一個最小的未被使用的數字給我們返回。
1.1.3. fd中0、1、2已經默認被系統占用了,因此用戶進程得到的最小的fd就是3了。這三個文件對應的fd就是0、1、2。這三個文件分別叫stdin、stdout、stderr。也就是標準輸入、標準輸出、標準錯誤。
1.2. open
1.2.1. 在linux系統中要操作一個文件,一般是先open打開一個文件,得到一個文件描述符,然後對文件進行讀寫操作(或其他操作),最後close關閉文件即可
1.2.2. 文件平時是存在塊設備中的文件系統中的,我們把這種文件叫靜態文件。當我們去open打開一個文件時,linux內核做的操作包括:內核在進程中建立了一個打開文件的數據結構,記錄下我們打開的這個文件;內核在內存中申請一段內存,並且將靜態文件的內容從塊設備中讀取到內存中特定地址管理存放(叫動態文件)。
1.2.3. 打開文件後,以後對這個文件的讀寫操作,都是針對內存中這一份動態文件的,而並不是針對靜態文件的。當我們對動態文件進行讀寫後,此時內存中的動態文件和塊設備中的靜態文件就不同步了,當我們close關閉動態文件時,close內部內核將內存中的動態文件的內容去更新(同步)塊設備中的靜態文件。這樣做主要由於:塊設備本身有讀寫限制(回憶NnadFlash、SD等塊設備的讀寫特征),本身對塊設備進行操作非常不靈活。而內存可以按字節為單位來操作,而且可以隨機操作(內存就叫RAM,random),很靈活。所以內核設計文件操作時就這麽設計了。
1.3. read
ssize_t read(intView Codefd, void *buf, size_t count);
a. fd表示要讀取哪個文件,fd一般由前面的open返回得到
b. buf是應用程序自己提供的一段內存緩沖區,用來存儲讀出的內容
c. count是我們要讀取的字節數
d. 返回值ssize_t類型是linux內核用typedef重定義的一個類型(其實就是int),返回值表示成功讀取的字節數。
1.4. write
ssize_t write(int fd, const void *buf, size_t count);View Code
1.4.1. 寫入用write系統調用,write的原型和理解方法和read相似
1.5. lseek
off_t lseek(int fd, off_t offset, int whence);View Code
1.5.1. 文件指針:當我們要對一個文件進行讀寫時,一定需要先打開這個文件,所以我們讀寫的所有文件都是動態文件。動態文件在內存中的形態就是文件流的形式。
1.5.2. 在動態文件中,我們會通過文件指針來表征這個正在操作的位置。所謂文件指針,就是我們文件管理表這個結構體裏面的一個指針。所以文件指針其實是vnode中的一個元素。這個指針表示當前我們正在操作文件流的哪個位置。這個指針不能被直接訪問,linux系統用lseek函數來訪問這個文件指針。
1.5.3. 當我們打開一個空文件時,默認情況下文件指針指向文件流的開始。所以這時候去write時寫入就是從文件開頭開始的。write和read函數本身自帶移動文件指針的功能,所以當我write了n個字節後,文件指針會自動向後移動n位。如果需要人為的隨意更改文件指針,那就只能通過lseek函數了
1.5.4. 用lseek計算文件長度(length = lseek(fd,0,SEEK_END))
1.6. close
int close(int fd);View Code
1.6.1. 關閉打開的文件
PS:實時查man手冊
(1)當我們寫應用程序時,很多API原型都不可能記得,所以要實時查詢,用man手冊
(2)man 1 xx查linux shell命令,man 2 xxx查API, man 3 xxx查庫函數
二. open函數的flag詳解
2.1. 讀寫權限
a. O_RDONLY就表示以只讀方式打開,
b. O_WRONLY表示以只寫方式打開,
c. O_RDWR表示以可讀可寫方式打開
2.2. 打開存在並有內容的文件時
2.3. 打開不存在的文件時
2.4. O_NONBLOCK
2.5. O_SYNC
三. 文件讀寫的一些細節
3.1. errno
3.2. perror
3.3. 文件IO效率和標準IO
四. 退出進程方式
4.1. 在main(main函數由其父進程調用,故返回後進程就over)用return,一般原則是程序正常終止return 0,如果程序異常終止則return -1。
4.2. 正式終止進程(程序)應該使用exit或者_exit或者_Exit之一。
五. 文件共享的實現方式
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <string.h> int main(int argc, char *argv[]) { int fd1 = -1, fd2 = -1; // fd 就是file descriptor,文件描述符 char buf[100] = {0}; char writebuf[20] = "l love linux"; int ret = -1; // 第一步:打開文件 fd1 = open("a.txt", O_RDWR); fd2 = open("a.txt", O_RDWR); //fd = open("a.txt", O_RDONLY); if ((-1 == fd1) || (fd2 == -1)) // 有時候也寫成: (fd < 0) { //printf("\n"); perror("文件打開錯誤"); // return -1; _exit(-1); } else { printf("文件打開成功,fd1 = %d. fd2 = %d.\n", fd1, fd2); } #if 0 // 第二步:讀寫文件 // 寫文件 ret = write(fd, writebuf, strlen(writebuf)); if (ret < 0) { //printf("write失敗.\n"); perror("write失敗"); _exit(-1); } else { printf("write成功,寫入了%d個字符\n", ret); } #endif #if 1 while(1) { // 讀文件 memset(buf, 0, sizeof(buf)); ret = read(fd1, buf, 2); if (ret < 0) { printf("read失敗\n"); _exit(-1); } else { //printf("實際讀取了%d字節.\n", ret); printf("fd1:[%s].\n", buf); } sleep(1); // 讀文件 memset(buf, 0, sizeof(buf)); ret = read(fd2, buf, 2); if (ret < 0) { printf("read失敗\n"); _exit(-1); } else { //printf("實際讀取了%d字節.\n", ret); printf("fd2:[%s].\n", buf); } } #endif // 第三步:關閉文件 close(fd1); close(fd2); _exit(0); }View Code
linux中文件IO