《APUE》中的函数整理
第1章 unix基礎知識
1. char *strerror(int errnum)
該函數將errnum(就是errno值)映射為一個出錯信息字符串,返回該字符串指針。聲明在string.h文件中。
2.void perror(const char *s)
該函數基于當前的errno值,在標準出錯文件中輸出一條出錯消息,然后返回。聲明在stdio.h文件中。它首先輸出由s指向的字符串,然后是一個冒號,一個空格,接著是errno值對應的出錯信息,最后是一個換行符。
?
第2章 UNIX標準化及實現
1.long sysconf(int name)
?? long fpathconf(int fd, int name)
?? long pathconf(char *path, int name)
第一個函數用來獲取系統運行時的配置信息。后兩個函數用來獲取文件的name選項的配置信息,它們的區(qū)別在于一個提供文件描述符,一個提供文件路徑。聲明在unistd.h文件中。這些name參數的常量值參考《apue》。
2.系統基本數據類型。這些類型在不同系統上被聲明為不同的c基本類型。因此使用它們可以增強代碼的可移植性。
caddr_t? ? ? 核心地址 ??????????? ? ? ? ? ? ?? clock_t?? 時鐘滴答計數器(進程時間) ?????????? comp_t??? 壓縮的時鐘時間
dev_t??????? 設備號(主和次) ??????????? fd_set?? 文件描述符集 ???????????????????????????????????? fpos_t?? 文件位置
gid_t ? ? ? ? 用戶組ID ??????????????? ? ? ? ??? ino_t??? i節(jié)點編號 ????????????? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? mode_t??? 文件類型,文件創(chuàng)建模式
nlink_t?????? 目錄項的鏈接技術 ??????????? off_t??? 文件大小和偏移量 ???????????? ? ? ? ? ? ? ?? ??? pid_t??? 進程ID和進程組ID
ptrdiff_t???? 兩個指針相減的結果 ???????? rlim_t??? 資源限制 ???????????? ? ? ? ? ? ? ? ? ? ? ? ? ? ???? sig_atomic_t??? 能原子訪問的數據類型
sigset_t???? 信號集 ????????????????????????????? size_t??? 對象大小(例如字符串),無符號 ???? ssize_t??? 返回的字節(jié)計數,帶符號的(如read,write返回值,需要錯誤值)
time_t??????? 日歷時間的秒計數器 ??????? uid_t???? 用戶ID ??????????????????????????????????????????????? wchar_t??? 能表示所有不同的字符碼
?
第3章 文件IO
1.int open(const char *pathname, int flags)
?? int open(const char *pathname, int flags, node_t mode)
該函數用來打開文件,聲明在fcntl.h中。flags標志有如下:O_RDONLY,O_WRONLY,O_RDWR(這三個標志必須要包含其中之一),還有一些文件創(chuàng)建標志和狀態(tài)標志,可以包含0個或多個,O_CLOEXEC,O_CREAT,O_DIRECTORY,O_EXEL,O_NOCITY,O_NOFOLLOW,O_TRUNC,O_TTY_INIT,O_NONBLOCK,O_SYNC。(這些都是打開的時候才設置,屬于某個打開的文件,inode中并不存在)
另外,當使用到O_CREAT標志來創(chuàng)建文件時,mode中需要包含下列符號:S_IRWXU,S_IRUSR,S_IWUSR,S_IXUSR,S_IRWXG,S_IRGRP,S_IWGRP,S_IXGRP,
S_IRWXO,S_IROTH,S_IWOTH,S_IXOTH。
2.int creat(const char *pathname, mode_t mode)
該函數用來創(chuàng)建一個文件,聲明在fcntl.h中。mode中的值同上。當open中的flags包含O_CREAT時,open和creat功能是一樣的。
3.int close(int fd)
該函數用來關閉有open/creat打開的文件描述符,聲明在unistd.h中。
4.off_t lseek(int fd, off_t offset, int whence)
該函數用來對已打開文件fd進行定位,聲明在unistd.h中。whence中可以包含如下值:SEEK_SET,SEEK_CUR,SEEK_END,分別表示從文件開頭,文件當前位置,文件結尾作為偏移量的開始。
5.ssize_t read(int fd, void *buf, size_t count)
該函數用來從文件中讀數據,聲明在unistd.h中。
6.ssize_t write(int fd, const void *buf, size_t count)
該函數用來向文件中寫數據,聲明在unistd.h中。
7.ssize_t pread(int fd, void *buf, size_t count, off_t offset)
該函數類似于read函數,區(qū)別在于該函數是原子執(zhí)行,并且不更新文件指針位置,offset用來設置在文件中讀取的位置。聲明在unistd.h中。
?8.ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset)
該函數也類似于pread和write。聲明在unistd.h中。
注意:7,8兩個函數主要用在多線程對同一文件進行讀寫的場合。
9.int dup(int oldfd)
?? int dup2(int oldfd, int newfd)
這兩個函數用來復制文件描述符,聲明在unistd.h中。dup使用最小的空閑描述符作為新描述符,dup2的newfd提供了新描述符。
10.void sync(void)
? ?? int fsync(int fd)
int fdatasync(int fd)
這三個函數用來將文件緩沖區(qū)中的數據寫入磁盤文件中。聲明在unistd.h中。區(qū)別在于,sync將塊緩沖區(qū)放入寫隊列就返回,并不等待實際寫入磁盤中;fsync將fd文件緩沖塊
立即寫入磁盤中,并等待寫入后才返回,同時更新文件屬性;fdatasync類似于syncfs,不過只影響文件的部分數據。
11.?int fcntl(int fd, int cmd, ... /* arg */ )
該函數用來更改已打開文件fd的屬性,聲明在fcntl.h中。該函數實現了5中功能,體現在不同的cmd所包含的標志中:
a.復制一個現有描述符(cmd=F_DUPFD)
b.獲得/設置文件描述符標志(cmd=F_GETFD/F_SETFD)
c.獲得/設置文件狀態(tài)標志(cmd=F_GETFL/F_SETFL)
d.獲得/設置異步I/O所有權(cmd=F_GETOWN/cmd=F_SETOWN)
e.獲得/設置記錄鎖(cmd=G_GETLK/G_SETLK/F_SETLKW)
當cmd為F_DUPFD,fcntl函數相當于dup/dup2函數,返回復制后的新描述符,該描述符是空閑描述符中大于或等于第三個參數的值;當cmd為F_GETFD,用來獲取文件描述符的所有標志(當前只有一個標志FD_CLOEXEC),當cmd為F_SETFD,用來設置文件描述父的標志(由第三個參數提供);當cmd為F_GETFL,用來獲取文件的狀態(tài)標志,文件狀態(tài)標志如下所示。當cmd為F_SETFL,用來設置文件的狀態(tài)標志。當cmd為F_GETOWN,用來取當前接收SIGIO和SIGURG信號的進程ID和進程組ID;當cmd為F_SETOWN,設置接收SIGIO和SIGURG信號的進程ID和進程組ID。
文件狀態(tài)標志:
O_RDONLY?????????????? 只讀打開
O_WRONLY????????????? 只寫打開
O_RDWR?????????????????? 讀寫打開
O_APPEND?????????????? 追加打開
O_NONBLOCK????????? 非阻塞模式
O_SYNC?????????????????? 等待寫完成(數據和屬性)?
O_DSYNC???????????????? 等待寫完成(僅數據)
O_RSYNC???????????????? 同步讀寫
O_FSYNC???????????????? 等待寫完成(僅FreeBSD和Mac OS X)
O_ASYNC???????????????? 異步I/O(僅FreeBSD和Mac OS X)
12.int ioctl(int d, int request, ...)
該函數進行I/O設置,不能用本章中其他函數設置的I/O操作都可用該函數完成。比如終端I/O等等。聲明在sys/ioctl.h中。
?
第4章 文件和目錄
1.int stat(const char *path, struct stat *buf)
?? int fstat(int fd, struct stat *buf)
? ?int lstat(const char *path, struct stat *buf)
這幾個函數用來獲取文件狀態(tài)(從inode節(jié)點中取出,除了st_ino之外),將文件狀態(tài)保存在buf指向的struct stat類型結構體中。聲明在sys/stat.h中。區(qū)別在于,stat和lstat中文件以路徑名形式提供,fstat中文件以文件描述符形式提供,另外,lstat用來獲得軟連接的文件信息,而不是軟連接所指向文件的信息。struct stat結構體信息如下:
struct stat {
?????????????? dev_t???? st_dev;???? /* ID of device containing file */
?????????????? ino_t???? st_ino;???? /* inode number */
?????????????? mode_t??? st_mode;??? /* protection */? //該域中保存了文件的訪問權限以及文件類型的信息,還包括了設置用戶ID位和設置組ID位
?????????????? nlink_t?? st_nlink;?? /* number of hard links */
?????????????? uid_t???? st_uid;???? /* user ID of owner */
?????????????? gid_t???? st_gid;???? /* group ID of owner */
?????????????? dev_t???? st_rdev;??? /* device ID (if special file) */
?????????????? off_t???? st_size;??? /* total size, in bytes */
?????????????? blksize_t st_blksize; /* blocksize for filesystem I/O */
?????????????? blkcnt_t? st_blocks;? /* number of 512B blocks allocated */
?????????????? time_t??? st_atime;?? /* time of last access */
?????????????? time_t??? st_mtime;?? /* time of last modification */
?????????????? time_t??? st_ctime;?? /* time of last status change */
?????????? };
2.S_ISDIR
?? S_ISREG
?? S_ISCHR
?? S_ISBLK
?? S_ISFIFO
?? S_ISLNK
?? S_ISSOCK
這幾個都是宏定義,依據struct stat中st_mode成員的值進行判斷(eg: S_ISDIR(buf.st_mode)),當前文件是否是目錄文件,普通文件,字符設備,塊設備,FIFO,軟連接,套接字。聲明在sys/stat.h中。
3.S_ISUID
?? S_ISGID
這兩個宏依據struct stat中st_mode成員的值,來測試該文件是否設置了“設置用戶ID位”和“設置組ID位”。
4.int access(const char *pathname, int mode)
該函數用來檢測當前進程的實際用戶和實際組是否有訪問由pathname所指示的文件。聲明在unistd.h文件中。mode可取以下值:
R_OK????? 測試讀權限:
W_OK????? 測試寫權限
X_OK?????? 測試執(zhí)行權限
F_OK?????? 測試文件是否存在
5.mode_t umask(mode_t mask)
該函數用來設置當前進程的文件模式創(chuàng)建屏蔽字,這會屏蔽掉當前進程中創(chuàng)建的文件的那些權限。聲明在sys/stat.h文件中。該函數不改變shell的屏蔽字。mask取值集合和open函數的mode參數一致。注意:umask中mask包含的權限會被屏蔽掉,這和open用法相反。
6.int chmod(const char *path, mode_t mode)
?? int fchmod(int fd, mode_t mode)
這兩個函數用來改變文件的訪問權限。聲明在sys/stat.h文件中。區(qū)別在于一個提供了文件的路徑,另一個提供了文件描述符。此外,該函數的mode中除了能包含open的mode取值集合,還可以包含如下三個:S_ISUID,S_ISGID,S_ISVTX,其中前兩個分別是設置用戶ID位和設置組ID位,第三個是粘住位。
7.int chown(const char *path, uid_t owner, gid_t group)
?? int fchown(int fd, uid_t owner, gid_t group)
? ?int lchown(const char *path, uid_t owner, gid_t group)
這幾個函數用來設置文件的用戶ID和組ID。聲明在unistd.h文件中。第一三個通過文件路徑來提供文件,第二個用fd提供文件。第三個函數只是修改符號鏈接的用戶ID和組ID,沒有修改符號鏈接指向的文件。只有調用這些函數的進程的有效用戶為文件的擁有者時才能完成修改,超級用戶可以修改任何文件。
8.int truncate(const char *path, off_t length)
? ?int ftruncate(int fd, off_t length)
這兩個函數用來將文件截短為length字節(jié)(即將length字節(jié)以后的部分去掉)。聲明在unistd.h文件中。
9.int link(const char *oldpath, const char *newpath)
該函數為oldpath文件創(chuàng)建一個硬鏈接newpath(實際上就是個目錄項)。聲明在unistd.h文件中。最后oldpath和newpath指向了同一個inode節(jié)點。inode中的鏈接計數加1。
創(chuàng)建硬鏈接一般需要:1.超級用戶權限才能創(chuàng)建目錄的硬鏈接,2.硬鏈接和文件位于同一文件系統中。
10.int unlink(const char *pathname)
該函數刪除一個目錄項(也就是刪除一個硬鏈接),該目錄項指向的inode中的鏈接計數減1。聲明在unistd.h文件中。
11.int remove(const char *pathname)
這是c的標準庫函數,聲明在stdio.h中。用來刪除文件或目錄的鏈接。刪除文件時相當于unlink,刪除目錄時相當于rmdir。
12.int rename(const char *oldpath, const char *newpath)
該函數是c的標準庫函數,聲明在stdio.h中。用來為文件或者目錄重命名。
13.int symlink(const char *oldpath, const char *newpath)
該函數用來為oldpath文件創(chuàng)建一個符號鏈接newpath。聲明在unistd.h文件中。
14.ssize_t readlink(const char *path, char *buf, size_t bufsiz)
該函數用來讀一個符號鏈接文件中的值(而不是所指向的文件)。聲明在unistd.h文件中。雖然open函數可以打開文件,但是由于其跟隨符號鏈接(打開符號鏈接所指向的文件),故不能打開符號鏈接文件本身。
15.int utime(const char *filename, const struct utimbuf *times)
該函數用來更改一個文件的訪問和修改時間。聲明在utime.h文件中。struct utimbuf結構體如下:
struct utimbuf {
?????????????? time_t actime;?????? /* access time */
?????????????? time_t modtime;????? /* modification time */
?????????? };
16.int mkdir(const char *pathname, mode_t mode)
該函數用來創(chuàng)建一個新目錄。聲明在sys/stat.h文件中。創(chuàng)建目錄時,至少需要設置一個執(zhí)行權限位,以允許訪問該目錄的文件名。
17.int rmdir(const char *pathname)
該函數用來刪除一個空目錄。聲明在unistd.h文件中。
18.DIR *opendir(const char *name)
???? DIR *fdopendir(int fd)
這兩個函數用來打開目錄文件。聲明在dirent.h文件中。返回一個指向目錄流的指針,目錄流不用關心。
19.struct dirent *readdir(DIR *dirp)
該函數從打開的目錄文件中讀取目錄項,保存到struct dirent結構中,并返回指向該結構的指針。聲明在dirent.h文件中。該結構體如下:
struct dirent {
?????????????? ino_t????????? d_ino;?????? /* inode number */
?????????????? off_t????????? d_off;?????? /* not an offset; see NOTES */
?????????????? unsigned short d_reclen;??? /* length of this record */
?????????????? unsigned char? d_type;????? /* type of file; not supported
????????????????????????????????????????????? by all filesystem types */
?????????????? char?????????? d_name[256]; /* filename */
?????????? };
20.void rewinddir(DIR *dirp)
該函數用來重置目錄流內的指針位置,使之指向目錄的開頭。聲明在dirent.h文件中。
21.int closedir(DIR *dirp)
該函數關閉dirp所指向的目錄流。聲明在dirent.h文件中。
22.long telldir(DIR *dirp)
該函數返回目錄流內指針的當前位置。聲明在dirent.h文件中。
23.void seekdir(DIR *dirp, long loc)
該函數設置目錄流內的指針位置,下一次readdir將從設置好的位置上讀取。聲明在dirent.h文件中。
24.int chdir(const char *path)
???? int fchdir(int fd)
這兩個函數更改當前進程的當前工作目錄。聲明在unistd.h文件中。
25.char *getcwd(char *buf, size_t size)
該函數獲取當前進程的當前工作目錄。聲明在unistd.h文件中。
?
第5章 標準I/O庫
1.int fwide(FILE *stream, int mode)
該函數設置文件流stream的定向(字節(jié)定向或寬定向)。但是不設置已經定向的流的定向。該函數無出錯返回(返回負值說明是字節(jié)定向,返回正值是寬定向,返回0無定向)。聲明在wchar.h文件中。根據mode取值不同,函數功能不同:
mode值為負,將流設置為字節(jié)定向。
mode值為正,將流設置為寬定向。
mode值為0,不設置流的定向,但返回該流定向值。
2.void setbuf(FILE *stream, char *buf)
? ?int setvbuf(FILE *stream, char *buf, int mode, size_t size)
這兩個函數改變給定流stream的緩沖類型。聲明在stdio.h文件中。對于setbuf而言,當buf為NULL,則stream流被設置為無緩沖;否則,buf的長度應該定義為BUFSIZE(在stdio.h中定義),將根據stream流是磁盤文件或者終端設備而將其設置為全緩沖或者行緩沖。對setvbuf而言,根據mode取值不同,設置的緩沖區(qū)類型也不同:
mode取值_IOFBF,設置為全緩沖,buf為非空,全緩沖為用戶的buf,大小為size;buf為NULL,系統自動分配合適大小的全緩沖區(qū)。
mode取值_IOLBF,設置為行緩沖,同上,全緩沖改為行緩沖即可。
mode取值_IONBF,設置為無緩沖。(忽略buf和size)
此外,setvbuf函數要在所有對流進行操作的函數之前被調用(打開流以后就立即調用該函數)。
3.?int fflush(FILE *stream)
該函數強制沖洗一個流,將stream流的所有未寫數據傳送到內核(然后寫入磁盤/終端設備中)。若stream為NULL,強制沖洗所有輸出流。
4.FILE *fopen(const char *path, const char *mode)?
?? FILE *freopen(const char *path, const char *mode, FILE *stream)
?? FILE *fdopen(int fd, const char *mode)
這幾個函數打開一個標準I/O流。聲明在stdio.h文件中。fopen打開指定的文件;freopen在指定的流上打開一個指定的文件,如果指定流已打開,那么先關閉該流,如果指定流已定向,那么清除該定向,該函數一般用來將一個指定的文件打開到標準輸入/輸出/出錯流上;fdopen為一個打開的文件描述符關聯一個標準I/O流。這幾個函數的mode取值如下:
r/rb??????????????????????????? 為讀而打開
w/wb????????????????????????? 為寫而打開或為寫而創(chuàng)建,并將文件截短為0
a/ab?????????????????????????? 為追加而打開或為追加而創(chuàng)建
r+/r+b/rb+????????????????? 為讀寫而打開
w+/w+b/wb+?????????????? 為讀寫而打開,并將文件截短為0
a+/a+b/ab+??????????????? 為讀或追加打開或創(chuàng)建
(b僅代表二進制文件,不影響其他)
?fdopen的mode取值有些特殊,取各種“寫”的時候,并不截短文件(因為fd表明該文件已經打開,而不是由標準I/O打開的,所以標準I/O沒資格截短)。而且取各種“寫”的時候不能沒有創(chuàng)建功能,因為fd文件已存在,無須創(chuàng)建。
另外,如果多個進程使用標準I/O打開一個文件,那么并發(fā)去寫文件都可以正確將數據寫入文件,標準I/O流的強大之處。。還有,如果同時以讀和寫打開一個文件,那么:1.如果中間沒有fflush,fseek,fsetpos或rewind,則在輸出后不能直接跟隨輸入;2.如果中間沒有fseek,fsetpos或rewind,或者一個輸入操作沒有達到文件尾部,則在輸入操作之后不能直接跟隨輸出。
最后,用標準I/O創(chuàng)建的文件無法說明文件的訪問權限。
5.int fclose(FILE *fp)
該函數關閉標準I/O流fp。聲明在stdio.h文件中。關閉之前沖洗緩沖區(qū)的輸出數據,丟棄緩沖區(qū)中的輸入數據。
6.int getc(FILE *stream)
? ?int fgetc(FILE *stream)
?? int getchar(void)
這些函數從標準I/O流stream中每次讀一個字符。聲明在stdio.h文件中。其中getc是宏函數,getchar相當于fgetc(stdin)。返回值為讀到的字符,字符類型本為unsigned char,在這里強制轉化成int類型,是為了當出錯或者到達文件末尾的時候能夠返回-1。在stdio.h中,EOF定義為-1,因此將這些函數返回值和EOF比較就可知道是否出錯或者是否到達文件末尾。
7.int ferror(FILE *stream)
?? int feof(FILE *stream)
?? void clearerr(FILE *stream)
前兩個函數是為了判斷流是否出錯。每個流在FILE對象中維持了兩個標志:出錯標志和文件結束標志(用以區(qū)分出錯和到達文件末尾)。它們都聲明在stdio.h文件中。第三個函數清除這兩個標志。
8.int ungetc(int c, FILE *stream)
該函數將字符c回送到stream流中,而不是文件中。下次從流中讀取時,將讀到該字符。聲明在stdio.h文件中。
9.int putc(int c, FILE *stream)
?? int fputc(int c, FILE *stream)
?? int putchar(int c)
這三個函數將字符c輸出到輸出流中。和6中的三個函數類似,putc是宏函數,putchar相當于fputc(c,stdout)。聲明在stdio.h文件中。
10.char *gets(char *s)
???? char *fgets(char *s, int size, FILE *stream)
gets函數從stdin中讀取一行字符存入s中(在stdin中遇到換行符或者EOF結束標志就算一行),不推薦使用該函數,容易造成緩沖區(qū)溢出。fgets從指定的流stream中讀取,并且指定了緩沖區(qū)大小為size,也是每次讀取一行,讀取的字符數最多為size-1,最后加‘\0’。對于這兩個函數而言,讀取到一行后,會將'\n'或者EOF替換為‘\0’。它們都聲明在stdio.h文件中。
11.int fputs(const char *s, FILE *stream)
???? int puts(const char *s)
fputs函數將以‘\0’結尾的串s輸出到stream流中。puts函數將以‘\0’結尾的串s輸出到stdout中。‘\0’都不寫入流中。puts函數最后會將一個‘\n’輸出到stdout流中。它們都聲明在stdio.h文件中。
12.size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)
? ? ?size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream) ???
這兩個函數是二進制I/O函數,可以進行塊的讀寫操作。聲明在stdio.h文件中。
13.long ftell(FILE *stream)
???? int fseek(FILE *stream, long offset, int whence)
? ? ?void rewind(FILE *stream)
這幾個函數用來對標準I/O流進行定位(偏移量都以字節(jié)為單位)。ftell獲取流內指針的當前位置,? fseek將流內指針偏移量設為offset,whence提供了偏移量的相對起始位置,這和第三章的第4個函數lseek的該參數取值相同。rewind函數將流內指針設置到文件的開頭。對于fseek函數而言,定位文本文件時,whence只能取SEEK_SET,offset只能取兩種值:0或ftell的返回值。它們都聲明在stdio.h文件中。
14.int fseeko(FILE *stream, off_t offset, int whence)
? ? ?off_t ftello(FILE *stream)
這兩個函數和13中前兩個函數用法相同,區(qū)別是這兩個函數的偏移類型為off_t。聲明在stdio.h文件中。 ?
15.int fgetpos(FILE *stream, fpos_t *pos)
? ? ?int fsetpos(FILE *stream, fpos_t *pos)
第一個函數將定位的流內指針位置保存到pos中,第二個函數可以使用pos將流內指針設置為該位置。聲明在stdio.h文件中。
16.int printf(const char *format, ...)
? ? ?int fprintf(FILE *stream, const char *format, ...)
???? int sprintf(char *str, const char *format, ...)
???? int snprintf(char *str, size_t size, const char *format, ...)
這幾個函數用于格式化輸出。聲明在stdio.h文件中。第一個函數輸出到標準輸出流stdout。第二個函數輸出到stream流中。后兩個函數輸出到str緩沖區(qū)中。sprintf可能會造成緩沖區(qū)溢出,因此不建議使用。snprintf規(guī)定了緩沖區(qū)大小為size,因此比較安全。
17.int vprintf(const char *format, va_list ap)
? ? ?int vfprintf(FILE *stream, const char *format, va_list ap)
? ? ?int vsprintf(char *str, const char *format, va_list ap)
???? int vsnprintf(char *str, size_t size, const char *format, va_list ap)
這四個函數和16中的函數類似,區(qū)別是將可變參數替換成了ap參數。聲明在stdarg.h文件中。
18.int scanf(const char *format, ...)
???? int fscanf(FILE *stream, const char *format, ...)
???? int sscanf(const char *str, const char *format, ...)
這幾個函數用于格式化輸入。聲明在stdio.h文件中。第一個函數從標準輸入流stdin中讀取,第二個函數從stream流中讀取,第三個函數從str緩沖區(qū)中讀取。
19.int vscanf(const char *format, va_list ap)
???? int vsscanf(const char *str, const char *format, va_list ap)
???? int vfscanf(FILE *stream, const char *format, va_list ap)
這幾個函數類似于18中的函數,也是將可變參數替換成了ap參數。聲明在stdarg.h文件中。
20.int fileno(FILE *stream)
該函數將stream流所關聯的文件描述符返回。聲明在stdio.h文件中。
21.char *tmpnam(char *s)??
第一個函數用來產生一個與現有文件名不同的有效路徑名字符串。如果s為NULL,函數自動申請空間來存放有效路徑名字符串,并返回空間地址,如果s不為NULL,則將有效路徑名字符串存放于s中,并將s地址返回,然后就可用該路徑名在程序中手動創(chuàng)建文件了。聲明在stdio.h文件中。該函數有個問題,就是在產生出一個有效文件名和使用該文件名創(chuàng)建文件中間一般會有時間差,在該時間差內可能別的進程會使用該文件名創(chuàng)建文件,這樣就會出現問題。
22.FILE *tmpfile(void)
???? char *tempnam(const char *dir, const char *pfx)
第一個函數創(chuàng)建一個臨時二進制文件,在關閉該文件或程序結束時將自動刪除該文件。第二個函數也是用來創(chuàng)建一個臨時文件。關閉該文件或者程序運行完后文件會被自動刪除。聲明在stdio.h文件中。其中,dir規(guī)定了創(chuàng)建臨時文件所在的目錄,pfx如果非NULL的話,是一個最多包含5個字符的字符串,作為文件名的頭幾個字符。第二個函數也存在21函數的時間差問題。dir有如下要求:
a.如果定義了環(huán)境變量TMPDIR,則用其作為目錄。
b.如果dir非空,則用dir作為目錄。
c.將stdio.h中的字符串P_tmpdir用作目錄。
d.將本地目錄(通常是/tmp)用作目錄。
該函數自動分配空間來存放臨時的文件名,不接受用戶自己的buf。
23.? int mkstemp(char *template)
該函數也是用來創(chuàng)建一個臨時文件,返回文件描述符。與上邊的函數不同的是,它所創(chuàng)建的文件不會被自動刪除。臨時文件名由template參數提供,該串的最后6個字符為XXXXXX,然后該函數用不同字符代替XXXXXX,以創(chuàng)建唯一路徑名。聲明在stdio.h文件中。
?
第6章 系統數據文件和信息
1.struct passwd *getpwuid(uid_t uid)
?? struct passwd *getpwnam(const char *name)
這兩個函數通過口令文件(/etc/passwd,該文件中存放了用戶的所有信息除了用戶密碼),將inode中的用戶ID或者登錄時輸入的登錄名在口令文件中的項找出來,填入struct passwd結構中,并返回該結構指針,該結構是函數中定義的靜態(tài)結構,以后的函數調用會不斷覆蓋該結構的內容。該結構中包含了用戶的各種信息。聲明在pwd.h文件中。
2.struct passwd *getpwent(void)
?? void setpwent(void)
?? void endpwent(void)
第一個函數在循環(huán)中,可以逐個取出口令文件中的項。第二個函數用于返繞口令文件,即設置口令文件指針到文件的開頭。第三個函數用于關閉由第一個函數打開的口令文件。它們都聲明在pwd.h文件中。
3.struct spwd *getspnam(const char *name)
?? struct spwd *getspent(void)
?? void setspent(void)
?? void endspent(void)
這幾個函數用來讀取陰影文件(/etc/shadow,保存著用戶名和用戶密碼)的項。聲明在shadow.h文件中。這些函數用法和1,2函數類似。第一個函數根據用戶名name獲得對應的項,第二個函數在循環(huán)中可以逐個取出陰影文件中的項,最后兩個函數用來返繞和關閉陰影文件。
4.struct group *getgrgid(gid_t gid)
?? struct group *getgrnam(const char *name)
這兩個函數通過組文件(/etc/group,該文件中存放了所有用戶組的信息),將組ID或者組名對應的項讀取出來,寫入struct group結構,并返回該結構的指針。類似于1中的函數。聲明在grp.h文件中。struct group結構體如下:
?struct group {
?????????????? char?? *gr_name;?????? /* group name */
?????????????? char?? *gr_passwd;???? /* group password */
?????????????? gid_t?? gr_gid;??????? /* group ID */
?????????????? char? **gr_mem;??????? /* group members */? //該組的所有用戶名構成的數組
?????????? };
5.struct group *getgrent(void)
?? void setgrent(void)
?? void endgrent(void)
這些函數用來讀取組文件中的項,類似于2中的函數。聲明在grp.h文件中。
6.int getgroups(int size, gid_t list[])
該函數用來獲取當前用戶的附加組ID。聲明在unistd.h文件中。每個用戶除了可以加入口令文件中顯示的那個組外,同時可以加入若干個附加組。對于該函數,list數組用來存放所有的附加組ID數值,size說明了數組的元素個數。當size為0是,該函數返回當前用戶的附加組數(可以用來分配list的長度)。
7.int setgroups(size_t size, const gid_t *list)
該函數為當前用戶設置附加組ID表。聲明在grp.h文件中。該函數一般只在initgroups函數中進行調用。
8.int initgroups(const char *user, gid_t group)
該函數用來為當前用戶初始化附加組ID表。聲明在grp.h文件中。該函數會讀整個組文件,找到user所在的所有組,構成list數組后,調用setgroups函數來設置。并將當前用戶所在組的ID號group也存入附加組ID表。
9.struct utmp
1 struct utmp { 2 short ut_type; /* Type of record */ 3 pid_t ut_pid; /* PID of login process */ 4 char ut_line[UT_LINESIZE]; /* Device name of tty - "/dev/" */ 5 char ut_id[4]; /* Terminal name suffix, 6 or inittab(5) ID */ 7 char ut_user[UT_NAMESIZE]; /* Username */ 8 char ut_host[UT_HOSTSIZE]; /* Hostname for remote login, or 9 kernel version for run-level 10 messages */ 11 struct exit_status ut_exit; /* Exit status of a process 12 marked as DEAD_PROCESS; not 13 used by Linux init(8) */ 14 /* The ut_session and ut_tv fields must be the same size when 15 compiled 32- and 64-bit. This allows data files and shared 16 memory to be shared between 32- and 64-bit applications. */ 17 #if __WORDSIZE == 64 && defined __WORDSIZE_COMPAT32 18 int32_t ut_session; /* Session ID (getsid(2)), 19 used for windowing */ 20 struct { 21 int32_t tv_sec; /* Seconds */ 22 int32_t tv_usec; /* Microseconds */ 23 } ut_tv; /* Time entry was made */ 24 #else 25 long ut_session; /* Session ID */ 26 struct timeval ut_tv; /* Time entry was made */ 27 #endif 28 29 int32_t ut_addr_v6[4]; /* Internet address of remote 30 host; IPv4 address uses 31 just ut_addr_v6[0] */ 32 char __unused[20]; /* Reserved for future use */ 33 }; struct utmp/run/utmp文件中保存了當前所有登錄到系統中的用戶。通過該結構體可以對文件中的項進行讀取和寫入。
10.int uname(struct utsname *buf)
該函數可以獲取當前主機和操作系統的有關信息。聲明在sys/utsname.h文件中。struct utsname結構體如下:
struct utsname {
?????????????? char sysname[];??? /* Operating system name (e.g., "Linux") */
?????????????? char nodename[];?? /* Name within "some implementation-defined
???????????????????????????????????? network" */
?????????????? char release[];??? /* Operating system release (e.g., "2.6.28") */
?????????????? char version[];??? /* Operating system version */
?????????????? char machine[];??? /* Hardware identifier */
?????????? #ifdef _GNU_SOURCE
?????????????? char domainname[]; /* NIS or YP domain name */
?????????? #endif
?????????? };
11.int gethostname(char *name, size_t len)
該函數獲取主機的名字存入name數組中,len指定了數組長度。聲明在unistd.h文件中。
12.time_t time(time_t *t)
該函數返回當前時間和日期(從1970.1.1 0:0:0開始至今的秒數),存入t所指向的變量中。聲明在time.h文件中。
13.int gettimeofday(struct timeval *tv, struct timezone *tz)
該函數也是返回當前時間,將時間保存在struct timeval結構體中。聲明在sys/time.h文件中。該函數比time函數有更高分辨率(微秒級),tz一般為NULL。struct timeval結構體如下:
struct timeval {
?????????????? time_t????? tv_sec;???? /* seconds */?? //秒,也是從1970.1.1 0:0:0開始至今的秒數,和time函數獲取數值的相同
?????????????? suseconds_t tv_usec;??? /* microseconds */??? //微秒
?????????? };
14.struct tm *gmtime(const time_t *timep)
???? struct tm *localtime(const time_t *timep)
第一個函數將timep的秒數轉換成國際標準時間(年月日時分秒星期),第二個函數將timep的秒數轉換成本地時間。轉換后的時間保存在struct tm靜態(tài)結構體中,然后返回結構體指針。聲明在time.h文件中。
15.time_t mktime(struct tm *tm)
該函數將本地時間轉換成time_t類型的值(秒數)。聲明在time.h文件中。
16.char *asctime(const struct tm *tm)
???? char *ctime(const time_t *timep)
這兩個函數分別將struct tm類型的時間和time_t類型的秒數轉換成26字節(jié)的字符串。eg:Sun Sep? 7 22:27:44 CST 2014。聲明在time.h文件中。
17.size_t strftime(char *s, size_t max, const char *format,? const struct tm *tm)
該函數將tm中的時間格式化輸出到長度為max的s數組中。若max大小不夠,則函數返回0。否則返回存入的字符數。聲明在time.h文件中。format格式和printf的format類似,但有一些不同,相見《APUE》p145頁。
?
第7章 進程環(huán)境
18.void exit(int status)
???? void _Exit(int status)
這兩個函數用來正常終止一個進程。第二個函數立即進入內核,第一個函數執(zhí)行完清理工作(關閉標準IO)然后進入內核。status是進程的終止狀態(tài)。聲明在stdlib.h文件中。
在main函數中調用exit等價于return語句。
19.void _exit(int status)
該函數也是用來正常終止一個進程。并且直接進入內核。聲明在unistd.h文件中。status是進程的終止狀態(tài)。
?注:在我的ubuntu14.04上測試,main函數的結束處未顯式的寫出返回語句(18,19函數或return),或者返回語句中沒有明確的返回狀態(tài)(數字),則進程終止狀態(tài)均為6,而不是《apue》中提到的0。
在這里總結下進程的5種正常終止方式和3種異常終止方式:
a.在main函數中執(zhí)行return。(等價于執(zhí)行exit,也就是在主線程中結束進程,如果僅想結束主線程而不是進程,則在main最后調用pthread_exit即可)
b.調用exit函數(主線程中或者任意子線程中調用)。該函數將調用由atexit登記過的終止處理函數,并關閉所有的標準I/O流。
c.調用_exit或_Exit函數。這兩個函數直接陷入內核,因此就不會執(zhí)行終止處理程序或者信號處理程序。
d.進程的最后一個線程從其啟動例程(就是線程的執(zhí)行函數)中返回。線程的返回值不會做為進程的返回值,進程將以終止狀態(tài)0返回。
e.進程的最后一個線程調用pthread_exit函數。這種情況下,進程的終止狀態(tài)為0,而不是pthread_exit的參數。
3種異常終止方式:
a.調用abort。
b.當進程接收到某些信號。
c.最后一個線程對“取消”請求做出響應。
無論進程以何種方式結束,最終會執(zhí)行內核中的同一段代碼,來關閉所有打開的文件描述符,釋放它所使用的存儲器等。如果子進程希望父進程知道它是如何結束的,通過調用exit,_exit,_Exit函數,并把退出狀態(tài)作為參數傳遞給它們,而在異常終止情況下,內核會產生一個指示其異常終止原因的終止狀態(tài),最終父進程通過調用wait或waitpid函數就可以獲取到終止狀態(tài)。exit和_Exit最終還是調用_exit函數,將退出狀態(tài)轉換成進程的終止狀態(tài)。exit,_exit,_Exit三個函數的參數(退出狀態(tài))必然等于進程的終止狀態(tài),但是線程的結束函數pthread_exit的參數(退出狀態(tài))不會等于進程的終止狀態(tài)。
20.int atexit(void (*function)(void))
由該函數登記的function函數,會被exit(或者主線程的return/最后一個線程的return)自動調用,也就說只有在進程正常結束時才調用,線程結束不會調用,就算是在某個非主線程中用atexit設置的清理函數,也得等到整個進程結束時才調用。聲明在stdlib.h文件中。
21.void *malloc(size_t size)
???? void *calloc(size_t nmemb, size_t size)
???? void *realloc(void *ptr, size_t size)
???? void free(void *ptr)
前三個函數在堆中為進程分配空間,最后一個函數釋放這些空間。其中,malloc函數不對分配的空間進行初始化,calloc函數將分配的空間初始化為 0,realloc用來調整(變大或減小)由前兩個函數非配空間的大小,其中size參數是新存儲區(qū)的大小,不是新舊存儲區(qū)大小之差,當擴大空間時,如果原存儲區(qū)周圍的空閑空間不足,則在其他地方重新開辟空間,再將 原存儲區(qū)中的值拷貝到新空間;如果原存儲區(qū)周圍空間足夠,則在原存儲區(qū)位置上向高地址方向擴充。聲明在stdlib.h文件中。
22.char *getenv(const char *name)
該函數在進程的環(huán)境表(環(huán)境表是一個指針數組,每個元素指向了“name=value”形式的串)中查找name的環(huán)境變量值。返回一個字符串指針。聲明在stdlib.h文件中。
23.int putenv(char *string)
???? int setenv(const char *name, const char *value, int overwrite)
???? int unsetenv(const char *name)
這幾個函數用來設置進程環(huán)境表中的環(huán)境變量值。聲明在stdlib.h文件中。對于putenv函數,會將string指向的串(形式為“name=value”)放到環(huán)境表name的項中,如果name已存在則刪除name的定義。對于setenv函數,name和value值分別由兩個參數提供,如果overwrite非0,則會刪除環(huán)境表中已存在的name定義,如果overwrite為0,則不會刪除刪除環(huán)境表中已存在的name定義,也不設置新的value值,也不出錯。對于unsetenv函數,將刪除環(huán)境表中已存在的name定義,若環(huán)境表中不存在name的定義也不出錯。
在改變或者增加環(huán)境變量的時候,有如下規(guī)定:
a.修改一個現有name,如果新value長度小于或等于現有value長度,則就在原空間中寫入新字符串。
b.修改一個現有name,如果新value長度大于現有value長度,則用malloc在堆中為新value分配空間,然后將該空間地址放入環(huán)境表中原來的name元素中。
c.新增一個name,如果是第一次新增,調用malloc分配一個新的環(huán)境表,并將原來環(huán)境表中的值復制過來,將新的value串的指針放在環(huán)境表的表尾,再在其后放入一個空指針。此時環(huán)境表中的其他大多數指針仍指向了棧頂之上的各個value串。
d.新增一個name,如果這不是第一次新增,說明之前調用過malloc,則使用realloc改變環(huán)境表的大小,之后操作類似于c。
24.int setjmp(jmp_buf env)
? ? ?void longjmp(jmp_buf env, int val)
第一個函數將調用時的堆棧內容保存在env中,以供第二個函數使用。第二個函數使用env值,會跳到設置env的函數中(即setjmp),由于setjmp可以對應多個longjmp函數,因此val變量傳遞的數字會對多個longjmp函數加以區(qū)分。
《apue》上說,對于無優(yōu)化的編譯,使用longjmp函數跳回后,全局變量,靜態(tài)局部變量,自動變量,寄存器變量,volatile聲明的變量都不會回滾到以前的值。對于優(yōu)化后的編譯,自動變量和寄存器變量會回滾到以前的值。筆者未進行過測試。
25.int getrlimit(int resource, struct rlimit *rlim)
???? int setrlimit(int resource, const struct rlimit *rlim)
每個進程都有一組資源限制,這兩個函數可以獲取和設置每個資源限制。聲明在sys/resource.h文件中。resource代表資源限制的類型,struct rlimit結構體中裝有該資源的軟限制值和硬限制值。該結構體如下:
struct rlimit {
?????????????? rlim_t rlim_cur;? /* Soft limit */
?????????????? rlim_t rlim_max;? /* Hard limit (ceiling for rlim_cur) */
?????????? };
a.任何一個進程都可將一個軟限制值更改為小于或等于其硬限制值。
b.任何一個進程都可降低其硬限制值,但它必須大于或等于其軟限制值,這種降低對普通用戶而言是不可逆的。
c.只有超級用戶進程可以提高硬限制值。
?
第8章 進程控制
1.pid_t getpid(void)
? ?pid_t getppid(void) ?
?? uid_t getuid(void)
?? uid_t geteuid(void)
?? gid_t getgid(void)
?? gid_t getegid(void)
這些函數用來返回當前進程的進程ID,父進程ID,實際用戶ID,有效用戶ID,實際組ID,有效組ID。它們均無出錯返回。聲明在unistd.h文件中。注意:當前進程的保存的設置用戶ID無法返回。
2.pid_t fork(void)
該函數為調用進程創(chuàng)建一個子進程。聲明在unistd.h文件中。由該函數創(chuàng)建的子進程和其父進程使用了”寫時復制“技術。在父進程中,該函數返回一個正數;在子進程中,該函數返回0;出錯的話該函數返回-1。
子進程會繼承父進程的以下屬性或資源:
a.實際用戶ID,實際組ID,有效用戶ID,有效組ID。
b.附加組ID。? c.進程組ID。?? d.會話ID。? e.控制終端
f.設置用戶ID標志和設置組ID標志。?? g.當前共組目錄。???
h.根目錄???? i.文件模式創(chuàng)建屏蔽字。
j.信號屏蔽和安排。
k.針對任一打開文件描述符的在執(zhí)行時關閉(close-on-exec)標志。
l.環(huán)境。? m.鏈接的共享存儲段。? n.存儲映射??? o.資源限制。
p.父進程所打開的文件描述符的文件表項(父子進程共享由父進程打開的文件)。
父子進程有如下區(qū)別:
a.fork返回值不同。? b.進程ID不同。?
c.兩個進程具有不同的父進程ID。
d.子進程的tms_utime,tms_stime,tms_cutime,tms_ustime均被設置為0。
e.父進程設置的文件鎖不會被子進程繼承。
f.子進程未處理的鬧鐘(alarm)被清除。
g.子進程的未處理信號集設置為空集。
fork失敗的兩個主要原因:
a.系統中的進程數太多。
b.該實際用戶擁有的進程總數超過了系統限制。
fork有下面兩種用法:
a.父進程希望子進程復制父進程的代碼,使得父,子進程執(zhí)行不同的代碼段。網絡服務器進程一般這么用。
b.子進程執(zhí)行其他的程序。子進程的fork返回后立即調用exec。
3.pid_t vfork(void)
該函數和fork類似,也是用來為當前進程創(chuàng)建一個子進程。區(qū)別是:該函數不使用“寫時復制”技術。也就是說,該函數創(chuàng)建的子進程目的就是要用exec執(zhí)行一個新程序。子進程如果不執(zhí)行exec函數,就永遠運行在父進程的地址空間中,就算執(zhí)行寫操作,也不會復制父進程的地址空間到子進程。此外,vfork會保證子進程先運行,子進程調用exec或exit之后父進程才可能被調度。(如果在調用者兩個函數之前子進程依賴于父進程的進一步動作,則會導致死鎖)
4.pid_t wait(int *status)
?? pid_t waitpid(pid_t pid, int *status, int options)
在父進程中調用這兩個函數來等待子進程的結束并收回子進程所占資源。聲明在sys/wait.h文件中。二者區(qū)別是:wait函數等待第一個結束的子進程,如果還沒有子進程結束的話父進程會阻塞;waitpid進程則可以等待由pid指定的進程,并且option提供了若干選項,可以選擇該函數非阻塞。有若干個宏可以用來檢測這兩個函數返回的子進程狀態(tài)status,詳見man手冊。下面說下waitpid函數的pid和options參數的用法:
pid == -1 ? ? ? ? ? ? 等待任一子進程,相當于wait函數
waitpid > 0 ? ? ? ?? 等待進程ID為pid的子進程
pid == 0 ? ? ? ? ? ?? 等待進程組ID等于調用進程組ID的任一子進程
pid < -1 ? ? ? ? ? ? ? 等待進程組ID等于pid絕對值的任一子進程
options選項:
WCONTINUED????????? 若系統支持作業(yè)控制,那么由pid指定的任一子進程在暫停后已經繼續(xù),但其狀態(tài)尚未報告,返回其狀態(tài)
WNOHANG??????? ? ? ?? 若由pid指定的子進程還沒有結束,則waitpid函數不阻塞,立即返回
WUNTRACED ? ??????? 若系統支持作業(yè)控制,那么由pid指定的任一子進程處于暫停狀態(tài),并且自暫停狀態(tài)以來還未報告過,返回其狀態(tài)
5. int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options)
該函數和waitpid函數類似,也是等待指定pid的子進程返回。聲明在sys/wait.h文件中。但區(qū)別是:該函數把要等待的子進程ID和子進程所在組ID區(qū)分開了,分別表示。infop中包含了引起子進程狀態(tài)改變的信號的詳細信息。idtype提供了要等待的ID類型:
P_PID??????? 等待進程號為id的子進程
P_PGID???? 等待進程組號為id的子進程
P_ALL?????? 等待任一子進程,忽略id
options選項:
WCONTINUED???? 同上
WEXITED???????????? 等待已退出的進程
WNOHANG????????? 同上
WNOWAIT ???? 不破壞子進程的退出狀態(tài),該退出狀態(tài)可有后續(xù)的wait,waitid,waitpid調用取得
WSTOPPED ? 等待一個已暫停但是狀態(tài)還未報告的子進程
6.pid_t wait3(int *status, int options, struct rusage *rusage);
?? pid_t wait4(pid_t pid, int *status, int options, struct rusage *rusage)
這兩個函數和上面函數類似,只是多了一個功能:rusage可以收集到子進程的資源統計信息(用戶CPU時間總量,系統CPU時間總量,頁面出錯次數,接收到信號的次數等)。聲明在sys/wait.h文件中。
7.int execl(const char *path, const char *arg, ...)
?? int execlp(const char *file, const char *arg, ...)
?? int execle(const char *path, const char *arg,? ..., char * const envp[])
?? int execv(const char *path, char *const argv[])
?? int execvp(const char *file, char *const argv[])
?? int execvpe(const char *file, char *const argv[], char *const envp[])
這些函數將調用進程所執(zhí)行的程序替換為新程序,一般在子進程中使用。聲明在unistd.h文件中。它們之間的區(qū)別:函數名中包含p的,以文件名(file)為參數提供可執(zhí)行文件(若file串中包含/,將它視為路徑名;否則安PATH環(huán)境變量來找),函數名不包含p的,是以路徑名(path)為參數來提供可執(zhí)行文件;函數名中包含l的(前三個函數),命令行參數為可變參數(除了第0個參數,它已經用arg給出來了),其他函數是以指針數組形式提供命令行參數;函數名以e結尾的,最后一個參數是環(huán)境表,其他函數則沒有環(huán)境表參數。
子進程中通過exec函數執(zhí)行新程序之后,2中所列出的a-o那些屬性不會改變,外加tms_utime,tms_stime,tms_cutime,tms_ustime這些值也不會改變,還要注意實際用戶ID和實際組ID是否改變根據新可執(zhí)行文件的設置用戶ID位和設置組ID位是否設置而定;p的話,要根據所打開文件描述符是否設置FD_CLOEXEC標志(執(zhí)行時關閉close-on-exec)有關,若設置了就關閉,否則不關閉。
8.int setuid(uid_t uid)
?? int setgid(gid_t gid)
這兩個函數分別用來設置當前進程的實際用戶ID/有效用戶ID和實際組ID/有效組ID。聲明在unistd.h文件中。有如下設置規(guī)則:
a.如果進程具有超級用戶權限,可將進程的實際用戶ID,有效用戶ID,保存的設置用戶ID設置為uid。
b.如果進程沒有超級用戶權限,但是uid等于進程的實際用戶ID或保存的設置用戶ID,則只將有效用戶ID設置為uid,不改變實際用戶ID和保存的設置用戶ID。
c.如果a和b都不滿足,則將error設置為EPERM,并返回-1。
還要注意以下三點:
a.只有超級用戶進程可以更改實際用戶ID。
b.僅當對程序文件設置了設置用戶ID位時,exec函數才會設置有效用戶ID為程序文件的擁護者ID。否則exec函數不改變有效用戶ID,維持原先值。上邊的b表明了任何時候都可調用setuid將有效用戶ID設置為實際用戶ID或保存的設置用戶ID。
c.保存的設置用戶ID是有exec復制有效用戶ID而得來的。
9.int setreuid(uid_t ruid, uid_t euid)
?? int setregid(gid_t rgid, gid_t egid)
這兩個函數用來交換實際用戶ID和有效用戶ID,實際組ID和有效組ID。聲明在unistd.h文件中。任一參數值為-1,則相應的ID應當保持不變。
10.int seteuid(uid_t euid)
???? int setegid(gid_t egid)
這兩個函數類似于8中的函數,但是只改變當前進程的有效用戶ID,無論是當前是特權用戶還是非特權用戶。聲明在unistd.h文件中。特權用戶進程可將有效用戶ID設置為euid,非特權用戶進程可將有效用戶ID設置為實際用戶ID或保存的設置用戶ID。
注意:上述所有的組ID設置和用戶ID設置類似,不再贅述。附加組ID不受這些函數影響。
?11.解釋器文件:第一行包含“#!pathname”的文件就是解釋器文件。shell會創(chuàng)建一個子進程來執(zhí)行pathname所指示的命令,然后該命令再去解釋執(zhí)行解釋器文件。解釋器文件不一定非得是shell腳本,也可能是其他腳本(比如awk程序腳本)。每一種類型的解釋器文件都會有自己的命令解釋器。內核在執(zhí)行pathname命令時,傳遞給該命令的參數如下:argv[0]:pathname路徑;argv[1]:解釋器文件#!pathname后邊跟的參數;argv[2]:exec的第0個參數(解釋器文件的絕對路徑);argv[3]及以后的參數:exec的第2個及以后的參數。
eg:execl(“home/sar/bin/testinterp”,? "testinterp", "myarg1", "MY ARG2", (char *)0);
其中home/sar/bin/testinterp是解釋器文件。查看該解釋器內容:cat home/sar/bin/testinterp ===>#!/home/sar/bin/echoarg foo。該解釋器文件第一行包含了解釋器命令為/home/sar/bin/echoarg,foo為該命令的參數。execl將執(zhí)行/home/sar/bin/echoarg程序,將“/home/sar/bin/echoarg”,“foo”,“home/sar/bin/testinterp”,"myarg1","MY ARG2"作為參數傳遞進去。
12.int system(const char *command)
該函數用來執(zhí)行一個shell命令。聲明在stdlib.h文件中。在該函數內部調用了fork,exec,waitpid。command字符串中包含了命令以及參數等(包括由管道連接的多個命令,以及輸入輸出重定向),shell會對其進行語法分析,將其分成命令行參數。一般情況下,fork和exec之間會有安全漏洞問題,需要在fork之后,exec之前將子進程的有效用戶改為普通用戶,然后再讓子進程執(zhí)行exec。該函數中fork出的子進程去執(zhí)行/bin/sh程序,然后shell子進程再創(chuàng)建子進程去執(zhí)行command。由于該函數中調用了fork,exec和waitpid,因此該函數的返回值較為特殊,需要注意,有三種返回值:
a.如果fork失敗或者waitpid返回除EINTR之外的出錯,則system返回-1,并且errno中設置了錯誤類型。
b.如果exec失敗(表示不能執(zhí)行shell),則system返回值如同shell執(zhí)行了exit(127)一樣。
c.否則三個函數都執(zhí)行成功,則system返回值是shell的終止狀態(tài)。
注:該函數對信號的處理有要求。該函數在fork子進程前,會先屏蔽掉SIG_INT和SIGQUIT信號,同時屏蔽掉調用進程的SIGCHLD信號(防止用戶在該信號處理函數中調用了wait等函數回收子進程,這樣system函數中的wait函數就獲取不到子進程的終止狀態(tài)),函數返回時會恢復一切。
?
第10章 信號
1.typedef void (*sighandler_t)(int);
? ?sighandler_t signal(int signum, sighandler_t handler)
該函數用來設置進程的信號處理函數。一般不推薦使用該函數,而應該使用sigaction函數替代它。聲明在signal.h文件中。signum參數是要設置的信號名,handler參數是一個函數指針變量,該變量也可以取以下三種值值:SIG_IGN,SIG_DFL,信號處理函數的地址。第一個表示忽略此信號,第二個表示采用系統的默認動作處理此信號,第三個表示此信號發(fā)生時,調用該處理函數來處理此信號。函數返回值是前一個信號處理函數的指針。一般情況下,執(zhí)行一個程序時,如果沒有設置信號的處理函數,則系統會把信號設置為默認的處理動作。當進程執(zhí)行exec函數后,也會把之前設置的信號處理動作更改為信號的默認處理動作。當進程創(chuàng)建了一個子進程時,子進程會繼承父進程的信號處理方式。
注:信號的三種處理方式:忽略,捕獲,默認方式(默認方式一般是終止接收進程)。
2.int kill(pid_t pid, int sig)
?? int raise(int sig)
第一個函數將新號發(fā)送給進程或者進程組。第二個函數向本進程發(fā)送信號。聲明在signal.h文件中。第二個函數等價為kill(getpid(),sig)。kill函數的pid有以下四種情況:
a.pid > 0????????? 將該信號發(fā)送給進程ID為pid的進程
b.pid == 0??????? 將該信號發(fā)送給和發(fā)送進程屬于同一個進程組的所有進程(不包括系統進程),而且發(fā)送進程具有向這些進程發(fā)送信號的權限。
c.pid < 0????????? 將信號發(fā)送給進程組ID等于|pid|的所有進程(不包括系統進程)。并且也要具有發(fā)送權限,類似于b
d.pid == -1?????? 將信號發(fā)送給系統上的所有進程(不包括系統進程)。并且也要具有發(fā)送權限,類似于b和c
上邊提到的發(fā)送權限是這樣的:超級用戶可將信號發(fā)送給任意進程;非超級用戶,是否有權限的依據是發(fā)送者的實際或者有效用戶ID是否等于接收者的實際或者有效用戶ID,相等的話就具有權限,否則不具有權限。另外,普通進程可以將SIGCONT信號發(fā)送給同一個會話中的所有進程,包括系統進程。
如果kill的sig參數為0,用來測試pid號進程是否存在,不存在的話kill返回-1并設置errno為ESRCH,這種測試一般沒有什么價值。另外,kill函數向本進程發(fā)送信號時,會在函數返回前將信號傳遞至該進程。
3.unsigned int alarm(unsigned int seconds)
該函數設置一個鬧鐘(計時器),當鬧鐘時間到之后會投遞一個SIGALRM信號至調用進程。該信號的默認處理是終止調用進程。每個進程只能有一個鬧鐘時間,如果調用alarm函數設置了一個鬧鐘時間,在該時間到之前又調用alarm函數設置了一個鬧鐘時間,則第二次調用alarm函數返回值為第一次alarm調用設置時間的剩余值,然后按照第二次設置的時間重新計時;如果第二次alarm調用參數為0,則取消鬧鐘時間,并將第一次alarm調用設置時間的剩余值返回。聲明在unistd.h文件中。
4.int pause(void)
該函數將進程掛起直到捕捉到一個信號。聲明在unistd.h文件中。當該掛起進程接收到一個信號,執(zhí)行完信號處理函數并從其返回時,pause函數才返回,返回值為-1,并將errno設置為EINTR。
5.int sigemptyset(sigset_t *set)
?? int sigfillset(sigset_t *set)
?? int sigaddset(sigset_t *set, int signum)
?? int sigdelset(sigset_t *set, int signum)
?? int sigismember(const sigset_t *set, int signum)
這五個函數用來初始化信號集。聲明在signal.h文件中。sigemptyset函數會清空set所指向的信號集。sigfillset函數會將所有信號放入set指向的信號集中。所有信號集在使用之前,都要調用這兩個函數的其中之一來初始化信號集。一旦初始化了一個信號集,就可使用其余的函數在信號集中增加或刪除特定的信號。
如果實現的信號數目少于一個整型所包含的位數,則可以用整型變量作為信號集,整型的每一位代表一個信號。此時,sigemptyset函數將整型量設置為0,sigfillset函數將整型量的各個位都設置為1。這兩個函數被實現為宏函數:
#define? sigemptyset(ptr)??? {*(ptr) = 0}
#define? sigfillset(ptr)????????? {*(ptr) = ~(sigset_t)0, 0}
可看出,這兩個宏函數返回值均為0。
sigaddset函數打開一位(將該位置為1),sigdelset函數關閉一位(將該位置為0),sigismember函數測試一個指定位。
6.int sigprocmask(int how, const sigset_t *set, sigset_t *oldset)
該函數設置進程的信號屏蔽字。聲明在signal.h文件中。如果oldset參數非空,則進程的當前信號屏蔽字通過oldset返回;如果set參數非空,這時how指定當前信號屏蔽字的修改方法。how的取值如下:
SIG_BLOCK????????????? 該進程新的信號屏蔽字是其當前的信號屏蔽字和set指向信號集的并集。set包含了我們希望阻塞的附加信號。
SIG_UNBLOCK???????? 該進程新的信號屏蔽字是其當前的信號屏蔽字和set指向信號集的交集。set包含了我們希望解除阻塞的信號。
SIG_SETMASK????????? 該進程新的信號屏蔽字將被set指向的信號集所替代。
如果set為空,則不改變該進程的信號屏蔽字,how值也無意義。另外,調用sigprocmask函數后如果有任何沒被阻塞的未決信號,會在該函數返回前被遞送給該進程。
注意:sigprocmask函數僅能在單線程的進程中使用。多線程中需要使用pthread_sigmask函數。
7.int sigpending(sigset_t *set)
該函數用來查看當前進程的未決信號(實際上就是被阻塞的信號)集。該信號集通過set參數返回。聲明在signal.h文件中。
8. int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)
該函數類似于signal函數,用來查看或者設置信號的處理函數。聲明在signal.h文件中。一般推薦使用該函數,去替代signal函數。參數signum是要查看或者修改的信號編號,若參數act非空,則要修改信號處理動作,若參數oldact非空,返回該信號的上一個處理動作。struct sigaction結構體如下:
struct sigaction {
?????????????? void???? (*sa_handler)(int);
?????????????? void???? (*sa_sigaction)(int, siginfo_t *, void *);
?????????????? sigset_t?? sa_mask;
?????????????? int??????? sa_flags;
?????????????? void???? (*sa_restorer)(void);
?????????? };
sa_handler字段指向了信號處理函數的地址;sa_mask字段指定了一個信號集,調用上述信號處理函數之前會屏蔽掉該信號集,從信號處理函數中返回時恢復信號屏蔽字(這樣就保證了在處理一個給定信號時,若這種信號再次發(fā)生,它將被阻塞到對前一個信號的處理結束為止,如果同一種信號發(fā)生多次,通常部將它們排隊,也就是說當解除信號阻塞后,該信號只會被投遞一次)。sa_flags字段指定對信號進行處理的各個選項:
SA_INTERRUPT??????? 由此信號中斷的系統調用不會自動重啟。
SA_NOCLDSTOP?????? 若signum參數是SIGCHLD,當子進程暫停(作業(yè)控制)時或者由暫停繼續(xù)運行時,不產生此信號;當子進程終止時產生此信號。
SA_NOCLDWAIT??????? 若signum參數是SIGCHLD,則當調用進程的子進程終止時,不產生將死進程;若調用進程依然wait子進程,則調用進程阻塞知道所有子進程都終止,才 ? ? ? ? ? ? ? ? ? ? 會返回,返回值為-1,并將errno設置為ECHILD。
SA_NODEFER??????????? 當捕捉到該信號并執(zhí)行其信號處理函數時,系統不自動阻塞此信號(除非sa_mask中包括了此信號)。此種類型操作對應于早期的不可靠信號。
SA_ONSTACK??????????? 若用sigaltstack函數聲明了一個替換棧,則將此信號遞送給替換棧上的進程。
SA_RESETHAND??????? 在此信號函數的入口處,將此信號的處理方式復位為SIG_DFL,并清除SA_SIGINFO標志。此種類型操作對應于早期的不可靠信號。此標志不能自動復 位SIGILL和SIGTRAP這個信號。設置了此標志也會有設置SA_NODEFER標志后的效果。
SA_RESTART???????????? 由此信號中斷的系統調用會自動重啟動。
SA_SIGINFO?????????????? 此選項會用sa_sigaction指向的信號處理函數來處理此信號,而不用sa_handler所指向的信號處理函數。
sa_sigaction指針指向的信號處理函數類型為:void? * function(int, siginfo_t *, void *)。siginfo_t結構體類型如下:
siginfo_t該結構體將包含信號產生原因的有關信息。
9.int sigsetjmp(sigjmp_buf env, int savesigs)
?? void siglongjmp(sigjmp_buf env, int val)
如果要在信號處理函數中實現遠跳轉,應該使用這兩個函數進行堆棧保存和恢復,而避免使用setjmp和longjmp。因為執(zhí)行信號處理程序時系統自動設置當前信號的屏蔽字,信號處理程序結束時系統自動恢復屏蔽字,而setjmp和longjmp函數不對屏蔽字進行任何操作,因此使用longjmp遠跳轉之后,當前信號的屏蔽字就無法恢復了(以后無法處理當前信號了)。因此需要使用9中這兩個函數。sigsetjmp函數比setjmp多出了一個savesigs參數,該參數非0時,該函數會保存當前進程的信號屏蔽字,然后siglongjmp函數會將保存的信號屏蔽字恢復,這樣當從信號處理函數中遠跳轉后,進程的信號屏蔽字仍然會恢復到以前的狀態(tài)。聲明在setjmp.h文件中。
10.int sigsuspend(const sigset_t *mask)
進程中調用該函數用來等待一個信號。聲明在signal.h文件中。mask參數用來設置進程的信號屏蔽字,也就是說,該函數將等待除了被屏蔽掉的信號以外的任意一個信號到來,在這一信號到來之前當前進程處于阻塞狀態(tài)。信號到來之后并執(zhí)行完信號處理函數后,才從該函數返回。該函數返回時,會自動將信號屏蔽字恢復為該函數被調用之前的狀態(tài)。該函數的返回值總是-1,并將errno設置為EINTR,表示一個被中斷的系統調用。
11.void abort(void)
該函數發(fā)送SIGABRT信號至當前進程,使當前進程非正常終止。無論程序中在調用abort之前,對SIGABRT信號的處理進行何種設置,abort都會終止該進程(abort內部會重新將SIGABRT信號的處理設置為默認方式)。聲明在stdlib.h文件中。
12.unsigned int sleep(unsigned int seconds)
該函數使調用線程睡眠seconds秒。該函數一般使用alarm實現,因此該函數和用戶調用的alarm的之間有交互作用,需要注意。該函數是調用線程睡眠,并不是整個進程睡眠,當然,如果進程只有一個主線程,那么主線程睡眠就相當于進程睡眠。
注意:信號這一章節(jié)有個重要的知識,就是中斷的系統調用。一般情況下,系統調用將進程阻塞后,如果進程收到了任意可以處理的信號,就會激活進程,若設置了該信號的處理函數,則去執(zhí)行該處理函數并且返回后也會從該引起進程阻塞的系統調用處返回。也有一部分系統調用會重新啟動。
13.與作業(yè)控制有關的信號:
SIGCHLD???????? 子進程已暫停或終止
SIGCONT???????? 使暫停的進程繼續(xù)運行
SIGSTOP??????? ? 使進程暫停(不能捕獲或忽略)
SIGTSTP?????????? 交互式暫停信號(用終端命令使進程暫停,比如ctrl+z)
SIGTTIN??????????? 后臺進程組成員讀控制終端
SIGTTOU????????? 后臺進程組成員讀控制終端
除了SIGCHLD以外,其它信號應用程序不去處理。當按ctrl+z時,SIGTSTP信號被送至前臺進程組的所有進程,使之暫停(并轉入后臺)。在命令行下使用fg %n或bg %n時,SIGCONT信號被發(fā)給相應進程,使之轉向前臺繼續(xù)運行或者在后臺繼續(xù)運行。對一個進程產生四種停止信號(SIGTSTP,SIGSTOP,STGTTIN,SIGTTOU)中的任意一種時,對該進程的任一未決SIGCONT信號就會被丟棄。類似,當對一個進程產生SIGCONT信號時,對同一進程的任一未決的停止信號將被丟棄。當對一個暫停的進程產生SIGCONT信號時,無論SIGCONT信號是被阻塞或是忽略,進程都將繼續(xù)運行。
14. void psignal(int sig, const char *s)
該函數將字符串s輸出到標準出錯文件,后接一個冒號和空格,再接著是對sig信號的說明,最后是換行符。聲明在signal.h文件中。類似于perror函數。
15.char *strsignal(int sig)
給出一個信號sig,函數會返回說明該信號的字符串。應用程序可用該字符串打印關于接收到信號的出錯消息。聲明在string.h文件中。類似于strerror函數。
?
第11章 線程概念
1.int pthread_equal(pthread_t t1, pthread_t t2)
該函數用來比較兩個線程IDti和t2是否相等。相等返回非0值,不相等返回0。聲明在pthread.h文件中。由于每個平臺的pthread_t類型不一定相同,因此不能直接比較,只能通過該函數比較。
2.pthread_t pthread_self(void)
該函數獲取當前線程的線程ID。聲明在pthread.h文件中。1,2函數一般可以配合來使用。
3.int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg)
該函數用來創(chuàng)建一個新線程。聲明在pthread.h文件中。thread中包含了新線程的線程ID。attr結構可以為新線程設置線程屬性。start_routine是線程的啟動例程(線程要執(zhí)行的函數)。arg為線程傳遞參數,一般是結構類型指針。線程創(chuàng)建后并不保證哪個線程先運行,所以不能做任何假設。新線程繼承了調用線程的浮點環(huán)境和信號屏蔽字,新線程的未決信號集被清除。并且每個線程都有errno的副本,當線程出錯后,錯誤碼會保存在各自的errno中。新創(chuàng)建的線程一般通過2中函數來獲取自己的線程ID,而不能使用thread參數來獲取,因為新線程可能在創(chuàng)建函數返回前就運行了,而此時也許thread還未設置成線程ID。其他平臺同一個進程中的多個線程,它們的進程號是相同的,線程號是不同的;但是Linux系統,由于它的線程和進程實現機制是一樣的,都用clone系統調用來完成創(chuàng)建,因此Linux系統中的線程實際上是其創(chuàng)建進程的子進程,該子進程可以共享父進程一定數量的執(zhí)行環(huán)境。
4.void pthread_exit(void *retval)
該函數用來結束調用線程。聲明在pthread.h文件中。參數retval是線程的返回碼。其他線程可以通過pthread_join函數獲得該返回碼。線程的三種退出方式如下:
a.線程從啟動例程中返回,返回值是線程的退出碼
b.線程可以被同一進程中的其他線程取消
c.線程調用pthread_exit返回
5.int pthread_join(pthread_t thread, void **retval)
該函數等待一個線程ID號為thread的線程結束。類似于waitpid。該函數會一直阻塞直到等待的線程調用pthread_exit,或從啟動例程返回,或者被其他線程取消。聲明在pthread.h文件中。該函數可以獲得4中函數的返回碼,保存到retval指向的單元,如果對等待線程的返回碼不感興趣,可將retval置為NULL,則該函數不會獲取等待線程的返回碼。如果等待線程是被其他線程取消的,則retval指向的內存單元被置為PTHREAD_CANCELED。
4,5兩個函數的retval參數可以傳遞結構體指針,來包含更多的數值。該結構體一般要用全局結構或者是malloc分配的單元。
6.int pthread_cancel(pthread_t thread)
線程通過調用該函數來取消同一進程中的其他線程。聲明在pthread.h文件中。默認情況下,被取消的線程會表現得跟調用了pthread_exit(PTHREAD_CANCELED)函數一樣。線程可以選擇忽略取消方式或者控制取消方式。此外,pthread_cancel并不等待線程終止,它僅提出請求。
7.void pthread_cleanup_push(void (*routine)(void *)
?? void pthread_cleanup_pop(int execute)
第一個函數用來注冊線程結束時要執(zhí)行的清理函數。類似于進程中的atexit函數。清理函數的執(zhí)行順序與注冊順序相反。這兩個函數需要配對使用,調用了幾次pthread_cleanup_push函數,也需要對應的調用幾次pthread_cleanup_pop函數。它們均聲明在pthread.h文件中。當線程執(zhí)行以下動作時會調用清理函數:
a.調用pthread_exit時
b.響應取消請求時
c.用非0的execute參數賴掉用pthread_cleanup_pop時
當使用參數execute=0來調用pthread_cleanup_pop函數后,無論在上邊那種情況下,清理函數都不會被調用,而會被清理掉。此外,還要注意,當線程從它的啟動例程中返回的話(比如使用return),即使注冊了清理函數,清理函數也不會被調用。
8.int pthread_detach(pthread_t thread)
該函數用來分離一個線程。聲明在pthread.h文件中。一個線程被分離后,它的底層存儲資源在線程終止時立即被收回。而不用等待其他線程調用pthread_join函數或者整個進程結束。注意,一個線程分離后,不能再調用pthread_join函數來等待該分離線程。
9.線程同步
a.互斥量
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr)
int pthread_mutex_destroy(pthread_mutex_t *mutex)
這兩個函數用來初始化互斥量(動態(tài)初始化)和清理互斥量資源。聲明在pthread.h文件中。如果想靜態(tài)初始化互斥量,定義一個全局的pthread_mutex_t 類型變量,并給其賦值為PTHREAD_MUTEX_INITIALIZER。如果互斥量單元是用malloc等進行分配的,那么在釋放內存之前需要調用pthread_mutex_destroy對資源進行清理。用默認屬性初始化互斥量時只需把attr置為NULL。
int pthread_mutex_lock(pthread_mutex_t *mutex)
int pthread_mutex_trylock(pthread_mutex_t *mutex)
int pthread_mutex_unlock(pthread_mutex_t *mutex)
使用第一個函數對互斥量進行加鎖,使用第三個函數對互斥量解鎖。它們均聲明在pthread.h文件中。如果互斥量已經上鎖,那么調用進程將阻塞直到互斥量被解鎖。第二個函數也是用來對互斥量進行加鎖,如果互斥量已經上鎖,它不會阻塞進程,返回EBUSY,如果成功加鎖則返回0。
b.讀寫鎖
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr)
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock)
這兩個函數用來初始化讀寫鎖和清理讀寫鎖。它們和a中的函數類似。聲明在pthread.h文件中。如果希望讀寫鎖有默認屬性,將attr置為NULL。在釋放動態(tài)分配的內存前,需要先調用第二個函數釋放資源。
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock)
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock)
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock)
這幾個函數分別在讀模式下或者寫模式下鎖定讀寫鎖,無論哪種模式下鎖定讀寫鎖,都可用第三個函數來解鎖。它們均聲明在pthread.h文件中。讀寫鎖實現的時候可能會對共享模式下可獲取的鎖數量進行限制,所以需要檢查pthread_rwlock_rdlock函數返回值。
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock)
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock)
這兩個函數也類似于a中的函數,獲得鎖時返回0,鎖被占時返回EBUSY,也不會阻塞線程。聲明在pthread.h文件中。
c.條件變量
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr)?
int pthread_cond_destroy(pthread_cond_t *cond)
這兩個函數和上邊的函數是類似的。聲明在pthread.h文件中。第一個函數用來對條件變量進行初始化。第二個函數用來對條件變量進行資源清理。如果條件變量是由malloc等手動分配的,那么在釋放該空間前,要先調用第二個函數進行資源清理。如果使用默認屬性進行初始化,attr傳遞NULL即可。
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex)
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime)
這兩個函數用來等待一個條件變量cond為真。聲明在pthread.h文件中。如果cond不為真,會將調用線程放到等待條件的線程列表上,并阻塞當前線程,釋放互斥鎖mutex。第二個函數僅僅等待abstime時長,若cond還不為真則返回錯誤碼,同時獲取互斥鎖。這兩個函數的條件變量cond均受到互斥鎖mutex的保護,當函數將線程放入等待條件列表上后,會釋放該互斥鎖。該互斥鎖mutex將會保護兩個資源:一個是消息隊列,另一個是條件變量cond。具體的使用例子參考《apue》。第二個函數的struct timespec參數類型如下:
struct timespec {
time_t?????? tv_sec,???????? /*seconds*/
long????????? tm_nsec;???? /*nanoseconds*/
};
第二個函數的等待時間是個絕對數而不是相對數,也就是說,如果需要等待3分鐘,就要在當前時間上加3分鐘然后轉換到struct timespec結構中。
int pthread_cond_signal(pthread_cond_t *cond)
int pthread_cond_broadcast(pthread_cond_t *cond)
這兩個函數用于通知線程條件已經滿足。第一個函數將喚醒等待該條件的某個線程,第二個函數會喚醒等待該條件的所有線程。
?
第12章 線程控制
1.int pthread_attr_init(pthread_attr_t *attr)
?? int pthread_attr_destroy(pthread_attr_t *attr)
這兩個函數用于對線程的屬性結構體進行初始化和清理。聲明在pthread.h文件中。Linux系統支持的線程屬性有下邊四個:
detachstate?????? 線程的分離屬性
guardsize????????? 線程棧末尾的警戒緩沖區(qū)大小(字節(jié)數)
stackaddr????????? 線程棧的最低地址
stacksize?????????? 線程棧的大小(字節(jié)數)
可取消狀態(tài)
可取消類型
并發(fā)度
2.int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate)
?? int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)
這兩個函數用來獲取和設置線程的分離屬性。聲明在pthread.h文件中。detachstate參數代表了屬性值,有兩個:PTHREAD_CREATE_DETACHED(以分離狀態(tài)啟動線程),PTHREAD_CREATE_JOINABLE(正常啟動線程)。如果一開始就知道不需要獲取線程的返回碼,那就可以分離屬性來創(chuàng)建線程,這樣線程就會以分離狀態(tài)來啟動,線程結束時系統就會收回線程資源。
3.int pthread_attr_getstack(pthread_attr_t *attr, void **stackaddr, size_t *stacksize)
?? int pthread_attr_setstack(pthread_attr_t *attr,? void *stackaddr, size_t stacksize)
這兩個函數用來獲取和設置線程的線程棧地址屬性和線程棧大小屬性。聲明在pthread.h文件中。一個進程中的所有線程共用該進程的棧地址空間,當線程數太多時,可能會消耗完進程的棧地址空間,于是就需要使用malloc或者mmap為線程棧重新分配空間。分配好之后需要用第二個函數來設置棧。stackaddr是線程棧的最低地址,因此它可能是線程的開始或者結尾(棧從高地址向低地址方向伸展)。
4.int pthread_attr_getstacksize(pthread_attr_t *attr, size_t *stacksize)
?? int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize)
這兩個函數用來專門獲取和設置線程棧的大小屬性。聲明在pthread.h文件中。如果希望改變線程棧的默認大小,但又不想自己處理棧的分配問題,就可以用該函數。
5.int pthread_attr_getguardsize(pthread_attr_t *attr, size_t *guardsize)
?? int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize)
這兩個函數用來獲取和設置線程棧的警戒緩沖區(qū)屬性。聲明在pthread.h文件中。參數guardsize控制著線程棧末尾之后用意避免棧溢出的擴展內存(警戒緩沖區(qū))的大小。該緩沖區(qū)默認大小是PAGESIZE字節(jié)。當把guardsize置為0時,則不為線程棧提供警戒緩沖區(qū)。另外,如果對線程屬性stackaddr做了修改,也會使線程棧緩沖區(qū)機制無效,等同于把guardsize置為0。當線程的棧指針溢出到警戒區(qū)域,應用程序就會收到出錯信號。
6.int pthread_getconcurrency(void)
?? int pthread_setconcurrency(int new_level)
這兩個函數用來獲取和設置線程的并發(fā)度。聲明在pthread.h文件中。如果系統當前正控制著并發(fā)度,那么第一個函數會返回0。用第二個設置函數設置并發(fā)度時,只是對系統做一個提示,系統并不保證一定采用new_level參數指定的并發(fā)度。使用new_level參數為0來調用設置函數,會撤消在這之前用非0的new_level參數設置的并發(fā)度。
7.同步屬性
a.互斥量屬性
int pthread_mutexattr_init(pthread_mutexattr_t *attr)
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr)
這兩個函數用來初始化和清理互斥量的屬性。聲明在pthread.h文件中。第一個函數會用默認的互斥量屬性初始化pthread_mutexattr_t結構。有兩個互斥量屬性值得注意:互斥量共享屬性和類型屬性。
int pthread_mutexattr_getpshared(const pthread_mutexattr_t * restrict attr, int *restrict pshared)
int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared)
這兩個函數用來獲取和設置互斥量的共享屬性。在一個進程中,多個線程默認可以訪問同一個同步對象(指互斥量,讀寫變量,條件變量),因此線程共用的同步對象屬性需要設置為PTHREAD_PROCESS_PRIVATE,這也是默認的屬性值;而多個進程可以把同一個內存區(qū)映射到它們各自獨立的地址空間中,多個進程訪問同一個內存區(qū)的數據時也需要同步,那么在共享內存區(qū)域中分配的互斥量就可用于這些進程的同步,這時需要將互斥量的共享屬性設置為PTHREAD_PROCESS_SHARED。
int pthread_mutexattr_gettype(const pthread_mutexattr_t *restrict attr, int *restrict type)
int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type)
這兩個函數用來獲取和設置互斥量的類型屬性。互斥量類型屬性值如下:
PTHREAD_MUTEX_NORMAL ? ? ? ? ? ? ? ? ? ? 遞歸加鎖:死鎖 ? ? ? ? 解別人的鎖:未定義 ? ? ? 已解鎖時解鎖:未定義 ?????????
PTHREAD_MUTEX_ERRORCHECK?????????? 遞歸加鎖:返回錯誤? 解別人的鎖:返回錯誤?? 已解鎖時解鎖:返回錯誤???
PTHREAD_MUTEX_RECURSIVE??????????????? 遞歸加鎖:允許 ? ? ??? 解別人的鎖:返回錯誤?? 已解鎖時解鎖:返回錯誤
PTHREAD_MUTEX_DEFAULT???????????????????? 遞歸加鎖:未定義?? ? 解別人的鎖:未定義 ? ? ? 已解鎖時解鎖:未定義
在一個函數的加鎖,解鎖調用之間需要調用另外的函數,而另外的函數內部也要獲取同一個鎖,這時就需要設置遞歸鎖屬性,否則就會死鎖。此外,使用遞歸鎖時,需要一定的編程技巧。
b.讀寫鎖屬性
int pthread_rwlockattr_init(pthread_rwlockattr_t *attr)
int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr)
這兩個函數用來初始化和清理讀寫鎖的屬性。聲明在pthread.h文件中。類似于互斥量的該函數。
int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *? restrict attr, int *restrict pshared)
int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int pshared)
這兩個函數用來獲取和設置讀寫鎖的共享屬性。聲明在pthread.h文件中。類似于互斥量的該函數。讀寫鎖僅支持共享屬性,沒有類型屬性。
c.條件變量屬性
int pthread_condattr_init(pthread_condattr_t *attr)
int pthread_condattr_destroy(pthread_condattr_t *attr)
這兩個函數用來初始化和清理條件變量的屬性。聲明在pthread.h文件中。類似于互斥量和讀寫鎖的該函數。
int pthread_condattr_getpshared(const pthread_condattr_t *restrict attr, int *restrict pshared)
int pthread_condattr_setpshared(pthread_condattr_t *attr, int pshared)
這兩個函數用來獲取和設置條件變量的共享屬性。聲明在pthread.h文件中。類似于互斥量和讀寫鎖的該函數。讀寫鎖僅支持共享屬性,沒有類型屬性。
8.void flockfile(FILE *filehandle)
?? int ftrylockfile(FILE *filehandle)
?? void funlockfile(FILE *filehandle)
這幾個函數用來為FILE對象加鎖和解鎖。聲明在stdio.h文件中。其中第二個函數不會阻塞當前線程。標準IO函數都是線程可重入的函數(所謂一個函數是可重入的,指的是該函數可以被并發(fā)的調用),因為它們會對標準IO流FILE進行加鎖訪問。并不是說標準IO函數內部調用了這幾個函數,而是表現出來的特性就像是調用了這幾個函數。這幾個函數主要是留給應用程序來調用,對沒有加鎖的FILE流進行加鎖保護。、
注:可重入函數一般分為對線程可重入(線程安全)或者對信號處理程序可重入(異步-信號安全),前者的話就是上邊的定義,后者的話指的是在該函數中發(fā)生信號后函數仍然是安全的(執(zhí)行完信號處理函數,返回到該函數中)。
9.int getc_unlocked(FILE *stream)
?? int getchar_unlocked(void)
?? int putc_unlocked(int c, FILE *stream)
?? int putchar_unlocked(int c)
這幾個函數是標準IO函數的不加鎖版本。聲明在stdio.h文件中。在使用這幾個函數的時候就需要用到8中的函數。
10.int pthread_key_create(pthread_key_t *key, void (*destructor)(void*))
在分配線程的私有數據之前,需要調用該函數創(chuàng)建與該數據關聯的鍵。這個鍵用于獲取對線程私有數據的訪問權。聲明在pthread.h文件中。該函數的一般用法:在主線程中調用完成鍵的創(chuàng)建,然后在其他線程中使用該鍵去綁定私有數據即可(因此鍵的變量要定義為全局變量)。創(chuàng)建好的鍵存放在key所指向的內存單元中,這個鍵可以被進程中的所有線程使用,而每個線程把這個鍵與不同的線程私有數據地址進行關聯。創(chuàng)建新鍵時,每個線程的數據地址設為NULL。destructor參數用來為該鍵關聯析構函數,當線程正常退出時,如果傳遞的destructor參數非NULL,就會調用該析構函數。而當線程調用exit,_exit,_Exit函數或者以非正常方式退出時,就不會調用該析構函數。通常會使用mallocl為線程私有數據分配內存空間,在析構函數中釋放這些空間,否則,就可能使線程所屬的進程出現內存泄漏。線程可以為私有數據分配多個鍵,每個鍵的析構函數可以相同也可以不同。當所有析構函數都調用完后,系統會檢查是否還有非NULL的線程私有數據與鍵關聯,有的話繼續(xù)執(zhí)行相應析構函數,直到所有私有數據為NULL。
11.int pthread_key_delete(pthread_key_t key)
該函數用來取消鍵與私有數據值之間的關聯關系。聲明在pthread.h文件中。該函數在釋放鍵時,并不會激活鍵的析構函數,因此需要在程序中手動釋放由malloc函數分配的私有數據空間。
12.pthread_once_t once_control = PTHREAD_ONCE_INIT;
???? int pthread_once(pthread_once_t *once_control, void (*init_routine)(void))
當多個線程中同時調用10中函數創(chuàng)建鍵的時候,很可能會發(fā)生沖突(其實鍵只需要被創(chuàng)建一次就行)。該函數用來避免此沖突。因為該函數只要在線程中被調用一次后,init_routine參數指向的函數將只會被調用一次,因此在多個線程中都調用一次pthread_once函數,系統就能保證init_routine函數只會被調用一次,然后再在init_routine函數中調用10中的函數來完成鍵的創(chuàng)建,這樣多個線程之間就不會發(fā)生沖突。另外,該函數的once_control參數必須是全局或者靜態(tài)變量,而且被初始化為PTHREAD_ONCE_INIT。具體例子可以參考《apue》。
13.void *pthread_getspecific(pthread_key_t key)
???? int pthread_setspecific(pthread_key_t key, const void *value)
這兩個函數用來用來獲取和設置key鍵所對應的線程私有數據。聲明在pthread.h文件中。如果key還沒有對應的私有數據,第一個函數返回NULL,否則返回私有數據空間地址,第二個函數可以根據第一個函數的返回值進行判斷,進而選擇是否要為鍵設置私有數據空間。另外,只有鍵所對應的私有數據非空時,析構函數才會被調用。
14.int pthread_setcancelstate(int state, int *oldstate)
該函數用來設置當前線程的可取消狀態(tài)。聲明在pthread.h文件中。該函數把當前的可取消狀態(tài)設置為state,把原來的可取消狀態(tài)保存到oldstate指向的單元中。pthread_cancel調用并不等待線程終止,該調用執(zhí)行后,被取消線程繼續(xù)運行,當運行到某個取消點時,才被檢查自己是否要被取消。《apue》書中列舉了POSIX.1所定義的取消點函數。當線程長時間都執(zhí)行不到這些取消點函數時,可以調用15中的函數,它是專門的取消點函數。線程的可取消狀態(tài)state有兩個值:PTHREAD_CANCEL_ENABLE和PTHREAD_CANCEL_DISABLE,線程啟動時默認取值是前者。如果線程的可取消狀態(tài)值設為前者,則線程在取消點可以被取消;否則設為后者的話,在取消點也不會殺死線程,這時,取消請求對該線程而言處于未決狀態(tài),當該線程的可取消狀態(tài)再次被設為PTHREAD_CANCEL_ENABLE時,該線程將在下一個取消點上對所有的未決請求進行處理。
15.void pthread_testcancel(void)
該函數用來為當前線程添加取消點。聲明在pthread.h文件中。如果當前線程的可取消狀態(tài)被設為PTHREAD_CANCEL_DISABLE,則該函數沒有任何效果。
16.int pthread_setcanceltype(int type, int *oldtype)
該函數用來用當前線程設置可取消類型。聲明在pthread.h文件中。type是新類型,oldtype存放的是之前的舊類型。線程的可取消類型值有兩個:PTHREAD_CANCEL_DEFERRED和PTHREAD_CANCEL_ASYNCHRONOUS。默認情況下,線程的可取消類型是前者,即延遲取消(就是執(zhí)行完pthread_cancel調用后,被請求線程并不馬上被取消)。后者是異步取消,也就是說線程可以在任意時間取消,而不是非得遇到取消點才能被取消。
17.int pthread_sigmask(int how, const sigset_t *set, sigset_t *oldset)
該信號用來屏蔽當前線程的某些信號。聲明在pthread.h文件中。信號是屬于一個進程的,因此可以被進程的所有線程共享,一般情況下,信號到達進程時,哪個線程正在運行,就在該線程的上下文中執(zhí)行信號處理函數。但是,每個線程都有自己的信號屏蔽字,也就是說每個線程都可以屏蔽自己不想處理的信號,但這不影響其他線程的信號屏蔽字。另外,由于信號是屬于進程的,所以任何線程中設置或修改了信號的處理函數,整個進程的信號處理函數也就隨之改變了。
18.int sigwait(const sigset_t *set, int *sig)
線程通過調用該函數等待一個或多個信號發(fā)生。聲明在pthread.h文件中。函數的返回值存放在sig指向的變量中,表明信號發(fā)送的數量。如果信號集set中的某個信號在sigwait調用的時候處于未決狀態(tài),那么sigwait函數將無阻塞的返回,返回前sigwait將從進程中移除那些處于未決狀態(tài)的信號。線程在調用sigwait函數之時,必須阻塞它所等待的信號,從而避免錯誤發(fā)生(在函數返回之前的時間內,又會有新的等待信號產生)。sigwait函數在返回前會取消信號集的阻塞狀態(tài)(即恢復線程的屏蔽字)。
使用該函數使得線程簡化了信號處理,將異步產生的信號用同步的方式來處理,說白了就是當信號產生時,不去執(zhí)行進程的信號處理函數,而是被等待信號的線程所攔截(通過sigwait函數)而從sigwait函數返回,這樣就相當于將等待信號的線程環(huán)境(線程中sigwait以后的代碼)作為了信號處理程序,而不是進程的信號處理函數。當有多個線程調用sigwait函數來等待同一個信號,信號發(fā)生時只會有一個線程從sigwait返回。這塊討論的信號到底是由線程的sigwait捕獲還是進程的信號處理函數捕獲,是由操作系統實現的,操作系統要么實現前者,要么實現后者,而不會二者皆可。
19.int pthread_kill(pthread_t thread, int sig)
線程通過調用該函數向其他線程發(fā)送信號,thread參數是其他線程的線程id,sig是信號名。聲明在pthread.h文件中。當傳遞sig=0的參數到函數中時,可以用來檢查thread線程是否存在。另外,如果信號的默認處理動作是終止進程,那么把信號傳遞給某個線程也仍然會殺死整個進程。
20.int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void))
該函數用于清除子進程中的鎖狀態(tài)。聲明在pthread.h文件中。當在一個線程中使用fork創(chuàng)建子進程時,子進程通過繼承父進程的整個地址空間,從而也繼承了父進程的所有互斥量,讀寫鎖和條件變量狀態(tài)。但是,在子進程中只存在一個線程,那就是父進程中調用fork的那個線程的副本。由于子進程不包含其他線程副本,于是子進程就無法感知到其他線程中的鎖,因此需要借助該函數來清理鎖狀態(tài)。如果子進程創(chuàng)建好后,立即調用了exec函數,就不需要清理那些鎖了,因為整個舊的地址空間會被拋棄。
該函數的作用是來幫助fork后的子進程把其所有繼承而來的鎖打開。該函數的原理:該函數在fork函數之前進行調用,來注冊三個處理函數prepare,parent和child。注冊成功后,prepare函數會在fork函數創(chuàng)建子進程之前調用,作用是獲取父進程定義的所有鎖;parent會在fork創(chuàng)建完子進程,將要返回父進程環(huán)境時調用,作用是打開父進程的所有鎖;child函數會在fork創(chuàng)建完子進程,將要返回子進程環(huán)境時調用,作用是打開子進程的所有鎖。至此,子進程中所有繼承而來的鎖全部被打開了。注意:如果不想用哪個處理函數,注冊時該函數的參數傳遞為NULL。該函數可以在fork之前多次調用,或者在多個模塊中調用,詳細例子參考《apue》。
?
第14章 高級I/O
.......
........
從I/O多路轉接開始。。。。。
1.int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)
該函數具有進行同步I/O多路轉接功能(確定多個文件描述符fd的狀態(tài))。聲明在sys/select.h文件中。nfns參數告訴內核需要確定的描述符范圍為0~nfds-1,內核將來只會返回該范圍的文件描述符狀態(tài)。readfds,writefds,excpetfds三個參數用來設置需要了解其狀態(tài)的描述符,同時用來返回已準備好的可讀,可寫,有異常消息的描述符。timeout參數用來設置select的阻塞時間。
先來說明最后一個參數,它指定了select函數愿意等待的時間,struct timeval結構如下:
struct timeval {
?????????????? long??? tv_sec;???????? /* seconds */
?????????????? long??? tv_usec;??????? /* microseconds */
?????????? };
當timeout == NULL時,select函數永遠等待。當所指定的文件描述符之一準備好時或者函數捕捉到一個信號時,等待結束。如果捕捉到一個信號函數返回-1并設置errno值。
當timeout->tv_sec == 0 && timeout->tv_usec == 0時,select函數完全不阻塞。測試完所指定的描述符后立即返回。這種方式適合用輪詢來獲得描述符狀態(tài)。
當timeout->tv_sec != 0 || timeout->tv_usec != 0時,select函數等待所指定的秒數或微秒數。當指定的描述符之一準備好時或者指定的時間已超過時立即返回。
中間的三個參數readfds,writefds,excpetfds用下面的函數來設置。(這三個參數類型是fd_set,也就是描述符集,每個描述符在其中占據一位,下面的函數使用描述符fd來指定是哪一位)
void FD_CLR(int fd, fd_set *set)
int? FD_ISSET(int fd, fd_set *set)
void FD_SET(int fd, fd_set *set)
void FD_ZERO(fd_set *set)
第一個函數將fd指定的一位清除。第二個函數測試一個指定位是否設置。第三個函數用來設置一個指定位。最后一個函數將fd_set變量的所有位設置為0。一般在定一個了一個描述符集變量后,必須先用FD_ZERO清除其所有位,然后再設置我們關心的各個位。
select函數的中間三個參數readfds,writefds,excpetfds中的任意一個或者全部都可以為空指針,這表示對相應狀態(tài)并不關心。如果這三個參數都是空指針,select函數就相當于sleep函數,不過睡眠的時間更加精確。select有三個可能的返回值:
a.返回值為-1表示出錯。比如所有指定的描述符都沒有準備好時(select正阻塞著),進程接收到了一個其它的信號。
b.返回值為0表示沒有描述符準備好。這種情況出現在,要么select不阻塞立即返回,要么阻塞的時間已到,描述符都沒有準備好。這是readfds,writefds,excpetfds三個描述符集的所有位均為0。
c.返回值為正值表示已經有描述符準備好了。如果有同一個描述符的讀和寫都準備好了,返回值為2。這時,readfds,writefds,excpetfds三個描述符集中打開的位(為1)對應于準備好的描述符。
另外,對于普通文件描述符,獲取其讀,寫,異常狀態(tài)時總是返回準備好了。如果在一個描述符上碰到了文件結尾處,select指示該描述符是可讀狀態(tài),而不指示其為異常狀態(tài)。
2.int pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timespec *timeout, const sigset_t *sigmask)
該函數域select函數類似。聲明在sys/select.h文件中。有以下區(qū)別:
a.select的超時值用timeval結構指定,但pselect使用timespec結構指定。該結構如下所示:
struct timespec {
?????????????? long??? tv_sec;???????? /* seconds */
?????????????? long??? tv_nsec;??????? /* nanoseconds */
?????????? };
b.pselect的超時值被設置為const,這保證了函數調用期間不會改變該該結構值。
c.pselect函數可以設置信號屏蔽字,如果sigmask為null,那么在信號有關方面該函數和select運行狀況相同。該函數以原子操作安裝信號屏蔽字,在函數返回時恢復屏蔽字。
3.int poll(struct pollfd *fds, nfds_t nfds, int timeout)
該函數和select類似,不過接口不同。聲明在poll.h文件中。該函數使用了一個結構體數組fds,為每個要獲取其狀態(tài)的描述符分配了一個結構體(結構體中包含了描述符的各種狀態(tài)),而不是為每個狀態(tài)構造一個描述符集。stuct pollfd結構體如下:
struct pollfd {
?????????????? int?? fd;???????? /* file descriptor */
?????????????? short events;???? /* requested events */
?????????????? short revents;??? /* returned events */
?????????? };
結構體中的events成員用來告訴內核我們關心該描述符的什么狀態(tài)。函數返回時,內核設置revents成員,以說明對于該描述符已經發(fā)生了什么事件。events和revents取值如下:
events:
POLLIN ? ? ? ? ? ? ? ? ?? 不阻塞地可讀除高優(yōu)先級外的數據(等效于POLLRDNORM | POLLRDBAND) ?????????????
POLLRDNORM??????? 不阻塞地可讀普通數據(優(yōu)先級波段為0)
POLLRDBAND????????? 不阻塞地可讀非0優(yōu)先級波段數據
POLLPRI?????????????????? 不阻塞地可讀高優(yōu)先級數據
前四行用來測試可讀性
POLLOUT???????????????? 不阻塞地可寫普通數據
POLLWRNORM???????? 與POLLOUT 相同
POLLWRBAND????????? 不阻塞地可寫非0優(yōu)先級波段數據
這三行用來測試可寫性
revents:
POLLERR????????????????? 已出錯
POLLHUP????????????????? 已掛斷
POLLNVAL???????????????? 描述符不引用一打開文件
當描述符被掛斷后,就不能再寫向該描述符,但仍可能從該描述符中讀到數據。應該區(qū)分文件結束和掛斷之間的區(qū)別:如果正從終端輸入數據,并鍵入了文件結束字符,POLLIN被打開,于是就可讀文件結束標志(read返回0)。如果測試的正在讀調制解調器,并且電話線已掛斷,則在revents中將街道POLLHUP通知(之前POLLHUP在revents中沒有打開)。
4.ssize_t readv(int fd, const struct iovec *iov, int iovcnt)
?? ssize_t writev(int fd, const struct iovec *iov, int iovcnt)
這兩個函數在一次函數調用中讀,寫多個非連續(xù)緩沖區(qū)。聲明在sys/uio.h文件中。第一個函數將fd文件中的數據讀到iov數組中,第二個函數將iov數組中的數據聚集起來寫入fd文件中。struct iovec結構如下:
struct iovec {
?????????????? void? *iov_base;??? /* Starting address */
?????????????? size_t iov_len;???? /* Number of bytes to transfer */
?????????? };
iov_base成員指向緩沖區(qū)的首地址,iov_len成員代表該緩沖區(qū)大小。writev以順序iov[0],iov[1]至iov[iovcnt-1]從緩沖區(qū)中聚集輸出數據。函數返回輸出的字節(jié)總數,通常,它應該等于所有緩沖區(qū)長度之和。readv將讀到的數據按照上述的順序散步到緩沖區(qū)中。readv函數總是填滿一個緩沖區(qū),再填寫下一個緩沖區(qū)。readv返回讀到的字節(jié)數,遇到文件結尾返回0。
5.void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset)
該函數將一個給定文件映射到一個存儲區(qū)域中。聲明在sys/mman.h文件中。addr用于指定映射存儲區(qū)的起始地址,通常將其設置為0,表示由系統選擇該映射區(qū)的起始地址(系統一般選擇堆區(qū)和棧區(qū)中間的共享區(qū))。該函數返回值為映射區(qū)的起始地址。fd參數指定要被映射文件的描述符,在映射之前,先要打開該文件。length參數指示映射區(qū)的長度(字節(jié)為單位)。offset參數指示要映射字節(jié)在文件中的起始偏移量。prot參數說明對映射存儲區(qū)的保護要求:
PROT_READ?????? 映射區(qū)可讀
PROT_WRITE???? 映射區(qū)可寫
PROT_EXEC?????? 映射區(qū)可執(zhí)行
PROT_NONE????? 映射區(qū)不可訪問
對映射存儲區(qū)的保護要求不能超過文件open模式訪問權限。例如,若該文件是只讀打開的,那么對映射存儲區(qū)就不能指定PROT_WRITE。
flags參數影響映射存儲區(qū)的多種屬性:
MAP_FIXED???????? 返回值必須等于addr。不推薦使用該標志,因為不利于可移植性。如果未指定此標志,而且addr非0,則內核只把addr視為一種建議值,并不保證會采用
??????????????????????????? 該地址。將addr指定為0可獲得最大可移植性。
MAP_SHARED???? 該標志說明本進程對該映射區(qū)做的修改對其他進程是可見的。說白了,就是本進程對該映射區(qū)做的修改都會真正修改磁盤上的該文件。這樣,其它使用該
???????????????????????????? 標志映射該文件的進程就會和本進程共享該文件,對該區(qū)域的修改對于這些進程都是可見的,最終也會修改磁盤上的文件。
MAP_PRIVATE???? 使用了該標志,只是將文件的一個副本映射到存儲區(qū)中,進程對存儲區(qū)的修改,只是在副本上進行修改,不會影響到磁盤上的文件,當進程終止后,磁盤
??????????????????????????? 上的該文件沒有變化。當然,對映射區(qū)的修改對于多個進程都是不可見的。
還有許多MAP_類標志,詳情參考man手冊。
需要注意的是,一般映射區(qū)的長度會是內存頁長的整數倍,那么需要映射的文件如果比內存的頁長小,那么系統會把映射區(qū)內除了文件以外的多余部分設置為0,對這部分的操作不會影響到文件。例如,文件長度為12字節(jié),系統頁長為512字節(jié),因此多出的500字節(jié)系統會設置為0,并且操作這500字節(jié)對文件無影響(如果想讓文件映射滿一頁,可以先對打開的文件進行l(wèi)seek操作,將文件指針移動到距離文件開頭一頁大小的位置,再隨意向文件中寫一個字符即可,實際上該操作將文件變大了再映射)。此外,與映射區(qū)相關信號有兩個:SIGSEGV和SIGBUS。第一個信號通常用于指示進程試圖訪問對它不可用的存儲區(qū),如果進程企圖存數據到mmap指定為只讀的映射區(qū),那么也會產生該信號。如果訪問映射區(qū)的一部分,該部分已經不存在了,則會產生第二個信號。例如進程映射了一個文件后,另外一個進程又將該文件截短了,那么該進程如果訪問被截去的部分時就會接收到第二個信號。最后,用fork產生子進程后,子進程會繼承存儲映射區(qū)(因為存儲映射區(qū)也是父進程地址空間的組成部分)。子進程調用exec執(zhí)行新程序后,不再繼承存儲映射區(qū)。
6.int mprotect(void *addr, size_t len, int prot)
該函數用來更改一個已映射存儲區(qū)的權限。聲明在sys/mman.h文件中。prot許可值與mmap函數的該參數一樣。addr地址必須是系統頁邊界對齊。
7.int msync(void *addr, size_t length, int flags)
如果修改了共享映射存儲區(qū)中的頁,就可以使用該函數將頁沖洗到被映射的文件中。該函數類似于fsync,但作用于映射存儲區(qū)。如果映射的存儲區(qū)是私有的,就不會修改被映射的文件。addr地址也必須和頁邊界對其。flags參數用來控制沖洗的方式,必須要指定MS_ASYNC和MS_SYNC二者中的一個。MS_ASYNC表示不等待沖洗完成,MS_SYNC表示等待沖洗完成后函數才返回(沖洗完成之前進程一直阻塞)。MS_INVALIDATE標志是可選標志,用來丟棄與磁盤沒有同步的任何頁(會將函數參數表示的地址范圍的所有頁丟棄),這一般不是期望的操作。聲明在sys/mman.h文件中。
8.int munmap(void *addr, size_t length)
當進程終止時,或者調用了該函數后,映射的存儲區(qū)會被自動解除。關閉文件描述符不會解除映射。聲明在sys/mman.h文件中。該函數調用時,不會將映射區(qū)的內容寫到磁盤文件中。對于MAP_SHARED標志后的映射區(qū),內容同步的工作由內核虛存算法自動進程,和該函數是沒有關系的。對于MAP_PRIVATE標志的映射區(qū),調用該函數解除后將被丟棄。
?
第15章 進程間通信
1.int pipe(int pipefd[2])
該函數用來創(chuàng)建管道。聲明在unistd.h文件中。pipefd數組將返回兩個文件描述符:pipefd[0]為讀而打開,pipefd[1]為寫而打開。向pipefd[1]描述符中寫入的數據,可以從pipefd[0]描述符中讀出。一般不在同一個進程中使用管道,因為沒有意義,而是在父進程和子進程之間使用管道。當用fork創(chuàng)建子進程后,子進程會繼承父進程已打開的文件描述符,因此也就繼承了管道。這時候父進程和子進程中總共有四個管道端口,它們之間都是通的,也就是說,向父進程的pipefd[1]寫入的數據既可以從父進程的pipefd[0]中讀出也可以從子進程的pipefd[0]中讀出;同理,向子進程的pipefd[1]寫入的數據既可以從子進程的pipefd[0]中讀出也可以從父進程的pipefd[0]中讀出。但是管道中的數據被讀一次后,就會被清除,不會保留。向管道中連續(xù)寫入的數據,第二次的數據回追加到第一次數據后面,而不會覆蓋第一次的數據。多個進程對同一個管道進行寫操作時,如果寫入的數據長度大于管道的尺寸PIPE_BUF時,可入的數據可能會穿插,如果寫入的數據小于PIPE_BUF,則沒有穿插問題(可用pathconf/fpathconf函數獲取PIPE_BUF值的大小)。一般在父子進程間使用管道,會關閉父進程的讀(或寫)端描述符,同時關閉子進程的寫(或讀)端描述符,使父子進程半雙工通信。當子進程執(zhí)行exec后,文件描述符會丟失,因此一般將管道端口描述符先重定向到標準輸入輸出描述符中,再使子進程執(zhí)行exec,這樣在子進程中就可繼續(xù)使用管道。當管道的一端被關閉后,下面兩個規(guī)則起作用:
a.當讀一個寫端已被關閉的管道時,在所有數據被讀取后,read返回0,表示到達了文件末尾。
b.如果寫一個讀端已被關閉的管道時,會產生SIGPIPE信號。如果忽略該信號或者捕捉該信號并從信號處理函數返回后,write函數返回-1,errno設置為EPIPE。
最后,a.歷史上的管道是半雙工的,b.管道只能在具有公共祖先的進程之間使用。
2.FILE *popen(const char *command, const char *type)
? ?int pclose(FILE *stream)
第一個函數用來創(chuàng)建一個子進程,同時,創(chuàng)建父子進程之間的管道(這和fork/system是不同的),返回管道的一個端口至父進程。該子進程執(zhí)行shell程序,然后再創(chuàng)建子進程執(zhí)行commad命令(在這點上類似于system函數)。type參數可以是“r”或者“w”,如果是r,管道的寫端將連接到commad進程的標準輸出,此時函數返回管道的讀端;如果w,管道的讀端將連接到commad進程的標準輸入,此時函數返回管道的寫端。
pclose函數用來關閉標準I/O流,等待命令執(zhí)行結束,然后返回shell的終止狀態(tài)。函數的返回值類似于system函數的返回值。由于pclose中會調用waitpid等待它所創(chuàng)建的所有子進程,因此如果在程序中,用戶自行設置了SIGCHILD的處理函數,并且函數中調用了wait等函數,則可能會影響pclose中的waitpid函數。他們均聲明在stdio.h文件中。
3.協同進程(coprocess):當一個進程產生某個過濾進程的輸入,同時又讀取該過濾進程的輸出,則稱該過濾進程為協同進程。實際上協同進程就是幫其它進程處理數據,當然要從其它進程中獲取要處理的數據,然后再將處理以后的數據返回給其它進程。
4.int mkfifo(const char *pathname, mode_t mode)
該函數用來創(chuàng)建一個FIFO文件。聲明在sys/stat.h文件中。創(chuàng)建一個FIFO類似與創(chuàng)建一個文件,因此該函數類似于open/creat函數。mode參數取值和open/creat的mode參數相同。創(chuàng)建FIFO文件的用戶和組權限規(guī)則同創(chuàng)建普通文件是相同的。創(chuàng)建好的FIFO文件可以使用一般的I/O函數(open,close,read,write,unlink等)來操作它。
當打開一個FIFO時:
a.如果沒有指定O_NONBLOCK,那么只讀open要阻塞到某個其它進程為寫而打開它;類似的,只寫open要阻塞到某個其它進程為讀而打開它。
b.如果指定了O_NONBLOCK,只讀open函數會立即返回(如果沒有其它進程寫FIFO);只寫open將返回-1,并設置errno為ENXIO(如果沒有其它進程為讀而打開FIFO)。
和管道類似,若用write寫一個尚無進程為讀而打開的FIFO,則產生信號SIGPIPE;若某個FIFO的最后一個寫進程關閉了該FIFO,則將為該FIFO的讀進程產生一個文件結束標志(如果以讀寫打開FIFO,最后一個寫進程關閉FIFO時,不會產生文件結束標志)。如果有多個進程寫同一個FIFO,如果不希望數據穿插,則需要考慮原子操作。和管道類似,常量PIPE_BUF說明了可被原子寫入FIFO的最大數據量。
FIFO有兩種用途:
a.FIFO由shell命令使用以便將數據從一條管道線傳送到另一條,為此無需創(chuàng)建中間臨時文件。
b.FIFO用于客戶進程---服務器進程應用程序中,以便在客戶進程和服務器進程之間傳遞數據。
5.key_t ftok(const char *pathname, int proj_id)
該函數用來為XSI IPC創(chuàng)建一個鍵(key)。聲明在sys/msg.h文件中。所謂XSI IPC,指的是消息隊列,信號量,共享存儲這三種進程間通信方式。不包括前邊提到的管道和FIFO。XSI IPC創(chuàng)建的IPC結構,不會因為進程的終止而消失,不像管道和FIFO,進程終止后管道就不存在了,FIFO雖然存在,但其數據已被清空。使用該函數創(chuàng)建鍵時,pathname參數所指定的文件必須存在,該函數將使用指定文件的stat結構中st_dev和st_ino字段和proj_id參數的后8位組合起來生成鍵。生成的鍵可供下邊的函數來創(chuàng)建XSI IPC結構。創(chuàng)建消息隊列,信號量,共享存儲所用的函數分別為msgget,semget,shmget,這三個函數都有兩個類似的參數:一個key(就是由ftok生成的鍵)和一個整型msgflag。可以用這三個函數創(chuàng)建新的IPC結構或者打開已存在的IPC結構。如若要創(chuàng)建新結構,需滿足下面兩個條件之一:
a.key值為IPC_PRIVATE
b.key當前未與特定類型的IPC結構相結合,并且msgflag終止定了IPC_CREAT位
如若需要打開已存在的IPC結構,key值必須等于創(chuàng)建IPC結構時所指定的鍵,并且msgflag中不應指定IPC_CREAT。為了打開已存在的IPC結構,key值決不能為IPC_PRIVATE,該值比較特殊,它總是用來創(chuàng)建一個新IPC結構。如果需要訪問用IPC_PRIVATE鍵創(chuàng)建的IPC結構,一定要知道該結構的標識符,然后用其他IPC調用(msgsnd和msgrcv)使用該標識符,而不能用msgget,semget,shmget三個函數來打開。如果需要創(chuàng)建一個新的IPC結構,而且要確保不是引用具有同一標識符的一個已存在IPC結構,需要在msgflag中同時指定IPC_CREAT和IPC_EXCL位,這樣的話,如果IPC結構已經存在就會造成出錯,返回EEXIST(這與指定了O_CREAT和O_EXCL標志的open函數類似)。消息隊列,信號量,共享存儲三者有各自的IPC結構,后邊會列出。這些IPC結構中包含了一個相同結構struct ipc_perm結構,裝有該IPC結構的權限和所有者。如下所示:
struct ipc_perm
? {
??? __key_t __key;?? ??? ??? ?/* Key.? */
??? __uid_t uid;?? ??? ??? ?/* Owner's user ID.? */
??? __gid_t gid;?? ??? ??? ?/* Owner's group ID.? */
??? __uid_t cuid;?? ??? ??? ?/* Creator's user ID.? */
??? __gid_t cgid;?? ??? ??? ?/* Creator's group ID.? */
??? unsigned short int mode;?? ??? ?/* Read/write permission.? */
?? ............
?? ...........
? };
uid,gid,cuid,cgid分別指定了擁有者ID,擁有者組ID,創(chuàng)建者ID,創(chuàng)建者組ID。mode字段指定了訪問者權限,如下所示:(IPC結構不需要執(zhí)行,因此沒有執(zhí)行權限)
用戶讀??????????????????? 0400
用戶寫(更改)????? 0200
組讀??????????????????????? 0040
組寫(更改)????????? 0020
其他讀??????????????????? 0004
其他寫(更改)????? 0002
6.int msgget(key_t key, int msgflg)
該函數用來創(chuàng)建一個新的消息隊列或者打開一個現存消息隊列,返回消息隊列標識符。后面對消息隊列進行操作的函數可以使用該標識符。該隊列ID可以聲明在sys/msg.h文件中。該函數會創(chuàng)建如下所示的結構體,并初始化一部分成員:msg_perm中的各個值;msg_qnum,msg_lspid,msg_lrpid,msg_stime,msg_ctime都設置為0;msg_ctime設置為當前時間;msg_qbytes設置為系統限制值。
struct msqid_ds
{
? struct ipc_perm msg_perm;?? ?/* structure describing operation permission */
? __time_t msg_stime;?? ??? ?/* time of last msgsnd command */
? .......
? __time_t msg_rtime;
? .......
? __time_t msg_ctime;
? .......
? __syscall_ulong_t __msg_cbytes;
? msgqnum_t msg_qnum;?? ??? ?/* number of messages currently on queue */
? msglen_t msg_qbytes;?? ??? ?/* max number of bytes allowed on queue */
? __pid_t msg_lspid;?? ??? ?/* pid of last msgsnd() */
? __pid_t msg_lrpid;?? ??? ?/* pid of last msgrcv() */
? .......
? .......
};
函數執(zhí)行成功,返回一個非負的隊列ID。該隊列可被用于其他三個消息隊列函數。
7.int msgctl(int msqid, int cmd, struct msqid_ds *buf)
該函數可以對消息隊列進行各種控制操作。聲明在sys/msg.h文件中。該函數是XSI IPC中類似于ioctl的函數(垃圾桶函數)。msqid是消息隊列ID(就是msgget函數返回的隊列描述符),cmd參數指定了隊列要執(zhí)行的命令:
a.IPC_STAT???????? 取此隊列的msqid_ds結構,并將它存放在buf指向的結構中
b.IPC_SET?????????? 按由buf指向結構中的值,設置與此隊列相關結構中的下列四個字段:msg_perm.uid,msg_perm.gid,msg_perm.mode和msg_qbytes。此命令只能由下 面兩種進程執(zhí)行:一種是其有效用戶ID等于msg_perm.cuid或msg_perm.uid,另一種是具有超級用戶權限的進程。此外,只有超級用戶才能增加 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?msg_qbytes的值。
c.IPC_RMID???????? 從系統中刪除該消息隊列以及仍在該對列中的所有數據。這種刪除立即生效。仍在使用這一消息隊列的其他進程在它們下一次試圖對此隊列進行操作時,?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 將出錯返回EIDRM。此命令只能由下列兩種進程執(zhí)行:一種是其有效用戶ID等于msg_perm.cuid或msg_perm.uid,另一種是具有超級用戶權限的進程。
8.int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg)
調用該函數將數據放到消息隊列中。聲明在sys/msg.h文件中。每個消息都有三部分組成,它們是:正長整型類型字段,非負長度(msgsz)以及實際數據字節(jié)(對應于長度)。消息總是放在隊列尾端。msgp參數指向一個長整型數,它包含了癥的整型消息類型,緊跟其后的是消息數據。(若msgsz是0,則無消息數據)若發(fā)送的最長消息是512字節(jié),則可定義下列結構:
struct mymesg {
long mtype;
char mtext[512];
};
然后,ptr就是一個指向該結構的指針。接收者可以使用消息類型以非先進先出的次序取消息。參數msgflg的值可以指定為IPC_NOWAIT,這類似于文件I/O的非阻塞標志。若消息隊列已滿(或者是隊列中的消息總數等于系統限制值,或對列中的字節(jié)總數等于系統限制值),則指定IPC_NOWAIT的話,msgsnd立即出錯并返回EAGAIN;沒有指定IPC_NOWAIT的話,進程將阻塞到下述情況出現為止:有空間可以容納要發(fā)送的消息;從系統中刪除了此隊列,或捕捉到一個信號,并從信號處理程序返回。第二種情況下,返回EIDRM(“標識符被刪除”)。第三種情況則返回EINTR。
需要注意的是,對刪除消息隊列的處理不是很完善,因為對每個消息隊列并沒有設置一個引用計數器(對打開文件則有這種計數器),所以刪除一個隊列會造成仍在使用這一隊列的進程在下次對隊列進行操作時出錯返回。信號量機制也是如此。而文件刪除就比較完善,當使用某個文件的最后一個進程關閉了它的文件描述符后,才能刪除文件內容。
當msgsnd成功返回后,與消息隊列相關的struct msqid_ds結構將得到更新,以標明發(fā)出該消息的進程ID(msg_lspid),進行該調用的時間(msg_stime),并指示隊列中增加了一條消息(msg_qnum增加1)。
9.ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg)
該函數從隊列中取出消息。聲明在sys/msg.h文件中。該函數的msqid參數和msgp參數和9中的函數類似。msgsz參數表示要接收的長度。如果返回的消息長度大于msgsz,并且msgflg中設置了MSG_NOERROR,則接收到的消息會被截短。(這種情況下,不會通知我們消息被截短了,消息截去的那一部分被丟棄)如果沒有設置MSG_NOERROR標志,而消息有太長,則函數出錯返回E2BIG(消息仍留在隊列中)。type參數用于指定想要取哪一種消息:
type == 0???? 返回隊列中的第一個消息
type > 0?????? 返回隊列中消息類型為type的第一個消息
type < 0?????? 返回隊列中消息類型值小于或等于|type|的消息,如果這種消息有若干個,則取類型值最小的消息。
type值非0用于以非先進先出次序讀消息。例如,若應用程序對消息賦優(yōu)先權,那么type就可以是優(yōu)先權值。如果一個消息隊列由若干個客戶進程和一個服務器進程使用,那么type字段可以用來包含客戶進程的進程ID(只要進程ID可以存放在長整型中)。
可以指定flag值為IPC_NOWAIT,使操作不阻塞。這使得如果沒有指定類型的消息,則msgrcv函數返回-1,errno設置為ENOMSG;如果沒有指定IPC_NOWAIT,則進程阻塞到如下情況出現才終止:有了指定類型的消息;從系統中刪除了此隊列(出錯則返回-1且errno置為EIDRM);或捕捉到一個信號并從信號處理程序返回(msgrcv函數返回-1,errno設置為EINTR)。
msgrcv函數成功執(zhí)行時,內核更新與該消息隊列相關聯的struct msqid_ds結構,已指示調用者的進程ID(msg_lrpid)和調用時間(msg_rtime),并將隊列中的消息數減1(msg_qnum)。
注意:當初實施消息隊列時,是因為其他形式IPC都是半雙工管道。而現在有了全雙工管道。因此,在新的應用程序中不應該再使用消息隊列。
10.
11
12
13.int shmget(key_t key, size_t size, int shmflg)
該函數用來創(chuàng)建或者打開一個共享存儲區(qū),返回存儲區(qū)標識符。聲明在sys/shm.h文件中。后邊對共享區(qū)操作的函數就可以使用該標識符。該函數的創(chuàng)建或者打開規(guī)則和msgget函數相同。當創(chuàng)建一個新段時,會創(chuàng)建一個相關的結構體struct shmid_ds,并初始化下列成員:shm_perm中的各個值;shm_lpid,shm_nattch,shm_atime,shm_dtime成員都設置為0;shm_ctime成員設置為當前時間;shm_segsz設置為請求長度size。
?struct shmid_ds
? {
??? struct ipc_perm shm_perm;?? ??? ?/* operation permission struct */
??? size_t shm_segsz;?? ??? ??? ?/* size of segment in bytes */
??? __time_t shm_atime;?? ??? ??? ?/* time of last shmat() */
........
??? __time_t shm_dtime;?? ??? ??? ?/* time of last shmdt() */
?? ........
??? __time_t shm_ctime;?? ??? ??? ?/* time of last change by shmctl() */
?? ........
??? __pid_t shm_cpid;?? ??? ??? ?/* pid of creator */
??? __pid_t shm_lpid;?? ??? ??? ?/* pid of last shmop */
??? shmatt_t shm_nattch;?? ??? ?/* number of current attaches */
?? .........
? };
size參數用來指定該共享存儲段的長度,實現通常將其向上取為系統頁長的整數倍。但是,如果應用指定的size值并非系統頁長的整數倍,則最后一頁的余下部分不可使用。如果正在創(chuàng)建一個新段,那么必須指定size值;如果正在引用一個現存段,則將size指定為0。當創(chuàng)建一個新段時,段內的內容初始化為0。
14.int shmctl(int shmid, int cmd, struct shmid_ds *buf)
該函數用來對指定的共享存儲段進行控制。聲明在sys/shm.h文件中。cmd指定了下面5中命令中的一種:
IPC_STAT????? ? ? 取此段的shmid_ds結構,并將它存放在由buf指向的結構中。?
IPC_SET??????????? 按buf指向結構中的值設置與此段相關結構中的下列三個字段:shm_perm.uid,shm_perm.gid和shm_perm.mode。此命令只能由下 ? 面兩種進程執(zhí)行:一種是其有效用戶ID等于shm_perm.cuid或shm_perm.uid,另一種是具有超級用戶權限的進程。
IPC_RMID????????? 從系統中刪除共享存儲段,因為每個共享存儲段有一個連接計數(shmid_ds結構中的shm_nattch字段),所以除非該段最后一個進程終止或與該段分離,否 ? ? 則不會實際上刪除該存儲段。不管此段是否仍在使用,該段標識符立即被刪除,所以不能再用shmat函數與該段連接。此命令只能由下 ? 面兩種進程執(zhí)行:一種是其有效用戶ID等于shm_perm.cuid或shm_perm.uid,另一種是具有超級用戶權限的進程。
IPC_LOCK???????? 將共享存儲段鎖定在內存中,此命令只能由超級用戶執(zhí)行。
IPC_UNLOCK??? 解鎖共享存儲段。此命令只能由超級用戶執(zhí)行。
15.void *shmat(int shmid, const void *shmaddr, int shmflg)
一旦創(chuàng)建了共享存儲段,就可以使用該函數將其連接到進程的地址空間中。聲明在sys/shm.h文件中。共享存儲段連接到調用進程的哪個地址上與shmaddr參數以及在flag中是否指定SHM_RND位有關:
a.如果shmaddr為0,則此段連接到由內核選擇的第一個可用地址上。推薦使用這種方式。(一般位于堆區(qū)和棧區(qū)中間的共享區(qū))
b.如果shmaddr非0,并且沒有指定SHM_RND,則此段連接到shmaddr所指定的地址上。
c.如果shmaddr非0,并且指定了SHM_RND,則此段連接到shmaddr mod ulus SHMLBA所表示的地址上。
如果在shmflag中指定了SHM_RDONLY位,則以只讀方式連接此段,否則以讀寫方式連接此段。shmat函數的返回值是該段所連接的實際地址,如果函數出錯返回-1。如果執(zhí)行成功那么內核將使該共享存儲段shmid_ds結構中的shm_nattch計數器值加1。
16.int shmdt(const void *shmaddr)
當今成使用完存儲共享區(qū)后,調用該函數是共享區(qū)與進程分離。聲明在sys/shm.h文件中。該操作并不從系統中刪除共享區(qū)標識符及其數據結構。該標志符讓然存在,直至某個進程調用shmctl(帶命令IPC_RMID)刪除它。shmaddr參數是共享區(qū)在進程中的起始地址。如果函數執(zhí)行成功,shmid_ds結構中的shm_nattch計數器值減1。
?小結:掌握管道和FIFO,因為在大量應用程序中仍可有效地使用這兩種基本技術。在新的應用程序中,盡量避免使用消息隊列以及信號量,而用全雙工管道和記錄鎖取代。共享存儲段有其應用場合,而mmap函數也能提供同樣的功能。
?
第16章 網絡IPC:套接字
1.int socket(int domain, int type, int protocol)
該函數用來創(chuàng)建一個套接字。聲明在sys/socket.h文件中。參數domain用來指定套接字使用的地址族,取值如下:
AF_INET??????????? ipv4因特網域
AF_INET6????????? ipv6因特網域
AF_UNIX??????????? unix域
AF_UNSPEC????? 未指定
參數type指定套接字的類型,取值如下:
SOCK_DGRAM??????????????? 長度固定的,無連接的不可靠報文傳遞(UDP)
SOCK_RAW????????????????????? IP協議的數據包接口
SOCK_SEQPACKET???????? 長度固定的,有序,可靠的面向連接報文傳遞
SOCK_STREAM??????????????? 有序,可靠,雙向的面向連接字節(jié)流(TCP)
參數protocol通常為零,表示按給定的域和套接字類型選擇默認的協議。當對同一域和套接字類型支持多個協議時,可以使用protocol參數選擇一個特定協議。在AF_INET通信域中套接字類型SOCK_STREAM的默認協議是TCP,在AF_INET通信域中套接字類型SOCK_DGRAM的默認協議是UDP。
socket函數創(chuàng)建的套接字描述符可用普通文件操作函數來操作,但并適用所有的文件操作函數。
2.int shutdown(int sockfd, int how)
該函數用來關閉套接字的輸入/輸出端口。聲明在sys/socket.h文件中。如果參數how是SHUT_RD(關閉讀端),那么無法從套接字讀取數據(但仍可以讀);如果參數how是SHUT_WR(關閉寫端),那么無法使用套接字發(fā)送數據(但仍可以寫);使用SHUT_RDWR則將無法同時讀取和發(fā)送數據。需要注意的是,close函數雖然可以關閉套接字,但是如果使用dup2復制了套接字描述符,那么直到最后一個套接字描述符被關閉后,套接字才會被釋放;而shutdown函數是一個套接字處于不活動狀態(tài),無論它的描述符有多少。
3.uint32_t htonl(uint32_t hostlong)
?? uint16_t htons(uint16_t hostshort)
?? uint32_t ntohl(uint32_t netlong)
?? uint16_t ntohs(uint16_t netshort)
這四個函數用來完成本地字節(jié)序和網絡字節(jié)序之間的轉換。聲明在arpa/inet.h文件中。n代表network,h代表host,l代表long,s代表short。奔騰處理器上的Linux系統采用小端字節(jié)序。
4.地址格式
為了是不同格式地址能夠被傳入到套接字函數,地址會被強制轉換成通用的地址結構struct sockaddr。
Linux對該通用地址結構的實現如下(/usr/include/i386/bits/socket.h):
struct sockaddr
? {
??? __SOCKADDR_COMMON (sa_);?? ?/* Common data: address family and length.? */?? /* #define __SOCKADDR_COMMON(sa_)? \ sa_family_t sa_prefix##family */
??? char sa_data[14];?? ??? ?/* Address data.? */
? };
Linux中IPV4的套接字地址sockaddr_in格式如下:
struct sockaddr_in
? {
??? __SOCKADDR_COMMON (sin_);
??? in_port_t sin_port;?? ??? ??? ?/* Port number.? */??????? /*?typedef uint16_t in_port_t; */
??? struct in_addr sin_addr;?? ??? ?/* Internet address.? */??????? /*?typedef uint32_t in_addr_t; */
??? /* Pad to size of `struct sockaddr'.? */
??? unsigned char sin_zero[sizeof (struct sockaddr) -
?? ??? ??? ??? __SOCKADDR_COMMON_SIZE -
?? ??? ??? ??? sizeof (in_port_t) -
?? ??? ??? ??? sizeof (struct in_addr)];
? };
struct in_addr
? {
??? in_addr_t s_addr;
? };
Linux中IPV6的套接字地址sockaddr_in6格式如下:
struct sockaddr_in6
? {
??? __SOCKADDR_COMMON (sin6_);
??? in_port_t sin6_port;?? ?/* Transport layer port # */
??? uint32_t sin6_flowinfo;?? ?/* IPv6 flow information */
??? struct in6_addr sin6_addr;?? ?/* IPv6 address */
??? uint32_t sin6_scope_id;?? ?/* IPv6 scope-id */
? };
struct in6_addr
? {
??? union
????? {
?? ?uint8_t?? ?__u6_addr8[16];
#if defined __USE_MISC || defined __USE_GNU
?? ?uint16_t __u6_addr16[8];
?? ?uint32_t __u6_addr32[4];
#endif
????? } __in6_u;
#define s6_addr?? ??? ??? ?__in6_u.__u6_addr8
#if defined __USE_MISC || defined __USE_GNU
# define s6_addr16?? ??? ?__in6_u.__u6_addr16
# define s6_addr32?? ??? ?__in6_u.__u6_addr32
#endif
? };
在使用時,無論是ipv4還是ipv6,均要被強制轉換成struct sockaddr_in結構傳入到套接字例程中。
5.in_addr_t inet_addr(const char *cp)
? ?char *inet_ntoa(struct in_addr in)
?? const char *inet_ntop(int af, const void *src, char *dst, socklen_t size)
?? int inet_pton(int af, const char *src, void *dst)
這些函數用于二進制地址格式和點分十進制字符串格式之間的相互轉換。它們均聲明在arpa/inet.h文件中。前兩個函數只能用于IPV4地址。后兩個新函數支持IPV4和IPV6地址。第一和第三個函數將點分十進制字符串轉換成二進制地址,第二和第四個函數剛好相反。后兩個函數的af參數取值為:AF_INET和AF_INET6,分別代表ipv4域和ipv6域。第三個函數的size參數指定了保存文本字符串緩沖區(qū)(dst)的大小,有兩個常數用于簡化工作:INET_ADDRSTRLEN和INET6_ADDRSTRLEN,前者可放ipv4地址,后者可放ipv6地址。對于第四個函數,dst參數指向的地址空間必須足夠大,以便能夠存入32位地址(當af指定為AF_INET)和128位地址(當af指定為AF_INET6)。
6.struct hostent *gethostent(void)?
?? void sethostent(int stayopen)
?? void endhostent(void)
調用這些函數可以獲取計算機的主機信息。均聲明在netdb.h文件中。第一個函數會打開數據庫文件(如果該文件沒有打開),如果文件是打開的,則該函數返回文件的下一條目;第二個函數也可以打開數據庫文件,如果文件是打開的,則將其回繞。第三個函數關閉數據庫文件。第一個函數返回的struct hostent結構如下:
struct hostent {
?????????????? char? *h_name;??????????? /* official name of host */
?????????????? char **h_aliases;???????? /* alias list */
?????????????? int??? h_addrtype;??????? /* host address type */
?????????????? int??? h_length;????????? /* length of address */
?????????????? char **h_addr_list;?????? /* list of addresses */??????
?????????? }
7.struct netent *getnetbyaddr(uint32_t net, int type)
?? struct netent *getnetbyname(const char *name)
?? struct netent *getnetent(void)
?? void setnetent(int stayopen)
?? void endnetent(void)
這幾個函數用來獲取網絡名字和網絡號。均聲明在netdb.h文件中。將網絡名字和網絡號包含在struct netent結構體中,該結構體如下:
struct netent {
?????????????? char????? *n_name;???? /* official network name */
?????????????? char???? **n_aliases;? /* alias list */
?????????????? int??????? n_addrtype; /* net address type */
?????????????? uint32_t?? n_net;????? /* network number */??????? //返回的地址采用網絡字節(jié)序。
?????????? }
8.struct protoent *getprotobyname(const char *name)
?? struct protoent *getprotobynumber(int proto)
?? struct protoent *getprotoent(void)
?? void setprotoent(int stayopen)
?? void endprotoent(void)
這幾個函數用來獲取協議名字和協議號。均聲明在netdb.h文件中。將協議名字和協議號包含在struct protoent結構體中,該結構體如下:
struct protoent {
?????????????? char? *p_name;?????? /* official protocol name */
?????????????? char **p_aliases;??? /* alias list */
?????????????? int??? p_proto;????? /* protocol number */
?????????? }
9.struct servent *getservbyname(const char *name, const char *proto)
?? struct servent *getservbyport(int port, const char *proto)
?? struct servent *getservent(void)
?? void setservent(int stayopen)
?? void endservent(void)
這幾個函數用來獲取服務名字和服務的端口號。均聲明在netdb.h文件中。返回的struct servent結構如下:
struct servent {
?????????????? char? *s_name;?????? /* official service name */
?????????????? char **s_aliases;??? /* alias list */
?????????????? int??? s_port;?????? /* port number */
?????????????? char? *s_proto;????? /* protocol to use */
?????????? }
10.int getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res)
const char *gai_strerror(int errcode)
???? void freeaddrinfo(struct addrinfo *res)
第一個函數通過主機名字或服務名字來獲取主機的地址信息。聲明在netdb.h文件中。主機可能有多個地址,因此返回一個節(jié)點類型為struct addrinfo的鏈表res。struct addrinfo結構如下:
struct addrinfo {
?????????????? int????????????? ai_flags;
?????????????? int????????????? ai_family;
?????????????? int????????????? ai_socktype;
?????????????? int????????????? ai_protocol;
?????????????? socklen_t??????? ai_addrlen;
?????????????? struct sockaddr *ai_addr;
?????????????? char??????????? *ai_canonname;
?????????????? struct addrinfo *ai_next;
?????????? };
hints參數是一個過濾地址的模板,用來選擇特定的地址。它僅使用ai_family,ai_flags,ai_protocol和ai_socktype字段。剩余的整數字段必須為0,指針字段必須為空。ai_flags的使用的標志參考《apue》。如果該函數失敗,不能使用perror或strerror函數來生成錯誤信息,而要使用第二個的函數。第二個函數用來釋放這些鏈表節(jié)點。
11.int getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, size_t hostlen, char *serv, size_t servlen, int flags)
該函數將套接字地址轉換成主機名或服務器名。聲明在netdb.h文件中。如果host非空,它指向一個長度為hostlen字節(jié)的緩沖區(qū)用于存儲返回的主機名;如果serv非空,它指向一個長度為servlen字節(jié)的緩沖區(qū)用于存儲返回的服務名。flags參數指定一些轉換的控制方式,取值參考《apue》。
12.?int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
該函數將地址綁定到一個套接字上。聲明在sys/socket.h文件中。參數addr有以下規(guī)則:
a.在進程所運行的機器上,指定的地址必須有效,不能指定一個其他機器上的地址。
b.地址必須和創(chuàng)建套接字時的地址族所支持的格式相匹配。
c.端口號必須不小于1024,除非該進程具有超級用戶特權。
對于因特網域(AF_INET/AF_INET6),如果指定IP地址為INADDR_ANY,套接字端點可以被綁定到系統的所有網絡接口上,這意味著可以收到這個系統所安裝的所有網卡的數據。一般情況下,與客戶端套接字關聯的地址沒有太大意義,可以讓系統選擇一個默認地址;服務器端需要綁定地址一個眾所周知的地址,使得客戶端可以進行連接。另外,如果沒有使用bind綁定地址,那么當調用connect或listen時,系統會自動將一個地址綁定到套接字上。
13.int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
調用該函數來獲得綁定到套接字上的一個地址。聲明在sys/socket.h文件中。調用該函數時,addrlen參數指定了addr緩沖區(qū)的大小;函數返回時,該參數被設置成緩沖區(qū)實際被使用的大小。如果提供的緩沖區(qū)和地址的實際大小不匹配,則將其截斷而不報錯。如果套接字當前沒有綁定地址,函數結果沒有意義。
14.int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
如果套接字已經和對方連接,調用該函數可以獲得對方的地址。聲明在sys/socket.h文件中。該函數和13中的函數類似。
15.int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
該函數在sockfd套接字和addr指定的服務器之間建立一個連接。聲明在sys/socket.h文件中。使用該函數建立連接時,可能會失敗,因此應用程序必須能夠處理該函數返回的錯誤,這些錯誤可能由一些瞬時變化條件引起(這在一些負載很重的服務器上很可能發(fā)生)。關于函數返回的錯誤值類型和原因,參考man手冊。另外,對于UDP套接字調用該函數的話,所有發(fā)送報文的目標地址被設置為該函數中指定的地址,這樣每次傳送報文的時候就不許要再提供地址,此外,也僅能接收來自指定地址的報文。
16.int listen(int sockfd, int backlog)
服務器端調用該函數監(jiān)聽套接字,來宣告可以接受客戶端的連接請求。聲明在sys/socket.h文件中。backlog參數指定了該套接字能連接的最大數量。一旦超過了最大值,服務器會拒絕多余連接請求。
17.int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
一旦服務器調用了listen,就可調用該函數獲得連接請求并建立連接。聲明在sys/socket.h文件中。該函數返回一個套接字描述符,該套接字連接到了客戶端。返回的套接字和原始套接字sockfd具有相同的套接字類型和地址族。而原始套接字sockfd并沒有關聯到客戶端的套接字,而是繼續(xù)保持可用狀態(tài)并接受其他連接請求。如果不關心客戶端的地址,則可將addr參數和addrlen置為null。否則,addr設為足夠大的緩沖區(qū)來存放客戶端的地址信息,len指定了該緩沖區(qū)的大小。返回時,函數會在緩沖區(qū)填充客戶端地址信息并更新len的大小。
如果沒有連接請求,accept函數會阻塞到直到一個請求到來。如果sockfd設置為非阻塞模式,accept函數會返回-1并將errno設置為EAGAIN或EWOULDBLOCK。此外,服務器端可以使用poll或者select函數來等待一個請求的到來,這時,一個請求套接字會以可讀方式出現。
18.ssize_t send(int sockfd, const void *buf, size_t len, int flags)
???? ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen)
???? ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags)
這三個函數用來發(fā)送數據。聲明在sys/socket.h文件中。第一個函數的用法和write類似,用于TCP套接字發(fā)送數據(UDP套接字不能使用它來發(fā)送),flags標志取值如下:
MSG_DONTROUTE??????? 勿將數據路由出本地網絡
MSG_DONTWAIT??????????? 允許非阻塞操作(等價于使用了O_NONBLOCK)
MSG_EOR?????????????????? ? ? 如果協議支持,此為記錄結束 ??
MSG_OOB?????????????????????? 如果協議支持,發(fā)送帶外數據
如果send成功返回,并不代表連接的另一端接收到了數據,所保證的僅是當send成功返回時,數據已經無錯誤的發(fā)送到網絡上。
UDP套接字可以使用第二個函數來發(fā)送數據,dest_addr參數指定了目標地址。TCP套接字如果使用第二個函數來發(fā)送數據的話,會忽略目標地址。
第三個函數用來發(fā)送由struct msghdr結構體指定的多重緩沖區(qū)的數據,該函數和writev函數類似。struct msghdr結構體如下:
struct msghdr {
?????????????? void???????? *msg_name;?????? /* optional address */
?????????????? socklen_t???? msg_namelen;??? /* size of address */
?????????????? struct iovec *msg_iov;??????? /* scatter/gather array */
?????????????? size_t??????? msg_iovlen;???? /* # elements in msg_iov */
?????????????? void???????? *msg_control;??? /* ancillary data, see below */
?????????????? size_t??????? msg_controllen; /* ancillary data buffer len */
?????????????? int?????????? msg_flags;????? /* flags on received message */
?????????? };
19.ssize_t recv(int sockfd, void *buf, size_t len, int flags)
???? ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen)
???? ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags)
這三個函數用來接收數據。聲明在sys/socket.h文件中。第一個函數的用法和read函數類似,flags標志取值如下:
MSG_OOB?????? ? ? ? ? 如果協議支持,接收帶外數據
MSG_PEEK????????????? 返回報文內容而不真正取走報文
MSG_TRUNC?????????? 即使報文被截斷,要求返回的是報文的實際長度
MSG_WAITALL???????? 等待直到接收完len指定的數據長度(僅SOCK_STREAM)
當指定為MSG_PEEK標志時,可以查看下一個要讀的數據但不會真正取走,當再次調用read或recv函數時會返回剛才查看到的數據。
對于SOCK_STREAM套接字(TCP套接字),默認情況下接收到數據可以比請求的少,但是如果設置了MSG_WAITALL標志,則直到接收完len指定的數據長度,recv函數才返回。對于SOCK_DRAM和SOCK_SEQPACKET套接字(它們是基于報文而不是字節(jié)流的),MSG_WAITALL標志沒有改變什么行為,因為這些套接字本身就是一次取走整個報文。如果發(fā)送者調用shudown來結束傳輸,或者發(fā)送端已經關閉,那么當所有數據接收完畢后,recv返回0。
第二個函數可以獲得發(fā)送者的套接字端點地址(src_addr和addrlen必須非空),該函數通常用于UDP套接字,也可以用于TCP套接字(這時等同于recv)。
第三個函數將接收到的數據送入多個緩沖區(qū)(類似于readv),也可以接收輔助數據。msg參數指定了用于接收數據的緩沖區(qū)。可以設置參數flags來改變recvmsg的默認行為,返回時,msghdr結構中的msg_flags字段被設置為所接收數據的各種特征(進入recvmsg時的msg_flags被忽略),這些返回的msg_flags標志如下:
MSG_CTRUNC???????? 控制數據被截斷
MSG_DONTWAIT???? recvmsg處于非阻塞模式
MSG_EOR??????????????? 接收到記錄結束符
MSG_OOB??????????????? 接收到帶外數據
MSG_TRUNC??????????? 一般數據被截斷
20.int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen)?
該函數用來設置套接字選項。聲明在sys/socket.h文件中。套接字有三個層次的選項(XSI僅定義了前兩個選項):
a.通用選項,工作在所有套接字類型上。
b.在套接字層次管理的選項,但是依賴于下層協議的支持。
c.特定于某協議的選項,為每個協議所獨有。
參數level標識了選項的層次,如果是通用層次的選項,level設置成SOL_SOCKET;否則,level設置成套接字層次的選項,例如,對于TCP選項,level是IPPROTO_TCP,對于IP選項,level是IPPROTO_TCP。參數optname代表通用層次和套接字層次的選項,參考《apue》。參數optval根據選項的不同指向一個數據結構或者一個整數。一些選項是on/off開關,當valopt整數非0,選項被啟用,當optval整數為0,選項被禁止。參數optlen指定了optval指向對象的大小。
21.int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen)
該函數用來獲取套接字當前的選項。聲明在sys/socket.h文件中。該函數調用時,optlen設置成optval緩沖區(qū)大小,函數返回時,optlen被更新為實際尺寸。如果實際尺寸大于optlen初始設置值,選項會被截斷而不報錯。
22.帶外數據:帶外數據又叫做緊急數據,TCP協議支持一個字節(jié)的帶外數據,UDP不支持帶外數據。在三個send函數中指定了標志MSG_OOB,就可以傳輸待外數據,傳送的若干個字節(jié)中最后一個字節(jié)就是帶外數據。此外,接收進程可以調用fcntl(sockfd,F_SETOWN,pid)函數來設置pid進程對套接字信號進行處理。當接收進程接收到帶外數據時,pid指定的進程就會收到一個套接字信號。調用owner = fcntl(sockfd,F_GETOWN,0)可以獲得處理套接字信號的進程ID。
23.int sockatmark(int sockfd)
TCP協議支持緊急標記(在普通數據流中緊急數據所處的位置),如果采用套接字選項SO_OOBINLINE,那么可以在普通數據中接收緊急數據。當系一個要讀的字節(jié)是緊急數據的話,該函數返回1。
此外,當帶外數據出現在套接字讀取隊列時,select函數會返回一個文件描述符并且擁有一個異常狀態(tài)。可以在普通數據流上接受緊急數據,或者在某個recv函數中采用MSG_OOB標志在其他隊列數據之前接收緊急數據。TCP隊列僅有一字節(jié)的緊急數據,如果在接收當前的緊急數據之前又有新的緊急數據到來,那么當前的字節(jié)就成了普通字節(jié)(新的最后一個字節(jié)是緊急數據)。
24.套接字非阻塞和基于套接字的異步I/O
通常,recv函數沒有數據可用時會阻塞等待。同樣地,當套接字輸出隊列沒有足夠空間來發(fā)送消息send函數也會阻塞。在套接字非阻塞模式下,這些函數不會阻塞而是失敗,設置errno為EWOULDBLOCK或者EAGAIN。非阻塞模式下,可以使用poll或select來判斷套接字何時能接收或者傳輸數據。
在基于套接字的異步I/O中,當能夠從套接字中讀取數據,或者套接字寫隊列中的空間變得可用時,可以安排發(fā)送信號SIGIO。通過兩個步驟來使用異步I/O:
a.建立套接字擁有者關系,信號可以被傳送到合適的進程
b.設置套接字選項(使用fcntl),使得套接字可用時(可讀或可寫)發(fā)信號告知
有三種方式完成步驟a:
aa.在fcntl中使用F_SETOWN命令
bb.在ioctl中使用FIOSETOWN命令
cc.在ioctl中使用SIOCSPGRP命令
有兩種方式完成步驟b:
aa.在fcntl中使用F_SETFL命令并且啟用文件標志O_ASYNC。
bb.在ioctl中使用FIOASYNC。
轉載于:https://www.cnblogs.com/liangning/p/3959903.html
總結
以上是生活随笔為你收集整理的《APUE》中的函数整理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 字节对齐的问题
- 下一篇: “放到桌面”的Servlet实现