天天看點

[Linux]----檔案操作(複習C語言+檔案描述符)

文章目錄

  • 前言
  • 一、基礎概念
  • 二、回顧C語言
  • 2.1 對檔案進行寫操作
  • 2.2 追加寫檔案
  • 2.3 讀檔案
  • 2.4 簡易cat功能
  • 總結
  • stdin&stdout&stderr
  • 打開檔案的方式
  • 三、系統檔案I/O
  • 接口介紹
  • open介紹
  • 使用open接口
  • close
  • write
  • read
  • 四、檔案描述符
  • 先驗證0,1,2就是标準的IO
  • 标準輸入流
  • 标準輸出流
  • 标準錯誤流
  • 驗證0,1,2和stdin,stdout,stderr的對應關系
  • 檔案描述符的配置設定規則
  • 總結

前言

今天這個小結節,我來大家來了解Linux下的檔案操作。首先我們來複習一下C語言的檔案操作,基于C語言的檔案操作我們對Linux的學習就會友善很多了!我帶大家首先來了解檔案相關系統的接口和檔案描述符,并且了解重定向!

最後在基于重定向,在下一小節将我上節寫的myshell完善一下!

​正文開始!​

一、基礎概念

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

二、回顧C語言

2.1 對檔案進行寫操作

[Linux]----檔案操作(複習C語言+檔案描述符)

當我們以w方式打開檔案,準備寫入的時候,其實檔案已經先被清空了!

#include<stdio.h>
#include<unistd.h>

int main()
{
    FILE* fp=fopen("log.txt","w");
    if(fp==NULL)
    {
        perror("fopen");
        return 1;
    }
    const char* msg="hello rose!";
    int cnt=0;
    while(cnt<10)
    {
        fprintf(fp,"%s %d\n",msg,cnt);
        cnt++;
    }
    fclose(fp);
    return 0;
}      
[Linux]----檔案操作(複習C語言+檔案描述符)

預設這個"log.txt"檔案會在哪裡形成呢?—>目前路徑

那麼什麼是目前路徑呢?–>程序目前的路徑

接下來帶大家檢視程序的資訊

#include<stdio.h>
#include<unistd.h>

int main()
{
    FILE* fp=fopen("log.txt","w");
    if(fp==NULL)
    {
        perror("fopen");
        return 1;
    }
    printf("%d\n",getpid());
    while(1)
    {
        sleep(1);
    }//在這裡會一直休眠下去,直到我們殺掉這個程序
    const char* msg="hello rose!";
    int cnt=0;
    while(cnt<10)
    {
        fprintf(fp,"%s %d\n",msg,cnt);
        cnt++;
    }
    fclose(fp);
    return 0;
}      
ll /proc/程序id
[Linux]----檔案操作(複習C語言+檔案描述符)

在這裡我們就可以看到"log.txt"就在目前cwd,也就是程序所處的路徑了。接下來我們有意識的更改目前路徑,這裡需要用到系統接口chdir();

[Linux]----檔案操作(複習C語言+檔案描述符)
#include<stdio.h>
#include<unistd.h>

int main()
{
    chdir("/home/hulu");
    FILE* fp=fopen("log.txt","w");
    if(fp==NULL)
    {
        perror("fopen");
        return 1;
    }
    printf("%d\n",getpid());
    while(1)
    {
        sleep(1);
    }
}      
[Linux]----檔案操作(複習C語言+檔案描述符)
[Linux]----檔案操作(複習C語言+檔案描述符)

2.2 追加寫檔案

#include<stdio.h>
#include<unistd.h>

int main()
{
    FILE* fp=fopen("log.txt","a");
    if(fp==NULL)
    {
        perror("fopen");
        return 1;
    }
    const char* msg="hello rose!";
    int cnt=0;
    while(cnt<5)
    {
        fprintf(fp,"%s %d\n",msg,cnt);
        cnt++;
    }
    fclose(fp);
    return 0;
}      
[Linux]----檔案操作(複習C語言+檔案描述符)

追加寫入,不斷的往檔案中新增内容—>追加重定向!

[Linux]----檔案操作(複習C語言+檔案描述符)

2.3 讀檔案

[Linux]----檔案操作(複習C語言+檔案描述符)
#include<stdio.h>
#include<unistd.h>

int main()
{
    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語言+檔案描述符)

2.4 簡易cat功能

#include<stdio.h>
#include<unistd.h>

//myfile filename
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);
    }
}      
[Linux]----檔案操作(複習C語言+檔案描述符)
[Linux]----檔案操作(複習C語言+檔案描述符)

當我們向檔案寫入的時候,最終是不是向磁盤寫入?

因為磁盤是硬體,是以隻有OS有資格向硬體寫入!

那麼能繞開作業系統嗎?

答:不能!那麼所有上層的通路檔案的操作,都必須貫穿作業系統!

作業系統是如何被上層使用的呢?

因為作業系統不相信任何人,是以必須使用作業系統提供的相關系統調用!

那麼為什麼要進行封裝檔案操作接口呢?

  • 原生系統接口,使用成本比較高!
  • 語言不具備跨平台性!

那麼封裝是如何解決跨平台性的問題呢?

  • 窮舉所有底層的接口+條件編譯!

C庫提供的檔案通路接口是來自于系統調用!

那麼就能解釋不同的語言有不同的檔案通路接口!!

是以無論什麼語言的底層的接口是不變的!

是以這就要求我們必須學習檔案級别的系統接口!

總結

stdin&stdout&stderr

  • C預設會打開三個輸入輸出流,分别是stdin,stdout,stderr
  • 仔細觀察發現,這三個流的類型都是FILE*,fopen傳回值類型,檔案指針

打開檔案的方式

[Linux]----檔案操作(複習C語言+檔案描述符)

三、系統檔案I/O

接口介紹

open介紹

[Linux]----檔案操作(複習C語言+檔案描述符)

傳回值

[Linux]----檔案操作(複習C語言+檔案描述符)

打開檔案的選項

[Linux]----檔案操作(複習C語言+檔案描述符)
[Linux]----檔案操作(複習C語言+檔案描述符)
[Linux]----檔案操作(複習C語言+檔案描述符)

對于O_RDONLY,O_WRONLY,O_RDWR,O_APPEND,O_CREAT!這些都是宏!

系統傳遞标記位,使用位圖結構來進行傳遞的!

每一個宏标記,一般隻需要有一個比特位為1,并且和其他宏對于的值不能重疊。

代碼模拟實作

#include<stdio.h>

#define PRINT_A 0x1
#define PRINT_B 0x2
#define PRINT_C 0x4
#define PRINT_D 0x8
#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()
{
    Show(PRINT_DFL);
    Show(PRINT_A);
    Show(PRINT_B);
    Show(PRINT_A|PRINT_B);
    Show(PRINT_C|PRINT_D);
    Show(PRINT_A|PRINT_B|PRINT_C|PRINT_D);
    return 0;
}      

我們通過傳入不同的選項,列印出不同的語句。

[Linux]----檔案操作(複習C語言+檔案描述符)

使用open接口

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main()
{
  int fd = open("log.txt",O_WRONLY|O_CREAT);
  if(fd<0)
  {
    perror("open error");
    return 1;
  }
  printf("fd=%d\n",fd);

      return 0;
}      
[Linux]----檔案操作(複習C語言+檔案描述符)
[Linux]----檔案操作(複習C語言+檔案描述符)

是以我們要打開曾經不存在的檔案,我們要用到第二個open函數,帶有權限的設定!

int open(const char *pathname, int flags, mode_t mode);

int fd = open(“log.txt”,O_WRONLY|O_CREAT,0666);

[Linux]----檔案操作(複習C語言+檔案描述符)

可以看到我們建立檔案的權限是0666,可是實際顯示的是0664呢?

這就和我們之前學的掩碼umask有聯系了!

[Linux]----檔案操作(複習C語言+檔案描述符)

不清楚的話可以去看看這篇部落格權限的了解!

close

[Linux]----檔案操作(複習C語言+檔案描述符)

write

[Linux]----檔案操作(複習C語言+檔案描述符)
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main()
{
    int fd = open("log.txt",O_WRONLY|O_CREAT,0666);
    if(fd<0)
    {
      perror("open error");
      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語言+檔案描述符)

C在w方式打開檔案的時候,會清空的!

int main()
{
    int fd = open("log.txt",O_WRONLY|O_CREAT,0666);
    if(fd<0)
    {
      perror("open error");
      return 1;
    }
    printf("fd=%d\n",fd);
    int cnt=0;
    const char* str="aaaa";
    //const char* str="hello file!\n";
    while(cnt<5)
    {
      write(fd,str,strlen(str));
      cnt++;
    }

    close(fd);
    return 0;
}      

修改我們的代碼後

[Linux]----檔案操作(複習C語言+檔案描述符)

我們發現此處直接覆寫曾經的資料,但是曾經的資料為什麼保留呢了?

因為我們沒有帶有截斷選項O_TRUNC

[Linux]----檔案操作(複習C語言+檔案描述符)

接下來我們帶上這個選項後

int main()
{
    int fd = open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
    if(fd<0)
    {
      perror("open error");
      return 1;
    }
    printf("fd=%d\n",fd);
    int cnt=0;
    const char* str="hello rose!\n";
    while(cnt<5)
    {
      write(fd,str,strlen(str));
      cnt++;
    }

    close(fd);
    return 0;
}      
[Linux]----檔案操作(複習C語言+檔案描述符)

現在我們就發現之前的資料被截斷了!

是以我們現在可以類比與C語言的fopen,底層的open的選項就是"O_WRONLY|O_CREAT|O_TRUNC"!!!

接下來我們驗證追加選項"O_APPEND"

代碼如下

int main()
{
    int fd = open("log.txt",O_WRONLY|O_CREAT|O_APPEND,0666);
    if(fd<0)
    {
      perror("open error");
      return 1;
    }
    printf("fd=%d\n",fd);
    int cnt=0;
    const char* str="hello hulu!\n";
    while(cnt<5)
    {
      write(fd,str,strlen(str));
      cnt++;
    }

    close(fd);
    return 0;
}      
[Linux]----檔案操作(複習C語言+檔案描述符)

read

我們打開檔案就預設他是存在的,不需要攜帶"O_CREAT"選項

如果檔案不存在會傳回-1;

[Linux]----檔案操作(複習C語言+檔案描述符)
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define NUM 1024
int main()
{
    int fd = open("log.txt",O_RDONLY);
    if(fd<0)
    {
      perror("open error");
      return 1;
    }
    printf("fd=%d\n",fd);
    char buffer[NUM];
    while(1)
    {
      ssize_t s=read(fd,buffer,sizeof(buffer)-1);
      if(s>0)
      {
        buffer[s]='\0';
        printf("%s",buffer);
      }
      else
        break;
    }

    close(fd);
    return 0;
}      
[Linux]----檔案操作(複習C語言+檔案描述符)

四、檔案描述符

在上面的實驗中我們了解到打開檔案後傳回給檔案描述符fd=3,這是為什麼?

接下來先來看代碼,讓我們去了解檔案描述符

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{
    int fda=open("loga.txt",O_WRONLY|O_CREAT|O_APPEND,0666);
    int fdb=open("logb.txt",O_WRONLY|O_CREAT|O_APPEND,0666);
    int fdc=open("logc.txt",O_WRONLY|O_CREAT|O_APPEND,0666);
    int fdd=open("logd.txt",O_WRONLY|O_CREAT|O_APPEND,0666);
    int fde=open("loge.txt",O_WRONLY|O_CREAT|O_APPEND,0666);
    printf("fda=%d\n",fda);
    printf("fdb=%d\n",fdb);
    printf("fdc=%d\n",fdc);
    printf("fdd=%d\n",fdd);
    printf("fde=%d\n",fde);
    return 0;
}      
[Linux]----檔案操作(複習C語言+檔案描述符)
  1. 為什麼檔案描述符預設是從3開始的呢?那麼0,1,2去哪了?

    因為0,1,2被預設打開了

  • 0:标準輸入,鍵盤
  • 1:标準輸出,顯示器
  • 2:标準錯誤,顯示器
  • [Linux]----檔案操作(複習C語言+檔案描述符)
  • 這就與我們C語言聯系起來了!因為C語言封裝的系統接口。

    首先我們之前在C語言中學到FILE*–>檔案指針—>FILE是什麼呢?—>C語言提供的結構體!–>封裝了多個成員

    因為對于檔案操作而言,系統接口隻認識fd;(FILE内部必定封裝了fd)

  1. 0,1,2,3,4…,我們之前見過什麼樣的資料是這個樣子的呢?

    這和我們之前學習的C/C++的數組下标相似

    程序:記憶體檔案的關系—>記憶體—>被打開的檔案是存在記憶體裡面的!!!

    一個程序可不可以打開多個檔案?–>當然可以,是以在核心中,程序:打開的檔案=1:n–>是以系統在運作中,有可能會存在大量的被打開的檔案!—>OS要不要對這些被打開的檔案進行管理呢??—>作業系統如何管理這些被打開的檔案呢??—>答案是先描述,在組織。

    一個檔案被打開,在核心中,要建立該被打開的檔案的核心資料結構—先描述

    那麼程序如何和打開的檔案建立映射關系呢??

[Linux]----檔案操作(複習C語言+檔案描述符)

先驗證0,1,2就是标準的IO

标準輸入流

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main()
{
  char buffer[1024];
  ssize_t s=read(0,buffer,sizeof(buffer)-1);
  if(s>0)
  {
    buffer[s]='\0';
    printf("echo:%s",buffer);
  }
  return 0;
}      
[Linux]----檔案操作(複習C語言+檔案描述符)

标準輸出流

int main()
{
  const char* s="hello write!\n";
  write(1,s,strlen(s));
}      
[Linux]----檔案操作(複習C語言+檔案描述符)

标準錯誤流

int main()
{
  const char* s="hello write!\n";
  write(2,s,strlen(s));
}      
[Linux]----檔案操作(複習C語言+檔案描述符)

我們看出标準錯誤流也列印到了顯示器上面。

至于标準輸出和标準錯誤的差別,我們稍後帶大家了解。

驗證0,1,2和stdin,stdout,stderr的對應關系

int main()
{
    printf("stdin: %d\n",stdin->_fileno);
    printf("stdout: %d\n",stdout->_fileno);
    printf("stderr: %d\n",stderr->_fileno);
}      
[Linux]----檔案操作(複習C語言+檔案描述符)

由上面的實驗我們可以得出,FILE結構體中的fileno就是封裝了檔案描述符fd!!!

[Linux]----檔案操作(複習C語言+檔案描述符)

0,1,2—>stdin,stdout,stderr—>鍵盤,顯示器,顯示器(這些都是硬體呀!)也用你上面的struct file來辨別對應的檔案嗎??

[Linux]----檔案操作(複習C語言+檔案描述符)

如何證明呢?我來帶大家看看LInux下的核心結構!!!

[Linux]----檔案操作(複習C語言+檔案描述符)
[Linux]----檔案操作(複習C語言+檔案描述符)
[Linux]----檔案操作(複習C語言+檔案描述符)

檔案描述符的配置設定規則

int main()
{
    close(0);
    //close(1);
    //close(2);
    int fd=open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
    if(fd<0)
    {
        perror("open");
        return 1;
    }
    printf("fd=%d\n",fd);
}      
[Linux]----檔案操作(複習C語言+檔案描述符)

我們發現把1關掉後什麼都沒有了!

因為printf->stdout->1雖然不在指向對應的顯示器了,但是已經指向了log.txt的底層struct_file對象!

[Linux]----檔案操作(複習C語言+檔案描述符)

總結