文件IO
2021-05-31 12:46:14 星期一
文件描述符:是有限資源
文件描述符 | POSIX名稱 | 用途 | stdio流 |
---|---|---|---|
0 |
STDIN_FILENO |
標准輸入 | stdin |
1 |
STDOUT_FILENO |
標准輸出 | stdout |
2 |
STDERR_FILENO |
標准錯誤 | stderr |
基礎IO
open
#include <fcntl.h>
int open(const char *path, int oflag, mode_t mode);
path
文件的路徑和名稱flag
文件的打開模式,組合使用時需要使用位運算或- 基本模式
-
只讀 只寫 讀寫 O_RDONLY
O_WRONLY
O_RDWR
00
01
02
-
- 附加模式(只列常用)
O_APPEND
:總是在文件末尾添加數據O_EXCL
:配合O_CREAT
標志- 表明如果文件存在則不會打開文件,並使open調用失敗,否則能夠創建並打開文件。這樣確保了調用
open()
的進程即為創建文件的進程. - 同時不允許path是符號鏈接
- 表明如果文件存在則不會打開文件,並使open調用失敗,否則能夠創建並打開文件。這樣確保了調用
O_CREAT
:沒有文件存在時會創建文件,需要mode參數指明文件權限,一共9個O_TRUNC
:如果文件存在且為普通文件且該進程對改文件有寫權限,則清空文件內容
- 基本模式
mode
S_IRUSR
:文件所有者有讀權限S_IWUSR
:文件所有者有寫權限S_IXUSR
:文件所有者有執行權限S_IRGRP
:同組用戶有讀權限S_IWGRP
:同組用戶有寫權限S_IXGRP
:同組用戶有執行權限S_IROTH
:其他用戶有讀權限S_IWOTH
:其他用戶有寫權限S_IXOTH
:其他用戶有執行權限
- 返回文件描述符
fd
或者-1
錯誤
creat
舊版使用,新版都用open進行創建文件
read
將open
返回的fd
文件描述符中讀取bytes
字節的數據到buf
中,返回實際讀取的字節數,不成功返回-1
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t bytes);
- 當
read
普通文件:調用成功返回實際讀取的字節數,遇到文件EOF
時返回0
,出現錯誤返回-1
。 - 當
read
讀取終端,遇到\n
即返回
注意:read
系統調用是逐字節讀取的,所以無法遵守C語言中的字符串的規則。比如C語言中字符串以\0
(0x0
)作為結束,但是read
認為這里的字節值是0x0
並繼續讀下去.所以使用read
讀數據時,通常的方式是:每次讀取數據,再將的實際讀取到的size
處設置成C語言認可的字符串結束符,即buffer[size] = '\0';
#define MAX_READ 16
char buffer[MAX_READ + 1];
ssize_t size;
size = read(fd, buffer, MAX_READ);
if (size == -1)
exit(0);
buffer[size] = '\0';
close(fd);
一個例子
該文件從終端讀取一行(因為read讀終端時以\n作為結束)字符並打印出來,同時打印每一個字符
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#define MAX_READ 16
int main(int argc, char **argv)
{
char buffer[MAX_READ + 1];
memset(buffer, 0x23, sizeof(buffer)); // fill with #
buffer[MAX_READ] = '\0';
ssize_t numRead, i;
numRead = read(STDIN_FILENO, buffer, MAX_READ);
if (numRead == -1)
perror("read");
// buffer[numRead] = '\0';
printf("the input data was:%s\n", buffer);
for (i = 0; buffer[i] != '\0' && i < MAX_READ; i++)
printf("%ld->%x\n", i, buffer[i]);
return 0;
}
$ ./4.4
abcd
the input data was:abcd
############
0->61
1->62
2->63
3->64
4->a
5->23
6->23
...
16->0
由此可見read只是以二進制的形式照搬數據,並不對數據進行處理,因此,對數據的處理留給了程序員
write
將bytes
字節的buf
數據寫到open
返回的fd
文件描述符所指的文件中,返回實際寫的字節數,不成功返回-1
,寫入已打開的文件。調用成功並不代表已經寫入磁盤,可能先進入緩存(這樣減少磁盤活動量、加快write
調用)。
#include <unistd.h>
ssize_t write(int fd, void *buf, size_t bytes);
close
#include <unistd.h>
int close (int fd);
close函數也有錯誤處理,編程時也應該錯誤檢查。
lseek
內核打開的文件時會記錄文件偏移量,第一字節的偏移量為0,文件打開時,會將偏移量設置為0
十分重要:有時候讀文件讀不出來,可能就是因為文件偏移量在文件末尾處,這時候需要重置
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
fd
文件描述符offset
表示偏移量- 正值為正向移動,向繼續往下讀的方向
wherece
表示文件讀寫指針從哪里開始計數SEEK_SET
表示起始位置SEEK_CUR
表示當前位置SEEK_END
表示末尾位置的后一個字節(這里直接寫數據的話是恰好和文件連接)
- 返回新的文件偏移量或
-1
(執行失敗)
文件空洞
從文件結尾后到新寫入的數據間的空間,他不占用磁盤空間,直到寫入了數據。這時文件的名義的大小可能比磁盤存儲的總量大。具體的在14節
ls -l file 查看文件邏輯大小
du -c file 查看文件實際占用的存儲塊多少
od -c file 查看文件存儲的內容
unlink刪除
只是刪除path到文件的一個鏈接,其文件對於的i-node
減1,為0時,改文件才從磁盤刪除。
#include <unistd.h>
int unlink(const char *path);
iotcl
文件和目錄
目錄是另一種文件,只是內容是包含的文件信息和目錄信息。
鏈接
每一個文件對應一個inode
,文件的鏈接數對應inode
中的鏈接數,記錄着這個文件的鏈接數值,即指向該inode
的文件數,文件和inode
是多對一的關系。本質上rm
指令調用系統調用unlink
函數,將這個文件的inode
的鏈接數-1
,為0
時才真正刪除
- 硬鏈接
- 相鏈接的文件總是同步
- 軟鏈接
- 理解為Windows的快捷方式
$ touch a.txt
$ echo "hello" > a.txt
$ ls -li
total 4
2104443 -rw-rw-r-- 1 dwr dwr 6 Apr 10 22:20 a.txt
# inode編號 文件權限 用戶 組用戶 不知道 創建月 日 時 文件名
$ ln a.txt a.txt.bak # 建立硬鏈接
$ ls -li
total 8
2104443 -rw-rw-r-- 2 dwr dwr 6 Apr 10 22:20 a.txt
2104443 -rw-rw-r-- 2 dwr dwr 6 Apr 10 22:20 a.txt.bak
$ ln -s a.txt a.txt.s # 建立軟鏈接
$ ls -li
total 8
2104443 -rw-rw-r-- 2 dwr dwr 6 Apr 10 22:20 a.txt
2104443 -rw-rw-r-- 2 dwr dwr 6 Apr 10 22:20 a.txt.bak
2099642 lrwxrwxrwx 1 dwr dwr 5 Apr 10 22:25 a.txt.s -> a.txt
#include <unistd.h>
int link(const char *__from, const char *__to);
int symlink(const char *__from, const char *__to);
錯誤打印
perror
<errno.h>
根據設置的errno值打印對應的錯誤信息,打印規則是先打印s
中用戶定義的錯誤輸出,在打印系統調用錯誤的輸出提示。一定要在系統調用之后緊跟打印,否則會被覆蓋
void perror(const char *s);
strerror
將錯誤代碼轉換為字符串錯誤信息。
char *strerror(int errno);
原子IO
fcntl
#include <fcntl.h>
int fcntl(int __fd, int __cmd, ...)
文件IO緩沖
這里是Unix系統編程手冊第13章內容,不全待完善
read()
和write()
系統調用在操作磁盤文件時不會直接發起磁盤訪問,而是僅僅在用戶空間緩沖區與內核緩沖區高速緩存(kernel buffer cache)之間復制數據。write()
在后續某個時刻,內核會將其緩沖區中的數據寫入(刷新至)磁盤。
Linux 內核對緩沖區高速緩存的大小沒有固定上限。內核會分配盡可能多的緩沖區高速緩存頁,而僅受限於兩個因素;可用的物理內存總量,以及出於其他目的對物理內存的需求(例如,需要將正在運行進程的文本和數據頁保留在物理內存中)。若可用內存不足,則內核會將
解釋一下書中對IO系統調用的實驗:
- 總用時=CPU用時+磁盤讀寫用時
- CPU用時=用戶CPU用時(用戶模式下執行的代碼)+系統CPU用時(內核模式(系統調用和數據在用戶和內核模式下傳輸)下執行的代碼)
stdio的緩沖
C語言中IO
函數可以理解為系統IO
調用+數據緩沖,免於編寫者自己處理對數據的緩沖
int setvbuf(FILE *stream, char *buf, int modes, size_t n)
控制stdio庫函數的緩沖形式,需要最先調用,之后的stdio操作才有效
stream
- 表示配置緩沖的文件流
buf
- 不為空則使用size大小作為緩沖區(這個buf空間應該是堆內存上,避免函數調用和返回對棧進行修改)
- 為空則自動分配(根據mode選擇是否分配)size大小空間
modes
_IONBF
:not,不緩沖,每一次調用stdio函數都立即調用系統調用_IOLBF
:line,行緩沖,遇到換行符或緩沖區滿則調用系統調用,指向終端設備的流默認使用該模式_IOFBF
:file,全緩沖,指向磁盤的流默認使用該模式
- 出錯返回非0,成功返回0