檔案描述符:
- 檔案 = 檔案内容 + 檔案屬性。 (檔案屬性也是資料-->即便你建立一個空檔案,也要占據磁盤空間)
- 檔案操作 = 檔案内容的操作 + 檔案屬性的操作。(有可能在操作的過程中,即改變内容,有改變屬性)
- 所謂的"打開"檔案,究竟在幹什麼?将檔案的屬性或内容加載到記憶體彙總! -- 馮諾依曼體系決定
- 是不是所有的檔案,都會處于被打開的狀态?絕對不是!沒有被打開的檔案,在哪兒裡?隻在磁盤上存儲着!
- 打開的檔案(記憶體檔案)和磁盤檔案
- 通常我們打開檔案、通路檔案、關閉檔案。是誰在進行相關操作?fopen,fclose,fread,fwrite....... ---》 代碼 --》程式 --》 當我們的檔案程式運作起來的時候,才會執行對應的代碼,然後才是真正的對檔案進行相關的操作。
- 程序和打開檔案的關系
0.複習檔案操作(C語言)
0.1由一段C語言檔案操作産出幾個問題
#include <stdio.h>
int main()
{
FILE *fp = fopen("log.txt","w");//寫入
if(fp == NULL)
{
perror("fopen");
return 1;
}
const char* msg = "hello file";
int cnt = 1;
while(cnt < 20)
{
fprintf(fp,"%s:%d\n",msg,cnt++);
}
fclose(fp);
return 0;
}
運作結果:
這是我們寫的一段最簡單的C語言代碼,這段代碼可以産出幾個問題:
0.1.1 log.txt生成時沒有帶路徑,預設這個檔案會在哪裡形成呢?
我們都知道是在目前路徑。那麼什麼是目前路徑呢?目前路徑是源代碼所在的路徑嗎?其實這種說法是一種感性的認識,其實目前路徑是程序所在的路徑。為了驗證這一結論。我們使用指令來檢視一下。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
FILE *fp = fopen("log.txt","w");//寫入
if(fp == NULL)
{
perror("fopen");
return 1;
}
printf("pid:%d\n",getpid());//擷取目前程序的pid
while(1)
{
sleep(1);
}
const char* msg = "hello file";
int cnt = 1;
while(cnt < 20)
{
fprintf(fp,"%s:%d\n",msg,cnt++);
}
fclose(fp);
return 0;
}
使用指令檢視
ls /proc/pid值 -l
我們發現有一個cwd(current working directory)--目前工作路徑
是以我們驗證了:目前路徑是目前程序所處的工作路徑。
此時我們如果對目前工作路徑進行修改,我們仍然可以得到想要的結果(此時我們更改目前的工作路徑到/home/Lxy)
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
chdir("/home/Lxy");//更改目前程序的工作路徑
FILE *fp = fopen("log.txt","w");//寫入
if(fp == NULL)
{
perror("fopen");
return 1;
}
printf("pid:%d\n",getpid());
while(1)
{
sleep(1);
}
const char* msg = "hello file";
int cnt = 1;
while(cnt < 20)
{
fprintf(fp,"%s:%d\n",msg,cnt++);
}
fclose(fp);
return 0;
}
我們繼續使用指令來進行檢視
ls /proc/pid值 -l
0.1.2 複習a選項
a 選項是一個寫入操作,寫入到檔案的結尾。也就是追加操作。我們趕緊有C語言來看看效果
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
FILE *fp = fopen("log.txt","a");//寫入
if(fp == NULL)
{
perror("fopen");
return 1;
}
const char* msg = "hello file";
int cnt = 1;
while(cnt <= 5)
{
fprintf(fp,"%s:%d\n",msg,cnt++);
}
fclose(fp);
return 0;
}
0.1.3 複習w選項
我們看到w操作解釋的第一句話是Truncate file to zero length or create text file for writing. (将檔案截斷為零長度或建立文本檔案進行寫入).意思就是如果檔案不存在則建立之;如果檔案已經存在則從頭開始寫入。我們首先驗證一下存在不寫會有什麼結果。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
FILE *fp = fopen("log.txt","w");//寫入
if(fp == NULL)
{
perror("fopen");
return 1;
}
fclose(fp);
return 0;
}
我們看到了檔案被清空了。是以我們得到當我們以'w'方式打開檔案,準備寫入時,其實檔案已經被清空了。這是w和a的一個最大的差別!
0.1.4 複習讀操作
fgets
fgets是從特定的檔案流(FILE * stream)中讀取特定的資料(char *s),大小是size.傳回值成功了就是讀取的起始位址。我們也用C語言來實作一下
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
//chdir("/home/Lxy");//更改目前程序的工作路徑
FILE *fp = fopen("log.txt","r");//寫入
if(fp == NULL)
{
perror("fopen");
return 1;
}
char buffer[64];
while(fgets(buffer,sizeof(buffer),fp) != NULL)
{
printf("echo:%s",buffer);
}
fclose(fp);
return 0;
}
我們根據讀操作寫一個小程式玩一玩,我們的想法是./myfile filename 可以列印出檔案的内容
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main(int argc,char* argv[])
{
if(argc != 2)
{
printf("Usage:%s filename\n",argv[0]);
return 1;
}
FILE * fp = fopen(argv[1],"r");
if(fp == NULL)
{
perror("fopen");
return 1;
}
char buffer[64];
while(fgets(buffer,sizeof(buffer),fp) != NULL)
{
printf("%s",buffer);
}
fclose(fp);
return 0;
}
我們來一起看看吧~我們發現我們所寫的和系統提供的cat指令大差不大啦~(感興趣的小夥伴自己也嘗試一下吧)
至此,我們檔案基本操作(C語言)就複習到這裡~
1.認識檔案相關系統調用接口
我們回歸理論:當我們像檔案寫入的時候,最終是不是像磁盤寫入?肯定是,那麼作業系統是硬體,隻有作業系統有資格像硬體寫入。那麼能繞開作業系統嗎?答案是不能。所有的上層通路檔案的操作都必須貫穿作業系統。作業系統是如何被上層使用的呢?答案肯定是使用作業系統提供的相關系統調用!
那麼這裡有兩個問題?
- 如何了解printf? ---- 封裝了系統調用接口
- 我們怎麼從來沒見過系統調用接口呢? ---- 所有的語言都對系統接口做了封裝
那麼為什麼要做封裝?
- 原生系統接口,使用成本比價高(後期我們會看并且使用系統接口)
- 直接使用原生系統接口,語言不具備跨平台性。是以作業系統不同,作業系統所提供的原生接口不同。那麼封裝是如何解決跨平台問題的呢?--- 窮舉所有的底層接口+條件編譯 (根據具體對象調用對應的系統調用接口) 比如你用的是Linux系統,C語言就會把win,MacOs等接口關閉,隻露出Linux的接口。上層使用者用的都是一個函數,但是其實底層做了封裝。
1.1 見一見系統接口
1.1.1 open
open是檔案系統接口最重要的接口,沒有之一。是以我們先來看看這個接口。
第一個參數(const char* pathname):帶路徑的檔案名
第二個參數(flags):打開檔案傳遞的選項(下面會重點介紹)
第三個參數(mode):設定權限
傳回值:int , -1表示出現錯誤 (C語言FILE*)
第一個參數介紹:
帶路徑的檔案名,如果隻寫檔案名預設在目前路徑下建立。
第二個參數介紹:
flags标志位有很多選項,其中這些選項都是宏。其中系統傳遞标記位時,是用位圖結構來進行傳遞的。是以每一個宏标記隻需要有一個比特位是1,并且有其他宏對應的值不能重疊。在這裡我們講自己寫一個接口來驗證一下
#include <stdio.h>
#define PRINT_A 0x1 // 0000 00001
#define PRINT_B 0x2 // 0000 00010
#define PRINT_C 0x4 // 0000 00100
#define PRINT_D 0x8 // 0000 01000
#define PRINT_DFL 0x0
void Show(int flags)
{
if(flags & PRINT_A) printf("Hello A\n");
if(flags & PRINT_B) printf("Hello B\n");
if(flags & PRINT_C) printf("Hello C\n");
if(flags & PRINT_D) printf("Hello D\n");
if(flags == PRINT_DFL) printf("hello Default\n");
}
int main()
{
printf("hello Default\n");
Show(PRINT_DFL);
printf("Hello A\n");
Show(PRINT_A);
printf("Hello B\n");
Show(PRINT_B);
printf("PRINT_A和PRINT_B\n");
Show(PRINT_A | PRINT_B);
printf("PRINT_C和PRINT_D\n");
Show(PRINT_C | PRINT_D);
printf("PRINT_A和PRINT_B和PRINT_C和PRINT_D\n");
Show(PRINT_A | PRINT_B | PRINT_C | PRINT_D);
return 0;
}
通過這個例子我們就更好的了解了open的第二個參數,介紹這兩個參數後,我們就可以使用open系統函數了,我們也用C語言來實作一下
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int fd = open("log.txt",O_WRONLY | O_CREAT);
if(fd < 0 )
{
perror("open");
return 1;
}
printf("fd: %d\n",fd);
return 0;
}
第三個參數:
通過列印值我們發現fd傳回值确實是一個int型。而且我們發現log.txt檔案的權限部分是一對亂碼,原因是,當我們建立一個檔案的時候,我們要設定檔案的權限。是以我們得出一個結論,如果我們要建立一個不存在的檔案,不能使用兩個參數的open接口,而是要使用帶有權限參數的open接口。我們重新建立一下log.txt,并設定權限位0666
int main()
{
int fd = open("log.txt",O_WRONLY | O_CREAT,0666);
if(fd < 0 )
{
perror("open");
return 1;
}
printf("fd: %d\n",fd);
return 0;
}
此時我們發現權限比剛才的正常多了,确實不在是亂碼現象了。但是0664,而不是0666。這是因為權限掩碼是umask。預設的umask是0002.是以如果我們就想要權限是0666.我們手動設定umask為0即可。我們再來看看
1.1.2 close
我們把檔案打開了,總得關閉檔案吧。是以我們來看看關閉檔案的系統接口close.是以這個接口非常簡單好用。
1.1.3 write
我們把檔案打開和關閉了解了之後,接下來我們要對檔案進行操作了。第一個我們要了解的是write。像檔案内寫多西。
第一個參數(int fd):
fd,特定的檔案描述符。也就是向那個檔案寫。
第二個參數(const void* buf):
寫入緩沖區的起始位址
第三個參數(count):
寫入緩沖區的大小
我們也用C語言來練一練
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
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 file\n";
while(cnt<5)
{
write(fd,str,strlen(str));
cnt++;
}
//關閉檔案
close(fd);
return 0;
}
我們看到成功的向log.txt檔案寫入了5條hello file
至此我們已經見過了最基本的系統接口,後續我們還要對系統調用接口詳細介紹使用
(本篇完)