目錄
前言
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體系結構
- 作業系統是一種特殊的軟體,它控制計算機硬體資源,提供程式執行環境
- 此軟體稱為核心,它相對較小,位於環境的中心
- 核心的介面被稱為系統呼叫
- 公共函式庫構建在系統呼叫介面上
- 系統呼叫一般比普通函式呼叫需要花費更多時間
- 應用軟體可以呼叫公共函式庫或者使用系統呼叫
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
- 作用:建立或開啟一個檔案
- 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
- 作用:建立檔案
- 等價於
open(pathname, O_WRONLY|O_CREATE|O_TRUNC, mode) 複製程式碼
4. close
- 作用:關閉檔案
- 關閉會釋放加在該檔案上的所有記錄鎖
- 程式終止時,核心自動關閉它開啟的檔案,故可以不用顯示呼叫close
5. lseek
- 作用:設定開啟檔案的偏移量
- 預設偏移量為0,如果設定O_APPEND屬性,預設偏移量為檔案末尾
- whence的取值:
- SEEK_SET:設定檔案偏移為pos值
- SEEK_CUR:設定檔案偏移為當前位置+pos
- SEEK_END:設定檔案偏移為檔案長度+pos
6. read
- 作用:從開啟的檔案中讀資料
- 讀取成功,返回讀到的位元組數。讀到末尾,返回0。
- 導致讀到的位元組數小於要求讀位元組數的情況:
- 普通檔案:讀到達到要求位元組數時,已經讀到檔案結尾了
- 終端裝置檔案:一次最多讀一行
- 網路資料:快取區大小小於要讀位元組
- 管道檔案:管道包含的位元組小於要讀位元組
7. write
- 作用:向開啟檔案中寫資料
- 返回值通常與nbyte相同,否則出錯
- 寫成功後,檔案偏移量增加寫入位元組數量
9. 檔案共享
9.1 開啟檔案的核心資料結構
unix支援在不同程式間共享開啟的檔案,unix核心使用什麼資料結構來支援這種共享呢?
- 程式表記錄來所有的程式
- 每個程式都有一個記錄項,用來記錄開啟檔案的檔案描述表
- 檔案描述符的每一項包括:
- 檔案描述符標識
- 指向檔案表項的指標
- 檔案表項由核心維護,每一項包括:
- 檔案狀態標識(讀,寫,同步,阻塞等)
- 當前檔案偏移量
- 指向該檔案v節點表項的指標
- 每個開啟檔案都有v節點(v-node)結構,這些資訊是開啟檔案時從磁碟讀入記憶體的。包括:
- 檔案型別
- 對此檔案進行各種操作的指標
- i節點資訊(索引資訊):包括長度,所有者,所在裝置,磁碟位置指標等
9.2 兩個獨立程式各自開啟同一檔案
- 給定的檔案,只有一個v節點表項
- 每個程式都有自己的檔案表項,以使自己有獨立的檔案偏移量
9.3 兩個獨立程式共享同一個檔案表項
- 使用dup和fork函式時,父子程式對於每一個檔案描述符,都共享同一個檔案表項,達到檔案共享的目的
9.4 建立共享檔案的函式
- 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提供的延時寫功能,通過提供緩衝區以減少磁碟讀寫次數,但是降低了檔案內容更新速度,這幾個函式用於保證緩衝區內容與檔案內容的同步,保證一致性。
- sync:將修改的快緩衝區排入寫佇列,立馬返回,不等待真正寫磁碟
- fsync:針對指定的檔案描述符起作用,且等待磁碟寫完才返回。同步內容包括資料和檔案屬性。適用於資料庫系統。
- fdatasync:包括fsync的功能,但是隻同步資料,不同步檔案屬性。
12. fcntl函式
- 作用:改變已開啟檔案的性質
- 引數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 如何獲取檔案屬性
- state:根據檔名獲取屬性
- fstate:根據描述符獲取屬性
- lstate:返回符號連結的屬性
1.3 修改屬性的部分方法
- 訪問時間和修改時間: utime函式,引數為struct utimbuf,每一項都是utc時間
- 檔案使用者id和組id:chown,fchown,lchown
2. 檔案型別:
2.1 st_mode欄位控制的檔案型別
- 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個訪問許可權位對應的值為:
- 更改檔案訪問許可權的函式:chmod和fchmod
4. UFS檔案系統
4.1 磁碟,分割槽和檔案系統圖
- 一個磁碟分為多個分割槽,每個分割槽可以包含一個檔案系統
- i節點是固定長度的記錄項
4.2 詳細的柱面組的i節點和資料塊
- 每個柱麵包括:i節點陣列,資料庫,目錄塊
- 每個i節點包含檔案的大部分資訊:檔案型別,訪問許可權,長度,佔用的實際資料庫。(stat結構大多數資訊取自i節點)
- 每個目錄塊包括:目錄名稱,i節點號
- 同一個i節點,可以被不同的目錄指向,i節點的連結計數統計指向的數量
- 檔案改名時,實際內容並未移動,只是構造一個新目錄項,指向現有的節點,並解除舊記錄項的連結
5. 硬連結
硬連結直接指向檔案的i節點
5.1 建立一個指向現有檔案的連結:link方法
- 如果newpath已經存在,返回出錯
- 只能建立newpath中最後一個分量,路徑中其他部分必須已經存在
- 很多檔案系統不允許堆目錄建立硬連結
- 超級使用者能直接建立目錄硬連結
5.2 刪除一個現有的連結項:unlink方法
- 將path所引用的檔案的連結數減1
- 只有當連線技術為0,該檔案的內容才被刪除
- 對於檔案,可以使用remove功能,和unlink一樣
- 對於目錄,可以使用rmdir功能,和unlink一樣
6. 符號連結
符號連結是指向一個檔案的間接指標。
6.1 符號連結是為了避開硬連結的一些限制
- 硬連結要求連結和檔案位於同一檔案系統中
- 只有超級使用者才能建立指向目錄的硬連結
6.2 使用符號連結需要注意的事情
- 當呼叫某個函式時,需要注意函式處理的是連結的檔案,還是連結本身
6.3 符號連結相關的函式
- 建立符號連結: symlink
- 開啟符號連結:readlink
7. 目錄
- 建立目錄:mkdir
- 刪除目錄:rmdir。入爐連結計數為0,且沒有程式開啟次目錄,釋放目錄空間。
- 讀取目錄:
- 更改當前工作目錄: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 設定緩衝型別
- setbuf
- setvbuf:第三個引數:
- _IOFBF:全緩衝
- _IOLBF: 行緩衝
- _IONBF:無緩衝
3. 開啟流
- fopen:開啟一個指定的檔案
- freopen:將一個檔案讀到一個指定的流。如果流已經開啟,就先關閉,已經定向,就先清除定向。
- fdopen:通過檔案描述符開啟檔案。因為管道和網路通訊等特殊檔案不能用標準io函式fopen開啟,所以用到該函式。
- type:指定檔案的開啟方式
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 格式化輸出
- printf:格式化資料寫到標準輸出
- fprintf:格式化資料到指定流
- sprintf:格式化的資料送入陣列buf中,尾部自動加入null。可能會導致緩衝區溢位,需呼叫者自己保證
- 轉換說明以%開始
6.2 格式化輸入
六. 系統資料檔案
1. 口令檔案
- 存放目錄:/etc/passwd
- 資料結構:<pwd.h>中的passwd結構體
- 檢視指定使用者口令的函式介面:
- 檢視所有使用者口令的函式介面:
2. 陰影檔案(加密口令)
- 存放目錄:/etc/shadow
- 檢視的介面:
3. 組檔案
- 存放目錄:/etc/group
- 資料結構:<grp.h>中的group
- 檢視指定組:
- 檢視所有組:
4. 其他資料檔案
- 伺服器提供服務的資料檔案:/etc/services
- 記錄協議資訊的資料檔案:/etc/protocols
- 記錄網路資訊的資料檔案:/etc/networks
5. 登陸賬號檔案
- 當前登陸進系統的使用者:/var/run/utmp
- 跟蹤登陸和登出資訊:/var/log/wtmp
6. 獲取系統資訊
- 獲取主機與作業系統相關資訊
- 只獲取主機名
7. 時間格式
- 日曆時間(UTC時間)
- 更高精度的時間
- 各種時間的轉化關係