文章目錄
- 前言
- 一、基礎概念
- 二、回顧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完善一下!
正文開始!
一、基礎概念
- 檔案=檔案内容+檔案屬性(屬性也是資料,即便你建立一個空檔案,也要占據磁盤空間)
- 檔案操作=檔案内容的操作+檔案屬性的操作(有可能再操作檔案的時候,即改變内容,又改變屬性)
- 對于檔案操作,我們首先要打開檔案。所謂"打開"檔案,究竟在幹什麼?将檔案的屬性或者内容加載到記憶體中!(馮諾依曼體系結構決定!)
- 是不是所有的檔案,都會被處于打開的狀态呢?沒有被打開的檔案,在哪裡呢?(隻在磁盤上存儲!)
- 打開的檔案(記憶體檔案)和磁盤檔案
- 通常我們打開檔案,通路檔案,關閉檔案,是誰在進行相關操作?fopen,fwrite,fread,fclose…->代碼->程式->當我們檔案程式,運作起來的時候,才會執行對應的代碼,然後才是真正的對檔案進行相關的操作(程序在做相關操作!!!)
- 程序和打開檔案的關系!
二、回顧C語言
2.1 對檔案進行寫操作
當我們以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;
}
預設這個"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
在這裡我們就可以看到"log.txt"就在目前cwd,也就是程序所處的路徑了。接下來我們有意識的更改目前路徑,這裡需要用到系統接口chdir();
#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);
}
}
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;
}
追加寫入,不斷的往檔案中新增内容—>追加重定向!
2.3 讀檔案
#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;
}
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);
}
}
當我們向檔案寫入的時候,最終是不是向磁盤寫入?
因為磁盤是硬體,是以隻有OS有資格向硬體寫入!
那麼能繞開作業系統嗎?
答:不能!那麼所有上層的通路檔案的操作,都必須貫穿作業系統!
作業系統是如何被上層使用的呢?
因為作業系統不相信任何人,是以必須使用作業系統提供的相關系統調用!
那麼為什麼要進行封裝檔案操作接口呢?
- 原生系統接口,使用成本比較高!
- 語言不具備跨平台性!
那麼封裝是如何解決跨平台性的問題呢?
- 窮舉所有底層的接口+條件編譯!
C庫提供的檔案通路接口是來自于系統調用!
那麼就能解釋不同的語言有不同的檔案通路接口!!
是以無論什麼語言的底層的接口是不變的!
是以這就要求我們必須學習檔案級别的系統接口!
總結
stdin&stdout&stderr
- C預設會打開三個輸入輸出流,分别是stdin,stdout,stderr
- 仔細觀察發現,這三個流的類型都是FILE*,fopen傳回值類型,檔案指針
打開檔案的方式
三、系統檔案I/O
接口介紹
open介紹
傳回值
打開檔案的選項
對于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;
}
我們通過傳入不同的選項,列印出不同的語句。
使用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;
}
是以我們要打開曾經不存在的檔案,我們要用到第二個open函數,帶有權限的設定!
int open(const char *pathname, int flags, mode_t mode);
int fd = open(“log.txt”,O_WRONLY|O_CREAT,0666);
可以看到我們建立檔案的權限是0666,可是實際顯示的是0664呢?
這就和我們之前學的掩碼umask有聯系了!
不清楚的話可以去看看這篇部落格權限的了解!
close
write
#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;
}
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;
}
修改我們的代碼後
我們發現此處直接覆寫曾經的資料,但是曾經的資料為什麼保留呢了?
因為我們沒有帶有截斷選項O_TRUNC
接下來我們帶上這個選項後
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;
}
現在我們就發現之前的資料被截斷了!
是以我們現在可以類比與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;
}
read
我們打開檔案就預設他是存在的,不需要攜帶"O_CREAT"選項
如果檔案不存在會傳回-1;
#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;
}
四、檔案描述符
在上面的實驗中我們了解到打開檔案後傳回給檔案描述符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;
}
-
為什麼檔案描述符預設是從3開始的呢?那麼0,1,2去哪了?
因為0,1,2被預設打開了
- 0:标準輸入,鍵盤
- 1:标準輸出,顯示器
- 2:标準錯誤,顯示器
-
這就與我們C語言聯系起來了!因為C語言封裝的系統接口。
首先我們之前在C語言中學到FILE*–>檔案指針—>FILE是什麼呢?—>C語言提供的結構體!–>封裝了多個成員
因為對于檔案操作而言,系統接口隻認識fd;(FILE内部必定封裝了fd)
-
0,1,2,3,4…,我們之前見過什麼樣的資料是這個樣子的呢?
這和我們之前學習的C/C++的數組下标相似
程序:記憶體檔案的關系—>記憶體—>被打開的檔案是存在記憶體裡面的!!!
一個程序可不可以打開多個檔案?–>當然可以,是以在核心中,程序:打開的檔案=1:n–>是以系統在運作中,有可能會存在大量的被打開的檔案!—>OS要不要對這些被打開的檔案進行管理呢??—>作業系統如何管理這些被打開的檔案呢??—>答案是先描述,在組織。
一個檔案被打開,在核心中,要建立該被打開的檔案的核心資料結構—先描述
那麼程序如何和打開的檔案建立映射關系呢??
先驗證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;
}
标準輸出流
int main()
{
const char* s="hello write!\n";
write(1,s,strlen(s));
}
标準錯誤流
int main()
{
const char* s="hello write!\n";
write(2,s,strlen(s));
}
我們看出标準錯誤流也列印到了顯示器上面。
至于标準輸出和标準錯誤的差別,我們稍後帶大家了解。
驗證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);
}
由上面的實驗我們可以得出,FILE結構體中的fileno就是封裝了檔案描述符fd!!!
0,1,2—>stdin,stdout,stderr—>鍵盤,顯示器,顯示器(這些都是硬體呀!)也用你上面的struct file來辨別對應的檔案嗎??
如何證明呢?我來帶大家看看LInux下的核心結構!!!
檔案描述符的配置設定規則
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);
}
我們發現把1關掉後什麼都沒有了!
因為printf->stdout->1雖然不在指向對應的顯示器了,但是已經指向了log.txt的底層struct_file對象!