1.系統(tǒng)下的文件操作:
?是不是只有C\C++有文件操作呢???Python、Java、PHP、go也有,他們的文件操作的方法是不一樣的啊
1.1對(duì)于文件操作的思考:
我們之前就說過了:文件=內(nèi)容+屬性
針對(duì)文件的操作就變成了對(duì)內(nèi)容的操作和對(duì)屬性的操作
?當(dāng)文件沒有被操作的時(shí)候,文件一般會(huì)在什么位置???磁盤
?當(dāng)我們對(duì)文件進(jìn)行操作的時(shí)候,文件需要在哪里???內(nèi)存?為什么呢???因?yàn)轳T諾依曼體系結(jié)構(gòu)
?通常我們打開文件、訪問文件和關(guān)閉文件,是誰(shuí)在進(jìn)行相關(guān)操作?
運(yùn)行起來的時(shí)候,才會(huì)執(zhí)行對(duì)應(yīng)的代碼,然后才是真正的對(duì)文件進(jìn)行相關(guān)的操作。
實(shí)際上是 進(jìn)程在對(duì)文件進(jìn)行操作! 在系統(tǒng)角度理解是我們?cè)?jīng)寫的代碼變成了進(jìn)程。
進(jìn)程執(zhí)行調(diào)度對(duì)應(yīng)的代碼到了 fopen,write 這樣的接口,然后才完成了對(duì)文件的操作。
當(dāng)我執(zhí)行 fopen 時(shí),對(duì)應(yīng)地就把文件打開了,所以文件操作和進(jìn)程之間是撇不開關(guān)系。
?當(dāng)我們對(duì)文件進(jìn)行操作的時(shí)候,文件需要提前被load到內(nèi)存?load是內(nèi)容or屬性???至少得有屬性吧
?當(dāng)我們對(duì)文件進(jìn)行操作的時(shí)候,文件需要被提前l(fā)od到內(nèi)存,是不是只有你一個(gè)人在load呢?
??不是,內(nèi)存中一定存在大量不同文件的屬性、
所以綜上,打開文件本質(zhì)就是將需要的文件屬性加載到內(nèi)存中,OS內(nèi)部一定一定會(huì)同時(shí)存在大量的被打開的文件,?那么操作系統(tǒng)要不要管理這些被打開的文件呢?
??先描述再組織
先描述,構(gòu)建在內(nèi)存中的文件結(jié)構(gòu)體struct file (就可以從磁盤來,struct file*next),被打開的文件
每一個(gè)被打開的文件,都要被OS內(nèi)對(duì)應(yīng)文件對(duì)象的struct結(jié)構(gòu)體,可以將所有的struct file結(jié)構(gòu)體用某種數(shù)據(jù)結(jié)構(gòu)連接起來——在os內(nèi)部,對(duì)被打開的文件進(jìn)行管理,就被轉(zhuǎn)換成為了對(duì)鏈表的增刪查改
結(jié)論:文件被打開,OS要為被打開的文件,創(chuàng)建對(duì)應(yīng)的內(nèi)核數(shù)據(jù)結(jié)構(gòu)
struct file
{
//各種屬性
//各種連接關(guān)系
}
文件其實(shí)可以被分為兩大類:磁盤文件、被打開的文件(內(nèi)存文件)
?文件被打開,是誰(shuí)在打開呢???OS,但是是誰(shuí)讓OS打開的呢?用戶(進(jìn)程為代表)
我們之前的所有的文件操作,都是進(jìn)程和被打開文件的關(guān)系
都是進(jìn)程和被打開文件的關(guān)系:struct task_struct和struct_file
快速回憶一下c語(yǔ)言的文件操作(fopen,fwrite等)
#include<stdio.h>
#define LOG "log.txt"
int main()
{
FILE*fp=fopen(LOG,"w");
if(fp==NULL)
{
perror("fopen");
return 1;
}
const char*msg="hello xiaolu,hello 107";
int cnt=5;
while(cnt)
{
fputs(msg,fp);
cnt--;
}
fclose(fp);
return 0;
}
默認(rèn)如果只是打開,文件內(nèi)容會(huì)自動(dòng)被清空,同時(shí),每次進(jìn)行寫入的時(shí)候,都會(huì)從最開始進(jìn)行寫入
1.2文件操作模式:
r:只讀模式,打開一個(gè)已存在的文本文件,允許讀取文件。
r+:讀寫模式,打開一個(gè)已存在的文本文件,允許讀寫文件。
w:只寫模式,打開一個(gè)文本文件并清除其內(nèi)容,如果文件不存在,則創(chuàng)建一個(gè)新文件。
w+:讀寫模式,打開一個(gè)文本文件并清除其內(nèi)容,如果文件不存在,則創(chuàng)建一個(gè)新文件。
a:追加模式,打開一個(gè)文本文件并將數(shù)據(jù)追加到文件末尾,如果文件不存在,則創(chuàng)建一個(gè)新文件。
a+:讀寫模式,打開一個(gè)文本文件并將數(shù)據(jù)追加到文件末尾,如果文件不存在,則創(chuàng)建一個(gè)新文件。
這些我們 在c語(yǔ)言中已經(jīng)有了詳細(xì)的講解了,就不做解釋了
2.文件系統(tǒng)接口
printf 一定封裝了系統(tǒng)調(diào)用接口。而這個(gè)函數(shù)就是snprintf函數(shù)
所有的語(yǔ)言提供的接口,之所以你沒有見到系統(tǒng)調(diào)用,因?yàn)樗械恼Z(yǔ)言都被系統(tǒng)接口做了 封裝。
所以你看不到對(duì)應(yīng)的底層的系統(tǒng)接口的差別。為什么要封裝?原生系統(tǒng)接口,使用成本比較高。
系統(tǒng)接口是 OS 提供的,就會(huì)帶來一個(gè)問題:如果使用原生接口,你的代碼只能在一個(gè)平臺(tái)上跑。
直接使用原生系統(tǒng)接口,必然導(dǎo)致語(yǔ)言不具備 跨平臺(tái)性 (Cross-platform) !
我們首先要明確一個(gè)概念,C語(yǔ)言接口和操作系統(tǒng)接口是上下級(jí)的關(guān)系,任何一個(gè)語(yǔ)言,不管是C、C++、java、Python都有自己打開文件關(guān)閉文件讀寫文件的庫(kù)函數(shù),但是這些庫(kù)函數(shù)的使用都是在Linux和Windows系統(tǒng)下進(jìn)行的,所以任何語(yǔ)言的接口和系統(tǒng)接口是一種上下級(jí)的關(guān)系。
在系統(tǒng)調(diào)用接口中,我們打開文件使用open、關(guān)閉文件close、寫入write、讀取read。那這些接口和C中庫(kù)函數(shù)接口有什么聯(lián)系呢?我們可以這樣理解:C中調(diào)用得這些庫(kù)函數(shù)底層一定封裝了系統(tǒng)調(diào)用接口,可以認(rèn)為fopen底層調(diào)用open,fclose底層調(diào)用close,fread底層調(diào)用read,fwrite底層調(diào)用write。我們?cè)趙indows中打開文件,windows底層也有一套自己的windows相關(guān)的api系統(tǒng)接口,當(dāng)我們?cè)趙indows使用C的庫(kù)函數(shù)時(shí),C調(diào)用的就是windows下的系統(tǒng)接口。這樣在語(yǔ)言層面上就實(shí)現(xiàn)了跨平臺(tái)性。
2.1文件打開:open()
打開文件,在 C 語(yǔ)言上是 fopen,在系統(tǒng)層面上是 open。
open 接口是我們要學(xué)習(xí)的系統(tǒng)接口中最重要的一個(gè),沒有之一!所以我們放到前面來講。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
這里的參數(shù)有點(diǎn)抽象,我來給大家解釋一下
-
pathname: 要打開或創(chuàng)建的目標(biāo)文件
-
flags: 打開文件時(shí),可以傳個(gè)參數(shù)選項(xiàng),用下面的一個(gè)或者多個(gè)常量進(jìn)行“或”運(yùn)算,構(gòu)成flags。
參數(shù):
O_RDONLY: 只讀打開
O_WRONLY: 只寫打開
O_RDWR : 讀,寫打開
這三個(gè)常量,必須指定一個(gè)且只能指定一個(gè)
O_CREAT : 若文件不存在,則創(chuàng)建它。需要使用mode選項(xiàng),來指明新文件的訪問權(quán)限
O_APPEND: 追加寫
其中flags為標(biāo)志位,并且它是個(gè)整數(shù)類型(C99 標(biāo)準(zhǔn)之前沒有 bool 類型)
標(biāo)記位實(shí)際上我們?cè)炀陀眠^了,比如定義 flag 變量,設(shè) flag=0,設(shè) flag=1,傳的都是單個(gè)的。
? 思考:但如果我想一次傳遞多個(gè)標(biāo)志位呢?定義多個(gè)標(biāo)記位?flag1, flag2, flag3…
那我要傳 20 個(gè)呢,定義 20 個(gè)標(biāo)記位不成?遇到不確定傳幾個(gè)標(biāo)志位的情況下,該怎么辦?
我們看看寫底層的大佬是如何解決的:
?? 方案:系統(tǒng)傳遞標(biāo)記位是通過 位圖 來進(jìn)行傳遞的。
如果你要?jiǎng)?chuàng)建這個(gè)文件,該文件是要受到 權(quán)限的約束的!
創(chuàng)建一個(gè)文件,你需要告訴操作系統(tǒng)默認(rèn)權(quán)限是什么。
當(dāng)我們要打開一個(gè)曾經(jīng)不存在的文件,不能使用兩個(gè)參數(shù)的 open,而要使用三個(gè)參數(shù)的 open!
也就是帶 mode_t mode 的 open,這里的 mode 代表創(chuàng)建文件的權(quán)限:
int open(const char* pathname, int flags, mode_t mode);
文件描述符(open對(duì)應(yīng)的返回值)本質(zhì)就是數(shù)組下標(biāo)
2.2文件關(guān)閉:close()
#include <unistd.h>
int close(int fd);
該接口相對(duì) open 相對(duì)來說比較簡(jiǎn)單,只有一個(gè) fd 參數(shù),我們直接看代碼:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h> // 需引入頭文件
int main(void)
{
umask(0);
int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);
perror("open");
return 1;
}
printf("fd: %d\n", fd);
close(fd); // 關(guān)閉文件
return 0;
}
2.3文件寫入:write()
#include <unistd.h>
ssize_t write(int fd, const void* buf, size_t count);
write 接口有三個(gè)參數(shù):
- fd:文件描述符
- buf:要寫入的緩沖區(qū)的起始地址(如果是字符串,那么就是字符串的起始地址)
- count:要寫入的緩沖區(qū)的大小
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h> // 需引入頭文件
int main(void)
{
umask(0);
int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);
if (fd < 0) {
perror("open");
return 1;
}
printf("fd: %d\n", fd);
int cnt = 0;
const char* str = "hello xiaolu!\n";
while (cnt < 5) {
write(fd, str, strlen(str));
cnt++;
}
close(fd);
return 0;
}
這里strlen(str)不可以+1,+1就會(huì)把\0寫出來,但是vim是沒有\(zhòng)0的,因此會(huì)出現(xiàn)亂碼
順便教一個(gè)清空文件的小技巧: > 文件名 ,前面什么都不寫,直接重定向 + 文件名:
$ > log.txt
3.系統(tǒng)傳遞標(biāo)記位
通過上文的講解,想必大家已對(duì)文件系統(tǒng)基本的接口有一個(gè)簡(jiǎn)單的了解,接下來我們將繼續(xù)深入講解,繼續(xù)學(xué)習(xí)系統(tǒng)傳遞標(biāo)志位,介紹 O_WRONLY, O_TRUNC, O_APPEND 和 O_RDONLY。
3.1.O_WRONLY 沒有像 w 那樣完全覆蓋?
C語(yǔ)言在 w模式打開文件時(shí),文件內(nèi)容是會(huì)被清空的,但是 O_WRONLY 好像并非如此?
當(dāng)前我們的 log.txt 內(nèi)有 5 行數(shù)據(jù),現(xiàn)在我們執(zhí)行下面的代碼:
int main(void)
{
umask(0);
// 當(dāng)我們只有 O_WRONLY 和 O_CREAT 時(shí)
int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);
if (fd < 0) {
perror("open");
return 1;
}
printf("fd: %d\n", fd);
// 修改:向文件寫入 2 行信息
int cnt = 0;
const char* str = "666\n"; // 修改:內(nèi)容改成666(方便辨識(shí))
while (cnt < 2) {
write(fd, str, strlen(str));
cnt++;
}
close(fd);
return 0;
}
?O_WRONLY 怎么沒有像 w 那樣完全覆蓋???
我們以前在 C語(yǔ)言中,w 會(huì)覆蓋把全部數(shù)據(jù)覆蓋,每次執(zhí)行代碼可都是會(huì)清空文件內(nèi)容的。
而我們的 O_WRONLY 似乎沒有全部覆蓋,曾經(jīng)的數(shù)據(jù)被保留了下來,并沒有清空!
其實(shí),沒有清空根本就不是讀寫的問題,而是取決于有沒有加 O_TRUNC 選項(xiàng)!
因此,只有 O_WRONLY 和 O_CREAT 選項(xiàng)是不夠的:
如果想要達(dá)到 w 的效果還需要增添 O_TRUNC
如果想到達(dá)到 a 的效果還需要 O_APPEND
下面我們就來介紹一下這兩個(gè)選項(xiàng)!
3.2.O_TRUNC 截?cái)嗲蹇眨▽?duì)標(biāo) w)
在我們打開文件時(shí),如果帶上 O_TRUNC 選項(xiàng),那么它將會(huì)清空原始文件。
如果文件存在,并且打開是為了寫入,O_TRUNC 會(huì)將該文件長(zhǎng)度縮短 (truncated) 為 0。
也就是所謂的 截?cái)嗲蹇?(Truncate Empty) ,我們默認(rèn)情況下文件系統(tǒng)調(diào)用接口不會(huì)清空文件的,
但如果你想清空,就需要給 open() 接口 帶上 O_TRUNC 選項(xiàng):
int main(void)
{
umask(0);
int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (fd < 0) {
perror("open");
return 1;
}
printf("fd: %d\n", fd);
// 向文件寫入 2 行信息
int cnt = 0;
const char* str = "666\n";
while (cnt < 2) {
write(fd, str, strlen(str));
cnt++;
}
close(fd);
return 0;
}
3.3.O_APPEND 追加(對(duì)標(biāo) a)
現(xiàn)在我們用 open,追加是不清空原始內(nèi)容的,所以我們不能加 O_TRUNC,得加 O_APPEND:
int fd = open("log.txt", O_WRONLY | O_CREATE | O_APPEND, 0666);
3.4.O_REONLY 讀取
如果我們想讀取一個(gè)文件,那么這個(gè)文件肯定是存在的,我們傳 O_RDONLY 選項(xiàng):
4.文件描述符:
在認(rèn)識(shí)返回值之前,先來認(rèn)識(shí)一下兩個(gè)概念: 系統(tǒng)調(diào)用 和 庫(kù)函數(shù)
- 上面的 fopen fclose fread fwrite 都是C標(biāo)準(zhǔn)庫(kù)當(dāng)中的函數(shù),我們稱之為庫(kù)函數(shù)(libc)。
- 而, open close read write lseek 都屬于系統(tǒng)提供的接口,稱之為系統(tǒng)調(diào)用接口
- 回憶一下我們講操作系統(tǒng)概念時(shí),畫的一張圖
系統(tǒng)調(diào)用接口和庫(kù)函數(shù)的關(guān)系,一目了然。
所以,可以認(rèn)為,f#系列的函數(shù),都是對(duì)系統(tǒng)調(diào)用的封裝,方便二次開發(fā)。
任何一個(gè)進(jìn)程,在啟動(dòng)的時(shí)候,默認(rèn)會(huì)打開當(dāng)前進(jìn)程的三個(gè)文件:
標(biāo)準(zhǔn)輸入 標(biāo)準(zhǔn)輸出 標(biāo)準(zhǔn)錯(cuò)誤
stdin stdout stderr C
cin cout cerr C++
輸出和錯(cuò)誤的區(qū)別:
#include<iostream>
#include<cstdio>
int main()
{
//C
printf("hello printf->stdout\n");
fprintf(stdout,"hello fprintf->stdout\n");
fprintf(stderr,"hello fprintf->stderr\n");
//C++
std::cout<<"hello cout->cout"<<std::endl;
std::cerr<<"hello cerr ->cout"<<std::endl;
return 0;
}
標(biāo)準(zhǔn)輸入——設(shè)備文件->鍵盤文件
標(biāo)準(zhǔn)輸出——設(shè)備文件->顯示器文件
標(biāo)準(zhǔn)錯(cuò)誤——設(shè)備文件->顯示器文件
所謂的輸出重定向是把輸入和輸出重定向到文件中,錯(cuò)誤留在了顯示器
標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤都會(huì)向顯示器打印,但是其實(shí)是不一樣的
0默認(rèn)是標(biāo)準(zhǔn)輸入
1默認(rèn)是標(biāo)準(zhǔn)輸出
2默認(rèn)是標(biāo)準(zhǔn)錯(cuò)誤
?因?yàn)長(zhǎng)inux下一切皆文件, 所以向顯示器打印,本質(zhì)上就是向文件中寫入,如何理解?
相信各位讀者應(yīng)該都聽過一個(gè)概念,C語(yǔ)言程序會(huì)默認(rèn)打開3個(gè)輸入輸出流,其中這三個(gè)輸入輸出流對(duì)應(yīng)的名為stdin,stdout,stderr,文件類型為FILE*,而FILE*是C語(yǔ)言的概念,底層對(duì)應(yīng)的文件描述符,其中stdin對(duì)應(yīng)0,stdout對(duì)應(yīng)1,stderr對(duì)應(yīng)2,換言之012被默認(rèn)已經(jīng)打開了,再打開時(shí)就是從3開始打開了,所謂的文件描述符,本質(zhì)其實(shí)就是數(shù)組下標(biāo)。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main()
{
char buf[1024];
ssize_t s = read(0, buf, sizeof(buf));
if(s > 0){
buf[s] = 0;
write(1, buf, strlen(buf));
write(2, buf, strlen(buf));
}
return 0;
}
而現(xiàn)在知道,文件描述符就是從0開始的小整數(shù)。當(dāng)我們打開文件時(shí),操作系統(tǒng)在內(nèi)存中要?jiǎng)?chuàng)建相應(yīng)的數(shù)據(jù)結(jié)構(gòu)來描述目標(biāo)文件。于是就有了file結(jié)構(gòu)體。表示一個(gè)已經(jīng)打開的文件對(duì)象。而進(jìn)程執(zhí)行open系統(tǒng)調(diào)用,所以必須讓進(jìn)程和文件關(guān)聯(lián)起來。每個(gè)進(jìn)程都有一個(gè)指針*files, 指向一張表files_struct,該表最重要的部分就是包涵一個(gè)指針數(shù)組,每個(gè)元素都是一個(gè)指向打開文件的指針!所以,本質(zhì)上,文件描述符就是該數(shù)組的下標(biāo)。所以,只要拿著文件描述符,就可以找到對(duì)應(yīng)的文件
4.1文件描述符底層原理
一個(gè)進(jìn)程是可以可以打開多個(gè)文件的,無非就是多調(diào)用幾次open,而我們的計(jì)算機(jī)中是同時(shí)存在大量進(jìn)程的,而這些進(jìn)程可能會(huì)打開各種各樣的文件,所以系統(tǒng)中在任何時(shí)刻都可能存在大量已經(jīng)打開的文件,操作系統(tǒng)的功能之一就是文件管理,就是要對(duì)這些打開的文件進(jìn)行管理。
??而我們都知道,所謂管理就是先描述再管理,底層中描述文件的數(shù)據(jù)結(jié)構(gòu)叫做struce file,一個(gè)文件對(duì)應(yīng)一個(gè)struct file,大量的文件就有大量的struct file,我們只需將這些數(shù)據(jù)結(jié)構(gòu)用雙鏈表連接起來,所以對(duì)文件的管理就變成了對(duì)雙鏈表的增刪改查。而我們現(xiàn)在要做的,這些已經(jīng)被打開的文件那些文件屬于某個(gè)特定的進(jìn)程,就需要建立進(jìn)程和文件的對(duì)應(yīng)關(guān)系。
?進(jìn)程如何和打開的文件建立映射關(guān)系?打開的文件哪一個(gè)屬于我的進(jìn)程呢?
當(dāng)一個(gè)程序加載了就是一個(gè)進(jìn)程,進(jìn)程就會(huì)有task_struct
當(dāng)磁盤有一個(gè)文件,其實(shí)這個(gè)被打開的文件就會(huì)被os加載到內(nèi)存,會(huì)在內(nèi)存中創(chuàng)建一個(gè)files_struct包含了文件的大部分屬性
我們進(jìn)程的task_struct結(jié)構(gòu)體中也會(huì)有一個(gè)struct files_struct*files指針指向下面這個(gè)結(jié)構(gòu)體
在內(nèi)核中,task_struct 在自己的數(shù)據(jù)結(jié)構(gòu)中包含了一個(gè) struct files_struct *files (結(jié)構(gòu)體指針):
struct files_struct *files;
而我們剛才提到的 “數(shù)組” 就在這個(gè) file_struct 里面,該數(shù)組是在該結(jié)構(gòu)體內(nèi)部的一個(gè)數(shù)組。
struct file* fd_array[32];
4.2文件描述符的分配規(guī)則
文件描述符的分配規(guī)則:在files_struct數(shù)組當(dāng)中,找到當(dāng)前沒有被使用的最小的一個(gè)下標(biāo),作為新的文件描述符。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int fd = open("myfile", O_RDONLY);
if(fd < 0){
perror("open");
return 1;
}
printf("fd: %d\n", fd);
close(fd);
return 0;
}
關(guān)閉0或者2,在看
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
close(0);
//close(2);
int fd = open("myfile", O_RDONLY);
if(fd < 0){
perror("open");
return 1;
}
printf("fd: %d\n", fd);
close(fd);
return 0;
}
發(fā)現(xiàn)是結(jié)果是: fd: 0 或者 fd 2 可見
4.3理解:Linux 下一切皆文件
我之前一直說Linux下一切皆文件,我們一直不理解為什么,我們?cè)谶@里來好好理解一下這個(gè)話題
深灰色層:對(duì)應(yīng)的設(shè)備和對(duì)應(yīng)的讀寫方法一定是不一樣的。
黑色層:看見的都是 struct file 文件(包含文件屬性, 文件方法),OS 內(nèi)的內(nèi)存文件系統(tǒng)。
紅色箭頭:再往上就是進(jìn)程,如果想指向磁盤,通過 找到對(duì)應(yīng)的 struct file,根據(jù)對(duì)應(yīng)的 file 結(jié)構(gòu)調(diào)用讀寫方法,就可以對(duì)磁盤進(jìn)行操作了。如果想指向?qū)?yīng)的顯示器,通過 fd 找到 struct file……最后調(diào)用讀寫,就可以對(duì)顯示器操作了…… 以此類推。
我們會(huì)發(fā)現(xiàn)os將這些外設(shè)抽象成結(jié)構(gòu)體,因此os在操作的時(shí)候就變成了對(duì)struct file_struct的操作了,也就是變成了對(duì)文件的操作
我們使用os的本質(zhì):
都是通過進(jìn)程的方式進(jìn)行os的訪問!
操作系統(tǒng)層面,我們必須要訪問fd(文件描述符),我們才能找到文件 ,然后語(yǔ)言層訪問外設(shè)或者文件必須經(jīng)歷os
FILE是什么呢?誰(shuí)提供的?和我們剛剛講的內(nèi)核的struct file有關(guān)系嗎?
FILE是結(jié)構(gòu)體,是C語(yǔ)言給你提供的,沒有關(guān)系,要是硬扯的話就是上下層的關(guān)系
5.重定向
5.1fflush 函數(shù)
fflush 刷新緩沖區(qū)
int main(void)
{
close(1);
int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (fd < 0) {
perror("open");
return 1;
}
printf("fd: %d\n", fd);
fflush(stdout);
close(fd);
}
我們發(fā)現(xiàn)它內(nèi)容不往顯示器打印了,而變成在文件當(dāng)中,這不就是重定向嘛?。?!
此時(shí),我們發(fā)現(xiàn),本來應(yīng)該輸出到顯示器上的內(nèi)容,輸出到了文件 myfile 當(dāng)中,其中,fd=1。這種現(xiàn)象叫做輸出重定向。常見的重定向有:>, >>, <
那重定向的本質(zhì)是什么呢?
5.2dup函數(shù)
函數(shù)原型如下:
#include <unistd.h>
int dup2(int oldfd, int newfd);
dup2 可以讓 newfd 拷貝 oldfd,如果需要可以將 newfd 先關(guān)閉。
newfd 是 oldfd 的一份拷貝,將后者 (newfd) 的內(nèi)容寫入前者 (oldfd),最后只保留 oldfd。
至于參數(shù)的傳遞,比如我們要輸出重定向 (stdout) 到文件中:
我們要重定向時(shí),本質(zhì)是將里面的內(nèi)容做改變,所以是要把 fd 的內(nèi)容拷貝到 1 中的:
oldfd:fd 《—newfd:1
當(dāng)我們最后進(jìn)行輸出重定向的時(shí)候,所有的內(nèi)容都和 fd 的內(nèi)容是一樣的了。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(void)
{
int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (fd < 0) {
perror("open");
return 0;
}
dup2(fd, 1); // fd ← 1
fprintf(stdout, "打開文件成功,fd: %d\n", fd);
fflush(stdout);
close(fd);
return 0;
}
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main() {
int fd = open("log.txt", O_CREAT | O_RDWR);
if (fd < 0) {
perror("open");
return 1;
}
close(1);
dup2(fd, 1);
for (;;) {
char buf[1024] = {0};
ssize_t read_size = read(0, buf, sizeof(buf) - 1);
if (read_size < 0) {
perror("read");
break;
}
printf("%s", buf);
fflush(stdout);
}
return 0;
}
6.緩沖區(qū)的理解
① 什么是緩沖區(qū)?緩沖區(qū)的本質(zhì)就是一段內(nèi)存。
②為什么要有緩沖區(qū)?為了 解放使用緩沖區(qū)的進(jìn)程時(shí)間。
緩沖區(qū)的存在可以集中處理數(shù)據(jù)刷新,減少 IO 的次數(shù),從而達(dá)到提高整機(jī)的效率的目的。
6.1語(yǔ)言級(jí)緩沖區(qū):
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int main(void)
{
// 給它們都帶上 \n
printf("Hello printf\n"); // stdout -> 1
fprintf(stdout, "Hello fprintf!\n");
fputs("Hello fputs!\n", stdout);
const char* msg = "Hello write\n";
write(1, msg, strlen(msg));
sleep(5);
return 0;
}
現(xiàn)在我們?cè)侔?\0 去掉:
int main(void)
{
printf("Hello printf"); // stdout -> 1
fprintf(stdout, "Hello fprintf!");
fputs("Hello fputs!", stdout);
const char* msg = "Hello write";
write(1, msg, strlen(msg));
sleep(5);
return 0;
}
write先打印出來,printf和fprintf fputs是五秒后打印出來的
然而 write 無論帶不帶 \n 都會(huì)立馬刷新,也就是說,只要 printf, fprint, fputs 調(diào)了 write 數(shù)據(jù)就一定顯示。
我們繼續(xù)往下深挖,stdout 的返回值是 FILE,F(xiàn)ILE 內(nèi)部有 struct,封裝很多的成員屬性,其中就包括 fd,還有該 FILE 對(duì)應(yīng)的語(yǔ)言級(jí)緩沖區(qū)。
C 庫(kù)函數(shù) printf, fwrite, fputs… 都會(huì)自帶緩沖區(qū),但是 write 系統(tǒng)調(diào)用沒有帶緩沖區(qū)。
我們現(xiàn)在提及的緩沖區(qū)都是用戶級(jí)別的緩沖區(qū),為提高性能,OS 會(huì)提供相關(guān)的 內(nèi)核級(jí)緩沖區(qū)。
庫(kù)函數(shù)在系統(tǒng)調(diào)用的上層,是對(duì)系統(tǒng)調(diào)用做的封裝,但是 write 沒有緩沖區(qū),這說明了:
該緩沖區(qū)是二次加上的,由 C 語(yǔ)言標(biāo)準(zhǔn)庫(kù)提供,我們來看下 FILE 結(jié)構(gòu)體:
放到緩沖區(qū),當(dāng)數(shù)據(jù)積累到一定程度時(shí)再刷。
- 每一個(gè)文件都有一個(gè) fd 和屬于它自己的語(yǔ)言級(jí)別緩沖區(qū)。
6.2緩沖區(qū)的刷新策略
常規(guī)策略:
- 無緩沖 (立即刷新)
- 行緩沖 (逐行刷新)
- 全緩沖 (緩沖區(qū)打滿,再刷新)
特殊情況:
- 進(jìn)程退出
- 用戶強(qiáng)制刷新(即調(diào)用 fflush)
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int main(void)
{
const char* str1 = "hello printf\n";
const char* str2 = "hello fprintf\n";
const char* str3 = "hello fputs\n";
const char* str4 = "hello write\n";
// C 庫(kù)函數(shù)
printf(str1);
fprintf(stdout, str2);
fputs(str3, stdout);
// 系統(tǒng)接口
write(1, str4, strlen(str4));
// 調(diào)用完了上面的代碼,才執(zhí)行的 fork
fork();
return 0;
}
到此為止都很正常
但如果我們此時(shí)重定向,比如輸入 ./a.out > log.txt,怪事就發(fā)生了!log.txt 中居然有 7 條消息:
當(dāng)我們重定向后,本來要顯示到顯示器的內(nèi)容經(jīng)過重定向顯示到了文件里,
如果對(duì)應(yīng)的是顯示器文件,刷新策略就是 行刷新
如果是磁盤文件,那就是 全刷新,即寫滿才刷新
- 一般C庫(kù)函數(shù)寫入文件時(shí)是全緩沖的,而寫入顯示器是行緩沖。
- printf fwrite 庫(kù)函數(shù)會(huì)自帶緩沖區(qū)(進(jìn)度條例子就可以說明),當(dāng)發(fā)生重定向到普通文件時(shí),數(shù)據(jù)的緩沖方式由行緩沖變成了全緩沖。
- 而我們放在緩沖區(qū)中的數(shù)據(jù),就不會(huì)被立即刷新,甚至fork之后
- 但是進(jìn)程退出之后,會(huì)統(tǒng)一刷新,寫入文件當(dāng)中。
- 但是fork的時(shí)候,父子數(shù)據(jù)會(huì)發(fā)生寫時(shí)拷貝,所以當(dāng)你父進(jìn)程準(zhǔn)備刷新的時(shí)候,子進(jìn)程也就有了同樣的一份數(shù)據(jù),隨即產(chǎn)生兩份數(shù)據(jù)。
- write 沒有變化,說明沒有所謂的緩沖。
綜上: printf fwrite 庫(kù)函數(shù)會(huì)自帶緩沖區(qū),而 write 系統(tǒng)調(diào)用沒有帶緩沖區(qū)。另外,我們這里所說的緩沖區(qū),都是用戶級(jí)緩沖區(qū)。其實(shí)為了提升整機(jī)性能,OS也會(huì)提供相關(guān)內(nèi)核級(jí)緩沖區(qū),不過不再我們討論范圍之內(nèi)。那這個(gè)緩沖區(qū)誰(shuí)提供呢? printf fwrite 是庫(kù)函數(shù), write 是系統(tǒng)調(diào)用,庫(kù)函數(shù)在系統(tǒng)調(diào)用的“上層”, 是對(duì)系統(tǒng)調(diào)用的“封裝”,但是 write 沒有緩沖區(qū),而 printf fwrite 有,足以說明,該緩沖區(qū)是二次加上的,又因?yàn)槭荂,所以由C標(biāo)準(zhǔn)庫(kù)提供。文章來源:http://www.zghlxwxcb.cn/news/detail-729940.html
如果有興趣,可以看看FILE結(jié)構(gòu)體:
typedef struct _IO_FILE FILE; 在/usr/include/stdio.文章來源地址http://www.zghlxwxcb.cn/news/detail-729940.html
在/usr/include/libio.h
struct _IO_FILE {
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags
//緩沖區(qū)相關(guān)
/* The following pointers correspond to the C++ streambuf protocol. */
/* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
char* _IO_read_ptr; /* Current read pointer */
char* _IO_read_end; /* End of get area. */
char* _IO_read_base; /* Start of putback+get area. */
char* _IO_write_base; /* Start of put area. */
在/usr/include/libio.h
struct _IO_FILE {
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags
//緩沖區(qū)相關(guān)
/* The following pointers correspond to the C++ streambuf protocol. */
/* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
char* _IO_read_ptr; /* Current read pointer */
char* _IO_read_end; /* End of get area. */
char* _IO_read_base; /* Start of putback+get area. */
char* _IO_write_base; /* Start of put area. */
nters correspond to the C++ streambuf protocol. */
/* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
char* _IO_read_ptr; /* Current read pointer */
char* _IO_read_end; /* End of get area. */
char* _IO_read_base; /* Start of putback+get area. */
char* _IO_write_base; /* Start of put area. */
在/usr/include/libio.h
struct _IO_FILE {
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags
//緩沖區(qū)相關(guān)
/* The following pointers correspond to the C++ streambuf protocol. */
/* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
char* _IO_read_ptr; /* Current read pointer */
char* _IO_read_end; /* End of get area. */
char* _IO_read_base; /* Start of putback+get area. */
char* _IO_write_base; /* Start of put area. */
到了這里,關(guān)于【Linux】深入理解系統(tǒng)文件操作(1w字超詳解)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!