unix環境高階程式設計(上)-檔案篇

kinnylee發表於2018-10-16

目錄

前言

unix基礎知識

unix標準化和實現

unix提供的檔案IO

檔案和目錄

標準IO

系統資料檔案

前言

筆者將《unix環境高階程式設計》主要內容總結為三篇:檔案篇程式篇高階io和程式間通訊三大板塊。本文是unix環境高階程式設計系列文章第一篇:檔案篇。該篇主要包括:

unix基礎知識

介紹了unix的體系結構,以及unix中的檔案和目錄,輸入輸出,程式和程式,訊號等基本概念

unix標準與實現

標準包括C語言的標準和作業系統標準,實現包括BSD,FreeBSD,Linux,Solari,Mac os等

unix核心提供的檔案io函式

包括檔案描述符,對檔案的開啟,關閉,定位,讀,寫,改變檔案屬性操作。核心IO呼叫基於檔案描述符。還介紹了檔案的底層資料結構,瞭解資料結構之後就能理解檔案是如何支援共享的

檔案和目錄

主要介紹檔案的屬性和屬性對應的資料結構,以及各個欄位控制的問訪問許可權,檔案型別等。unix中一切皆檔案,這些檔案包括:普通檔案,目錄檔案,塊特殊檔案,字元特殊檔案,FIFO,套接字和符號連結。最後結束UFS檔案系統。

標準IO函式

標準io解決了核心io的很多細節問題,包括緩衝區分配。所有操作基於流和File物件

系統資料檔案

最後介紹系統提供的一些資料檔案,包括口令檔案,陰影檔案,朱文傑,登入賬號檔案,服務資料檔案,協議資料檔案,網路資料檔案等

一. unix基礎知識

1. unix體系結構

  • 作業系統是一種特殊的軟體,它控制計算機硬體資源,提供程式執行環境
  • 此軟體稱為核心,它相對較小,位於環境的中心
  • 核心的介面被稱為系統呼叫
  • 公共函式庫構建在系統呼叫介面上
  • 系統呼叫一般比普通函式呼叫需要花費更多時間
  • 應用軟體可以呼叫公共函式庫或者使用系統呼叫
    unix環境高階程式設計(上)-檔案篇

2. 檔案和目錄

  • 檔案系統是目錄和檔案組成的一種層次結構
  • 目錄的起點稱為根,名稱為/符號
  • 目錄是包含很多目錄項的檔案
  • 邏輯上可認為每個目錄項都包含檔名和檔案屬性。物理上是不包含的,因為一個檔案可被多次硬連結
  • 檔案屬性包括:型別(普通檔案,目錄),大小,所有者,許可權,修改時間等。state和fstate函式返回檔案屬性。

3. 輸入和輸出

3.1 檔案描述符

通常是一個小的非負整數,核心用它標識一個特定程式正在訪問的檔案

3.2 標準輸入,標準輸出,標準出錯

每當執行一個新程式時,shell都為其開啟三個檔案描述符:

說明 檔案描述符 標頭檔案 巨集
標準輸入 0 <unistd.h> STDIN_FILENO
標準輸出 1 <unistd.h> STDOUT_FILENO
標準出錯 2 <unistd.h> STDERR_FILENO

3.3 不用緩衝的io

  • 函式open,read,write,lseek,close提供了不用緩衝的io
  • 這些函式都使用檔案描述符
  • 標頭檔案為<fcntl.h>

3.4 標準io

  • 標準io提供一種帶緩衝io的介面
  • 使用標準io無需擔心如何選取最佳緩衝區大小,且簡化了堆輸入行的處理
  • 標準io標頭檔案為<stdio.h>

4. 程式和程式

4.1 程式

  • 存放在磁碟,處於某個目錄中的可執行檔案
  • exec函式執行時,核心將程式讀入儲存器並執行

4.2 程式

  • 程式的執行例項被稱為程式
  • 每個程式都有一個唯一的數字標識,稱為程式ID

4.3 程式控制

程式控制的主要函式:fork,exec和waitpid

4.4 執行緒

  • 程式內所有執行緒共享同一個地址空間,檔案描述符,棧,程式相關屬性
  • 執行緒訪問共享資料時需要採取同步措施避免不一致性
  • 執行緒也用ID標識,但是隻在它所屬程式內起作用

5. 訊號

  • 訊號是通知程式已發生某種情況的一種技術
  • 程式如何處理訊號有三種選擇:
    • 忽略該訊號
    • 按系統預設方式處理
    • 捕捉該訊號:提供一個函式,訊號發生時呼叫該函式。呼叫signal函式,第一個引數為訊號名稱,第二個引數為處理函式

6. 時間值

unix系統一直使用兩種不同的時間值

  • 日曆時間:UTC時間,用time_t表示。記錄自1970年1月1日以來鎖經過的秒數
  • 程式時間:cpu時間,用clock_t表示。已時鐘滴答計算

二. unix標準化和實現

1. unix標準化

  • ISO c:c語言國際化標準
  • POSIX:可移植的作業系統介面(protable operating system interface)

2. unix實現

  • SVR4:AT&T的UNIX系統實驗室產品,初版了系統V介面定義
  • BSD:加州伯克利分校研究和開發的,含有AT&T許可證的程式碼
  • FreeBSD:BSD去除AT&T許可證程式碼後,完全免費的版本
  • Linux:1991年Linux開發的一款被目前廣泛使用的unix作業系統
  • Mac OS:核心系統是Darwin,基於Mach核心和FreeBSD的組合
  • Solaris:sun公司開發的unix系統版本

三. unix提供的檔案IO

1. 檔案描述符

  • 核心中,所有開啟的檔案都通過檔案描述符引用
  • 開啟,新建時,核心向程式返回一個檔案描述符
  • 讀寫檔案時,將檔案描述符傳給read和write

2. open

unix環境高階程式設計(上)-檔案篇

  • 作用:建立或開啟一個檔案
  • pathname引數:檔名字
  • flag引數:由以下值進行“或”組成
    • O_RDONLY:只讀
    • O_WRONLY:只寫
    • O_RDWR:讀寫
    • O_APPEND:追加到末尾
    • O_CREATE:檔案不存在就建立
    • O_EXCL:同時指定O_CREATE時,如果檔案存在,就會出錯。使測試和建立成為原子操作
    • O_TRUNC:將檔案長度截短為0
    • O_NOCTTY:控制終端相關
    • O_NONBLOCK:非阻塞模式
  • mode引數:檔案訪問許可權,僅新建檔案時使用該引數

3. create

unix環境高階程式設計(上)-檔案篇

  • 作用:建立檔案
  • 等價於
    open(pathname, O_WRONLY|O_CREATE|O_TRUNC, mode)
    複製程式碼

4. close

unix環境高階程式設計(上)-檔案篇

  • 作用:關閉檔案
  • 關閉會釋放加在該檔案上的所有記錄鎖
  • 程式終止時,核心自動關閉它開啟的檔案,故可以不用顯示呼叫close

5. lseek

unix環境高階程式設計(上)-檔案篇

  • 作用:設定開啟檔案的偏移量
  • 預設偏移量為0,如果設定O_APPEND屬性,預設偏移量為檔案末尾
  • whence的取值:
    • SEEK_SET:設定檔案偏移為pos值
    • SEEK_CUR:設定檔案偏移為當前位置+pos
    • SEEK_END:設定檔案偏移為檔案長度+pos

6. read

unix環境高階程式設計(上)-檔案篇

  • 作用:從開啟的檔案中讀資料
  • 讀取成功,返回讀到的位元組數。讀到末尾,返回0。
  • 導致讀到的位元組數小於要求讀位元組數的情況:
    • 普通檔案:讀到達到要求位元組數時,已經讀到檔案結尾了
    • 終端裝置檔案:一次最多讀一行
    • 網路資料:快取區大小小於要讀位元組
    • 管道檔案:管道包含的位元組小於要讀位元組

7. write

unix環境高階程式設計(上)-檔案篇

  • 作用:向開啟檔案中寫資料
  • 返回值通常與nbyte相同,否則出錯
  • 寫成功後,檔案偏移量增加寫入位元組數量

9. 檔案共享

9.1 開啟檔案的核心資料結構

unix支援在不同程式間共享開啟的檔案,unix核心使用什麼資料結構來支援這種共享呢?

unix環境高階程式設計(上)-檔案篇

  • 程式表記錄來所有的程式
  • 每個程式都有一個記錄項,用來記錄開啟檔案的檔案描述表
  • 檔案描述符的每一項包括:
    • 檔案描述符標識
    • 指向檔案表項的指標
  • 檔案表項由核心維護,每一項包括:
    • 檔案狀態標識(讀,寫,同步,阻塞等)
    • 當前檔案偏移量
    • 指向該檔案v節點表項的指標
  • 每個開啟檔案都有v節點(v-node)結構,這些資訊是開啟檔案時從磁碟讀入記憶體的。包括:
    • 檔案型別
    • 對此檔案進行各種操作的指標
    • i節點資訊(索引資訊):包括長度,所有者,所在裝置,磁碟位置指標等

9.2 兩個獨立程式各自開啟同一檔案

unix環境高階程式設計(上)-檔案篇

  • 給定的檔案,只有一個v節點表項
  • 每個程式都有自己的檔案表項,以使自己有獨立的檔案偏移量

9.3 兩個獨立程式共享同一個檔案表項

unix環境高階程式設計(上)-檔案篇

  • 使用dup和fork函式時,父子程式對於每一個檔案描述符,都共享同一個檔案表項,達到檔案共享的目的

9.4 建立共享檔案的函式

unix環境高階程式設計(上)-檔案篇

  • dup:返回的檔案描述符為可用的最小值
  • dup2:返回fieldes2指定的描述符。如果fieldes2已經開啟,就關閉。如果fieldes=fieldes2,不關閉,直接返回。
  • fcntl:也可以建立共享檔案

10. 原子操作

  • 原子操作:指多步組成的操作,
  • 任何一個需要呼叫多個函式的操作都不可能是原子操作,因為中間可能會掛起該程式
  • unix提供了一些函式,使多個操作成為一個“原子操作”
  • O_APPEND標識:lseek和write的原子操作
  • pread:lseek和read的原子操作
  • pwrite:lseek和write的原子操作
  • 呼叫open時,通過制定O_CREAT和O_EXCL引數,將建立檔案作為原子操作

11. sync, fsync, fdatasync函式

這幾個函式出現的背景:unix提供的延時寫功能,通過提供緩衝區以減少磁碟讀寫次數,但是降低了檔案內容更新速度,這幾個函式用於保證緩衝區內容與檔案內容的同步,保證一致性。

unix環境高階程式設計(上)-檔案篇

  • sync:將修改的快緩衝區排入寫佇列,立馬返回,不等待真正寫磁碟
  • fsync:針對指定的檔案描述符起作用,且等待磁碟寫完才返回。同步內容包括資料和檔案屬性。適用於資料庫系統。
  • fdatasync:包括fsync的功能,但是隻同步資料,不同步檔案屬性。

12. fcntl函式

unix環境高階程式設計(上)-檔案篇

  • 作用:改變已開啟檔案的性質
  • 引數cmd的取值和作用:
    • F_DUPFD:複製一個現有的檔案描述符
    • F_GETFD: 設定檔案描述符標記
    • F_SETFD: 獲得檔案描述符標記
    • F_GETFL: 設定檔案狀態標記:讀,寫,追加,阻塞等。
    • F_SETFL: 獲得檔案狀態標記
    • F_GETOWN: 設定非同步io所有權
    • F_SEGOWN: 獲得非同步io所有權
    • F_GETLK:獲得記錄鎖
    • F_SETLK:設定記錄鎖
    • F_SETLKW:設定記錄鎖

四. 檔案和目錄

1. 檔案屬性

1.1 表示檔案屬性的資料結構:struct stat

```
struct stat {
    mode_t     st_mode;       //檔案模式,包含檔案型別,使用者id,組id,訪問許可權(9種)等資訊
    ino_t      st_ino;       //inode節點號
    dev_t      st_dev;        //裝置號碼
    dev_t      st_rdev;       //特殊裝置號碼
    nlink_t    st_nlink;      //檔案的連線數
    uid_t      st_uid;        //檔案所有者
    gid_t      st_gid;        //檔案所有者對應的組
    off_t      st_size;       //普通檔案,對應的檔案位元組數
    time_t     st_atime;      //檔案資料最後被訪問的時間
    time_t     st_mtime;      //檔案資料最後被修改的時間
    time_t     st_ctime;      //檔案狀態(i節點狀態)的最後修改時間
    blksize_t st_blksize;    //檔案內容對應的塊大小
    blkcnt_t   st_blocks;     //偉建內容對應的塊數量
  };
```
複製程式碼

1.2 如何獲取檔案屬性

unix環境高階程式設計(上)-檔案篇

  • state:根據檔名獲取屬性
  • fstate:根據描述符獲取屬性
  • lstate:返回符號連結的屬性

1.3 修改屬性的部分方法

  • 訪問時間和修改時間: utime函式,引數為struct utimbuf,每一項都是utc時間
  • 檔案使用者id和組id:chown,fchown,lchown
    unix環境高階程式設計(上)-檔案篇

2. 檔案型別:

2.1 st_mode欄位控制的檔案型別

unix環境高階程式設計(上)-檔案篇

  • S_ISREG:普通檔案。文字或二進位制;可執行檔案有固定的可被核心識別的格式。
  • S_ISDIR:目錄檔案。包含其他檔案的名字以及指向與這些檔案有關資訊的指標。
  • S_ISBLK:塊特殊檔案。提供堆裝置(如磁碟)帶緩衝的訪問,訪問長度固定。
  • S_ISCHR:字元特殊檔案。提供堆裝置(如磁碟)不帶緩衝的訪問,訪問長度不固定。
  • S_ISFIFO:FIFO,命名管道。用於程式間通訊
  • S_ISSOCK:套接字。用於網路間程式通訊
  • S_ISLINK:符號連結。指向另一個檔案

2.2 stat結構體本身控制的檔案型別

  • S_TYPEISMQ:訊息佇列
  • S_TYPEISSEM:訊號量
  • S_TYPEISSHM:共享儲存物件

3. 檔案訪問許可權

  • 許可權位儲存在st_mode屬性中
  • 9個訪問許可權位對應的值為:
    unix環境高階程式設計(上)-檔案篇
  • 更改檔案訪問許可權的函式:chmod和fchmod
    unix環境高階程式設計(上)-檔案篇

4. UFS檔案系統

4.1 磁碟,分割槽和檔案系統圖

unix環境高階程式設計(上)-檔案篇

  • 一個磁碟分為多個分割槽,每個分割槽可以包含一個檔案系統
  • i節點是固定長度的記錄項

4.2 詳細的柱面組的i節點和資料塊

unix環境高階程式設計(上)-檔案篇

  • 每個柱麵包括:i節點陣列,資料庫,目錄塊
  • 每個i節點包含檔案的大部分資訊:檔案型別,訪問許可權,長度,佔用的實際資料庫。(stat結構大多數資訊取自i節點)
  • 每個目錄塊包括:目錄名稱,i節點號
  • 同一個i節點,可以被不同的目錄指向,i節點的連結計數統計指向的數量
  • 檔案改名時,實際內容並未移動,只是構造一個新目錄項,指向現有的節點,並解除舊記錄項的連結

5. 硬連結

硬連結直接指向檔案的i節點

5.1 建立一個指向現有檔案的連結:link方法

unix環境高階程式設計(上)-檔案篇

  • 如果newpath已經存在,返回出錯
  • 只能建立newpath中最後一個分量,路徑中其他部分必須已經存在
  • 很多檔案系統不允許堆目錄建立硬連結
  • 超級使用者能直接建立目錄硬連結

5.2 刪除一個現有的連結項:unlink方法

unix環境高階程式設計(上)-檔案篇

  • 將path所引用的檔案的連結數減1
  • 只有當連線技術為0,該檔案的內容才被刪除
  • 對於檔案,可以使用remove功能,和unlink一樣
  • 對於目錄,可以使用rmdir功能,和unlink一樣

6. 符號連結

符號連結是指向一個檔案的間接指標。

6.1 符號連結是為了避開硬連結的一些限制

  • 硬連結要求連結和檔案位於同一檔案系統中
  • 只有超級使用者才能建立指向目錄的硬連結

6.2 使用符號連結需要注意的事情

  • 當呼叫某個函式時,需要注意函式處理的是連結的檔案,還是連結本身

6.3 符號連結相關的函式

  • 建立符號連結: symlink
  • 開啟符號連結:readlink

7. 目錄

  • 建立目錄:mkdir
  • 刪除目錄:rmdir。入爐連結計數為0,且沒有程式開啟次目錄,釋放目錄空間。
  • 讀取目錄:
    unix環境高階程式設計(上)-檔案篇
  • 更改當前工作目錄:chdir,fchdir

五. 標準IO

  • 標準io庫不僅在unix上,很多作業系統上都實現了。
  • 標準io處理很多細節,例如:緩衝區分配,優化長度執行io等。便於使用者使用。
  • 使用的標頭檔案為<stdio.h>。
  • 標準io的底層呼叫了前面介紹的unix核心io。
  • 標準io的缺點是效率低。這與它需要複製的資料量有關

1. 流和File物件

  • unix核心io提供的io函式都是針對檔案描述符的
  • 但是標準io的操作是針對流進行的
  • 標準io檔案流可用於單位元組或寬位元組字符集,由流定向決定(fwide函式)。
  • 標準io開啟一個檔案(fopen函式)時,返回一個FILE的指標,它包含了實際io的檔案描述符,指向用於該流緩衝區的指標,緩衝區長度,緩衝區當前字元數,出錯標誌,檔案結束標誌等資訊
  • 每個程式預定義三個流:標準輸入,標準輸出,標準出錯

2. 緩衝

2.1 緩衝型別

標準io提供三種型別的緩衝

  • 全緩衝:填滿標準io緩衝區後才進行實際的io操作(malloc申請緩衝區,flush執行寫操作)。
  • 行緩衝:輸入輸出中遇到換行符時進行實際的io操作。涉及終端裝置時,通常用行緩衝。
  • 不帶緩衝:不對字元進行緩衝儲存。標準出錯流通常不帶緩衝。

2.2 設定緩衝型別

unix環境高階程式設計(上)-檔案篇

  • setbuf
  • setvbuf:第三個引數:
    • _IOFBF:全緩衝
    • _IOLBF: 行緩衝
    • _IONBF:無緩衝

3. 開啟流

unix環境高階程式設計(上)-檔案篇

  • fopen:開啟一個指定的檔案
  • freopen:將一個檔案讀到一個指定的流。如果流已經開啟,就先關閉,已經定向,就先清除定向。
  • fdopen:通過檔案描述符開啟檔案。因為管道和網路通訊等特殊檔案不能用標準io函式fopen開啟,所以用到該函式。
  • type:指定檔案的開啟方式
    unix環境高階程式設計(上)-檔案篇

4. 讀和寫流

讀寫流有三種不同的方式

  • 每次讀寫一個字元:
    • 讀:getc,fgetc,getchar
    • 寫:putc,fputc,putchar

    不帶f字首的從標準輸入流讀取資料,帶f字首的從指定流讀取資料。不帶f字首的函式不推薦使用,因為它不指定緩衝區大小,會導致溢位。

  • 每次讀寫一行:
    • 讀:gets,fgets
    • 寫:puts,fputs
  • 每次讀寫一定數量的物件(直接io,二進位制io):
    • 讀:fread,需要指定要讀取的元素個數和每個元素的大小
    • 寫:fwrite
    • 缺點:不同系統間,交換二進位制資料會編譯期和計算機體系結構不同而有差異,所以必須用更高階的協議。

5. 定位流

定位標準io流有三種不同的方式

  • ftell(獲取),fseek(設定):long型別的檔案位置
  • ftello和fseeko:off_t型別的檔案位置
  • fgetpos和fsetpos:fpos_t的抽象資料型別表示檔案位置

6. 格式化io

6.1 格式化輸出

unix環境高階程式設計(上)-檔案篇

  • printf:格式化資料寫到標準輸出
  • fprintf:格式化資料到指定流
  • sprintf:格式化的資料送入陣列buf中,尾部自動加入null。可能會導致緩衝區溢位,需呼叫者自己保證
  • 轉換說明以%開始

6.2 格式化輸入

unix環境高階程式設計(上)-檔案篇

六. 系統資料檔案

1. 口令檔案

  • 存放目錄:/etc/passwd
  • 資料結構:<pwd.h>中的passwd結構體
  • 檢視指定使用者口令的函式介面:
    unix環境高階程式設計(上)-檔案篇
  • 檢視所有使用者口令的函式介面:
    unix環境高階程式設計(上)-檔案篇

2. 陰影檔案(加密口令)

  • 存放目錄:/etc/shadow
  • 檢視的介面:
    unix環境高階程式設計(上)-檔案篇

3. 組檔案

  • 存放目錄:/etc/group
  • 資料結構:<grp.h>中的group
  • 檢視指定組:
    unix環境高階程式設計(上)-檔案篇
  • 檢視所有組:
    unix環境高階程式設計(上)-檔案篇

4. 其他資料檔案

  • 伺服器提供服務的資料檔案:/etc/services
  • 記錄協議資訊的資料檔案:/etc/protocols
  • 記錄網路資訊的資料檔案:/etc/networks
    unix環境高階程式設計(上)-檔案篇

5. 登陸賬號檔案

  • 當前登陸進系統的使用者:/var/run/utmp
    unix環境高階程式設計(上)-檔案篇
  • 跟蹤登陸和登出資訊:/var/log/wtmp

6. 獲取系統資訊

  • 獲取主機與作業系統相關資訊
    unix環境高階程式設計(上)-檔案篇
  • 只獲取主機名
    unix環境高階程式設計(上)-檔案篇

7. 時間格式

  • 日曆時間(UTC時間)
    unix環境高階程式設計(上)-檔案篇
  • 更高精度的時間
    unix環境高階程式設計(上)-檔案篇
    unix環境高階程式設計(上)-檔案篇
  • 各種時間的轉化關係
    unix環境高階程式設計(上)-檔案篇

相關文章