Linux-基礎IO+minishell重定向

文件的輸入輸出在c語言之前瞭解過。比如標準庫的IO接口

回顧C語言的庫函數

fopen,fread,fwrite,fseek,fclose這幾個函數。關鍵在於參數的使用。

例如fopen,FILE *fopen(const char *path, const char *mode),在不同的模式下打開文件所能作的操作也不同。

文件使用方式 含義 如果指定文件不存在
r–只讀 爲了輸入數據,打開一個已經存在的文本 文件出錯
w–只寫 爲了輸出數據,打開一個文本文件 建立一個新文件
a–追加 向文本文件尾添加數據 出錯
rb–只讀 爲了輸入數據,打開一個二進制文件 出錯
wb–只寫 爲了輸出數據,打開一個二進制文件 建立一個新文件
ab–追加 向一個二進制文件添加數據 出錯
r±-讀寫 爲了讀和寫,打開一個文本文件 出錯
w±-讀寫 爲了讀和寫,建立一個新的文件 建立一個新文件
a±-讀寫 打開一個文件,在文件尾進行讀寫 建立一個新的文件
rb±-讀寫 爲了讀和寫打開一個二進制文件 出錯
wb±-讀寫 爲了讀和寫,新建一個新的二進制文件 建立一個新的文件
ab±-讀寫 打開一個二進制文件,在文件尾進行讀和寫 建立一個新的文件

先回顧一下C語言中的文件操作

#include <stdio.h>    
#include <unistd.h>    
#include <string.h>    
  int main(){    
    int ret;    
    FILE *fp = NULL;    
    fp = fopen("./tmp.txt","r+");    
    if(fp == NULL){    
      perror("打開失敗");                                                                                               
      return -1;    
    }    
  fseek(fp, 5, SEEK_END);    
      
  char *ptr = "nihao---\n";    
      
  ret = fwrite(ptr, 1, strlen(ptr), fp);    
  printf("write item:%d\n",ret);    
      
  fseek(fp, 0, SEEK_SET);    
  char buf[1024] = {0};    
      
  ret = fread(buf, 1, 1023, fp);    
  perror("讀取失敗");    
  printf("read buf[%s]-[%d]",buf,ret);    
      
  fclose(fp);    
  return 0;    
  }

先打開當前路徑下的tmp.txt文件以讀寫的方式打開,之後利用fseek函數跳轉讀寫位置從當前文件位置的末尾開始向後五個位置跳轉。然後寫入數據,一個字節寫入,寫入長度爲*ptr的字符串長度,然後再跳轉到文件的開頭開始讀取數據讀取到buf中。fread返回值是讀取的數據長度。最後關閉文件。

這是C語言庫函數中的使用方法。

系統調用IO接口

文件描述符和文件流指針

標準庫接口使用文件流指針 *FILE

系統調用接口使用文件描述符 比如 int fd

進程中使用open函數打開某個文件,前提是需要我們將進程與文件聯繫起來。所以進程PCB中就有一個 *FILE指針,這個*FILE指針指向一個數組files_struct,這個數組內每個元素都對應了一個文件指針,文件指針指向各個FILE結構體。

文件流指針這個結構體中就包含了文件描述符,當使用標準庫接口進行io,則最終是通過文件流指針找到文件描述符進而對文件進行操作

文件描述符是一個正整型數字。文件描述符實際上就是一個數組下標,當進程每打開一個文件,都會使用struct file描述這個文件,並且將描述信息添加到struct file這個結構中的file結構體數組中,並且向用戶返回數組下標作爲文件描述符,用戶通過文件描述符對文件進行操作,再內核實際上是通過文件描述符找到文件描述信息,進而操作文件。

​ 標準輸入 標準輸出 標準錯誤

​ stdin stdout stderr

​ 0 1 2

在這裏插入圖片描述

文件描述符分配規則:最小未使用

系統調用函數

open write read close lseek

int open(const char *pathname, int flags, mode_t mode);
ssize_t write(int fd, const void *buf, size_t count);
ssize_t read(int fd, void *buf, size_t count);
off_t lseek(int fd, off_t offset, int whence);
int open(const char *pathname, int flags, mode_t mode);
pathname:文件路徑名
flags:選項標誌
必選項:
	O_RDONLY 只讀
	O_WRONLY 只寫
	O_REWR 可讀可寫
可選項:
	O_CREAT 文化不存在則創建,存在則打開
	O_EXCL 與O_CREAT同用時,若文件存在則報錯
	O_APPEND 寫追加模式
mode:創建文件時給定權限 (八進制數字)
	mode &~umask)
返回值:文件描述符-正整數	錯誤:-1
ssize_t write(int fd, const void *buf, size_t count);
fd:打開文件所返回的文件描述符
buf:要向文件寫入數據
count:要寫入的數據長度
返回值:實際的寫入字節數	錯誤:-1
ssize_t read(int fd, void *buf, size_t count);
fd:打開文件所返回的文件描述符
buf:對讀取到的數據進行存儲的位置
count:要讀取的數據長度
返回值:實際的讀取字節數	錯誤:-1
off_t lseek(int fd, off_t offset, int whence);
fd:打開文件所返回的文件描述符
offset:偏移量
whence:偏移位置
	SEEK_SET
	SEEK_CUR
	SEEK_END
返回值:返回當前位置到文件起始位置的偏移量

掌握知識的最好辦法還是結合代碼!!!

#include <stdio.h>
#inlcude <unistd.h>
#include <string.h> 
#include <fcntl.h>
#include <stdlib.h>
int main(){
    //mode_t umask
    //修改調用進程的文件創建權限掩碼
    umask(0);
    int fd = open("./tmp.txt",O_RDWR | O_CREAT | O_APPEND,0777);
    if(fd < 0){
      perror("open error");
      return -1;
    }
    char buf[1024] = "nihaoa~~!!";
    int ret = write(fd, buf, strlen(buf));
    if(ret < 0){
      perror("write error");
      return -1;
    }
    lseek(fd, 0, SEEK_SET);
  
    memset(buf, 0x00, 1024);//對buf數組所在的內存空間全部初始化爲0,初始化的長度爲1024
    ret = read(fd, buf, 1023);
    if(ret < 0){
      perror("read error");
      return -1;
    }
    printf("read buf:[%s]\n", buf);
    close(fd);
    return 0;
  }

在這裏插入圖片描述

運行如圖,系統調用函數還是比較簡單的。

標準輸入輸出的重定向

這裏要學習一個dup2函數。

函數dup和dup2提供了複製文件描述符的功能。他們通常用於stdin,stdout或進程的stderr的重定向。

int dup2(int oldfd, int newfd);

dup2用來複制參數oldfd所指的文件描述符,並將oldfd拷貝到參數newfd後一起返回。若參數newfd爲一個打開的文件描述符,則newfd所指的文件會先被關閉,若newfd等於oldfd,則返回newfd,而不關閉newfd所指的文件。dup2所複製的文件描述符與原來的文件描述符共享各種文件狀態。共享所有的鎖定,讀寫位置和各項權限或flags。

返回值:如果成功則返回新的文件描述符,否則出錯返回-1.

由dup2函數返回的新文件描述符一定是當前文件描述符可用的最小值

#include <stdio.h>    
#include <unistd.h>    
#include <stdlib.h>    
#include <fcntl.h>    
int main(){    
  int fd = open("./tmp.txt",O_RDWR,0777);                                                                               
    
  dup2(fd, 1);    
    
  printf("%d\n",fd);    
    
  fflush(stdout);    
  close(fd);    
    
  return 0;    
} 

此時輸出的結果爲1,因爲oldfd爲打開文件的文件描述符,本應該爲3,但是調用了dup2函數,此時stdout關閉了,將oldfd複製進去此時在返回新的文件描述符即爲1。

基礎IO3

模擬實現minishell重定向

1、接收標準輸入數據

2、解析命令(判斷是否包含重定向符號 > \ >>)

3、如果包含,則認爲需要輸出重定向,這時候獲取重定向符號後邊的文件名將重定向符號替換成’\0’

4、在子進程中打開文件,將標準輸出重定向到這個文件,進行程序替換

#include <stdio.h>    
#include <stdlib.h>    
#include <fcntl.h>    
#include <string.h>    
#include <unistd.h>    
#include <ctype.h>    
#include <wait.h>                                                                                                       
//之前寫過一個關於minishell的文件,先對之前的一些進行封裝函數    
    
char buf[1024] = {0};    
char *argv[32];    
int argc = 0;    
    
    
void do_face(){    
  printf("[liuyucheng@localhost]$ ");    
  fflush(stdout);    
  memset(buf, 0x00, 1024);    
  if(scanf("%[^\n]",buf) != 1){    
    getchar();    
  }    
}    
    
void do_parse(){    
  char *ptr = buf;    
  argc = 0;    
  while(*ptr != '\0'){    
   if(!isspace(*ptr)){
     argv[argc++] = ptr; 
     while(!isspace(*ptr) && *ptr != '\0'){
       ptr++;
     }
    }else{
      *ptr = '\0';
      ptr++;
    }
  }
  argv[argc] = NULL;
  return;
}

int main(){
  while(1){
  do_face();
  int redirect = 0;
  char *file = NULL;
  char *ptr = buf;
  while(*ptr != '\0'){
    if(*ptr == '>'){                                                                                                    
      redirect = 1;//清空重定向
      *ptr++ = '\0';
      if(*ptr == '>'){
        redirect = 2;
        *ptr++ = '\0';
      }
      while(isspace(*ptr) && *ptr != '\0'){
        ptr++;
      }
      file = ptr;
      while(!isspace(*ptr) && *ptr != '\0'){
        ptr++;
      }
      *ptr = '\0';
    }
    ptr++;
  }
  do_parse();
  int pid = fork();
  if(pid < 0){
    exit(-1);
  }else if(pid == 0){
    if(redirect == 1){
      int fd = open(file, O_CREAT | O_WRONLY | O_TRUNC, 0664);
      dup2(fd, 1);
    }else if(redirect == 2){
      int fd = open(file, O_CREAT | O_WRONLY | O_TRUNC, 0664);                                                          
      dup2(fd, 1);
    }
    execvp(argv[0], argv);
    exit(0);
  }
  wait(NULL);
  }
  return 0;
}

Linux ext2文件系統

注:圖中應爲inode。

基礎IO4

上圖是一個磁盤文件系統的內置。

每個分區都有一個文件系統,不同的分區擁有不同的文件系統。

什麼是inode?inode裏面包含了大小,權限,用戶,時間,塊位置

利用指令ls -i可以查看inode節點號

存儲文件的流程

通過inode bitmap在inode table(表結構)找到空閒的inode節點,通過data bitmap在數據塊區域找到空閒數據塊,將數據塊位置信息,記錄到inode節點中,將文件數據寫入到數據塊中;將文件名和inode節點名寫入父目錄中。

目錄文件中:存放了一張目錄下有什麼文件的表,表中記錄了文件名。inode節點號—>目錄項

當我們 cat ./a.txt輸出文件內容時,在當前目錄文件中查找文件名信息,通過文件名獲取inode節點號,通過inode節點號,找到inode節點,進而訪問數據塊,讀取數據進行打印。

軟硬鏈接

創建硬鏈接:ln a.txt b.txt

創建軟鏈接:ln -s a.txt a.soft

硬鏈接是一個文件的另一個名字,跟源文件並沒有什麼區別,----inode節點號相同

軟鏈接是一個獨立的文件,像是一個文件的快捷方式,----inode節點號不同

刪除源文件,軟鏈接失效---->通過記錄的源文件名路徑查找源文件數據;

硬鏈接無影響---->通過inode節點找文件只是鏈接數-1

軟鏈接可以針對目錄進行創建,硬鏈接不可以

軟鏈接可以跨分區建立,硬鏈接不可以

靜態庫與動態庫

靜態庫:名字一般爲libxxx.a,編譯時會整合到可執行程序中。程序在編譯鏈接的時候把庫的代碼鏈接到可執行文件中。程序運行的時候將不再需要靜態庫 。優點是運行時不需要外部函數庫支持,缺點是編譯後程序較大,一旦靜態庫改變,程序需要重新編譯。

動態庫:名字一般爲libxxx.M.N.so,M爲主版本號,N爲副版本號 ,程序在運行的時候纔去鏈接動態庫的代碼,多個程序共享使用庫的代碼。優點是運行時,有需要時才動態調用外部庫中的函數,節省空間,缺點是運行環境中必須提供相應的庫,動態庫更新升級方便。

建立靜態庫

gcc -fPIC -c b.c -o b.o產生位置無關代碼

​ 生成靜態庫gcc -c b.c -o b.o

ar -cr libmytest.a b.o生成靜態庫

gcc選項:

​ -fPIC:產生位置無關代碼

​ --share:生成一個共享庫而不是可執行程序

​ ar:靜態庫打包所有命令

​ -c 創建

​ -r 替換

建立動態庫

生成動態庫gcc -fPIC -c b.c -o b.o

gcc --share b.o -o libmytest.so生成動態庫

庫的使用

動態庫—>libmytest.so 靜態庫—>libmytest.a

同名動態和靜態庫,先鏈接動態庫

鏈接庫的時候:gcc a.c -o main -lmytest。如果報錯,找不到庫(鏈接庫的查找路徑----庫的查找路徑)

庫鏈接的時候和運行加載的時候都需要在指定目錄下

庫文件的默認查找路徑/lib64 /usr/lib64

設置環境變量:LIBRARY_PATH=.(庫的鏈接路徑)------>使用選項 ’-L‘

因爲gcc默認是動態鏈接—因此優先使用動態庫生成可執行程序

注意!

通常我們自己鏈接靜態庫生成可執行程序的時候,並不是使用-static靜態鏈接,而是將靜態庫放到指定路徑下,然後直接使用gcc -L選項指定庫的鏈接路徑鏈接靜態庫生成可執行程序

-static:作用是可執行程序使用靜態鏈接生成,不依賴任何動態庫

關於基礎IO的總結

最重要的還是區分系統調用和庫函數調用的區別和用法。在不同的語言中有着不同的用法,Linux下的使用可能還要考慮到進程的創建,程序替換等操作。

還有就是文件系統的理解與inode節點的作用,磁盤的分配有着獨特的規則,通過inode來調用分配空間的使用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章