學習標記整理

SI/O of linux/unix
------------------------------------------------------
1、linux/unix 中文件名的最大長度是 NAME_MAX,路徑的最大長度是 PATH_MAX
2、用於打開和關閉一個文件流的函數有三個
(1)、fopen
(2)、fclose()
(3)、freopen()
3、我們可以使用 fputs(stream,buffer)向文件寫入內容,
可以使用 fgets(buffer,sizeof(buffer),stream) read string from stream.
4、無格式 IO
(1)、字符 I/O:所謂字符 I/O,就是每次只讀出 1 個字符
函數接口:fgetc(stream) and getc(stream) getchar(void)
fputc(int c,stream) and putc(int c,stream),putchar(int
c)
fgetc(stream):get the character from stream one by one,return int
getc(stream):just like fgetc(stream)
getchar(void):just like getc(stdin)
fputc(int c,stream):trans (int)c to (u_char)c,then write to stream
putc(int c,stream):like fputc(int c,stream)
putchar(int c):same ...
(2)、行 I/O:所謂行 I/O,就是每次處理的是一行
函數接口:
fgets(char* s,int count,FILE* fp):read count characters
gets(char*s):just read one line end with '\n'
getline(char** ptr,size_t *n,FILE* fp):read line
getdelim(char** ptr,size_t *n,int delimiter,FILE* fp)
5、文件定位
函數接口:
ftell(stream):return the current pos in stream
fseek(stream,long int offset,int whence):change the pos of stream
rewind(stream):set pos in the file's bugin
fgetpos(stream,fpos_t* pos):get the current pos
fsetpos(stream,const fpos_t* pos):set stream's pos
6、文件結束和錯誤指示器
feof(stream):return 0 if EOF
ferror(stream):return 0 if ERROR
7、流緩衝
(1)、全緩衝:以整個緩衝區爲單位進行讀寫,每次都讀寫固定 BUFFERSIZE 大小的內容。
(2)、行緩衝:只有遇到行結束標誌才進行讀寫
(3 )、無緩衝:沒有設置緩衝
函數接口:
setbuf(fp,char* buf):可以設置 buff 的大小,要關閉緩衝則將 buf 設置位 NULL
setvbuf(fp,char* buf,int type,size_t size):
type 可以位一下之一:
_IOFBUF->全緩衝
_IOLBUF->行緩衝
_IONBUF->無緩衝
可以通過 size 指定緩衝的大小,只有當設置位無緩衝時 size 才無效。
fflush(fp):刷新緩衝區
8、格式 I/O
(1)、格式輸出
printf(const char* format...):output to std
1fprintf(stream,const char* format...):output to stream
sprintf(char* buf,const char* format...):out put to buf
(2)、格式輸入
scanf(const char* format...):read from std
fscanf(stream,const char* format...):read from stream
sscanf(char* buf,const char* format...):read from array buf.
9、臨時文件
tmpnam(char* s)
tempnam(const char* dir,const char* pfx):用戶可以自己指定文件路徑和文件名前綴
tmpfile(void):生成一個臨時文件並且打開它
10、文件描數字的打開、創建、關閉
函數接口:
int open(const char* filename,int flags,mode_t mode/*this is
choosed...*/)
=>這個函數將創建一個文件描數字,並且將這個文件描數字返回,參數位文件名字,打開標

和可選的創建模式,但是 mode 只有在創建文件時纔是有效的。
int create(const char* filename,mode_t mode)
=>這個函數將創建一個新的文件,返回創建好的文件的文件描數字,需要指定文件名和創建
文件的 mode,這個函數其實等價於 fd=open(filename,O_WRONLY|O_CREAT|
O_TRUNC,mode)
int close(int filedes)
=>這個函數就是關閉一個文件描數字,需要注意的是當我們調用這個函數關閉一個文
件描數字
的時候,這個不再被使用的文件描數字可以被被再次使用,我們可以想象文件描數字其實
就是一個數組的下標,這個數組就是保存文件項的數組,當一個文件被關閉,那麼這個文件所
佔有的數組項將可用
ssize_t read(int filedes,void* buffer,size_t nbytes)
=>從已經打開的與文件描數字 filedes 相連接的文件中讀取 nbytes 個字節的內容到
buffer 中
ssize_t write(int filedes,const void* buffer,size_t nbytes)
=>向已經打開的與文件描數字 filedes 相連接的文件中把 buffer 中前 nbytes 字節寫進

無論是 read 還是 write,都將返回所操作的字節數目,不一定就是 nbytes,有可能遇到
文件尾而不能繼續操作...
11、問價描數字的文件位置
函數接口:off_t lseek(int filedes,off_t offset,int whence)
這個函數和 fseek(在流中)是一樣的,其中的 whence 只能取下列中一個:
SEEK_SET:從文件開始出開始偏移
SEEK_CUR:從當前文件位置開始偏移
SEEK_END:從文件尾部開始偏移
那麼,如何移動文件位置到文件尾部呢? ---> lseek(fd,0L,SEEK_END)
移動到文件開始處可以這樣做
---> lseek(fd,0L,SEEK_SET)
-------------------------------
需要注意的一點是,當我們用 lseek 來設置文件位置後,如果設置的文件位置大於當前文件大小,這
也是被允許的,這樣的
話,下一次開始寫將擴展該文件,這種情況稱爲在文件中生成了空洞!這些位置將被 0 填充。
【notice 】
(1)、lseek 只是會改變文件的位置,而不會進行任何 I/O 操作,所以它不會改變 inode
中的文件大小,也就是說,即使我們設置的文件位置超出了文件的大小,它也不會導致當前文尾的
改變,只是下次打開文件並且以 O_APPEND 模式下時會自動擴展文件
(2)、每當用 O_APPEND 標誌打開文件執行 write 操作時,當前文件首先移動到 inode 給出的文
件大小
2所指定的位置(所以會自動擴展),這也是實現在這種模式下只能從當前文件尾添加內容的依據。
12、dup 和 dup2 函數
函數接口:
int dup(int old):複製 old 文件描數字到最小可用新描數字(返回值)
int dup2(int old,int new):複製 old 文件描數字到 new 文件描數字,如果 new 文件描
數字一句被佔用,那麼
系統將首先關閉 new 文件描數字,然後再複製過去;如果 old==new,那麼只返回 new 但是不
關閉。
什麼叫做輸入輸出重定向?
==>改變一個特定文件描數字對應的文件或者管道
13、fdopen 和 fileno、fcntl
函數接口:
FILE* fdopen(int filedes,const char* opentype):將 filedes 指定的文件描數
字轉換位文件流
int
fileno(FILE* stream):獲取文件流的文件描數字
int
fcntl(int filedes,int cmd,...):用它可以對一句打開的文件描數字進行各
種操作,例如,重複一個
文件描數字、查詢或者設置文件描數字標籤、查詢或者設置文件描數字狀態標籤等
|-------------fcntl 命令常數名字及其含
義----------------------------------|
F_DUPFD
重複文件描數字
F_GETFD
獲取文件描數字標籤
F_SETFD
設置文件描數字標籤
F_GETFL
獲取文件狀態標籤
F_SETFL
設置文件狀態標籤
F_GETLK
獲取文件鎖
F_SETLK
設置文件鎖
F_SETLKW
等待完成的設置文件鎖
F_GETOWN
獲取收到 SIGIO 信號的進程或者進程組
F_SETOWN
設置....
|--------------------------------------------------------------------
---|
-
14、文件狀態標籤
什麼叫文件狀態標籤呢? ---->文件狀態標籤指明文件的打開屬性,他們由 open 的 flag 參數指
定。
文件狀態標籤可以分爲三類,如下:
|---------------文件狀態標籤----------|
(1)、訪問方式
-------------------------------------------------------------------
O_RDONLY,O_WRONLY,O_RDWR
-------------------------------------------------------------------
(2)、打開時標誌
-------------------------------------------------------------------
O_CREAT
->create the file if not exist now
O_EXCL
->當 o_creat 和此同時設置時,open 失敗,保證已經存在的文件不會被覆

O_NONBLOCK ->設置爲費阻塞 I/O
O_NOCITY
->...
O_TRUNC
->...
-------------------------------------------------------------------
3(3)、I/O 操作方式
-------------------------------------------------------------------
O_APPEND ->APPEND FILE IN THE END OF THIS CURRENT FILE
O_NONBBLOCK ->...
O_ASYNC
->..
O_SYNC
->..
O_DSYNC
->...
O_RSYNC
->...
-------------------------------------------------------------------
15、什麼叫做阻塞 I/O?
調用必須等待所有操作完成,即讀寫到數據(read)才能返回,非阻塞 I/O 就是:如果操作不能及
時完成,
函數就會返回錯誤信息。
有兩種方法可以指定非阻塞 I/O
(1)、在 open 時指定 O_NONBBLOCK
(2)、對已經打開的描數字,調用 fcntl 來設置
16、readv and writev
函數接口:
ssize_t
ssize_t

readv(int fildes,const struct iovec* iov,int iovcnt)
writev(...):
->writev 將 iov[0],iov[1]...iov[iovcnt-1]指定的存儲區中的數據寫到 fildes 指定
文件描數字中,返回值是寫出的數據總字節數。readv 則按 iov[0]...iov[iovcnt-1]規定
的順序和長度,分散的從 fildes 中讀出數據放到相應的位置裏面。
17、文件與目錄
什麼叫做文件呢?文件由那些部分組成?
答:文件本質上是一個存放數據的容器,文件由數據塊和 inode 組成。可以用 stat 命令查看某
個文件的 inode 信息。
18、stat、fstat、lstat 函數
函數接口:
int stat(const char* filepath,struct stat* buf):獲取指定文件路徑文件的
inode 信息,存在 buf 中
int
lstat(const char* filepath,struct stat* buf):當給定的文件不是符號
鏈接時,和 stat 一樣,否則 stat 將返回
鏈接所引用的文件 inode,而 lstat 返回鏈接本身的 inode
int
fstat(int filedes,struct stat* buf):獲取指定文件描數字的 inode 信息,
保存在 buf 中
19、文件類型
文件類型由 stat 結構的 st_mode 給出,unix 文件有普通文件、目錄、符號鏈接、特別文件、
FIFO、套接字等。
目錄:目錄是一種特殊的文件,但是用戶不能寫目錄。目錄是由目錄登記項組成的一張表,目錄中
4的每個文件和子目錄在其中有一個登機項,每個
登記項用來映射文件名到它的 inode,結構很簡單,一個 inode 號和對應的文件名。
-------------------------------------------------------------------------
-----------------------------------------------------
鏈接:unix 文件系統提供一種使得多個文件名錶示同一個文件的機制,這種機制就稱爲鏈接。系統
簡單的通過在目錄中建立一個新的登機項來表示這種
鏈接,這個登記項有一個新的文件名字和要鏈接的 inode 號。一個文件無論有多少個鏈接,磁盤上
只有一個描述文件的 inode,每個文件的鏈接數保存在
stat 結構中的 st_nlink 上面,最大允許的鏈接數量是:LINK_MAX
硬鏈接(鏈接)的缺點:
(1)、inode 號是系統內部管理的一個稱爲 i_list 表格的索引,unix 爲每個操作系統維護着一
個 i_list 表格,所以,inode 號在跨文件系統時 inode 號
可能相同,所以硬鏈接不能進行在跨文件系統之上
(2)、只有超級用戶可以創建目錄的硬鏈接
函數接口:
int link(const char* exitingpath,const char* newpath)
----------------------------------------------------------------------------
--------------------------------------------------
符號鏈接:符號鏈接是指向例外一個文件的特殊文件,是爲了克服硬鏈接不能跨文件系統的缺陷而
設計的,符號鏈接不是指向 inode,符號鏈接有自己的
inode,符號鏈接保存所指向文件的路徑名。而且,符號鏈接可以指向一個不存在的文件,但是直
到這個文件被創建,這個鏈接纔能有效,當然反過來,
如果一個符號鏈接所指向的文件被刪除了,那麼這個符號鏈接依然存在,相當於指向了不存在的文
件,直到這個文件再次被創建的時候這個符號鏈接纔是
有效的!
函數接口:
int symlink(const char* path,const char* sympath):創建一個符號鏈接文字
sympath,這個鏈接文件指向 path
int readlink(const char* pathname,char* buff,int bufsize):讀出 pathname
中的內容,保存在 buff 中,然後關閉文件
20、特別文件
什麼叫做特別文件呢?
=>特別文件也稱爲設備文件,特別文件不包含數據,他們的作用是將物理設備映射到文件系統
中的文件名,在 unix 系統中,每一種設備都至少與一個特別文件相
鏈接,特別文件可以用 mknod 系統調用來創建,並且與內核的一個軟件相連,這個軟件就稱爲設備
驅動程序
有兩個類型的特別文件。塊特別文件和字符特別文件
什麼叫做塊特別文件呢?
塊特別文件與塊設備相連,塊設備按特定大小並且隨機訪問的塊來執行 I/O,比如硬盤。
什麼叫做字符特別文件呢?
字符特別文件與字符設備相連,字符設備可以存儲和傳遞任意大小的數據,有一些字符設備可
以一個字符一個字符的傳遞數據,比如鍵盤。
21、文件的類型:文件類型保存在 stat 結構裏面的 st_mode 裏面,st_mode 包含兩種信息,文件類
型和文件方式。
可以用相關的宏來測試是不是屬於某種文件
S_ISREG(mode_t m): 普通文件
S_ISDIR(mode_t m): 目錄文件
S_ISCHR(mode_t m): 字符特別文件
S_ISBLK(mode_t m): 塊特別文件
S_ISLNK(mode_t m): 符號鏈接
5S_ISFIFO(mode_t m): 管道或者 FIFO
S_ISSOCK(mode_t m):套接字
22、文件的屬性和用戶組
文件的屬性和用戶組信息可以在 stat 結構中的 st_uid and st_gid
you can use these function to change the uid and gid of the file:
int chown(const char* pathname,uid_t owner,gid_t group)
int lchown(const char* pathname,uid_t owner,gid_t group)
int fchown(int filedes,uid_t owner,gid_t group)
23、文件訪問方式
文件訪問方式規定了文件訪問權限,一共 9 位文件方式位,他們的含義從左到右分別爲:
000 000 000
執行、寫、讀
分爲三組,每組三位,每組中從做到右分別表示可執行、可寫、可讀。
這三組分別位用戶自己、同組成員、其他用戶
可以在 stat 裏面的 st_mode 下面找到這些信息
----------------------------------------------
S_IRUSR:用戶讀
S_IWUSR:用戶寫
S_IXUSR:用戶執行
S_IRGRP:...
S_IWGRP:...
S_IXGRP:...
S_IROTH:...
S_IWOTH:...
S_IXOTH:...
------------------------------------------------
24、改變文件創建屏蔽
函數接口:
int chmod(const char* filename,mode_t mode)
int fchmod(int filedes,mode_t mode)
25、檢驗文件是否具有某種權限:
函數接口:
int access(const char* filename,int how):判斷調用進程是否允許按參數 how 指定
的方式訪問 filename 指定的文件
其中,how 的取值可以爲下列:
----------------------------
R_OK: test the read root
W_OK: test the write root
X_OK: test the exe root
F_OK: test exiting
----------------------------
注意:access 函數是根據進程的實際用戶 ID 和實際組 ID,而不是有效用戶 ID 和有效組 ID 來檢
查調用進程是否具有該權限,如果允許
則這個函數將返回 0,否則將返回-1
26、截斷文件
上面叫做截斷文件呢?
=>截斷文件就是將已經打開的文件縮減成我們指定的長度
函數接口:
6int ftruncate(int filedes,off_t length)
int truncate(const char* pathname,offset_t length)
這兩個函數都是將已經打開的文件截斷位 length 字節的大小的文件
27、文件的時間
應用可以利用 utime 函數來改變文件的時間,還有一個更加準確的函數是 utimes
函數接口如下:
int
utime(const char* pathname,const struct utimebuf* times)
the struct utimebuf is like that:
struct utimebuf{
time_t actime;//文件的訪問時間
time_t modetime;//文件的修改時間
};
int utimes(const char* path,const struct timeval values[2])
the struct timeval is like that:
struct timeval{
long tv_sec;//second num
long tv_usec;//micsecond num
};
28、文件的刪除與換名字
函數接口:
int unlink(const char* path):用於刪除一個文件名,並且減少該文件名的鏈接計數,
如果鏈接計數爲 0,文件的內容便被刪除
普通用戶是不能調用 unlink 來刪除一個目錄的,但是有其他的函數接口可以解決:
int rmdir(const char* pathname)
int remove(const char* pathname)
29、修改文件或者目錄的名字
函數接口:
int rename(const char* oldname,const char* newname):將 oldname 修改位
newname
30、目錄操作
工作目錄:每個進程都有一個目錄與之相連,這個目錄就是工作目錄,下面的函數接口可以獲取或
者修改進程的工作目錄
char* getwd(char* pathbuf)
char* getcwd(char* pathbuf,size_t size)
int
chdir(const char* pathname)
int
fchdir(int filedes)
---------------------------------------------------
關於創建目錄與刪除目錄
函數接口:
int mkdir(const char* pathname,mode_t mode)
int rmdir(const char* pathname)
-----------------------------------------------------
讀目錄流(用戶是不可能寫目錄的,只能讀):
需要說明的兩種數據結構:
DIR 數據結構:這就是目錄的結構
dirent 數據結構:每一個目錄項的數據結果,至少包括 inode 號和文件名字
函數接口:
DIR*
opendir(const char* dirname)
struct dirent*
readdir(DIR* dirp)
7int
closedir(DIR* dirp)
31、掃描命令行的參數
值。
函數接口:
int
getopt(int argc,char** argv,const char* optstring)
extern
char*
optarg
extern int
optind,opterr,optopt
說明:參數 optstring 給出合法的選項字符,選項字符後面可以跟着冒號 ‘ : ’ 以表示要求有選項
例如:當 optstring="if:ls" 時表示允許選項 -i,-f,-l,-s,而且-f 後面要求有選項
該函數每次獲取一個選項,如果存在選項,則該函數返回獲取到的選項,對於那些接受選項值
的選項,
它同時設置外部變量 optarg 指向選項值。
當遇到位置選項字符或者缺少選項值時,getopt 返回 ‘ ? ’ ,同時將該選項字符存儲於 optopt 中。
getopt 每次處理 argv 數組中的一項,同時設置外部變量 optind 爲下一個要處理的元素的下標,

沒有更多需要處理時,函數將返回-1
32、環境表
unix 系統用一個稱爲環境表的數據結構來表示所有的環境變量和變量值,它是一個指針,該數組的
每一個元素
都指向一個形如 “ name=value”的字符串,name 就是環境變量,而 value 就是環境變量的值
我們可以通過系統定義的全局變量 environ 來引用環境表,該變量稱爲環境指針,我們需要說明如
下語句來引用
系統的環境表:
extern char** environ;
這樣我們就可以查看環境變量及其值了。
當然我們不會這麼傻,我們有函數接口來直接獲取我們需要的環境變量的值,甚至是設置新的環境
變量和值
函數接口:
char* getenv(const char* name):就是取得名字叫做 name 的環境的值,如果不存在則
返回 null
int
putenv(char* str):設置環境變量,str 的格式形如 “ name=value”
33、終止進程
我們可以使用 exit 函數和_exit 函數來終止進程,但這兩個函數有一些不同,exit 會在終止之前
清理一些東西,在調用
_exit 函數返回到內核中,而_exit 函數直接終止進程並且返回到內核,這是很不安全的,不推薦
使用,其次,我們可以
使用 exit(EXIT_SUCCESS) and exit(EXIT_FAILURE)來表示程序是否是正常退出,更加複雜
的退出參數自己擴展把!~
函數接口:
void exit(int status);
void _exit(int status);
那麼我們現在來說說 exit 函數吧,既然 exit 函數需要在退出之前做一些事情,那麼這些事情是由
什麼人來做的呢?
這時候我們就需要知道一個註冊函數啦=>
int atexit(void(*func)(void))
參數是一個函數指針,沒有參數,以參數 func 調用 atexit 導致 func 所指向的函數被註冊爲程序
正常終止時要執行的函數
,所有註冊的函數以與註冊的順序相反的順序被調用,並且與調用的次數相同。
834、關於流產程序 abort
真的非常有趣,爲什麼叫 abort 爲流產程序呢?
首先,用 abort 來終止程序肯定是異常終止程序,但是有趣的事情是 abort 函數將在工作目錄中生
成一個內存轉儲文件 core
abort 會在退出之前關閉所有已經打開的流,其實所有的異常終止都是由信號造成的,abort 也是
通過生成一個信號來
流產程序的。例子將在後面介紹......
void abort(void)
35、進程的存儲空間
進程的地址空間和 PCB 反應了進程所運行程序的當前狀態,當進程用 exec 函數裝入一個新進程時,
內核便爲這個新的進程
創建了一個新的地址空間,進程的地址空間會有多個組成部分,如下:
正文段:存放程序的執行代碼,是隻讀的所以可以防止被惡意寫
初始化數據段:那些聲明瞭而且已經初始化的數據變量,如 int hujian=88888
爲初始化數據段:(bss 段),沒有初始化的變量,由內核賦予初始值
棧:存放函數內的變量等
堆:用於存放動態申請的變量
36、動態存儲與釋放
函數接口:
void *malloc(size_t size):這個函數將在申請一塊大小爲 size 的存儲空間,但是這個存儲
空間的初始值是不確定的,調用成功則
返回分配的首地址,並且保證是嚴格對齊的,調用失敗則返回 null 並且設置 errno 位 ENOMEM
void *calloc(size_t number_of_elem,size_t elem_size):很明顯這個函數用來爲結構
數組分配空間,第一個參數爲數組的大小,
第二個參數位數組元素的大小,分配成功將用 0 來填充,並且返回指向第一個元素的指針
void* realloc(void* ptr,size_t newsize):用於增加或者減少已經分配的內存大小,第
一個參數是已經獲得的內存指針,第二個
參數是要重新分配的內存大小,如果連續的延伸能夠滿足繼續分配,函數將不會移動數據,但是如
果不能滿足的話,那麼該函數將重新
尋找一塊內存,並且將數據搬遷到新的數據塊上面,然後返回新存儲塊的指針,所以千萬注意不要
丟失指針喔!
void* memalign(size_t size,size_t bound):這個函數是由用戶字節定義對齊字節,第一
個參數是要申請的內存大小,第二個參數是
對齊的字節
void* valloc(size_t):該函數只是對 memalign 函數的特殊使用,對齊是在頁邊界,實現如下:
void* valloc(size_t size){
memalign(size,PGSIZE);
}
37、一個很好玩的函數(兩個)
函數接口:
int
setjmp(jmp_buf env):設置返回點
void
longjmp(jmp_buf env,int val):返回 env,val 將作爲 setjmp 的返回值,如
果爲 0,則 setjmp 仍然返回 1
雖然很好玩,但是因爲和編譯器密切相關,一般應用很難用到,就不再贅述啦~
38、進程資源
(1)、上面叫做資源呢?簡單的講資源就是能夠影響進程的一些因素,比如進程最大存儲空間,可
使用的 CPU 時間等等,如果
這些東西沒有限制的話,那麼一個進程就舒服了,但那肯定是不可能的,我們能做上面呢那?我們應
該知道的一個事實就是,資源
是有限制的,那系統規定的資源限制的最大值稱爲硬限制,我們不能超過這個限制,但是我們可以
修改我們自己的進程的最大限制,
我們修改的最大限制稱爲軟限制,但是軟限制的最大值不能大於硬限制,我們可以減小我們自己的
硬限制,但是這是不可逆轉的!
9(2)、我們可以查看和設置資源限制
函數接口:
int
getrlimit(int resource,struct rlimit* rlptr):很明顯這是我們獲取資
源限制的函數啊
int
setrlimit(int resource,const struct rlimit* rlptr):很明顯是設置資
源限制啊
我們需要知道的結構:
struct rlimit{
rlim_t rlim_cur;//這是資源限制的軟限制
rlim_t rlim_max;//給出資源限制的最大值,也就是硬限制
};
對了,rlim_t 其實就是一個長整形...
對了還有,函數裏面有一個參數叫做 resource,那是上面呢?就是資源啊,資源可以取下面這些
常數:
-------------------------------------------------------
RLIMIT_AS:進程可用虛擬空間的最大字節數
RLIMIT_CORE:core 文件的最大字節數
RLIMIT_CPU:進程能夠使用的 CPU 時間
RLIMIT_DATA:進程數據段空間的最大值(初始化數據段,爲初始化數據段,堆空間之和的最大
值)
RLIMIT_FSIZE:進程能夠創建的最大文件的字節數
RLIMIT_MEMLOCK:可以用 mlock 和 mlockall 鎖住在物理存儲器的最大虛擬存儲字節數
RLIMIT_NOFILE:進程最多能夠打開的文件個數
RLIMIT_NPROC:最多能夠創建的子進程個數
RLIMIT_RSS:進程應當得到的最大物理內存頁數
RLIMIT_STACK:棧的最大字節數
RLIMIT_NLIMITS:系統中不同資源個數
RLIMIT_INFINTY:展示一個常數,表示對指定的資源沒有限制
----------------------------------------------------------
(3)、資源使用統計
函數接口:
int
getrusage(int who,struct rusage* rusage):報告當前進程或者該進程的所
有已經終止並且要等待的子進程
的資源使用情況,參數 who 指明是當前進程還是子進程,只能取如下兩個值:
---------------------------
RUSAGE_SELF:調用此函數的進程
RUSAGE_CHILDREN:該進程所有已經終止並且在等待的子進程
---------------------------
39、用戶信息
用戶名:函數接口:char* getlogin(void):返回用戶名
用戶數據庫:
用戶數據庫將保存所有的用戶信息,在現在的 unix 中有兩個文件屬於保存用戶信息的:
/etc/passwd and /etc/shadow
前者稱爲口令數據庫文件,對所有用戶開放。後者稱爲口令影子文件,只有特權用戶才能訪問。
這連個文件中,沒一行都由七個區域組成,兩個區域之間用冒號隔開,格式如下
------------------------------------------------------------------
user_name:command(x means no
command):user_id:user_group_id:user_name,address,telephone_job,telephone_hom
e:user_dir:shell
這七個域由一個叫做 passwd 的結構來表示:
struct passwd{
char* pw_name;
char*
pw_passwd;
uid_t pw_uid;
gid_t
pw_gid;
10char*
pw_gecos;
char*
pw_dir;
char* pw_shell;
}//end of struct
我們有兩種方式來讀取數據庫文件,第一種是給定用戶名或者用戶 id 的情況下查找特定的一項,如
果沒有則返回 null
struct passwd*
getpwuid(uid_t uid):根據 uid 來查找信息
struct passwd*
gtpwnam(const char* name):根據用戶名來查找信息
下面一種方法就是掃描整個數據庫,一個一個用戶的讀所有用戶的數據項
函數接口:
struct passwd*
getpwent(void)
void
setpwent(void)
void
endpwent(void)
有趣的是,每次調用 getpwent,都是指向下一個信息,第一次調用是返回首個信息,然後再
次調用則返回
下一個信息,如果沒有了,就返回 null
setpwent 和 endpwent 分別打開和關閉數據庫
------------------------------------------------
struct passwd* getpwnam(const char* name)
{
struct passwd* ptr;
//open the database
setpwent();
while((ptr=getpwent())!=NULL){
if(strcmp(name,ptr->pw_name)==0){
break;
}//end if
}//end while
//close the database
endpwent();
return (ptr);
}
--------------------------------------------------
組數據庫:
和用戶數據庫一樣 /etc/group and /etc/gshadow 是組數據庫,前者包含每個組的信息,
後者包含
系統中所有組的信息每個組對應一行,每一行有四個域,中間有冒號隔開:
如:user:x:500:hujian,libai
lib:x:190:hujian
第一個域是組名,第二個是組 id,第三個域是口令,沒有則位 x,第四個是組成員,成員間用
逗號隔開,簡單說一下,hujian 不僅屬於
user 這個組,還屬於 lib 這個組,user 是基本組,lib 是附加組
函數接口:
在看函數接口之前,先看看 group 數據結構:
struct group{
char* gr_name;//group name
char*
gr_passwd;//group passwd
int
gr_gid;//group id
char** gr_mem;//point to member of group
};
下面就是函數接口:
struct group*
getgrgid(gid_t gid):get the group by gid
11struct
struct
void
void
group*
getgrnam(const char* name):get the group by name
group*
getgrent():one by one...
setgrent():open the database
endgrent():close the database
獲取附加組 id
函數接口:
int
getgroups(int gidsetsize,gid_t group_list[]);
該函數把調用進程的附加組 id 填進 group_list 中,第一個參數則是限定第二個參數的大小,
實際的附加組個數
由函數返回,如果只是想直到附加組的個數,那麼就指定第一個參數位 0,那麼第二個參數就不
會被填充。
40、進程的身份憑證
unix 系統提供了三組 id 來表示進程身份憑證
----------------------------------------------
第一組:指明實際用戶是誰
->實際用戶 id,實際組 id
第二組:用於操作和文件訪問權限測試
->有效用戶 id,有效組 id,附加組 id
第三組:由 exec 保存
->保存的調整用戶 id,保存的調整組 id
-----------------------------------------------
相關說明:
=>實際用戶 id 和實際組 id:由 login(1)程序在用戶註冊時設置。
=>有效用戶 id 和有效組 id:是進程當前起作用的 id,也稱爲現行用戶 id 和現行組 id,exec 加
載執行文件時,有效用戶 id
和有效組 id 默認設置成實際用戶 id 和實際組 id,但是如果文件設置了調整用戶/組 id 位以指明該
文件是要以調整用戶
id 或者是調整組 id 的方式來執行的話,exec 加載該文件時會將有效用戶 id 或者是有效組 id 改爲
文件擁有者的用戶 id
或者組 id。
有效用戶 id 和有效組 id 影響文件的創建和訪問,文件創建期,內核將文件的擁有者設置爲進程的
有效 id 和有效組 id,
文件訪問時,內核根據進程的有效 id 和有效組 id 來判斷進程是否有權限訪問文件。
=>保存的調整用戶 id/組 id:exec 在調整進程 id 之前,先保存進程的當前有效 id,以後可以恢
復。
函數接口:
uid_t
uid_t
gid_t
gid_t
gid_t
getuid():返回進程的實際用戶 id
geteuid():返回進程的有效用戶 id
getgid():返回進程的實際組 id
getegid():返回進程的有效組 id
getgroups(int count,gid_t groups[]):獲取進程的當前附加組 id
關於調整進程的身份:
進程總是可以調整它的有效用戶 id/組 id 回到實際用戶 id/組 id
函數接口:
int setuid(uid_t uid):如果進程有特權,那麼設置進程的實際用戶 id 和有效用戶 id
爲 uid,否則只設置有效
用戶 id 爲 uid,而且 uid 要麼等於實際用戶 id,要麼等於保存的調整用戶 id
int setgid(gid_t gid):same as setuid
int setreuid(uid_t r_uid,uid_t e_uid):-1 代表不設置,其他情況都設置
int setregid(gid_t r_gid,gid_t e_gid):same as setreuid
int seteuid(uid_t uid):只設置有效用戶 id,而且要有特權纔可以設置位其他,否則只
能設置位實際用戶 id 或者保存的調整用戶 id
12int
setegid(gid_t gid):same as seteuid
獲取進程當前有效用戶 id 的用戶名:
函數接口:
char*
cuserid(char* string)
注意:getlogin 得到的是運行進程的用戶註冊名,cuserid 得到的是與進程有效用戶 id 相
連的用戶名。
41、進程標誌
unix 中每個進程都有一個唯一的進程標誌,稱爲進程 id 或者 pid,pid 的類型是 pid_t,它是整
數類型
函數接口:
pid_t getpid():可以獲得進程的 pid
pid_t
getppid():可以獲得父進程的 pid
關於進程創建:
pid_t
fork():由進程 fork 出一個和自己一模一樣的進程,fork 成功時,子進程的返回
值爲 0,父進程的返回值
是新創建的子進程的 pid,若 fork 失敗,則返回-1 並且將設置 errno 以告知錯誤信息。
注意,fork 會返回兩次,一次是返回子進程,一次是返回父進程,需要用 if 來判斷是返回哪
個進程。
if(pid==0) child else father.
42、fork 出來的子進程和父進程之間的區別
相同點:實際用戶 id,實際組 id,有效用戶 id,有效組 id,附加組 id,會晤期 id 和控制終端,
調整用戶 id 標誌和調整組 id 標誌,當前
工作目錄和根目錄,文件創建屏蔽,信號屏蔽與設置,任何打開的文件描數字和執行時關閉標誌,
環境變量,所有相連的共享存儲段,
資源限制。
不同點:有自己唯一的 id,有各自不同的付父進程 id,進程操作文件描數字不會影響父進程,但文
件位置相同,子進程耗費的時間量
被設置爲 0,子進程不繼承父進程的文件鎖,定時器,任何懸掛信號將被清除,但繼承信號屏蔽和信
號動作。
vfork:
pid_t
vfork():該函數和 fork 函數一樣,不同的有兩點,fork 創建進程的整個地址空
間副本,而 vfork 不復制這一個副本,
第二個是 fork 允許父進程和子進程相互獨立的運行,但是 vfork 不允許,vfork 在借出父進
程的地址空間給子進程的同時阻塞
父進程,父進程被懸掛直到子進程調用了 exec 或者_exit,此時內核返回地址空間給父親進程
並且將其喚醒。
現在我們應該直到一件事情就是,什麼叫做 “ 寫- 複製 ” ,用這樣的方法時,父進程的數據和棧空間臨
時稱爲只讀的並且標誌位 “ 寫- 複製 ” ,
子進程一開始與父進程共享存儲頁,如果子進程或者父進程企圖修改某頁時,缺頁中斷將會出現,
於是複製該頁成爲新的可寫頁,這樣,
只有那些修改了的也需要複製,而不是複製整個地址空間,如果子進程調用 exec 或者 exit,這些
也將會轉換爲他們原來的保護並且將
“寫- 複製 ” 去掉。
43、執行程序(裝載程序)
exec 函數組:
int execl(const char* path,const char* arg0,...,(char*)0):
int
execlp(const char* file,const char* arg0,...,(char*)0):
int
execle(const char* path,const char* arg0,...,const char*
envp[]):
13int
int
int
envp[]):
execv(const char* path,const char* argv[]):
execvp(const char* file,const char* argv[]):
execve(const char* path,const char* argv[],const char*
一點解釋:p 結尾的函數意味着第一個參數是文件名 file,並且使用 PATH 環境變量來尋找可執行
文件。
l 結尾的函數意味着函數中含有新程序的參數表
e 結尾的函數具有 env 數組作爲參數,而不適用當前的環境變量表
44、等待進程完成
函數接口:
pid_t
wait(int *stat_loc):該函數首先檢查調用進程是否有任何已經終止了的子
進程,
如果有的話,它立刻返回,如果沒有的話,wait 將阻塞調用進程直到有一個子進程結束,
wait 將返回
已經終止的子進程的 pid,並且將該進程的終止狀態存儲在 stat_loc 中,當調用進程同時有
多個子進程
終止時,wait 將任意返回一個,如果調用進程沒有任何子進程,函數將返回-1,並且設置
errno
下面是一些用於檢查 wait 或者 waitpid 返回狀態的宏定義:
-----------------------------------------------------------------------
WIFEXITED(stat_val):正常終止,則位真
WIFSIGNALED(stat_val):異常終止,值爲真
WIFSTOPPED(stat_val):是被停止的子進程,則爲真
WEXITSTATUS(stat_val):可以獲得個體 exit 傳遞的出口狀態
WTERMSIG(stat_val):引起子進程終止的信號數量
WSTOPSIG(stat_val):引起子進程停止的信號數
------------------------------------------------------------------------
45、通過兩次調用 fork 來避免僵死進程
原理:
第一個子進程調用 fork 生成另一個子進程來執行程序並先於第二個子進程
而終止,由於第一個子進程的父進程調用了 waitpid,它不會成爲系統的
僵死進程。而第二個子進程沒有調用 wait 來等待,但是由於父親進程(第
一個子進程)先於它而終止使得它被 init(pid=1)繼承,這時它是活躍的
所以不會稱爲僵死進程,當它要終止時,init 進程將調用 wait 釋放它的
proc 結構,這是多麼神奇的事情啊
46、system 函數
這是一個集成的函數,它可以實現由一個程序執行另一個程序
函數接口:int sysytem(const char* command)
47、獲取和設置進程組
什麼叫進程組呢?
我們只需要直到進程組有唯一的進程組 id,每個進程組有一個進程組長,進程組長的進程 id 也就
是進程組
的進程 id,下面就是函數接口:
pid_t
getpgrp():獲取進程組的 id
int
setpgid(pid_t pid,pid_t pgid):改變進程 pid 的進程組 id 爲 pgid
48、會晤期
會晤期是一個或者多個進程組的集合,每一個進程屬於一個會晤期和一個進程組。每個會晤期有一
個會晤期主席,它就是
14創建會晤期的那個進程,下面是一個可以創建一個會晤期的函數接口:
pid_t setsid();
需要注意的是,當會晤期主席終止時,它會結束會晤期。
49、控制終端
每一個會晤期可以有一個控制終端,進程可以通過控制終端進行輸入輸出等操作。
需要注意的幾點是:
每個進程可以有一個控制終端。
每個會晤期可以控制終端也可以沒有控制終端。
下面這個函數將可以得到控制終端的文件名:
char*
ctermid(char* ptr):將保存在 ptr 中,但是如果 ptr 是空指針的話,那麼該
函數將返回得到的
控制終端文件名,一般是 /dev/tty
50、信號處理
上面叫做信號呢?
在 unix/linux 裏面,信號是作爲通知進程發生了某種事件的手段,並且其發生常常與進程當
前的活動無關,信號也稱爲
軟終端,它提供了一種處理異步事件的方法,產生信號的原因有很多,比如:
(1)、用戶按下了某個終止鍵
(2)、程序異常
(3)、kill 函數允許進程發送任何信號給其他進程或者進程組
(4)、當發生了某種必須讓進程知道的事件時,也會產生信號
(5)、企圖讀寫終端的後臺進程會得到作業控制信號
(6)、當進程超越了 CPU 或文件大小的限制時
生成信號的事件可以歸爲三類:程序錯誤,外部事件,顯示請求
信號的生成可以是同步的也可以是異步的,同步信號與程序中的某個操作有關並且在那個操作
進行的同時產生,異步信號
是進程之外的事件生成的信號,一般外部事件總是生成異步信號。
當一個信號到達進程時,進程可以選擇如下動作來處理到達的信號:
(1)、忽略信號
(2)、捕獲信號
(3)、執行系統默認動作
(4)、流產
(5)、終止
(6)、忽略
(7)、掛起
(8)、繼續
一些術語:
生成信號(發送信號):信號出現
交付(接收):進程識別了信號
懸掛:信號已經生成,但是還沒有交付
對於每一種信號,在某個時刻它對應的動作安排稱爲信號的佈局。
信號屏蔽字:每個進程有一個信號屏蔽字,就是來限制信號的識別,只識別我們需要的信號就可以
了。
51、打印信號數對應的描述信息
函數接口:
void psignal(int signo,const char* msg):
52、產生信號
15的
函數接口:
int
raise(int sig):簡單的發送信號 sig 給調用它的進程,如果安裝了信號 sig
信號句柄,則 raise 函數將在句柄函數返回之後才返回
int
kill(pid_t pid,int sig):向進程 pid 發送信號,sig 爲 0 則表示空信號,空信
號一般用來
檢測 pid 的合法性,如果進程 pid 不存在的話,向這個進程發送空信號是不會成功的,函數將
返回-1 並且設置
errno 信號,函數成功則返回 0,,參數 pid 可以是下面的四種情況之一:
(1)、pid>0:信號發送給進程號爲 pid 的進程一個 sig 信號
(2)、pid==0:發送給所在進程組的所有進程 sig 信號
(3)、pid<-1:信號發送給進程組 id 爲 |pid| 並且發送者有權向他發送信號的進程一個
sig 信號
(4)、pid==-1:廣播信號,到所有有權向他發送該信號的進程
53、設置信號的動作
首先給出函數接口:
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum,sighandler_t handler):參數 signum 指
明是哪一種信號,handler 就是我們
要做的動作,可以是下列三種情況之一:
(1)、SIG_DFL:採用默認動作,由系統指定動作函數
(2)、SIG_IGN:忽略該信號,但是有一些信號是不能忽略的,比如 SIGKILL,SIGSTOP
(3)、信號句柄,也就是我們自己定義的函數指針
需要注意的是,信號句柄應該是一個只有一個參數而且沒有返回值的函數,參數指明信號的類
型。
當 signal 函數調用成功時,返回值就是上述三種情況之一,如果調用出錯,那麼它將返回
SIG_ERR
並且設置 errno。
54、sigaction 函數
函數接口:
int sigaction(int signum,const struct sigaction* act,struct
sigaction*oact):
struct sigaction{
void
(*sa_handler)();
void
(*sa_sigaction)(int,siginfo_t*,void*);
sigset_t sa_mask;
int
sa_flags;
};
和 signal 函數一樣,sigaction 函數的第一個參數用來指定信號,第二個參數是新的信號動
作,爲 null
時則不改變信號的動作,第三個參數將獲得老的信號動作。
55、一個編譯器的修飾符
volatile:告訴編譯器這個變量會在某些地方可能會被修改,所以編譯器就不會對這個變量做
一些不必要的優化。
56、sigset_t 類型和信號集操作
信號屏蔽是被阻塞的信號集合,unix 中表示信號集合的數據類型是 sigset_t,下面是操作信號集
合的函數接口:
int sigemptyset(sigset_t *set):初始化信號集合爲空,即不包含任何信號,總是返回
0
16int sigaddset(sigset_t* set,int signo):向 set 中增加 signo 信號,成功返回 0,
失敗返回-1
int sigdelset(sigset_t* set,int signo):從 set 中刪除 signo 信號,成功返回 0,
失敗返回-1
int sigismember(const sigset_t* set,int signo):測試 signo 是否在 set 中,若
在返回 1,不在返回 0,失敗返回-1
57、設置信號屏蔽
函數接口:
int
sigprocmask(int how,const sigset_t* set,sigset_t *oset):用於改變
或者測試調用進程的信號屏蔽。
如果 set 不是 null 的話,它指向用於改變信號屏蔽的信號集合,此時參數 how 指明如何改變
信號屏蔽,oset 將返回原先
的信號屏蔽,下面是 how 的可取值:
(1)、SIG_BLOCK:阻塞 set 所指向集合的信號,即將它們加入到當前的信號屏蔽中
(2)、SIG_UNLOCK:放開 set 所指信號集合中的信號,即將它們從當前信號集合中除去
(3)、SIG_SETMASK:用 set 所指項的信號集合作爲新的信號屏蔽集合,拋棄原來的信號屏

58、檢查懸掛信號
函數接口:
int sigpending(sigset_t* set):將懸掛信號保存在 set 中
59、等待信號
函數接口:
int
pause(void):僅僅是等待信號出現,放在循環裏面用,用來等待信號出現
int
sigsuspend(const sigset_t* sigmask):用參數所指項的信號集合臨時替代調
用進程的信號屏蔽,
然後掛起調用進程直到有不屬於 sigmask 的信號到達
60、信號棧
所謂信號棧就是信號句柄所在的棧空間,一般情況下信號棧和用戶棧在一個位置,所以不安全,
我們需要
建立自己的信號棧來安放信號句柄。
函數接口:
int
sigaltstack(const stack_t* ss,stack_t* oss):很明顯 ss 是我們自己指定
的信號棧,oss 是原來的信號棧
stack_t 至少包含一個棧開始地址,棧大小,和棧狀態標誌,對於棧狀態標誌,只能取下面兩
種情況:
SS_ONSTACK:表示棧將開始生效,ss_sp+ss_size 這塊空間就是我們定義的信號棧
SS_DISABLE:這表示不用此棧,此時忽略 ss
函數成功成功返回 0,失敗返回-1,這個函數只是準備好了一個信號棧,至於哪些信號能用這
個信號棧,我們需要在
用 sigaction 函數時通過 SA_ONSTACK 標誌來指定。
61、關於時間的函數接口
time_t
time(time_t* tloc):返回從 unix 紀元開始的總秒數,如果 tloc 不是空指針,
那麼時間也可以保存在 tloc 裏面
double
difftime(time_t time_current,time_t time_start):計算兩個時間之間的
間隔秒數
int
gettimeofday(struct timeval* restrict tp,void *restrict tzp):
struct
timeval{ time_t tv_sev;suseconds_t tv_usec;};
上面這個函數將返回更加精確的時間,保存在 tp 中。
int
settimeofday(const struct timeval* tp,const void* tzp):設置時間,tzp
在 linux 下應該位空。
int
adjtime(const struct timeval* delta,struct timeval*
olddelta):這個函數用來調整系統時鐘,
delta 就是我們需要調的時間數量,爲負數表示要調慢,正數表示要調快。
17struct tm* gmttime(const time_t* time):將 time 轉換爲可讀形式
struct tm*
localtime(const time_t* time):得到本地時間
time_t
mktime(struct tm* brokentime):將可讀的時間轉換爲秒
62、格式化日期與時間
char*
asctime(const struct tm* tmptr):返回字符串形式的時間
char*
ctime(const time_t* timeval):是將原始日期轉換爲可讀形式,返回結果和
asctime 一樣
size_t
strftime(char* s,size_z maxsize,const char* fomat,struct tm*
timeptr):
這個函數和 sprintf 函數一樣,將時間 tmptr 按照 fomat 的格式保存在 s 數組中,maxsize 爲 s
的大小,函數將返回實際
轉換的字符數量,但是不包括終止符號,所以如果 maxsize 等於返回值,那麼說明數組大小太小了。
char*
strptime(const char* buf,const char* fomat,struct tm* timeptr):
這個函數是第一個函數的逆函數
63、CPU 時間和牆鍾時間
程序的運行時間有 CPU 時間和牆鍾時間,CPU 時間就是程序佔用 CPU 的時間,是一個固定值,
牆鍾時間表示進程從開始到
運行結束的時間,可能包含了其他進程運行的時間,所以不能用牆鍾時間來表示程序的性能。
我們可以用下面的函數接口來實現得到 CPU 時間,其實 time 函數只能得到牆鍾時間:
clock_t
clock():返回進程的當前 CPU 時間,包括用戶時間和系統時間,類型
clock_t 表示系統內部的
時間單位(1/CLOCKS_PER_SEC),CLOCKS_PER_SEC=1000000,爲了統計程序運行的 cpu
時間,一般在程序的開始和結束
處調用 clock,然後兩次調用的結果相減。
64、times 函數
函數接口:
clock_t
times(struct tms* buffer):此函數和 clock 函數一樣返回時間,但
是返回的時間更加精確,函數
的返回值爲當前的牆鍾時間,我們應該知道的事情是,系統時鐘發出一次中斷信號稱爲一個滴
答,我們可以用函數
sysconf(_SC_CLK_TCK)來得到這個滴答值,我們來看看 tms 的信息:
struct
tms{
clock_t
tms_utime:用戶態下執行進程用去的 CPU 時間
clock_t
tms_stime:系統爲進程服務用去的時間
clock_t
tms_cutime:子進程的 tms_utime+tms_cutime
clock_t
tms_cstime:子進程的 tms_stime+tms_cstime
};
65、睡眠函數
unsigned int sleep(unsigned int seconds):睡眠 seconds 秒或者捕獲了某種信號
並且信號句柄已經返回
66、設置定時器
每一個進程都有三個獨立無關的間隔定時器:
牆鍾定時器(ITIMER_REAL):到期會發送 SIGALRM 信號個給進程
虛擬定時器(ITIMER_VIRTUAL):僅當進程運行在用戶態時才走動,會發送 SIGVTALRM 信號
剖面定時器(ITIMER_PROF):用戶態或者內核態都會走,會發送 SIGPROF 信號
函數接口:
unsigned int alarm(unsigned int seconds):一次性的定時器,只能設置牆鍾定時器,
每一個進程只有一個
牆鍾定時器,再次設置時將返回上一次未完成的秒數。
18int
setitimer(int which,const struct itimerval* value,struct
itimerval *oldvalue)
int
getitimer(int which,struct itimerval* value)
struct
itimerval{
struct timeval it_interval;//定時間隔時間
struct timeval it_value;//定時開始時間
};
參數 which 指明是哪一種定時器,value 就是我們設置的定時器,它是一個連續的定時器,函
數將會返回上一個
定時器在 oldvalue 裏面,第二個函數可以得到定時器
67、實時時鐘與設定
unix 裏面設定有多種類型的時鐘,每一種時鐘用時鐘 id 來標誌,這個時鐘 id 是
clockid_t(int),在 linux 中,有下列的時鐘:
CLOCK_REALTIME:這是系統範圍內的時鐘
CLOCK_MONOTONIC:在 linux 裏面,是從系統啓動以來的時間,不能改變
CLOCK_PROCESS_CPUTIME_ID:這是進程範圍的時鐘,他給出進程特定的 cpu 時間
CLOCK_THREAD_CUPTIME_ID:這是線程範圍的時鐘,給出線程特定的 cpu 時間
struct timespec{
time_t tv_sec;//seconds
long
tv_nsec;//nseconds
};
函數接口:
int
clock_getres(clockid_t clock_id,struct timespec *res):得到特定時
鐘的分辨率,出錯將返回 1
int clock_gettime(clockid_t clock_id,struct timespec* res):得到當前時間
int clock_settime(clockid_t clock_id,const struct timespec* res):設定時

68、實時睡眠
函數接口:
int
nanosleep(const struct timespec* req,struct timespec* rem):和
sleep 一樣,剩餘時間將存在 rem 中,成功返回 0,失敗返回-1
69、創建和刪除實時定時器
int timer_create(clockid_t clockid,struct sigevent* restrict
evp,timer_t* restrict timerid):
第一個參數是時鐘類型,第二個參數用於設定支持此定時器的信號交付,第三個參數存放生成
的定時器 id
int
timer_delete(timer_t timeid):刪除 timeid
70、設置實時定時器
int
timer_gettime(timer_t timerid,struct itimerspec
*value):value 中將保存定時器剩餘時間
int
timer_settime(timer_t timeid,int flags,const struct
itimerspec* restrict value,
struct itimerspec* restrict ovalue):就是設置,flags=TIMER_ABSTIME
71、定時器超期計數
什麼叫做超期計數呢?
就是定時器信號沒有得到及時處理的次數。
int
timer_getoverrun(timer_t timeid);
1972、與終端設備有關的兩個函數接口
int
isatty(int fd):如果文件描述字 fd 與一打開的終端設備相連,則函數返回
1,否則返回 0
char* ttyname(int fd):如果文件描述字 fd 與一個打開的終端設備相連,則返回那
個終端的名字,否則返回 NULL
73、文件鎖
文件鎖有讀鎖和寫鎖:讀鎖可以是多個,他的目標是防止其他進程來跟改正在讀的文件內容。讀鎖
也被成爲 “ 共享鎖 ” 。
寫鎖只能有一個。他的作用是隔離文件使得他所寫的內容保持一致,有互斥鎖的區域不能有其他鎖。
所以寫鎖也被稱爲 “ 互斥的鎖 ” 。
函數接口:
int
fcntl(int filedes,int cmd,struct flock* lock):
struct flock{
short l_type;//the lock's type,can be =>F_RDLCK,F_WRLCK,F_UNLCK
off_t l_start;//offset
short l_whence;//where ->SEEK_SET,SEEK_CUR,SEEK_END
off_t l_len;//[l_start,l_start+len]
pid_t l_pid;//the process
};
cmd 可以取得幾個命令值:
F_GETLK,F_SETLK.
74、當 len=0 時,他允許鎖住文件的任意區域,解這類鎖時,需要注意,當我們擴充了文件內容之後,
解鎖時的區域長度應當包括新擴充的部分。
75、
我們需要知道的一些關於文件鎖和文件與進程之間關係的內容:
(1)、鎖是和進程相連接的,我們需要知道什麼叫鎖,及其設置鎖的目的是什麼,我們設置鎖的目
的就是爲了不讓別的進程來和自己這個進程
一起訪問或者修改文件的內容,所以,就算時 fork 出來的子進程也不會繼承父進程的文件鎖,
這也是很好理解的。
(2)、鎖之和文件相連而不和文件描數字相連,儘管我們設置鎖時使用的是文件描數字。這意味着
當進程用描數字來關閉一個文件時,他在此
文件上設置的所有鎖都將被釋放,即使這些鎖是這個進程用其他文件描數字設置的。
(3)、進程終止時,系統會關閉這個進程打開的文件,從而釋放該進程設置的所有文件鎖。
76、建議鎖和強制鎖
fcntl 函數不僅可以提供建議鎖機制,還可以提供強制鎖機制。
建議鎖就是建議,內核並不管你是不是遵守建議鎖機制,read 或者 write 可以凌駕在建議鎖機制
之上。
也就是說,建議鎖就是用戶自己的鎖機制,和內核沒有關係,所以在使用建議鎖的時候,我們應該
遵守
建議鎖的機制。而強制鎖則由內核管理,排除一切不合法的文件訪問與操作。
強制鎖通過設置 id 來設置。
77、信號驅動的 I/O
所謂信號驅動的 i/o,也就是異步 i/o,但本質上還是同步 i/o。
採用信號驅動 i/o,當在描述字上有數據到達時,進程會收到一個信號,此時對該描數字進行輸入
20輸出不會阻塞。
linux 上有兩個可用於信號驅動的 i/o 的信號:
SIGIO,SIGURG,後者只用於通知網絡鏈接上到達了帶外數據。
如果在文件描數字上設置了 O_ASYNC,則當該描數字上由 i/o 活動時會生成 SIGIO 信號,另外,對
於套接字,當其上
到達帶外數據時,總是會生成 SIGURG 信號。
進程可以調用 fcntl 函數來指定接收信號的進程或者進程組,當然也可以獲取接收信號的進程或者
進程組:
(1).set the process/process group for the signal
int fcntl(filedes,F_SETOWN,pid):pid 爲正數時,表示進程 id,負數表示進程組 id,出錯
返回-1
int
fcntl(filedes,F_GETOWN):返回進程或者進程組,正數是進程,負數是進程組
實現信號驅動的 i/o 的步驟應該爲:
(1)、用 signalaction 來建立信號句柄
(2)、用 fcntl 來設置接收信號的進程或者進程組
(3)、如果要接收的是 SIGIO 信號,則需要用 fcntl 與 F_SETFL 來設置文件描數字的 O_ASYNC
標誌使得能生成該信號。
78、多路轉接 i/o
不少應用需要同時接收來自多個輸入通道的輸入,並且只要有輸入到達就必須接收,這類應用應該
多數是時間驅動的應用。
函數接口:
int select(int nfds,fd_set* rfds,fd_set* wfds,fd_set* efds,struct
timeval* timeout);
select 函數告訴內核,調用他的進程需要等待多種 i/o 事件,並且僅當這些事件中的一個或
者多個出現時,或者
指定的時間已經過去時,纔會喚醒調用他的進程。
我們先來解釋一下最後一個參數的意義:
(1)、當 timeout=null 時,表明進程願意永遠等待沒有事件限制
(2)、當 timeout=0 時則正好相反,表示進程不想等待,檢查描數字後立即返回
(3)、當 timeout>0 時,表面進程願意等待一段時間
nfds 參數指明要測試的描數字的個數,最多可以用宏定義 FD_SETSIZE 來獲得
參數 rfds,wfds,efds 分別給出要求內核測試的讀、寫和另外條件的描數字集合
一些宏定義:
void FD_ZERO(fd_set* fdset):初始化爲空集合
void FD_CLR(int filedes,fd_set* fdset):將 filedes 從所描述的集合中清除
int
FD_ISSET(int filedes,fd_set* fdset):判斷是否屬於,返回 1 表示屬於,
0 表示不屬於
void FD_SET(int filedes,fd_set* fdset):添加描述字集合
關於函數返回值:
(1)、返回值大於 0,指出三個描數字集合中已經就緒的描數字個數
(2)、等於 0,意味着在有描數字就緒之前計時器已經到期
(3)、-1,意味着出現了錯誤
所以,只有覈實了 select 函數返回值大於 0 時檢查集合纔有意義。
79、函數 poll
函數接口:
int poll(struct pollfd fds[],nfds_t nfds,int timeout):
poll 和 select 函數在本質上是一致的,poll 檢查一組文件描數字以查看是否有任何懸
掛事件,並且可以有選擇
的爲等待某個描數字上的時間而指定時間。
下面我們看看 pollfd 這個結構:
struct pollfd{
int
fd;//要檢查的描述字
short
events;//感興趣的事件
short
revents;//fd 上發生的事件
21};
poll 將返回 revents 的值,events 和 revents 的取值可以如下:
POLLIN:有可讀的數據
POLLPRI:有可讀的高優先數據
POLLOUT:可以無阻塞的寫數據
POLLERR:出現錯誤
POLLHUP:出現掛起
POLLNAVL:描數字引用的不是已經打開的文件
fds 指定 nfds 的元素個數,timeout 和 select 一樣。
80、異步 i/o
首先我們應該知道什麼叫同步和異步 i/o。
同步 i/o:同步 io 操作導致發出請求的進程被阻塞直到 io 操作完成
異步 i/o:異步 io 操作在 io 期間不導致發出請求的進程被阻塞
下面是一些異步 io 函數:
----------------------------------------------------------------
aio_read: 對指定的文件描數字啓動讀請求
aio_write:
對指定的文件描數字啓動寫請求
aio_error:
返回指定操作的錯誤狀態
aio_return: 返回已經完成操作的狀態
aio_suspend: 懸掛調用進程直到指定的請求中至少有一個完成
aio_cancel: 刪除懸掛在文件描數字上的一個或者多個請求
lio_listio: 啓動一組 io 請求
aio_fsync:
異步的將系統緩衝區中含義的文件已經修改的數據寫到永久存儲
----------------------------------------------------------------------
異步 io 控制塊:這是一個數據結構,用來傳遞給內核所需要的信息,因爲異步 io 是由內核完成的。
我們就來看看這個數據結構吧:
struct aiocb
{
int
aio_fildes;//要讀寫的文件描數字
off_t
aio_offset;//文件位置
volatile void* aio_buf;//數據緩衝區的地址
size_t
aio_nbytes;//要傳遞的字節數
int
aio_reqprio;//請求優先位移
struct sigevent
aio_sigevent;//信號結構,我們待會再說
int
aio_lio_opcode;//要執行的操作,只用與 lio_listio
}
aio_sigevent 用於指定當異步 io 操作完成時以什麼方式通知進程,他另外一個結構:
struct sigevent
{
union sigval
sigev_value;//應用定義的值
int
sigev_signo;//要生成的信號
int
sigev_notify;//通知類型
void(*sigev_notify_function)(union sigval);//通知函數
pthread_attr_t*
sigev_notify_attributes;//通知屬性
}
其中,通知類型如下:
SIGEV_NONE:事件發生時不產生信號
SIGEV_SIGNAL:信號發生時將生成信號
SIGEV_THREAD:信號發生時將執行指定的通知函數
異步 io 的一般流程:
22(1)、調用 open 函數來打開指定的文件獲得文件描數字,然後使得文件位置指向文件開始並選擇
合適的標誌
(2)、創建並且填充異步 io 控制塊 aiocb
(3)、如果使用信號,建立信號句柄來捕獲異步 io 操作完成信號
(4)、調用 aio_read,aio_write,lio_listio 請求異步 io 操作,或者調用 aio_sync 使得
異步 io 與磁盤同步
(5)、如果應用需要等待 io 操作完成,調用 aio_suspend,或者繼續執行調用 aio_error 來查
詢操作是否完成
(6)、io 操作完成後,調用 aio_return 抽取返回值
(7)、調用 close 關閉文件。close 函數在關閉文件之前將等待所有異步 io 完成
81、存儲映射 i/o
read 和 write 函數效率是很低的,可以想象更高層次的抽象的效率.....
因爲對於 read 和 write,每一個 i/o 活動都需要調用一次系統調用來完成,而且,這種方式的
i/o 活動,對於有
多個進程訪問的文件,每個進程的地址空間中都含義這個文件的副本,增加了內存開銷。
所謂存儲映射 i/o,就是爲了解決這個問題的。存儲映射 i/o 在內核緩衝區中最多隻有一個文件的
副本。
當多個進程需要訪問該文件時,這個文件的地址都將被映射到進程的地址空間。
進程可以對一個文件建立兩種類型的的映射---共享的和私有的。
對於共享映射,內核使得所有對文件共享頁的寫操作都直接作用於該頁的共享副本,並且當這一頁
刷新時,將它
存回硬盤。對於私有映射,寫文件的存儲映射頁導致複製該頁的一個副本,並且針對此副本進行更
新,但是這種更新
不影響底層文件,也不會被寫回到硬盤。
函數接口:
void*
mmap(void* addr,size_t len,int prot,int flags,int
filedes,off_t off):
調用這個函數的效果是:映射文件描數字 filedes 指定的文件的[off,off+len]區域到調用
進程的內存
[paddr,paddr+len]區域。
(1)、paddr:這是函數的返回值,也就是映射區在內存的開始地址
(2)、addr:應用指定的內存映射區的開始地址,如果爲 0,則內核會選擇一個合理的地址,
但是不會覆蓋原來的映射
(3)、len:映射的字節數,如果 len 不是頁大小的整數倍,那麼將被填充至頁的倍數,多餘
的部分將被 0 填充,但是不能修改
(4)、prot:指明映射區域的保護權限,可以取值如下:
a:PORT_READ:可讀區域
b:PORT_WRITE:可寫區域
c:PORT_EXEC:可執行區域
d:PORT_NONE:不能被訪問的區域
值得注意的是,爲映射區域指定的保護權限必須和 open 指定的權限一致。
(5)、flags:指明映射區域的屬性
a:MAP_FIXED:要求返回值必須等於 addr,爲了兼容性的考慮,最好不要使用
b:MAP_SHARDE:共享
c:MAP_PRIVATE:私有
需要注意的是,三個必須要指定一個。
(6)、filedes:已經打開的文件描數字
(7)、off:給出文件中要映射的開始字節位置,off 需要是字對齊的
解除映射:
函數接口:
int
len 指明長度
23
munmap(void* paddr,size_t len):paddr 應該是 mmap 返回的值,想要存回磁盤,需要下面這個函數:
int
msync(void* addr,size_t len,int flags):
addr 是存儲映射區的某個地址,len 確定從該地址開始的連續字節數,flags 指明控
制這塊區域的方式:
a:MS_ASYNC:執行異步寫,寫操作一旦開始函數將返回
b:MS_SYNC:執行同步寫,等待操作完成函數返回
c:MS_INVALIDATE:作廢與指定映射區不一致的高速緩衝副本
a 和 b 必須而且只能選一個,c 用於作廢與指定映射區域數據不一致的其他映射,也就
是說,如果一個程序有多個
進程都將同一個文件映射到存儲器,其中一個調用了 sync(修改內容寫回磁盤),則
其他所有進程的映射區都
將更新爲與此進程一樣的。只對 MAP_SHARED 有效。
82、unix 中的幾種 IPC 機制
(1)、匿名管道和 FIFO 有名管道
(2)、消息隊列,信號量和共享存儲
(3)、套接字
83、管道:管道是連接一個進程的輸出到另一個進程的輸入的一種方法
--------------
---->in|
pipe
|--->out
--------------
創建管道:
函數接口:int pipe(int fdes[2]):fdes 將含有用於管道的兩個文件描數字,一個
作爲輸入,一個作爲輸出
調用函數成功後,內核在系統內部創建一條管道,fdes[0]設置爲讀而打開,與輸入端相
連,fdes[1]設置爲寫而打開,
與輸出端相連。調用成功返回 0,否則返回-1 ,錯誤在 errno 裏面。
管道的特點:
(1)、沒有名字
(2)、兩個描數字同時打開
(3)、不允許文件定位,讀和寫都是順序的
在管道通信中,任何寫到 fdes[1]的數據都可以從 fdes[0]讀出來,數據在管道中是連續
的字節流,按先入先出處理,比如 fdes[1]
寫入字符 abc,fdes[0]讀出的將是 abc。
父子進程之間的管道通信:
當進程調用 fork 派生子進程時,子進程將繼承父進程的所有打開的文件描數字。所以,
如果進程在 fork 之前創建 pipe 的話,
那麼 fork 之後,父子進程將都能夠訪問構成管道的這兩個文件描數字,於是就可以利
用管道在父子進程之間通信了。
父子進程之間的管道有兩種通信方式,一種是從父進程往子進程發送數據,另外一種
則相反。但是不能同時存在。
爲了避免通信混亂,我們需要選擇一個通信方向,如果選擇從父進程往子進程發送數
據,那麼那麼父進程要關閉他的
fdes[0](read),子進程要關閉他的 fdes[1](write).否則相反。
需要注意的一點是:如果管道不需要的一端不關閉的話,它絕對不會返回 EOF!
84、連接標準輸入和標準輸出的管道
父子進程通過管道通信的典型做法是:由子進程重複管道的文件描數字到標準輸入或者標準輸出,
然後調用 exec 執行新程序,新
程序繼承標準輸入輸出,由此就實現了父子進程之間的通信。
就像下面這些代碼一樣:
24childpid=fork();
if(childpid==0){
close(0);/*the filedes 0 is the stdin,close the stdin*/
dup(fd[0]);/*let the fd[0](read port) pointer to stdin*/
exec(...);/*exec new program*/
}
我們應該知道的事情是:dup 將複製給定的文件描數字到一個最小的可用的文件描數字上,因爲我
們關閉了 0(stdin),所以
管道的輸入端將被定向到 stdin,子進程繼承了標準輸入輸出,所以就可以和父進程通信了。
我們還可以用 dup2 來一次搞定:
childpid=fork();
if(childpid==0){
dup2(fd[0],0);/*close stdin,and copy fd[0] to stdin*/
exec(..);/*exec new program*/
}
85、popen 和 pclose
只是對管道的高度封裝,降低了靈活性。
函數接口:
FILE*
popen(const char* command,const char* mode):該函數在調用進程與要
執行的命令之間建立一個內部
的半雙工管道,然後派生一個子進程,調用 shell 執行 command 給出的命令。mode 指明管道
的讀寫方式, “ r” 或者 “ w”。
''r->對此流的讀操作導致從 shell 命令的標準輸出中讀取數據
'w'->對此流的寫操作導致寫至 shell 命令的標準輸入中
int
pclose(FILE* stream):該函數將等待由 popen 執行的進程終止,並且返回該進
程的出口狀態。
86、管道 io 的原子性
管道在內核中其實是一塊緩衝區,當寫入到管道的數據塊大小超過緩衝區的大小時,內核就需要分
批寫入。
這中間就會出現原子性被中斷。
87、FIFO 特別文件
管道只用與父子進程之間的通信,因爲管道建立在內核中,只有那些有遺傳關係的進程才能用到他。
FIFO 可以在沒有父子關係的
進程之間通信。他是半雙工的,FIFO 與管道的幾點不同:
(1)、FIFO 作爲特別文件存在於文件系統中
(2)、不同祖先的進程可以通過 FIFO 來共享數據
(3)、當共享完成後,如果不用 unlink 來解除他們,FIFO 文件將一直存在文件系統中。
FIFO 文件一旦打開,就可以像操作管道一樣操作它。與管道一樣,我們需要有一個進程爲了寫而打
開他,另一個則爲了讀而打開他。
可以用 shell 來新建 FIFO 文件:
mknod filename -p
mkfifo a=rw filename
函數接口:
int
mkfifo(const char* path,mode_t mode):新建一個新的特別文件,名字爲
path,權限爲 mode
int
mknod(const char* path,mode_t mode,dev_t dev):創建一個給定文件類型
的文件,名字爲 path,mode 爲文件類型,可以如下:
-----------------------------------
S_IFIFO:fifo 文件
25S_IFCHR:字符特別文件
S_IFDR:目錄
S_IFBLK:塊特別文件
S_IFREG:普通文件
-----------------------------------
dev 參數僅當指明文件類型爲 S_IFCHR or S_IFBLK 時才用來指明這個新創建的特別文件的
主設備和副設備號。其他情況將被忽略。
88、FIFO 操作
(1)、打開 FIFO 文件
需要注意的是,不能以 O_RDWR 方式打開 FIFO 特別文件,不能又讀又寫。如果指定了
O_NONBLOCK 標誌,他不僅僅指明對該文件的
讀寫是非阻塞的,同時也指明打開動作本身是非阻塞的,如果沒有設置該標誌,open 將阻塞直
到另一個進程以相反的讀寫方式
打開同一個 FIFO 文件。而對於普通文件而言,不論是否設置該標誌,open 都是不阻塞的。
(2)、讀寫 FIFO 文件
可以用 read 和 write 來讀寫 FIFO 文件。
讀寫 FIFO 受打開標誌 O_NONBLOCK 的影響。
調用 read 讀一個阻塞的空 FIFO 文件,進程將阻塞直到有數據可讀,操作完成後返回讀取的數
據量。
調用 read 讀一個非阻塞的空 FIFO 文件,進程將立即返回-1 並且設置 errno。
同管道讀寫一樣,PIPE_BUF 控制着 FIFO 文件操作的原子性,所以一般數據大小不要超過這個值,
否則就不是原子性的。
89、關於 IPC 的概述(系統 V 的 IPC)
該 ipc 由三種進程間通信組成:消息隊列,共享存儲,信號量
這三種機制各不相同,具體如下:
(1)、消息隊列:提供一種通用的消息傳遞方法,允許進程發送和接收不同大小的緩衝區數據,消
息的格式
可以由操作系統來設定,頁可以由用戶進程自己設定,只要通信雙方達成協議即可。
(2)、共享存儲:允許多個進程訪問存儲區中同一塊數據區,希望通信的進程之間通過一塊具有唯
一標誌的
共享存儲聯繫在一起,然後就可以進行通信了。
(3)、信號量:允許進程打開和關閉一組信號量集合的標誌,這些標誌告訴進程發生了什麼事情,
那些與同
一個信號量集合標誌相連的進程可以讀取和修改該信號量集合,信號量本身不傳遞數據,只是用來
控制對某種資源的訪問,
以實現進程之間的同步。
當然,這三種機制都有相似的特點,比如說他們都有唯一的整數標誌,都有一個關鍵字,都使用某
種相同的數據結構,並且都有
類似的獲取和控制 ipc 資源的函數接口。
------------------------------------------
關鍵字和標識:ipc 也是駐存在磁盤上的一種資源,但是和文件不同,每一個 ipc 資源有兩個唯一
的標識與之相連,也就是
關鍵字和標識。他們的作用與文件的文件名和文件描數字類似,關鍵字就是創建 ipc 資源的名字,
標識就是用來 invoke 該
ipc 資源的句柄,和文件描數字有着相同的作用。
(1)、關鍵字(KEY)
關鍵字的類型爲 key_t,這是一個長整形。他命名要使用的 ipc 資源,進程通過指定一個關鍵
字,然後調用相關的函數來
創建一個 ipc 資源。
關鍵字的值可以取值如下:
26IPC_PRIVATE:這個值通常爲 0,他標示總是創建一個新的 ipc 資源。如果用這個關鍵字調用
創建 ipc 的函數,那麼內核總是會
創建一個新的 ipc 資源,所以,這標示進程的私有 ipc 資源,其他進程一般得不到它,創建這
個 ipc 的子進程可以共享該 ipc 資源
非 0 整數:如果關鍵字不是 0,可以直接指定整數,這標示這個 ipc 可以被多個 使用
我們需要知道的是,希望訪問同一個 ipc 資源的進程必須使用相同的關鍵字,下面需要介紹一個函

key_t ftok(const char* path,int id):這個函數將根據兩個參數返回一個關鍵字,
該關鍵字可作爲其他函數的參數。
參數 path 必須是一個已經存在的文件,id 只有低 8 位有效,對於命名同一個文件的 path,相
同的 id 返回相同的關鍵字,當用
不同的 id 時,返回不同的關鍵字。
還有,該函數返回的關鍵字是根據 inode 來確定的,所以如果將 path 刪除後再創建,調用相
同的 id 返回的關鍵字可能不一樣。
(2)、標識(ID)
每一個獨立的消息隊列,信號量集合、共享存儲段都由一個唯一的正整數所標識,ipc 關
鍵字類似於文件名,標識相當於文件
描數字,用關鍵字獲得該資源的標識之後,該關鍵字不再有用,後繼對 ipc 資源的訪問
將靠這個 id。
需要知道的一點時:關鍵字的唯一性是說所有資源中的唯一性,而標識的唯一性是對與同
一種 ipc 資源的唯一性,不同資源
的標識可以相同,但是所有資源的關鍵字必須不同。
-----------------------------------
ipc 資源描述結構與成員 ipc_perm:
每一個 ipc 都是一個資源,所以需要有權限,來規定哪些進程可以訪問資源。創建 ipc 資
源的稱爲該 ipc 資源的創建者,
該進程擁有對這個資源的絕對控制權,這也是該 ipc 資源的屬主,當然這個資源是可以被
轉讓的,轉讓後的資源變爲
轉入進程的資源,獲得資源的進程成爲該 ipc 資源的當前屬主。但是 ipc 資源的原始創建
者是不會變的。
每一個 ipc 資源被創建時,系統都會在內核中創建他的標識和一個數據結構,並且設置他
的用戶,用戶組及其他的權限
----------------------------------------
關於 ipc_perm:
struct ipc_perm
{
uid_t uid;/*current*/
gid_t gid;
cuid_t cuid;/*creator*/
cgid_t cgid;
mode_t mode;
}
其中,mode 是一個位串,他的每一位確定 ipc 資源的一種訪問權限,訪問權限與文件的訪
問權限是相同的。但是執行權限總是
0,因爲執行 ipc 資源是沒有任何價值的。
系統會根據該數據結構的值來判斷一個進程是否與權限來 invoke 這個 ipc 資源:
(1)、進程具有特權,比如超級用戶,那麼可以訪問
(2)、進程的有效用戶 id 和 ipc 資源中的 cuid 或者 uid 相匹配,那麼可以訪問
(3)、有效用戶 id 不匹配,但是進程的有效組 id 與 cgid 或者 gid 匹配,則可以訪問
(4)、都不匹配,但是設置了其他用戶訪問位,那麼可以訪問
(5)、其他情況,一律無法訪問該 ipc 資源
-----------------------
27ipcs 和 ipcrm:
ipc 資源會駐村在磁盤上,直到被刪除,或者系統重啓。所以黨我們不再需要某個 ipc 資
源時,最好刪除
這個 ipc 資源。ipc 資源在系統中是沒有名字的,所以不能像刪除文件一樣刪除 ipc,但
是我們可以使用
ipcs 命令來查看系統裏面的 ipc 資源。用 ipcrm 來刪除不再需要的 ipc 資源。
ipcs 可以指定想要查看的 ipc 資源類型,-q,-m,-s 分別表示消息隊列,共享存儲和信
號量 ipc 資源
ipcrm 需要指定想要刪除的類型和 id。
90、消息隊列
消息隊列和管道類似,都是半雙工的,有讀方和寫方,但是與管道不同,管道是連續的字節流,但
是消息隊列則可以是自己定義的結構。
消息還可以有優先級。
每一個使用消息隊列的進程都可以進行兩種操作:發送消息和接收消息。
同一個消息隊列可以有多個進程發送消息,也可以有多個進程接收消息。消息按照他們到達的順利
連接在隊列尾部,
新發送的消息放在隊列的尾部,接收消息則從消息頭部拿出消息,只要消息被讀,則從隊列刪除。
當然,進程在發送和接收消息之前,需要先從操作系統得到一個消息隊列,我們可以通過 msgget
來向操作系統申請
一個消息隊列,每一個消息隊列相連一個內核數據結構 msgqid_ds。他記錄消息隊列的有關信息。
我們可以通過 msgctl 函數來得到這個結構的信息,當然也可以用該函數來刪除我們不再需要的消
息隊列。
下面就是消息正文,消息正文被存放在 分配的消息緩衝區中,消息緩衝區是一個結構,我們只需要
讓我們自己的消息
結構的第一個是 long int 類型並且大於 0 就可以,其他字段自有定義,我們都可以發送。
那爲什麼要求一定要有第一個字段呢?
(1)、給應用提供多路轉接來自多個進程的消息的能力,我們可以根據這個字段來區別消息的發送
者,然後
我們可以返回正確的消息給發送者
(2)、可以給隊列中的消息優先級,然後有選擇的讀取。
-----------一大波函數接口即將來臨--------------
int
msgget(key_t key,int flag):key 爲消息隊列的關鍵字,flag 需要給出創建標
志和訪問標誌,創建標誌
可以選擇如下兩個,訪問標誌和文件一模一樣:
a:IPC_CREAT:如果不存在與 key 相連的 ipc 資源,則創建他
b:IPC_EXCL:若已經存在與 key 相連的 ipc 資源,則失敗
該函數調用成功將返回與 key 相連的消息隊列的 ID,如果這是一個新創建的消息隊列,該函數將創
建與該 ID 相連的
數據結構 msqid_ds
爲了創建一個消息隊列,要麼指定 key 爲 IPC_PRIVATE,那麼總是創建一個新的消息隊列,當然,
除了可以創建新的
消息隊列,我們還可以用該函數來獲取某個已經存在的消息隊列的 id。比如:
mqid=msgget(1234,0):這個函數將返回已經與關鍵字 1234 關聯的消息隊列的 id,因爲我們不
需要上面限制,所以 flag=0。
-----------------------------------------------
消息隊列的查詢,設置與刪除:
函數接口:
int msgctl(int msqid,int cmd,struct msqid_ds* buf):
該函數將對消息隊列 msqid 執行 cmd 指定的操作,cmd 的取值和意義如下:
28(1)、IPC_STAT:複製消息隊列的內核數據結構 msqid_ds 到 buf
(2)、IPC_SET:設置內核數據結構 msqid_ds 的值爲 buf
(3)、IPC_RMID:刪除 msqid 指定的消息隊列
注意:執行第一個 cmd 的進程需要有對該消息隊列的讀權限,執行後兩個 cmd 的進程只能
是消息隊列的
創建者,擁有者和特權用戶,此外,只有特權用戶才能改變消息隊列的字節數。
---------------------------------------------------
發送和接收消息:
函數接口:
int msgsnd(int msqid,constvoid* msgp,size_t msgsz,int msgfg):
int msgrcv(int msqid,void* msgp,size_t msgsz,long int
msgtyp,int msgflg):
關於發送消息:
函數將向指定的消息隊列發送一條消息,消息的內容爲 msgp,msgsz 爲消息的
正文大小,不包括消息類型。
msfg 用來處理當進程阻塞時的動作,在下面的情況中,進程將阻塞:
(1)、消息隊列中的總字節數已經等於 msg_qbytes(已經滿了)
(2)、系統中的所有消息隊列中的消息數量達到上限
當發生上面的情況時,如果 msgfg 設置標誌:IPC_NOWAIT,則發生阻塞時消息
不被髮送。調用進程將設置 errno 並且返回失敗
當沒有設置 IPC_NOWAIT 標誌時,發生上面情況時,調用進程將阻塞直到發生下
面的事情之一:
(1)、導致阻塞的調價不再存在
(2)、消息隊列標誌已經被移除
(3)、調用進程收到信號
函數調用成功將會更新與消息隊列相連的數據結構的相關信息。所發送的消息將
在消息隊列中排隊以等待
其他進程來讀取該消息。
關於接收消息:
函數將從消息隊列中讀消息到指定的 msgp 中,調用成功則返回讀到的消息大小,讀完
消息後將消息刪除。
並且將會更新與消息隊列連接的數據結構的相關信息。
msgtyp 指明要接收的消息類型:
(1)、msgtyp==0,接收隊列中的第一個消息
(2)、msgtyp>0 接收類型等於 msgtyp 的第一個消息,這種方式只接收特定類型的
消息
(3)、msgtyp<0 接收類型小於或者等於 msgtyp 絕對值的第一個最低類型的消息,
實現優先級
第五個參數指明當所希望的消息不在消息隊列中時的動作,以及接收的消息大小大於
msgsz 時的動作:
(1)、如果接收的消息大於 msgsz,如果設置了 MSG_NOERROR 時,所接收的消息將
只取 msgsz 大小。如果
沒有設置該標誌,那麼函數返回錯誤
(2)、IPC_NOWAIT 標誌的意義和用法和發送消息時一樣
91、共享存儲段
共享存儲段有大小和存儲物理地址,想要訪問共享存儲段的進程可以連接這段存儲區域到自己的地
址空間。共享存儲將一直駐存在存儲器中
直到共享存儲標誌被刪除,或者沒有任何連接連接他們,或者系統被重啓。
29-----------------------------------
創建和獲得共享存儲區:int shmget(key_t key,size_t size,int shmflg):
參數 key 是共享存儲段的關鍵字,爲 IPC_PRIVATE 表示總是創建一個新的共享存儲段,當不等於
這個值時,shmget 的動作
將由參數 shmflg 決定。(IPC_CREAT and IPC_EXCL)
參數 size 指明要求的共享存儲段的大小,當 key 指定的共享存儲已經存在時,size 必須要麼爲
0,要麼不得大於該已經存在
的共享存儲段的大小。
函數調用成功則返回與 key 相連的共享存儲段的 id,然後將更新或者創建共享存儲段(全爲 0)和
與參數 key 相連的數據結構
shmid_ds 的值。
該函數調用失敗將返回-1,並且設置 errno 。
-----------------------------------
查看,設置,刪除共享存儲段:
int chmctl(int shmid,int cmd,struct shmid_ds* buf):
用法和意義和消息隊列一樣,只是需要注意幾點:
當用 IPC_SET 命名改變了 shmid 指定的共享存儲段的讀寫權限時,他並不影響那些已經建立
連接的進程,但
對那些想要來建立連接的進程將開始起作用。
函數調用成功返回 0,失敗返回-1,並且設置 errno
------------------------------------
共享存儲段的連接與分離:
我們想要訪問和操作共享存儲段,就要知道他在哪裏,也還要有適當的權限來做這樣的事情。
我們需要將共享存儲段映射到我們的進程的地址空間裏面,然後才能讀寫他。當然如果我們不

再用這個共享存儲段的時候,可以將這個映射解除。
函數接口:
void*
shmat(int shmid,const void*shmaddr,int shmflg):
int
shmdt(const void* shmaddr):
函數 shmat 指明要連接到的進程地址,如果 shamaddr 不爲 0,那麼就按設定的來,如果爲
0,那麼 os 會
爲你自動的找到合適的空間然後完成連接。一般就爲 0 吧。這個地址將會被返回。shmflg 的作
用是設定
一些權限,我們只需要知道 SHM_RDONLY 的用於,他指明連接的共享存儲段是隻讀的,如果進
程有讀權限
並且指明瞭該標誌,共享存儲段將以只讀的方式連接。如果沒有指明,則以讀寫方式連接。
調用成功則返回連接的實際地址,出錯則返回 0.
第二個函數將完成解連接的功能。他的參數應該是我們第一個函數的返回值。注意該函數將不
會刪除共享
存儲段,想要刪除共享存儲段,就需要用 shmctl。我們還需要更新共享存儲段的內核數據結構
的某些字段。
操作系統將根據某些字段的值來做出決策。
92、信號量
信號量是爲了實現避免出現競爭而存在的,主要用於兩種情況:
(1)、互斥:防止多個進程在同一時間訪問一個共享資源。
當多個資源都需要訪問共享資源時,可以用一個二值信號量來控制資源的訪問。
當有一個進程需要訪問資源時,可以執行一個 p 操作,當有進程正在使用資源時,這個 p 操作

被阻塞直到正在只用資源的進程執行了 v 操作。這樣,任意時刻都將只有一個進程在使用共享
資源。
我們將訪問共享資源的程序代碼成爲臨界區。在進入和出去臨界區時需要改變信號量以得到某
30種許可。
(2)、生產/消費模型
由協同工作的進程來使用,一個進程生產數據,另一個進程使用數據。他們之間通過信號量來
達到同步
目的。信號量應該初始化爲 0,表明當前沒有數據可消費,任何想要消費數據的進程的 p 操作都
將被阻塞。
直到生產着生產出了數據並且執行了 v 操作。
在 unix 中,爲了防止死鎖,信號量採用 “ 要麼全有,要麼全無 ” 的實現方式。
即:同時請求所需要的所有信號量,如果有一個被阻塞,那麼其他均不能獲得。這樣就避免了死
鎖的發生。
-------------------------------------------------
創建和獲取信號量標誌
信號量標誌引用的是一組信號量而不是單個信號量。如果我們只是想操作單個信號量,
那麼我們可以指定一個由單個信號量組成的信號量集合。每個信號量是一個 sem 結構體。
每一個信號量集合和一個內核數據結構 semid_ds 相連,我們可以在開發者文檔裏面找到他們
int
semget(key_t key,int nsems,int semflg):
這個函數將創建或者獲取和 key 關鍵字相連的信號量集合的標識。參數 key 是信號量的關鍵字,
IPC_PRIVATE 表示
總是創建一個新的信號量集合,如果不是這個值的話,那麼就取決於 semflg 裏面的指定了,
這和消息隊列或者
共享存儲段是一樣的。
參數 nsems 指定信號量的數量,需要大於 0,如果是一個已經存在的信號量集合,那麼這個信
號量的個數需要在 0 到
已經存在的信號量數量之間,否則函數將返回錯誤。
需要知道的一點是,信號量集合中的第一個信號量的編號爲 0,最後一個信號量的編號爲
nsems-1
-----------------------------------------------------
信號量的查詢、設置與刪除
int semctl(int semid,int semnum,int cmd,[union semun arg]):
semnum 將指定我們需要選擇的信號量的編號,參數 cmd 是操作命令,最後一個參數將配合
cmd 完成工作。
其中,cmd 參數的可選範圍如下:
-----------------------------------------
SETVAL:設置單個信號量的值
GETALL:返回信號量集合中所有信號量的值
SETALL:設置信號量集合中所有信號量的值
IPC_STAT:得到 semid_ds 的結構,將放在適當的位置返回給調用者
IPC_SET:設置 semid_ds 結構,需要指定一個 buf
GETVAL:返回單個信號量的值
GETPID:返回最後一個操作該信號量集合的進程 id
GETNCNT:返回 semncnt 的值(等待對該信號量執行 p 操作的進程的數量,需要注意的是,這
個變量裏面的是只需要等待
一個資源可用就可以的進程等待數量,而還有另外一個變量裏面的是需要所有等待的資源都可
用才行的進程數量)
GETZCNT:返回 semzcnt 的值,他的含義見上文
IPC_RMID:刪除指定的信號量集合
我們需要知道的一個聯合結構體:
也就是參數中可選的最後一個參數:
union semun
{
31};
int
val;//同於 SETVAL
struct semid_ds*
buf;//用於 IPC_STAT/IPC_SET
unsigned short*
array;//用於 GETALL/SETALL
------------------------------------------------------------------------
信號量的操作:
int
semop(int semid,struct sembuf* sops,size_t nsops):
該函數對指定的信號量集合 semid 執行 sops 指定的操作,nsops 是操作的數量,也就是 sops 的
大小。
現在我們來看一下 sembuf 的結構信息:
struct sembuf
{
unsigned short int sem_num;//信號量的編號
short int sem_op;//信號量操作
short int sem_flg;//操作在狀態
}
第一個成員指明操作的信號量編號。對於第二個成員,可以有三種情況:
sem_op=>
(1)、小於 0:減少一個信號量的值,減少的值爲 abs(sem_op),相當於請求所控制的資源,當
爲-1 時,相當於 p 操作
如果信號量的值大於 abs(sem_op),則操作成功,否則如果沒有設置 IPC_NOWAIT 則一直等待到滿
足條件
(2)、大於 0:增加一個信號量的值,爲 1 時相當於 v 操作
(3)、等於 0:等待信號量變爲 0 這對應於等待信號量控制的所有資源可用,如果信號量已經爲
0,返回。否則如果爲設置 IPC_NOWAT 則阻塞
對於成員 sem_flg:
(1)、IPC_NOWAIT:...
(2)、SEM_UNDO:如果設置,當進程退出時,執行信號量解除動作,對於指定了該標誌的每一個
信號量,有一個內部變量
semadj 與之相連,他的作用是在進程死亡時調整信號量的值。
具體而言:如果某個信號量的值減少了,那麼 semadj 的值就增加了,如果信號量的值增加了,那
麼 semadj 的值將減少。這樣
在進程死亡時,semadj 將會加到信號量中,也就是解除了信號量。
需要注意的是:當對信號量執行 SEM_UNDO 時,對同一個信號量的相反操作也需要指定該標誌。
該函數調用只有當集合中的所有操作都完成時纔會返回,如果集合中的某個操作無法完成的話,則
所有信號量的操作都不會被
執行,此時,如果這個信號量操作設置了 IPC_NOWAIT,則立即返回,否則進程被阻塞直到出現下
述情況之一:
(1)、所有信號量的操作都完成
(2)、進程收到一個信號
(3)、信號量集合被刪除
93、套接字與網絡通信
套接字不僅支持本地無關聯兩個網絡之間的通信,還支持網絡通信。他是雙向的。
--------------------------------------------------------------------
關於套接字 socket:
從編程的角度來看,套接字就是相當於網絡,就比如文件描數字就是磁盤一樣。
int socket(int domain,int type,int protocol):
該函數在通信域 domain 裏面創建一個類型爲 type,使用協議 peotocol 的套接字,並返回
這個套接字描數字,socket 和 open 一樣返回最小的未使用的描數字。
套接字和文件描數字可以使用相同的接口來操作,比如可以用 read 讀數據,write 來寫數據,
close 來
關閉描數字等。但是和文件描數字一樣,套接字描數字不支持文件定位。
32參數 domain 指明通信域,該參數指明瞭通信使用的網絡協議族,協議族不同,地址結構也不同
最常用的幾個域是:
(1)、AF_UNIX:unix 通信域,也就是同一臺計算機上兩個進程之間的通信,要求將文件系統路
徑名
作爲套接字的地址。
(2)、AF_INET:網絡通信,使用 ipv4
(3)、AF_INET6:網絡協議,使用 ipv6
參數 type 指明套接字的類型,常用的如下:
(1)、SOCK_STREAM:字節流套接字,簡稱流套接字,提供面向連接的,雙向的,可靠的數據流,
數據
沒有記錄邊界,可支持帶外數據,一對相連的流套接字類似於管道,只是數據流是雙向的。
(2)、SOCK_DGRAM:數據包套接字,他支持雙向通信,但是不可靠。沒有邊界,每次往返寫數據
時,數據
便成爲一個包,數據包套接字沒有連接,應用必須爲每一個包指定接收者的地址
參數 peotocol 是指定使用的協議,一般爲 0 就可以,因爲只要我們設定了 domain 和 type,那麼
協議就是唯一的。
兩個互相通信的進程必須使用相同的協議,不然無法識別。
socket 如果調用失敗則返回-1,可以在 errno 裏面看到錯誤信息。
--------------------------------------------------------------------
socket 只是創建一個套接字描數字,socketpair 則可以創建一對套接字。
int
socketpair(int dimain,int type,int protocol,int filedes[2]):
該函數將創建一對沒有命名的套接字,放在參數 filedes 裏面,套接字偶對是一個全雙工的
通信通道,在其兩端都可以讀寫。
-------------------------------------------------------------
當我們不再需要一個套接字時,可以用 close 來關閉,就像關閉文件描數字一樣。
但是,用 close 關閉套接字後,這個套接字將不再存在。
我們可以僅僅斷開連接,用下面的函數:
int shutdown(int socket,int how):
how 指定怎麼關閉:
SHUT_RD:停止接收數據
SHUT_WR:停止發送數據
SHUT_RDWR:停止接收和發送數據
94、地址
socket 函數只是在本地創建了一個資源,如果希望其他進程來訪問該資源,那麼還必須有一個名字,
也就是地址(頁可以是文件
路徑名(作爲本地套接字通信)),而在網絡上通信時,這個地址是 ip 地址加上 port 號。
ip 地址的結構:
in_addr:ipv4
in6_addr:ipv6
-------------------------
typedef uint32_t in_addr_t;
struct in_addr {in_addr_t s_addr;};/*32 bits ipv4 address*/
struct i6_addr {uint s6_addr[6];}/*128 bits ipv6 address*/
--------------------------------------------------
unix 提供了一組函數,可以實現 ip 地址的字符形式與二進制形式的轉換:
int
inet_aton(const char* name,struct in_addr* addr):將 name 所表示的 ipv4
地址轉換爲二進制形式,並保存在 addr 中
char* inet_ntoa(struct in_addr inaddr):將二進制的轉換爲可讀的字符形式的 ipv4 地
33址
int
inet_pton(int family,const char* nameptr,void* addrptr):既可以用於
ipv4,也可以用於 ipv6.作用和上面第一個函數
一樣,下面還有一個函數,實現了和第二個函數一樣的效果(其實主要是爲 ipv6 設計的這兩個函
數)
char*
inet_ntop(int family,void* addrptr,char* nameptr,size_t len):
參數 family 可以時 AF_INET AF_INET6,nameptr 指向地址,addrpte 可能是 in_addr 或者
in6_addr,len 這個參數是用來指明
addrptr 這個區域的大小的,可能爲 16 或者 46.
如果不足以保存獲得的地址的話,函數返回空。
以上的四個函數所返回的 ip 地址的數值都是標準的網絡字節順序,需要自己注意。
95、域名地址
網絡中的一臺機器除了可以用 ip 地址表示外,還可以用域名來表示,每一個域名對應一個 ip 地址。
處在網絡中的計算機可以有多個域名,他們是同一臺主機的別名。
unix 系統內部用一個主機網絡地址數據庫記住主機名與主機 ip 地址之間的映射,我們可以用函數

從該數據庫中獲得主機的詳細信息:
struct hostent* getbyname(const char* name):返回主機 name 的地址信息,如果失
敗,返回空指針,
如果成功,則保存這些信息在 hostent 結構中:
----------------------------------------------
struct hostent
{
char*
h_name;//主機的正式名字
char** h_aliases://主機的可選別名
int
h_addrtype;//主機地址類型
int
h_length;//地址長度
char** h_addr_list;//指向主機網絡地址數組的指針,他們的類型其實不是字符,而是相
應的地址類型。
}
注意,hostent 結構中的地址總是按照網絡字節順序來的。
參數 name 是域名,也可以時 ip 地址,後一種情況函數不做查詢,只是複製給結果然後返回。
struct hostent
gethostbyaddr(const void* addr,size_t length,int type):
該函數將返回地址爲 addr 的主機的相關信息,length 參數是地址長度,type 爲地址類型,如果
這兩個
函數調用失敗的話,可以通過查看變量 h_error 的值來確認。在我們使用之前,應該先聲明:
extern int h_error;
-------------------------------------
HOST_NOT_FOUND:數據庫中不知道的主機
TRY_AGAIN:發生在域名服務器不能連接時,如果再試一次的話,可能就成功了
NO_RECOVERY:遇到了不可恢復的錯誤
NO_ADDRESS:主機數據庫中含義該名字的登記項,但是沒有地址
-----------------------
gethostbyname 獲得地址信息有兩種途徑:
(1)、通過網絡中的域名系統
(2)、通過本地的主機地址映射文件
------------------------------
當我們需要讀多個主機的地址信息時,可以用 sethostent 打開本地數據庫,然後用 gethostent
來逐條讀取,
最好用 endhostent 來關閉數據庫(也是關閉套接字)。
void
sethostent(int stayopen):
struct hostent* gethostent();
34void
endhostent();
函數 sethostent 打開本地主機網絡地址數據庫,如果 stayopen 不爲 0,後繼的操作將不會關閉
數據庫(TCP),否則
操作完成後將關閉(UDP)。
返回空指針說明已經沒有可讀地址了。
endhostent 實際上是關閉 TCP 套接字。
96、服務與端口號
我們需要知道的一點是,網絡通信中套接字地址由 ip+port 的形式唯一標誌。
但是在系統中,一些端口已經被佔用,比如 FTP,HTTP 等已經佔用了固定的端口號,我們可以用的
端口號
在 5000 之後(unix)
-----------------------------------------------------------------------
|
|
|
|
0
IPPORT_RESERVED
IPPORT_USERRESERVED
65535
------------------------------------------------------------------------
當使用的是沒有指定地址的套接字,系統會爲他生成一個位於第二個區間的一個值作爲端口
號,這種端口號稱爲動態端口號,瞬間端口號等。
在 unix 系統裏面,有一個記錄標準服務的數據庫,(/etc/services/),用結構 servent 來表
示每一個
服務項:
-------------------------------
struct servent
{
char*
s_name;//服務程序的正式名字
char*
s_aliases;//服務程序的別名
int
s_port;//服務程序對應的端口號,端口號按照網路字節順序給出
char*
s_proto;//與該服務一起使用的協議名
}
對於標準服務,可以用端口直接指出,比如 21 就是 ftp,但是當標準服務所用的端口號
不確定時,可以用下面的函數來得到:
---------------------------------------------
struct servent*
getservbynama(const char* name,const char* proto):返
回使用協議 proto
並且名字爲 name 的有關信息
struct servent* getservbyport(int port,const char* proto):返回使用協議
proto 並且使用端口
號 port 的服務
void
setservent(int stayopen):
struct servent* getservent();
void
endservent();
上面三個函數的用法和前面的提到的類似三個函數的用法是一樣的。
97、套接字地址數據結構
(1)、unix 通信域套接字地址結構
struct sockaddr_un
{
sa_family_t sun_family;
char
sun_path[];
}
sun_family 指明地址族,對於 unix 通信域,應該爲 AF_UNIX,sun_path 給出 unxi 文件的路徑
35名。
他不能與文件系統的文件名重名。由該參數所命名的文件是在 bind()時創建的,此時內核在文件
系統中
爲他分配一個 inode 節點,如果這個 inode 沒有被解除,這個文件將一直存在於文件系統中,甚至

這個套接字被關閉之和也是如此,所以,當我們關閉一個文件名套接字時,應該用 unlink()或者
remove()從文件系統中刪除其文件名。
(2)、ipv4 和 ipv6 通信域套接字地址結構
表示 ipv4 套接字地址結構的是:
struct sockaddr_in
{
sa_family_t
sin_family;//AF_INET
in_port_t
sin_port;//16 位端口號,網絡字節順序
struct in_addr sin_addr;//32 bits ipv4 address
unsigned char
sin_zero[8];//reserve
}
表示 ipv6 套接字的地址結構
struct sockaddr_in6
{
sa_family_t
sin6_family;//AF_INET6
in_port_t
sin6_port;//16 bits port,網絡字節順序
uint32_t
sin6_flowinfo;//ipv6 流標號和優先級信息,網絡字節順序(用於處理實
時服務)
struct in6_addr
sin6_addr;//128 bits ipv6 address
}
(3)、通用套接字地址結構
struct sockaddr
{
sa_family_t sa_family;//AF_UNIX,AF_INET,AF_INET6
char
sa_data[];//套接字的實際地址信息
}
這是通用地址結構,我們需要將專用的地址結構轉換爲該地址結構,可以這樣操作:
--------------------------------------------------------------
struct sockaddr_un name;
name.sa_family=AF_UNIX;
strcpy(name.sun_path,filename);
bind(sockfd,(struct sockaddr*)&name,sizeof(name));
....
------------------------------------------------------------
98、字節順序
數據是存放在存儲器中的,存放數據的基本數據單元是字節。
有的計算機將一個數中最有意義的字節放在存儲器地址編號較小的字節(little_endian)
有的計算機將一個數中最有意義的字節擋在存儲器地址編號較大的字節(big_endian)
A B C D
---------->
我們用在上面的 ABCD 中分別存儲 1,2,3,4,那麼在 big_endian 裏面表示 0x01020304
而在 litter_endian 裏面表示 0x04030201
我們可以用代碼來查看我們自己的系統是採用什麼字節順序:
---------------------------------------------------
36如果我們需要封裝一個整型數據於一個消息中,並通過套接字傳輸,那麼就需要將字節順序變化爲
網絡中的標準網絡字節順序:
uint16_t htons(uint16_t hostshort)
uint32_t htonl(uint32_t hostlong)
uint16_t ntohs(uint16_t netshort)
uint32_t ntohl(uint32_t netlong)
h 代表主機,n 代表網絡,l 代表 long,s 代表 short。
所以很好理解上面的函數的功能。
注意:一定要注意的是,當我們需要將一個數據通過套接字發送出去時,一定要保證已經轉換爲
標準網絡字節順序了,可以通過 htons 或者 htonl 來完成,當我們接收到了來自套接字的數據時,
也應當將接收到的網絡字節順序轉化爲主機順序,不同的主機採用的字節順序是不一樣的,如果
不做轉化,那麼對方收到的數據做運算可能會出現難以預料的結果!!
99、命名套接字
我們用 socket 創建的套接字只是系統中一個沒有名字的資源,其他進程沒有辦法訪問他,當我們
調用函數
bind 爲套接字指定一個名字時,其他進程才能訪問他。
--------------------------------------------------------
int
bind(int socket,const struct sockaddr* address,socklen_t
address_len):
參數 socket 是套接字描數字,參數 address 指向結構類型爲 sockaddr 對象的指針,參數
address_len
指明地址的長度。
函數返回 0 代表成功,-1 代表失敗(一般是地址格式有誤,或者套接字已經被命名)
我們需要注意,無論在那個通信域,都使用 bind 來設置套接字的地址,所以我們需要將我們專用的
地址類型
強制轉化爲通用地址類型。
100、套接字通信模式
(1)、有連接模式
對於服務程序,他首先創建一個 socket,然後給該 socket bind 一個名字,然後便在等待客戶進
程與該套接字
建立連接。在多個客戶與該套接字建立連接的情況下,服務進程通過調用 listen()爲進入的連接
創建一個連接
隊列,通過調用 accept()逐一的接收這些連接,每次調用 accept,都會創建一個新的套接字,
他不同於已經命名
的套接字,這個新的套接字完全只用於與特定客戶的通信。
對於客戶端,用 socket 創建一個流描數字並且 bind 名字之後,就可以用函數 connect 來與服務
創建連接了。
雙方一旦建立了連接,那麼就可以實現雙向通信了
(2)、數據報模式
這是無連接的,所以較爲簡單。雙方都是對等的,他們的行爲一樣,每一方是服務方也是客戶方。
用 socket 創建一個套接字描述字後,bind,然後就可以用 sendto 或者 recvfrom 來進行通信了
101、流套接字的操作
(1)、請求連接
請求連接是客戶端的動作,是主動套接字
int
connect(int socket,const struct sockaddr* address,socklen_t
address_len);
socket 是套接字描數字,address 是要連接的遠程套接字地址,address_len 指明套接字
37的長度。
函數成功時,socket 將被連接到 address 給出的地址上,後者必須是已經存在的套接字地址。
成功返回 0,失敗返回-1,errno 的情況如下:
---------------------------------
EBADD:socket 不是合法的套接字
EALREADY:已經有一個懸掛的連接正在被處理
ETIMEDOUT:連接超時
ECONNREFUSED:服務端拒絕此連接
EINTR:被信號終端
-----------------------------------
正常情況下,當連接不能立即建立時,函數將等待直到服務程序回答了連接請求。
或者等待了某個時間才返回,如果超時返回,connect 將失敗並且流產該連接請求。
如果在連接請求期間收到信號而被終端,失敗但是連接請求不會流產,而是異步的建立。
可以對套接字設置爲非阻塞方式,從而使得函數不等待直接返回,可以用 fcntl 來達到這個目
的。
如果對套接字設置了非阻塞標誌 O_NONBLOCK,當連接不能立即建立時,函數將失敗但是會異步
建立,在
連接建立之前如果後繼對同一套接字請求連接,將會發生 EALREADY 的錯誤。
(2)、接收連接
接收連接是服務端的動作,爲了接收客戶端的連接請求,服務段需要創建一個保存連接請求的
偵探隊列,然後建立連接。
int
listen(int socket,int backlog):這個函數用來建立偵探隊列
,參數 backlog 指定套接字偵探隊列允許的懸掛請求數量。
函數成功返回 0,失敗返回-1.
int
accept(int socket,struct sockaddr* address,socklen_t*
address_len);
該函數用於接收 socket 上面的連接請求,在有進程企圖與 socket 建立連接時,它會創建一
個新的套接字
與這個客戶連接,並且返回這個新的套接字。如果參數 address 不是空指針,那麼將會把客戶
的套接字地址
保存在這個域裏面,第三個參數指明這個地址的長度。
如果沒有懸掛的連接請求,除非套接字設置了非阻塞方式,否則 accept 函數將阻塞直到有客
戶連接爲止。
(3)、獲取套接字的地址
int
getsockname(int socket,sockaddr* address,socklen_t address_len)
int
getpeername(int socket,sockaddr* address,socklen_t address_len)
第一個函數用於獲取本地套接字的 socket 的地址,第二個函數用於獲取與本地 socket 連接
的遠程套接字的
地址,address 指出存放地址的對象,address_len 指出存放地址的長度。
成功返回 0,失敗返回-1
(4)、多客戶服務
服務程序一直循環等待連接,並且每次只爲一個客戶服務,這種服務成爲迭代服務。
併發服務則是對每一個客戶連接派生一個子進程來爲其服務,自己則繼續守護在偵探套接字上
等待連接。
一旦有客戶連接請求,accept 立即返回,此時服務進程調用 fork 派生一個子進程來爲客戶服
務。
子進程首先關閉偵探套接字,然後用 accept 返回的套接字爲客戶服務,子進程關閉偵探套接

並不影響父進程的偵探套接字,因爲引用計數不爲 0,則文件不會被刪除。
(5)、send 和 recv 函數
ssize_t
send(int socket,const void* buff,size_t length,int flag)
38ssize_t
recv(int socket,const void* buff,size_t length,int flag)
我們可以用 read 和 write 直接操作套接字,這沒有問題,因爲套接字也是文件,我們可以像
操作文件一樣操作
套接字,我們也可以用上面這兩個函數來完成讀寫套接字,只是這兩個函數專門用於套接字讀
寫。
send 將發送一個數據到 socket,如果 flag 爲 0,那麼和 write 一模一樣,當然,flag 可
以取其他值:
MSG_OOB:導致 send 發送的數據成爲帶外數據(緊急數據 —— 暫且這麼理解吧)
MSG_DONTHOUTE:不在消息中包含路由信息
recv 函數與 read 一樣,從 socket 中讀出數據,當 flag 爲 0 時,和 read 一模一樣。
flag 的取值可以如下:
MSG_PEEK:窺視套接字上的數據而不實際讀出他們
MSG_OOB:讀取帶外數據
MSG_WAITALL:函數阻塞直到接收到所請求的全部數據
102、套接字選項
int
getsockopt(int socket,int level,int optname,void* optval,socklen_t*
optlen)
int
setsockopt(int socket,int level,int optname,void* optval,socklen_t*
optlen)
-->socket 必須是已經打開的套接字,level 指定選項所屬的層次。
-->getsockopt 將獲得套接字 socket 在層次 level 上的選項 optname 的值,存放在 optval
裏面,應該提供此
此緩衝區的大小 optlen,函數返回時 optlen 將保存實際的選項值大小。
-->setsockopt 將設置已經打開的套接字
103、帶外數據
帶外數據就是緊急的數據,在 tcp 的帶外數據中,只允許一個字節作爲帶外數據,tcp 將緊急指針
指向這個帶外數據。
104、數據包套接字
一種面向無連接的,不可靠的套接字服務。
發送和接收數據:
int recvfrom(int socket,void* buffer,size_t size,int flags,struct
sockaddr* from,size_t* addrlen);
int sendto(int socket,void* buffer,size_t size,int flag,struct
sockaddr* to,size_t* addrlen);
---------------------------
在數據報套接字中使用 connect 函數,他不像在流套接字那樣產生連接,而只是爲將來在
此套接字上的傳輸指明一個方向。
當我們用 connect 函數之後,就可以用 read、write 等函數來直接操作數據報套接字了,
而不需要每次都寫一大堆的參數。
--------------------------
關於超時處理:因爲數據報套接字是面向非連接的,所以我們需要自己處理數據丟失的情況。
我們可以設置超時來解決:
(1)、調用 alarm()使得系統在指定的時間片到期時生成 SIGALRM 信息
(2)、用 select()建立一個時間片來等待套接字就緒
(3)、使用 SO_RCVTIMEO,SO_SNDTIMEO 選項套接字,這兩個選項自動對套接字的讀寫
設置超時處理。
105、什麼叫線程
39簡單來說,線程是運行在進程上下文中的一個執行流。所以進程內部總是有一個執行流,總有一個
線程。
每一個線程對應於一個執行流。只有一個執行流的進程稱爲單線程進程,否則稱爲多線程進程。

我們需要知道的幾點:
進程上下文即進程運行所需要的資源和環境,包括各類存儲空間和操作系統內核的管理資源。
進程的上下文包括 text、data、虛地址空間,堆空間,共享存儲,用戶棧等內容。
寄存器上下文保存着程序執行時的當前狀態,內核棧用於系統調用時內核中的函數調用。
進程結構是由操作系統爲每個進程指定的,內核將根據這個結構來管理進程。
當然,線程也有上下文,但是線程的上下文比進程的上下文小很多,因爲線程在於進程之內,
除了作爲執行流需要的上下文之外,其他資源都可以與進程共享,線程只複製了使得其作爲
可執行流存在的基本資源。
-------------------------------------------------------------------
(1)、每一個線程有自己獨立的執行流,一個進程內的所有線程地位平等,沒有父子關係,只有兄
關係。
(2)、線程是隸屬於進程的,一個進程創建的所有線程都共享該進程的若干資源,這些資源包括進
程的地址
空間,環境變量,文件描數字,信號動作等,如果進程死亡,該進程的所有線程都將死亡。
(3)、線程有自己專用的棧空間和線程專有全局數據。
併發和並行:
------------------------------------------------------------
併發是指多個線程同時處於運行狀態並可按任意順序執行,但是不要求同時都正在被調度執行。
並行是指多個線程被同時調度執行,這必須有多處理機或者多核的支持。
總的來說,並行是併發的子集。
-----------------------------------------------------------------------
線程與進程的比較:
進程的上下文比較大,內核管理進程需要佔用較多的資源,而且每個進程的存儲空間是獨立的。這
導致新派生出來的進程具有和父進程一樣大小的空間,增加了存儲空間的開銷,還有,子進程不能
直接讀寫父進程的存儲單元。
106、Pthread 線程
----------------------------------------------------------------------------
----------------------
線程標誌:線程標誌由 pthread_t 類型來表示。他有可能不是一個整形,而是一個結構體。
所以,我們比較兩個線程的時候需要用函數來獲得結果:
pthread_t
pthread_self():該函數可以獲得線程自己的 TID
int
pthread_equal(pthread_t t1,pthread_t t2):當兩個線程相同時返回非
0,不同返回 0.
----------------------------------------------------------------------------
-----------------------
創建線程:
所有進程一開始就有一個線程,這個線程成爲初始線程,初始線程是在進程創建開始就自動存
在的。
每一個線程都有一個開始函數,叫做線程入口函數,主線程的入口函數爲 main。我們可以用下
面的
函數接口來創建一個新的線程:
int
pthread_create(pthread_t *restrict thread,const pthread_attr_t*
restrict attr,
void*(*start_routine)(void*),void* restrict arg):
第一個參數是函數返回的新創建線程的 TID,第二個參數是線程的屬性,一般爲 null,這時候
系統將
使用默認的屬性來創建線程,第三個參數爲入口函數,只接受一個 void*參數,返回類型也爲
void*
40且與
而最後一個參數爲入口函數的參數。
新創建的線程將繼承創建他的線程的信號屏蔽、調度策略和優先級,但不繼承懸掛信號集。並
創建他的線程是平等的,我們在編程的時候不應該對線程的執行順序做假設,因爲這是隨機的。
----------------------------------------------------
退出線程:
int
pthread_exit(void* value_ptr):
參數 value_pte 指出線程終止時的出口狀態,當另一個線程調用 pthread_join()等待該
線程時,那個
線程便可以得到 value_ptr 指定的值。
線程終止並不釋放進程相關的任何資源,比如文件描數字,共享存儲,文件鎖等。也不會執行
進程級別
的清理動作。
-------------------------------------------------
等待進程終止:
與父進程等待子進程類似,一個線程在創建了若干線程之後,可能會需要等待這些線程完
成工作並且
與自己匯合,線程可以調用下面的函數來等待另一個線程:
int pthread_join(pthread_t thread,void** value_ptr):
這個函數將懸掛調用線程直到 thread 執行返回(線程終止),如果 value_ptr 是非空的,
他所指的對象
將存放線程 thread 線程終止時傳遞的值,當一個線程需要獲得某個終止線程的出口狀態
時,就需要
這個函數來完成。
如果一個線程不是分離的,他的資源要在另一個線程調用 pthread_join 與他匯合之後才
能被釋放,這種
線程稱爲可匯合的線程,否則就是分離的線程,分離的線程一旦終止執行,系統將立即回
收其資源。
被另外一個
與進程只能是父進程等待子進程不同,任何線程都可以等待其他線程,但是一個線程只能
線程等待一次。
--------------------------------------------------------------
可匯合線程和分離線程:
由默認屬性創建的線程都是可匯合的線程,因此線程結束後停留在線程池裏面等待系統釋放資
源,這樣的話系統的資源
將不能利用最大化,所以,我們在退出某個線程的時候,如果我們不需要等待其他線程,那麼
就告訴系統,可以回收該
線程佔用的資源了。可以用下面的函數來做這件事情:
int
pthread_detach(pthread_t thread);
----------------------------------------------------------
創建特殊屬性的線程:
(1)、分離狀態,可以取兩個值:
PTHREAD_CREATE_DETACHED:表示分離的線程
PTHREAD_CREAT_JOINABLE:表示匯合的狀態,也是系統默認的狀態
int
pthread_attr_init(pthread_attr_t *attr):用於初始化一個線程屬性,
在使用這個線程屬性之前,必須要
先初始化,因爲 attr 現在是一個空指針,沒有指向的實質性對象,所以需要告訴系統我們
需要一個指向線程屬性的指針。
int
pthread_attr_destroy():用於銷燬一個線程屬性,當用這個線程屬性創建了
41線程之後,這個線程屬性
就沒有什麼用了,爲了節約內存,我們需要將這個空間釋放掉。
int pthread_attr_getdetachstate(pthread_attr_t* attr,int *
detachstate):查看線程屬性的分離狀態,
函數將返回該線程屬性的分離狀態或者匯合狀態
int
pthread_attr_setdetachstate(const pthread_attr_t* attr,int
* detachstate);
該函數用來設置線程屬性的分離狀態
(2)、棧大小
(3)、棧地址
int
pthread_attr_getstacksize(const pthread_attr_t* restrict
attr,size_t *restrict stacksize):
用來查看當前線程棧的大小
int pthread_attr_setstacksize(pthread_attr_t* attr,size_t
stacksize):用來設置棧的大小
int
pthread_attr_getstackaddr(const pthread_attr_t* restrict
attr,void**restrict stackaddr):
int
pthread_attr_setstackaddr(pthread_attr_t* attr,void*
stackaddr):
上面兩個函數用來獲取和設置棧的大小
int
pthread_attr_getstack(const pthread_attr_t* restrict
attr,void** restrict stackaddr,size_t* restrict stacksize):
int
pthread_attr_setstack(pthread_attr_t* attr,void**
stackaddr,size_t stacksize);
上面兩個函數將同時完成棧地址和大小的設置和查詢。推薦使用。
(4)、棧溢出保護區
int pthread_attr_getguardsize(const pthread_attr_t* restrict
attr,size_t* restrict guardsize):
int
pthread_attr_setguardsize(pthread_attr_t* attr,size_t
guardsize):
上面兩個函數用來查看或者設置保護區的大小
-------------------------
int mprotect(guard_addr,pagesize,PROT_NONE):該函數用來設置空間中某一區
間的讀寫許可,他將設置從
[guard_addr.......pagesize-1]的地址空間爲不可讀。
-------------------------------------------------------------------------
---------------------
互斥變量:線程之間有時需要同步,所以需要解決線程之間的通信問題。
一般有最基本的三種同步方法:
(1)、互斥執行:當兩個線程必須依次訪問某個共享對象時,就需要這種同步方法
(2)、條件同步:線程必須等待某個時間發生時,就需要這種同步
(3)、柵欄同步:用於控制線程執行過程中的匯合
-------------------------------------------
互斥變量主要用於多個線程競爭訪問某個共享資源。
表示互斥變量的數據類型爲:pthread_mutex_t
初始化:
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER:靜態互斥變量初始化(非 malloc
申請)
int
pthread_mutex_init(pthread_mutex_t* restrict mutex,const
42pthread_mutexattr_t* restrict attr)
int
pthread_mutex_destroy(pthread_mutex_t* mutex)
互斥變量初始化之後處於未鎖狀態,並且屬性不能再改變
-----------------------------------------------------------
互斥變量屬性:
unix 定義了兩種屬性:
(1)、進程共享屬性
PTHREAD_PROCESS_PRIVATE:限制在一個進程之內
PTHREAD_PROCESS_SHARED:進程間可用
(2)、類型屬性
PTHREAD_MUTEX_NORMAL:基本類型,沒有錯誤檢查
PTHREAD_MUTEX_RECURSIVE:遞歸類型,可以重複加鎖
PTHREAD_MUTEX_ERRORCHECK:檢查錯誤
PTHREAD_MUTEX_DEFAULT:默認類型
互斥變量屬性類型爲:pthread_mutexattr_t
int
pthread_mutexattr_init(pthread_mutexattr_t* attr):初始化,默認屬性
int
pthread_mutexattr_destroy(pthread_mutexattr_t* attr):銷燬
-------------------------------------------------------------------------
int
pthread_mutexattr_setpshared(pthread_mutexattr_t* attr,int
pshared);
int
pthread_mutexattr_getpshared(const pthread_mutexattr_t* restrict
attr,int *restrict pshared);
int
pthread_mutexattr_settype(thread_mutexattr_t* attr,int type);
int
pthread_mutexattr_gettype(const pthread_mutexattr_t* restrict
attr,int *restrict type);
對於上面的函數,不需要多說什麼,看字面意思就好。
-----------------------------------------------------------------
互斥變量的加鎖與解鎖
int
pthread_mutex_lock(pthread_mutex_t* mutex)
int
pthread_mutex_trylock(pthread_mutex_t* mutex)
int
pthread_mutex_unlock(pthread_mutex_t* mutex)
第二個函數和第一個函數的區別就是,當一個互斥鎖申請沒有得到滿足時,第二個函數將立即返回
並設置 EBUSY 的錯誤,而第一個函數將被阻塞直到獲得申請的鎖。
-------------------------------------------------------------------------
---
spin 鎖:
當線程對互斥變量上鎖 成功時,可以有以下解決方案:
(1)、立即阻塞,即釋放線程佔用的 cpu 資源,使其從運行狀態進入等待狀態
(2)、讓線程在一個循環中輪詢查看能否獲得,如果一段時間後還是無法成功,那麼進入阻塞
(3)、在一個循環中不斷輪詢,直到成功
spin 鎖就是,當上鎖受阻時,線程不必阻塞,而是可以輪詢直到獲得鎖。
----------------------------------------------------------------------
int pthread_spin_init(pthread_spinlock_t* lock,int pshared)
int
pthread_spin_detroy(pthread_spinlock_t* lock);
int
pthread_spin_lock(pthread_spinlock_t* lock);
int
pthread_spin_trylock(pthread_spinlock_t* lock)
int
pthread_spin_unlock(pthread_spinlock_t* lock)
----------------------------------------------------------------------
43----------------------------------------------------------------------
讀寫鎖:
讀寫鎖是專門設計來改善程序並行性的鎖機制,他支持對共享數據 “ 共享讀,互斥寫 ”
可以以讀方式上鎖,寫方式上鎖。
-----------------------------------------------------------------------
int
pthread_rwlock_t rwlock=PTHREAD_RWLOCK_INITIALIZER
int
pthread_rwlock_init(pthread_rwlock_t* restrict rwlock,const
pthread_rwlockattr_t* restrict attr);
int
pthread_rwlock_destroy(pthread_rwlock_t* lock);
讀寫鎖和互斥變量一樣,也可以設置爲在進程內部還是進程之間;
int
pthread_rwlockattr_init(pthread_rwlockattr_t* attr);
int
pthread_rwlockattr_destroy(pthread_rwlockattr_t* attr);
int
pthread_rwlockattr_setpshared(pthread_rwlockattr_t* attr,int
pshared);
int
pthread_rwlockattr_getpshared(pthread_rwlockattr_t* restrict
attr,int *restrict pshared);
int
pthread_rwlock_rdlock(pthread_rwlock_t* rwlock): 以讀方式上鎖
int
pthread_rwlock_tryrdlock(pthread_rwlock_t* rwlock):讀方式上鎖
int
pthread_rwlock_wrlock(pthread_rwlock_t* rwlock):以寫方式上鎖
int
pthread_rwlock_trywrlock(pthread_rwlock_t* rwlock):以寫方式上鎖
int
pthread_rwlock_unlock(pthread_rwlock_t* rwlock):解鎖
----------------------------------------------------------------------------
------------
條件變量:線程實現等待喚醒有兩種方式
(1)、輪詢計數,當計數不爲 0 時就自動脫落等待狀態
(2)、將自己加入到一個等待隊列,然後阻塞自己直到有人來喚醒自己
那什麼叫條件變量呢?
條件變量在線程需要等待共享數據滿足某個狀態時使用,如果狀態不滿足,他將在該條件變量
上面等待,直到將來另一個線程導致狀態滿足。並通過相應的條件變量來喚醒爲止。
條件變量有三個成分
(1)、條件變量本身
(2)、一個謂詞,就是線程用來查看是否需要等待或者被設置的條件
(3)、一個與之相連的互斥變量,是用來保護謂詞的
條件變量的結構時是:pthread_cond_t
創建和銷燬條件變量:
------------------------------------
pthread_cond_t cond=PTHREAD_COND_INITIALIZER;
int
pthread_cond_init(pthread_cond_t* restrict cond,const
pthread_condattr_t* restrict attr)
int
pthread_cond_destroy(pthread_cond_t* cond)
-------------------------------------------------
條件變量的屬性:
只能是 PTHREAD_PROCESS_PRIVATE OR PTHREAD_PROCESS_SHARED,表示用於進程之間還是
進程內部。
-------------------------------------------------------------------------
---------------------
int pthread_condattr_init(pthread_condattr_t* attr)
int
pthread_condattr_destroy(pthread_condattr_t* attr)
int
pthread_condattr_getpshared(pthread_condattr_t* attr,int*
pshared);
int
pthread_condattr_setpshared(pthread_condattr_t* attr,int*
pshared);
44需要注意的一點是,當我們設置爲在進程之間時,需要將他分配在共享存儲段裏面,不然不同進程
無法訪問。
-------------------------------------------------------------------------
------
等待條件變量:
一個執行正常的等待操作,一個執行定時等待(是終止時間而不是等待時間,設置到終點的時間)
----------------------------------------------------------------------------
------
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)
注意,互斥變量在線程調用該函數之前必須已經上鎖,這兩個函數在阻塞線程之前自動釋放
mutex。
----------------------------------------------------------------------------
---
喚醒條件變量:
(1 )、每次喚醒一個線程,稱爲 “ 發信號 ”
(2 )、一次就喚醒等待在同一個條件變量上面的所有線程,稱爲 “ 廣播 ”
int
int
pthread_cond_signal(pthread_cond_t* cond);
pthread_cond_broadcast(pthread_cond_t* cond);
如果在某個條件變量上面沒有等待的線程,調用該函數不起任何作用(除了浪費資源)
--------------------------------------------------------------------------
關於柵欄同步:
一個線程運行到某個點時,需要等待其他線程都到達這個點後才能繼續,這種等齊所有線程
到達同步稱爲柵欄同步,程序中的這一點稱爲柵欄同步點,實現柵欄同步需要兩個基本函數:
(1)、柵欄初始化函數,用於建立柵欄同步點需要等待的線程個數
(2)、柵欄等待函數,用於阻塞調用者直到指定個數的線程都到達此柵欄同步點爲止(喚醒)
107、線程專有數據
共享數據:只有一個存儲副本,並且所有線程都可以訪問,因爲只有一個副本,所以所有線程看到
的都是最後一個訪問
共享數據的結果
線程私有數據:每一個線程有各自私有的存儲副本的數據,線程可以隨意的讀取自己的私有數據而
不會影響到別的線程
線程專有數據:對線程私有的或者特定於線程的數據
-------------------------------------------------------------
pthread 實現的線程專有數據採用鍵/數據相結合的方法。
應用需要給每個線程專有數據連接一個鍵,這個鍵是所有線程共享的全局數據,當線程引用一個鍵
時,引用的是自己的部分。
線程專有數據鍵的類型爲:pthread_key_t
------------------------------------------------------------------------
45創建鍵:
int
pthread_key_create(pthread_key_t *key,void(*destructor)(void*));
創建鍵的作用是爲每一個線程相連一個指向專有存儲空間的指針。這些指針初始化爲 NULL,key
一旦創建,進程內的所有線程便可以通過函數 pthread_getspecific()使之指向各自分配的專有
數據空間。
第二個參數是我們指定的一個析構函數,這個函數負責在線程終止時釋放與 key 相連的線程專有數
據空間
每一個線程專有數據鍵只能創建一次,第二次創建將會替代第一次創建的內容。
所以我們可以用下面的函數來只創建一次:
------------------------------------------------------------------------
pthread_once_t once_control=PTHREAD_ONCE_INIT;
int pthread_once(pthread_once_t* once_control,void(*init_routine)(void));
參數 once_control 用來確定相連的初始化函數是否已經被調用。這個初始化函數就是第二個參數
-------------------------------------------------------------------------
當不再需要一個專有數據鍵時,可以調用函數 pthread_key_delete 來刪除
int
pthread_key_delete(pthread_key_t* key);
--------------------------------------------------------------------
使用線程專有數據:
每一個線程的專有數據鍵創建時,初始值爲 NULL,爲了使用專有數據,每一個線程需要給專有數據
鍵指定值。
int
pthread_setspecific(pthread_key_t key,const void* value);
void*
pthread_getspecific(pthread_key_t key);
108、取消線程
(1)、可取消狀態:
PTHREAD_CANCEL_ENALBE(允許取消)
PTHREAD_CANCEL_DISABLE(不允許取消)
(2)、可取消類型
PTHREAD_CANCEL_DEFERRED:延遲取消
PTHREAD_CANCEL_ASYNCHRONOUS:異步取消
--------------------------------------------------------
設置可取消狀態和可取消類型:
int
pthread_setcancelstate(int state,int* oldstate);
int
pthread_setcanceltype(int type,int* oldtype);
-------------------------------------------------------------
取消線程和取消點:
我們可以用下面的函數來取消一個線程:
int
pthread_cancel(pthread_t target_thread);
該函數負責向目標線程發送取消線程的消息,但是不負責監督線程取消,而是通知完成即刻返回。
我們可以用下面的函數來建立自己的取消點:
void
pthread_testcancel(void):該函數檢查調用線程是否有懸掛着的取消請求。
-------------------------------------------------------------------------
現場清理:
void
pthread_cleanup_push(void (*routine)(void*),void* argv);
void
pthread_cleanup_pop(int execute);
這兩個函數需要成對出現,而且不應該被嵌套,應該像括號一樣出現,不然無法達到預期的效果。
對於第二個函數,參數非 0 則表示將棧頂的清理函數移出來,然後執行。
109、線程調度
46線程需要操作系統調度才能獲得處理機資源,所以需要調度策略和算法。
線程調度模式:他描述一個線程與誰競爭
(1)、進程調度模式:PTHREAD_SCOPE_PROCESS
(2)、系統調度模式:PTHREAD_SCOPE_SYSTEM
用戶線程是由應用程序通過調用 pthread 庫來創建的,他們想要執行就需要與處理機關聯起來。
線程與處理機關聯是通過內核線程來實現的。內核線程由線程庫調用操作系統提供的系統調用
創建,用戶線程只有與操作系統內核線程相關聯才能被調度到處理器上面。
(1)、綁定的線程:用戶線程與一個內核線程唯一關聯
(2)、非綁定線程:可以與進程內的任意一個有效的內核線程相關聯
多線程的實現:
(1)、多對一模式:即進程內的所有用戶線程都與該進程唯一的一個內核線程相連,也稱爲用戶線
程實現,因爲
他是由用戶級別的線程庫來實現的,不需要操作系統的特殊支持,用戶線程庫負責調度
(2)、一對一模式:每一個用戶線程直接對應一個內核線程,並從始至終的與這個內核線程綁定在
一起,這種模式
是在操作系統內核空間實現的,因此也稱爲內核線程
(3)、混合模式:即多對一和一對一都存在的,也稱爲內核線程實現,因爲操作系統調度的是內核
線程而不是用戶線程
------------------------------------------------------
調度策略與優先級:
三種調度策略:
(1)、SCHED_FIFO:先進先出,具有優先級意識,維護多個優先級隊列,每次從隊列頭取出線程
執行,執行完放在隊列尾。
低優先級的線程有可能被高優先級的線程替代,需要注意的是:如果線程是被阻塞的,系統會將他
放在隊列的尾部,如果線程
是被取代的,那麼這個線程將被放在線程隊列裏面的最前面,但是這個策略不受時間片的影響,所
以可能會獨佔處理機。
(2)、SCHED_RR:時間片輪轉,如果一個線程的時間片用完了,系統將會把這個線程放在隊列的
尾部重新排隊,如果這個線程是
被具有更高優先級的線程取代的,則在他恢復執行後,使用被取代前剩餘的時間片大小。
(3)、SCHED_OTHER:我也不知道該怎麼說。
-----------------------------------------------------------------
查看優先級區間:
int
sched_get_priority_max(int policy):
int
sched_get_priority_min(int policy):
-----------------------------------------------------------------
線程調度屬性:
(1)、競爭範圍屬性,我們已經知道可以取哪兩個值
(2)、PTHREAD_INHERIT_SCHED OR PTHREAD_EXPLICIT_SCHED:繼承屬性,前者表示新線
程的調度策略和相連的
調度參數從創建線程繼承,後者則表示自己設定
(3)、SCHED_FIFO OR SCHED_RR OR SCHED_OTHER
(4)、調度參數屬性
int
pthread_attr_getscope(const pthread_attr_t* restrict attr,int
*restrict contentionscope)
int
pthread_attr_setscope(pthread_attr_t* attr,int
contentionscope)
47int
*inherit)
int
inherit)
int
int
pthread_attr_getinheritsched(pthread_attr_t* attr,int
pthread_attr_setinheritsched(pthread_attr_t* attr,int
pthread_attr_getschedpolicy(pthread_attr_t* attr,int* policy)
pthread_attr_setschedpolicy(pthread_attr_t* attr,int policy)
int
pthread_attr_getschedparam(const pthread_attr_t *restrict
attr,struct sched_param* restrict param)
int
pthread_attr_setschedparam(pthread_attr_t* attr,const struct
sched_param* restrict param)
----------------------------------------------------------------------
動態改變線程的調度策略和優先級:
int
pthread_getschedparam(pthread_t thread,int* restrict
policy,struct sched_param* restrict param)
int
pthread_setschedparam(pthread_t thread,int policy,const struct
sched_param* param)
int
pthread_setschedprio(pthread_t thread,int prio);
110、線程與信號
在多線程模式下,任然通過函數 sigaction 函數來建立,並且被進程中的所有線程共享,即對於每
一種信號,所有線程
的信號動作都相同,也就是說,信號動作是進程範文內的。
在多線程模式下,進程內的每一個線程都有一個信號屏蔽,這樣,儘管所有線程共享相同的信號動
作,但每一個線程可以阻塞
自己不想處理的信號。
int
pthread_sigmask(int how,const sigset_t* restrict set,sigset_t*
restrict oset)
我們可以回想函數:sigprocmask。
如果 set 不是 NULL 的話,它指向用於改變信號屏蔽的信號集合。此時參數 how 指明任何改變信號
(1)、SIG_BLOCK:將 set 所指的信號集合添加到現有的信號集合中
(2)、SIG_UNBLOCK:將 set 所指的信號集合從現有的信號集合中移除
(3)、SIG_SETMASK:將 set 所指的信號集合設置爲新的信號屏蔽集合
oset 將返回原來的信號屏蔽。
--------------------------------------------------------
向線程發送信號:
線程可以向進程發送信號,也可以向進程內的線程發送信號,但是不能向其他進程的線程發送
信號,因爲線程 ID
是進程範圍內有效的。
線程向進程發送信號任然使用 kill 函數,如果要向其他線程發送信號,則需要下面的函數來
實現:
int
pthread_kill(pthread_t thread,int sig):
該函數向 thread 發送一個 sig 信號,如果 sig 爲 0,則同 kill 一樣。
我們可以通過下面的函數調用來實現向線程向自己發送信號:
int
pthread_kill(pthread_self(),sig)
int
raise(sig)
----------------------------------------------------------------
等待信號:
使用下面這個函數,在收到信號後直接對信號進行處理而不再需要信號句柄:
int
sigwait(const sigset_t *restrict set,int *restrict sig)
48111、一種新的事件通知方法:SIGEV_THREAD
讓我們再次看看一個結構:
struct sigevent
{
union sigval sigev_value;
int
sigev_signo;
int
sigev_notify;
void(*sigev_notify_function)(union sigval);
pthread_attr_t* sigev_notify_attributes;
}
如果指定 sigev_notify 爲 SIGEV_THREAD,系統將調用 sigev_notify_attributes 指定的
屬性創建一個新的線程。該
線程將執行 sigev_notify_function 指定的函數,sigev_value 則將作爲該函數的參數。
--------end 2016/3/22-----------------hujian--------
nankai-------------------
49
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章