日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

Linux 基础IO

發布時間:2024/5/14 linux 46 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Linux 基础IO 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

    • 先來段代碼回顧C文件接口
      • 寫文件
      • 讀文件
      • 輸出信息到顯示器,你有哪些方法
    • 默認打開的三個流:stdin & stdout & stderr
    • 系統接口
      • open
      • close
      • write
      • read
    • 文件描述符fd
    • 理解一切皆文件
    • 文件描述符的分配規則
    • 重定向
      • 重定向原理
      • dup2
    • FILE
    • 理解文件系統
      • 磁盤的概念
      • 磁盤線性存儲
      • 磁盤分區與格式化
      • 磁盤分區
      • 磁盤格式化
      • EXT2文件系統的存儲方案
      • 初識文件inode
          • stat命令
      • 理解硬鏈接
      • 理解軟鏈接
      • A,C,M三個時間
        • 三個時間的應用場景

先來段代碼回顧C文件接口

寫文件

#include<stdio.h int main() {FILE * fp=fopen("./log.txt","w");//"a",appand 追加寫入if(NULL==fp) {perror("OPEN"); return 1;}int cnt=10;const char* str="ahah\n";while(cnt--){fputs(str,fp);}fclose(fp); return 0; }

讀文件

#include<stdio.h int main() {FILE * fp=fopen("./log.txt","r");if(NULL==fp){perror("OPEN");return 1;}int cnt=10;char buff[128];while(fgets(buff,sizeof(buff),fp)){printf("%s\n",buff);}fclose(fp); return 0; }

輸出信息到顯示器,你有哪些方法

#include<stdio.h int main() {const char * msg="hello fwrite\n";fwrite(msg,strlen(msg),1,stdout);printf("hello printf\n");fprintf(stdout,"hello fprintf\n"); return 0; }

所有的文件操作,表現上都是進程執行對應的函數!進程對文件的操作,對文件的操作就要先打開文件,打開文件的本質就是加載文件相關的屬性,加載到內存!

操作系統中存在大量的進程,進程對文件的比例是 1:n,那么系統中就存在可能更多的,打開文件!打開文件是加載到內存中,os必須對文件進行管理,先描述后組織,
struct file{

? //包含了打開文件的相關屬性

? // 打開文件之間的鏈接屬性

}

默認打開的三個流:stdin & stdout & stderr

extern FILE *stdin; extern FILE *stdout; extern FILE *stderr;
  • C默認會打開三個輸入輸出流,分別是stdin, stdout, stderr
  • 仔細觀察發現,這三個流的類型都是FILE*, fopen返回值類型,文件指針

C語言對系統接口進行封裝,那么就會有一套屬于C語言的IO,但是最終都是訪問硬件(顯示器,硬盤,文件(磁盤),os是硬件的管理者,所有的語言上對“文件”的操作,都必須貫穿os!一切皆文件

所以,幾乎所有的語言層的輸入輸出函數,在底層一定需要使用os提供的系統調用!為了更好的使用文件操作,我們需要學習文件的系統調用接口。

下面是c語言提供好的函數接口,本質就是對系統接口進行封裝

文件操作函數功能
fopen打開文件
fclose關閉文件
fputc寫入一個字符
fgetc讀取一個字符
fputs寫入一個字符串
fgets讀取一個字符串
fprintf格式化寫入數據
fscanf格式化讀取數據
fwrite向二進制文件寫入數據
fread從二進制文件讀取數據
fseek設置文件指針的位置
ftell計算當前文件指針相對于起始位置的偏移量
rewind設置文件指針到文件的起始位置
ferror判斷文件操作過程中是否發生錯誤
feof判斷文件指針是否讀取到文件末尾

系統接口

open

? 在認識返回值之前,先來認識一下兩個概念: 系統調用 和 庫函數

上面的 fopen fclose fread fwrite 都是C標準庫當中的函數,我們稱之為庫函數(libc)。
而, open close read write lseek 都屬于系統提供的接口,稱之為系統調用接口

可以認為,f#系列的函數,都是對系統調用的封裝,方便二次開發。

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

參數的介紹:

1、pathname參數: 文件路徑

2、flags參數: 傳遞標志位

int有32個bit,一個bit,代表一個標志,若將一個比特位作為一個標志位,則理論上flags可以傳遞32種不同的標志位。

O_WRONLY O_RDONLY O_CREAT等都是只有一個比特位是1的數據,而且不重復

所以我們傳入多個標志,只需要給每個相鄰的標志按位或即可

注意:實際上傳入flags的每一個選項在系統當中都是以宏的方式進行定義的。

例如:

#define O_RDONLY 00 #define O_WRONLY 01 #define O_RDWR 02 #define O_CREAT 0100

因為每個標志都是獨立的,所以open內部就可以通過&按位與來進行區分選項,

例如:

int open(arg1, arg2, arg3){if (arg2&O_RDONLY){//設置了O_RDONLY選項}if (arg2&O_WRONLY){//設置了O_WRONLY選項}if (arg2&O_RDWR){//設置了O_RDWR選項}if (arg2&O_CREAT){//設置了O_CREAT選項}//... } 參數選項含義
O_RDONLY以只讀的方式打開文件
O_WRONLY以只寫的方式打開文件
O_APPEND以追加的方式打開文件
O_RDWR以讀寫的方式打開文件
O_CREAT當目標文件不存在時,創建文件

3、mode_t mode參數: 打開權限

例如 將mode設置為:0666,則創建出來的文件權限為: -rw-rw-rw-

但實際上創建出來文件的權限值還會受到umask(文件默認掩碼)的影響,實際創建出來文件的權限為:mode&(~umask)。umask的默認值一般為0002,當我們設置mode值為0666時實際創建出來文件的權限為0664。

注意: 當不需要創建文件時,open的第三個參數可以不必設置

4、返回值:
成功:新打開的文件描述符
失敗:-1

如下面顯示:

int main() { //FILE* fd=fopen("./log.txt","w");等于下面 int fd=open("./log.txt",O_WRONLY|O_CREAT,0644);//創建失敗返回-1;if(fd<0){printf("open error\n"); }close(fd); return 0; }

close

系統接口中使用close函數關閉文件,close函數的函數原型如下:

int close(int fd);

使用close函數時傳入需要關閉文件的文件描述符即可,若關閉文件成功則返回0,若關閉文件失敗則返回-1。

write

系統接口中使用write函數向文件寫入信息,write函數的函數原型如下:

頭文件#include <unistd.h> ssize_t write(int fd, const void *buf, size_t count);函數說明:write()會把參數buf所指的內存寫入count個字節到參數fd所指的文件內。返回值:如果順利write()會返回實際寫入的字節數(len)。當有錯誤發生時則返回-1,錯誤代碼存入errno中。三個參數:第一個參數 文件描述符fd第二個參數 無類型的指針buf,可以存放要寫的內容第三個參數 寫多少字節數strlen()用來讀取長度 #include <stdio.h #include <string.h #include <unistd.h #include <sys/types.h6 #include <sys/stat.h #include <fcntl.h int main() {int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);if (fd < 0){perror("open");return 1;}const char* msg = "hello syscall\n";for (int i = 0; i < 5; i++){write(fd, msg, strlen(msg));// ‘\0’不需要被寫入,字符串以'\0'為結尾屬于c語言的標記規則。}close(fd);return 0; }

read

系統接口中使用read函數從文件讀取信息,read函數的函數原型如下:

頭文件 :#include <unistd.h> ssize_t read(int fd, void *buf, size_t count); read函數的三個參數: (1)fd:文件描述符 (2)buf:指定讀入數據的數據緩沖區 (3)count:指定讀入的字節數 返回值: 成功:返回讀取的字節數 出錯:返回-1并設置errno 如果在調read之前已到達文件末尾,則這次read返回0

我們可以使用read函數,從文件描述符為fd的文件讀取count字節的數據到buf位置當中。

#include <stdio.h #include <string.h #include <unistd.h #include <sys/types.h6 #include <sys/stat.h #include <fcntl.h int main() {int fd = open("./log.txt", O_RDONLY);if(fd < 0){perror("open");return 1;}char buffer[1024];ssize_t s = read(fd, buffer, sizeof(buffer)-1);if(s > 0){buffer[s-1] = 0;// '\n' 也會被讀取,所以在最后一個字符抹去'\n'printf("%s\n", buffer);}

文件描述符fd

通過對open函數的學習,我們知道了文件描述符就是一個小整數

當我們打開文件時,操作系統在內存中要創建相應的數據結構來描述目標文件。于是就有了file結構體。表示一個已經打開的文件對象。而進程執行open系統調用,所以必須讓進程和文件關聯起來。每個進程都有一個指針files_struct*files, 指向一張表files_struct,該表最重要的部分就是包涵一個指針數組,每個元素都是一個指向打開文件的指針!所以,本質上,文件描述符就是該數組的下標。所以,只要拿著文件描述符,就可以找到對應的文件指針,找到文件指針,就可以對文件結構體進行操作,對文件結構體的操作就是對磁盤文件操作 。通過上面分析,C語言FILE結構體,里面一定會包含對應的文件描述符。

補充:那么文件是什么時候關閉的,準確的來說file文件對象什么時候被移除,這里會涉及到引用計數器;

int main() { int fd1=open("./log.txt",O_WRONLY|O_CREAT,0644);//創建失敗返回-1; int fd2=open("./log.txt",O_WRONLY|O_CREAT,0644);//創建失敗返回-1; int fd3=open("./log.txt",O_WRONLY|O_CREAT,0644);//創建失敗返回-1; int fd4=open("./log.txt",O_WRONLY|O_CREAT,0644);//創建失敗返回-1;if(fd1<0){printf("open error\n"); }printf("%d\n",fd1);printf("%d\n",fd2);printf("%d\n",fd3);printf("%d\n",fd4); return 0; } //打印 3 4 5 6

其中:0 1 2下標存放標準輸入,標準輸出和標準錯誤文件指針。0123456 文件描述符本質上是一個指針數組的下標。printf() 是C語言的庫函數,對open()進行封裝,printf()想要訪問文件必須有文件描述符,printf()默認向stdout輸入數據,而stdout指向的是一個struct FILE類型的結構體,該結構體當中有一個存儲文件描述符的變量,而stdout指向的FILE結構體中存儲的文件描述符就是1,因此printf實際上就是向文件描述符為1的文件輸出數據。類似scanf()函數也是如此,stdin 指向的結構體,該結構體當中有一個變量存儲文件描述符0;

理解一切皆文件

驅動層用來傳遞和獲取硬件數據,每個硬件的讀取方式都不一樣,所以每個硬件都會一一對應屬于自己的一套交互方法。

軟件的虛擬層VFS虛擬文件系統,操作系統一切皆文件,上層要打開鍵盤,往鍵盤寫入,操作系統就需要維護一個file結構體。C語言層面上實現多態,讓以后file指向某種設備時,調用同一個方法實現不同的方式。我們可以在file結構體里定義一批 int(*read)(),int(*write)()的函數指針,在創建file時,我們給這批函數指針指向某種設備對應驅動層函數。在struct_file的上層看來,所有的文件,讀就調用read(),寫就調用write(),根本不關心你到底是什么文件。所有在上層就有統一的視角,一切皆文件。

小結:當我們調用文件操作函數時,我們整個的過程是這樣的,通過參數fd找到進程里對應的文件指針,找到對應系統維護的文件結構體file,再調用write,read系列的對應方法。

磁盤文件vs內存文件

類似于程序與進程的關系,當程序被運行時,先創建進程PCB,mm_struct,頁表等系統級的結構體,加載相關的數據和代碼到內存中,通過頁表對虛擬內存與物理內存物理產生映射關系;

磁盤文件與內存文件,也是一樣的,打開的文件都會有一個對象,用來保存相關的文件屬性,文件對象間的鏈接關系類似于雙鏈表,在要讀取文件等操作時,才會把文件數據加載到物理內存中

文件描述符的分配規則

通過一下實驗進行證明:

通過上面的一下樣例,0,1,2文件描述符對應標準輸入輸出錯誤,現在我們看看關閉文件 0,然后再打開一個文件,我們都知道如果不關閉0那么新打開的文件的文件描述符為3。

int main() {close(0);int fd1=open("./log.txt",O_WRONLY|O_CREAT,0644);//創建失敗返回-1;if(fd1<0){printf("open error\n"); }printf("%d\n",fd1); return 0; } //打印 0

close(0) ,file*fd_array[0]里的指針不指向可用文件,也就是說array[0]沒有被使用,指向新打開的./log.txt文件對象的指針存放到array[]里,通過文件描述符的分配規則把指針存放到相應的下標里,并且返回該文件描述符。

文件描述符的分配規則:在files_struct里的file* fd_array[]數組當中,找到當前沒有被使用的最小的一個下標,作為新的文件描述符

重定向

重定向原理

重定向原理:基于文件描述符的分配規則;

#include <stdio.h #include <unistd.h #include <sys/types.h #include <sys/stat.h #include <fcntl.h int main() {close(1);int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);if (fd < 0){perror("open failed");return 1;}printf("hello\n");fflush(stdout);close(fd);return 0; }

原本我們的printf要把"hello\n" 輸出到顯示器上的,現在卻輸出到log.txt的文件里;

其實就是把文件描述符1 里的內容不指向任何一個struct file,然后打開文件時,通過文件描述符分配規則進行分配;

現在文件描述符1里就放了新打開的文件指針


追加重定向也是類似

在上述上,打開文件時按照追加的形式打開即可;

#include <stdio.h #include <unistd.h #include <sys/types.h #include <sys/stat.h #include <fcntl.h int main() {close(1);int fd = open("log.txt", O_WRONLY|O_APPEND|O_CREAT, 0666);if (fd < 0){perror("open failed");return 1;}printf("hello\n");fflush(stdout);close(fd);return 0; }

需要注意的是:

1.printf() 是C語言的庫函數,對open()進行封裝,printf()想要訪問文件必須有文件描述符,printf()默認向stdout輸入數據,而stdout指向的是一個struct FILE類型的結構體,該結構體當中有一個存儲文件描述符的變量,而stdout指向的FILE結構體中存儲的文件描述符就是1,因此printf實際上就是向文件描述符為1的文件輸出數據。類似scanf()函數也是如此,stdin 指向的結構體,該結構體當中有一個變量存儲文件描述符0;

2.上述重定向的代碼都是對標準輸出進行重定向,如果想對stderr重定向,只需要close(2),然后打開重定向后的文件。

dup2

我們想要把輸出到顯示器,重定向到log.txt文件里,那么我們只需要把file * fd_array[3] 的內容復制到file * fd_array[1]里即可;

操作系統給我們提供了dup2接口,我們可以使用它完成重定向。

int dup2(int oldfd, int newfd); **函數功能**: dup2會將fd_array[oldfd]的內容拷貝到fd_array[newfd]當中, 如果有必要的話我們需要先使用關閉文件描述符為newfd的文件。**函數返回值**: dup2如果調用成功,返回newfd,否則返回-1。使用dup2時,我們需要注意以下兩點:1、 如果oldfd不是有效的文件描述符,則dup2調用失敗, 并且此時文件描述符為newfd的文件沒有被關閉。2、如果oldfd是一個有效的文件描述符,但是newfd和oldfd具有相同的值, 則dup2不做任何操作,并返回newfd。 #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_APPEND|O_CREAT, 0666);if (fd < 0){perror("open failed");return 1;}close(1);dup2(fd,1);printf("hello dup2\n");fflush(stdout);close(fd);return 0; }

上述代碼用途為輸出重定向,open打開文件log.txt返回fd指針,關閉fd 1(標準輸出),把 fd_array[fd] 復制到fd_array[1]里;printf是C庫當中的IO函數,一般往 stdout 中輸出,但是stdout底層訪問文件的時候,找的還是fd:1, 但此時,fd:1
下標所表示內容,已經變成了myfile的地址,不再是顯示器文件的地址,所以,輸出的任何消息都會往文件中寫入,進而完成輸出重定向 。

dup系列詳解

linux 關閉打開的文件描述符,關閉它們后,如何重新打開stdout和stdin文件描述符?

詳解

FILE

上述我們知道了write,open,read這些都是系統接口,在C語言里fprintf,等函數對這些系統接口進行封裝,其中在C語言上有stdout,stdin,stderr流,其實就是一個FILE*的指針,調用printf時其實就是向stdout指向的 struct _IO_FILE 這個結構體里寫入,這個結構體包含了fd,用戶級緩存區;printf把數據寫入到c緩沖區里,printf就完成任務了,然后C語言的緩存區按照刷新規則刷新到操作系統的緩沖區里這里的刷新也需要調用系統接口,也需要fd,然后再按照操作系統的刷新機制進行對硬件的寫入;下面來詳細介紹

緩沖區的作用如果你了解可以不點進來

  • 因為IO相關函數與系統調用接口對應,并且庫函數封裝系統調用,所以本質上,訪問文件都是通過fd訪問的。
  • 所以C庫當中的FILE結構體內部,必定封裝了fd

FILE結構體如下:

typedef struct _IO_FILE FILE;// 在/usr/include/stdio.hstruct _IO_FILE {int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ #define _IO_file_flags _flags//緩沖區相關/* The following pointers correspond to the C++ streambuf protocol. *//* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */char* _IO_read_ptr; /* Current read pointer */char* _IO_read_end; /* End of get area. */char* _IO_read_base; /* Start of putback+get area. */char* _IO_write_base; /* Start of put area. */char* _IO_write_ptr; /* Current put pointer. */char* _IO_write_end; /* End of put area. */char* _IO_buf_base; /* Start of reserve area. */char* _IO_buf_end; /* End of reserve area. *//* The following fields are used to support backing up and undo. */char *_IO_save_base; /* Pointer to start of non-current get area. */char *_IO_backup_base; /* Pointer to first valid character of backup area */char *_IO_save_end; /* Pointer to end of non-current get area. */struct _IO_marker *_markers;struct _IO_FILE *_chain;int _fileno; //封裝的文件描述符 #if 0int _blksize; #elseint _flags2; #endif_IO_off_t _old_offset; /* This used to be _offset but it's too small. */#define __HAVE_COLUMN /* temporary *//* 1+column number of pbase(); 0 is unknown. */unsigned short _cur_column;signed char _vtable_offset;char _shortbuf[1];/* char* _save_gptr; char* _save_egptr; */_IO_lock_t *_lock; #ifdef _IO_USE_OLD_IO_FILE };

我們看到的 int _fileno成員變量;就是封裝后的文件描述符fd;

首先來段代碼研究一下:

#include <stdio.h #include <string.h int main() {const char *msg0="hello printf\n";const char *msg1="hello fwrite\n";const char *msg2="hello write\n";printf("%s", msg0);fwrite(msg1, strlen(msg0), 1, stdout);write(1, msg2, strlen(msg2));fork();return 0; }

運行結果:

hello printf hello fwrite hello write

如果輸出重定向到一個文件時(./hellofile):

文件內容:

hello write hello printf hello fwrite hello printf hello fwrite

我們發現 printf 和 fwrite (庫函數)都輸出了2次,而 write 只輸出了一次(系統調用)

一般C庫函數寫入文件時是全緩沖的,而寫入顯示器是行緩沖。
printf fwrite 庫函數會自帶緩沖區(進度條例子就可以說明),當發生重定向到普通文件時,數據的緩沖方式由行緩沖變成了全緩沖。
而我們放在緩沖區中的數據,就不會被立即刷新,甚至fork之后
但是進程退出之后,會統一刷新,寫入文件當中。
但是fork的時候,父子數據會發生寫時拷貝,所以當你父進程準備刷新的時候,子進程也就有了同樣的一份數據,隨即產生兩份數據。
write 沒有變化,說明沒有所謂的緩沖

c語言緩存區刷新機制:

  • 不緩沖。
  • 行緩沖。(常見的對顯示器進行刷新數據)
  • 全緩沖。(常見的對磁盤文件寫入數據)
  • ? 行緩存按行刷新

    ? 全緩沖按滿了就刷新,或者進程退出的時候,會刷新FILE內部的數據到os緩沖區

    該緩沖區由C語言提供,該緩沖區的位置在FILE結構體中,也就是說,這里的緩沖區是由C語言提供,在FILE結構體當中進行維護的,FILE結構體當中不僅保存了對應文件的文件描述符還保存了用戶緩沖區的相關信息。

    在來看幾個例子:

    a、沒有輸出重定向

    int main() {const char *msg0="hello printf\n";const char *msg1="hello fwrite\n";const char *msg2="hello write\n";printf("%s", msg0);fwrite(msg1, strlen(msg0), 1, stdout);write(1, msg2, strlen(msg2));return 0; } // 向屏幕輸出 /* hello printf hello fwrite hello write */

    b、輸出重定向到 log.txt

    int main() {int fd = open("log.txt", O_WRONLY|O_APPEND|O_CREAT, 0666);const char *msg0="hello printf\n";const char *msg1="hello fwrite\n";const char *msg2="hello write\n";close(1);dup2(fd,1);// 輸出重定向printf("%s", msg0);fwrite(msg1, strlen(msg0), 1, stdout);write(1, msg2, strlen(msg2));close(1);return 0; }

    運行結果:
    log.txt文件內容:hello write
    分析:
    輸出重定向以后,輸入到顯示器的內容都要輸出到log.txt里,首先printf fwrite會把內容放到FILE的緩沖區里,輸出到硬盤文件按照滿刷新規則,在close(1)關閉以后,進程退出做刷新緩沖區操作,這時候要向文件描述符1寫入,但是fd1已經沒使用了,所以最后c語言的接口沒有輸出到log.txt文件里,write()為系統接口不會按照c規則刷新,也就是在close(1)關閉前已經輸入到文件去了。
    解決方法:
    調用flush(stdout);
    緩沖區相關的代碼:

    //緩沖區相關 /* The following pointers correspond to the C++ streambuf protocol. */ /* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */ char* _IO_read_ptr; /* Current read pointer */ char* _IO_read_end; /* End of get area. */ char* _IO_read_base; /* Start of putback+get area. */ char* _IO_write_base; /* Start of put area. */ char* _IO_write_ptr; /* Current put pointer. */ char* _IO_write_end; /* End of put area. */ char* _IO_buf_base; /* Start of reserve area. */ char* _IO_buf_end; /* End of reserve area. */ /* The following fields are used to support backing up and undo. */ char *_IO_save_base; /* Pointer to start of non-current get area. */ char *_IO_backup_base; /* Pointer to first valid character of backup area */ char *_IO_save_end; /* Pointer to end of non-current get area. */

    補充:

    操作系統也有緩存區

    當我們刷新用戶緩沖區(例如c語言緩沖區)時,并不是立刻將數據刷新到磁盤或顯示器上,而是先將數據緩存到操作系統的緩存區里,然后操作系統的緩存區等待操作系統自制的刷新機制進行刷新。這一方面我們沒必要過多的了解,只需知道操作系統也是有緩沖區的;

    理解文件系統

    內存文件是如何管理的上述已經介紹了,下面我們來理解一下文件系統是如何管理磁盤文件的。

    磁盤的概念

    把緩沖區數據刷新到磁盤實際就是os把數據寫入到盤片上

    什么是磁盤?

    磁盤是一種永久性存儲介質,在計算機中,磁盤幾乎是唯一的機械設備。與磁盤相對應的就是內存,內存是掉電易失存儲介質,目前所有的普通文件都是在磁盤中存儲的。

    盤片—扇區: 盤片被分成許多扇形的區域

    磁道:盤片以盤片中心為圓心,不同半徑的同心圓。

    柱面:硬盤中,不同盤片相同半徑的磁道所組成的圓柱。

    每個磁盤有兩個面,每個面都有一個磁頭。

    磁盤查詢方案:

    磁盤的讀寫時是如何查詢讀寫位置的?

    盤面(磁頭)---- 磁道 ---- 扇區

  • 確定讀寫信息在磁盤的哪個盤面。
  • 確定讀寫信息在磁盤的哪個柱面。
  • 確定讀寫信息在磁盤的哪個扇區。
  • 注意:磁盤寫入的基本單位是:扇區------512字節

    磁盤線性存儲

    理解文件系統,我們必須將磁盤盤片想象成一個線性的存儲介質,例如磁帶,當磁道卷起來時就像磁盤一樣是圓形的。

    站在os角度,我們認為磁盤是線性結構的。如圖:

    LBA索引相當于虛擬地址與物理內存的關系,LBA是站在os角度認識的,最后LBA要轉換成磁盤能讀懂,磁盤查詢步驟–確定盤面,柱面,扇區。

    磁盤分區與格式化

    磁盤分區

    os系統為了更好的管理磁盤,與是就對磁盤進行分區管理。

    計算機為了更好的管理磁盤,于是對磁盤進行了分區。磁盤分區就是使用分區編輯器在磁盤上劃分幾個邏輯部分,盤片一旦劃分成數個分區,不同的目錄與文件就可以存儲進不同的分區,分區越多,就可以將文件的性質區分得越細,按照更為細分的性質,存儲在不同的地方以管理文件,例如在Windows下磁盤一般被分為C盤和D盤兩個區域。
    在Linux操作系統中,我們也可以通過以下命令查看我們磁盤的分區信息:

    ls /dev/vda* -l

    磁盤格式化

    什么是格式?

    當磁盤完成分區后,我們還需要對磁盤進行格式化。格式化的本質就寫入文件系統。寫入文件系統為“高級格式化”,相當于建好了房屋,并且我可以通過房屋的建設圖紙去找到對應的房間,對于磁盤,按照EXT的方式去初始化磁盤也就有了存儲結構,操作系統按照EXT的方式去找到存儲位置來存儲數據。

    其中,寫入的管理信息是什么是由文件系統決定的,不同的文件系統格式化時寫入的管理信息是不同的,常見的文件系統有EXT2、EXT3、XFS、NTFS等。

    EXT2文件系統的存儲方案


    上述了解磁盤的基本概念,主要目的是為了把磁盤想象成線性的結構,為了更好的管理,我們給磁盤進行分區細分化,然后再給每個分區格式化—配上管理系統。接下來我們要學習 一個沒有被打開的文件是如何是磁盤是存儲的。


    os為了更好的管理磁盤,對磁盤進行分區,如果os把一個分區管理好了,實際上其他分區也可以按照同樣的方法進行管理。如果你想讓不同的分區按不同的文件系統進行管理,是可以的,因為現在所有的操作系統都支持多文件系統。

    而對于每一個分區,分區的頭部會有一個啟動塊(Boot Block),對于其他區域,EXT2文件系統會根據系統分區的大小將其劃分為一個個的塊組(Block Group)。

    其次,每個塊組都包含一個相同的結構,這個結構都由超級塊(Super Block)、塊組描述符表(Group Descriptor Table)、塊位圖(Block Bitmap)、inode位圖(inode Bitmap)、inode表(inode Table)以及數據表(Data Block)組成。

  • Super Block: 存放文件系統本身的結構信息。記錄的信息主要有:Data Block和inode的總量、未使用的Data Block和inode的數量、一個Data Block和inode的大小、最近一次掛載的時間、最近一次寫入數據的時間、最近一次檢驗磁盤的時間等其他文件系統的相關信息。Super Block的信息被破壞,可以說整個文件系統結構就被破壞了。

  • Group Descriptor Table: 塊組描述符表,描述該分區當中塊組的屬性信息。

  • Block Bitmap: 塊位圖當中記錄著Data Block中哪個數據塊已經被占用,哪個數據塊沒有被占用。

  • inode Bitmap: inode位圖當中記錄著每個inode是否空閑可用。

  • inode Table: 存放文件屬性,即每個文件的inode。

  • Data Blocks: 存放文件內容。
    注意:

  • 其他塊組當中可能會存在冗余的Super Block,當某一Super Block被破壞后可以通過其他Super Block進行恢復。

  • 磁盤分區并格式化后,每個分區的inode個數就確定了。

  • 重點學習后4個

    初識文件inode

    如果一個文件沒有被打開

    文件=文件內容+文件屬性;文件放在磁盤上

    文件內容就是文件當中存儲的數據,文件屬性就是文件的一些基本信息,例如文件名,文件大小創建時間等信息都是文件屬性,文件屬性又稱為元信息。

    我們使用ls -l的時候看到的除了看到文件名,還看到了文件元數據 。

    每行包含7列 :

    • 模式
    • 硬鏈接數
    • 文件所有者
    • 大小
    • 最后修改時間
    • 文件名

    ls -l讀取存儲在磁盤上的文件信息,然后顯示出來

    在文件系統里,文件的元信息和內容是分離的,保存元信息由一個inode的結構保存,在文件系統里存在大量的inode結構,所以我們需要給每個inode進行編號,即inode 號。在文件系統里幾乎所有的文件都有一個唯一標識的inode。

    stat命令

    其實這個信息除了通過這種方式來讀取,還有一個stat命令能夠看到更多信息

    File: ‘abc’Size: 9 Blocks: 8 IO Block: 4096 regular file Device: fd01h/64769d Inode: 787165 Links: 1 Access: (0664/-rw-rw-r--) Uid: ( 1003/ BBQ) Gid: ( 1003/ BBQ) Access: 2022-04-21 18:50:53.336108679 +0800 Modify: 2022-04-21 18:50:51.406039972 +0800 Change: 2022-04-21 18:50:51.409040079 +0800Birth: - 我們可以把inode看成一個結構體,結構體里大概存放了```cpp struct inode{//文件的所有屬性// 數據 int inode_number;int blocks[32];//數據塊列表int ref ;// 硬鏈接數量 }

    如何理解位圖?

    假設二進制的位圖:0000 1010

    從左往右比特位的位置含義: inode編號

    比特位的內容含義:特定inode“是否" 被占用

    例如編號為1的位置為0,0代表沒有被占用;

    注意:上面理解位圖,inode結構,都是方便大家理解。

    如何理解創建一個文件?

    將屬性和數據分開存放的想法看起來很簡單,但實際上是如何工作的呢?我們通過touch一個新文件來看看如何工作 。

    touch abc ls -i abc

    為了說明問題,我們將指令輸出簡化:

    創建一個新文件主要有一下4個操作:

  • 存儲屬性

    內核通過inode Bitmap先找到一個空閑的i節點(這里是263466)。內核把文件信息記錄到其中。

  • 存儲數據

    該文件需要存儲在三個磁盤塊,內核通過iBlock Bitmap找到了三個空閑塊:300,500,800。將內核緩沖區的第一塊數據

    復制到300,下一塊復制到500,以此類推。

  • 記錄分配情況

    文件內容按順序300,500,800存放。內核在inode上的磁盤分布區記錄了上述塊列表。

  • 將該文件的文件名和新的inode編號添加到目錄文件的數據塊中,建立映射關系。

  • 如何理解刪除一個文件?

    ? 1.將文件對應的inode在inode位圖當中置位無效。

    ? 2.數據塊也同樣如此,將該文件申請過的數據塊在Block Bitmap當中置位無效。

    所以,刪除一個文件,不是把文件的內容刪掉。如果我們誤刪一個文件,是可以恢復的。如果進行其他操作例如開辟一個文件,那么置為無效的文件可能被別人使用了,并修改了文件內容。所以有可能不能恢復。

    如何理解目錄?

  • 在Linux一切皆文件,目錄也是一個文件。
  • 目錄的inode存放目錄的屬性。
  • 目錄的內容存放該目錄下的文件名以及文件名映射的inode編號。
  • 理解硬鏈接

    我們看到,真正找到磁盤上文件的并不是文件名,而是inode。 其實在linux中可以讓多個文件名對應于同一個inode。

    ln abc def
    • abc和def的鏈接狀態完全相同,他們被稱為指向文件的硬鏈接。內核記錄了這個連接數,inode 263466 的硬連接數為2。
    • 我們在刪除文件時干了兩件事情:1.在目錄中將對應的記錄刪除,2.將硬連接數-1,如果為0,則將對應的磁盤釋放。

    硬鏈接本質是根本就不是一個獨立的文件,而是一個文件名和inode編號的映射關系,因為自己沒有獨立的inode。

    創建硬鏈接,本質是在特定的目錄下,填寫一對文件名和inode 的映射關系。

    理解軟鏈接

    硬鏈接是通過inode引用另外一個文件,軟鏈接是通過名字引用另外一個文件,在shell中的做法

    ln -s abc abcs

    軟鏈接是有自己獨立的inode的!軟鏈接是一個獨立文件!!!有自己的inode屬性,也有自己的數據塊(保存的是指向文件的所在路徑+文件名)

    A,C,M三個時間

    下面解釋一下文件的三個時間 :

    Access:最后訪問的時間 (較新的linux會在一定間隔的時間內進行更新,也就是說Access時間不會被立即更新,原因該時間高頻被訪問會影響性能)

    Modify :文件內容最后修改時間

    Change : 屬性最后修改時間

    注意:通常文件內容被修改后,不僅Modify被修改了,Change也會被修改,例如文件屬性的大小被修改了。

    touch file

    touch 指令更新已存在文件的三個時間。

    三個時間的應用場景

    makefile與gcc會根據時間問題,來判定源文件和可執行程序誰更新,從而指導系統那些源文件需要被重寫編譯。

    總結

    以上是生活随笔為你收集整理的Linux 基础IO的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。