APUE 札記

以apue第三版爲藍本
我是目錄
第1章 UNIX基礎知識 第2章 UNIX標準及實現 第3章 文件IO
第4章 文件和目錄 第5章 標準I/O庫 第6章 系統數據文件和信息
第7章 進程環境 第8章 進程控制 第9章 進程關係
第10章 信號 第11章 線程 第12章 線程控制
第13章 守護進程 第14章 高級I/O 第15章 進程間通訊
第16章 網絡IPC:套接字 第17章 高級進程間通訊 第18章 終端I/O
第19章 僞終端 第20章 數據庫函數庫 第21章 與網絡打印機通訊

第1章 UNIX基礎知識

1.二、UNIX體系結構

  • 內核(kernel)的接口被稱爲系統調用(system call)。公用函數庫(library routines)構建在系統調用接口之上,應用程序(applications)既可使用公用函數庫,也可以使用系統調用。shell是一個特殊的應用程序,爲運行其餘應用程序提供了一個接口。

UNIX 操做系統的體系結構

1.三、登陸

  • 口令文件(/etc/passwd)中的登陸項有7個以冒號分隔的字段組成,依次是:登陸名:加密口令:用戶ID:組ID:註釋字段:起始目錄:shell程序。加密口令存放在 /etc/shadow 中。

1.四、文件和目錄

  • (UNIX系統中)只有斜線(/)和空字符這兩個字符不能出如今文件名中。但推薦使用如下字符集:字母、數字、句點、短橫線和下劃線。
相關:Windows下文件名禁用的9個字符:/:*?"<>|(加上空字符應該有10個)。

1.六、程序和進程

  • 有3個用於進程控制的主要函數:fork、exec和waitpid。(exec函數有7種變體)

1.七、出錯處理

#include <string.h>
//返回值:指向消息字符串的指針
char *strerror(int errnum);

#include <stdio.h>
void perror(const char *msg);

1.八、用戶標識

  • 組文件(/etc/group)將組名映射爲數值的組ID,其中4個字段依次是:組名稱:組密碼:組ID:該組用戶列表(以逗號分隔)。

1.十、時間值

  (1)、日曆時間。該值是自協調世界時(UTC)1970年1月1日00:00:00這個特定時間以來所通過的秒數累計值。這些時間值可用於記錄文件最近一次的修改時間等。
  系統基本數據類型time_t用於保存這種時間值。
  UTC,Coordinated Universal Time,自協調世界時。早期的手冊稱UTC爲格林尼治標準時間。html

  (2)、進程時間。也被成爲CPU時間,用以度量進程使用的中央處理器資源。進程時間以時鐘滴答計算。每秒鐘曾經取爲50、60或100個時鐘滴答。(Linux3.2.0是100)
  系統基本數據類型clock_t保存這種時間值。
  當度量一個進程的執行時間時,UNIX系統爲一個進程維護了3個進程時間值:時鐘時間;用戶CPU時間;系統CPU時間。
  時鐘時間又稱爲牆上時鐘時間(wall clock time),它是進程運行的時間總量,其值與系統中同時運行的進程數有關。
  用戶CPU時間是執行用戶指令所用的時間量。
  系統CPU時間是爲該進程執行內核程序所經歷的時間。
  用戶CPU時間和系統CPU時間之和常被稱爲CPU時間。
  要取得任一進程的時鐘時間、用戶時間和系統時間是很容易的——只要執行命令time(1),其參數是要度量其執行時間的命令,例如:linux

$ cd /usr/include/

# real > user + sys
$ time -p grep -R _POSIX_SOURCE  > /dev/null
real 0.07
user 0.03
sys 0.03

# real = user + sys
$ time -p grep _POSIX_SOURCE */*.h  > /dev/null
real 0.02
user 0.01
sys 0.01

# real < user + sys
$ time -p rsync -a --dry-run /usr/ ~/fake
real 1.40
user 0.91
sys 0.70

1.十二、小結

  標準,特別是ISO C標準和POSIX.1標準,將影響本書的餘下部分。ios

第2章 UNIX標準及實現

2.二、UNIX標準化

2.2.一、ISO C

ISO C 標準定義的頭文件

2.2.二、IEEE POSIX

  POSIX是一個最初由IEEE(Institute of Electrical and Electronics Engineers,電氣和電子工程師學會)制訂的標準族。
  POSIX指的是可移植操做系統接口(Portable Operating System Interface)。
  POSIX.1是POSIX標準族中的一個標準。(1003.1)
  圖2-一、圖2-二、圖2-三、圖2-4,這4張圖中的表總結了本文所討論的4種UNIX系統實現所包含的頭文件。
POSIX 標準定義的必需的頭文件
POSIX 標準定義的 XSI 可選頭文件
POSIX 標準定義的可選頭文件算法

2.2.三、Single UNIX Specification

  Single UNIX Specification(SUS, 單一UNIX規範)是POSIX.1標準的一個超集,它定義了一些附加接口擴展了POSIX.1規範提供的功能。POSIX.1至關於Single UNIX Specification中的基本規範部分。
  POSIX.1中的X/Open系統接口(X/Open System Interface, XSI)選項描述了可選的接口,也定義了遵循XSI(XSI conforming)的實現必須支持POSIX.1的哪些可選部分。這些必須支持的部分包括:文件同步、線程棧地址和長度屬性、線程進程共享同步以及_XOPEN_UNIX符號常量。只有遵循XSI的實現才能稱爲UNIX系統。
圖片描述
  若是 ISO C 標準和 POSIX.1 出現衝突,POSIX.1 服從 ISO C 標準。shell

2.2.四、FIPS

  FIPS表明的是聯邦信息處理標準(Federal Information Processing Standard),這一標準是由美國政府發佈的,並由美國政府用於計算機系統的採購。數據庫

2.五、限制

  文件名的最大長度依賴於該文件處於何種文件系統。
  UNIX系統編程的3種限制:
(1)、編譯時限制(頭文件)。
(2)、與文件或目錄無關的運行時限制(sysconf函數)。
(3)、與文件或目錄有關的運行時限制(pathconf和fpathconf函數)。編程

2.5.四、函數 sysconf、pathconf 和 fpathconf

#include <unistd.h>
//3個函數返回值:若成功,返回相應值;若出錯,返回-1
long sysconf(int name);
long pathconf(const char *pathname, int name);
long fpathconf(int fd, int name);

  Linux(3.2) C下<limits.h>頭文件中NAME_MAX、PATH_MAX的值分別爲25五、4096。NAME_MAX爲文件名的最大字節數,不包括終止null字節;PATH_MAX爲相對路徑名的最大字節數,包括終止null字節。數組

第3章 文件I/O(unbuffered I/O,系統調用)

3.一、引言

UNIX系統中大多數文件I/O只需用到5個函數:open、read、write、lseek以及close。安全

3.十一、原子操做(Atomic Operations)

  一、函數pread和pwritebash

#include <unistd.h>
ssize_t pread(int fd, void *buf, size_t nbytes, off_t offset);
                             //返回值:讀到的字節數,若已到文件尾,返回0;若出錯,返回-1
ssize_t pwrite(int fd, const void *buf, size_t nbytes, off_t offset);
                             //返回值:若成功,返回已寫字節數;若出錯,返回-1

  調用pread至關於調用lseek後調用read,可是pread又與這種順序調用有下列重要區別。

(1)、調用pread時,沒法中斷其定位和讀操做。
(2)、不更新當前文件偏移量。
  調用pwrite至關於調用lseek後調用write,但也與它們有相似的區別。
  二、通常而言,原子操做指的是由多步組成的一個操做。若是該操做原子地執行,則要麼執行完全部步驟,要麼一步也不執行,不可能只執行全部步驟的一個子集。

3.十二、函數dup和dup2

#include <unistd.h>
//下面兩個函數均可以用來複制一個現有的文件描述符
//兩函數的返回值:若成功,返回新的文件描述符;若出錯,返回-1
int dup(int oldfd);
int dup2(int oldfd, int newfd);

3.1四、函數fcntl

#include <fcntl.h>
//返回值:若成功,則依賴於cmd;若出錯,返回-1
int fcntl(int fd, int cmd, .../* int arg */)
  fcntl函數能夠改變已經打開文件的屬性,有如下5種功能。

(1)、複製一個已有的描述符(cmd=F_DUPFD或F_DUPFD_CLOEXEC)
(2)、獲取/設置文件描述符標誌(cmd=F_GETFD或F_SETFD)
(3)、獲取/設置文件狀態標誌(cmd=F_GETFL或F_SETFL)
(4)、獲取/設置異步I/O全部權(cmd=F_GETOWN或F_SETOWN)
(5)、獲取/設置記錄鎖(cmd=F_GETLK、F_SETLK或F_SETLKW)

3.1五、函數 ioctl

  ioctl函數一直是I/O操做的雜物箱(catchall)。不能用本章中其餘函數表示的 I/O 操做一般都能用 ioctl 表示。

// System V
#include <unistd.h>    
// BSD and Linux
#include <sys/ioctl.h>  

//返回值:若出錯,返回-1;若成功,返回其餘值
int ioctl(int fd, int request, ...)

第4章 文件和目錄

  Linux各目錄及每一個目錄的詳細介紹

4.二、函數stat、fstat、fstatat和lstat

  本章主要討論4個stat函數以及它們的返回信息。

#include <sys/stat.h>
//全部4個函數的返回值:若成功,返回0;若出錯,返回-1
int stat(const char *restrict pathname, struct stat * restrict buf);
int fstat(int fd, struct stat *buf);
int lstat(const char *restrict pathname, struct stat *restrict buf);
int fstatat(int dirfd, const char *restrict pathname, struct stat *restrict buf, int flags);

4.三、文件類型

(1)、普通文件(regular file, -)
(2)、目錄文件(directory file,d)
(3)、塊特殊文件(block special file,b)
(4)、字符特殊文件(character special file,c)
(5)、管道或FIFO(,p)
(6)、套接字(socket,s)
(7)、符號連接(symbolic link,l)

4.四、設置用戶ID和設置組ID

表格代碼在線生成: Tables Generator
  • 與一個進程相關聯的ID有6個或更多:
實際用戶ID(real user ID) 咱們其實是誰
實際組ID(real group ID)
有效用戶ID(effective user ID) 用於文件訪問權限檢查
有效組ID(effective group ID)
附屬組ID(supplementary group IDs)
保存的設置用戶ID(saved set-user-ID) 由exec函數保存
保存的設置組ID(saved set-group-ID)

4.五、文件訪問權限

  每一個文件有9個訪問權限位,可將他們分爲3類:

st_mode 屏蔽 含義
S_IRUSR
S_IWUSR
S_IXUSR
用戶(user,owner)讀
用戶寫
用戶執行
S_IRGRP
S_IWGRP
S_IXGRP
組讀
組寫
組執行
S_IROTH
S_IWOTH
S_IXOTH
其餘讀
其餘寫
其餘執行

4.七、函數access和faccessat

  access 和 faccessat 函數按實際用戶ID和實際組ID進行訪問權限測試。

#include <unistd.h>
//兩個函數返回值:若成功,返回0;若出錯,返回-1
int access(const char *pathname, int mode);
int faccessat(int dirfd, const char *pathname, int mode, int flags);

4.八、函數 umask

  umask 函數爲進程設置文件模式建立屏蔽字,並返回以前的值。(這是少數幾個沒有出錯返回函數中的一個。)

#include <sys/stat.h>
//返回值:以前的文件模式建立屏蔽字
mode_t umask(mode_t cmask);

4.九、函數chmod、fchmod 和 fchmodat

  chmod、fchmod和fchmodat 這3個函數使咱們能夠更改現有文件的訪問權限。

#include <sys/stat.h>
//3個函數的返回值:若成功,返回0;若出錯,返回-1
int chmode(const char *pathname, mode_t mode);
int fchmode(int fd, mode_t mode);
int fchmodeat(int dirfd, const char *pathname, mode_t mode, int flags);

4.十、粘着位(sticky bit,saved-text bit,linux中的粘滯位)

4.十一、函數 chown、fchown、fchownat和lchown

  下面幾個 chown 函數可用於更改文件的用戶ID和組ID。若是兩個參數 owner 或 group 中的任意一個是-1,則對應的ID不變。

#include <unistd.h>
//4個函數的返回值:若成功,返回0;若出錯,返回-1
int chown(const char *pathname, uid_t owner, gid_t group);
int fchown(int fd, uid_t owner, gid_t group);
int fchownat(int dirfd, const char *pathname, uid_t owner, gid_t group, int flags);
int lchown(const char *pathname, uid_t owner, gid_t group);

4.1三、文件截斷

#include <unistd.h>
//兩個函數的返回值:若成功,返回0;若出錯,返回-1
int truncate(const char *pathname, off_t length);
itn ftruncate(int fd, off_t length);

  這兩個函數將一個現有文件長度截斷爲 length。若是改文件之前的長度大於 length,則超過 length 之外的數據就再也不能訪問。若是之前的長度小於 length,文件長度將增長,在一塊兒的文件尾端和新的文件尾端之間的數據將讀做0(也就是可能在文件中建立了一個空洞)。

4.1四、文件系統

任何一個葉目錄的(硬)連接計數老是2;任何一個非葉目錄的(硬)連接計數老是大於等於3。

4.1五、函數 link、linkat、unlink、unlinkat 和 remove

  建立指向現有文件的連接:

#include <unistd.h>
//只建立newpath中的最後一個份量,路徑中的其餘部分應當已經存在
//兩個函數的返回值:若成功,返回0;若出錯,返回-1
int link(const char *oldpath, const char *newpath);
int linkat(int olddirfd, const char * oldpath, int newdirfd, const char *newpath, int flags);

  刪除現有的目錄項:

#include <unistd.h>
//兩個函數的返回值:若成功,返回0;若出錯,返回-1
int unlink(const char *pathname);
int unlinkat(int dirfd, const char *pathname, int flags);

  若是pathname是符號連接,那麼 unlink 刪除該符號連接,而不是刪除有該連接所引用的文件。給出符號連接名的狀況下,沒有一個函數能刪除由改連接所引用的文件。
  用 remove 函數解除對一個文件或目錄的連接:

#include <stdio.h>
//對於文件,remove 的功能與 unlink 相同
//對於目錄,remove 的功能與 rmdir 相同
//返回值:若成功,返回0;若出錯,返回-1
int remove(const char *pathname);

4.1七、符號連接

  • 符號連接是對一個文件的間接指針,硬連接直接指向文件的i結點。

4.1九、文件的時間

  • 目錄是包含目錄項(文件名和相關的i結點編號)的文件。

第5章 標準I/O庫(ISO C)

5.二、流和 FILE 對象

  • 不帶緩衝的IO/函數圍繞文件描述符,標準I/O庫圍繞流。

5.四、緩衝

  Linux(3.2)聽從標準I/O緩衝的慣例:標準錯誤不帶緩衝,打開至終端設備的流是行緩衝,其餘流是全緩衝。

#include <stdio.h>
void setbuf(FILE *restrict fp, char *restrict buf);
//返回值:若成功,返回0;若出錯,返回非0
int setvbuf(FILE *restrict fp, char *restrict buf, int mode, size_t size);
//返回值:若成功,返回0;若出錯,返回EOF
int fflush(FILE *fp);
函數 mode buf 緩衝區及長度 緩衝類型
setbuf 非空 長度爲BUFSIZ的用戶緩衝區buf 全緩衝或行緩衝
NULL (無緩衝區) 不帶緩衝
setvbuf _IOFBF 非空 長度爲size的用戶緩衝區buf 全緩衝
NULL 合適長度的系統緩衝區buf
_IOLBF 非空 長度爲size的用戶緩衝區buf 行緩衝
NULL 合適長度的系統緩衝區buf
_IONBF 忽略 (無緩衝區) 不帶緩衝

5.五、打開流

#include <stdio.h>
//fopen打開路徑名爲pathname的一個指定文件
FILE *fopen(const char *restrict pathname, const char *restrict type);
//freopen通常用於將一個指定的文件打開爲一個預約義的流
FILE *freopen(const char *restrict pathname, const char *restrict type, FILE *restrict fp);
//fdopen取一個已有的文件描述符,並使一個標準的I/O流與該描述符相結合
FILE *fdopen(int fd, const char *type);

  若是有多個進程用標準I/O追加寫方式打開同一文件,那麼來自每一個進程的數據都將正確地寫到文件中。(由於將寫指針移到文件尾端和寫操做是一個atomic operation)

5.七、每次一行I/O

  • 建議不要使用gets和puts,推薦使用fgets和fputs。fgets和fputs老是須要本身處理行尾的換行符,這樣保持了一致性。

5.十二、實現細節

#include <stdio.h>
//返回與該流相關聯的文件描述符(感受fileno與fdopen相逆)
int fileno(FILE *fp);

5.1三、臨時文件

  • 對一個文件解除連接時並不會刪除其內容,直到關閉該文件時才刪除其內容。能夠利用這種特性建立臨時文件。

第6章 系統數據文件和信息

6.三、陰影口令

  • /etc/shadow文件的9個字段
username:password:lastchg:min:max:warn:inactive:expire:flag。
登陸名:加密口令:最後一次修改時間:最小時間間隔:最大時間間隔:警告時間:不活動時間:失效時間:標誌

6.3.一、Linux下/etc/shadow文件

第7章 進程環境

7.三、進程終止

  • 內核使程序執行的惟一方法是調用一個exec函數。進程自願終止的惟一方法是顯式或隱式地(經過exit)調用_exit或_Exit。進程也可非自願地由一個信號使其終止。

7.六、C 程序的存儲空間佈局

典型的存儲空間安排

7.十、函數setjmp和longjmp(C 語言中 setjmp 和 longjmp)

#include <setjmp.h>

//返回值:若直接調用,返回0;若從longjmp調用,則爲非0
int setjmp(jmp_buf env);

//參數 val 表示從 longjmp 函數傳遞給 setjmp 函數的返回值
//若是 val 值爲0, setjmp 將會返回1;不然返回 val。
void longjmp(jmp_buf env, int val);

  一、在C中,goto語句是不能跨越函數的,而執行這種類型跳轉功能的是函數setjmp和longjmp。這兩個函數對於處理髮生在很深層次嵌套函數調用中的出錯狀況是很是有用的。
  二、在但願返回到的位置調用 setjmp,setjmp 參數env的類型是一個特殊類型 jmp_buf。這一數據類型是某種形式的數組,其中存放在調用 longjmp 時能用來恢復棧狀態的全部信息。由於需在另外一個函數中引用env變量,因此一般將 env 變量定義爲全局變量。
  三、某些printf的格式字符串可能不適宜安排在程序文本的一行中。咱們沒有將其分紅多個printf調用,而是使用了ISO C的字符串鏈接功能,因而兩個字符串序列

"string1"  "string2"

等價於

"string1string2"

也即如下3種printf方式等價:

printf("string1""string2\n"); 
printf("string1" "string2\n"); 
printf("string1"
        "string2\n");

第8章 進程控制

8.三、函數fork

#include <unistd.h>
//返回值:子進程返回0,父進程返回子進程ID;若出錯,返回-1
pid_t fork(void);

  一、通常來講,在fork以後是父進程先執行仍是子進程先執行是不肯定的,這取決於內核所使用的調度算法。若是要求父進程和子進程以前相互同步,則要求某種形式的進程間通訊。
  二、strlen與sizeof的區別<1>:strlen計算不包含終止null字節的字符串長度,而sizeof則計算包括終止null字節的緩衝區長度。
  strlen與sizeof的區別<2>:使用strlen需進行一次函數調用,而對於sizeof而言,由於緩衝區已用已知字符串初始化,其長度是固定的,因此sizeof是在編譯時計算緩衝區長度。
  三、在重定向父進程的標準輸出時,子進程的標準輸出也被重定向。
  四、使fork失敗的兩個主要緣由是:(a)、系統中已經有了太多的進程,(b)、該實際用戶ID的進程總數超過了系統限制。
  五、本節有提到Linux的clone和FreeBSD的rfork。

8.五、函數exit

  一、如7.3節所述,進程有5種正常終止及3種異常終止方式。
  二、無論進程如何終止,最後都會執行內核中的同一段代碼。這段代碼爲相應進程關閉全部打開描述符,釋放它所使用的存儲器等。
  三、對於父進程已經終止的全部進程,它們的父進程都改變爲init進程。
  四、在UNIX術語中,一個已經終止、可是其父進程還沒有對其進行善後處理(獲取終止子進程的有關信息、釋放它仍佔用的資源)的進程被稱爲殭屍進程。
  五、由init收養的進程不會變成殭屍進程。

8.四、函數vfork

  vfork和fork的一個區別是:vfork保證子進程先運行。在可移植的應用程序中不該該使用這個函數(vfork)。

8.六、函數 wait 和 waitpid

  • 若是一個進程fork一個子進程,但不要它等待子進程終止,也不但願子進程處於殭屍狀態直到父進程終止,實現這一要求的訣竅是調用fork兩次。

8.十、函數exec

#include <unistd.h>
/* 如下7個函數的返回值:若出錯,返回-1;若成功,不返回 */
int execl(const char *pathname, const char *arg0, ... /* (char *)0 */);
int execv(const char *pathname, char *const argv[]);
int execle(const char *pathname, const char *arg0, ... /* (char *)0, char *const envp */);
int execve(const char *pathname, char *const argv[], char *const envp[]);
int execlp(const char *filename, const char *arg0, ... /* (char *)0 */);
int execvp(const char *filename, char *const argv[]);
int fexecve(int fd, char *filename, char *const envp[]);

關於arg0:(wiki)

The first argument arg0 should be the name of the executable file. 
  Usually it is the same value as the path argument. 
  Some programs may incorrectly rely on this argument providing the location of the executable, 
but there is no guarantee of this nor is it standardized across platforms.

  一、由於調用exec並不建立新進程,因此先後的進程ID並未改變。exec只是用磁盤上的一個程序替換了當前進程的正文段、數據段、堆段和棧段。
  二、進程控制原語:
  用fork能夠建立新進程,用exec能夠初始執行新的程序。exit函數和wait函數處理終止和等待終止。
  三、7個exec函數之間的區別:
  字母l(list)表示該函數取一個參數表,字母v(vector)表示該函數取一個argv[]矢量。l與v互斥。
  字母p表示該函數取filename做爲參數,而且用PATH環境變量尋找可執行文件。
  字母e表示該函數取envp[]數組,而不使用當前環境。
7 個 exec 函數之間的區別

8.十一、更改用戶ID和更改組ID

更改 3 個用戶 ID 的不一樣方法
設置不一樣用戶 ID 的各函數

8.十二、解釋器文件

  全部現今的UNIX系統都支持解釋器文件(interpreter file)。這種文件是文本文件,其起始行的形式是:

#! pathname[optional-argument]

在感嘆號和pathname之間的空格是可選的。最多見的解釋器文件如下列行開始:

#! /bin/sh
# or
#! /bin/bash

  pathname一般是絕對路徑名,對它不進行什麼特殊的處理(不使用PATH進行路徑搜索)。對這種文件的識別是由內核做爲exec系統調用處理的一部分來完成的。內核使調用exec函數的進程實際執行的並非該解釋器文件,而是在改解釋器文件第一行中pathname所指定的文件。必定要將解釋器文件(文本文件,它以#!開頭)和解釋器(由該解釋器文件第一行中的pathname指定)區分開來。
  不少系統對解釋器文件的第一行有長度限制。這包括#!、pathname、可選參數、終止換行符以及空格數。(Linux3.2.0中,該限制爲128字節)

8.1三、函數system

  一、將時間和日期放到某一個文件中的方法
  方法一:調用time獲得當前日曆時間,接着調用localtime將日曆時間變換爲年、月、日、時、分、秒、週日的分解形式,而後調用strftime對上面的結果進行格式化處理,最後將結果寫到文件中。
  方法二:

system("date > file")

  二、使用system而不是直接使用fork和exec的優勢是:sytem進行了所需的各類出錯處理以及各類信號處理。
  三、若是一個進程正以特殊的權限(設置用戶ID或設置組ID)運行,它又想生成另外一個進程執行另外一個程序,則它應當直接使用fork和exec,並且在fork以後、exec以前要更改回普通權限。設置用戶ID或設置組ID程序決不該調用system函數。

8.1八、小結

  • 進程控制必須熟練掌握的只有幾個函數——fork、exec系列、_exit、wait和waitpid。

第9章 進程關係

9.五、會話(session)

  一、會話是一個或多個進程組的集合。
  二、會話首進程老是一個進程組的組長進程。

#include <unistd.h>
pid_t setsid(void);
        //建立新會話。返回值:若成功,返回進程組ID;若出錯,返回-1
pid_t getsid(pid_t pid);
        //返回值:若成功,返回會話首進程的進程組ID;若出錯,返回-1

9.八、做業控制

  有3個特殊字符可以使終端驅動程序產生信號,並將它們發送至前臺進程組,它們是:

中斷字符(通常採用Delete或Ctrl+C)產生SIGINT
退出字符(通常採用Ctrl+\)產生SIGQUIT
掛起字符(通常採用Ctrl+Z)產生SIGTSTP

9.九、shell執行程序

  一、前臺進程組ID是終端的一個屬性,而不是進程的屬性。
  二、sh(Bourne shell,不支持做業控制):管道中的最後一個進程是shell的子進程,該管道中的第一個進程則是最後一個進程的子進程。例如:

ps -o pid,ppid,pgid,sid,comm | cat1

  cat1是shell(sh)的子進程,ps是cat1的子進程。Bourne shell首先建立將執行管道中最後一條命令的進程,而此進程是第一個進程的父進程。
  三、bash(Bourne-again shell,支持做業控制):shell是管道中進程的父進程。

ps -o pid,ppid,pgid,sid,comm | cat

  ps和cat都是shell(bash)的子進程。
  四、因此,使用的shell不一樣,建立各個進程的順序也可能不一樣。

9.十、孤兒進程組

  一、一個其父進程已終止的進程稱爲孤兒進程(orphan process), 這種進程由 init 進程「收養」。整個進程組也可成爲「孤兒」。
  二、POSIX.1將孤兒進程組(orphaned process group)定義爲:該組中每一個成員的父進程要麼是該組的一個成員,要麼不是該組所屬會話的成員。

第10章 信號

10.二、信號概念

  一、在頭文件<signal.h>中,信號名都被定義爲正整數常量(信號編號)。不存在編號爲0的信號。
  二、信號的處理:(1)、忽略此信號;(2)、捕捉信號;(3)、執行系統默認動做。
UNIX 系統信號

10.三、函數signal

#include <signal.h>
//註冊信號的回調函數
void (*signal(int signo, void (*func)(int))) (int);

變形:

typedef void Sigfunc(int);
Sigfunc *signal(int, Sigfunc *);

  一、signal函數有ISO C定義。由於ISO C不涉及多進程、進程組以及終端I/O等,因此它對信號的定義很是含糊,以至於對UNIX系統而言幾乎毫無用處。
  由於signal的語義與實現有關,因此最好使用sigaction函數代替signal函數。
  二、在UNIX系統中殺死(kill)這個術語是不恰當的。kill命令和kill函數只是將一個信號發送給一個進程或進程組。該信號是否終止則取決於該信號的類型,以及進程是否安排了捕捉該信號。

10.六、可重入函數

  可重入函數主要用於多任務環境中,一個可重入的函數簡單來講就是能夠被中斷的函數。
  一個通用的規則:當在信號處理程序中調用圖10-4中的函數時,應當在調用前保存errno,在調用後恢復errno。
信號處理程序能夠調用的可重入函數

10.七、SIGCLD語義

  Linux 3.2.0中SIGCLD等同於SIGCHLD。

10.九、函數kill和raise

  kill函數將信號發送給進程或進程組。raise函數則容許進程向自身發送信號。

#include <signal.h>
int kill(pid_t pid, int signo);
int raise(int signo);
raise(signo)

等價於

kill(getpid(), signo)

10.十、函數alarm和pause

  一、使用alarm函數能夠設置一個定時器(鬧鐘時間),在未來的某個時刻該定時器會超時。當定時器超時時,產生SIGALRM信號。若是忽略或不捕捉此信號,則其默認動做是終止調用該alarm函數的進程。
  二、pause函數使調用進程掛起直至捕捉到一個信號。只有執行了一個信號處理程序並從其返回時,pause才返回。在這種狀況下,puase返回-1,errno設置爲EINTR。

10.十一、信號集

#include <signal.h>

//函數sigemptyset初始化有set指向的信號集,清除其中全部信號
int sigemptyset(sigset_t *set);

//函數sigfillset初始化有set指向的信號集,使其包括全部信號
int sigfillset(sigset_t *set);

//函數sigaddset將一個信號添加到已有的信號集中
int sigaddset(sigset_t *set, int signo);

//函數sigdelset從信號集中刪除一個信號
int sigdelset(sigset_t *set, int signo);

                //以上4個函數的返回值:若成功,返回0;若出錯,返回-1

  全部應用程序在使用信號集前,要對信號集調用sigemptyset或sigfillset一次。這是由於C編譯程序將不賦初值的外部變量和靜態變量都初始化爲0,而這是否與給定系統上信號集的實現相對應卻並不清楚。

int sigismember(const sigset_t *set, int signo);
                //返回值:若真,返回1;若假,返回0

10.十二、函數sigprocmask

  調用sigprocmask能夠檢測或更改進程的信號屏蔽字。how的取值有三種:SIG_BLOCK、SIG_UNBLOCK、SIG_SETMASK。

#include <signal.h>
int sigprocmask(int how, const sigset_t*restrict set, sigset_t *restrict oset);
                //返回值:若成功,返回0;若出錯,返回-1
  sigprocmask是僅爲單線程進程定義的。處理多線程進程中信號的屏蔽使用另外一個函數(pthread_sigmask)。

10.1三、函數sigpending
  sigpending函數返回一信號集(並不更改任何值),對於調用進程而言,其中的各信號是阻塞不能遞送的,於是也必定是當前未決的。該信號經過set參數返回。

#include <signal.h>
int sigpending(sigset_t *set);
                //返回值:若成功,返回0;若出錯,返回-1

10.1四、函數sigaction

#include <signal.h>
//返回值:若成功,返回0;若出錯,返回-1
int sigaction(int signo, const struct sigaction *restrict act, 
                struct sigaction *restrict oact);

  其中,參數signo是要檢測或修改其具體動做的信號編號。若act指針非空,則要修改其動做。若是oact指針非空,則系統由oact指針返回該信號的上一個動做。
  sigaction函數的功能是檢查或修改(或檢查並修改)與指定信號相關聯的處理動做。此函數取代了UNIX早期版本使用的signal函數。
  本書中全部調用signal的實例均爲下面實現的函數。

#include "apue.h"

Sigfunc *signal(int signo, Sigfunc *func)
{
    struct sigaction act, oact;
    act.sa_handler = func;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    if (signo == SIGALRM) {
#ifdef SA_INTERRUPT
        act.sa_flags |= SA_INTERRUPT;
#endif
    }
    else {
        act.sa_flags += SA_RESTART;
    }
    if (sigaction(signo, &act, &oact) < 0) {
        return(SIG_ERR);
    }
    return(oact.sa_handler);
}

10.1五、函數sigsetjmp和siglongjmp

#include <setjmp.h>
int sigsetjmp(sigjmp_buf env, int savemask);
                //返回值:若直接調用,返回0;若從siglongjmp調用返回,則返回非0
void siglongjmp(sigjmp_buf env, int val);

  這兩個函數和setjmp、longjmp之間惟一區別是sigsetjmp增長了一個參數。若是savemask非0,則sigsetjmp在env中保存進程的當前信號屏蔽字。調用siglongjmp時,若是帶非0 savemask的sigsetjmp調用已經保存了env,則siglongjmp從其中恢復保存的信號屏蔽字。

10.1六、函數sigsuspend

  sigsuspend函數在一個原子操做中先恢復信號屏蔽字,而後使進程休眠。

#include <signal.h>
int sigsuspend(const sigset_t *sigmask);

  進程的信號屏蔽字設置爲由sigmask指向的值。在捕捉到一個信號或發生了一個會終止該進程的信號以前,該進程被掛起。若是捕捉到一個信號並且從該信號處理程序返回,則sigsuspend返回,而且該進程的信號屏蔽字設置爲調用sigsuspend以前的值。
  注意,此函數沒有成功返回值。若是它返回到調用者,則老是返回-1,並將errno設置爲EINTR(表示一個被中斷的系統調用)。

10.1九、函數sleep、nanosleep和clock_nanosleep

#include <unistd.h>
unsigned int sleep(unsigned int seconds);
                //返回值:0或未休眠的秒數

  此函數使調用進程被掛起直到知足下面兩個條件之一。
  (1)、已通過了seconds所指定的牆上時鐘時間。
  (2)、調用進程捕捉到一個信號並從信號處理程序返回。
  如同alarm信號同樣,因爲其餘系統活動,實際返回時間比所要求的會遲一些。

#include <time.h>

int nanosleep(const struct timespec *reqtp, struct timespec *remtp);
                //返回值:若休眠到要求時間,返回0;若出錯,返回-1

int clock_nanosleep(clockid_t clock_id, int flags, 
                        const struct timespec *reqtp, struct timespec *remtp);
                //返回值:若休眠到要求的時間,返回0;若出錯,返回錯誤碼

  除了出錯返回,調用

clock_nanosleep(CLOCK_REALTIME, 0, reptp, reqtp);

和調用

nanosleep(reqtp, remtp);

的效果是相同的。

10.20、函數sigqueue

#include <signal.h>
int sigqueue(pid_t pid, int signo, const union sigval value);
                //返回值:若成功,返回0;若出錯,返回-1

  sigqueue函數只能把信號發給單個進程,可使用value參數向信號處理程序傳遞整數和指針值,除此以外,sigqueue函數與kill函數相似。信號不能被無限排隊,到達相應的限制之後,sigqueue就會失敗,將errno設爲EAGAIN。

10.2一、做業控制信號

  在圖10-1所示的信號中,POSIX.1認爲如下6個與做業控制有關。

SIGCHLD  子進程已中止或終止。
SIGCONT  若是進程已中止,則使其繼續運行。
SIGSTOP  中止信號(不能被捕捉或忽略)。
SIGTSTP  交互式中止信號。
SIGTTIN  後臺進程組成員讀控制終端。
SIGTTOU  後臺進程組成員寫控制終端。

第11章 線程

11.一、引言

  無論在什麼狀況下,只要單個資源須要在多個用戶間共享,就必須處理一致性問題。

11.四、線程建立

#include <pthread.h>

int pthread_create(pthread_t *restrict tidp,
                     const pthread_attr_t *restrict attr,
                     void *(*start_rtn)(void *),
                     void *restrict arg);
                //返回值:若成功,返回0;不然,返回錯誤編號。

  tidp用於返回線程ID。

11.五、線程終止

  單個線程能夠經過3種方式退出,所以能夠在不終止整個進程的狀況下,中止它的控制流。
  (1)、線程能夠簡單地從啓動例程中返回,返回值是線程的退出碼。
  (2)、線程能夠被同一個進程中的其餘線程取消。
  (3)、線程調用pthread_exit。

#include <pthread.h>

void pthread_exit(void *rval_ptr);

  rval_ptr參數是一個無類型指針,與傳給啓動例程的單個參數相似。進程中的其餘線程也能夠經過調用pthread_join函數訪問到這個指針。

#inlucde <pthread.h>

int pthread_join(pthread_t tread, void **rval_ptr);
                //返回值:若成功,返回0;不然,返回錯誤編號。

  注意這個rval_ptr參數是二級指針。
  調用線程將一直阻塞,直到指定的線程調用pthreaed_exit、從啓動例程中返回或者被取消。若是線程簡單地從它的啓動例程返回,rval_ptr就包含返回碼。若是線程被取消,由rval_ptr指定的內存單元就設置爲PTHREAD_CANCELED。

#include <pthread.h>

int ptrhead_cancel(pthread_t tid);
                //返回值:若成功,返回0;不然,返回錯誤編號

  線程能夠經過調用pthread_cancel函數來請求取消同一進程中的其餘線程。

  在默認狀況下,pthread_cancel函數會使得由tid標識的線程的行爲表現爲如同調用了參數爲PTHREAD_CANCELED的pthread_exit函數。可是,線程能夠選擇忽略取消或者控制如何被取消。注意pthread_cancel並不等待線程終止,它僅僅提出請求。

#include <pthread.h>

void pthread_cleanup_push(void (*rtn)(void *), void *arg);

void pthread_cleanup_pop(int execute);

  一個線程能夠創建多個清理處理程序。處理程序記錄在棧中,也就是說,它們的執行順序與它們註冊時相反。

  當線程執行如下動做時,清理函數rtn是由pthread_cleanup_push函數調度的,調用時只有一個參數arg:

調用pthread_exit時

響應取消請求時

用非零execute參數調用pthread_cleanup_pop函數時。

  若是execute參數設置爲0,清理函數將不被調用。無論發生上述那種狀況,pthread_cleanup_pop都將刪除上次pthread_cleanup_push調用創建的清理處理程序。  
進程和線程原語的比較

  在默認狀況下,線程的終止狀態會保存知道對該線程調用pthread_join。若是線程已經被分離,線程的底層存儲資源能夠在線程終止時當即被收回。在線程被分離後,咱們不能用pthread_join函數等待它的終止狀態,由於對分離狀態的線程調用pthread_join會產生未定義行爲。能夠調用pthread_detach分離線程。

#include <pthread.h>

int pthread_detach(pthread_t tid);
                //返回值:若成功,返回0;不然,返回錯誤編號

11.六、線程同步

  線程同步的5個基本的同步機制:互斥量、讀寫鎖、條件變量、自旋鎖以及屏障。

11.6.一、互斥量

  互斥量(mutext)從本質上說是一把鎖,在訪問共享資源乾兌互斥量進行設置(加鎖),在訪問完成後釋放(解鎖)互斥量。

11.6.二、避免死鎖

  能夠經過仔細控制互斥量的順序來避免死鎖的發生。
  有時候,應用程序的結構使得對互斥量進行排序是很困難的。這種狀況下,能夠先釋放佔有的鎖,而後過一段時間再試。

11.6.四、讀寫鎖

  讀寫鎖(reader-writer lock)與互斥量相似,不過讀寫鎖容許更高的並行性。互斥量要麼是鎖住狀態,要麼是不加鎖狀態,並且一次只有一個線程能夠對其加鎖。讀寫鎖能夠有3中狀態:讀模式下加鎖狀態,寫模式下加鎖狀態,不加鎖狀態。一次只有一個線程能夠佔有寫模式的讀寫鎖,可是多個線程能夠同時佔有讀模式的讀寫鎖。
  讀寫鎖也叫作共享互斥鎖(shared-exclusive lock)。當讀寫鎖是讀模式鎖住時,就能夠說成是以共享模式鎖住的。當它以寫模式鎖住的時候,就能夠說是以互斥模式鎖住的。

11.6.六、條件變量

  條件變量(Condition Variables)是線程可用的另外一種同步機制。條件變量給多個線程提供了一個會合的場所。條件變量與互斥量一塊兒使用時,容許線程以無競爭的方式等待特定的條件發生。
  條件自己是由互斥量保護的。線程在改變條件狀態以前必須首先鎖住互斥量。其餘線程在得到互斥量以前不會覺察到這種改變,由於互斥量必須在鎖定之後才能計算條件。

11.6.七、自旋鎖

  自旋鎖(Spin Locks)與互斥量相似,但它不是經過休眠使進程阻塞,而是在獲取鎖以前一直處於盲等(自旋)阻塞狀態。自旋鎖可用於如下狀況:鎖被持有的時間段,並且線程並不但願在從新調度上花費太多時間。

11.6.八、屏障

  屏障(Barriers)是用戶協調多個線程並行工做的同步機制。屏障容許每一個線程等待,知道全部的合做線程都達到某一點,而後從該點繼續執行。

第12章 線程控制

12.四、同步屬性

  就像線程具備屬性同樣,線程的同步對象也有屬性。11.6.7節中介紹了(同步對象)自旋鎖,它有一個屬性稱爲進程共享屬性。本節討論互斥量屬性、讀寫鎖屬性、條件變量屬性和屏障屬性。

12.4.一、互斥量屬性

  值得注意的3個屬性是:進程共享屬性、健壯性屬性以及類型屬性。

12.4.二、讀寫鎖屬性

  讀寫鎖支持的惟一屬性是進程共享屬性。

12.4.三、條件變量屬性

  Single UNIX Specification目前定義了條件變量的兩個屬性:進程共享屬性和時鐘屬性。

12.4.四、屏障屬性

  目前定義的屏障屬性只有進程共享屬性。

12.五、重入(Reentrancy)

  一、若是一個函數在相同的時間點能夠被多個線程安全的調用,就稱該函數是線程安全的。
  二、若是一個函數對多個線程來講是可重入的,就說這個函數是線程安全的。但這並不能說明對信號處理程序來講該函數也是可重入的。若是函數對異步信號處理程序的重入是安全的,那麼就能夠說函數是異步信號安全的。
  三、圖12-12的程序由於這句

pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);

須要加上編譯選項-D_GNU_SOURCE。

12.六、線程特定數據

  線程特定數據(thread-specific data),也稱爲線程私有數據(thread-private data),是存儲和查詢某個特定線程相關數據的一種機制。咱們把這種數據稱爲線程特定數據或線程私有數據的緣由是,咱們但願每一個線程能夠訪問它本身單獨的數據副本,而不須要擔憂與其餘線程的同步訪問問題。

#include <pthread.h>
pthread_once_t initflag = PTHREAD_ONCE_INIT;
int pthread_once(pthread_once_t *initflag, void (*initfn)(void));
                                 //返回值:若成功,返回0;不然,返回錯誤編號

12.七、取消選項

  有兩個線程屬性並無包含在pthread_attr_t結構中,他們是可取消狀態和可取消類型。這兩個屬性影響着線程在響應pthread_cancel函數調用時所呈現的行爲。

12.八、線程和信號

  鬧鐘定時器是進程資源,而且全部的線程共享相同的鬧鐘。因此,進程中的多個線程不可能互不干擾(或互不合做)地使用鬧鐘定時器。

#include <signal.h>
/*
how參數能夠取下列3個值之一:
 SIG_BLOCK,把信號集添加到線程信號屏蔽字中;
 SIG_SETMASK,用信號集替換線程的信號屏蔽字;
 SIG_UNBLOCK,從線程信號屏蔽字中移除信號集。
返回值:若成功,返回0;不然,返回錯誤編號
*/
int pthread_sigmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);
//線程能夠經過調用sigwait等待一個或多個信號的出現
//同時,sigwait會解除信號的阻塞狀態
//返回值:若成功,返回0;不然,返回錯誤編號
int sigwait(const sigset_t *restrict set, int *restrict gignop);

12.九、線程和fork

#include <pthread.h>
int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void));
        //返回值:若成功,返回0;不然,返回錯誤編號

  用pthread_atfork函數最多能夠安裝3個幫助清理鎖的函數。
  prepare fork處理程序由父進程在fork建立子進程前調用。這個fork處理程序的任務是獲取父進程定義的全部鎖。
  parent fork處理程序是在fork建立子進程之後、返回以前在父進程上下文中調用的。這個fork處理程序的任務是對prepare fork處理程序獲取的全部鎖進行解鎖。
  child fork處理程序在fork返回以前在子進程上下文中調用。與parent fork處理程序同樣,child fork處理程序也必須釋放prepare fork處理程序所獲取的全部鎖。

第13章 守護進程

13.二、守護進程的特徵

  一、守護進程分爲內核守護進程和用戶層(/級)守護進程。全部守護進程都沒有控制終端,其終端名爲問號。
  二、內核(守護)進程
(1)、父進程ID爲0的各進程一般是內核進程,它們做爲系統引導裝入過程的一部分而啓動。
(2)、在ps的輸出實例中,內核守護進程的名字出如今方括號中。
(3)、Ubuntu 12.04使用一個名爲kthreadd的特殊內核進程來建立其餘內核進程,因此kthreadd表現爲其餘內核進程的父進程。
  三、用戶層(/級)守護進程
(1)、init進程ID爲1,父進程ID爲0,但不是內核進程。
(2)、init是一個由內核在引導裝入時啓動的用戶層次的命令,它是其餘用戶層進程的父進程。

13.三、編程規則

  • 書中後面屢次用到的 daemonize 函數在這節定義。

13.四、出錯記錄(syslog Tips)

#include <syslog.h>
void openlog(const char *ident, int option, int facility);
void syslog(int priority, const char *format, ...);
void closelog(void);
int setlogmask(int maskpri);    //返回值:前日誌記錄優先級屏蔽字值

  調用openlog是可選擇的。若是不調用openlog,則在第一次調用syslog時,自動調用openlog。調用closelog也是可選擇的,由於它只是關閉曾被用於與syslog守護進程進行通訊的描述符。

13.五、單示例守護進程(Single-Instance Daemons)

  文件和記錄鎖提供了一種方便的互斥機制。若是每個守護進程建立一個有固定名字的文件,並在該文件的總體上加一把寫鎖,那麼只容許建立一把這樣的寫鎖。在此以後建立寫鎖的嘗試都會失敗,這向後續守護進程副本指明本身已有一個副本正在運行。

第14章 高級I/O

14.三、記錄鎖

二、fcntl記錄鎖
  fcntl函數能夠用F_GETLK命令測試可否創建一把鎖,而後用F_SETLK或F_SETLKW創建那把鎖。注意二者不是原子操做,不能保證在這兩次fcntl調用之間不會有另外一個進程插入並創建一把相同的鎖。
三、鎖的隱含繼承和釋放
(1)、鎖與進程和文件二者相關聯。
(2)、由fork產生的子進程不繼承父進程所設置的鎖。
(3)、在執行exec後,新程序能夠繼承原執行程序的鎖。

14.四、I/O多路轉接

14.4.一、函數 select 和 pselect

#include <sys/select.h>

//返回值:準備就緒的描述符數目;若超時,返回0;若出錯;返回-1
int select(int maxfdp1, fd_set *restrict readfds, 
            fd_set *restrict writefds, fd_set *restrict exceptfds,
            struct timeval *restrict tvptr);
            
//返回值:準備就緒的描述符數目;若超時,返回0;若出錯;返回-1
int pselect(int maxfdp1, fd_set *restrict readfds,
            fd_set *restrict writefds, fd_set *restrict exceptfds,
            const struct timespec *restrict tsptr,
            const sigset_t *restrict sigmask);

14.4.二、函數poll

#include <poll.h>

//返回值:準備就緒的描述符數目;若超時,返回0;若出錯,返回-1
int poll(struct pollfd fdarray[], nfds_t nfds, int timeout);
PS:epoll是Linux內核爲處理大批量文件描述符而做了改進的poll。(select、poll、epoll之間的區別總結[整理])

第15章 進程間通訊

15.一、引言

  本章討論5種經典的IPC(Inter-Process Communication):管道、FIFO(命名管道)、消息隊列、信號量以及共享存儲。

15.三、函數popen和pclose

#include <stdio.h>
//若成功,返回文件指針;若出錯,返回NULL
FILE *popen(const char *cmdstring, const char *type);
//若成功,返回cmdstring的終止狀態;若出錯,返回-1
int pclose(FILE *fp);

  函數popen先執行fork,而後調用exec執行cmdstring,而且返回一個標準I/O文件指針。

15.六、XSI IPC

  • 有3種稱做XSI IPC的IPC:消息隊列、信號量及共享存儲器。(相關命令:ipcs/ipcmk/ipcrm)

15.七、消息隊列

  消息隊列是消息的連接表,存儲在內核中。考慮到使用消息隊列時遇到的問題,咱們得出的結論是,在新的應用程序中不該當再使用它們。

#include <sys/msg.h>
/*
  msgget的功能是打開一個現有隊列或建立一個新隊列
  返回值:若成功,返回消息隊列ID;若出錯,返回-1 */
int msgget(ket_t key, int flag);
/*
  msgctl函數對隊列執行多種操做。它和另外兩個與信號量及共享存儲有關的函數(semctl和shcmctl)
  都是XSI IPC的相似於ioctl的函數(亦即垃圾桶函數)。
  返回值:若成功,返回0;若出錯,返回-1 */
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
/*
  msgsnd將數據放到消息隊列中
  若成功,返回0;若出錯,返回-1 */
int msgsnd(int msqid, const void *ptr, size_t nbytes, int flag);
/*
  msgrcv從隊列中取用消息
  返回值:若成功,返回消息數據部分的長度;若出錯,返回-1 */
ssize_t msgrcv(int msqid, void *ptr, size_t nbytes, long type, int flag);

15.九、共享存儲

#include <sys/shm.h>
//返回值:若成功,返回共享存儲ID;若出錯,返回-1
int shmget(ket_t key,  size_t size,  int flag);
//返回值:若成功,返回0;若出錯,返回-1
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
//將共享存儲鏈接到進程的地址空間
//返回值:若成功,返回執行共享存儲段的指針;若出錯,返回-1
void *shmat(int shmid, const void *addr, int flag);
//將進程與共享存儲分離,addr參數是之前調用shmat的返回值
//返回值:若成功,返回0;若出錯,返回-1
int shmdt(const void *addr);

  一、XSI共享存儲和內存映射文件的不一樣之處在於,前者沒有相關的文件。XSI共享存儲段是內存的匿名段。
  二、若是在兩個無關進程之間要使用共享存儲段,那麼有兩種可選方案:一種是應用程序使用XSI共享存儲函數;另外一種是使用mmap將同一文件映射至它們的地址空間,爲此使用MAP_SHARED標誌。

15.十、POSIX信號量

  介紹POSIX接口的動機之一就是,經過設計,它們的性能要明顯好於現有XSI信號量接口。由於二進制信號量能夠像互斥量同樣來使用,咱們可使用信號量來建立本身的鎖原語從而提供互斥。

15.十二、小結

【建議】
  一、要學會使用管道和FIFO,由於這兩種基本技術仍可有效地應用於大量的應用程序。
  二、在新的應用程序中,要儘量避免使用消息隊列以及信號量,而應當考慮全雙工管道和記錄鎖,它們使用起來會簡單得多。
  三、共享存儲仍然有它的用途,雖然經過mmap也能提供一樣的功能。

第16章 網絡IPC:套接字

16.二、套接字描述符

#include <sys/socket.h>
//返回值:若成功,返回文件(套接字)描述符;若出錯,返回-1
int socket(int domain, int type, int protocol);
//返回值:若成功,返回0;若出錯,返回-1
int shutdown(int sockfd, int how);

可以關閉(close)一個套接字,爲什麼還使用shutdown呢?書中給出了若干理由。

16.三、尋址

16.3.一、字節序

  一、字節序是一個處理器架構特性,用於指示像整數這樣的大數據類型內部的字節如何排序。
  二、關鍵詞:大端(big-endian)、小端(little-endian)、最低有效字節(Least Significant Byte, LSB)、最高有效字節(Most Significant Byte, MSB)
  三、無論字節序如何,最高有效字節老是在左邊,最低有效字節老是在右邊。
  四、Intel處理器通常是小端字節序,TCP/IP協議棧使用大端字節序。網絡字節序與CPU和操做系統無關。

#include <arpa/inet.h>
//將主機字節序轉爲網絡字節序
//返回值:以網絡字節序表示的32位整數
uint32_t htonl(uint32_t hostlong);
//將主機字節序轉爲網絡字節序
//返回值:以網絡字節序表示的16位整數
uint32_t htonl(uint32_t hostshort);
//將網絡字節序轉爲主機字節序
//返回值:以網絡字節序表示的32位整數
uint32_t ntohl(uint32_t netlong);
//將網絡字節序轉爲主機字節序
//返回值:以網絡字節序表示的32位整數
uint32_t ntohl(uint32_t netshort);

16.3.三、地址查詢

  gethostbyname 和 gethostbyaddr 兩個函數僅支持IPv4,同時支持IPv4和IPv6的替代函數是getaddrinfo。

16.3.四、將套接字與地址關聯

#include <sys/socket.h>
//如下三個函數返回值:若成功,返回0;若出錯,返回-1

//關聯地址和套接字
int bind(int sockfd, const struct sockaddr *addr, socklen_t len);
//由套接字描述符獲得本地地址信息
int getsockname(int sockfd, struct sockaddr *restrict localaddr, socklen_t *restrict alenp);
//由套接字描述符獲得對端地址信息
int getpeername(int sockfd, struct sockaddr *restrict peeraddr, socklen_t *restrict alenp);

16.四、創建鏈接

#include <sys/socket.h>
//返回值:若成功,返回0;若出錯,返回-1
int connect(int sockfd, const struct sockaddr *addr, socklen_t len);
//返回值:若成功,返回0;若出錯,返回-1
int listen(int sockfd, int backlog);
//返回值:若成功,返回文件(套接字)描述符;若出錯,返回-1
int accept(int sockfd, struct sockaddr *restrict addr, socklen_t *restrict len);
  • 注意:UDP socket不須要listen和accept。

16.五、數據傳輸

  儘管能夠經過read和write交換數據,但這就是這兩個函數所能作的一切。若是想指定選項,從多個客戶端接收數據包,或者發送帶外數據,就須要使用6個爲數據傳遞而設計套接字函數中的一個。

#include <sys/socket.h>

//如下三個函數返回值:若成功,返回發送的字節數;若出錯,返回-1
ssize_t send(int sockfd, const void *buf, size_t nbytes, int flags);
ssize_t sendto(int sockfd, const void *buf, size_t nbytes, int flags, 
                const struct sockaddr *destaddr, socklen_t destlen);
ssize_t send(int sockfd, const struct msghdr *msg, int flags);

//如下三個函數返回值:返回數據的字節長度;若無可用數據或對等方已經按序結束,返回0;若出錯,返回-1
ssize_t recv(int sockfd, void *buf, size_t nbytes, int flags);
ssize_t recvfrom(int sockfd, void *restrict buf, size_t len, int flags,
                  struct sockaddr *restrict addr, socklen_t *restrict addrlen);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
  • 注意:書中ruptime例子須要在/etc/services文件中添加服務名和端口/協議的映射。
ruptime   8888/tcp

17章 高級進程間通訊

17.二、UNIX域套接字(UNIX Domain Sockets)

  UNIX域套接字用於在同一臺計算機上運行的進程通訊。雖然因特網套接字可用於同一目的,但UNIX域套接字的效率更高。UNIX域套接字僅僅複製數據,它們並不執行協議處理,不須要添加或刪除網絡報頭,無需計算校驗和,不要產生順序號,無需發送確認報文。
UNIX域套接字提供流和數據報兩種接口。UNIX域數據報服務是可靠的,既不會丟失報文也不會傳遞出錯。UNIX域套接字就像是套接字和管道的混合。可使用它們面向網絡的域套接字接口或者使用socketpair函數來建立一對無命名的、相互鏈接的UNIX域套接字。

#include <sys/socket.h>
//返回值:若成功,返回0;若出錯,返回-1
int socketpair(int domain, int type, int protocol, int sockfd[2]);

  雖然接口足夠通用,容許socketpair用於其餘域,但通常來講操做系統僅對UNIX域提供支持。
  一對相互鏈接的UNIX域套接字能夠起到全雙工管道的做用:兩端對讀和寫開放。咱們將其稱爲fd管道(fd-pipe),以便與普通的半雙工管道區分開來。

  • PS:書中poll程序的epoll實現

17.四、傳送文件描述符

  若是在linux下編譯本節的程序報 #error passing credentials is unsuppported! ,編譯時添加 _GNU_SOURCE 宏便可。

17.六、open服務器進程第2版

  SUS包括了一系列的約定和規範來保證命令行語法的一致性,其中包括一些建議,如「限制每一個命令行選項爲一個單一的阿拉伯字符」一級「全部選項必須以‘-’做爲開頭字符」。
  幸運的是,getopt函數可以幫助命令開發者以一致的方式處理命令行選項。

#include <unistd.h>
//返回值:若全部選項被處理完,返回-1;不然,返回下一個選項字符
int getopt(int argc, char *const argv[], const char *options);
extern int optind, opterr, optopt;
extern char *optarg;

  參數argc和argv與傳入main函數的同樣。options參數是一個包含該命令支持的選項字符的字符串。若是一個選項字符後面接了一個冒號,則表示該選項須要參數;不然,改選項不須要額外參數。舉例來講,若是一條命令的用法說明以下:

command [-i] [-u username] [-z] filename

  則咱們能夠給getopt傳送一個"iu:z"做爲options字符串。

第18章 終端I/O

18.二、綜述

  一、終端I/O有兩種不一樣的工做模式。
(1)、規範模式輸入處理。在這種模式中,對終端輸入以行爲單位進行處理。對於每一個讀請求,終端驅動程序最多返回一行。
(2)、非規範模式輸入處理。輸入字符不裝配成行。
  二、全部能夠檢測和更改的終端設備特性都包含在termios結構中。該結構定義在頭文件<termios.h>中。POSIX標準以前的System V版本有一個名爲<termio.h>的頭文件和一個名爲termio的數據結構。爲了與先前版本有所區別,POSIX.1在這些名字後加了一個s。

struct termios {
    tcflag_t c_iflag;        // input flags
    tcflag_t c_oflag;        // output flags
    tcflag_t c_cflag;        // control flags
    tcflag_t c_lflag;        // local flags
    cc_t     c_cc[NCCS];     // control characters
};

18.三、特殊輸入字符

終端特殊輸入字符彙總

18.四、得到和設置終端屬性

#include <termios.h>
//兩個函數的返回值:若成功,返回0;若出錯,返回-1
int tcgetattr(int fd, struct termios *termptr);
int tcsetattr(int fd, int opt, const struct termios *termptr);

18.九、終端標識

#include <stdio.h>
//返回值:若成功,返回指向控制終端名的指針;若出錯,返回指向空字符串的指針
char *ctermid(char *ptr);

#include <unistd.h>
//返回值:若爲終端設備,返回1(真);不然,返回0(假)
int isatty(int fd);
//返回值:指向終端路徑名的指針;若出錯,返回NULL
char *ttyname(int fd);

第19章 僞終端

19.二、概述

僞終端(pseudo terminal)這個術語是指,對於一個應用程序而言,它看上去像一個終端,但事實上它並非一個真正的終端。

19.三、打開僞終端設備

#include <stdlib.h>
#include <fcntl.h>

//打開一個pty主設備
//返回值:若成功,返回下一個可用的PTY主設備文件描述符;若出錯,返回-1
int posix_openpt(int flag);

//更改PTY從設備的權限
//返回值:若成功,返回0;若出錯,返回-1
int grantpt(int fd);

//容許打開PTY從設備
//返回值:若成功,返回0;若出錯,返回-1
int unlockpt(int fd);

19.七、高級特性

一、打包模式

二、遠程模式

三、窗口大小變化

四、信號發生

第20章 數據庫函數庫

20.一、引言

本章將開發一個簡單的、多用戶數據庫的C函數庫。調用此函數庫提供的C語言函數,其餘程序能夠獲取和存儲數據庫中的記錄。

20.十、小結

本章詳細介紹了一個數據庫函數庫的設計與實現。考慮到篇幅,這個函數庫儘量小和簡單,但也包括了多進程併發訪問須要的對記錄加鎖的功能。
此外,還使用不一樣數量的進程以及不一樣的加鎖方法:不加鎖、建議性鎖和強制性鎖,研究了這個函數庫的性能。

第21章 與網絡打印機通訊

21.一、引言

如今咱們開發一個可以與網絡打印機通訊的程序。這些打印機經過以太網與多個計算機互聯,而且一般既支持純文本文件也支持PostScript文件。儘管一些應用程序也支持其餘通訊協議,但通常使用網絡打印協議(Internet Printing Protocol,IPP)與打印機通訊。
咱們將描述兩個程序:打印假脫機守護進程(print spooler daemon)將做業發送到打印機;命令行程序將打印做業提交到假脫機守護進程。

21.六、小結

本章仔細考查了兩個完整的程序:一個打印假脫機守護進程將做業發送到網絡打印機和一個命令行程序將打印做業提交到假脫機守護進程。這給咱們一個機會,考查一個實際程序中使用前面章節所講述的許多特性,如線程、I/O多路技術、文件I/O、套接字I/O以及信號。

相關文章
相關標籤/搜索