《UNIX环境高级编程——APUE》
《UNIX環境高級編程——APUE》
【附】小知識
1、同步、異步
概念:消息的通知機制
解釋:涉及到IO通知機制;
同步,就是發起調用后,被調用者處理消息,必須等處理完才直接返回結果,沒處理完之前是不返回的,調用者主動等待結果;
eg. 我去銀行辦理業務, 選擇排隊等,排到頭了就辦理。
eg. 我去銀行辦理業務*,* 取一個小紙條上面有我的號碼*,* 等到排到我這一號時由柜臺的人通知我輪到我去辦理業務。
2、阻塞與非阻塞
概念:程序等待調用結果時的狀態
解釋:涉及到CPU線程調度;
阻塞:就是調用結果返回之前,該執行線程會被掛起,不釋放 CPU 執行權,線程不能做其它事情,只能等待,只有等到調用結果返回了,才能接著往下執行;
eg. 上面的那個例子, 不論是排隊還是使用號碼等待通知,如果在這個等待的過程中,等待者除了等待消息之外不能做其它的事情, 那么該機制就是阻塞的。
非阻塞:就是在沒有獲取調用結果時,不是一直等待,線程可以往下執行,如果是同步的,通過輪詢的方式檢查有沒有調用結果返回,如果是異步的,會通知回調。
eg. 在銀行辦理這些業務的時候一邊打打電話發發短信一邊等待,這樣的狀態就是非阻塞的。
3、基本的 進程控制語言
? 用 fork 創建新進程;
? 用 exec 可以初始執行新的程序;
? exit 函數 和 wait 函數處理終止和等待終止。
exit(0) 正常退出
exit(1) 非正常退出
4、進程組、會話、前臺進程組、后臺進程組、終端控制
進程組與會話:
進程組是一組相關進程的集合,會話是一組相關進程組的集合。進程都有父進程, 父進程也有父進程, 這就形成了一個以init進程為根的家族樹。除此以外,進程還有其他層次關系:進程、進程組、會話。進程組和會話在進程之前形成了兩級的層次:進程組是一組相關進程的集合,會話是一組相關進程組的集合。這樣說來,一個進程會有如下ID:
? PID:進程的唯一標識。對于多線程的進程而言所有線程調用getpid()函數會返回相同值。
? PGID:進程組ID。每個進程都會有進程組ID,表示該進程所屬的進程組。默認情況下新創建的進程會繼承父進程的進程組ID。
? SID:會話ID。每個進程也都有會話ID。默認情況下,新創建的進程會繼承父進程的會話ID
會話:
由于Linux是多用戶多任務的分時系統,所以必須要支持多個用戶同時使用一個操作系統。當一個用戶登錄一次系統就形成一次會話。
一個會話包含多個進程組,但是只能有一個前臺進程組。每個會話都有一個會話首領 (leader),即創建會話的進程 sys_setsid() / setsid() 調用能創建一個會話。但必須注意的是,只有當前進程不是進程組組長時,才能創建一個新的會話。調用 setsid 之后,該進程成為會話的 leader 。
一個會話只能有一個控制終端。這通常是登錄到其上的終端設備(在終端登錄情況下)或者偽終端設備在網絡登錄情況下。建立與控制終端連接的會話被稱為控制進程一個會話中的幾個進程組可以分為前臺進程組與后臺進程組。所以一個會話中,應該包括控制進程(會話首進程),一個前臺進程組與任意多個后臺進程組。
前臺進程 與 后臺進程:
用戶在 shell 中可以同時執行多個命令。對于耗時很久的命令(如編譯大型工程),用戶不必傻傻等待命令運行完畢才執行下一個命令。用戶在執行命令時,可以在命令的結尾添加 “&” 符號,表示將命令放入后臺執行。這樣該命令對應的進程組即為后臺進程組。
在任意時刻,可能同時存在多個后臺進程組,但是不管什么時候都只能有一個前臺進程組。只有在前臺進程組中進程才能在控制終端讀取輸入。當用戶在終端輸入信號生成終端字符(如ctrl+c、ctrl+z、ctr+\等)時,對應的信號只會發送給前臺進程組。
shell 中可以存在多個進程組,無論是前臺進程組還是后臺進程組,它們或多或少存在一定的聯系,為了更好地控制這些進程組(或者稱為作業),系統引入了會話的概念。會話的意義在于將很多的工作囊括在一個終端,選取其中一個作為前臺來直接接收終端的輸入及信號,其他的工作則放在后臺執行。
終端控制:
會話的領頭進程打開一個終端后,該終端就會成為該會話的控制終端(SVR4/linux),與控制終端建立連接的會話領頭進程成為控制進程(session leader)。一個會話只能有一個控制終端,產生在控制終端上的輸入和信號將發送給會話的前臺進程組中的所有進程,終端上的連接斷開時(比如網絡斷開或Modem斷開),掛起信號將發送到控制進程(session leader)。
綜上:
進程屬于一個進程組,進程組屬于一個會話,會話可能有也可能沒有控制終端。一般而言,當用戶在某個終端上登錄時,一個新的會話就開始了。
進程組由組中的領頭進程標識,領頭進程的進程標識符就是進程組的組 標識符。類似的,每個會話也有對應一個領頭進程。同一會話中的進程通過該會話的領頭進程和一個終端相連,該終端作為這個會話的控制終端。
一個會話只有一個控制終端,而一個控制終端只能控制一個會話。用戶通過控制終端,可以向控制終端所控制的會話中的進程發送鍵盤信號。
同一個會話中只能有一個前臺進程組,屬于前臺進程組的進程可以從控制終端獲得輸入,而其他進程均是后臺進程,可能分屬于不同的后臺進程組。
當我們打開多個終端窗口時,實際上就創建了多個終端會話。每個會話都有自己的前臺工作和后臺工作
5、init
在計算機上啟動Linux時,內核裝入并啟動init程序。然后init程序裝載硬盤和啟動終端程序。登錄終端程序時,它啟動命令行界面Shell。在計算機上啟動Linux之后,init程序監視任何關閉計算機的信號,如不間斷電源(UPS)發生的電源故障信號和重新啟動命令。init是Linux系統操作中不可缺少的程序之一。所謂的init進程,它是一個由內核啟動的用戶級進程。內核自行啟動(已經被載入內存,開始運行,并已初始化所有的設備驅動程序和數據結構等)之后,就通過啟動一個用戶級程序init的方式,完成引導進程。所以,init始終是第一個進程(其進程編號始終為1)。 內核會在過去曾使用過init的幾個地方查找它,它的正確位置(對Linux系統來說)是/sbin/init。如果內核找不到init,它就會試著運行/bin/sh,如果運行失敗,系統的啟動也會失敗。6、Unix/Linux 的 System V、BSD、Posix概念
System V和BSD
??Unix操作系統在操作風格上主要分為System V和BSD(目前一般采用BSD的第4個版本SVR4),前者的代表的操作系統有Solaris操作系統,在Solaris1.X之前,Solaris采用的是BSD風格,2.x之后才投奔System V陣營。后者的代表的操作系統有FreeBSD。
??System V它最初由AT&T開發,曾經也被稱為AT&T System V,是Unix操作系統眾多版本中的一支。在1983年第一次發布,一共發行了4個System V的主要版本,System V Release4,或者稱為SVR4,是最成功的版本,該版本有些風格成為一些UNIX共同特性的源頭,如下表格的初始化腳本/etc/init.d。用來控制系統的啟動和關閉。
??BSD(Berkeley Software Distribution,伯克利軟件套件)是Unix的衍生系統,1970年代由伯克利加州大學(Uni Versity of California, Berkeley)開創。BSD用來代表由此派生出的各種套件集合。
Poxis和System V
??System V的概念如上所述。Posix是Portable Operating System Interface(可移植性操作系統接口)的簡稱,是一個電氣與電子工程學會即IEEE開發的一系列標準,目的是為運行在不同操作系統的應用程序提供統一的接口,實現者是不同的操作系統內核。
??將這兩個名詞放在一起討論的一般是在Linux的進程間通信中,如在信號量編程中,有Posix信號量和System V信號量。它們都可以用于進程或者線程間的同步。然而, Posix信號量是基于內存的,即信號量值是放在共享內存中的,它使與文件系統中的路徑名對應的名字來標識。當我們談論“Posix 信號量”時,所指的是單個計數信號量。在Linux操作系統中,Posix信號量(共享內存、消息隊列)可以通過ipcs命令查看。Posix信號量多用于進程間通信。
??System v信號量測試基于內核的,它放在內核里面,要使用System V信號量需要進入內核態,所以在多線程編程中一般不建議使用System V信號量,因為線程相對于進程是輕量級的,從操作系統的調度開銷角度看,如果使用System V信號量會使得每次調用都要進入內核態,喪失了線程的輕量優勢。當我們討論“System v信號量”時,所指的是計數信號量集。
第1章-UNIX基礎知識
1、UNIX 和Linux的區別:
? UNIX 誕生于 20 世紀 60 年代末,
Windows 誕生于 20 世紀 80 年代中期,
Linux 誕生于 20 世紀 90 年代初
(后來的 Windows 和 Linux 都參考了 UNIX)
? UNIX大多與硬件配套(UNIX操作系統),Linux 可運行在多種硬件平臺上。
? UNIX收費(貝爾實驗室),Linux 免費開源
2、什么是Linux 系統
Linux系統主要由以下4部分構成:
Linux內核
內核主要負責以下四種功能:
系統內存管理
內核不斷地在交換空間(swap space)和實際物理內存之間交換虛擬內存中的內容。
2.軟件程序管理(進程管理)
3.硬件設備管理
通過驅動程序實現硬件設備與應用程序之間的通信。在Linux系統中加入驅動程序代碼的方式有以下兩種: 1.編譯進內核的設備驅動代碼
2.可插入內核的設備驅動代碼
4.文件系統管理
ext,ext2,ext3,ext4,minix,nfs,ntfs,XFS等。
GNU工具
GNU(GNU is not Unix的縮寫),是一套為Unix系統管理員設計的一套類似于Unix的環境。
Linux 系統和 GNU 工具的結合體稱為 Linux系統,也叫做 GNU/Linux系統。 核心 GNU 工具(coreutils)包括以下三部分:
1.用以處理文件的工具
2.用于處理文本的工具
3.用于管理進程的工具
還包括 shell,例如 bash shell。
圖形化桌面環境
X Window軟件包:直接和 PC 上的顯卡和顯示器打交道的底層程序,可以產生圖形化顯示環境。 其中最流行的軟件包時 x.org。 桌面環境:KDE、GNOME、Unity(Ubuntu特有)等
應用程序
3、進程控制
進程
進程:程序的執行實例被稱為進程(process)。
進程ID: UNIX系統確保每個進程都有一個唯一的數字標識符(是一個非負整數)。
進程控制:有 3 個用于進程控制的主要函數: folk 、exec 和 waitpid。
線程
線程:某一時刻執行的一組機器指令。
線程ID:線程也有自己的ID標識符,只在它所屬的進程內起作用。一個線程中的線程ID在另一個進程中沒有意義。
信號
信號(signal): 用于通知進程發生了某種情況。
第 2 章- UNIX 標準及實現
沒講啥
第 3 章-文件 I/O
1、引言
文件 I/O函數主要包括:打開文件、讀文件、寫文件 操作。用到的函數有:open、 read、 write、 lseed、 close。2、文件描述符
1) UNIX系統shell把
文件描述符 0 與進程的標準輸入關聯;
文件描述符 1 與標準輸出關聯;
文件描述符 2 與標準錯誤關聯;
一個套接字端點表示為一個文件描述符(16.5)。
3、函數 open 和 openat
在 <fcnt1.h>中定義了幾個常用的常量,表示對文件的操作:
O_RDONLY 只讀打開, 一般定義為 0
O_WRONLY 只寫打開, 一般定義為 1
O_RDWR 讀、寫打開, 一般定義為2
4、函數 creat
#include <fcnt1.h>
int creat(const char *path, mode_t mode);
//返回值:成功,返回為只寫打開的文件描述符。出錯,返回-1
//path 創建的文件名, mode 權限位
//eg.
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define PERM 0755
int main(void)
{
static char filename[] = “file.txt”; //會創建一個名為 file的txt文件
int fd;
}
4.1 Linux 系統中采用三位十進制數表示權限,如0755, 0644.
ABCD A- 0, 表示十進制 B-用戶 C-組用戶 D-其他用戶
— -> 0 (no excute , no write ,no read) --x -> 1 excute, (no write, no read) -w- -> 2 write -wx -> 3 write, excute r-- -> 4 read r-x -> 5 read, excute rw- -> 6 read, write , rwx -> 7 read, write , excute
0755-> 即用戶具有讀/寫/執行權限,組用戶和 其它用戶具有讀寫權限; 0644->即用戶具有讀寫權限, 組用戶和 其它用戶具有只讀權限;
一般賦予目錄0755權限,文件0644權限。
5、函數 close
頭文件:
#include<unistd.h>
//功能:關閉一個已經打開的文件
原型
int close(int fd)
//參數說明: fd:是需要關閉的文件描述符
返回值
//成功:返回0;
//失敗:返回-1,并設置errno
6、函數 lseek
可以調用 lseek 顯示地為一個打開文件設置偏移量。7、函數 read
從打開文件中讀數據。8、函數write
像打開文件寫數據11、原子操作
1.函數 pread 和 pwrite
這兩種擴展允許原子性地定位并執行 I/O
12、函數 dup 和 dup2
這兩個函數可用來復制一個現有的文件描述符。文件描述符:
Linux 中一切皆文件。Linux 會給每一個文件分配一個編號(ID),這個編號就是一個整數,也就是文件描述符。
13、函數 sync、fsync 和fdatasync
延遲寫(delay write):我們向文件寫入數據時,內核通常先將數據復制到緩沖區中,然后排入隊列,晚些時候再寫入磁盤。
這三個函數是為了保證磁盤上實際文件系統與緩沖區中內容的一致性。
14、函數 fcnt1
它可以改變已經打開文件的屬性
15、函數 ioct1
ioct1 函數一直是 I/O 操作的雜物箱。終端I/O是使用 ioct1最多的地方。16、 /dev.fd
其目錄項是名為0、1、2等的文件。 打開文件 /dev/fd/n 等效于復制描述符 n(假定描述符 n 是打開的)。第 4 章- 文件和目錄
1、
2、函數 stat、 fstat、fstatat 和 lstat
功能:返回與此命名文件有關的信息結構。3、文件類型
? 普通文件(regular file)
? 目錄文件(directory file)
? 塊特殊文件(block special file)
? 字符特殊文件(character special file)
? FIFO
? 套接字(socket)
? 符號鏈接(symbolic link)
4、設置用戶 ID 和設置組 ID
5、文件訪問權限
每個文件有 9 個訪問權限位S_IRUSR 用戶讀
S_IWUSR 用戶寫
S_IXUSR 用戶執行
S_IRGRP 組讀
S_IWGRP 組寫
S_IXGRP 組執行
S_IROTH 其他讀
S_IWOTH 其他寫
S_IXOTH 其他執行
6、新文件和目錄的所有權
7、函數 access 和 faccessat
這兩個函數是按實際用戶 ID 和實際組 ID 進行訪問權限測試的。
8、函數 umask
9、函數 chmod 、fchmod 、和 fchmodat
10、黏著位
11、函數 chown、fchown、fchownat 和 lchowm
13、文件截斷
#include <unistd.h>
int truncate(const char *pathname, off_t length);
int ftruncate(int fd, off_t length);
函數 truncate 和ftruncate 可以將一個現有文件長度截斷為 length 長
14、文件系統
任何一個文件可以有多個目錄指向其 i 節點。15、函數 link、linkat、unlink、unlinkat 和 remove
函數link、linkat 可以創建一個指向現有文件的鏈接。#include <unistd.h>
int link(const char *existingpath, const char *newpath);
int linkat(int efd, const char *existingpath, int nfd, const char *newpath, int flag);
函數 unlink 可以刪除一個現有的目錄項。
#include <unistd.h>
int unlink(const char *pathname);
int unlink(int fd, const char *pathname, int flag);
函數 unlinkat 可以類似于 rmdir 一樣刪除目錄
函數 remove 可以解除對一個文件或目錄的鏈接。
16、函數 rename 和 renameat
文件或目錄可以用 rename 函數 或者renameat函數進行重命名。
17、符號鏈接
18、創建和讀取符號鏈接
可以用 symlink 或 symlinkat函數創建一個符號鏈接
19、文件的時間
20、函數 futimens、utimensat 和 utimes
futimens 和 utimensat函數可以指定納秒級精度的時間戳。21、函數 mkdir、mkdirat 和 rmdir
用 mkdir 和 mkdirat 函數創建目錄
用 rmdir 函數刪除目錄。
22、讀目錄
對某個目錄具有訪問的任一用戶都可以讀該目錄,但是為了防止文件系統產生混亂,只有內核才能寫目錄。23、函數 chdir、 fchdir 、 和 getcwd
進程調用 chdir 和 fchdir 函數可以更改當前工作目錄。24、設備特殊文件
25、文件訪問小結
第 5 章- 標準I/O庫
1、
2、流和 FILE 對象
? 對于所有的 I/O函數,都是圍繞文件描述符的;
對于標準的 I/O庫,它們的操作是圍繞 流(stream)進行的。
? 流的定向(stream’s orientation)決定了所讀、寫的字符是單字節還是多字節。
有兩個函數可以改變流的定向: freopen 、 fwide;
=> freopen函數可以清除一個流的定向;
fwide 函數可用于設置流的定向。
3、標準輸入、標準輸出 和標準錯誤
分別是: STDIN_FILENO 、STDOUT_FILENO、STDERR_FILENO4、緩沖
標準 I/O庫提供緩沖的目的是盡可能減少使用 read 和write調用的次數。標準I/O庫提供了三種類型的緩沖:? 全緩沖:在填滿標準I/O緩沖區后才進行實際I/O操作。
? 行緩沖:當再輸入和輸出中遇到換行符時,標準I/O庫執行I/O操作。
? 不帶緩沖:標準I/O庫不對字符進行緩沖存儲。
5、打開流
與以下三個函數有關: fopen、 freopen、 fdopen。? fopen 函數打開路徑名為 pathname 的一個指定的文件;
? fropen 函數在一個指定的流上打開一個指定的文件,若該流已經打開,則先關閉該流;
? fdopen 函數取一個已有的文件描述符,并使一個標準的I/O流與該描述符相結合。
6、讀和寫流
一旦打開了流,則可在3種不同類型的非格式化I/O中進行選擇,對其進行讀、寫操作。
? 每次一個字符的I/O;
? 每次一行的I/O;
? 直接I/O。
7、每行一次 I/O
有兩個函數指定了緩沖區的地址,讀入的行將送入其中:fgets、gets函數。
? fgets 必須指定緩沖的長度 n
? gets 函數是一個不推薦的函數。
問題:調用子在使用gets時不能指定緩沖區的長度。
8、標準 I/O 的效率
9、二進制 I/O
Linux 提供了兩個函數以執行二進制I/O操作: fread 函數 和 fwrite函數。
函數功能是 返回讀或寫的對象數。
問題:它只能用于讀在同一系統上已寫的數據。
10、定位流
有三種方法定位 I/O流:
? ftell 和 fseek 函數。
? ftello 和 fseeko 函數
? fgetpos 和 fsetpos 函數。
11、格式化 I/O
? 格式化輸出:
由5個printf函數來處理:
o printf:將格式化數據寫到標準輸出
o fprintf:寫至指定的流;
o dprintf:寫至指定的文件描述符;
o sprintf:將格式化的字符送入數組 buf 中。
o snprintf:能夠解決緩沖區溢出問題。
? 格式化輸入:
由3個 scanf函數來處理:
o scanf 族用于分析輸入字符串,并將字符序列轉換成指定類型的變量。
12、實現細節
13、臨時文件
ISO C標準I/O庫提供了兩個函數以幫助創建臨時文件。
? tmpnam 函數產生一個與現有文件名不同的一個有效路徑名字字符串。
? tmpfile 函數創建一個臨時二進制文件(類型 wb+),在關閉該文件或程序結束時將自動刪除這種文件。
14、內存流
我們已經看到,標準 I/O庫把數據緩存在內存中,因此每次一字符和每次一行的 I/O更有效。有的I/O都是通過在緩沖區主存之間來回傳送字節來完成的。
15、標準 I/O的替代軟件
第 6 章- 系統數據文件和信息
2、口令文件
某些系統提供了 vipw 命令,允許管理員使用該命令編輯口令文件。vipw命令串行化地更改口令文件。
POSIX.1定義了兩個獲取口令文件項的函數。在給出用戶登錄名或數值用戶ID后,這兩個函數就能查看相關項目:getpwuid函數、getpwnam函數。
getpwuid: 由 ls(1)程序使用,它將i節點中的數字用戶ID映射為用戶登錄名。在鍵入登錄名后,getpwnam函數由login(1)程序使用。
3、陰影口令
為使別人難以獲得原始資料(加密口令),現在,某些系統將加密口令存放在另一個通常稱為陰影口令(shadow password)的文件中。該文件至少要包含用戶名和加密口令。
4、組文件
5、附屬組 ID
1983年左右,4.2 BSD( 伯克利軟件套件 )引入了附屬組ID的概念。這樣不僅可以屬于口令文件記錄項中組ID所對應的組,也可以屬于多至15個另外的組。
6、實現區別
7、其它數據文件
8、登陸賬戶記錄
大多數時候UNIX系統都提供下列兩個數據文件:
utmp文件記錄當前登陸到系統的各個用戶;
wtmp文件跟蹤各個登陸和注銷事件。
9、系統標識
POSIX.1(可移植操作系統接口)定義了uname 函數,它返回與主機和操作系統有關的信息。
10、時間和日期歷程
第7章-進程環境
1、引言
本章將學習:
當程序執行時,其main函數是如何被調用的; 02
命令行參數是如何傳遞給新程序的; 04
典型的存儲空間布局是什么樣式; 06
如何分配另外的存儲空間; 08
進程如何使用環境變量 09
進程的各種不同終止方式; 03
longjmp 和 setjmp 函數以及它們與棧的交互作用。 10
2、 main函數
main 函數的原型是:
int main( int argc, char *argv[] )
其中 argc 是命令行參數的數目; argv是指向參數的各個指針所構成的數組( 存放命令行每個字符串的首地址)。
在調用 mian 函數前先調用一個特殊的 啟動例程。可執行程序文件將此啟動例程指定為程序的起始地址——這是由 連接編輯器設置的,而連接編輯器則是由 C 編譯器調用。
3、進程終止
有8種方式使進程終止(termination):
5種正常終止:
? 從 main 返回;
? 調用 exit;
? 調用 _exit 或 _Exit;
? 最后一個線程從其啟動歷程返回 (11.5節)
? 從最后一個線程調用 pthread_exit; (11.5節)
3種異常終止:
? 調用 abort (10.17節)
? 接到一個信號 (10.2節)
? 最后一個線程對取消請求作出響應(11.5節、12.7節)
3.1- 退出函數
有 3 個函數可用于正常終止一個程序:
? _exit 和 _Exie 會立即進入內核;
? exit 則先執行一些清理處理,然后返回內核。
這3個退出函數都帶一個整形參數,稱為 終止狀態。
若 main 的返回類型是整形, 并且 main 執行到最后一條語句時返回(隱式返回),那么該進程的終止狀態是 0。
于是: main函數中的 return 0; 等價于 exit(0);
3.2- 函數 atexit
按照國際標準化組織的規定,一個進程可以登記多至32個終止處理程序(exit handler)的函數,這些函數將由 exit 自動調用,系統同時調用 atexit 函數來登記這些函數。
4、命令行參數
當執行一個程序時,調用 exec 的進程可將命令行參數傳遞給該新程序。
5、環境表
每個程序都接收到一張環境表。
與參數表一樣,環境表也是一個字符指針,其中每個指針包含一個以 null 結束的 C 字符串的地址。
全局變量 環境指針( environ) 則包含了該指針數組的地址:
extern char **environ;
6、 C程序的存儲空間布局
歷史沿襲至今,C程序一直由下列幾部分組成:
? 正文段;
? 初始化數據段;
? 未初始化數據段;
? 棧;
? 堆。
7、共享庫
共享庫使得可執行文件中不再需要包含公用的庫函數,而只需在所有進程都可引用的存儲區中保存這種庫例進程的一個副本。
8、存儲空間分配
國際標準組織 說明了 3 個用于存儲空間動態分配的函數:
? malloc : 分配指定字節數的存儲區;
? calloc : 為指定數量指定長度的對象分配存儲空間;
? realloc : 增加或減少以前分配區的長度。
9、環境變量
環境字符串的形式是:
name = value
國際標準化組織 C 定義了一個函數 getenv, 可以用其取環境變量值,但是該標準又稱環境的內容是由實現定義的。
10、函數 setjmp 和 longjmp
在 C 中,goto 語句不能跨越函數,而 setjmp 和 longjmp 函數可以。
這兩個函數對于處理發生在很深層嵌套函數調用中的出錯情況是非常有用的。
11、函數 getrlimit 和setrlimit
函數 getrlimit 和 setrlimit 可以進行查詢和更改資源限制。
對這兩個函數的每一次調用都指定一個資源以及一個指向下列結構的指針。
12、小結
本章說明了:
? 一個進程是如何啟動和終止的;
? 如何向其傳遞參數表和環境;
? C 程序的典型存儲空間布局;
? 一個進程如何動態地分配和釋放存儲空間;
? setjmp 和 longjmp 函數,他們提供了一種在進程內非局部轉移的方法。
第 8 章-進程控制
1、引言
本章介紹 UNIX 系統的進程控制,包括:創建新進程、執行程序和進程終止。
還將說明進程屬性的各種 ID ——實際、有效和保存的用戶 ID 和組ID ,以及它們如何受到進程控制原語的影響。
2、進程標識
每個進程都有一個非負整數表示的唯一進程 ID 。P.S. 雖然進程 ID 是唯一的,但是當一個進程終止后,其進程 ID 就稱為復用的候選者。
系統中有一些專用進程 ID:
? ID 為 0 的進程通常是調度進程,也被稱為 交換進程(swapper) ,它并不執行任何磁盤上的程序,因此也被稱為系統進程。
? ID 為 1 通常是 init 進程,次進程負責在自舉內核后啟動一個 UNIX 系統。
? ID 為 2 是頁守護進程( page daemon),此進程負責支持虛擬存儲器系統的分頁操作。
3、函數 fork
fork() 系統調用通過復制一個現有進程來創建一個全新的進程(進程的另外一個名字叫做任務)。
由 fork 創建的新進程被稱為 子進程(child process)
fork 函數被調用一次,但返回兩次。兩次返回的區別是子進程的返回值是 0 ,而父進程的返回值則是新建子進程的進程 ID。
子進程是父進程的副本。eg. 子進程獲得父進程數據空間、堆和棧的副本,父進程和子進程并不共享這些存儲空間。
4、函數 vfork
vfork 函數的調用序列和返回值與 fork 相同,但 兩者 的語義不同。
vfork 函數用于創建一個新進程,而該新進程的目的是執行一個新程序。但是 vfork 和 fork還是有 2 點不同的(書 p .187)
5、函數 exit
詳細介紹了 5 中正常終止及 3 種異常終止的方式。
終止函數: exit 、 _exit、 _Exit,它們終止后,會給父進程傳遞退出狀態( exit status )的參數。
僵死進程: 一個已經終止、但是其父進程尚未對其進行善后處理 (獲取終止子進程的有關信息、釋放它仍占用的資源)的進程被稱為僵死進程。
6、 函數 wait 和 waitpid
當一個進程正常或異常終止時,內核就向其父進程發送 SIGCHLD 信號。
一個進程有多個子進程,只要有一個子進程終止, wait 就返回。
大家知道,當用fork啟動一個新的子進程的時候,子進程就有了新的生命周期,并將在其自己的地址空間內獨立運行。但有的時候,我們希望知道某一個自己創建的子進程何時結束,從而方便父進程做一些處理動作。同樣的,在用 ptrace 去 attach 一個進程滯后,那個被 attach 的進程某種意義上說可以算作那個 attach 它進程的子進程,這種情況下,有時候就想知道被調試的進程何時停止運行。
以上兩種情況下,都可以使用 Linux 中的 waitpid() 函數做到。先來看看waitpid函數的定義:
如果在調用waitpid()函數時,當指定等待的子進程已經停止運行或結束了,則waitpid()會立即返回;但是如果子進程還沒有停止運行或結束,則調用waitpid()函數的父進程則會被阻塞,暫停運行。
調用 wait 或 waitpid 的進程可能會放生什么?
? 如果其所有子進程都還在運行,則阻塞;
? 如果一個子進程已終止,正等待父進程獲得其終止狀態,則取得該子進程的終止狀態立即返回;
? 如果它沒有任何子進程,則立即出錯返回。
? waitpod 函數提供了 wait 函數沒有提供的 3 個功能:
7、函數 waitid
UNIX 包含了另一個取得進程終止狀態的函數—— waitid , 此函數類似于 waitpid,但提供了更多的靈活性。
waitid 函數允許一個進程指定要等待的子進程。
8、函數 wait3 和 wait4
大多數 UNIX 系統實現提供了另外兩個函數 wait3 和 wait4。
相較于函數 wait 、 waitpid 、waitid 所提供的功能要多一個,這與附加參數有關。該參數允許內核返回由終止進程及其所有子進程使用的資源概況,包括: CPU時間總量、系統CPU時間總量、缺頁次數、接收到信號的次數等。
9、競爭條件
當多個進程都企圖對共享數據進行某種處理,而最后的結果又取決于進程運行的順序時,我們認為發生了競爭關系( race condition )。
10、函數 exec
在 “3-函數 fork” 中提到:用 fork 函數創建新的子進程后,子進程往往要調用一種 exec 函數以執行另一個程序。
當進程 調用一種 exec 函數時,該進程執行的程序完全替換為 新程序,而新程序是從其 main 函數開始執行。
因為調用 exec 并不創建新進程,所以前后的進程 ID 并未改變。 exec 只是用磁盤上的一個新程序替換了當前進程的正文段、數據段、堆段和棧段。
11、更改用戶 ID 和 更改組 ID
在 UNIX 系統中,特權(如能改變當前日期的表示法)以及訪問控制(如能否讀、寫一個特定文件),是基于用戶 ID 和 組 ID 的。
當程序需要增加特權,或需要訪問當前不允許訪問的資源時,我們需要更換自己的用戶 ID 或 組 ID,使得新 ID 具有合適的特權或訪問權限。當程序需要降低其特權或阻止對某些資源的訪問時,也需要更換用戶 ID 或組 ID,新 ID 不具有相應特權或訪問這些資源的能力。
12、解釋器文件
解釋器文件是一種文本文件。
13、函數 system
ISO C定義了 system函數,它總共有 3 種返回值。
相較于 fork 、 exec, system的優點為: system進行了所需的各種出錯處理以及各種信號處理。
14、 進程會計
大多數 UNIX 系統提供了一個選項以進行 進程會計( process accounting )處理。
啟用該選項后,每當進程結束時內核就寫一個會計記錄。一般進程會計包括: 命令名、所使用的 CPU 時間總量、用戶 ID 和 組 ID 、啟動時間等。
15、用戶標識
任一進程都可以得到其實際用戶 ID 和有效用戶 ID 及組 ID。
但是,有時候我們希望找到運行該程序用戶的登陸名。系統通常記錄用戶登陸時使用的名字,然后用 getlogin 函數可以獲取此登錄名。
如果調用此函數的進程沒有連接到用戶登陸時所用的終端,則函數失敗。通常稱這些進程為 守護進程( daemon )
16、 進程調度
調度策略和調度優先級是有內核確定的。
進程可以通過調整 友好值 選擇以更優先級運行(通過調整友好值降低它對 CPU 的占有,因此該進程是有好的)。只有特權進程允許提高調度權限。
友好值越小,優先級越高(越靠前嘛!)。
進程可以通過 nice 函數獲取或更改它的友好值。使用這個函數,進程只能影響自己的友好值,不能影響任何其它進程的友好值。
17、進程時間
在 書 p16中說明了可以度量的 3 個時間:墻上時鐘時間、用戶 CPU時間 和 系統 CPU時間。
任一進程都可以調用 times 函數獲得它自己以及終止子進程的上述值。
18、小結
UNIX 必須掌握的幾個函數: fork、exec系列、 _exit、wait、waitpid。
? fork:用來創建新的進程;
? exec:用 fork 函數創建新的子進程后,子進程往往要調用一種 exec 函數才能執行另一個程序;
? _exit:給父進程傳遞退出狀態( exit status )的參數;
? wait、waitpid:我們希望知道某一個自己創建的子進程何時結束;
? 本章說明了 system函數和進程會計,這也使我們進一步了解所有這些進程控制函數。
system進行了所需的各種出錯處理以及各種信號處理
? 本章還說明了 exec函數的另一種變體:解釋器文件及它們的工作方式。
? 對各種不同的用戶 ID 和組 ID(實際、有效 和 保存的)的理解,對編寫安全的設置用戶 ID 程序是至關重要的。
在 UNIX 系統中,特權(如能改變當前日期的表示法)以及訪問控制(如能否讀、寫一個特定文件),是基于用戶 ID 和 組 ID 的。
當程序需要增加特權,或需要訪問當前不允許訪問的資源時,我們需要更換自己的用戶 ID 或 組 ID,使得新 ID 具有合適的特權或訪問權限。當程序需要降低其特權或阻止對某些資源的訪問時,也需要更換用戶 ID 或組 ID,新 ID 不具有相應特權或訪問這些資源的能力。
第 9 章-進程關系
1、引言
本章將詳細地說明進程組 以及 POSIX.1(可一只操作系統接口)引入的會話的概念。還將介紹登陸 shell(登錄時所調用的)和所有從登陸 shell 啟動的進程之間的關系。
2、終端登陸
系統經由內核中的終端設備驅動程序。
4 種終端登陸方式:
? BSD 終端登陸 ( Berkeley Software Distribution 伯克利軟件套件)
? Mac OS X 終端登陸
? Linux 終端登陸
? Solaris 終端登錄
3、網絡登陸
通過串行終端登錄至系統和經由網絡登陸至系統兩者之間主要(物理上的)區別是:網絡登錄時,在終端和計算機之間的連接不再是點到點的。在網絡登陸情況下, login僅僅是一種可用的服務,這與其它網絡服務(如 FTP 或 SMTP)的性質相同。
在 9.2 的終端登錄中, init 知道哪些終端設備可用來進行登陸,并為每個設備生成一個 getty 進程。但是,對網絡登陸情況則有所不同,所有登陸都經由內核的網絡接口驅動程序(如 以太網驅動程序 )。
為使同一個軟件既能處理終端設備,又能處理網絡登陸,系統使用了一種稱為 偽終端(pseudo terminal )的軟件驅動程序,它仿真串行終端的運行行為,并將終端操作映射為網絡操作。
有以下四種登陸方式:
? BSD 網絡登陸
? Mac OS X 網絡登陸
? Linux 網絡登錄
? Solaris 網絡登陸
4、進程組
每個進程除了有一個進程 ID 之外,還屬于一個進程組。進程組是一個或多個進程的集合。
每個進程組有一個唯一的進程組 ID,進程組 ID 類似于進程 ID ——它是一個正整數,并可存放在 pid_t 數據類型中。
進程調用 setpgid 函數 可以加入一個現有的進程組或者創建一個新進程組。
#include <unistd.h>
int setpgid( pid_t pid, pid_t pgid);
//返回值:若成功,返回0; 若出錯,返回 -1
setpgid 函數 將 pid 進程的進程組 ID 設置為 pgid。
一個進程只能為它自己或它的子進程設置進程組 ID。 在它的子進程調用了 exec 后,它就不再改該子進程的進程組 ID。
5、會話
會話( session ) 是一個或多個進程組的集合。
通常是由 shell 的管道將幾個進程編成一組的。
進程調用 setsid 函數建立一個新會話。
#include <unistd.h>
pid_t setsid(void);
//返回值:若成功,返回進程組 ID; 若出錯,返回 -1
? 如果該調用進程已經是一個進程組的組長,則此函數返回出錯(有具體解決方法);
? 如果調用此函數的進程不是一個進程組的組長,則此函數創建一個新會話。(具體會發生 3 件事)
6、控制終端
會話 和 進程組還有一些其它特性:
? 一個會話可以有一個控制終端( controlling terminal )。這通常是終端設備(在終端登錄情況下)或偽終端設備(在網絡登陸情況下)。
? 建立與控制終端連接的會話首進程被稱為 控制進程( controlling process )
? 一個會話的幾個進程組可被分成一個 前臺進程組( foreground process group )以及一個或多個 后臺進程組( background process group )。
? 如果一個會話有一個控制終端,則它有一個前臺進程組,其他進程組稱為后臺進程組。
7、函數 tcgetpgrp、 tcsetpgrp 和 tcgetsid
有時候需要有一種方法來通知內核哪一個進程組是前臺進程組,這樣,終端設備驅動程序就能知道將終端輸入和終端產生的信號發往何處。
函數 tcgetpgrp 返回前臺進程組 ID,它與在 fd 上打開的終端相關聯。
如果進程有一個控制終端,則該進程可以調用 tcsetpgrp 將前臺進程組 ID 設置為 pgrpid。
大多數應用程序并不直接調用這兩個函數。他們通常由 作業控制 shell調用。
需要管理控制終端的應用程序可以調用 tcgetsid 函數識別出控制終端的會話首選進程的 會話ID (它等價于會話首進程的進程組 ID)
8、作業控制
作業控制是 BSD 在 1980年左右增加的一個新特性。它允許在一個終端是啟動多個作業(進程組),它控制哪一個作業可以訪問該終端以及哪些作業再后臺運行。
一個作業只是幾個進程的集合,通常是一個進程管道。
作業控制要求一下 3 種形式的支持:
9、 shell 執行程序
PID: (Process ID) 進程 ID號
PPID: (Parent process) ID 父進程 ID號
PGID: (Process Group ID) 進程組 ID號
SID: (Session ID) 會話ID
10、孤兒進程組
一個其父進程已終止的進程稱為 孤兒進程(orphan process ),這種進程由 init 進程“收養”。
孤兒進程組:( orphaned process group ):
? 該組中每個成員的父進程要么是該組的一個成員,要么不是該組所屬會話的成員。
? (另一種描述)一個進程組不是孤兒進程組的條件是——該組中有一個進程,其父進程在屬于同一會話的另一個組中。如果進程組不是孤兒進程組,那么在屬于同一會話的另一個組中的父進程就有機會重新啟動該組中停止的進程。
11、 FreeBSD 實現
12、小結
第 10 章- 信號
1、引言
本章先對信號機制進行綜述,并說明每種信號的一般用法。
然后分析早期實現的問題。
在分析存在的問題之后再說明解決這些問題的方法,這種安排有助于加深對改進機制的理解。
2、信號概念
定義:
信號更多的是通知事件的發生。信號產生之后第一時間也不是直接處理而是先存儲下來。
流程:
信號的產生—>信號的注冊—>信號的阻塞(不處理)—>信號的注銷—>信號的處理。
分類:
① 不可靠信號(非實時信號):1~31
② 可靠信號(實時信號):34~64
Linux下有62種信號,使用kill -l 命令查看
每個信號都有一個名字。這些名字都以 3 個字符 SIG 開頭。
e.g. SIGABRT 是夭折信號,當進程調用 about 函數時產生這種信號;
SIGALRM 是鬧鐘信號,由 alarm 函數設置的定時器超時后將產生此信號;
不存在編號為 0 的信號,因為(10.9節)kill 函數對信號編號 0 有特殊的應用。
3、函數 signal
UNIX 系統信號機制最簡單的接口是 signal 函數。
當指定函數地址時,則在信號發生時,調用該函數,我們稱這種處理為捕捉該信號,稱此函數為信號處理程序( signal handler )或信號捕捉函數( signal-catching function )。
signal 函數原型說明此函數要求兩個參數,返回一個函數指針,而該指針所指向的函數無返回值( void )。
? 程序啟動
當執行一個程序時,所有信號的狀態都是系統默認或忽略。
? 進程創建
當一個進程調用 fork 時,其子進程繼承父進程的信號處理方式。
4、不可靠的信號
不可靠在這里指的是:信號可能會丟失。
5、終端的系統調用
早期 UNIX 系統的一個特性是: 如果進程在執行一個低速系統調用而阻塞期間捕捉到一個信號,則該系統調用就被終端不再繼續執行。
該系統調回返回出錯,其 error 設置為 EINTR。
6、可重入函數
7、 SIGCLD語義
8、可靠信號術語和語義
每個進程都有一個 信號屏蔽字( signal mask ),它規定了當前要阻塞遞送到該進程的信號集。
對于每種可能的信號,該屏蔽字中都有一位與之對應,對于某種信號,若其對應位已設置,則它當前是被阻塞的。進程可以調用 sigprocmask(10.2節)來檢測和更改其當前信號屏蔽字。
9、函數 kill 和 raise
kill 函數將信號發送給進程或進程組; raise 函數則允許進程向自身發送信號。
如前所述,進程將信號發送給其它進程需要權限。超級用戶可將信號發送給任一進程。對于非超級用戶,其基本規則是發送者的實際用戶 ID 或有效用戶 ID 必須等于接受者的實際用戶 ID 或有效用戶 ID。
10、函數 alarm 和 pause
使用 alarm 函數可以設置一個定時器(鬧鐘時間),在將來的某個時刻該定時器會超時。當定時器超時時,產生 SIGALRM 信號。
每個進程只能有一個鬧鐘時間。
pause 函數使調用者進程掛起直至捕捉到一個信號。
只有執行了一個信號處理程序并從其返回時, pause才返回。在這種情況下, pause 返回 -1,errno設置為 EINTR。
11、信號集
數據集 用來告訴內核不允許發生該信號集中的信號。
函數 sigemptyset 初始化由 set 指向的信號集,清除其中所有信號。 函數 sigfillset 初始化由 set 指向的信號集,使其包括所有信號。所有應用程序在使用信號集前,要對該信號集調用 sigemptyset 或 sigfillset 一次。這是因為 C 編譯器程序將 不賦初值的 外部變量和靜態變量都初始化為 0。
12、 函數 sigprocmask
在(10.8)中提及一個進程的信號屏蔽字規定了當前阻塞而不能遞送給該進程的信號集。調用函數 sigprocmask 可以檢測或更改,或同時進行檢測和更改進程的信號屏蔽字。
13、函數 sigpending
sigpending 函數返回一信號集,對于調用進程而言,其中的各信號是阻塞不能遞送的,因而也一定是當前未決的。 該信號集通過 set 參數返回。
14、函數 sigaction
sigaction 函數的功能是檢查或修改(或檢查并修改)與指定信號相關聯的處理動作。此函數取代了 UNIX 早起版本使用的 signal 函數。
15、 函數 sigsetjmp 和 siglongjmp
16、函數 sigsuspend
更改進程的信號屏蔽字可以阻塞所選擇的信號,或解除對它們的阻塞。使用這種技術可以保護不希望由信號中斷的代碼臨界區。
17、 函數 abort
abort 函數的功能是使 程序異常終止。
該函數將 SIGABRT信號發送給調用進程(進程不應忽略此信號)。 ISO C 規定,調用 abort將向主機環境遞送一個未成功終止的通知,其方法是調用 raise(SIGABRT) 函數。
18、函數 system
POSIX.1 要求 system 忽略 SIGINT 和 SIGQUIT ,阻塞 SIGCHLD。
system 的返回值
注意 system 的返回值,它是 shell 的終止狀態,但 shell 的終止狀態并不總是執行命令字符串進程的終止狀態。
19、函數 sleep、nanosleep 和 clock_nanosleep
這本書中很多例子中都已經使用了 sleep 函數。
nanosleep 函數與 sleep函數類似,但是它提供了納秒級的精度。這個函數掛起調用進程,知道要求的時間已經超時或者某個信號中斷了該函數。
20、函數 sigqueue
除了對信號排隊以外,這些擴展允許應用程序在遞交信號時傳遞更多的信息。這些信息嵌入在 siginfo 結構中。除了系統提供的信息,應用程序還可以想信號處理程序傳遞整數或者指向包含更多信息的緩沖區指針。
sigqueue 函數只能吧信號發送給單個進程,可以使用 value 參數想信號處理程序傳遞整數和指針值,除此之外, sigqueue 函數與 kill 函數類似。
21、作業控制信號
POSIX.1 認為有以下 6 個與作業控制有關:
除了 SIGCHLD 以外,大多數應用程序并不處理這些信息,交互式 shell 則通常會處理這些信號的所有工作。
當我們通知 shell 在前臺或后臺回復運行一個作業時, shell 向該作業中的所有進程發送 SIGCONT 信號。
22、信號名和編號
可以使用 psignal 函數可移植地打印與信號編號對應的字符串。
如果在 sigaction 信號處理程序中有 siginfo 結構,可以使用 psiginfo 函數打印信號信息。
23、小結
本章 說明了早起信號實現的問題以及它們是如何顯現出來的。
然后介紹了 POSIX.1 的可靠信號概念,以及所有相關的函數。
在此基礎上提供了 about、system 和 sleep 函數的 POSIX.1實現。
最后以觀察分析作業控制信號以及信號名和信號編號之間的轉換結束。
第 11 章-線程
1、引言
本章了解如何使用多個控制線程( 簡單說就是線程 )在單進程中執行多個任務。一個進程中的所有線程都可以訪問該進程的組成部件,如文件描述符和內存。
最后本章將討論目前可用的同步機制,防止多個線程在共享資源時出現不一致的問題。
有時,也可以將應用程序設計成使用多線程的(11章),從而避免使用非阻塞 I/O(14章)。
2、線程概念
典型的 UNIX 進程可以看成只有一個控制線程:一個進程在某一時刻只能做一件事情。有了多個控制線程以后,在程序設計時就可以把進程設計成在某一時刻能夠不止做一件事,每個線程處理各自獨立的任務。
每個線程都包含有表示執行環境所必須的信息,其中包括進程中標識線程的 線程ID、一組寄存器值、棧、調度優先級和策略、信號屏蔽字、 errno變量(1.7節)以及線程私有數據(12.6節)。
一個進程的所有信息對該進程的所有線程都是共享的,包括可執行程序的代碼、程序的全局內存和堆內存、棧以及文件描述符。
3、進程標識
就像每個進程有一個進程 ID 一樣,每個線程也有一個線程 ID。進程 ID 在整個系統中是唯一的,但 線程 ID 不同,線程 ID 只有在它所屬的進程上下文中才有意義。
線程 ID 是用 pthread_t數據類型來表示的。 用結構表示 pthread_t 數據類型的后果是不能用一種可以只的方式打印該數據類型的值。在程序調試過程中打印線程 ID 有時是非常有用的。
線程可以通過調用 pthread_self函數獲得自身的線程 ID。
pthread_self函數可以與 pthread_equal 函數一起使用。例如,主線程可能把工作任務放在一個隊列中,用線程 ID 來控制每個工作線程處理哪些作業。
4、 線程創建
在傳統 UNIX 進程模式中,每個進程只有一個控制線程。
新增的線程可以通過調用 pthread_create 函數創建(注,它不是系統默認的庫,需要在編譯的時候加上 -lpthread )。
線程創建時并不能保證哪個線程會先運行:是新創建的線程,還是調用線程。
5、進程終止
如果進程中的任意線程調用了 exit、 _Exit 或者 _exit,那么整個進程就會終止。與此相類似,如果默認的動作是終止進程,那么,發送到線程的信息就會終止整個進程。
單個線程可以通過 3 種方式退出,因此可以在不終止整個進程的情況下,停止它的控制流:
?
?
? 線程調用 pthread_exit
進程中的其它線程也可以通過調用 函數 pthread_join 訪問到其它線程指針。 pthread_join 函數可以等待指定的線程終止,但并不獲取線程的終止狀態。
當一個線程通過調用 函數pthread_exit 退出或者簡單地從啟動歷程中返回時,進程中的其它線程可以通過調用 pthread_join 函數獲得該線程的退出狀態。
線程可以通過調用 pthread_cancel 函數來請求取消同一進程中的其它線程。【注】該函數僅僅是提出請求。
線程可以安排它退出時需要調用的函數,這與進程在退出時可以用 atexit函數 安排退出是類似的。這樣的函數稱為 線程清理處理程序( thread cleanup handler )。一個線程可以建立多個清理處理程序。
6、線程同步
當多個控制線程共享相同的內存時,需要確保每個線程看到一致的數據視圖。
當一個線程可以修改的變量,其它線程也可以讀取或者修改的時候,我們就需要對這些線程進行同步,確保它們在訪問變量的存儲內容時不會訪問到無效的值。
在變量修改時間多于一個存儲器訪問周期的處理器結構中,當存儲器讀與存儲器寫這兩個周期交叉時,這種不一致就會出現。
為了解決這個問題,線程不得不使用 鎖 ,同一時間只允許一個線程訪問該變量。
兩個或多個線程視圖在同一時間修改同一變量時,也需要進行同步。考慮變量增量操作的情況,增量操作通常分解為以下 3 步:
1.
2. 。
3. 。
11.6.1- 互斥量
可以使用 pthread 的互斥忌口來保護數據,確保同一時間只有一個線程訪問數據。
互斥量( mutex )從本質上說是一把鎖,在訪問共享資源前對互斥量進行 設置(加鎖),在訪問完成之后釋放(解鎖)互斥量。(通過休眠使進程阻塞)
互斥變量是用 pthread_mutex_t 數據類型表示的。在使用互斥變量以前,必須首先對它進行初始化。
11.6.2- 避免死鎖
?般情況下,如果同?個線程先后兩次調?lock,在第?次調?時,由于鎖已經被占?,該線程會掛起等待別的線程釋放鎖,然?鎖正是被??占?著的,該線程又被掛起?沒有機會釋放鎖,因此 就永遠處于掛起等待狀態了,這叫做死鎖(Deadlock)。
另一種情況:線程A獲 得了鎖1,線程B獲得了鎖2,這時線程A調?lock試圖獲得鎖2,結果是需要掛起等待線程B釋放 鎖2,?這時線程B也調?lock試圖獲得鎖1,結果是需要掛起等待線程A釋放鎖1,于是線程A和B都 永遠處于掛起狀態了。
有時候,應用程序的結構使得對互斥量進行排序是很困難的。如果涉及了太多的鎖和數據結構,可用的函數并不能把它轉換成簡單的層次,那么就需要采用另外到底方法。
在這匯總情況下,可以先釋放占有的鎖,然后過一點時間再試。這種情況可以使用 pthread_mutex_trylock接口 避免死鎖。
11.6.3- 函數 pthread_mutex_timelock
當線程試圖獲取一個已加鎖的互斥量時, pthread_mutex_timelock互斥量 原語允許綁定線程阻塞時間。 該接口和 pthread_mutex_trylock接口 是基本等價的,但是在達到超時時間值時, pthread_mutex_timelock 不會對互斥量進行加鎖,而是返回錯誤碼 ETIMEDOUT。
11.6.4- 讀寫鎖
讀寫鎖( reader-write lock )與互斥量類似,不過讀寫鎖允許更高的并行性。
讀寫鎖非常適合于對數據結構讀的次數遠大于寫的情況。
讀寫鎖也叫共享互斥鎖( shared-exclusive lock )。當讀寫鎖是讀模式鎖定時,就可以說城市共享模式鎖住的。當它是寫模式鎖住的時候,就可以說成是以互斥鎖模式鎖定的。
讀寫鎖可以有 3 種狀態:
? 讀模式下加鎖狀態;
? 寫模式下加鎖狀態;
? 不加鎖狀態。
一次只有一個線程可以占有寫模式的讀寫鎖,但是多個線程可以同時占有讀模式的讀寫鎖。
11.6.5- 帶有超時的讀寫鎖
帶有超時的讀寫鎖加鎖函數可以使應用程序在獲取讀寫鎖時避免陷入永久阻塞狀態。
這兩個函數分別是: pthread_rwlock_timedrdlock 和 pthread_rwlock_timedwelock。
11.6.6- 條件變量
條件變量是線程可用的另一種同步機制。條件變量給多個線程提供了一個會和的場所。
條件變量與互斥量一起使用時,允許線程以無競爭的方式等待待定的條件發生。
在使用條件變量之前,必須先對它進行初始化。由 pthread_cond_t 數據類型表示的條件變量可以用兩種方式進行初始化。 在釋放條件變量底層的內存空間之前,可以使用 pthread_cond_destroy 函數對條件進行反初始化( deinitialize )。
11.6.7- 自旋鎖
自旋鎖與互斥量基本類似,但它不是通過休眠使進程阻塞,而是在獲取鎖之前一直處于忙等(自旋)阻塞狀態。
自旋鎖可用于以下情況:鎖被持有的時間短,而且線程并不希望在重新調度上花費太多成本。
只有一個屬性是自旋鎖所特有的:那就是 進程共享屬性 支持線程進程共享同步。
當自旋鎖用在非搶占式內核中時是非常有用的:除了提供互斥機制以外,它們會阻塞終端,這樣中斷處理程序就不會讓系統陷入死鎖狀態,因為它需要獲取已被加鎖的自旋鎖( 把中斷想成是另一種搶占 )。
11.6.8- 屏障
屏障( barrier )是用戶協調多個線程并行工作的同步機制。屏障允許每個線程等待,知道所有的合作線程都到達某一點,然后從該點繼續執行。
屏障對象的概念很廣,它們允許任意數量的線程等待,知道所有的線程完成處理工作,而線程不需要退出。所有線程達到屏障后可以接著工作。
可以使用 pthread_barrier_init 函數 對屏障進行初始化。
一旦達到屏障計數值,而且線程處于非阻塞狀態,屏障就可以被重用。
11.7 小結
本章介紹了線程的概念,主要討論了 5 個基本的同步機制:
第 12 章- 線程控制
1、引言
本章將介紹控制線程行為方面的內容:
? 線程屬性;
? 同步原語屬性;
? 同一進程中的多個線程之間如何保持數據的私有性;
? 基于進程系統調用如何與線程進行交互。
2、線程限制
線程限制的使用時為了增強應用程序在不同的操作系統實現之間的可移植性。
3、線程屬性
ptherd接口允許我們通過設置每個對象關聯的不同屬性來細調線程和同步對象的行為。通常,管理這些屬性的函數都遵循相同的模式:
4、同步屬性
就像線程具有屬性一樣,線程同步對象也有屬性。
在 11.6.7中介紹了 自旋鎖的屬性: 進程共享屬性。接下來介紹另外四種同步機制的屬性。
在談到屬性時一般談及:
12.4.1- 互斥量屬性
在進程中,多個線程可以訪問同一個同步對象。互斥量有 3 個屬性。
進程共享互斥量屬性需設置為 PTHREAD_PROCESS_PRIVATE。如果進程共享互斥量屬性社會為 PTHREAD_PROCESS_SHARED,從多個進程彼此之間共享的內存數據塊中分配的互斥量就可以用于這些進程的同步。
互斥量健壯屬性 與在多個進程間共享的互斥量有關。 這意味著,當持有互斥量的進程終止時,需要解決不吃量狀態恢復的問題。這種情況發生時,互斥量處于鎖定狀態,恢復起來很困難。
類型互斥量屬性 控制著互斥量的鎖定特性。
12.4.2- 讀寫鎖屬性
讀寫鎖支持的唯一屬性是 進程共享屬性。
它與互斥量的 進程共享屬性 是相同的。就像互斥量的進程共享屬性一樣,有一堆函數用于讀取和設置讀寫鎖的進程共享屬性。
12.4.3- 條件變量屬性
條件變量有 2 個屬性: 進程共享屬性 和 時鐘屬性。與其它的屬性對象一樣,條件變量屬性也有一對函數用于初始化和反初始化條件變量屬性。
進程共享屬性: 它控制著條件變量是可以被單進程的多個線程使用,還是可以被多進程的線程使用。
時鐘屬性: 控制計算 pthread_cond_timedwait 函數的超時參數 (tsptr)時采用的是哪個時鐘。
12.4.4- 屏障屬性
屏障屬性 目前只有 進程共享屬性:它控制著屏障是可以被多進程的線程使用,還是只能被初始化屏障的進程內的多線程使用。
12.5、 重入
如果一個函數在相同的時間點可以被多個線程安全地調用,就稱該函數是 線程安全 的。
如果一個函數對多個線程來說是可 重入 的,就說這個函數就是線程安全的。但這并不能說明 對信號處理程序來說該函數也是可重入的。如果函數對異步信號處理程序的重入是安全的,那么就可以說函數是 異步信號安全 的。
12.6、 線程特定數據
線程特定數據( thread-specific data ),也稱為 線程私有數據( thread-private data )是存儲和查詢某個特定線程相關數據的一種機制。 因為我們希望每個線程可以訪問它自己單獨的數據副本,而不需要擔心與其它線程的同步訪問問題。
優點:
我們知道,一個進程中的所有線程都可以訪問這個進程的整個地址空間。 除了使用寄存器以外,一個線程沒有辦法阻止另一個線程訪問它的數據。線程特定數據也不例外。
雖然底層的實現部分并不能阻止這種訪問能力,但管理線程特定數據的函數可以提高線程間的數據獨立性,使得線程不太容訪問到其它線程的線程特定數據。
12.7、 取消選項
可取消狀態 和 可取消類型 這兩個屬性影響著線程在響應 pthread_cancel 函數調用時所 呈現的行為。
可取消狀態屬性 可以是 PTHREAD_CANCEL_ENABLE, 也可以是 PTHREAD_CANCEL_DISABLE。線程可以通過調用 pthread_setcancelstate 修改它的 可取消狀態。
在默認情況下,線程在取消請求發出以后還是繼續運行,直到線程到達某個取消點。 取消點是線程檢查它是都被取消的一個位置,如果取消了,則按照要求行事。
線程啟動時默認的 可取消狀態是 PTHREAD_CANCEL_ENABLE。
異步取消 與 推遲取消不同,因為使用異步取消時,線程可以在任意時間撤銷,不是非得遇到取消點才能被取消。
12.8、 線程 和 信號
每個線程都有自己的信號屏蔽字,但是信號的處理是進程中所有線程共享的。
12.9、 線程 和 fork
當線程調用 fork 時,就為子進程創建了整個進程地址空間的副本。
在多線程的進程中沒了避免不一致狀態的問題,在 fork 返回和子進程調用其中一個 exec 函數之間,子進程只能調用異步信號安全的函數。這就限制了在調用 exec 之前子進程能做什么,但不涉及子進程中鎖狀態的問題。
用 pthread_atfork 函數最多可以安裝 3 個幫助清理鎖的函數。
? prepare fork 處理程序由父進程在 fork 創建子進程前調用。這個 fork 處理程序的任務是獲取父進程定義的所有鎖。
? parent fork處理程序是在 fork創建子進程以后、返回之前在父進程上下文中調用的。
? child fork 處理程序在 fork 返回之前在子進程上下文中調用。
12.10、 線程 和 I/0
很多函數( eg. 3.11節中提及的 pread 、 pwrite )在多線程環境下也是非常有用的,因為進程中的所有線程共享相同的文件描述符。
12.11、 小結
本章中了解了:
? 如何調整線程和它們的同步原語
? 討論了線程的可重入性
? 還學習了線程如何與其它面向進程的系統調用進行交互。
第 13 章- 守護進程
1、引言
守護進程常常用作服務器進程。
守護進程( daemon )是生存期長的一種進程。 它們常常在系統引導裝入時啟動,僅在系統關閉時才終止。因為它們沒有控制終端,所以說它們是在后臺運行的。
2、守護進程的特征
大多數守護進程都以超級用戶( root )特權運行。所有的守護進程都沒有控制終端,其終端名設置為問號。大多數用戶層守護進程都是進程組的組長進程以及會話的首進程,而且是這些進程組和會話中的唯一進程。
3、編程規則
在編寫守護進程程序時需要遵循一些基本規則,以防止產生不必要的交互作用。
? 首先要做的是調用 umask 將文件模式創建屏蔽字設置為一個已知值(通常是0);
? 調用 fork,然后使父進程 exit;
? 調用 setsid 創建一個新會話;
? 將當前工作目錄更改為根目錄;
? 關閉不再需要的文件描述符;
? 某些守護進程打開 /dev/null 使其具有文件描述符 0、1 和 2 ,這樣,任何一個試圖讀標準輸入、寫標準輸出或標準錯誤的庫歷程都不會產生任何效果。
4、出錯記錄
守護進程存在的一個問題是如何處理出錯消息。 因為它本就不應該有控制終端,所以不能只是簡單地寫到標準錯誤上。
有以下 3 種產生日志消息的方法:
? 內核例程可以調用 log 函數;
? 大多數用戶進程(守護進程 )調用 syslog(3)函數來產生日志消息。
? 無論一個用戶進程是在此主機上,還是在通過 TCP/IP網絡連接到此主機的其它主機上,都可以將日志消息發向 UDP端口 514。
5、單實例守護進程
為了正常運作,某些守護進程會實現為,在任一時刻只運行該守護進程的一個副本。
如果守護進程需要訪問一個設備,而該設備驅動程序有時會阻止想要多次打開 /dev 目錄下相應設備節點的嘗試。這就限制了在一個時刻只能運行守護進程的一個副本。
文件 和 記錄鎖機制為一種方法提供了基礎,該方法保證一個守護進程只有一個副本在運行。
文件和記錄鎖提供了一種方便的互斥機制。
守護進程的每個副本都將試圖創建一個文件,并將其進程 ID 寫到該文件中。
6、守護進程的慣例
在 UNIX 系統中,守護進程遵循下列通用慣例:
? 若守護進程使用鎖文件,那么該文件通常存儲在 /var/run 目錄中;
? 若守護進程支持配置選項,那么配置文件通常存放在 /etc 目錄中;
? 守護進程可用命令行啟動,但通常它們是由系統初始化腳本之一( /etc/rc* 或 /etc/init.d/* )啟動的;
? 若一個守護進程有一個配置文件,那么當該守護進程啟動時會讀該文件,但在此之后一般就不會再查看它。
7、客戶進程-服務器進程 模型
守護進程常常用作服務器進程。
一般而言, 服務器進程 等待 客戶進程 與其通信,提出某種類型的服務要求。有的通信是單向的,有的通信是雙向的。
8、小結
在大多數 UNIX 系統中,守護進程是一直運行的。
為了初始化我們自己的進程,使之作為守護進程運行,需要一些審慎的思索并理解第 9 章 說明的進程之間的關系。
本章還討論了守護進程記錄 出錯消息 的幾種方法。
第 14 章- 高級 I/O
1、引言
本章會講到:
? 非阻塞 I/O;
? 記錄鎖;
? I/O多路轉換( select 和 poll 函數 );
? 異步 I/O;
? readv 和 writev 函數;
? 存儲映射 I/O( mmap );
2、非阻塞 I/O
10.5節中曾將系統調用分成兩類:“低速”系統調用 和 其它。 低速系統調用是可能會使進程永遠阻塞的一類系統調用。
非阻塞 I/O 使我們可以發出 open、read 和 write這樣的 I/O 操作,并使這些操作不會永遠阻塞。
有時,也可以將應用程序設計成使用多線程的(11章),從而避免使用非阻塞 I/O。如若我們能在其它線程中繼續進行,則可以允許單個線程在 I/O 調用中阻塞。
3、記錄鎖
商用 UNIX 系統提供了記錄鎖機制。
記錄鎖( record locking )的功能是:當第一個進程正在讀或修改文件的某個部分時,使用記錄鎖可以阻止其它進程修改同一文件區。(“記錄”這個詞并不準確,因為 UNIX 系統內核沒有使用文件記錄這種說法。 一個更合適的術語是 “字節范圍鎖”( byte-range locking ),因為它鎖定的只是文件中的一個區域 )。
14.3.1- 歷史
早起 UNIX 是沒有 記錄鎖的。
14.3.2- fcnt1 記錄鎖
主要分為兩種類型的鎖: 共享讀鎖( l_type 為 L_RDLCK )和獨占性寫鎖( L_WRLCK )。基本規則是:任意多個進程在一個給定的字節上可以有一把共享的讀鎖,但是在一個給定字節上只能有一個進程有一把獨占寫鎖。
14.3.3- 鎖的隱含繼承和釋放
關于記錄鎖的自動繼承和釋放有 3 條規則:
? 鎖與進程和文件兩者相關聯;
? 由 fork 產生的子進程不繼承父進程所設置的鎖;
? 在執行 exec后,新程序可以繼承原執行程序的鎖。
14.3.4- FreeBSD 實現
14.3.5- 在文件尾端加鎖
14.3.6- 建議性鎖 和 強制性鎖
考慮數據庫訪問例程庫。
如果該庫中所有函數都以一致的方法處理記錄鎖,則稱使用這些函數訪問數據庫的進程集為 合作進程( cooperating process )。如果這些函數是唯一地用來訪問數據庫的函數,那么它們使用建議性鎖是可行的。但是建議性鎖并不能阻止對數據庫文件有寫權限的任何其它進程寫這個數據庫文件。
4、 I/O 多路轉接
當從一個描述符讀,然后又寫到另一個描述符時,可以在下列形式的循環中使用阻塞 I/O: … 這種形式的阻塞 I/O 到處可見。但是如果必須從兩個描述符讀,又將如何呢?
? 法1- 將一個進程變成兩個進程(用 fork),每個進程處理一條數據通路。如果使用兩個進程,則可使每個進程都執行阻塞 read。
? 法2- 我們可以不使用 兩個進程,而是用一個進程中的兩個線程。 雖然避免了終止的復雜性,但卻要求處理兩個線程之間的同步。
? 法3- 仍舊使用一個進程執行該程序,但使用非阻塞 I/O讀取數據。
? 法4- 還有一種技術成為 異步I/O( asynchronous I/O )。
? 法5- 一種比較好的技術是使用 I/O多路轉接( I/O multiplexing )。為了使用這種技術,先構造一張我們感興趣的描述符(通常不止一個)的列表,然后調用一個函數,知道這些描述符中的一個已準備好進行 I/O時,該函數才返回。
14.4.1- 函數 select 和 pselect
select函數使我們可以執行 I/O多路轉接。
pselect 是 select函數的變體。
14.4.2- 函數 poll
poll 函數類似于 select,但是程序員接口有所不同。 雖然 poll 函數是 System V 引入進來支持 STREAMS 子系統的,但是 poll函數可用于任何類型的文件描述符。
5、異步 I/O
14.5.1- System V 異步 I/O
14.5.2- BSD 異步 I/O
14.5.3- POSIX 異步 I/O
6、 函數 readv 和 writev
readv 和 writev 函數用于在一次函數調用中讀、寫多個非連續緩沖區。有時也將這兩個函數稱為 散步讀( scatter read )和聚集寫( gather write )
7、函數 readn 和 writen
函數 readn 和 writen 的功能分別是讀、寫指定的 N 字節數據,并處理返回值可能小于要求值的情況。這兩個函數只是按需多次調用 read 和 wtire直至讀、寫了 N 字節數據。
8、存儲映射 I/O
存儲映射 I/O( memory-mapped I/O )能將一個磁盤文件映射到存儲空間中的一個緩沖區上,于是,當從緩沖區取數據時,就相當于讀文件中的相應字節。與此類似,將數據存入緩沖區時,相應字節就自動寫入文件。這樣就可以在不使用 read 和 write 的情況下執行 I/O。
9、小結
本章描述了很多高級 I/O 功能:
? 非阻塞 I/O——發一個 I/O操作,不使其阻塞;
? 記錄鎖
? I/O多路轉接—— select 和 poll 函數
? readv 和 writev 函數
? 存儲映射 I/O(mmap)
第 15 章- 進程間通信
1、引言
進程間通信( InterProcess Communication, IPC )。
本章討論經典的 IPC: 管道(pipe)、FIFO、消息隊列、信號量、共享存儲。
2、管道
管道是 UNIX 系統 IPC 的最古老的形式,一般是半雙工的(即數據只能在一個方向時流動),現在某些系統也提供全雙工管道。
每當在管道中鍵入一個命令序列, 讓 shell 執行時, shell 都會為每一條命令單獨創建一個進程,然后用管道將前一條命令進程的標準輸出與后一條命令的標準輸入相連接。
管道通常用 pipe 函數創建。
fstat 函數對管道的每一端都返回一個 FIFO(也稱命名管道) 類型的文件描述符。
3、函數 popen 和 pclose
常見的操作是創建一個連接到另一個進程的管道,然后讀其輸出或向其輸入端發送數據。
為此,標準 I/O 庫提供了兩個函數 popen 和 pclose : 創建一個管道, fork 一個子進程,關閉未使用的管道端,執行一個 shell 運行命令,然后等待命令終止。
4、協同進程
UNIX 系統過濾程序從標準輸入讀取數據,向標準輸出寫數據。幾個過濾程序通常在 shell 管道中線性連接。當一個過濾程序即產生某個過濾程序的輸入,又讀取該過濾程序的輸出時,它就變成了 協同進程( coprocess )。
協同進程通常在 shell 的后臺運行,其標準輸入和標準輸出通過管道連接到另一個程序。
popen 只提供連接到另一個進程的標準輸入或標準輸出的一個單向管道;
而協同進程則有連接到另一個進程的兩個單項管道:一個接到其標準輸入,另一個則來自其標準輸出。
5、FIFO
FIFO 有時被稱為 命名管道。
未命名的管道只能在兩個相關的進程之間使用,而且這兩個相關的進程還有有一個共同的創建了它們的祖先進程。
但是通過 FIFO,可以用于非線性連接,不相關的進程也能交換數據!
FIFO 有以下兩種用途:
FIFO 可以用于復制一系列 shell 命令中的輸出流,這就防止了將數據寫向中間磁盤文件。
6、 XSI IPC
有 3 種稱為 XSI IPC 的 IPC:消息隊列、信號量、共享存儲器。
15.6.1- 標識符和鍵
每個內核中的 IPC 結構(消息隊列、信號量 或 共享存儲段)都用一個非負整數的 標識符( identifier )加以引用。(例如,要向一個消息隊列發送消息或者從一個消息隊列取消息,只需要知道其隊列標識符)。
為此,每個 IPC 對象都與一個 鍵( key )相關聯,將這個鍵作為該對象的外部名。
15.6.2- 權限結構
XSI IPC 為每一個 IPC 結構關聯了一個 ipc_perm 結構。該結構規定了權限和所有者。
15.6.3- 結構限制
所有 3 中形式的 XSI IPC 都有內置限制。大多數限制可以通過重新配置內核來改變。
15.6.4- 優點和缺點
7、消息隊列
消息隊列是消息的鏈接表,存儲在內核中,由消息隊列標識符標識。
msgget 用于創建一個新隊列或打開一個現有隊列;
msgsnd 將新消息添加到隊列尾端。
8、 信號量
信號量 與已經介紹過的 IPC機構( 管道、FIFO、以及消息隊列 )不同。它是一個計數器,用于為多個進程提供對共享數據對象的訪問。
為了正確地實現信號量,信號量值的測試及減 1 操作應當是原子操作。為此,信號量通常是在內核中實現的。
常用的信號量形式被稱為 二元信號量( binary semaphore )。一般而言,信號量的初值可以使任意一個正值,該值表明有多少個共享資源單位可供共享應用。
9、共享存儲
共享存儲 允許兩個或多個進程共享一個給定的存儲區。
因為數據不需要在客戶進程和服務器進程之間復制,所以這是最快的一種 IPC。
使用共享村粗時要掌握的唯一竅門是,在多個進程之間同步訪問一個給定的存儲區。
10、 POSIX 信號量
11、 客戶進程-服務器進程 屬性
12、小結
本章詳細說明了進程間通信的多種形式: 管道、命名管道( FIFO )、通常稱為 XSI IPC 的 3 種形式的 IPC( 消息隊列、信號量 和 共享存儲 ),以及 POSIX 提供的替代信號量機制。
給出了一下建議:
? 要學會使用管道 和 FIFO;
? 要盡可能避免使用消息隊列以及信號量;
? 應該考慮全雙工管道和記錄鎖;
? 共享存儲仍然有它的用途, 雖然通過 mmap 函數也可能提供通項的功能。
第 16 章- 網絡 IPC: 套接字
進程間通信( InterProcess Communication, IPC )。
1、引言
本章將考察不同計算機(通過網絡相連)上的進程相互通信的機制:網絡進程間通信( network IPC ).
在本章中,將描述套接字網絡進程間通信接口,進程用改接口能夠和其它進程通信,無論它們是在同一臺計算機上還是不同的計算機上。本章僅是一個套接字 API 的概述。
2、 套接字描述符
套接字是通信端點的抽象。
套接字描述符在 UNIX 系統中被當做是一種文件描述符。
為創建一個套接字,調用 socket 函數。
對于數據報( SOCK_DGRAM )接口,兩個對等進程之間通信時不需要邏輯連接。只需要向對等進程所使用的套接字送出一個報文。因此 數據報提供了一個無連接的服務。
另一方面,字節流( SOCK_STREAM )要求在交換數據之前,在本地套接字和通信的對等進程的套接字之間建立一個邏輯連接。
流控制傳輸協議( Stream Control Transmission Protocol, SCTP )提供了因特網域上的順序數據包服務。
調用 socket 與調用 open 相類似。在兩種情況下,均可獲得用于 I/O 的文件描述符。當不再需要該文件描述符時,調用 close 來關閉對文件或套接字的訪問,并且釋放該描述符以便重新使用。
套接字通信是雙向的。 可以采用 shutdown 函數來禁止一個套接字的 I/O。
3、尋址
進程標識由兩部分組成:
? 計算機的網絡地址;
? 計算機上用 端口號 表示的服務。
16.3.1- 字節序
字節序 是一個處理器架構特性,用于指示像整數這樣的大數據類型內部的字節如何排序。
如果處理器架構支持 大端( big-endian )字節序:那么最大字節地址出現在最低有效字節( Least Significant Byte, LSB )上。
小端( little-endian )字節序:最低有效字節包含最小字節地址。
【注】不管字節如何排序,自最高有效字節(Most Significant Byte, MSB)總在左邊,最低有效字節總是在右邊。
網絡協議指定了字節序,因此異構計算機系統能夠交換協議信息而不會被字節序所混淆。 TCP/IP 協議棧使用大端字節序。對于TCP/IP,地址用網絡字節序來表示,所以應用程序有時需要在處理器的字節序與網絡字節序之間轉換他們。
16.3.2- 地址格式
一個地址標識一個特定通信域的套接字端點,地址格式與這個特定的通信域相關。
16.3.3- 地址查詢
16.3.4- 將套接字與地址關聯
對于服務器,需要給一個接受客戶端請求的服務器套接字關聯上一個眾所周知的地址。
客戶端應有一種方法來發現連接服務器所需要的地址,最簡單的方法就是服務器保留一個地址并且注冊在 /etc/services 或者某個名字服務中。
4、建立連接
如果要處理一個面向連接的 網絡服務( SOCK_STREAM 或 SOCK_SEQPACKET ),那么在開始叫喚數據以前,需要在請求服務的進程套接字(客戶端)和提供服務的進程套接字(服務器)之間建立一個連接。 使用 connect 函數 來建立連接。
5、數據傳輸
既然一個套接字端點表示為一個文件描述符,那么只要建立連接,就可以使用 read 和 write來通過套接字通信。
在套接字描述符上使用 read 和 write 是非常有意義的,因為這意味著可以將套接字描述符傳遞給那些原先為處理本地文件而設計的函數。 而且還可以安排將套接字描述符傳遞給子進程,而該子進程執行的程序并不了解套接字。
6、套接字選項
套接字機制提供了兩個套接字選項接口來控制套接字行為:
? 一個接口用來設置選項;
? 另一個接口可以查詢選項的狀態。
可以獲取 or 設以下 3 種選項:
? 通用選項,工作在所有套接字類型上;
? 在套接字層次管理的選項,但是依賴于下層協議的支持;
? 特定于某協議的選項,每個協議獨有的。
可以使用 setsockopt 函數來設置套接字選項。
7、帶外數據
帶外數據( out-of-band data )是一些通信協議所支持的可選功能,與普通數據相比,它允許更好優先級的數據傳輸。帶外數據先行傳輸,即使傳輸隊列已經有數據。
TCP 支持帶外數據,但是 UDP 不支持。 套接字口對帶外數據的支持很大程度上受 TCP 帶外數據具體實現的影響。
TCP 將帶外數據成為 緊急數據( urgent data )。 TCP 僅支持一個字節的緊急數據,但是允許緊急數據在普通數據傳遞機制數據流之外傳輸。
8、非阻塞和異步 I/O
9、小結
本章討論了:
? 套接字端點如何命名,在連接服務器時,如何發現所用的地址。
? 給出了采用無連接的(即基于數據報的)套接字和面向連接的套接字的客戶端和服務器的實例;
? 討論了 異步和 阻塞的套接字 I/O;
? 用于管理套接字選項的接口。
第 17 章-高級進程間通信
1、引言
本章將會:
? 介紹一種高級 IPC(進程間通信)——UNIX 域套接字機制,并說明它的應用方法。這種形式的 IPC 可以在同一計算機系統上運行的兩個進程之間傳送打開文件描述符。
? 服務進程可以使它們打開的文件描述符與指定的名字相關聯,同一系統上運行的客戶進程可以使用這些名字與服務器進程匯聚。
? 還會了解到操作系統如何為每一個客戶進程提供一個獨用的 IPC 通道。
2、 UNIX域套接字
UNIX 域套接字用于在同一臺計算機上運行的進程之間的通信。
雖然因特網網域套接字可以用于同一目的,但 UNIX 域套接字的效率更高。
UNIX 域套接字提供流和數據報兩種接口,UNIX 域套接字更像是套接字和管道的混合。
3、唯一連接
服務器進程可以使用標準 blind、listen 和 accept函數,為客戶進程安排一個唯一 UNIX 域連接。客戶進程使用 connect 與服務器進程聯系。在服務器進程接受了 connect 請求后,在服務器進程和客戶進程之間就存在了 唯一連接。
4、傳送文件描述符
傳送文件描述符可以使一個進程(通常是服務器進程)能夠處理打開一個文件所要做的一切操作(包括將網絡名翻譯為網絡地址、撥號調制調節器、協商文件鎖等)以及向調用進程送回一個描述符,該描述符可被用于以后的所有 I/O 函數。
5、 打開服務器進程第 1 版
6、 打開服務器進程第 2 版
7、小結
如何在兩個進程之間傳送文件描述符,以及服務器進程如何接受來自客戶進程的唯一連接。
了解了如何用它們來實現一個全雙工的管道以及如何利用它們來適應 14.4 節的 I/O多路轉接函數以間接地用于 XSI 消息隊列中。
第 18 章- 終端 I/O
總結
以上是生活随笔為你收集整理的《UNIX环境高级编程——APUE》的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: iSAM1论文推导学习--第二节QR部分
- 下一篇: 计算机考研数学和英语考什么,考研英语几与