目錄
一、文件創建(creat 系統調用)?
1.1 函數原型
1.2 參數說明?
1.3 返回值?
1.4 使用示例
二、文件打開(open 系統調用)?
2.1 函數原型
2.2 參數說明?
2.3 返回值?
2.4 使用示例
三、文件讀寫(read 和 write 系統調用)?
3.1 read 系統調用?
3.2 write 系統調用?
3.3 使用示例
四、文件定位(lseek 系統調用)?
4.1 函數原型
4.2 參數說明?
4.3 返回值?
4.4 使用示例
五、文件關閉(close 系統調用)?
5.1 函數原型
5.2 參數說明?
5.3 返回值?
5.4 注意事項?
5.5 使用示例
六、總結與
在 Linux 系統中,文件操作是非常核心且基礎的功能。無論是應用程序的開發,還是對系統的日常管理,都離不開對文件的創建、打開、讀寫、定位和關閉等操作。而這些操作的底層實現,很大程度上依賴于系統調用。本文將詳細介紹 Linux 文件操作中涉及的主要系統調用,深入理解 Linux 文件操作的原理和實現方式。?
一、文件創建(creat 系統調用)?
在 Linux 中,創建一個新文件可以使用 creat 系統調用。它的主要功能是創建一個新的空文件,如果指定的文件已經存在,則會將其截斷為零長度(即清空文件內容)。?
1.1 函數原型
#include <fcntl.h>
int creat(const char *pathname, mode_t mode);
1.2 參數說明?
- pathname:指向要創建的文件路徑名的字符串指針。可以是絕對路徑,也可以是相對路徑。?
- mode:指定新文件的權限模式。它由一系列權限位組成,這些權限位決定了文件所有者、所屬組和其他用戶對該文件的訪問權限。常見的權限位有:?
- S_IRUSR:所有者具有讀權限(0400)?
- S_IWUSR:所有者具有寫權限(0200)?
- S_IXUSR:所有者具有執行權限(0100)?
- S_IRGRP:所屬組具有讀權限(0040)?
- S_IWGRP:所屬組具有寫權限(0020)?
- S_IXGRP:所屬組具有執行權限(0010)?
- S_IROTH:其他用戶具有讀權限(0004)?
- S_IWOTH:其他用戶具有寫權限(0002)?
- S_IXOTH:其他用戶具有執行權限(0001)?
這些權限位可以通過按位或(|)運算組合使用,例如,S_IRUSR | S_IWUSR 表示所有者具有讀寫權限。?
1.3 返回值?
- 成功:返回一個非負的文件描述符(file descriptor),該文件描述符用于后續對文件的操作。?
- 失敗:返回 - 1,并設置 errno 來指示錯誤類型。常見的錯誤有:?
- EEXIST:指定的文件已經存在,且沒有被截斷(當使用 O_CREAT | O_EXCL 標志時,如果文件存在則會返回該錯誤,但 creat 系統調用本身如果文件存在會截斷文件,所以此錯誤在 creat 中較少見)?
- ENOENT:路徑名中的目錄不存在?
- EACCES:沒有權限創建文件或訪問路徑中的目錄?
- ENOSPC:文件系統沒有足夠的空間創建新文件?
1.4 使用示例
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>int main() {int fd;// 創建一個名為"testfile.txt"的文件,所有者具有讀寫權限,所屬組和其他用戶無權限fd = creat("testfile.txt", S_IRUSR | S_IWUSR);if (fd == -1) {perror("creat failed");exit(EXIT_FAILURE);}printf("File created successfully with file descriptor: %d\n", fd);close(fd); // 創建文件后要記得關閉文件描述符return 0;
}
使用 creat 函數創建了一個名為 “testfile.txt” 的文件,設置所有者具有讀寫權限。如果創建成功,會輸出文件描述符;如果失敗,會通過 perror 函數打印錯誤信息。?
二、文件打開(open 系統調用)?
open 系統調用是 Linux 中用于打開一個已存在的文件或創建一個新文件的主要系統調用。它比 creat 系統調用功能更強大,可以通過不同的標志組合實現多種操作。?
2.1 函數原型
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
2.2 參數說明?
pathname:與 creat 系統調用中的 pathname 參數相同,指向要打開或創建的文件路徑名。?
flags:用于指定打開文件的方式和行為,是一個整數,可以由多個標志按位或組合而成。常見的標志有:?
- O_RDONLY:以只讀方式打開文件?
- O_WRONLY:以只寫方式打開文件?
- O_RDWR:以讀寫方式打開文件?
- O_CREAT:如果指定的文件不存在,則創建該文件。此時需要提供第三個參數 mode 來指定新文件的權限。?
- O_EXCL:與 O_CREAT 一起使用,如果文件已經存在,則 open 調用失敗(返回 - 1),可以用于防止覆蓋已存在的文件。?
- O_TRUNC:如果文件已經存在且以可寫方式打開(O_WRONLY 或 O_RDWR),則將文件截斷為零長度。?
- O_APPEND:以追加方式打開文件,每次寫操作都將數據添加到文件的末尾。?
mode:當 flags 中包含 O_CREAT 標志時,該參數用于指定新文件的權限模式,與 creat 系統調用中的 mode 參數相同。如果 flags 中不包含 O_CREAT,則該參數被忽略。?
2.3 返回值?
- 成功:返回一個非負的文件描述符,用于后續對文件的操作。?
- 失敗:返回 - 1,并設置 errno 指示錯誤類型。常見的錯誤與 creat 系統調用類似,此外還有:?
- EINVAL:flags 參數無效?
- EMFILE:進程已打開的文件描述符達到上限?
2.4 使用示例
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>int main() {int fd1, fd2, fd3;// 以只讀方式打開已存在的文件"testfile.txt"fd1 = open("testfile.txt", O_RDONLY);if (fd1 == -1) {perror("open for read failed");exit(EXIT_FAILURE);}// 以讀寫方式打開文件,如果文件不存在則創建,權限為所有者讀寫,組和其他用戶只讀fd2 = open("newfile.txt", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);if (fd2 == -1) {perror("open for read-write and create failed");close(fd1);exit(EXIT_FAILURE);}// 以只寫、追加方式打開文件,若文件不存在則創建fd3 = open("logfile.txt", O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR);if (fd3 == -1) {perror("open for write and append failed");close(fd1);close(fd2);exit(EXIT_FAILURE);}printf("fd1: %d, fd2: %d, fd3: %d\n", fd1, fd2, fd3);// 關閉文件描述符close(fd1);close(fd2);close(fd3);return 0;
}
展示了 open 系統調用的幾種常見用法:只讀打開已存在文件、讀寫打開并創建新文件、只寫追加打開并創建新文件。?
三、文件讀寫(read 和 write 系統調用)?
打開文件后,就可以進行讀寫操作了。Linux 提供了 read 和 write 系統調用來實現對文件的讀寫。?
3.1 read 系統調用?
read 系統調用用于從已打開的文件中讀取數據。?
①函數原型
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
②參數說明?
- fd:文件描述符,即 open 或 creat 系統調用返回的非負整數,標識要讀取的文件。?
- buf:指向一個緩沖區的指針,用于存儲讀取到的數據。?
- count:指定要讀取的字節數。?
③ 返回值?
- 成功:返回實際讀取到的字節數。如果已經到達文件末尾,則返回 0。?
- 失敗:返回 - 1,并設置 errno 指示錯誤類型,如 EBADF(文件描述符無效)、EIO(I/O 錯誤)等。?
3.2 write 系統調用?
write 系統調用用于向已打開的文件中寫入數據。?
①函數原型
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
②參數說明?
- fd:文件描述符,標識要寫入的文件。?
- buf:指向一個緩沖區的指針,該緩沖區中存儲了要寫入的數據。?
- count:指定要寫入的字節數。?
③返回值?
- 成功:返回實際寫入的字節數。注意,實際寫入的字節數可能小于 count,這并不一定表示錯誤,可能是由于各種原因(如磁盤空間不足、信號中斷等)導致的。?
- 失敗:返回 - 1,并設置 errno 指示錯誤類型,如 EBADF(文件描述符無效或不具有寫權限)、EIO(I/O 錯誤)等。?
3.3 使用示例
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>int main() {int fd;char write_buf[] = "Hello, Linux File Operation!";char read_buf[1024];ssize_t nwritten, nread;// 以讀寫方式打開文件,不存在則創建fd = open("rwtest.txt", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);if (fd == -1) {perror("open failed");exit(EXIT_FAILURE);}// 寫入數據nwritten = write(fd, write_buf, strlen(write_buf));if (nwritten == -1) {perror("write failed");close(fd);exit(EXIT_FAILURE);}printf("Wrote %zd bytes: %s\n", nwritten, write_buf);// 將文件指針移到文件開頭(否則讀取不到剛寫入的數據)off_t offset = lseek(fd, 0, SEEK_SET);if (offset == -1) {perror("lseek failed");close(fd);exit(EXIT_FAILURE);}// 讀取數據nread = read(fd, read_buf, sizeof(read_buf) - 1); // 留一個字節給'\0'if (nread == -1) {perror("read failed");close(fd);exit(EXIT_FAILURE);} else if (nread == 0) {printf("Reached end of file\n");} else {read_buf[nread] = '\0'; // 加上字符串結束符printf("Read %zd bytes: %s\n", nread, read_buf);}close(fd);return 0;
}
先向文件寫入一段字符串,然后使用 lseek 系統調用將文件指針移到文件開頭,再讀取文件內容并打印。需要注意的是,write 調用返回的實際寫入字節數可能小于請求的字節數,因此在實際應用中可能需要循環寫入,直到所有數據都被寫入。同樣,read 調用也可能需要循環讀取,直到讀取到預期的數據量或到達文件末尾。?
四、文件定位(lseek 系統調用)?
在對文件進行讀寫操作時,系統會維護一個文件偏移量(file offset),也稱為文件指針,它指示了下一次讀寫操作開始的位置。lseek 系統調用用于修改這個文件偏移量,實現對文件的隨機訪問。?
4.1 函數原型
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
4.2 參數說明?
- fd:文件描述符,標識要操作的文件。?
- offset:偏移量,是一個有符號整數,表示要移動的字節數。?
- whence:用于指定偏移量的參考位置,有以下三種取值:?
- SEEK_SET:將文件偏移量設置為 offset 字節(從文件開頭開始計算)。?
- SEEK_CUR:將文件偏移量設置為當前偏移量加上 offset 字節。?
- SEEK_END:將文件偏移量設置為文件末尾加上 offset 字節(offset 為負時表示向文件開頭方向移動)。?
4.3 返回值?
- 成功:返回新的文件偏移量(從文件開頭開始計算的字節數)。?
- 失敗:返回 - 1,并設置 errno 指示錯誤類型,如 EBADF(文件描述符無效)、ESPIPE(文件是管道、FIFO 或套接字,這些文件不支持定位操作)等。?
4.4 使用示例
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>int main() {int fd;char buf[100];off_t offset;// 創建并打開一個文件fd = open("seektest.txt", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);if (fd == -1) {perror("open failed");exit(EXIT_FAILURE);}// 寫入一些數據const char *data = "0123456789abcdefghijklmnopqrstuvwxyz";write(fd, data, strlen(data));// 將文件指針移到開頭offset = lseek(fd, 0, SEEK_SET);if (offset == -1) {perror("lseek to beginning failed");close(fd);exit(EXIT_FAILURE);}printf("Moved to beginning, offset: %ld\n", (long)offset);// 讀取5個字節ssize_t nread = read(fd, buf, 5);if (nread == -1) {perror("read failed");close(fd);exit(EXIT_FAILURE);}buf[nread] = '\0';printf("Read %zd bytes: %s\n", nread, buf);// 從當前位置向后移動10個字節offset = lseek(fd, 10, SEEK_CUR);if (offset == -1) {perror("lseek from current failed");close(fd);exit(EXIT_FAILURE);}printf("Moved 10 bytes from current, new offset: %ld\n", (long)offset);// 讀取5個字節nread = read(fd, buf, 5);if (nread == -1) {perror("read failed");close(fd);exit(EXIT_FAILURE);}buf[nread] = '\0';printf("Read %zd bytes: %s\n", nread, buf);// 從文件末尾向前移動20個字節offset = lseek(fd, -20, SEEK_END);if (offset == -1) {perror("lseek from end failed");close(fd);exit(EXIT_FAILURE);}printf("Moved 20 bytes before end, new offset: %ld\n", (long)offset);// 讀取5個字節nread = read(fd, buf, 5);if (nread == -1) {perror("read failed");close(fd);exit(EXIT_FAILURE);}buf[nread] = '\0';printf("Read %zd bytes: %s\n", nread, buf);close(fd);return 0;
}
展示了 lseek 系統調用的三種使用方式:從文件開頭設置偏移量、從當前位置調整偏移量、從文件末尾調整偏移量。通過 lseek,我們可以靈活地定位到文件的任意位置進行讀寫操作,實現隨機訪問。?
五、文件關閉(close 系統調用)?
當對文件的操作完成后,需要使用 close 系統調用來關閉文件,釋放與該文件相關的資源,如文件描述符等。?
5.1 函數原型
#include <unistd.h>
int close(int fd);
5.2 參數說明?
- fd:要關閉的文件的文件描述符。?
5.3 返回值?
- 成功:返回 0。?
- 失敗:返回 - 1,并設置 errno 指示錯誤類型,如 EBADF(文件描述符無效或已經關閉)等。?
5.4 注意事項?
- 每個進程能打開的文件描述符數量是有限的,因此在使用完文件后,一定要及時調用 close 關閉文件,以釋放文件描述符,避免資源耗盡。?
- 關閉文件后,該文件描述符將不再有效,不能再用于對文件的讀寫等操作。?
5.5 使用示例
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>int main() {int fd;fd = open("closetest.txt", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);if (fd == -1) {perror("open failed");exit(EXIT_FAILURE);}// 對文件進行一些操作...// 關閉文件if (close(fd) == -1) {perror("close failed");exit(EXIT_FAILURE);}printf("File closed successfully\n");return 0;
}
這個示例比較簡單,展示了打開文件后進行一些操作,然后關閉文件的過程。在實際應用中,無論對文件的操作是否成功,都應該在適當的時候關閉文件。?
六、總結與
在實際使用這些系統調用時,需要注意以下幾點:?
- 始終檢查系統調用的返回值,及時處理可能出現的錯誤。?
- 注意文件權限的設置,確保文件的訪問安全。?
- 及時關閉不再使用的文件,釋放系統資源。?
- 對于讀寫操作,要考慮到實際讀寫的字節數可能小于請求的字節數,必要時進行循環操作。?
隨著 Linux 系統的不斷發展,文件操作的方式和接口也在不斷完善。但這些基礎的系統調用仍然是理解 Linux 文件系統和進行底層開發的關鍵。未來,我們可以進一步學習 Linux 文件系統的底層原理,以及如何利用這些系統調用來實現更復雜的文件操作功能,如文件復制、移動、權限修改等。同時,也可以學習更高層次的文件操作庫函數(如標準 C 庫中的 fopen、fread、fwrite 等),它們是對這些系統調用的封裝,使用起來更加方便,但了解其底層實現的系統調用有助于我們更好地理解和使用這些庫函數。?
通過不斷學習和實踐,我們可以更深入地掌握 Linux 文件操作技術,為開發高效、穩定的 Linux 應用程序打下堅實的基礎。