Chapter 1. UNIX System Overview
UNIX 結構:
操作系統是控制硬件資源和提供程序運行環境的一種軟件。
系統調用是訪問內核的接口。
Shell是特殊的應用程序,提供運行其他程序的接口。
登入:
用戶名,/etc/passwd, shell
文件和目錄:
文件系統,文件名,路徑名,工作目錄,Home目錄
輸入和輸出:
文件描述符,標準輸入、輸出、錯誤,unbuffered I/O,標準I/O
程序和進程:
程序是磁盤上的可執行文件,讀入到內存中并由內核執行。
進程,進程ID,進程控制(fork, exec, waitpid),線程和線程id(id只在本進程內有效)
錯誤處理:
當函數執行錯誤發生時,將返回一個負值,并設置errno為一個整數以提供額外的信息。strerror和perror打印錯誤信息。
錯誤定義可以分為兩類:致命和非致命的錯誤。非致命錯誤一般是暫時的,可以恢復,如缺少資源,就可以延遲一段時間再重試。
用戶身份:
用戶id,組id,補充組id(一個用戶可以屬于多個組中)
信號:
用來通知進程有情況發生的一種技術。進程收到信號后有三種選擇1,忽略 2,默認處理方法 3,自己提供一個函數當信號發生時調用。
時間值:
有兩種:1,日歷時間time_t 2,進程時間clock_t( clock time, user cpu time, system cpu time)
日歷時間可用來記錄文件最后修改的時間等。
Clock time是此進程開始執行到終止的時間,包括了進程的切換,以及其他進程運行的時間片。
系統調用和庫:
系統調用是在內核空間中運行。庫封裝了系統調用,運行在用戶空間。
Chapter 2. UNIX Standardization and Implementation
UNIX標準化:
ISO C, IEEE POSIX,Single UNIX Specification(POSIX的超集)
UNIX系統的實現:
System V (AT&T)
BSD (Berkeley)
Linux
Mac OS(core is Darwin, combination of Mach kernel and FreeBSD)
Solaris (Sun, based on System V)
限制:
由于不同系統的實現定義了很多magic numbers和常量。為了增強可移植性,需要兩類的限制1,編譯時的限制2,運行時的限制。處理方法分為三種:1,編譯時的限制(headers)
2,運行時和文件、目錄無關的限制(sysconf函數)3,運行時和文件、目錄相關的限制(pathconf和fpathconf函數)
選項:
同樣為了不同系統的代碼可移植性.
1. Compile-time options are defined in <unistd.h>.
2. Runtime options that are not associated with a file or a directory are identified with the sysconf function.
3. Runtime options that are associated with a file or a directory are discovered by calling either the pathconf or the fpathconf function.
特性測試宏:
測試系統是否支持某種特性的宏定義,如__STDC__
原語系統數據類型:
<sys/types.h>中定義的數據類型。如time_t, off_t, pid_t等。
標準間的沖突:
為了兼容性,很多POSIX的系統實現了ISO C的函數。
Chapter 3. File I/O
文件描述符:
打開文件、創建文件時返回的值,用來讀、寫、定位文件的標識。
open,creat,close函數:
好像沒什么好說的。要注意open時一些特殊的文件狀態標記,如O_SYNC,O_NOCTTY等。
lseek函數:
定位文件,可以產生空洞文件。定位可以從頭,從尾,從當前位置開始。
Read,write函數:
沒啥好說的。I/O效率根據自己設定的buffer大小而不同。因此,調用read,write的次數不同,耗去的系統cpu時間不同。測試時,第一次和之后的測試效率是不同的,因為cache的存在。
文件共享:
文件共享是指不同進程間打開文件的共享。內核通過三種數據結構來表示打開的文件,1,每個進程有個進程表項,表中是打開文件的描述符向量。每個向量包含了文件描述符標記和一個指向文件表項的指針;2,內核為所有打開的文件維護一個文件表,每個文件表項包括了a,文件狀態標記,如讀、寫、添加、非阻塞等b,當前的文件偏移量c,指向文件v-node表項的指針;3,每個打開文件或設備都有一個v-node結構 ,包含文件類型和指向操作文件的函數的指針。
當兩個以上獨立進程打開同一個文件實現文件共享時,內核維護不同的文件表項,就是兩個以上進程表項中的文件表項指針指向不同的文件表項,而不同的文件表項中的v-node指針指向同一個v-node而實現文件共享。由于每個進程有自己的打開文件表項,所以有自己的文件打開狀態以及文件偏移量。
原子操作:
為了解決多個進程間共享文件時讀寫不一致的問題。如多個進程對同一個文件添加內容,如多個進程創建同一個文件,如lseek后的write,當多個進程同時執行時就會使文件寫的內容不是預期的。
函數pread,pwrite是把lseek和read/write作為原子操作來實現,避免上述問題。
Dup和dup2函數:
復制描述符,dup是將文件描述符復制到最小可用的描述符,讓最小可用的描述符指向原描述符所指向的文件表項。
dup2是將指定的文件描述符指向原描述符所指向的文件表項。如果指定的描述符已打開則先關閉。
Sync,fsync,fdatasync函數:
延遲寫是將排隊在buffer或page cache中的數據過一些時間再寫到磁盤中。當再次用到這些cache時再寫到磁盤中。
提供這些函數是將數據立即寫到磁盤中,保持文件系統的一致性。Sync和fsync的區別是,前者不等待數據寫到磁盤完成即返回,而后者等待完成后才返回。前者是寫入cache中的所有數據,系統中的update是daemon進程,它調用sync,并30s中刷新一次。而后者是指定單個文件刷新數據,多用于數據庫的更新。Fdatasync和fsync類似,但是它只刷新數據部分,而不更新文件的屬性。
fcntl函數:
作用是能夠改變打開文件的屬性。根據參數不同主要有五種用途:
1, 復制已存在的描述符
2, 獲得和設置文件描述符標記
3, 獲得和設置文件狀態標記
4, 獲得和設置異步I/O的所屬者
5, 獲得和設置記錄鎖
注意的是:我們在設置文件描述標記或文件狀態標記時,不能只是F_SETFD 或 F_SETFL的fcntl的調用,而是要先獲得值再設置值。
文件狀態標記O_SYNC,表示同步寫,每個write要等待數據寫到磁盤上才返回。而在通常情況下write只是將要寫的數據排隊,某個時刻后才寫到磁盤上。
還提到fcntl這樣的用處,fcntl可以設置shell打開的標準輸出輸入的屬性,因為它只要知道文件描述符就能做到文件屬性的改變。
Iocntl函數:
主要用于終端的I/O和提供本章中提到的函數無法實現的功能。如對磁帶的讀寫和定位的操作。
/dev/fd:
打開/dev/fd/n文件就等于是對文件描述符dup(n),假定n是已打開的描述符。
即open(”/dev/fd/n”, mode) == dup(n)
Chapter 4. Files and Directories
stat, fstat, lstat函數:
獲得磁盤上文件的結構信息,由結構stat描述。這些信息應該是從文件系統中的inode結構讀出的。包括:文件類型、訪問權限、大小、所屬用戶id等等信息。
文件類型:
普通文件,目錄文件,符號鏈接,字符設備、塊設備、socket、FIFO。
Set-User-ID和Set-Group-ID:
每個進程有六個和他相關聯的id,真實用戶id、有效用戶id、保存設置用戶id以及三個對應的組id。注意的是這是與進程相關的,以前總是弄混。
真是用戶id是用戶登錄時使用的id。
一般,有效用戶id=真是用戶id。例外是在文件模式字st_mode中置位一個特殊標記set-user-id,那么有效用戶id就是此進程所執行的文件所屬者的用戶id。有用的是,判斷一個文件是否可以被進程訪問,是根據進程的有效用戶id是否等于文件所屬的用戶id來的。舉例:普通用戶用程序passwd來修改密碼時,會訪問到root用戶的文件/etc/passwd和/etc/shadow。為什么能成功,是因為passwd是set-user-id程序并且所屬是root,當普通用戶執行時,它的有效用戶id就變成root,就可以訪問上面兩個文件。
新建文件和目錄的所屬者:
新文件的用戶id是進程的有效用戶id。
而新文件的組id有兩種說法 1.進程的有效組id;2. 文件所在的目錄的組id。具體實現,每個系統有所不同。
access函數:
它的用處是檢測真實用戶id的訪問文件的權限,而不管它是否是set-user-id程序。
umask函數:
創建文件時的屏蔽位,設置創建的文件的訪問權限。shell中也有umask命令。
chmod和fchmod函數:
改變已有文件的訪問權限。
Sticky bit:
作用是在可執行程序在第一次執行時,程序的代碼段text將復制到swap空間中。在下次執行時,此程序load到內存中就很快。如今的unix系統,虛擬內存以及快速文件系統的出現,這項技術并不需要了。
chown, fchown, and lchown Functions:
改變文件的所屬者。大多數系統中,只有root能改變文件的所屬者。Whether the owner can be changed depends on the different systems.
文件大小:
stat結構中的st_size包含文件的字節大小。但是只對普通、目錄、符號鏈接文件有意義。
對于普通文件,大小為0是允許的。第一次讀就得到end-of-file。
對于目錄文件,大小通常是16或512的倍數。
對于符號鏈接文件,大小是被鏈接的文件路徑名長度。此外,它不包含C語言中字符串的終止空字符。
stat結構中的st_blksize為文件I/O時所使用的塊大小。
stat結構中的st_blocks為文件所占的實際磁盤塊數。
文件中的洞,當文件中有洞時,通過ls –l core顯示時,會包含洞所占的字節數。而用du –s
core則顯示文件實際所占的磁盤塊數。當用read讀文件中洞的內容時,得到的是0(不是‘0’而是‘\0’,字符串的結束符),打印時將什么都不會顯示。wc –c core可以統計文件中的字節數。 經驗證,在Linux中,雖然是空洞文件,但是空洞仍然占磁盤塊空間。
文件截斷:
truncation,ftruncation函數。例外:當截斷的長度大于原有文件的長度時,效果依賴于具體的系統。符和XSI的系統文件變長,而變長的部分read出內容是0.
文件系統:
磁盤可以有多個分區,每個分區可以是不同的文件系統。文件系統主要包含部分:boot block,super block,cylinder group0~n。
每個cylinder group中包含:super block copy,i-node map,block bitmap, i-nodes(一個文件/目錄一個), data/directory blocks(存放文件/目錄的數據)。
對于普通文件,i-node指向了所屬文件的數據塊。而目錄文件,i-node則指向了所屬目錄的directory blocks。
每個directory block中的主要內容是目錄項所包含的文件的i-node號以及文件名。當目錄項包含這樣的信息時,被包含的文件的i-node中stat的成員st_nlink即鏈接數就增1。這種鏈接就是硬鏈接,通過ln命令實現,即創建一個新目錄項指向已有的文件。而只有當鏈接數為0的時候,文件才被刪除。
修改文件名如mv命令,是添加一個新目錄項指向已有文件,再unlink掉原目錄項,而不需要移動文件的實際內容。
葉目錄(目錄中不包含任何其他目錄)的鏈接數是2,分別是目錄中dot,以及父目錄對它的指向。在葉目錄增加一個子目錄,則葉目錄的鏈接數就增1,因為新增子目錄的dotdot指向它。
link,unlink,remove,rename函數:
通過link創建一個新的目錄項指向一個文件,并使鏈接數增1。這樣多個目錄項就可以指向同一個文件。大多數的系統不允許對目錄硬鏈接,因為會產生文件系統中的loop。
unlink刪除目錄項,鏈接數減1.參數是符號鏈接的話,將移除本身而不是所鏈接的文件。超級用戶能用unlink去刪除目錄,同rmdir。
remove對文件來說相當于unlink,對目錄來說是rmdir。
rename的處理情況較多復雜些。暫時不去關注,用到再說。
符號鏈接:
硬鏈接是直接指向文件的i-node,通過對i-node的鏈接增加計數來實現的。使用限制有兩點1,鏈接和文件必須在同一個文件系統中,因為硬鏈接是與i-node號相關的,不同的文件系統中不同文件可能有相同的i-node號。2,只有超級用戶才能創建目錄的硬鏈接。
符號鏈接則是間接指向一個文件,通過創建新的i-node,i-node指向data block,data block內容是文件的路徑名。符號鏈接通常是用來將一個文件或整個目錄層次移動到系統的另一個位置。
注意的是,當用到文件名作參數的函數時,函數是否follow符號鏈接。
syslink和readlink函數:
創建符號鏈接syslink。
open函數會follow符號鏈接,如果要打開符號鏈接并讀其中的內容,則要用readlink。
文件的時間:
有三種,1,最后訪問時間 2,最后修改時間 3,最后i-node狀態改變時間。
utime函數可以修改1,2的時間,而3的時間調用utime時自動更新。
mkdir和rmdir函數
mkdir創建空的目錄,并自動創建dot和dotdot目錄項。注意創建時mode的權限除了讀寫外,還要有執行,這不同于文件。新目錄的用戶id和組id需要討論。
rmdir則是刪除空目錄。
讀目錄:
有訪問權限就可讀目錄,而只有內核才可以寫目錄。
Chdir, fchdir,getcwd函數
改變當前的工作目錄chdir,fchdir。當前工作目錄是進程的一個屬性。它并不影響調用進程的當前工作目錄。
Getcwd得到當前工作目錄。
Chapter 5. Standard I/O Library
Streams and FILE Objects:
就是通過標準庫如fopen打開的返回值指針FILE,使Stream關聯到打開的文件。
fwide函數的作用是改變Stream的模式,是wide-oriented還是byte-oriented。Wide是為了支持國際化字符集,寬字符。
標準輸出、輸入、錯誤:
對應文件描述符的有相應的流, stdin, stdout, stderr。在stdio.h中有定義。
Buffering:
目的是為了最小化系統調用read、write的次數。
分為完全緩沖、行緩沖、無緩沖三種。
完全緩沖:行緩沖的流如果沒有連接到終端設備上,則是完全緩沖。Stdin和stdout如果被重定位到文件上,則是完全緩沖。
行緩沖:stdin,stdout
無緩沖:stderr
打開流:
Fopen,freopen,fdopen。
讀寫流:
一字節一次I/O: getc, fgetc, getchar. Putc,fputc, putchar.其中,getc,putc一般由宏來實現。
一行一次I/O:gets,fgets,puts,fputs,最好不要使用gets,puts,不安全。造成緩沖區溢出。
直接I/O(二進制I/O):fread,fwrite,結構體的讀寫。以上的幾個讀寫函數均不適用,如遇到空字節在結構體中時,上面函數的讀寫就無法完成。存在問題,當編譯器或系統的不同,結構體字節對齊會造成讀寫不正確;機器體系結構的不同,二進制格式對多字節的整數或浮點數的存儲也會不同。
標準I/O的效率:
驗證的結果是,用戶不用去設定在系統調用read、write時所用的緩沖區大小,在標準I/O中內部已經自動設定了這樣最佳的buffer,使系統CPU的時間使用最少。而fgets和fputs中用戶設定的line buffer的大小只會影響用戶CPU的時間。因此,數據的copy有兩次:一次在內核和標準I/O buffer之間(系統調用read,write);另一次在標準I/O buffer和我們的行buffer之間。
流的定位:
ftell,fseek,rewind
fgetpos,fsetpos(可移植的)
格式化I/O:
用得最多的是printf,scanf, sprintf, snprintf, 具體的參數標志細節,用到時再查。
臨時文件:
由兩個函數生成,tmpnam, tmpfile. 目前作用貌似不大。
Chapter 6. System Data Files and Information
Password File:
/etc/passwd文件中包含了各個用戶的如用戶名、密碼、用戶id、組id等信息。并通過passwd結構來描述。
通過如getpwuid、getpwnam、getpwent函數來獲得passwd結構。
Shadow Passwords:
出于安全考慮,用戶登錄密碼在/etc/shadow文件中加密。由spwd結構描述。
加密是one-way加密算法,意思是你不能通過加密后的密碼得出原密碼,而只能通過原密碼去驗證是否正確。
提供了相似的訪問函數。但是shadow中的用戶加密密碼是不可讀出的。
Group File,Supplementary Group ID:
/etc/group文件描述組的信息,并由group結構描述,包括組名、加密密碼、組id、所屬組
的用戶名數組。
Other Data Files:
/etc/services; /etc/protoclos; /etc/networks;
Login Accounting:
有兩個相關的文件:utmp,記錄當前所有登錄的用戶。wtmp,記錄所有的登錄和退出。
System Identification:
主機號、操作系統名、版本號等信息。uname,gethostname來獲取。
Time and Date Routines:
提供一系列的時間的相關函數,具有不同的表示方式。有個不同時間函數之間的關系圖可作
參考。
Chapter 7. Process Environment
main函數:
在執行之前,內核通過start-up routine(通常是由匯編編寫的)來執行main;
進程終止:
多種exit的終止:exit(會關閉打開的流), _exit, Exit,還有線程的pthread_exit。
atexit注冊終止時調用的函數,按照注冊的順序,反序依次調用。
shell命令:echo $? 查看程序的返回狀態。
命令行參數:
沒什么好說的
環境列表:
很少用到,主要是程序運行時的環境變量,如:HOME,SHELL,PATH,USER,LOGNAME等。
C程序的內存布局:
Text, Data, BSS, Stack, Heap.
Text是程序的代碼段。
Data是程序中初始化了的全局、靜態數據變量。
BSS是程序中未初始化的全局、靜態數據變量。即使全局、靜態數據變量初始化為0仍然是屬于BSS段。
Stack是程序中的局部變量,由高地址到低地址向下增長。
Heap是malloc調用動態分配的內存,由低地址到高地址向上增長。
注意:定義的字符串常量,其中變量算Data,而字符串的大小是屬于Text的。
共享庫:
好處是1.共享,減少可執行文件的大小 2.庫更新時,使用庫的每個程序都不用重新鏈接。
編譯時用$ cc -static hello1.c 就取消了使用共享庫,編譯出的程序很大。
內存分配:
malloc, calloc, realloc
環境變量:
提供讀取和設置環境變量的接口,getenv,setenv。。用處大概就是通過程序來設置和修改環境變量。
setjmp和longjmp:
用處是處理有深度嵌套調用的不同函數間的錯誤情況。用goto只能是本函數內的局部跳轉,這兩個函數就可以在函數間進行跳轉。
有問題是跳轉會影響不同類型變量的狀態,對于沒有優化編譯的程序,即使是register變量也是放在內存中,因此所有類型的變量仍然得到內存中的值。但是優化-O編譯的程序,由于自動變量、register變量是在寄存器中,當執行setjmp時,會從寄存器中讀取數值,setjmp之后的重新賦值將不起作用。而全局、靜態、以及volatile變量仍在內存中,值不會受到影響。因此,當寫可移植性非局部跳轉代碼時,應使用volatile屬性。
getrlimit和setrlimit函數:
每個進程都有資源限制的一個集合。如進程可用的總內存的大小,core文件的大小,創建文件的最大字節數等等。
包括軟限制和硬限制。可以設置軟限制小于等于硬限制。可以降低硬限制大于等于軟限制但不可逆。只有超級用戶進程才能提升硬限制。
資源限制影響調用的進程,并由子進程繼承。一般內建到shell中設置資源限制。
Chapter 8. Process Control