天天看點

[ Linux ] 複習C檔案IO相關操作及認識檔案相關系統調用接口

檔案描述符:

  1. 檔案 = 檔案内容 + 檔案屬性。 (檔案屬性也是資料-->即便你建立一個空檔案,也要占據磁盤空間)
  2. 檔案操作 = 檔案内容的操作 + 檔案屬性的操作。(有可能在操作的過程中,即改變内容,有改變屬性)
  3. 所謂的"打開"檔案,究竟在幹什麼?将檔案的屬性或内容加載到記憶體彙總! -- 馮諾依曼體系決定
  4. 是不是所有的檔案,都會處于被打開的狀态?絕對不是!沒有被打開的檔案,在哪兒裡?隻在磁盤上存儲着!
  5. 打開的檔案(記憶體檔案)和磁盤檔案
  6. 通常我們打開檔案、通路檔案、關閉檔案。是誰在進行相關操作?fopen,fclose,fread,fwrite....... ---》 代碼 --》程式 --》 當我們的檔案程式運作起來的時候,才會執行對應的代碼,然後才是真正的對檔案進行相關的操作。
  7. 程序和打開檔案的關系

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;
}      

運作結果:

[ Linux ] 複習C檔案IO相關操作及認識檔案相關系統調用接口

這是我們寫的一段最簡單的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      
[ Linux ] 複習C檔案IO相關操作及認識檔案相關系統調用接口

我們發現有一個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      
[ Linux ] 複習C檔案IO相關操作及認識檔案相關系統調用接口
[ Linux ] 複習C檔案IO相關操作及認識檔案相關系統調用接口

0.1.2 複習a選項

[ Linux ] 複習C檔案IO相關操作及認識檔案相關系統調用接口

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;
}      
[ Linux ] 複習C檔案IO相關操作及認識檔案相關系統調用接口

0.1.3 複習w選項

[ Linux ] 複習C檔案IO相關操作及認識檔案相關系統調用接口

我們看到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;
}      
[ Linux ] 複習C檔案IO相關操作及認識檔案相關系統調用接口

我們看到了檔案被清空了。是以我們得到當我們以'w'方式打開檔案,準備寫入時,其實檔案已經被清空了。這是w和a的一個最大的差別!

0.1.4 複習讀操作

fgets

fgets是從特定的檔案流(FILE * stream)中讀取特定的資料(char *s),大小是size.傳回值成功了就是讀取的起始位址。我們也用C語言來實作一下

[ Linux ] 複習C檔案IO相關操作及認識檔案相關系統調用接口
[ Linux ] 複習C檔案IO相關操作及認識檔案相關系統調用接口
#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;
}      
[ Linux ] 複習C檔案IO相關操作及認識檔案相關系統調用接口

我們根據讀操作寫一個小程式玩一玩,我們的想法是./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指令大差不大啦~(感興趣的小夥伴自己也嘗試一下吧)

[ Linux ] 複習C檔案IO相關操作及認識檔案相關系統調用接口

至此,我們檔案基本操作(C語言)就複習到這裡~

1.認識檔案相關系統調用接口

我們回歸理論:當我們像檔案寫入的時候,最終是不是像磁盤寫入?肯定是,那麼作業系統是硬體,隻有作業系統有資格像硬體寫入。那麼能繞開作業系統嗎?答案是不能。所有的上層通路檔案的操作都必須貫穿作業系統。作業系統是如何被上層使用的呢?答案肯定是使用作業系統提供的相關系統調用!

那麼這裡有兩個問題?

  1. 如何了解printf? ---- 封裝了系統調用接口
  2. 我們怎麼從來沒見過系統調用接口呢? ---- 所有的語言都對系統接口做了封裝

那麼為什麼要做封裝?

  1. 原生系統接口,使用成本比價高(後期我們會看并且使用系統接口)
  2. 直接使用原生系統接口,語言不具備跨平台性。是以作業系統不同,作業系統所提供的原生接口不同。那麼封裝是如何解決跨平台問題的呢?--- 窮舉所有的底層接口+條件編譯 (根據具體對象調用對應的系統調用接口) 比如你用的是Linux系統,C語言就會把win,MacOs等接口關閉,隻露出Linux的接口。上層使用者用的都是一個函數,但是其實底層做了封裝。

1.1 見一見系統接口

1.1.1 open

open是檔案系統接口最重要的接口,沒有之一。是以我們先來看看這個接口。

第一個參數(const char* pathname):帶路徑的檔案名

第二個參數(flags):打開檔案傳遞的選項(下面會重點介紹)

第三個參數(mode):設定權限

傳回值:int , -1表示出現錯誤 (C語言FILE*)

[ Linux ] 複習C檔案IO相關操作及認識檔案相關系統調用接口
[ Linux ] 複習C檔案IO相關操作及認識檔案相關系統調用接口

第一個參數介紹:

帶路徑的檔案名,如果隻寫檔案名預設在目前路徑下建立。

第二個參數介紹:

flags标志位有很多選項,其中這些選項都是宏。其中系統傳遞标記位時,是用位圖結構來進行傳遞的。是以每一個宏标記隻需要有一個比特位是1,并且有其他宏對應的值不能重疊。在這裡我們講自己寫一個接口來驗證一下

[ Linux ] 複習C檔案IO相關操作及認識檔案相關系統調用接口
[ Linux ] 複習C檔案IO相關操作及認識檔案相關系統調用接口
#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;
}      
[ Linux ] 複習C檔案IO相關操作及認識檔案相關系統調用接口

通過這個例子我們就更好的了解了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;
}      
[ Linux ] 複習C檔案IO相關操作及認識檔案相關系統調用接口

第三個參數:

通過列印值我們發現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;

}      
[ Linux ] 複習C檔案IO相關操作及認識檔案相關系統調用接口

此時我們發現權限比剛才的正常多了,确實不在是亂碼現象了。但是0664,而不是0666。這是因為權限掩碼是umask。預設的umask是0002.是以如果我們就想要權限是0666.我們手動設定umask為0即可。我們再來看看

[ Linux ] 複習C檔案IO相關操作及認識檔案相關系統調用接口

1.1.2 close

我們把檔案打開了,總得關閉檔案吧。是以我們來看看關閉檔案的系統接口close.是以這個接口非常簡單好用。

[ Linux ] 複習C檔案IO相關操作及認識檔案相關系統調用接口
[ Linux ] 複習C檔案IO相關操作及認識檔案相關系統調用接口

1.1.3 write

我們把檔案打開和關閉了解了之後,接下來我們要對檔案進行操作了。第一個我們要了解的是write。像檔案内寫多西。

[ Linux ] 複習C檔案IO相關操作及認識檔案相關系統調用接口
[ Linux ] 複習C檔案IO相關操作及認識檔案相關系統調用接口

第一個參數(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;
}      
[ Linux ] 複習C檔案IO相關操作及認識檔案相關系統調用接口

我們看到成功的向log.txt檔案寫入了5條hello file

至此我們已經見過了最基本的系統接口,後續我們還要對系統調用接口詳細介紹使用

(本篇完)