作業系統-記憶體、檔案管理

sleeeeeping發表於2024-08-18

一、記憶體管理的層次關係

使用者層
STL 自動分配、自動釋放 呼叫C++
C++ new/delete、構造/析構 呼叫C
C malloc\ calloc\ realloc\ free 呼叫POSIX\Linux
POSIX sbrk\ brk 呼叫Kernal
Linux mmap\ munmap 呼叫Kernal
系統層
Kernal kmalloc\ vmalloc 呼叫驅動Driver
Driver get_free_page ...

二、程序映像

程式與程序:

  • 程式是儲存在磁碟上的可執行檔案,當程式被執行時,系統會把程式從磁碟載入到記憶體中執行,正在執行中的程式稱為程序,一個程式可以同時被載入多次,形成多個程序,每個程序相互獨立,由作業系統管理

程序映像:程序在記憶體空間中的分佈使用情況就稱為程序映像,從低地址到高地址分別是:

  • 程式碼段 text
    • 儲存二進位制指令、字面值常量、被const修飾過的原data段的資料
    • 許可權r-- 或者 r-x 許可權只讀
  • 資料段 data
    • 儲存初始化過的全域性變數和靜態變數
  • 靜態資料段 BSS
    • 儲存未初始化過的全域性變數和靜態變數
    • 程序一旦載入,此記憶體段會被作業系統自動清零,預設值是0
    • 如果初始化的值給0,依然還在BSS
  • 堆區 heap
    • 要程式設計師動態分配、動態釋放,從低地址向高地址擴招
    • 使用malloc系列函式進行記憶體管理
    • malloc系列函式底層呼叫作業系統的API(brk\ sbrk\ mmap\ munmap
  • 棧區 stack
    • 儲存非靜態區域性變數、塊變數,包括函式的引數(除了main函式的引數)、返回值
    • 記憶體擴充套件從高地址向低地址擴充套件
    • 棧區與堆區中間有一段預留空間,一個作用為了預留,第二個是讓共享庫的記憶體以及共享記憶體使用此段記憶體
  • 命令列引數與環境變數表
    • 裡面儲存環境變數表以及命令列傳給main的引數內容

三、系統呼叫(系統API)

  • 由作業系統嚮應用程式提供的程式介面資訊,本質上就是應用程式與作業系統之間互動的介面。
  • 作業系統的主要功能是為了管理硬體資源和為應用軟體的開發人員提供一個良好的環境,使得應用程式具有更好的相容性,為了達到這個目的,核心提供一套統一的具有一定功能的核心介面函式,稱為系統呼叫\系統函式,以C語言函式的格式提供給使用者
  • 作業系統負責把應用程式的請求傳給核心,由核心呼叫具體核心功能完成所需請求,結束後把結果透過作業系統的介面函式的返回值傳遞給呼叫者
  • UNIX\ Linux大部分系統中的系統功能都是透過系統呼叫來實現的,相當於呼叫系統函式,但是它們不是真正意義的函式,當呼叫它們時,程序立即進入核心態,當呼叫結束,會重新轉入回使用者態

普通函式與系統函式(系統呼叫)的區別:

普通函式的呼叫步驟

  1. 呼叫者會把要傳遞的引數壓入棧記憶體
  2. 根據函式名也就是該函式的程式碼段地址,跳轉到該函式程式碼段中執行
  3. 從棧記憶體中彈出傳遞的引數資料
  4. 定義的相關區域性變數會入棧到該函式的棧記憶體進行擴充套件,並執行相關程式碼
  5. 返回執行結果
  6. 銷燬該函式的棧記憶體
  7. 返回撥用語句處繼續執行
系統函式的呼叫過程:
  • 當執行到系統函式的位置時,會觸發軟體中斷機制
  • 然後程序會轉入核心態執行,由核心負責把引數從使用者空間複製到核心空間
  • 然後核心根據中斷編號來執行相應的操作
  • 等執行完畢後,核心再把執行結果從核心空間再複製回使用者空間中
  • 返回到中斷觸發位置,轉換回使用者態繼續執行程序

二、一切皆檔案

  • 在UNIX\Linux系統中,作業系統把所有服務、裝置都抽象成了檔案,因為這樣可以給各種裝置、服務提供同一套統一而簡單的操作介面,程式就可以像訪問普通檔案一樣,控制串列埠、網路、印表機等裝置
  • 因此在UNIX\Linux系統中對檔案就具有特別重要的意義,一切皆檔案,所以大多數情況下,只需要五個基本系統函式操作 open/close/read/write/ioctl,既可以完成對各種裝置的輸入、輸出控制

檔案分類:

  • 普通檔案 - 包括文字檔案、二進位制檔案、各種壓縮包檔案等
  • 目錄檔案 d 類似Windows的資料夾
  • 塊裝置檔案 b 用於儲存大塊資料的裝置,例如磁碟
  • 字元裝置檔案 c 用於對字元處理的服務、裝置,例如 鍵盤
  • 連結檔案 l 類似於Windows的快捷方式
  • 管道檔案 p 用於早期程序通訊
  • Socket檔案\套接字檔案 s 用於網路通訊

三、檔案描述符

什麼是檔案描述符:
  • 檔案描述符是一種非負的整數,用於表示一個開啟了的檔案
  • 由系統呼叫(open\creat)返回值,在後續操作檔案時可以被核心空間引用去操作對應的檔案
  • 它代表了一個核心物件(類似於FILE*),因為核心不能暴露它的記憶體地址,因此不能返回真正的檔案地址給使用者
  • 核心中有一張表格,記錄了所有被開啟的檔案物件,檔案描述符就是訪問這張表格的下標,檔案描述符也叫做控制代碼,是使用者操作檔案的憑證
  • 核心只要啟動,一定會給每個程序開啟三個檔案描述符,並且是一直預設開啟,除非自己手動關閉
//	在<unistd.h> 定義了三個宏

#define STDIN_FILENO    0   //	標準輸入 檔案指標 stdin 
#define STDOUT_FILENO   1   //	標準輸出 檔案指標 stdout
#define STDERR_FILENO   2   /* 標準輸入 檔案指標 stderr

檔案描述符與檔案指標:
  • 在Linux系統中開啟檔案後,記憶體中(核心區間)就會有一個核心物件,也就是記錄了該檔案相關資訊的結構變數,但是核心為了自己的安全不能把它的地址返回給使用者,而且核心區間使用者是無法直接訪問的,就算返回也無許可權訪問。
  • 而且一個程序可以同時開啟多份檔案,所以作業系統會在核心區間創造一份索引表,表中的每一項都指向了一個開啟的檔案核心物件,而使用者拿到的檔案描述符就是該索引表的下標(主鍵),因此不同程序之間直接互動檔案描述符是沒有意義的。
  • C語言標準函式中,使用檔案指標代表檔案,檔案指標指向的區間是程序的使用者 區間的一個結構變數(檔案結構變數FILE型別),裡面記錄了該檔案的檔案描述符還有一些檔案緩衝區,因此某種程度上可以理解檔案指標就是檔案描述符,只是不同的形式,給不同的物件使用而已
int fileno(FILE *stream);
功能:把檔案指標轉換成檔案描述符

FILE *fdopen(int fd, const char *mode);
功能:把檔案描述符轉換成檔案指標

四、檔案的建立與開啟

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *pathname, int flags);
功能:開啟檔案
pathname:檔案路徑
flags:開啟檔案的方式
返回值:檔案描述符

int open(const char *pathname, int flags, mode_t mode);
功能:開啟或建立檔案
pathname:檔案路徑
flags:開啟檔案的方式
mode:建立檔案時的許可權
返回值:成功返回檔案描述符,失敗返回負數

注意:open\creat所返回的一定是當前未使用過的最小的檔案描述符
注意:一個程序可以同時開啟多個檔案描述符,最大的數量受limit.h中宏控制數量,在不同的系統標準中不一樣,POSIX中不低於16,傳統UNIX63個,現代的Linux系統255個

int creat(const char *pathname, mode_t mode);
功能:專門用於建立檔案,但是基本不用,因為open可以覆蓋它的功能

flags:
	O_RDONLY	只讀許可權
	O_WRONLY	只寫許可權
	O_RDWR		讀寫許可權
	O_APPEND	開啟檔案後位置指標指向末尾
	O_CREAT		檔案不存在會建立
	O_EXCL		如果檔案存在則建立失敗,如果沒有該flags,檔案存在直接開啟
	O_TRUNC		清空內容開啟
	O_ASYNC		當檔案描述符可讀/可寫時,會向呼叫程序傳送訊號SIGIO

mode: 提供一個三位八進位制數表示許可權,當flags為O_CREAT時必須提供
	 宏名		許可權碼
	 S_IRWXU  00700  使用者許可權碼
     S_IRUSR  00400  	讀許可權
	 S_IWUSR  00200 	寫許可權
	 S_IXUSR  00100 	執行許可權
     S_IRWXG  00070  同組其它使用者許可權
     S_IRGRP  00040 
     S_IWGRP  00020 
     S_IXGRP  00010 
     S_IRWXO  00007 除了同組的使用者外,其它使用者的許可權
     S_IROTH  00004 
     S_IWOTH  00002 
     S_IXOTH  00001 

#include <unistd.h>
int close(int fd);
功能:關閉檔案,成功0 失敗-1
問題1:C語言可不可以定義重名函式?
可以,但是需要在不同作用域下才可以重名,同一作用域下不可以
情況1:在不同的原始檔中,static宣告的函式可以與其他原始檔中的普通函式重名
情況2:在函式內定義的函式可以與普通函式重名
問題2: 系統呼叫為什麼可以重名?
因為系統呼叫本身就不是真正的函式,而是藉助了軟中斷實現核心執行對應的系統操作,而決定執行哪個系統呼叫操作是由中斷編號決定的,而不是名字
問題3:標準庫函式中的 r \ r+ \ w \ w+ \ a \ a+ 分別對應系統呼叫中的flags的哪些標誌?
strace ./a.out
r		O_RDONLY
r+		O_RDWR
w		O_WRONLY|O_CREAT|O_TRUNC  0666
w+    	O_RDWR|O_CREAT|O_TRUNC  0666
a		O_WRONLY|O_CREAT|O_APPEND, 0666
a+		O_RDWR|O_CREAT|O_APPEND, 0666

五、檔案讀寫

ssize_t write(int fd, const void *buf, size_t count);
功能:寫入檔案內容
fd:檔案描述符
buf:要寫入的資料的記憶體首地址
count:要寫入的位元組數
返回值:成功寫入的位元組數

ssize_t read(int fd, void *buf, size_t count);
功能:從檔案中讀取資料到記憶體
fd:檔案描述符
buf:儲存讀取到的資料的記憶體首地址
count:想要讀取的位元組數,一般就是buf的大小
返回值:成功讀取到的位元組數

注意:它們與標準C的 fwrite/fread 很像,但是它們更純粹直接

練習1:以二進位制形式寫入10000000個整數到檔案中,分別使用標準IO和系統IO來完成,比較它們的速度誰更快,為什麼?

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

void std_io(void)
{
    FILE* fp = fopen("test.txt","w");
    if(NULL == fp)
    {
        perror("fopen");
        return;
    }

    for(int i=0; i<10000000; i++)
    {
        int num = rand();
        fwrite(&num,sizeof(num),1,fp);
    }

    fclose(fp);
}

void sys_io(void)
{
    int fd = open("test.txt",O_WRONLY|O_CREAT|O_TRUNC,0644);
    if(0 > fd)
    {
        perror("open");
        return;
    }

    for(int i=0; i<10000000; i++)
    {
        int num = rand();
        write(fd,&num,sizeof(num));
    }

    close(fd);
}

int main(int argc,const char* argv[])
{
    //std_io();                                                                                  
    sys_io();
}

六、系統IO與標準IO

  • 當系統呼叫被執行時,需要從使用者態轉換成核心態,執行完畢後又要轉回使用者態,如果頻繁來回切換會導致效能丟失
  • 在標準IO中,內部維護一個緩衝區(1k,1024位元組),要寫入的資料先儲存到緩衝區中,只有當滿足特定條件時才會把緩衝區中資料全部透過系統呼叫write寫入檔案,從而降低了系統呼叫的使用頻率,減少了狀態的切換,因此標準IO的效率要比直接使用系統IO要快
  • 如果想要提高系統IO的速度,可以嘗試自己維護一個緩衝區,先把資料儲存到緩衝區,等滿後再呼叫write,這樣提高速度
  • 普通情況下建議使用標準IO,因為更快,如果對速度還有更高的要求,可以自己使用系統IO+大緩衝區
void sys_io(void)
{
    int fd = open("test.txt",O_WRONLY|O_CREAT|O_TRUNC,0644);                                                               
    if(0 > fd)
    {
        perror("open");
        return;
    }

    int buf[1024] = {};

    for(int i=0; i<10000000; i++)
    {
        int num = rand();
        buf[i%1024] = num;
        if(0 == (i+1)%1024)
        {
            write(fd,buf,sizeof(buf));
        }
    }

    close(fd);
}

  • UNIX/Linux只有一套讀寫檔案的系統呼叫,沒有像標準C中的文字讀寫 ,那麼可以先把資料轉換成字串(sprintf/sscanf),然後再透過write\read讀寫,從而實現文字讀寫的效果
作業1:使用系統IO實現一個帶有覆蓋檢查的cp命令 ./CP a b

七、檔案位置指標

  • 與標準IO的檔案讀寫位置指標一樣,系統IO時也會有一個表示位置的指標在移動,會隨著讀寫操作的執行向後自動移動
  • 當需要隨機位置進行讀寫操作時,那麼需要移動位置指標的位置
off_t lseek(int fd, off_t offset, int whence);
功能:調整檔案指標的位置
fd:要調整的檔案描述符
offset:
	偏移值
whence:基礎位置
	SEEK_SET	檔案開頭
	SEEK_CUR	當前位置
	SEEK_END	檔案末尾
返回值:返回當前位置指標的位置 從檔案開頭計算該位置的位元組數

注意:系統IO中不需要ftell函式的系統呼叫,因為lseek就能夠完成獲取位置指標位置的功能
  • 當超過檔案末尾的位置再寫入資料時,原末尾與最後的資料之間會有一個“資料黑洞”,但是黑洞不佔用磁碟大小,但是會計算成檔案的大小,不會影響後序的讀寫

八、檔案同步

  • 大多數磁碟I/O都有緩衝區機制,寫入檔案其實先寫入緩衝區,直到緩衝區滿才將其排入寫佇列。降低寫操作的次數,提高寫操作效率,但是可能會導致磁碟檔案與緩衝區資料不同步,可以藉助系統呼叫來強制讓磁碟與緩衝區的內容同步:
void sync(void);
功能:將所有被修改過的緩衝區中的資料排入寫佇列,立即返回,不等待寫入磁碟的完成
int fsync(int fd);
功能:只針對檔案fd,並且會等待寫入完成才返回
int fdatasync(int fd);
功能:只同步檔案fd的資料,不同步檔案屬性,等待寫入完成才返回

九、檔案描述符的狀態標誌

#include <unistd.h>
#include <fcntl.h>

int fcntl(int fd, int cmd, ... /* arg */ );
功能:設定或獲取檔案描述符的狀態標誌
cmd:
	F_GETFD		獲取檔案描述符狀態標誌
	F_SETFD		設定檔案描述符狀態標誌
	F_GETFL		獲取檔案狀態標誌
				其中 O_CREAT\O_EXCL\O_TRUNC 獲取不了
	F_SETFL		追加檔案狀態標誌
				

十、檔案鎖

檔案鎖的意義:
  • 當多個程序同時訪問同一個檔案時,就有可能造成檔案的資料讀寫混亂,為了解決這一問題可以在讀寫檔案前給檔案嘗試並加鎖
檔案鎖的限制:
  • 一般情況下,系統提供的檔案鎖都是勸解鎖,核心主要負責檔案的加鎖和檢查是否上鎖,而不直接參與鎖的控制以及協同操作,這類鎖就需要程式設計師每次用之前都要檢查是否被別的程序加鎖,再實現併發操作
int fcntl(int fd, int cmd, flock* lock );
功能:對檔案的某一部分進行鎖操作
struct flock {
 	short l_type;  /* 鎖的型別
 				F_RDLCK,讀鎖
             	F_WRLCK, 寫鎖
             	F_UNLCK 解鎖*/
   short l_whence;  /* 偏移起點
   				SEEK_SET, SEEK_CUR, SEEK_END */
   off_t l_start;   /* 偏移值  鎖區起始位置:l_whence+l_start */
   off_t l_len;     /* 鎖區長度 0表示鎖到檔案末尾*/
   pid_t l_pid;     /* 加鎖的程序id -1表示讓核心自動設定 */
   };
cmd可選:
	F_GETLK		測試lock所表示的鎖能否加鎖
				如果可以加則講lock.l_type設定為F_UNLCK
				否則會透過lock.l_type返回當前鎖資訊
	F_SETLK		設定檔案的鎖定狀態為lock.l_type
				成功返回0,失敗返回-1,如果有其他程序持有該鎖導致加鎖失敗,返回EACCES or EAGAIN
	F_SETLKW	設定檔案的鎖定狀態為lock.l_type
				成功返回0,否則一直等待,除非被其它訊號打斷返回-1
	 

程序A:讀鎖 程序B:讀鎖 可共享

程序A:寫鎖 程序B:讀鎖 互斥

程序A:讀鎖 程序B:寫鎖 互斥

程序A:寫鎖 程序B:寫鎖 互斥

十一、複製檔案描述符

int dup(int oldfd);
功能:複製檔案描述符
oldfd:已經開啟的要複製的檔案描述符
返回值:返回一個新的檔案描述符,是當前可用的檔案描述符中最小值,失敗-1

int dup2(int oldfd, int newfd);
功能:複製oldfd檔案描述符成指定的檔案描述符newfd
如果newfd原來已經被佔用,則會把它關閉重新複製
返回值:成功0 失敗-1
    
注意:複製成功後,相當於兩個檔案描述符對應同一個開啟的檔案
複製檔案描述符的意義:
  • 複製成功後,相當於兩個檔案描述符對應同一個開啟的檔案,可以以此實現很多奇特的操作,例如重定向檔案讀寫、重定向命令 ls >> file
  • 透過把一個已經開啟了的檔案描述符fd,透過dup2重定向為標準輸入0或者標準輸出1,此時就會先把0、1檔案關閉,然後0\1指向我們剛剛的檔案fd,此後,透過輸出語句執行時,相當於把本來要輸出到螢幕的內容,直接寫如到檔案fd中,相當於執行write,如果執行輸入語句,相當於把本來要從鍵盤中輸入的內容,直接從檔案fd中讀取,相當於執行了read操作

十二、獲取檔案屬性

int stat(const char *pathname, struct stat *buf);
功能:根據檔案路徑獲取該檔案的屬性
int fstat(int fd, struct stat *buf);
功能:根據檔案描述符獲取該檔案的屬性
int lstat(const char *pathname, struct stat *buf);
功能:根據檔案路徑獲取連結檔案的屬性

struct stat {
       dev_t  st_dev;         /* 檔案的裝置ID*/
       ino_t  st_ino;         /* 檔案的inode節點號*/
       mode_t st_mode;        /* 檔案的型別和許可權 */
       nlink_t st_nlink;      /* 硬連結數量 */
       uid_t   st_uid;       /* 屬主ID */
       gid_t     st_gid;     /* 屬組ID */
       dev_t     st_rdev;    /* 特殊裝置ID */
       off_t     st_size;    /* 檔案的總位元組數 */
       blksize_t st_blksize; /* 檔案的IO塊數量*/
       blkcnt_t  st_blocks;  /* 以512位元組為一塊,該檔案佔了幾塊*/
       struct timespec st_atim;  /* 最後訪問時間*/
       struct timespec st_mtim;  /* 最後內容修改時間*/
       struct timespec st_ctim;  /* 最後檔案狀態屬性修改時間*/
       
       		#define st_atime st_atim.tv_sec 時間換算成總秒數     
           #define st_mtime st_mtim.tv_sec
           #define st_ctime st_ctim.tv_sec
       };

st_mode	記錄了檔案型別和許可權
       S_IFMT     0170000   獲取檔案型別的掩碼
       S_IFSOCK   0140000   socket檔案	
       S_IFLNK    0120000   軟連結檔案
       S_IFREG    0100000   普通檔案
       S_IFBLK    0060000   塊裝置檔案
       S_IFDIR    0040000   目錄
       S_IFCHR    0020000   字元裝置檔案
       S_IFIFO    0010000   FIFO	管道檔案
      /*
	stat(pathname, &sb);
    if ((sb.st_mode & S_IFMT) == S_IFREG) {
               /* Handle regular file */}*/
或者藉助提供的宏函式來判斷檔案型別:
 	S_ISREG(m)  是否是普通檔案
	S_ISDIR(m)  目錄
	S_ISCHR(m)  字元裝置檔案
	S_ISBLK(m)  塊裝置檔案
	S_ISFIFO(m) 管道檔案
	S_ISLNK(m)  軟連結檔案
	S_ISSOCK(m) socket檔案
	
判斷許可權:
   S_IRWXU     00700   判斷屬主的讀寫執行許可權的許可權碼
   S_IRUSR     00400   owner has read permission
   S_IWUSR     00200   owner has write permission
   S_IXUSR     00100   owner has execute permission

   S_IRWXG     00070   判斷屬組其他使用者的讀寫執行許可權的許可權碼
   S_IRGRP     00040   group has read permission
   S_IWGRP     00020   group has write permission
   S_IXGRP     00010   group has execute permission

   S_IRWXO     00007   判斷其它使用者的讀寫執行許可權的許可權碼
   S_IROTH     00004   others have read permission
   S_IWOTH     00002   others have write permission
   S_IXOTH     00001   others have execute permission


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

//  顯示檔案型別和許可權
const char* mtos(mode_t m)
{
    static char s[11];

    if(S_ISREG(m))
        strcpy(s,"-");
    else if(S_ISDIR(m))
        strcpy(s,"d");
    else if(S_ISCHR(m))
        strcpy(s,"c");
    else if(S_ISBLK(m))
        strcpy(s,"b");
    else if(S_ISFIFO(m))
        strcpy(s,"p");
    else if(S_ISLNK(m))
        strcpy(s,"l");
    else if(S_ISSOCK(m))
        strcpy(s,"s");

    strcat(s,m & S_IRUSR ? "r":"-");
    strcat(s,m & S_IWUSR ? "w":"-");
    strcat(s,m & S_IXUSR ? "x":"-");

    strcat(s,m & S_IRGRP ? "r":"-");
    strcat(s,m & S_IWGRP ? "w":"-");
    strcat(s,m & S_IXGRP ? "x":"-");

    strcat(s,m & S_IROTH ? "r":"-");
    strcat(s,m & S_IWOTH ? "w":"-");
    strcat(s,m & S_IXOTH ? "x":"-");

    return s;
}

const char* ttos(time_t t)
{
    static char s[20];
    
    struct tm* time = localtime(&t);
    sprintf(s,"%04d-%02d-%02d %02d:%02d:%02d",
            time->tm_year+1900,
            time->tm_mon+1,
            time->tm_mday,
            time->tm_hour,
            time->tm_min,
            time->tm_sec);
    return s;
}

int main(int argc,const char* argv[])
{
    if(2 > argc)
    {
        printf("User:./a.out <path>\n");
        return 0;
    }

    struct stat st;
    
    if(-1 == stat(argv[1],&st))
    {
        perror("stat");
        return -1;
    }

    printf("裝置ID:%llu\n",st.st_dev);
    printf("inode節點號:%lu\n",st.st_ino);
    printf("檔案型別:%s\n",mtos(st.st_mode));
    printf("最後訪問時間:%s\n",ttos(st.st_atime));
    printf("最後修改時間:%s\n",ttos(st.st_mtime));
    printf("最後狀態修改時間:%s\n",ttos(st.st_ctime));
}

十三、檔案的許可權

測試檔案的許可權:
int access(const char *pathname, int mode);
功能:測試當前使用者對該檔案的許可權
pathname:要測試的檔案路徑
mode:
	R_OK	讀許可權
	W_OK	寫許可權
	X_OK	執行許可權
	F_OK	測試檔案是否存在
返回值:存在許可權返回0 否則返回-1
修改檔案許可權:
int chmod(const char *pathname, mode_t mode);
int fchmod(int fd, mode_t mode);
功能:修改檔案的許可權為mode,mode可以使用提供的宏,或者直接使用一個八進位制數表示三組許可權
注意:許可權都可以修改
檔案的許可權遮蔽碼:
  • 當使用open\creat 建立檔案時,無論給什麼許可權建立都會成功,但是系統中記錄有一個許可權遮蔽碼會對使用者建立的檔案的許可權進行過濾遮蔽,最終建立出來的檔案許可權要除去檔案遮蔽碼
//	可以透過命令 umask 檢視當前使用者的許可權遮蔽碼
//	可以透過命令 umask 0xxx 修改當前終端這一次的許可權遮蔽碼為0xxx

mode_t umask(mode_t mask);
功能:給當前程序設定許可權遮蔽碼
mask:新的遮蔽碼
返回值:舊的遮蔽碼
注意:只對當前程序有效

注意:遮蔽碼隻影響open、creat ,對於chmod、fchmod 不受影響

十四、修改檔案大小

int truncate(const char *path, off_t length);
int ftruncate(int fd, off_t length);
length:想要修改成的檔案位元組數
成功返回0,失敗返回-1
截短末尾丟棄,加長末尾添0
#include <stdio.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc,const char* argv[])
{
    int fd = open("trunc.txt",O_RDWR|O_CREAT|O_TRUNC,0644);
    if(-1 == fd)
    {
        perror("open");
        return -1;
    }

    //	要對映新檔案前,需要讓檔案大小超過0 才能對映並輸入、輸出
    if(-1 == ftruncate(fd,100))
    {
        perror("ftruncate");
        close(fd);
        return -1;
    }

    char* str = mmap(NULL,100,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_FILE,fd,0);
    if((void*)-1 == str)
    {
        perror("mmap");
        return 0;
    }
    //  往對映後的虛擬記憶體寫入資料 
    //printf("----%s\n",str);
    sprintf(str,"hello worldxxxxxxxxxxiid\n");
    printf("----%s\n",str);
//  str[0] = 'a';   
    //  取消對映
    munmap(str,100);

    close(fd);
}    
作業:實現一個函式,可以刪除檔案的[n,m]位元組的內容

十五、連結檔案

Linux的檔案系統會把磁碟分割槽成主要的兩大部分
  • inode資訊塊
    • 預設128B,裡面主要記錄檔案的許可權、大小、所有者、修改時間等基本資訊
  • block資料塊
    • 預設4Kb,記錄了檔名和真正的檔案資料內容
  • 每個檔案必須擁有一個唯一的inode以及若干個block組成,讀取檔案需要藉助檔案所在目錄的block中記錄的檔案inode號,找到該檔案的inode,inode中記錄了該檔案的block位置,從而最終讀取檔案
什麼是軟、硬連結檔案?
硬連結檔案
  • 硬連結檔案沒有自己inode和block,只是在不同的目錄下複製了一份原始檔的inode資訊,可以透過該inode找到同一份原始檔的block
軟連結檔案
  • 軟連結檔案會建立自己的新的inode和block,它的inode也是為了找到自己的block,而在它的block中儲存的是連結原始檔的檔名和inode資訊
區別
  • 刪除原始檔,只是刪除原始檔的inode塊,但是硬連結檔案不受影響,而軟連結檔案就無法訪問了
  • 當一個檔案的硬連結數刪除成0時,檔案才被真正的刪除
  • 修改硬連結檔案內容,原始檔也會被修改;而修改軟連結的block,不會改變原始檔的內容,反而會讓軟連結無法找到原始檔
  • 硬連結不能連結目錄,軟連結可以
硬連結檔案的建立和刪除:
int link(const char *oldpath, const char *newpath);
功能:建立硬連結檔案
與命令 link \ ln 功能一樣

int unlink(const char *pathname);
功能:刪除檔案的硬連結,檔案的硬連結數-1
   
int remove(const char *pathname);
功能:與unlink一致,都可以刪除普通檔案以及硬連結檔案
注意:如果刪除的檔案正在被開啟,則會等待檔案關閉後刪除
注意:如果刪除的是軟連結檔案,則會只刪除軟連結檔案本身,而不會對原始檔有任何影響,而且沒有任何一個可以藉助軟連結來刪除連結物件檔案的函式
軟連結檔案的建立與讀取:
int symlink(const char *target, const char *linkpath);
功能:建立軟連結檔案
對應命令:ln -s 

ssize_t readlink(const char *pathname, char *buf, size_t bufsiz);
功能:獲取到軟連結檔案自身的block內容,也就是它連結物件的檔名,而不會獲取到連結物件的檔案內容
如果想要讀取連結物件的檔案內容,還是透過read\write進行

十六、工作目錄

  • 工作目錄指的是當前程序所在的目錄,它是相對路徑的起點,在操作檔案時,如果沒有提供檔案的絕對路徑資訊,那麼會操作工作目錄下的檔案,一般預設下工作目錄就是當前程序的目錄
char *getcwd(char *buf, size_t size);
功能:獲取當前程序的工作路徑,相當於命令 pwd

int chdir(const char *path);
int fchdir(int fd);
功能:修改工作路徑,相當於cd

十七、建立、刪除、讀取目錄

int mkdir(const char *pathname, mode_t mode);
功能:建立空白目錄
mode:目錄的許可權,目錄必須有執行許可權次才能進入

int rmdir(const char *pathname);
功能:刪除目錄,只能刪除空白目錄

int chdir(const char *path);
int fchdir(int fd);
功能:修改工作路徑,相當於cd

DIR *opendir(const char *name);
DIR *fdopendir(int fd);
功能:開啟目錄檔案
返回值:成功返回目錄流指標,失敗返回NULL

struct dirent *readdir(DIR *dirp);
功能:從目錄流物件中讀取一條條目資訊
注意:讀取完一條條目後,會自動的往後移動,只需要再次呼叫該函式,即可以讀下一條目錄
返回值:返回該條目錄資訊的結構指標或者NULL(讀取失敗或者讀到了目錄末尾結束)
結構體 struct dirent裡面儲存了目錄中某個檔案的資訊
struct dirent {
   ino_t          d_ino;       /* inode節點號*/
   off_t          d_off;       /* 下一條條目的偏移量 注意是磁碟偏移量,而非記憶體地址*/
   unsigned short d_reclen;    /* 當前條目的長度 */
   unsigned char  d_type;      /* 檔案型別 */
   char           d_name[256]; /* 檔名 */
};
d_type的取值:
	DT_BLK  塊裝置檔案
   	DT_CHR  字元裝置檔案
   	DT_DIR  目錄
  	DT_FIFO 管道檔案
   	DT_LNK  軟連結檔案
   	DT_REG  普通檔案
   	DT_SOCK socket檔案
    DT_UNKNOWN  未知

void rewinddir(DIR *dirp);
功能:復位目錄流,設定目錄流的位置指標回到開頭

long telldir(DIR *dirp);
功能:獲取當前目錄流的位置

void seekdir(DIR *dirp, long loc);
功能:設定目錄流的讀取位置,這樣可以進行任意條目的獲取

int closedir(DIR *dirp);
功能:關閉目錄檔案
作業2:實現 ls -al 命令的功能
作業3:實現 rm -rf 命令 (建議:先備份虛擬機器檔案 使用者名稱.vdi)

gcc xxx.c -o RM

RM filename 非空目錄也可以刪除

相關文章