linux学习笔记 -- 系统编程
系統(tǒng)編程
- 相關(guān)概念
- 概念
- 簡(jiǎn)易cpu結(jié)構(gòu)
- mmu內(nèi)存管理單元
 
- 環(huán)境變量
- PATH
- SHELL
- HOME
- LANG
- TERM
- getenv
- setenv
- unsetenv
 
- 進(jìn)程控制
- fork函數(shù)
- getpid
- getppid
- getuid
- getgid
- 父子進(jìn)程共享
- gdb調(diào)試
 
- exec
- execl
- execlp
- execle
- execv
- execvp
- execve
- 一般規(guī)律
 
- 回收子進(jìn)程
- 孤兒進(jìn)程
- 僵尸進(jìn)程
- wait
- waitpid
 
 
- 2.IPC方法
- 管道
- 管道的概念
- pipe函數(shù)
- 管道的讀寫行為
- 管道緩沖區(qū)大小
- 管道的優(yōu)勢(shì)
 
- FIFO
- 共享存儲(chǔ)映射(共享內(nèi)存)
- 文件進(jìn)程間通訊
- 存儲(chǔ)映射I/O
- mmap父子進(jìn)程通訊
- 匿名映射
- mmap無(wú)血緣關(guān)系進(jìn)程間通訊
 
 
- 3.信號(hào)
- 信號(hào)的事件和狀態(tài)
- 信號(hào)的產(chǎn)生
- 終端按鍵產(chǎn)生的信號(hào)
- 硬件異常產(chǎn)生信號(hào)
- kill產(chǎn)生
- raise和abort
- 軟件條件產(chǎn)生
 
- 信號(hào)的捕捉
- **signal函數(shù):**
- **sigaction函數(shù):**
- 信號(hào)捕捉特性
 
- 信號(hào)集操作函數(shù)
- 信號(hào)集設(shè)定
- sigprocmask
- sigpending函數(shù)
 
- 競(jìng)態(tài)條件
- pause
- 時(shí)序競(jìng)態(tài)
- 全局變量異步IO
- 可/不可重入函數(shù)
 
- SIGCHLD信號(hào)
- SIGCHLD的產(chǎn)生條件
- 借助SIGCHLD信號(hào)回收子進(jìn)程
 
- 信號(hào)傳參
- 發(fā)送信號(hào)傳參
- 捕捉函數(shù)傳參
 
- 中斷系統(tǒng)調(diào)用
 
- 4.守護(hù)進(jìn)程
- 終端
- 終端的啟動(dòng)流程
- ttyname
- 網(wǎng)絡(luò)終端
 
- 進(jìn)程組
- 進(jìn)程操作函數(shù)
 
- 會(huì)話
- 創(chuàng)建會(huì)話
 
- 守護(hù)進(jìn)程
- 創(chuàng)建守護(hù)進(jìn)程模型
 
 
- 5.線程
- 線程的概念
- 什么是線程
- linux內(nèi)核線程實(shí)現(xiàn)原理
- 線程共享資源
- 線程非共享資源
- 線程優(yōu)缺點(diǎn)
 
- 線程控制原語(yǔ)
- pthread_self
- pthread_create
- pthread_exit
- pthread_join
- pthread_detach
- pthread_cancel
- pthread_equal
- 控制原語(yǔ)對(duì)比
 
- 線程屬性
- 線程屬性初始化
- 線程的分離狀態(tài)
- 線程的棧地址
- 線程的棧大小
 
- NPTL
- 線程注意事項(xiàng)
 
- 線程同步
- 同步概念
- 線程同步
- 數(shù)據(jù)混亂原因
 
- 互斥量mutex(互斥鎖)
- 主要應(yīng)用函數(shù)
- 加鎖與解鎖
- 加鎖步驟測(cè)試
 
- 死鎖
- 讀寫鎖
- 讀寫鎖狀態(tài)
- 讀寫鎖特性
- 主要應(yīng)用函數(shù)
- 讀寫鎖實(shí)例
 
- 條件變量
- 主要應(yīng)用函數(shù):
- 生產(chǎn)者消費(fèi)者條件變量模型
- 條件變量的優(yōu)點(diǎn)
 
- 信號(hào)量
- 主要應(yīng)用函數(shù)
- 生產(chǎn)者消費(fèi)者信號(hào)量模型
 
- 進(jìn)程間同步
- 互斥量mutex
- 文件鎖
 
 
- 其他
- 文件存儲(chǔ)
- UNIX
- 優(yōu)化
 
黑馬教程分享視頻學(xué)習(xí)筆記
相關(guān)概念
概念
程序:編輯好的二進(jìn)制文件(.out);在磁盤上,不占用系統(tǒng)資源(cpu、內(nèi)存、打開的文件、設(shè)備、鎖);進(jìn)程:抽象的概念,與操作系統(tǒng)原理聯(lián)系緊密。進(jìn)程是活躍的程序,占用系統(tǒng)資源。在內(nèi)存中執(zhí)行(程序運(yùn)行起來(lái),產(chǎn)生一個(gè)進(jìn)程)同一個(gè)程序可以同時(shí)啟動(dòng)兩個(gè)進(jìn)程。并發(fā):并行執(zhí)行。 單道程序設(shè)計(jì)模式:不同任務(wù)之間排隊(duì)使用cpu(DOS) 多道程序設(shè)計(jì)模式:不同程序同時(shí)使用cpu;通過(guò)時(shí)鐘中斷,硬件手段在不同進(jìn)程之間回收、分配cpu使用權(quán)力;簡(jiǎn)易cpu結(jié)構(gòu)
 數(shù)據(jù)讀取時(shí),硬盤到內(nèi)存中,再到緩沖區(qū)中,然后進(jìn)入cpu的寄存器中。cpu的預(yù)取器取出一條指令;譯碼器翻譯指令并存儲(chǔ)相關(guān)數(shù)據(jù)到寄存器中;然后交給ALU進(jìn)行計(jì)算,操作寄存器堆,再放回緩存,返回到內(nèi)存。
mmu內(nèi)存管理單元
 虛擬地址:可用的地址空間有4G。
mmu將虛擬內(nèi)存地址與物理內(nèi)存地址進(jìn)行映射;設(shè)置修改內(nèi)存訪問(wèn)級(jí)別(內(nèi)核空間和用戶空間),cpu可使用的級(jí)別有四種,linux只使用了最高和最低兩種。
PCB:進(jìn)程描述符、進(jìn)程控制塊。
進(jìn)程之間彼此獨(dú)立,每運(yùn)行一個(gè)程序,需要進(jìn)行物理內(nèi)存映射,開辟新的物理內(nèi)存空間使用。但不同進(jìn)程之間的映射的物理內(nèi)存是同一塊的,由mmu實(shí)現(xiàn)不同進(jìn)程的PCB描述數(shù)據(jù)不同。
進(jìn)程控制塊PCB:
? 在linux內(nèi)核的進(jìn)程控制塊是task_struct結(jié)構(gòu)體。查找結(jié)構(gòu)體命令:grep -r "task_struct {" /usr/
? 存在于/usr/src/linux-headers-3.16.0-30/include/linux/sched.h,常用成員如下:
1. 進(jìn)程ID。系統(tǒng)中每個(gè)進(jìn)程有唯一的id,在c語(yǔ)言中用pid_t類型表示,非負(fù)整數(shù) 2. 進(jìn)程狀態(tài)。就緒(包括初始化,等待cpu分配時(shí)間片)、運(yùn)行(占用cpu)、掛起(等待除cpu以外的其他資源 主動(dòng)放棄cpu)、停止 3. 進(jìn)程切換時(shí)需要保存和恢復(fù)的一些cpu寄存器 4. 描述虛擬地址空間的信息 5. 描述控制終端的信息 6. 當(dāng)前工作目錄 7. umask掩碼 8. 文件描述符表,包括很多指向file結(jié)構(gòu)體的指針。 9. 和信號(hào)相關(guān)的信息。 10. 用戶id和組id 11. 會(huì)話和進(jìn)程組。 12. 進(jìn)程可以使用的資源上限。``ulimit -a`` linux系統(tǒng)中查看資源上下限環(huán)境變量
linux 是多任務(wù)、多用戶的開源操作系統(tǒng)。
環(huán)境變量,是指在操作系統(tǒng)中用來(lái)指定操作系統(tǒng)運(yùn)行環(huán)境的一些參數(shù)。具備一下特征:
存儲(chǔ)形式:與命令行參數(shù)類似。char* []數(shù)組,數(shù)組名environ,內(nèi)部存儲(chǔ)字符串,NULL作為哨兵結(jié)尾。
使用形式:與命令行參數(shù)類似。
加載位置:與命令行參數(shù)類似。位于用戶區(qū),高于stack的起始位置。
引入環(huán)境變量表:需聲明環(huán)境變量。extern char** environ;
#include<stdio.h>extern char **environ;int main(void) {int i;for(i = 0; environ[i]; i++){printf("%s\n",environ[i]);}return 0; }PATH
? 可執(zhí)行文件的搜索路徑。ls命令也是一個(gè)程序,執(zhí)行他不需要提供完整的路徑名稱/bin/ls,但是通常我們執(zhí)行當(dāng)前目錄下的程序a.out確需要提供完整的路徑名/a.out,這是因?yàn)镻ATH環(huán)境變量里的值包含了ls命令所在的目錄/bin,卻不包含a.out所在的目錄。PATH環(huán)境變量的值可以包含多個(gè)目錄,用:號(hào)隔開,使用時(shí),shell會(huì)按照環(huán)境變量順序,從前到后檢索對(duì)應(yīng)路徑下是否有可用的應(yīng)用程序,知道最后或找到。在shell中用echo命令可以查看這個(gè)環(huán)境變量的值:echo $PATH
SHELL
? 當(dāng)前Shell,他通常是/bin/bash,當(dāng)前命令解析器。
HOME
? 當(dāng)前的家目錄
LANG
? 當(dāng)前的預(yù)言,執(zhí)行echo $LANG后可以看到為 zh_CN.UTF-8.
TERM
? 當(dāng)前終端類型,在圖形界面終端下它的值通常是xterm,終端類型決定了一些程序的顯示方式,比如圖形界面終端可以顯示漢字,而字符終端一般不行。
getenv
獲取環(huán)境變量值
char *getenv(const char * name);成功:返回環(huán)境變量的值;失敗:NULL(name不存在)
setenv
設(shè)置環(huán)境變量的值
int setenv(const char *name, const char *value, int overwrite);成功:0;失敗-1.
參數(shù)overwrite取值:1. 覆蓋原環(huán)境變量;0. 不覆蓋(常用于設(shè)置新環(huán)境變量)
unsetenv
刪除環(huán)境變量name的定義
int unsetenv(const char * name);成功:0;失敗:-1
注意: name不存在仍返回0,當(dāng)name命名為“ABC=”時(shí)會(huì)出錯(cuò)。
#include<stdio.h> #include<stdlib.h> #include<string.h>int main(void) {char *val;const char *name = "wxf";val = getenv(name);printf("1 %s=%s\n",name,val);int ret = setenv(name, "hallo world", 1);val = getenv(name);printf("2 %s=%s\n",name,val);#if 0ret = unsetenv("wxf=");printf("3 ret = %d\n",ret);val = getenv(name);printf("4 %s=%s\n",name,val); #elseret = unsetenv(name);printf("3 ret = %d\n",ret);val = getenv(name);printf("4 %s=%s\n",name,val); #endifreturn 0; }進(jìn)程控制
fork函數(shù)
? 創(chuàng)建一個(gè)子進(jìn)程。
? pid_t fork(void); 失敗返回-1;成功返回:父進(jìn)程返回子進(jìn)程的ID(非負(fù))、子進(jìn)程返回0 。
? pid_t 類型表示進(jìn)程ID,但為了表示-1,他是有符號(hào)整形。(0不是有效的進(jìn)程ID,init最小,為1)
? 注意返回值,不是fork函數(shù)能返回兩個(gè)值,而是fork后,fork函數(shù)變成兩個(gè),父子需各自返回一個(gè)。fork函數(shù)執(zhí)行完成后,創(chuàng)建了子進(jìn)程,父、子進(jìn)程同時(shí)繼續(xù)執(zhí)行fork函數(shù)后面的代碼。
#include<stdio.h> #include<unistd.h> #include<stdlib.h>int main(void) {printf("begin 88888888888888888888\n");pid_t pid;pid = fork();if(pid == -1){perror("fork");exit(1);}printf("pid = %d \n",pid);if(pid == 0){printf("child process,pid = %u,ppid=%u\n",getpid(),getppid());}else{printf("parent process,pid = %u,ppid=%u\n",getpid(),getppid());}printf("end 88888888888888888888\n");return 0; }循環(huán)創(chuàng)建n個(gè)子進(jìn)程
? 一次fork函數(shù)調(diào)用可以創(chuàng)建一個(gè)子進(jìn)程。那么創(chuàng)建N個(gè)子進(jìn)程應(yīng)該如何實(shí)現(xiàn)呢,簡(jiǎn)單想,``for(int i=0;i<n;i++)){fork()}即可,但是是這樣嗎。
#include<stdio.h> #include<unistd.h> #include<stdlib.h>int main(void) {printf("begin 88888888888888888888\n");pid_t pid;int times = 5;int i = 0;for ( i = 0; i < times; i++){pid = fork();if(pid == -1){perror("fork");exit(1);}if(pid == 0){printf("child %d process,pid = %u,ppid=%u\n",i+1, getpid(),getppid());break;//子進(jìn)程跳出循環(huán),不再產(chǎn)生孫進(jìn)程}}if(i < 5){sleep(i);//為了保證輸入的先后順序printf("end child %d pid = %u\n",i+1,getpid()); }else{sleep(i);printf("end 88888888888888888888\n");}return 0; }? 子進(jìn)程產(chǎn)生后與父進(jìn)程同時(shí)搶奪cpu使用時(shí)間,(tips:沒(méi)有理論依據(jù),但父進(jìn)程獲取cpu的幾率大些)
? 去除sleep后打印混亂。執(zhí)行可執(zhí)行命令的shell進(jìn)程(爺爺進(jìn)程)在父進(jìn)程return后輸出控制臺(tái),而子進(jìn)程可能還沒(méi)結(jié)束,將繼續(xù)打印。
getpid
返回當(dāng)前進(jìn)程IDpid_t getpid();
getppid
返回當(dāng)前進(jìn)程父進(jìn)程IDpid_t getppid();
getuid
獲取當(dāng)前進(jìn)程實(shí)際用戶ID:uid_t getuid(void);
對(duì)應(yīng) 獲取當(dāng)前進(jìn)程有效用戶ID:uid_t geteuid(void);
getgid
獲取當(dāng)前進(jìn)程實(shí)際用戶組ID:gid_t getgid(void);
對(duì)應(yīng) 獲取當(dāng)前進(jìn)程有效用戶組ID:gid_t getegid(void);
父子進(jìn)程共享
剛fork之后(后續(xù)執(zhí)行代碼后按照各自進(jìn)程處理):
? 父子相同處:全局變量、.data、.text、棧、堆、環(huán)境變量、用戶ID、宿主目錄、進(jìn)程工作目錄、信號(hào)處理方式等。
? 父子不同處:
1. 進(jìn)程ID2. fork返回值3. 父進(jìn)程ID4. 進(jìn)程運(yùn)行時(shí)間5. 鬧鐘(定時(shí)器)6. 未決信號(hào)集? 父子進(jìn)程間遵循讀時(shí)共享,寫時(shí)復(fù)制的原則。無(wú)論子進(jìn)程執(zhí)行父進(jìn)程的邏輯還是執(zhí)行自己的邏輯都能節(jié)省內(nèi)存開銷。
注意:父子進(jìn)程共享:
gdb調(diào)試
編譯時(shí)需要加-g:gcc fork.c -g
進(jìn)入調(diào)試:gbd a.out
? 使用gdb調(diào)試的時(shí)候,只能跟蹤一個(gè)進(jìn)程,可以在fork之前,通過(guò)指令設(shè)置gdb調(diào)試工具跟蹤父進(jìn)程或者子進(jìn)程。默認(rèn)跟蹤父進(jìn)程。
? set follow-fork-mode child 命令設(shè)置gdb在fork之后跟蹤子進(jìn)程
? set follow-fork-mode parent 命令設(shè)置gdb在fork之后跟蹤父進(jìn)程
exec
? fork創(chuàng)建子進(jìn)程后執(zhí)行的是和父進(jìn)程相同的程序(但有可能執(zhí)行不同的代碼分支),子進(jìn)程往往需要調(diào)用一種exec函數(shù)以執(zhí)行另一個(gè)程序。當(dāng)進(jìn)程調(diào)用一種exec函數(shù)時(shí),該進(jìn)程的用戶空間代碼和數(shù)據(jù)完全被新程序替換,從新程序的啟動(dòng)例程(main)開始執(zhí)行。調(diào)用exec并不創(chuàng)建新進(jìn)程,所以調(diào)用exec前后該進(jìn)程的ID并未改變。
? 將當(dāng)前進(jìn)程的.text、.data替換為所要加載的程序的.text、.data,然后讓進(jìn)程從新的.text第一條指令開始執(zhí)行,但進(jìn)程ID不變,換核不換殼。
? 其實(shí)有六種以exec開頭的函數(shù),統(tǒng)稱為exec函數(shù)。
execl
加載一個(gè)進(jìn)程,通過(guò) 路徑+程序名 來(lái)加載。
int execl(const char * path, const char * arg,...);
成功:無(wú)返回;失敗:-1
對(duì)比execlp, 如加載ls命令帶有-l, -F參數(shù)
execlp("ls", "ls", "-l", "-F", NULL); 使用程序名在PATH中搜索。
execl("/bin/ls", "ls", "-l", "-F", NULL); 使用參數(shù)1給出的絕對(duì)路徑搜索
也可以執(zhí)行自己的程序,如當(dāng)前目錄下的fork:execl("./fork", "fork", NULL);
execlp
int execlp(const char *file, const char * arg,...);
list,path
參數(shù):file 可執(zhí)行程序文件名;arg命令行參數(shù),注意第一個(gè)arg相當(dāng)于argv[0],相當(dāng)于ls -l中的ls,一般而言,可執(zhí)行程序可能不會(huì)讀取argv[0],所以argv[0],只起到站位的作用,只要后續(xù)參數(shù)不錯(cuò)就行。另外需要以NULL結(jié)尾。
該函數(shù)需要配合PATH環(huán)境變量來(lái)使用,當(dāng)PATH中所有目錄搜索后沒(méi)有參數(shù)1則出錯(cuò)返回。
該函數(shù)通常用來(lái)調(diào)用系統(tǒng)程序。如:ls、date、cp、cat等命令。
#include<stdio.h> #include<stdlib.h> #include<unistd.h>int main() {pid_t pid;pid = fork();if(pid == -1){perror("fork");exit(1);}if(pid > 0){printf("parent process\n");sleep(1);}else{execlp("ls", "ls", "-l", "-a", NULL);}return 0; }只有發(fā)生錯(cuò)誤的時(shí)候,函數(shù)才會(huì)有返回值-1,成功時(shí)不會(huì)有返回值。
execle
int execle(const char * path, const char * arg, ... ,char * const envp[]);
需要引入環(huán)境變量。
execv
int execv(const char * path,char * const argv[]);
char *argv[] = {"ls", "-l","-a",NULL}; execv("/bin/ls",argv);execvp
int execvp(const char * file, char * const argv[]);
execve
int execve(const char * path,char * const argv[], char *const envp[]);
將所有進(jìn)程保存在文件中
#include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<fcntl.h>int main() {int fd;fd = open("ps.out", O_WRONLY | O_CREAT | O_TRUNC, 0644);if(fd < 0){perror("open");exit(1);}dup2(fd, STDOUT_FILENO);execlp("ps", "ps", "ax", NULL);return 0; }一般規(guī)律
? exec函數(shù)一旦調(diào)用成功立即執(zhí)行新的程序,不返回。只有失敗才返回,錯(cuò)誤值-1。所以通常我們直接在exec函數(shù)調(diào)用后直接調(diào)用perror()和exit(),無(wú)需if判斷。
l(list) 命令行參數(shù)列表
p(path) 搜索file時(shí)使用path變量
v(vector) 使用命令行參數(shù)數(shù)組
e(environment) 使用環(huán)境變量數(shù)組,不適用進(jìn)程原有的環(huán)境變量, 設(shè)置新加載程序運(yùn)行的環(huán)境變量。
? 事實(shí)上,只有execve是真正的系統(tǒng)調(diào)用,其他五個(gè)函數(shù)最終都調(diào)用execve,所以execve在man手冊(cè)第二節(jié),其他函數(shù)在第三節(jié),關(guān)系如下圖。
回收子進(jìn)程
孤兒進(jìn)程
? 父進(jìn)程先于子進(jìn)程結(jié)束,則子進(jìn)程成為孤兒進(jìn)程,子進(jìn)程的父進(jìn)程成為init進(jìn)程,稱為init進(jìn)程領(lǐng)養(yǎng)孤兒進(jìn)程。
僵尸進(jìn)程
? 進(jìn)程終止,父進(jìn)程尚未回收,子進(jìn)程殘留資源(PCB)存放在內(nèi)核中,變成僵尸進(jìn)程。
? 注意:僵尸進(jìn)程是不能使用kill命令清除掉的。因?yàn)閗ill命令只是用來(lái)終止進(jìn)程的,而僵尸進(jìn)程已經(jīng)終止。
? 通過(guò)殺死進(jìn)程的父進(jìn)程,使init進(jìn)程領(lǐng)養(yǎng)該進(jìn)程,int進(jìn)而回收此進(jìn)程資源。
wait
? 一個(gè)進(jìn)程在終止時(shí)會(huì)關(guān)閉所有文件描述符,釋放在用戶空間分配的內(nèi)存,但它的PCB還保留著,內(nèi)核在其中保存了一些信息:如果是正常終止則保存退出狀態(tài),如果是異常終止則保存著導(dǎo)致該進(jìn)程終止的信號(hào)是哪個(gè)。這個(gè)進(jìn)程的父進(jìn)程可以調(diào)用wait或waitpid獲取這些信息,然后徹底清除掉這個(gè)進(jìn)程。我們知道一個(gè)進(jìn)程的退出狀態(tài)可以在shell中使用特殊變量$?查看,因?yàn)閟hell是它的父進(jìn)程,它終止時(shí)shell調(diào)用wait或者waitpid得到它的退出狀態(tài)同時(shí)徹底清除這個(gè)進(jìn)程。
? 父進(jìn)程調(diào)用wait函數(shù)可以回收子進(jìn)程終止信息。該函數(shù)有三個(gè)功能:
1. 阻塞等待子進(jìn)程退出 2. 回收子進(jìn)程殘留資源 3. 獲取子進(jìn)程退出狀態(tài)(原因)**pid_t wait(int * status);**成功:清除掉的子進(jìn)程ID;失敗:-1(沒(méi)有子進(jìn)程)
? 當(dāng)進(jìn)程終止時(shí),操作系統(tǒng)的隱式回收機(jī)制會(huì):1. 關(guān)閉所有文件描述符 2. 釋放用戶空間分配的內(nèi)存。內(nèi)核的PCB仍存在。其中保存該進(jìn)程的退出狀態(tài)。(正常終止–退出值;異常終止–終止信號(hào))
? 可使用wait函數(shù)傳出參數(shù)status來(lái)保存進(jìn)程的退出狀態(tài)。借助宏函數(shù)來(lái)進(jìn)一步判斷進(jìn)程終止的具體原因。宏函數(shù)可分為如下三組:
WIFEXITED(status) 為非0,進(jìn)程正常結(jié)束
WEXITSTATUS(status) 如上宏為真,使用此宏,獲取進(jìn)程退出狀態(tài)(exit的參數(shù))
WIFSIGNALED(status) 為非0, 進(jìn)程異常終止
 WTERMSIG(status) 如上宏為真,使用此宏,獲取使進(jìn)程終止的信號(hào)的編號(hào)
WIFSTOPPED(status) 為非0,進(jìn)程處于暫停狀態(tài)
 WSTOPSIG(status) 如上宏為真,使用此宏,獲取使進(jìn)程暫停的信號(hào)的編號(hào)
 WIFCONTINUED(status) 為真,進(jìn)程暫停后已經(jīng)繼續(xù)運(yùn)行
? 一次wait調(diào)用,回收一個(gè)子進(jìn)程。
waitpid
? 作用同wait,但可指定pid進(jìn)程清理,可以不阻塞。
? pid_t waitpid(pid_t pid,int *status, in options);成功:返回清理掉的子進(jìn)程ID;失敗-1
? 特殊參數(shù)和返回情況:
? 參數(shù)pid:
1. 大于0,回收指定ID的子進(jìn)程2. -1, 回收任意子進(jìn)程(相當(dāng)于wait)3. 0, 回收和當(dāng)前調(diào)用waitpid一個(gè)組的所有子進(jìn)程(一次回收一個(gè))4. 小于0, 回收指定進(jìn)程組內(nèi)的任意子進(jìn)程? 參數(shù)status:用戶獲取進(jìn)程退出狀態(tài)。
? 參數(shù)options:0:阻塞;WNOHANG:非阻塞。
? 返回0:參數(shù)3為WNOHANG,且子進(jìn)程正在運(yùn)行,不阻塞;使用時(shí)如果為了保證進(jìn)程退出回收,可以輪詢調(diào)用。其他情況與wait一樣。
? 一次wait和waitpid調(diào)用只能清理一個(gè)子進(jìn)程,清理多個(gè)子進(jìn)程應(yīng)使用循環(huán)。
#include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<sys/wait.h>int main(int argc, char* argv[]) {int n = 5,i;pid_t p, q, wpid;if(argc == 2){n = atoi(argv[1]);}for ( i = 0; i < n; i++){p = fork();if(p == 0){break;}else if(i == 3){q = p;}}if(n == i){sleep(n);printf("parent process,pid = %d\n",getpid()); #if 0while(wait(NULL)>0){}printf("parent end\n"); #else// while(waitpid(-1,,NULL,0)>0){//阻塞,同wait// }// printf("parent end\n");// waitpid(q, NULL, o);//阻塞,回收指定進(jìn)程do{wpid = waitpid(-1, NULL, WNOHANG);//非阻塞if(wpid > 0){n--;}//如果wpid==0,說(shuō)明子進(jìn)程正在運(yùn)行sleep(1);}while(n > 0);printf("parent end\n"); #endif}else{sleep(i);printf("child %d process,pid=%d\n",i+1,getpid());}return 0; }2.IPC方法
? Linux環(huán)境下,進(jìn)程地址空間相互獨(dú)立,每個(gè)進(jìn)程各自有不同的用戶地址空間。任何一個(gè)進(jìn)程的全局變量在另一個(gè)進(jìn)程中都看不到,所以進(jìn)程和進(jìn)程之間不能互相訪問(wèn),要交換數(shù)據(jù)必須通過(guò)內(nèi)核,在內(nèi)核中開辟一塊緩沖區(qū),進(jìn)程1把數(shù)據(jù)從用戶空間考到內(nèi)核緩沖區(qū),進(jìn)程2再?gòu)膬?nèi)核緩沖區(qū)把數(shù)據(jù)讀走,內(nèi)核提供的這種機(jī)制稱為進(jìn)程間通訊(IPC,InterProcess Communication)。
? 在進(jìn)程間完成數(shù)據(jù)傳遞需要借助操作系統(tǒng)提供的方法,如:文件、管道、信號(hào)、共享內(nèi)存、消息隊(duì)列、套接字、命名管道等。隨著計(jì)算機(jī)的蓬勃發(fā)展,一些方法由于自身設(shè)計(jì)缺陷被淘汰和棄用,如今的進(jìn)程間通訊方式有:
1. 管道(使用最簡(jiǎn)單) 2. 信號(hào)(開銷最小) 3. 共享映射區(qū)(無(wú)血緣關(guān)系) 4. 本地套接字(最穩(wěn)定)管道
管道的概念
? 管道是一種最基本的IPC機(jī)制,作用于有血緣關(guān)系的進(jìn)程之間,完成數(shù)據(jù)傳遞。調(diào)用pipe系統(tǒng)函數(shù)即可創(chuàng)建一個(gè)管道,如下特質(zhì):
1. 本質(zhì)是一個(gè)偽文件(實(shí)為內(nèi)核緩沖區(qū)) 2. 由兩個(gè)文件描述符引用,一個(gè)表示讀端,一個(gè)表示寫端。 3. 規(guī)定數(shù)據(jù)從管道的寫端流入管道,從讀端流出。占用存儲(chǔ)空間的文件類型(普通文件-;符號(hào)鏈接s;目錄d)。
偽文件類型(套接字s;塊設(shè)備b;字符設(shè)備c;管道p)。
管道的原理:管道實(shí)為內(nèi)核使用環(huán)形隊(duì)列機(jī)制,借助內(nèi)核緩沖區(qū)(4k)實(shí)現(xiàn)。
管道的局限性:
? 1) 數(shù)據(jù)自己讀不能自己寫。
? 2)數(shù)據(jù)一旦被讀走,便不再管道中存在,不可反復(fù)讀取。
? 3)由于管道采用半雙工通信方式。因此,數(shù)據(jù)只能在一個(gè)方向上流動(dòng)。
? 4)只能在有公共祖先的進(jìn)程間(有血緣關(guān)系的進(jìn)程)使用管道。
pipe函數(shù)
? 創(chuàng)建管道
? int pipe(int pipefd[2]); 成功:0;失敗:-1,設(shè)置errno
? 函數(shù)調(diào)用成功返回r/w兩個(gè)文件描述符。無(wú)需open,但需手動(dòng)close。規(guī)定:fd[0] ,r;fd[1],w;就像0對(duì)應(yīng)標(biāo)準(zhǔn)輸入,1對(duì)應(yīng)標(biāo)準(zhǔn)輸出一樣。向管道文件讀寫數(shù)據(jù)其實(shí)是在讀寫內(nèi)核緩沖區(qū)。
? 管道創(chuàng)建成功以后,創(chuàng)建該管道的進(jìn)程(父進(jìn)程)同時(shí)掌握著管道的讀端和寫端。如何實(shí)現(xiàn)父子進(jìn)程通信呢,通常步驟如下:
1. 父進(jìn)程調(diào)用pipe函數(shù)創(chuàng)建管道,得到兩個(gè)文件描述符fd[0]、fd[1]指向管道的讀端和寫端。 2. 父進(jìn)程調(diào)用fork創(chuàng)建子進(jìn)程,那么子進(jìn)程也有兩個(gè)文件描述符指向同一管道。 3. 父進(jìn)程關(guān)閉管道讀端,子進(jìn)程關(guān)閉管道寫端。父進(jìn)程可以向管道中寫入數(shù)據(jù),子進(jìn)程將管道中的數(shù)據(jù)讀出。由于管道是利用環(huán)形隊(duì)列實(shí)現(xiàn)的,數(shù)據(jù)從寫端流入管道,從讀端流出,這樣就實(shí)現(xiàn)了進(jìn)程間通信。 #include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<string.h>int main(int argc, char* argv[]) {int fd[2];int ret = pipe(fd);if(ret == -1){perror("pipe");exit(1);}pid_t pid = fork();if(-1 == pid){perror("fork");exit(1);}if( 0 == pid ){printf("child process read\n");close(fd[1]);char buf[1024];ret = read(fd[0], buf, sizeof(buf));if(ret == 0){printf("read over\n");}write(STDOUT_FILENO, buf, ret);}else{printf("parent process write\n");close(fd[0]);write(fd[1], "pipe trans\n",strlen("pipe trans\n"));}return 0; }管道的讀寫行為
1. 如果所有指向管道寫端的文件描述符都關(guān)閉了(管道寫端引用計(jì)數(shù)為0),而仍然有進(jìn)程從管道讀端讀數(shù)據(jù),那么管道中剩余的數(shù)據(jù)都被讀取后,再次read會(huì)返回0,就像讀到文件末尾一樣。 2. 如果有指向管道寫端的文件描述符沒(méi)有關(guān)閉(管道寫端引用計(jì)數(shù)不為0),且持有寫端描述符的進(jìn)程也沒(méi)有向管道中寫數(shù)據(jù),這時(shí)讀端從管道中讀取數(shù)據(jù)后,將會(huì)阻塞read,直到管道中有了數(shù)據(jù)再繼續(xù)讀取。 3. 如果所有指向管道讀端的文件描述符都關(guān)閉了(管道讀端引用計(jì)數(shù)為0),這時(shí)持有管道寫端文件描述符的進(jìn)程會(huì)接收到信號(hào)SIGPIPE,導(dǎo)致該進(jìn)程異常終止。 4. 如果有指向管道讀端的文件描述符沒(méi)有關(guān)閉(讀端引用計(jì)數(shù)不為0),此時(shí)如果管道已經(jīng)數(shù)據(jù)寫滿了,那么將會(huì)阻塞write,直到有空間可以寫入數(shù)據(jù)。管道緩沖區(qū)大小
? 可以使用ulimit -a命令來(lái)查看當(dāng)前系統(tǒng)中創(chuàng)建管道文件所對(duì)應(yīng)的內(nèi)核緩沖區(qū)大小,其大小通常為4k,每個(gè)扇區(qū)512b,使用8個(gè)扇區(qū)。
管道的優(yōu)勢(shì)
優(yōu)點(diǎn):簡(jiǎn)單
缺點(diǎn):1. 只能單向通信,雙向可建立兩個(gè)管道
? 2. 只能用于父子進(jìn)程,兄弟進(jìn)程通信。
FIFO
? FIFO通常叫做命名管道,以區(qū)分管道(pipe)。
? FIFO是linux基礎(chǔ)文件類型中的一種,但是FIFO在磁盤上沒(méi)有數(shù)據(jù)庫(kù),僅僅用來(lái)標(biāo)識(shí)內(nèi)核中的一條通道。各個(gè)進(jìn)程可以打開這個(gè)文件進(jìn)行讀寫操作,實(shí)際上是在讀寫內(nèi)核通道,這樣就實(shí)現(xiàn)了進(jìn)程間通信。
? int mkfifo(const char * pathname, mode_t mode); 成功:0;失敗:-1
? 創(chuàng)建FIFO后,可以使用open打開他,常用的io操作都可用于FIFO,如:close、read、write、unlink等。
? 可以在非血緣關(guān)系進(jìn)程間實(shí)現(xiàn)通訊,借助隊(duì)列實(shí)現(xiàn),不能反復(fù)讀取。
共享存儲(chǔ)映射(共享內(nèi)存)
文件進(jìn)程間通訊
? 使用文件也可以完成IPC,理論依據(jù)是,fork后,父子進(jìn)程共享文件描述符,也就是共享打開的文件。
存儲(chǔ)映射I/O
? 存儲(chǔ)映射I/O(memory-mapped I/O)使一個(gè)磁盤文件與存儲(chǔ)空間中的一個(gè)緩沖區(qū)相映射。于是當(dāng)從緩沖區(qū)中取數(shù)據(jù),就相當(dāng)于讀文件中的相應(yīng)字節(jié)。與此類似,將數(shù)據(jù)存入緩沖區(qū),則相應(yīng)的字節(jié)就自動(dòng)寫入文件。這樣,就可在不使用read和write函數(shù)的情況下,使用地址(指針)完成I/O操作。
? 使用這種方法,首先應(yīng)通知內(nèi)核,將一個(gè)指定文件映射到存儲(chǔ)區(qū)域中。這個(gè)映射工作可以通過(guò)mmap函數(shù)來(lái)實(shí)現(xiàn)。
mmap父子進(jìn)程通訊
mmap函數(shù):
void * mmap(void *adrr, size_t length, int prot, int flags, int fd, off_t offset);
返回:成功:返回創(chuàng)建的映射區(qū)首地址; 失敗:MAP_FAILED宏
參數(shù):
MAP_SHARED: 會(huì)將映射區(qū)所做的操作反應(yīng)到物理設(shè)備(磁盤)上。
MAP_PRIVATE:映射區(qū)所做的修改不會(huì)反應(yīng)到物理設(shè)備。
注意:
父子進(jìn)程通信
? MAP_PRIVATE:父子進(jìn)程獨(dú)享映射,進(jìn)程內(nèi)的映射區(qū)不受其他進(jìn)程影響
 ? MAP_SHARED: 父子進(jìn)程共享映射
當(dāng)以上mmap創(chuàng)建方式使用MAP_PRIVATE后,第二次打印出的信息為parent, *p = 0, var = 100
父子進(jìn)程共享:
匿名映射
? 通過(guò)使用發(fā)現(xiàn),使用映射區(qū)來(lái)完成文件讀寫操作十分方便,父子進(jìn)程間通信也比較容易。但缺陷是,每次創(chuàng)建映射區(qū)一定要依賴一個(gè)文件才能實(shí)現(xiàn)。通過(guò)為了創(chuàng)建映射區(qū)要open一個(gè)temp文件,創(chuàng)建好了之后再unlink、close掉,比較麻煩。可以直接使用匿名映射來(lái)代替。其實(shí)linux系統(tǒng)為我們提供了創(chuàng)建匿名映射的方法,無(wú)需依賴文件即可創(chuàng)建映射區(qū)。同樣需要借助標(biāo)志位參數(shù)flags來(lái)指定。
使用MAP_ANONYMOUS(或者M(jìn)AN_ANON),如:
? int *p=mmap(NULL, 4, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1 ,0);
4隨意舉例,該位置表大小,按需求填寫即可。
? 需要注意的是,MAP_ANONYMOUS 和 MAP_ANON 這兩個(gè)宏是linux操作系統(tǒng)特有的宏,在類unix系統(tǒng)中無(wú)該宏定義,可使用如下兩步來(lái)完成匿名映射區(qū)的建立。/dev/zero文件可大可小,沒(méi)有限制。
fd = open('/dev/zero', O_RDWR); p = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED,fd,0);?
mmap無(wú)血緣關(guān)系進(jìn)程間通訊
? 實(shí)質(zhì)上mmap是內(nèi)核借助文件幫我們創(chuàng)建了一個(gè)映射區(qū),多個(gè)進(jìn)程間利用該映射區(qū)完成數(shù)據(jù)傳遞。由于內(nèi)核空間多進(jìn)程共享,因此無(wú)血緣關(guān)系的進(jìn)程間也可以使用mmap來(lái)完成通信。只要設(shè)置相應(yīng)的標(biāo)志位參數(shù)即可MAP_SHARED,映射的同一個(gè)文件即可。
讀進(jìn)程:
#include<stdio.h> #include<fcntl.h> #include<string.h> #include<unistd.h> #include<stdlib.h> #include<sys/mman.h> #include<sys/stat.h>struct STU{int id;char name[20];char sex; };int main(int argc, char *argv[]) {int fd;struct STU student;struct STU *mm;if(argc < 2){printf("./a.out file_shared\n");exit(1);}fd = open(argv[1], O_RDONLY);if(-1 == fd){printf("open");exit(1);}mm = mmap(NULL, sizeof(student), PROT_READ, MAP_SHARED , fd, 0);if(mm == MAP_FAILED){perror("mmap error");exit(1);}close(fd);while(1){printf("id=%d\tname=%s\t%c\n",mm->id,mm->name,mm->sex);sleep(2);}munmap(mm, sizeof(student));return 0; }寫進(jìn)程:
#include<stdio.h> #include<fcntl.h> #include<string.h> #include<unistd.h> #include<stdlib.h> #include<sys/mman.h> #include<sys/stat.h> #include<sys/types.h>struct STU{int id;char name[20];char sex; };int main(int argc, char *argv[]) {int fd;struct STU student = {10,"xiaoming",'m'};struct STU *mm;if(argc < 2){printf("./a.out file_shared\n");exit(1);}fd = open(argv[1], O_RDWR | O_CREAT, 0664);if(-1 == fd){printf("open");exit(1);}ftruncate(fd, sizeof(student));mm = mmap(NULL, sizeof(student), PROT_READ, MAP_SHARED , fd, 0);if(mm == MAP_FAILED){perror("mmap error");exit(1);}close(fd);while(1){memcpy(mm, &student, sizeof(student));student.id++;sleep(1);}munmap(mm, sizeof(student));return 0; }練習(xí):
博主鏈接:https://blog.csdn.net/bureau123/category_10691972.html
2. 簡(jiǎn)易shell #include <unistd.h> #include <string.h> #include <sys/wait.h> #include <sys/types.h> #include <sys/stat.h> #include <dirent.h> #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <malloc.h>#define MAX_CMD_LENGTH 255 #define MAX_PATH_LENGTH 255 #define MAX_BUF_SIZE 4096 #define MAX_ARG_NUM 50 #define MAX_VAR_NUM 50 #define MAX_CMD_NUM 10 #define MAX_VAR_LENGTH 500#define FORK_ERROR 2 #define EXEC_ERROR 3struct cmd{struct cmd * next;int begin,end; // pos in cmdStrint argc;char lredir,rredir; 0:no redirect 1 <,> ; 2 >>char toFile[MAX_PATH_LENGTH],fromFile[MAX_PATH_LENGTH]; // redirect file pathchar *args[MAX_ARG_NUM];char bgExec; //failExec };struct cmd cmdinfo[MAX_CMD_NUM]; char cmdStr[MAX_CMD_LENGTH]; int cmdNum,varNum; char envVar[MAX_VAR_NUM][MAX_PATH_LENGTH];void Error(int ); void debug(struct cmd*); void init(struct cmd*); void setIO(struct cmd*,int ,int ); int getInput(); int parseCmds(int); int handleVar(struct cmd *,int); int getItem(char *,char *,int); int parseArgs(); int execInner(struct cmd*); int execOuter(struct cmd*);int main(){while (1){cmdNum = varNum = 0;printf("# ");fflush(stdin);int n = getInput();if(n<=0)continue; parseCmds(n);if(parseArgs()<0)continue;for(int i=0;i<cmdNum;++i){struct cmd *pcmd=cmdinfo+i, * tmp;//debug(pcmd);//pcmd = reverse(pcmd);int status = execInner(pcmd);if(status==1){/*notice!!! Use child proc to execute outer cmd, bacause exec funcs won't return when successfully execed. */pid_t pid = fork();if(pid==0)execOuter(pcmd);else if(pid<0)Error(FORK_ERROR);if(!pcmd->bgExec)wait(NULL); //background exec/* free malloced piep-cmd-node,and the first one is static , no need to free; */ pcmd=pcmd->next; while(pcmd){tmp = pcmd->next;free(pcmd);pcmd=tmp;}}}}return 0; }/* funcs implementation */ void init(struct cmd *pcmd){pcmd->bgExec=0;pcmd->argc=0;pcmd->lredir=pcmd->rredir=0;pcmd->next = NULL;pcmd->begin=pcmd->end=-1;/* // notice!!! Avoid using resudent args */for(int i=0;i<MAX_ARG_NUM;++i)pcmd->args[i]=NULL; }void Error(int n){switch(n){case FORK_ERROR:printf("fork error\n");break;case EXEC_ERROR:printf("exec error\n");break;default:printf("Error, exit ...\n");}exit(1); }int getInput(){/* multi line input */int pCmdStr=0,cur;char newline = 1;while(newline){cur = MAX_CMD_LENGTH-pCmdStr;if(cur<=0){printf("[Error]: You cmdStr is too long to exec.\n");return -1;// return -1 if cmdStr size is bigger than LENGTH}fgets(cmdStr+pCmdStr,cur,stdin);newline = 0;while(1){if(cmdStr[pCmdStr]=='\\'&&cmdStr[pCmdStr+1]=='\n'){newline=1;cmdStr[pCmdStr++]='\0';break;}else if(cmdStr[pCmdStr]=='\n'){break;}++pCmdStr;}}return pCmdStr; }int parseCmds(int n){/* clean the cmdStr and get pos of each cmd in the cmdStr (OoO) */char beginCmd=0;struct cmd * head; // use head cmd to mark background.for( int i=0;i<=n;++i){switch(cmdStr[i]){case '&':{if(cmdStr[i+1]=='\n'||cmdStr[i+1]==';'){cmdStr[i]=' ';head->bgExec=1;}}case '\t':cmdStr[i]=' ';break;case ';':{//including ';' a new cmdStrbeginCmd = 0;cmdStr[i]='\0'; cmdinfo[cmdNum++].end=i;break;}case '\n':{cmdStr[i]='\0';cmdinfo[cmdNum++].end =i;return 0;}case ' ':break;default:if(!beginCmd){beginCmd=1;head = cmdinfo+cmdNum;cmdinfo[cmdNum].begin = i;}}} }int getItem(char *dst,char*src, int p){ /* get redirect file path from the cmdStr */int ct=0;while(src[++p]==' ');if(src[p]=='\n')return -1; //no file char c;while(c=dst[ct]=src[p]){if(c==' '||c=='|'||c=='<'||c=='>'||c=='\n')break;++ct,++p;}dst[ct]='\0';return p-1; }int handleVar(struct cmd *pcmd,int n){char * arg = pcmd->args[n];int p_arg=0,p_var=0;while(arg[p_arg]){if((arg[p_arg]=='$')&&(arg[p_arg-1]!='\\')){if(arg[p_arg+1]=='{')p_arg+=2;else p_arg+=1;char *tmp=&envVar[varNum][p_var];int ct=0;while(tmp[ct]=arg[p_arg]){if(tmp[ct]=='}'){++p_arg;break;}if(tmp[ct]==' '||tmp[ct]=='\n'||tmp[ct]=='\0')break;++ct,++p_arg;}tmp[ct]='\0';tmp = getenv(tmp);for(int i=0;envVar[varNum][p_var++]=tmp[i++];);p_var-=1; //necessary}else envVar[varNum][p_var++]=arg[p_arg++];}envVar[varNum][p_var]='\0';pcmd->args[n] = envVar[varNum++];return 0; }int parseArgs(){/* get args of each cmd and create cmd-node seperated by pipe */char beginItem=0,beginQuote=0,beginDoubleQuote=0,hasVar=0,c;int begin,end;struct cmd* pcmd;for(int p=0;p<cmdNum;++p){if(beginQuote||beginItem||beginDoubleQuote){return -1; // wrong cmdStr}pcmd=&cmdinfo[p];begin = pcmd->begin,end = pcmd->end;init(pcmd);// initalize for(int i=begin;i<end;++i){c = cmdStr[i];if((c=='\"')&&(cmdStr[i-1]!='\\'&&(!beginQuote))){if(beginDoubleQuote){cmdStr[i]=beginDoubleQuote=beginItem=0;if(hasVar){hasVar=0;handleVar(pcmd,pcmd->argc-1); //note that is argc-1, not argc}}else{beginDoubleQuote=1;pcmd->args[pcmd->argc++]=cmdStr+i+1;}continue;}else if(beginDoubleQuote){if((c=='$') &&(cmdStr[i-1]!='\\')&&(!hasVar))hasVar=1;continue;}if((c=='\'')&&(cmdStr[i-1]!='\\')){if(beginQuote){cmdStr[i]=beginQuote=beginItem=0;}else{beginQuote=1;pcmd->args[pcmd->argc++]=cmdStr+i+1;}continue;}else if(beginQuote) continue;if(c=='<'||c=='>'||c=='|'){if(beginItem)beginItem=0;cmdStr[i]='\0';}if(c=='<'){if(cmdStr[i+1]=='<'){pcmd->lredir+=2; //<<cmdStr[i+1]=' ';}else{pcmd->lredir+=1; //<}int tmp = getItem(pcmd->fromFile,cmdStr,i);if(tmp>0)i = tmp;}else if(c=='>'){if(cmdStr[i+1]=='>'){pcmd->rredir+=2; //>>cmdStr[i+1]=' ';}else{pcmd->rredir+=1; //>}int tmp = getItem(pcmd->toFile,cmdStr,i);if(tmp>0)i = tmp;}else if (c=='|'){/*when encountering pipe | , create new cmd node chained after the fommer one */pcmd->end = i;pcmd->next = (struct cmd*)malloc(sizeof(struct cmd));pcmd = pcmd->next;init(pcmd);}else if(c==' '||c=='\0'){if(beginItem){beginItem=0;cmdStr[i]='\0';}}else{if(pcmd->begin==-1)pcmd->begin=i;if(!beginItem){beginItem=1;if((c=='$') &&(cmdStr[i-1]!='\\')&&(!hasVar))hasVar=1;pcmd->args[pcmd->argc++]=cmdStr+i;}}if(hasVar){hasVar=0;handleVar(pcmd,pcmd->argc-1); //note that is argc-1, not argc}}pcmd->end=end;//printf("%dfrom:%s %dto:%s\n",pcmd->lredir,pcmd->fromFile,pcmd->rredir,pcmd->toFile);} }int execInner(struct cmd* pcmd){ /*if inner cmd, {exec, return 0} else return 1 */if (!pcmd->args[0])return 0;if (strcmp(pcmd->args[0], "cd") == 0) {struct stat st;if (pcmd->args[1]){stat(pcmd->args[1],&st);if (S_ISDIR(st.st_mode))chdir(pcmd->args[1]);else{printf("[Error]: cd '%s': No such directory\n",pcmd->args[1]);return -1;}}return 0;}if (strcmp(pcmd->args[0], "pwd") == 0) {printf("%s\n",getcwd(pcmd->args[1] , MAX_PATH_LENGTH));return 0;}if (strcmp(pcmd->args[0], "unset") == 0) {for(int i=1;i<pcmd->argc;++i)unsetenv(pcmd->args[i]);return 0;}if (strcmp(pcmd->args[0], "export") == 0) {for(int i=1;i<pcmd->argc;++i){ //putenv(pcmd->args[i]);char *val,*p;for(p = pcmd->args[i];*p!='=';++p);*p='\0';val = p+1;setenv(pcmd->args[i],val,1);}return 0;}if (strcmp(pcmd->args[0], "exit") == 0)exit(0);return 1; } void setIO(struct cmd *pcmd,int rfd,int wfd){/* settle file redirect */if(pcmd->rredir>0){ // >, >>int flag ;if(pcmd->rredir==1)flag=O_WRONLY|O_TRUNC|O_CREAT; // > note: trunc is necessary!!!else flag=O_WRONLY|O_APPEND|O_CREAT; //>>int wport = open(pcmd->toFile,flag);dup2(wport,STDOUT_FILENO);close(wport);}if(pcmd->lredir>0){ //<, <<int rport = open(pcmd->fromFile,O_RDONLY);dup2(rport,STDIN_FILENO);close(rport);}/* pipe */if(rfd!=STDIN_FILENO){dup2(rfd,STDIN_FILENO);close(rfd);}if(wfd!=STDOUT_FILENO){dup2(wfd,STDOUT_FILENO);close(wfd);} } int execOuter(struct cmd * pcmd){if(!pcmd->next){setIO(pcmd,STDIN_FILENO,STDOUT_FILENO);execvp(pcmd->args[0],pcmd->args);}int fd[2];pipe(fd);pid_t pid = fork();if(pid<0){Error(FORK_ERROR);}else if (pid==0){close(fd[0]);setIO(pcmd,STDIN_FILENO,fd[1]);execvp(pcmd->args[0],pcmd->args);Error(EXEC_ERROR);}else{wait(NULL);pcmd = pcmd->next; //noticeclose(fd[1]);setIO(pcmd,fd[0],STDOUT_FILENO); execOuter(pcmd);} }博主鏈接:https://www.jianshu.com/p/d6d9b5b976e8
3. 本地聊天室簡(jiǎn)單 //server #include <unistd.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <sys/types.h> #include <sys/stat.h> #include <string.h>#define SERVER_FIFO "/home/wx/test/sysday3/codes/SERVER_FIFO"struct client {char clientName[20];//客戶端名字int fifoDis;//私有管道的描述符 };typedef struct client CL; //用來(lái)記錄客戶機(jī)的數(shù)量 int clientlen=0; //利用數(shù)組將存儲(chǔ)客戶隊(duì)列(不方便,而且會(huì)浪費(fèi)),可以改造為鏈表(最好)。 CL clientDueue[100];struct messagePacket {int messageNo;//消息編號(hào)char senderName[20];//消息發(fā)送方char receiverName[20];//消息接收方char data[1024];//數(shù)據(jù)采用不定長(zhǎng)消息 };typedef struct messagePacket MSP;//公共管道 int serFifo; //服務(wù)器啟動(dòng)標(biāo)志 int startFlags=0;//初始化,負(fù)責(zé)初始化服務(wù)器。 void initServer(); //負(fù)責(zé)接收客戶端發(fā)送的包 void receiverPacket(); //負(fù)責(zé)將客戶端發(fā)送的包解析 void parsingPacket(MSP *msp); //負(fù)責(zé)客戶端登陸,將客戶端插入客戶隊(duì)列中,并創(chuàng)建私有管道 void clientLogin(char* loginName); //負(fù)責(zé)將消息發(fā)送到對(duì)應(yīng)的接受方 void messageSend(MSP *pMsp); //負(fù)責(zé)客戶端的退出,將客戶端從客戶隊(duì)列中刪除,并刪除創(chuàng)建的管道 void clientQuit(char* quitName); //負(fù)責(zé)關(guān)閉服務(wù)器,關(guān)閉打開的管道和刪除客戶機(jī)列表 void closeServer(); //負(fù)責(zé)處理輸入的數(shù)據(jù) void messageHanle(char* pMes);#define BUFSIZE 1068void initServer() {//將STDIN_FILENO修改為非阻塞int serFlags=fcntl(STDIN_FILENO,F_GETFL);serFlags|=O_NONBLOCK;fcntl(STDIN_FILENO,F_SETFL,serFlags);int results = mkfifo(SERVER_FIFO, 0666);if(results<0){perror("SERVER mkfifo:");exit(1);}//以非阻塞只讀的方式打開管道serFifo=open(SERVER_FIFO,O_RDONLY|O_NONBLOCK);if(serFifo<0){perror("SERVER OPEN:");exit(1);}printf("服務(wù)器已啟動(dòng),正在監(jiān)聽(tīng)...\n");startFlags=1-startFlags; }void receiverPacket() {char buf[BUFSIZE];MSP *msp;int len=read(serFifo,buf,sizeof(buf));if(len>0){msp=(MSP*)buf;parsingPacket(msp);} }void parsingPacket(MSP *msp) {//根據(jù)相應(yīng)的功能號(hào),調(diào)用相應(yīng)的函數(shù)。switch(msp->messageNo){case 0:clientLogin(msp->senderName);break;case 1:messageSend(msp);break;case 2:clientQuit(msp->senderName);break;} }void clientLogin(char* loginName) {//不能直接賦值,會(huì)造成淺拷貝strcpy(clientDueue[clientlen].clientName,loginName);char path[23]="./";strcat(path,loginName);//確保創(chuàng)建的文件的權(quán)限為分配權(quán)限umask(0);//創(chuàng)建管道m(xù)kfifo(path,0777);//將管道的文件描述符存入數(shù)組中clientDueue[clientlen].fifoDis=open(path,O_WRONLY);char buf[]="您和服務(wù)器的連接已經(jīng)成功建立,可以開始通訊了\n";write(clientDueue[clientlen].fifoDis,buf,sizeof(buf));//這里應(yīng)該將管道創(chuàng)建為臨時(shí)的,如果是使用數(shù)據(jù)庫(kù),可以創(chuàng)建為永久的unlink(path);//沒(méi)有對(duì)cientlen進(jìn)行限制++clientlen; }void clientQuit(char* quitName) {//最好是利用鏈表管理登錄的客戶機(jī)int i=0;for(i=0;i<clientlen;i++){if(strcmp(quitName,clientDueue[i].clientName)==0){//關(guān)閉對(duì)應(yīng)的私有通過(guò)close(clientDueue[i].fifoDis);clientDueue[i].fifoDis=-1;clientDueue[i].clientName[0]='\0';break;}}printf("%s已退出\n",quitName); }void messageSend(MSP *pMes) {int i=0;char* buf=(void*)pMes;if(strlen(pMes->receiverName)!=0){//單發(fā)for(i=0;i<clientlen;++i){if(strcmp(pMes->receiverName,clientDueue[i].clientName)==0){write(clientDueue[i].fifoDis,buf,BUFSIZE);break;}}}else{//群發(fā)for(i=0;i<clientlen;++i){write(clientDueue[i].fifoDis,buf,BUFSIZE);}} }void messageHanle(char* pMes) {if(strcmp(pMes,"quit-->()")==0){closeServer();}//可以繼續(xù)增加一些命令(顯示有幾個(gè)客戶端,客戶端的管道描述符等) } void closeServer() {char buf[]="服務(wù)器維護(hù)中,請(qǐng)稍后登錄。";int i=0;for(i=0;i<clientlen;++i){if(clientDueue[i].fifoDis!=-1){write(clientDueue[i].fifoDis,buf,strlen(buf));close(clientDueue[i].fifoDis);}}close(serFifo);startFlags=1-startFlags;printf("以關(guān)閉所有管道,服務(wù)器安全退出"); } int main() {initServer();char mes[1024];while(startFlags){receiverPacket();if(scanf("%s",mes)!=EOF){messageHanle(mes);}}return 0; } //Client #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h> #define SERVER_FIFO "/home/wx/test/sysday3/codes/SERVER_FIFO"int linkFlags=0;//連接標(biāo)志 int serFifo;//公共管道文件描述符 int cliFifo;//客戶端私有端道文件描述符 char clientName[20];//客戶端名稱struct messagePacket {int messageNo;//消息編號(hào)char senderName[20];//消息發(fā)送方char receiverName[20];//消息接收方char data[1024];//數(shù)據(jù)采用定長(zhǎng)消息 };typedef struct messagePacket MSP;//初始化客戶大端 void initClient(); //登陸服務(wù)器 void loginServer(); //處理用戶輸入的數(shù)據(jù) void messageHanle(char* pMes); //向服務(wù)器發(fā)送消息 void sendSerMes(int mesNO); //向其他用戶發(fā)送消息 void sendMessage(char* receiverName,char* data); //接收消息 void receiverMes(); //關(guān)閉客戶端 void closeClient();//localClient.c #define BUFSIZE 1068void initClient() {loginServer();//將連接標(biāo)志置為1.linkFlags=1-linkFlags;//將STDIN文件屬性修改為非阻塞int flags=fcntl(STDIN_FILENO,F_GETFL);flags |= O_NONBLOCK;fcntl(STDIN_FILENO,F_SETFL,flags); }void loginServer() {printf("請(qǐng)輸入客戶端名稱(不超過(guò)20個(gè)字符):\n");//write(STDIN_FILENO,clientName,20);scanf("%s",clientName);serFifo=open(SERVER_FIFO,O_WRONLY|O_NONBLOCK);if(serFifo<0){perror("open server fifo");exit(1);}sendSerMes(0);char path[23]="./";strcat(path,clientName);//測(cè)試管道是否創(chuàng)建成功while(access(path,F_OK)!=0);cliFifo=open(path,O_RDONLY|O_NONBLOCK);if(cliFifo<0){perror("open client fifo");}printf("私有管道創(chuàng)建成功\n"); }void sendSerMes(int mesNO) {MSP msp;char *buf;msp.messageNo=mesNO;strcpy(msp.senderName,clientName);buf=(void*)&msp;write(serFifo,buf,sizeof(msp)); }void messageHanle(char* pMes) {//將“quit-->()”設(shè)置為退出消息if(strcmp(pMes,"quit-->()")==0){sendSerMes(2);closeClient();return;}//發(fā)送數(shù)據(jù)格式為:接受者姓名:消息內(nèi)容//如果數(shù)據(jù)不符合規(guī)范,則將消息轉(zhuǎn)為群發(fā)。int i=0;int j=0;char receiverName[20];char data[1024];while(pMes[i]!='\0'&&pMes[i]!=':'){receiverName[i]=pMes[i];++i;}receiverName[i]='\0';if(pMes[i]==':'){//將:跳過(guò)++i;}else{i=0;receiverName[0]='\0';}while(pMes[i]!='\0'){data[j++]=pMes[i++];}data[j]='\0';sendMessage(receiverName,data); }void sendMessage(char* receiverName,char* data) {MSP msp;char *buf;msp.messageNo=1;strcpy(msp.senderName,clientName);strcpy(msp.receiverName,receiverName);strcpy(msp.data,data);buf=(void*)&msp;write(serFifo,buf,sizeof(msp)); }void receiverMes() {char buf[BUFSIZE];int len=read(cliFifo,buf,sizeof(MSP));MSP *pMes=NULL;pMes=(void*)buf; if(len>0&&pMes->messageNo==1){printf("%s:%s\n",pMes->senderName,pMes->data);}else if(len>0){printf("系統(tǒng)提示:%s",buf);} }void closeClient() {//將連接標(biāo)志置為0linkFlags=1-linkFlags;//關(guān)閉私有管道close(cliFifo);//關(guān)閉公共管道close(serFifo);printf("以關(guān)閉所以管道,客戶端安全退出\n"); }int main() {initClient();char mesBuf[1024];while(linkFlags){//scanf()默認(rèn)遇空格終止scanf("%49[^\n]",mesBuf)!=EOF//int len=write(STDIN_FILENO,mesBuf,BUFSIZE);if(scanf("%s",mesBuf)!=EOF){messageHanle(mesBuf);} receiverMes();}return 0; }原文鏈接:https://blog.csdn.net/qq_39038983/article/details/88418412
3.信號(hào)
? 信號(hào)是信息的載體,linux環(huán)境下,經(jīng)典的通信方式,依然是主要的通信手段。
? 機(jī)制:A給B發(fā)送信號(hào),B收到信號(hào)之前執(zhí)行自己的代碼,收到信號(hào)后,不管執(zhí)行到程序的什么位置,都要暫停運(yùn)行,去處理信號(hào),處理完畢再繼續(xù)執(zhí)行。與硬件中斷類似,異步模式。但是信號(hào)是軟件層次上實(shí)現(xiàn)的中斷,早期被稱為軟中斷。
? 特質(zhì):由于信號(hào)是通過(guò)軟件的方法實(shí)現(xiàn),所以導(dǎo)致信號(hào)有很強(qiáng)的延時(shí)性。但對(duì)于用戶,不易察覺(jué)。
? 每個(gè)進(jìn)程收到的所有信號(hào),都是由內(nèi)核負(fù)責(zé)發(fā)送的,內(nèi)核處理。
信號(hào)的事件和狀態(tài)
產(chǎn)生信號(hào):
1. 按鍵產(chǎn)生:如ctrl + c 2. 系統(tǒng)調(diào)用: 如kill 3. 軟件條件:定時(shí)器 4. 硬件異常:如非法訪問(wèn)內(nèi)存(段錯(cuò)誤)、除0 5. 命令產(chǎn)品:kill命令**遞達(dá):**遞送并到達(dá)進(jìn)程
未決:產(chǎn)生和遞達(dá)之間的狀態(tài)。主要由于阻塞(屏蔽)導(dǎo)致該狀態(tài)
信號(hào)的處理方式:
1. 執(zhí)行默認(rèn)動(dòng)作 2. 忽略(丟棄) 3. 捕捉(調(diào)用戶處理函數(shù))? linux內(nèi)核的進(jìn)程控制塊PCB是一個(gè)結(jié)構(gòu)體,task_struct,除了包含進(jìn)程id,狀態(tài),工作目錄,用戶id,組id,文件描述符表,還包含了信號(hào)相關(guān)的信息,主要指阻塞信號(hào)集和未決信號(hào)集。
? **阻塞信號(hào)集(信號(hào)屏蔽字):**將某些信號(hào)加入集合,對(duì)他們?cè)O(shè)置屏蔽,當(dāng)屏蔽x信號(hào)后,再收到該信號(hào),該信號(hào)的處理將推后(解除屏蔽后)。
? 未決信號(hào)集:
1. 信號(hào)產(chǎn)生,未決信號(hào)集中描述該信號(hào)的位立刻翻轉(zhuǎn)為1,表示信號(hào)處于未決狀態(tài)。當(dāng)信號(hào)被處理對(duì)應(yīng)位翻轉(zhuǎn)回為0,這一過(guò)程往往非常短暫。 2. 信號(hào)產(chǎn)生后由于某些原因(主要是阻塞)不能抵達(dá)。這類信號(hào)的集合稱之為未決信號(hào)集。在屏蔽解除前,信號(hào)一直處于未決狀態(tài)。信號(hào)的編號(hào):
? 可以使用kill -l命令查看當(dāng)前系統(tǒng)可使用的信號(hào)有哪些。
信號(hào)的四要素:
1. 編號(hào) 2. 名稱 3.事件 4.默認(rèn)處理動(dòng)作可通過(guò)命令man 7 signal查看幫助文檔獲取。
? 默認(rèn)處理動(dòng)作:
? Term:終止進(jìn)程
 ? core:終止進(jìn)程,生成core文件(檢查進(jìn)程死亡原因,用戶gdb調(diào)試)
 ? stop:停止(暫停)進(jìn)程
 ? cont:繼續(xù)運(yùn)行進(jìn)程
 ? ign:忽略信號(hào)(默認(rèn)即時(shí)對(duì)該種信號(hào)忽略操作)
? 9-SIGKILL 和 19-SIGSTOP信號(hào),不允許忽略和捕捉,只能執(zhí)行默認(rèn)動(dòng)作,甚至不能設(shè)置為阻塞。
信號(hào)的產(chǎn)生
終端按鍵產(chǎn)生的信號(hào)
ctrl+c → 2-SIGINT(終止/終端)
ctrl+z → 20-SIGTSTP(暫停/停止)停止終端交互進(jìn)程的運(yùn)行
ctrl+ \ → 3-SIGQUIT(退出)
硬件異常產(chǎn)生信號(hào)
除0操作 → 8-SIGFPE(浮點(diǎn)數(shù)例外)
非法訪問(wèn)內(nèi)存 → 11-SIGSEGV(段錯(cuò)誤)
總線錯(cuò)誤 → 7-SIGBUS
kill產(chǎn)生
kill命令產(chǎn)生信號(hào):kill SIGKILL pid
kill函數(shù):給指定進(jìn)程發(fā)送指定信號(hào)(不一定殺死)
int kill(pid_t pid, int sig);成功:0,失敗-1(ID非法,信號(hào)非法,普通用戶殺init進(jìn)程等權(quán)級(jí)問(wèn)題。設(shè)置errno。
sig:不推薦直接使用數(shù)字,應(yīng)使用宏名,因?yàn)椴煌僮飨到y(tǒng)信號(hào)編號(hào)可能不同,但名稱一致。
pid > 0:發(fā)送信號(hào)給指定的進(jìn)程。
pid = 0:發(fā)送信號(hào)給 與調(diào)用kill函數(shù)進(jìn)程 屬于同一進(jìn)程組的所有進(jìn)程。
pid < 0:取|pid|發(fā)給對(duì)應(yīng)進(jìn)程組。
pid = -1:發(fā)送給進(jìn)程有權(quán)限發(fā)送的系統(tǒng)中所有進(jìn)程。
? 進(jìn)程組:每個(gè)進(jìn)程都屬于一個(gè)進(jìn)程組,進(jìn)程組是一個(gè)或多個(gè)進(jìn)程集合,他們相互關(guān)聯(lián),共同完成一個(gè)實(shí)體任務(wù),每個(gè)進(jìn)程組都有一個(gè)進(jìn)程組長(zhǎng),默認(rèn)進(jìn)程組ID與進(jìn)程組長(zhǎng)ID相同。
? 權(quán)限保護(hù):super用戶(root)可以發(fā)送信號(hào)給任意用戶,普通用戶是不能向系統(tǒng)用戶發(fā)送信號(hào)的。kill -9(root用戶的pid)是不可行的。同樣普通用戶也不能向其他普通用戶發(fā)送信號(hào),終止其進(jìn)程。只能向自己創(chuàng)建的進(jìn)程發(fā)送信號(hào)。普通用戶基本規(guī)則:發(fā)送者實(shí)際或有效用戶ID == 接受者實(shí)際或有效用戶ID
raise和abort
raise:給當(dāng)前進(jìn)程發(fā)送指定信號(hào)(自己給自己發(fā)) raise(signo)== kill(getpid(),signo);
? int raise(int sig);成功:0,失敗:非0值
abort:給自己發(fā)送異常終止信號(hào) 6-SIGABRT信號(hào),終止產(chǎn)生的core文件
? void abort(void);函數(shù)無(wú)返回
軟件條件產(chǎn)生
alarm函數(shù):
? 設(shè)置定時(shí)器(鬧鐘)。在指定seconds后,內(nèi)核會(huì)給當(dāng)前進(jìn)程發(fā)送 14-SIGALRM信號(hào)。進(jìn)程收到該信號(hào),默認(rèn)動(dòng)作終止。
? 每個(gè)進(jìn)程都有且只有為一個(gè)定時(shí)器。
? unsigned int alarm(unsigned int seconds);返回0或剩余的秒數(shù),無(wú)失敗。
? 常用:取消定時(shí)器alarm(0),返回舊鬧鐘余下秒數(shù)。
? 定時(shí),與進(jìn)程狀態(tài)無(wú)關(guān)(自然定時(shí)法)就緒、運(yùn)行、掛起(阻塞、暫停)、終止、僵尸,無(wú)論進(jìn)程處于何種狀態(tài),alarm都計(jì)時(shí)。
? 可以使用time a.out查看程序運(yùn)行時(shí)間,實(shí)際時(shí)間,用戶時(shí)間,內(nèi)核時(shí)間。實(shí)際時(shí)間=用戶時(shí)間+內(nèi)核時(shí)間+等待時(shí)間。
setitimer函數(shù):
? 設(shè)置定時(shí)器(鬧鐘)。可以代替alarm函數(shù)。精度微秒us,可以實(shí)現(xiàn)周期定時(shí)。
? int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);成功:0,失敗:-1,設(shè)置errno
? 參數(shù):which:指定定時(shí)方式
? 1、自然定時(shí):ITIMER_REAL → 14-SIGALRM 計(jì)算自然時(shí)間
? 2、虛擬空間計(jì)時(shí)(用戶空間):ITIMER_VIRTUAL → 26-SIGVTALRM 只計(jì)算進(jìn)程占用cpu的時(shí)間
? 3、運(yùn)行時(shí)計(jì)時(shí)(用戶+內(nèi)核):ITIMER_PROF → 27-SIGPROF 計(jì)算占用cpu及執(zhí)行系統(tǒng)調(diào)用的時(shí)間
struct itimerval {struct timeval it_interval;//下一次定時(shí)的值struct timeval it_value;//當(dāng)前定時(shí)的值 } struct timeval{time_t tv_sec;//秒suseconds_t tv_usec;//微妙 } #include<stdio.h> #include<stdlib.h> #include<sys/time.h>unsigned int myalarm(unsigned int sec) {struct itimerval it, oldit;int ret;it.it_value.tv_sec = sec;it.it_value.tv_usec = 0;it.it_interval.tv_sec = 0;it.it_interval.tv_usec = 0;ret = setitimer(ITIMER_REAL, &it, &oldit);if(-1 == ret){perror("setitimer");exit(1);}return oldit.it_value.tv_sec; }int main(){int i ;myalarm(1);for ( i = 0; ; i++){printf("%d\n",i);}return 0; }信號(hào)的捕捉
signal函數(shù):
? typedef void (*sighandler_t)(int);
? sighandler_t signal(int signum, sighandler_t handler);
? 參數(shù):signum,信號(hào),可以使用宏
? handler,信號(hào)捕捉函數(shù)
#include<stdio.h> #include<stdlib.h> #include<sys/time.h> #include<signal.h> #include<errno.h>typedef void (*sighandler_t)(int);void myfunc(int signo) {printf("deal signal = %d\n",signo); }int main(){struct itimerval it, oldit;sighandler_t handler;handler = signal(SIGALRM, myfunc); //注冊(cè)sigalrm信號(hào)的捕捉處理函數(shù)if(handler == SIG_ERR){perror("signal");exit(1);}int ret;it.it_value.tv_sec = 5;it.it_value.tv_usec = 0;it.it_interval.tv_sec = 3;it.it_interval.tv_usec = 0;ret = setitimer(ITIMER_REAL, &it, &oldit);if(-1 == ret){perror("setitimer");exit(1);}while(1);return 0; }sigaction函數(shù):
int sigactiong(int signum, const struct sigactiong *act, struct sigaction *oldact);
返回:成功:0;失敗:-1;設(shè)置errno
struct sigaction{void (*sa_handler)(int);//指定信號(hào)捕捉后的處理函數(shù)名(即注冊(cè)函數(shù))。也可賦值SIG_IGN表示忽略或SIG_DFL表示執(zhí)行默認(rèn)動(dòng)作void (*sa_sigaction)(int, siginfo_t *, void *);//當(dāng)sa_flags被指定為SA_SIGINFO標(biāo)志時(shí),使用該信號(hào)處理程序(很少使用)sigset_t sa_mask;//信號(hào)處理函數(shù)期間屏蔽的信號(hào)集;調(diào)用信號(hào)處理函數(shù)時(shí),所要屏蔽的信號(hào)集合(信號(hào)屏蔽字)。注意:僅在處理函數(shù)被調(diào)用期間屏蔽生效,臨時(shí)性設(shè)置。int sa_flags;//捕捉函數(shù)處理期間對(duì)相同信號(hào)的處理;通常為0,表示使用默認(rèn)屬性。void (*sa_restorer)(void);//過(guò)時(shí),不應(yīng)該使用,棄用 }參數(shù):signum,信號(hào)
? act:傳入?yún)?shù),新的處理方式
? oldact:傳出參數(shù),舊的處理方式
#include<stdio.h> #include<stdlib.h> #include<sys/time.h> #include<signal.h>void myfunc(int signo) {printf("deal signal = %d\n",signo); }int main(){struct sigaction act;act.sa_handler = myfunc;sigemptyset(&act.sa_mask);sigaddset(&act.sa_mask,SIGQUIT);act.sa_flags = 0;//自動(dòng)屏蔽本信號(hào)int ret = sigaction(SIGINT, &act, NULL);if(-1 == ret){perror("sigaction");exit(1);}while(1);return 0; }信號(hào)捕捉特性
1. 進(jìn)程正常運(yùn)行時(shí),默認(rèn)PCB中有一個(gè)信號(hào)屏蔽字,假定為*,他決定了進(jìn)程自動(dòng)屏蔽哪些信號(hào)。當(dāng)注冊(cè)了某個(gè)信號(hào)捕捉函數(shù),捕捉到該信號(hào)以后,要調(diào)用該函數(shù)。而該函數(shù)有可能執(zhí)行很長(zhǎng)時(shí)間,在這期間所屏蔽的信號(hào)不由 *來(lái)指定。而是用 sa_mask來(lái)指定。調(diào)用完信號(hào)處理函數(shù),再恢復(fù)為 *。 2. 某個(gè)信號(hào)捕捉函數(shù)執(zhí)行期間,該信號(hào)自動(dòng)被屏蔽。 3. 阻塞的常規(guī)信號(hào)不支持排隊(duì),產(chǎn)生多次只記錄一次。(后32個(gè)實(shí)時(shí)信號(hào)支持排隊(duì))? 被屏蔽的信號(hào)會(huì)在執(zhí)行捕捉函數(shù)執(zhí)行后執(zhí)行,如果該信號(hào)是被捕捉的信號(hào),則由捕捉函數(shù)處理,如果是其他信號(hào),則按照其默認(rèn)處理方式處理。
信號(hào)集操作函數(shù)
? 內(nèi)核通過(guò)讀取未決信號(hào)集來(lái)判斷信號(hào)是否應(yīng)被處理。信號(hào)屏蔽字mask可以影響未決信號(hào)集。我們可以在應(yīng)用程序中自定義set來(lái)改變mask。以達(dá)到屏蔽指定信號(hào)的目的。
信號(hào)集設(shè)定
sigset_t set; //typedef unsigned log sigset_t int sigemptyset(sigset_t *set); 將某個(gè)信號(hào)集清0 成功:0;失敗-1 int sigfillset(sigset_t *set); 將某個(gè)信號(hào)集置1 成功:0;失敗-1 int sigaddset(sigset_t *set, int signum); 將某個(gè)信號(hào)加入信號(hào)集 成功:0;失敗-1 int sigdelset(sigset_t * set,int signum); 將某個(gè)信號(hào)清出信號(hào)集 成功:0;失敗-1 int sigismember(const sigset_t *set,int signum); 判斷某個(gè)信號(hào)是否在信號(hào)集中 返回值:在集合:1;不在:0;出錯(cuò)-1? sigset_t 類型的本質(zhì)是位圖,但不應(yīng)該直接使用位操作,而應(yīng)該使用上述函數(shù),保證跨系統(tǒng)操作有效。
對(duì)比認(rèn)知select函數(shù)。
sigprocmask
? 用來(lái)屏蔽信號(hào),解除屏蔽也使用該函數(shù)。其本質(zhì),讀取或修改進(jìn)程的信號(hào)屏蔽字(PCB中)
? 嚴(yán)格注意,屏蔽信號(hào):只是將信號(hào)處理延后執(zhí)行(延至解除屏蔽);而忽略表示將信號(hào)丟處理。
? int sigprocmask(int how,const sigset_t *set, sigset_t *oldset);成功:0;失敗:-1,設(shè)置errno。
? 參數(shù):
 ? set :傳入?yún)?shù),是一個(gè)位圖,set中哪位置1,就表示當(dāng)前進(jìn)程屏蔽哪個(gè)信號(hào)。
 ? oldset:傳出參數(shù),保存舊的信號(hào)屏蔽集。
 ? how:參數(shù)取值:假設(shè)當(dāng)前的信號(hào)屏蔽字為mask
sigpending函數(shù)
? 讀取當(dāng)前進(jìn)程的未決信號(hào)集。
? int sigpending(sigset_t * set);set傳出參數(shù)。成功:0;失敗:-1,設(shè)置errno
#include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<signal.h>void myfunc(sigset_t *ped) {int i = 0;for(i = 1; i < 32; i++){if(sigismember(ped,i) == 1){putchar('1');}else{putchar('0');}}printf("\n"); }int main(){sigset_t myset, oldset, ped;sigemptyset(&myset);sigaddset(&myset, SIGQUIT);sigaddset(&myset, SIGINT);int ret = sigprocmask(SIG_BLOCK, &myset, &oldset);while(1){sigpending(&ped);myfunc(&ped);sleep(1);}return 0; }競(jìng)態(tài)條件
? 也稱為時(shí)序競(jìng)態(tài)
pause
? 調(diào)用該函數(shù)可以造成進(jìn)程主動(dòng)掛起,等待信號(hào)喚醒。調(diào)用該系統(tǒng)調(diào)用的進(jìn)程將處于阻塞狀態(tài)(主動(dòng)放棄cpu)直到信號(hào)遞達(dá)將其喚醒。
? int pause(void); 返回值:-1,并設(shè)置errno為EINTR。
? 返回值:
1. 如果信號(hào)的默認(rèn)處理動(dòng)作是終止進(jìn)程,則進(jìn)程終止,pause函數(shù)沒(méi)有機(jī)會(huì)返回。 2. 如果信號(hào)的默認(rèn)處理動(dòng)作是忽略,進(jìn)程繼續(xù)處于掛起狀態(tài),pause函數(shù)不返回。 3. 如果信號(hào)的默認(rèn)處理動(dòng)作是捕捉,則 調(diào)用完信號(hào)處理函數(shù)之后,pause返回-1,errno設(shè)置為EINTR,表示被信號(hào)中斷。 4. pause收到的信號(hào)不能被屏蔽,如果被屏蔽,那么pause就不能被喚醒。. //模范sleep #include<stdio.h> #include<unistd.h> #include<stdlib.h> #include<signal.h> #include<errno.h>void catch(int singno) {; }unsigned int sleepwx(unsigned int seconds) {int ret;struct sigaction act, oldact;act.sa_handler = catch;sigemptyset(&act.sa_mask);act.sa_flags = 0;ret=sigaction(SIGALRM, &act, &oldact);if(-1 == ret){perror("sigaction");exit(1);}alarm(seconds);ret = pause();if(-1 == ret && errno == EINTR){printf("pause ok\n"); }ret = alarm(0);sigaction(SIGALRM, &oldact, NULL);//恢復(fù)alrm信號(hào)原處理方式return ret; }int main(void) {while(1){sleepwx(5);printf("sleep end!!!!!!!!!!\n"); }return 0; }? pause函數(shù)使調(diào)用進(jìn)程掛起直到捕捉到一個(gè)信號(hào)。只有執(zhí)行了一個(gè)信號(hào)處理程序并從其返回時(shí),pause才返回。
時(shí)序競(jìng)態(tài)
? 如果在pause之前,調(diào)用alarm之后,程序失去cpu且時(shí)間較長(zhǎng),這期間 定時(shí)結(jié)束,發(fā)送完信號(hào),再執(zhí)行pause,程序?qū)?huì)一直卡住。
解決時(shí)序問(wèn)題
? 可以通過(guò)設(shè)置屏蔽SIGALRM的方法來(lái)控制程序執(zhí)行邏輯,但無(wú)論如何設(shè)置,程序都有可能在“解除信號(hào)屏蔽”與“掛起等待信號(hào)”這兩個(gè)操作間隙失去cpu資源。除非將這兩步驟合并成一個(gè)“原子操作”。sigsuspend函數(shù)具備這個(gè)功能。在對(duì)時(shí)序要求嚴(yán)格的場(chǎng)合下都應(yīng)該使用sigsuspend替換pause。
? int sigsuspend(const sigset_t *mask); 掛起等待信號(hào)。
? sigsuspend 函數(shù)調(diào)用期間,進(jìn)程信號(hào)屏蔽字由其參數(shù)mask指定。
? 可將某個(gè)信號(hào) 如SIGALRM從臨時(shí)信號(hào)屏蔽字mask中刪除,這樣在調(diào)用sigsuspend時(shí)將解除對(duì)該信號(hào)的屏蔽,然后掛起等待,當(dāng)sigsuspend返回時(shí),進(jìn)程的信號(hào)屏蔽字恢復(fù)為原來(lái)的值。如果原來(lái)對(duì)該信號(hào)是屏蔽態(tài),sigsuspend函數(shù)返回后仍然屏蔽該信號(hào)。
#include<stdio.h> #include<unistd.h> #include<stdlib.h> #include<signal.h> #include<errno.h>void catch(int singno) {; }unsigned int sleepwx(unsigned int seconds) {int ret;struct sigaction act, oldact;sigset_t newmask, oldmask, suspmask;act.sa_handler = catch;sigemptyset(&act.sa_mask);act.sa_flags = 0;ret = sigaction(SIGALRM, &act, &oldact);if(-1 == ret){perror("sigaction");exit(1);}//設(shè)置阻塞信號(hào)集集,阻塞sigalrm信號(hào)sigemptyset(&newmask);sigaddset(&newmask, SIGALRM);sigprocmask(SIG_BLOCK, &newmask, &oldmask);//定時(shí)alarm(seconds);//構(gòu)造一個(gè)調(diào)用sigsuspend臨時(shí)有效的阻塞信號(hào)集,在臨時(shí)阻塞信號(hào)集里解除sigalrm的阻塞suspmask = oldmask;sigdelset(&suspmask, SIGALRM);//sigsuspend調(diào)用期間,采用臨時(shí)阻塞信號(hào)集suspmask替換原有的阻塞信號(hào)集;這個(gè)信號(hào)集中不包含sigalrm信號(hào),同時(shí)掛起等待,當(dāng)sigsuspend被信號(hào)喚醒時(shí)返回,恢復(fù)原有阻塞信號(hào)集。sigsuspend(&suspmask);ret = alarm(0);sigaction(SIGALRM, &oldact, NULL);//恢復(fù)alrm信號(hào)原處理方式sigprocmask(SIG_SETMASK, &oldmask, NULL);//解除SIG_ALRM的阻塞return ret; }int main(void) {while(1){sleepwx(5);printf("sleep end!!!!!!!!!!\n"); }return 0; }總結(jié):
? 競(jìng)態(tài)條件,跟系統(tǒng)負(fù)載有很緊密的關(guān)系,體現(xiàn)出信號(hào)的不可靠性。系統(tǒng)負(fù)載越嚴(yán)重,信號(hào)不可靠性越強(qiáng)。
? 不可靠由其實(shí)現(xiàn)原理所致。信號(hào)是通過(guò)軟件方式(跟內(nèi)核調(diào)度高度依賴,延時(shí)性強(qiáng)),每次系統(tǒng)調(diào)用結(jié)束后,或中斷處理結(jié)束后,需通過(guò)掃描PCB中的未決信號(hào)集,來(lái)判斷是否應(yīng)該處理某個(gè)信號(hào),當(dāng)系統(tǒng)負(fù)載過(guò)重時(shí),會(huì)出現(xiàn)時(shí)序混亂。
? 這種意外情況只能在編寫程序過(guò)程中,提早遇見(jiàn),主動(dòng)避免,而無(wú)法通過(guò)gdb程序調(diào)試等其他手段彌補(bǔ)。且由于該錯(cuò)誤不具規(guī)律性,后期捕捉和重現(xiàn)十分困難。
全局變量異步IO
? 如下例子,去掉回調(diào)函數(shù)中的sleep后將會(huì)出現(xiàn)錯(cuò)誤。
#include<stdio.h> #include<unistd.h> #include<stdlib.h> #include<signal.h> #include<errno.h>int n = 0, flag = 0;void sys_err(char *str) {perror(str);exit(1); }void do_sig_child(int num) {printf("i am child %d\t%d\n",getpid(), n);n += 2;flag = 1;sleep(1); }void do_sig_parent(int num) {printf("i am parent %d\t%d\n",getpid(), n);n += 2;flag = 1;sleep(1); }int main(void) {pid_t pid;struct sigaction act;if((pid = fork())<0)sys_err("fork");else if(pid > 0){n = 1;sleep(1);//子進(jìn)程可以注冊(cè)完信號(hào)act.sa_handler = do_sig_parent;sigemptyset(&act.sa_mask);act.sa_flags = 0;//注冊(cè)自己的信號(hào)捕捉函數(shù) 父進(jìn)程使用sigusr2信號(hào)sigaction(SIGUSR2, &act, NULL);do_sig_parent(0);while(1){if(flag == 1){//父進(jìn)程數(shù)數(shù)完畢kill(pid, SIGUSR1);flag = 0;//標(biāo)志已經(jīng)給子進(jìn)程發(fā)送完信號(hào)}}}else if(pid == 0){n = 2;act.sa_handler = do_sig_child;sigemptyset(&act.sa_mask);act.sa_flags = 0;sigaction(SIGUSR1, &act, NULL);while(1){if(flag == 1){//進(jìn)程數(shù)數(shù)完畢kill(pid, SIGUSR2);//如果此處失去cpu,且父進(jìn)程已經(jīng)處理完并發(fā)送來(lái)信號(hào),子進(jìn)程數(shù)完數(shù)不會(huì)再次發(fā)送信號(hào),而是執(zhí)行下面的flag=0;flag = 0;//標(biāo)志已經(jīng)給父進(jìn)程發(fā)送完信號(hào)}}}return 0; }? 例子通過(guò)flag變量標(biāo)記進(jìn)程執(zhí)行進(jìn)度。flag置1表述處理完信號(hào)(數(shù)數(shù)+2)。flag置0表示給對(duì)方發(fā)送信號(hào)完成。
? 問(wèn)題出現(xiàn)的位置,在父、子進(jìn)程kill函數(shù)之后需要緊接著調(diào)用flag,復(fù)位0,標(biāo)記信號(hào)已經(jīng)發(fā)送。但是,這期間有可能被內(nèi)核調(diào)度,失去執(zhí)行權(quán)力,而對(duì)方獲取了執(zhí)行時(shí)間,通過(guò)發(fā)送信號(hào)回調(diào)捕捉函數(shù),從而修改了全局flag。
? 避免全局變量,在多個(gè)時(shí)序中進(jìn)行全局變量進(jìn)行修改。可在回調(diào)函數(shù)中發(fā)送信號(hào)。
可/不可重入函數(shù)
? 一個(gè)函數(shù)在被調(diào)用執(zhí)行期間(尚未調(diào)用結(jié)束),由于某種時(shí)序又被重復(fù)調(diào)用,稱之為“重入“(類似遞歸)。根據(jù)函數(shù)實(shí)現(xiàn)的方法可分為”可重入函數(shù)“和”不可重入函數(shù)“兩種。
調(diào)用了 malloc 和 free
標(biāo)準(zhǔn)的I/O函數(shù)
SIGCHLD信號(hào)
SIGCHLD的產(chǎn)生條件
? 子進(jìn)程終止時(shí)
? 子進(jìn)程收到SIGSTOP信號(hào)停止時(shí)
? 子進(jìn)程處在停止態(tài),接收到SIGCONT后喚醒時(shí)
借助SIGCHLD信號(hào)回收子進(jìn)程
? 子進(jìn)程結(jié)束運(yùn)行,其父進(jìn)程會(huì)收到SIGCHLD信號(hào)。該信號(hào)的默認(rèn)處理動(dòng)作是忽略。可以捕捉該信號(hào),在捕捉函數(shù)中完成子進(jìn)程狀態(tài)的回收。
#include<stdio.h> #include<unistd.h> #include<stdlib.h> #include<signal.h> #include<errno.h> #include<sys/types.h> #include<sys/wait.h>void sys_err(char *str) {perror(str);exit(1); } //一次捕捉,回收所有死亡進(jìn)程 void do_sig_child(int signo) {int status;pid_t pid;// if((pid = waitpid(0, &status, WNOHANG)) > 0){//可能同時(shí)死亡,只有一個(gè)信號(hào)while((pid = waitpid(0, &status, WNOHANG)) > 0){//盡量循環(huán)調(diào)用if(WIFEXITED(status))printf("-----------child %d exit %d \n",pid, WEXITSTATUS(status));else if(WIFSIGNALED(status))printf("child %d cancel signal %d\n",pid, WTERMSIG(status));} }int main(void) {pid_t pid;int i;//阻塞SIGCHLDfor(i = 0; i < 10; i++){if((pid = fork()) == 0){break;}else if(pid < 0)sys_err("fork");}if(0 == pid){int n = 1;while(n--){printf("child ID %d\n", getpid());sleep(1);}return i + 1;}else if(pid > 0){//阻塞SIGCHLDstruct sigaction act;act.sa_handler = do_sig_child;sigemptyset(&act.sa_mask);act.sa_flags = 0;sigaction(SIGCHLD, &act, NULL);//解除SIGCHLD阻塞while(1){printf("parent id %d \n",getpid());sleep(1);}}return 0; } #include<stdio.h> #include<unistd.h> #include<stdlib.h> #include<signal.h> #include<errno.h> #include<sys/wait.h> #include<string.h> void do_wait(int signo) {pid_t pid;int status;while((pid = (waitpid(0, &status, WNOHANG)))>0){printf("-----------------wait child \n");if(WIFEXITED(status)){printf("child exit with %d/n", WEXITSTATUS(status));}else if(WIFSIGNALED(status)){printf("child killed by %d/n", WTERMSIG(status));}} }int main(void) {pid_t pid;int fd[2];pipe(fd);pid = fork();if(0 == pid){close(fd[1]); //子進(jìn)程從管道中讀數(shù)據(jù),關(guān)閉寫端// dup2(fd[0], STDIN_FILENO); //讓wc從管道中讀數(shù)據(jù)char bufout[256] = {'0'};int read_ret = read(fd[0],bufout,256);if(read_ret == -1){perror("write error!");exit(-1);}else{printf("read %d bytes :%s\n",read_ret,bufout);}return 0;//execlp("wc", "wc", "-l, NULL");//wc命令默認(rèn)從標(biāo)準(zhǔn)讀入取數(shù)據(jù)}else{struct sigaction act;char bufin[256] = {"hello child"};act.sa_handler = do_wait;sigemptyset(&act.sa_mask);act.sa_flags = 0;sigaction(SIGCHLD, &act, NULL);close(fd[0]);//父進(jìn)程向通道中寫數(shù)據(jù),關(guān)閉讀端// dup2(fd[1], STDOUT_FILENO);write(fd[1],bufin,strlen(bufin));//execlp("ls", "ls", NULL);//ls輸出結(jié)果默認(rèn)對(duì)應(yīng)屏幕sleep(5);}return 0; }信號(hào)傳參
發(fā)送信號(hào)傳參
sigqueue函數(shù)對(duì)應(yīng)kill函數(shù),但可向指定進(jìn)程發(fā)送信號(hào)的同時(shí)攜帶參數(shù)
int sigqueue(pid_t pid, int sig, const union sigval value); union sigval{int sival_int;void *sival_ptr; }? pid :要發(fā)送進(jìn)程的進(jìn)程ID
? sig :要發(fā)送的信號(hào)
? value:攜帶的數(shù)據(jù)
返回:成功:0;失敗:-1,設(shè)置errno;
? 向指定進(jìn)程發(fā)送指定信號(hào)的同時(shí),攜帶參數(shù)。但,如傳地址,需注意,不同進(jìn)程之間虛擬地址空間各自獨(dú)立,將當(dāng)前進(jìn)程地址傳遞給另一進(jìn)程沒(méi)有實(shí)際意義(可以應(yīng)用在給本進(jìn)程發(fā)送信號(hào))。
捕捉函數(shù)傳參
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); struct sigaction{void (*sa_handler)(int);void (*sa_sigaction)(int, siginfo_t*, void*);sigset_t sa_mask;int sa_flags;void (*sa_restorer)(void); };? 當(dāng)注冊(cè)信號(hào)捕捉函數(shù),希望獲得更多信號(hào)相關(guān)信息,不應(yīng)使用sa_handler而應(yīng)該使用sa_sigaction。但此時(shí)的sa_flags必須指定為SA_SIGINFO。siginfo_t是一個(gè)成員十分豐富的結(jié)構(gòu)體類型,可以攜帶各種與信號(hào)相關(guān)的數(shù)據(jù)。
中斷系統(tǒng)調(diào)用
系統(tǒng)調(diào)用可分為兩類:慢速系統(tǒng)調(diào)用和其他系統(tǒng)調(diào)用。
結(jié)合pause,回顧慢速系統(tǒng)調(diào)用:
? 慢速系統(tǒng)調(diào)用被中斷的相關(guān)行為,實(shí)際上就是pause的行為:如,read
1. 想中斷pause,信號(hào)不能被屏蔽 1. 信號(hào)的處理方式必須時(shí)捕捉(默認(rèn)和忽略都不可以) 1. 中斷后返回-1,設(shè)置errno為eintr(表示“被信號(hào)中斷”)? 可修改sa_flags參數(shù)來(lái)設(shè)置被信號(hào)中斷后系統(tǒng)調(diào)用是否重啟。SA_INTERRURT不重啟。SA_RESTART重啟。
? sa_flags有許多可選參數(shù),適用于不同情況。如:捕捉到信號(hào)后,在執(zhí)行捕捉函數(shù)期間,不希望自動(dòng)阻塞該信號(hào),可將sa_flags設(shè)置為SA_NODEFER,除非sa_mask中包含該信號(hào)。
4.守護(hù)進(jìn)程
終端
? 輸入輸出設(shè)備總稱
? 在unix系統(tǒng)中,用戶通過(guò)終端登錄系統(tǒng)后得到一個(gè)shell進(jìn)程,這個(gè)終端稱為shell進(jìn)程的控制終端,在進(jìn)程中,控制終端是保存在pcb中的信息,而fork會(huì)復(fù)制pcb中的信息,因此由shell進(jìn)程啟動(dòng)的其他進(jìn)程的控制終端也是這個(gè)終端。默認(rèn)情況下(沒(méi)有重定向),每個(gè)進(jìn)程的標(biāo)準(zhǔn)輸入輸出和標(biāo)準(zhǔn)錯(cuò)誤輸出都指向控制終端,進(jìn)程從標(biāo)準(zhǔn)輸入讀也就是用戶的鍵盤輸入,進(jìn)程往標(biāo)準(zhǔn)輸出和錯(cuò)誤輸出寫也就是輸出到顯示器上。信號(hào)中,在控制終端輸入一些特殊控制鍵可以給前臺(tái)進(jìn)程發(fā)信號(hào),如ctrl + c 表示SIGINT。
? Alt+Ctl+F1、F2、F3、F4、F5、F6 字符終端
? pts(pseudo terminal slave)指?jìng)谓K端
? Alt+F7 圖形終端
? SSH Telnet 網(wǎng)絡(luò)終端
終端的啟動(dòng)流程
? 每個(gè)進(jìn)程都可以通過(guò)一個(gè)特殊的設(shè)備文件/dev/tty 訪問(wèn)他的控制終端。事實(shí)上每個(gè)終端設(shè)備都對(duì)應(yīng)一個(gè)不同的設(shè)備文件,/dev/tty提供了一個(gè)通用的接口,一個(gè)進(jìn)程要訪問(wèn)他的控制終端既可以通過(guò)/dev/tty,也可以通過(guò)該終端設(shè)備對(duì)應(yīng)的設(shè)備文件來(lái)訪問(wèn)。ttyname函數(shù)可以由文件描述符查出對(duì)應(yīng)的文件名,該文件描述符必須指向一個(gè)終端設(shè)備而不能是任意文件。
? 其步驟如下:
? init→fork→exec→getty→用戶輸入賬號(hào)→login→輸入密碼→exec→bash
? 硬件驅(qū)動(dòng)程序負(fù)責(zé)讀寫實(shí)際的硬件設(shè)備,比如從鍵盤讀入字符和把字符輸出到顯示器,線路規(guī)程(line disciline,用來(lái)過(guò)濾鍵盤輸入的內(nèi)容)像一個(gè)過(guò)濾器,對(duì)于某些特殊字符并不是讓他直接通過(guò),而是做特殊處理,比如在鍵盤上按下ctrl + z,對(duì)應(yīng)的字符并不會(huì)被用戶程序的read讀到,而是被線路規(guī)程截獲,解釋成SIGTSTP信號(hào)發(fā)送給前臺(tái)進(jìn)程,通常會(huì)使該進(jìn)程停止。線路規(guī)程應(yīng)該過(guò)濾哪些字符和哪些特殊處理是可以配置的。
ttyname
? 由文件描述符查出對(duì)應(yīng)的文件名
? char *ttyname(int fd); 成功:終端名稱;失敗NULL,設(shè)置errno
借助ttyname查看不同終端設(shè)備的名稱:
#include<stdio.h> #include<unistd.h>int main(void) {printf("fd 0:%s\n",ttyname(0));//printf("fd 1:%s\n",ttyname(1));printf("fd 2:%s\n",ttyname(2));return 0; }網(wǎng)絡(luò)終端
? 虛擬終端或串口終端的數(shù)目是有限的,虛擬終端(字符控制終端)一般就是/dev/tty1 - /dev/tty6六個(gè),串口終端的數(shù)目也不超過(guò)串口的數(shù)據(jù)。然而網(wǎng)絡(luò)終端或圖形終端窗口的數(shù)目卻是不受限制的,這是通過(guò)偽終端(Pseudo TTY)實(shí)現(xiàn)的。一套偽終端由一個(gè)主設(shè)備(PTY Master)和一個(gè)從設(shè)備(PTY Slave)組成。主設(shè)備在概念上相當(dāng)于鍵盤和顯示器,只不過(guò)他不是真正的硬件而是一個(gè)內(nèi)核模塊,操作它的也不是用戶而是另外一個(gè)進(jìn)程。從設(shè)備和上面介紹的/dev/tty1 這樣的終端設(shè)備模塊類似,只不過(guò)它的底層驅(qū)動(dòng)程序不是訪問(wèn)硬件,而是訪問(wèn)主設(shè)備。網(wǎng)絡(luò)終端或圖形終端窗口的shell進(jìn)程以及它啟動(dòng)的其他進(jìn)程都會(huì)認(rèn)為自己控制終端是偽終端從設(shè)備。例如/dev/pts/0、/dev/pts/1等。
? TCP/IP協(xié)議棧:在數(shù)據(jù)包上添加報(bào)頭。
? 如果telnet客戶端和服務(wù)器之間的網(wǎng)絡(luò)延遲較大,我們會(huì)觀察到按下一個(gè)鍵之后要過(guò)幾秒鐘才能回顯到屏幕上。這說(shuō)明我們沒(méi)按一個(gè)鍵,telnet客戶端都會(huì)立刻把該字符發(fā)送到服務(wù)器,然后這個(gè)字符經(jīng)過(guò)偽終端主設(shè)備和從設(shè)備之后被shell進(jìn)程讀取,同時(shí)回顯到偽終端從設(shè)備,回顯字符再經(jīng)過(guò)偽終端主設(shè)備、telnet服務(wù)器和網(wǎng)絡(luò)發(fā)回給telnet客戶端,顯示給用戶看。每一個(gè)按鍵都會(huì)走一個(gè)來(lái)回。
進(jìn)程組
? 進(jìn)程組,也叫作業(yè)。代表一個(gè)或多個(gè)進(jìn)程的集合。每個(gè)進(jìn)程都屬于一個(gè)進(jìn)程組。在waitpid函數(shù)和kill函數(shù)的參數(shù)中都可以使用。操作系統(tǒng)設(shè)計(jì)進(jìn)程組的概念,是為了簡(jiǎn)化對(duì)多個(gè)進(jìn)程的管理。
? 當(dāng)父進(jìn)程,創(chuàng)建子進(jìn)程的時(shí)候,默認(rèn)子進(jìn)程與父進(jìn)程屬于同一進(jìn)程組。進(jìn)程組ID等于第一個(gè)進(jìn)程ID(組長(zhǎng)進(jìn)程)。所有,組長(zhǎng)進(jìn)程標(biāo)識(shí):其進(jìn)程組ID==其進(jìn)程ID。
? 可以使用kill -SIGKILL -進(jìn)程組ID(負(fù)數(shù))來(lái)將整個(gè)進(jìn)程組內(nèi)的進(jìn)程全部殺死。
? 組長(zhǎng)進(jìn)程可以創(chuàng)建一個(gè)進(jìn)程組,創(chuàng)建該進(jìn)程組中的進(jìn)程,然后終止。只要進(jìn)程組中有一個(gè)進(jìn)程存在,進(jìn)程組就存在,與組長(zhǎng)進(jìn)程是否終止無(wú)關(guān)。
? 進(jìn)程組生存期:進(jìn)程組創(chuàng)建到最后一個(gè)進(jìn)程離開(終止或轉(zhuǎn)移到另一個(gè)進(jìn)程組)。
? 一個(gè)進(jìn)程可以為自己或子進(jìn)程設(shè)置進(jìn)程組ID。
進(jìn)程操作函數(shù)
getpgrp
? 獲取當(dāng)前進(jìn)程的進(jìn)程組ID
? pid_t getpgrp(void);總是返回調(diào)用者的進(jìn)程組ID。
getpgid
? 獲取指定進(jìn)程的進(jìn)程組ID
? pid_t getpgid(pid_t pid);成功:0;失敗:-1,設(shè)置errno
? 如果pid=0,那么該函數(shù)作用和getpgrp一樣
setpgid
? 改變進(jìn)程默認(rèn)所屬的進(jìn)程組。通常可用來(lái)加入一個(gè)現(xiàn)有的進(jìn)程組或創(chuàng)建一個(gè)新的進(jìn)程組。
? int setpgid(pid_t pid,pid_t pgid);成功:0;失敗:-1,設(shè)置errno
將參數(shù)1對(duì)應(yīng)的進(jìn)程,加入?yún)?shù)2對(duì)應(yīng)的進(jìn)程組中。
注意:
1. 如改變子進(jìn)程為新的組,應(yīng)forkhou,exec前。 2. 權(quán)級(jí)問(wèn)題。非root進(jìn)程只能改變自己創(chuàng)建的子進(jìn)程,或有權(quán)限操作的進(jìn)程(如自己)。 #include<stdio.h> #include<unistd.h> #include<stdlib.h> #include<signal.h> #include<errno.h> #include<sys/types.h> #include<sys/wait.h>void sys_err(char *str) {perror(str);exit(1); }int main(int argc, char *argv[]) {pid_t pid;if(argc < 2){printf("./a.out num\n");return 0;}int count = atoi(argv[1]);int i = 0;//阻塞SIGCHLDfor(i = 0; i < count; i++){if((pid = fork()) == 0){break;}else if(pid < 0)sys_err("fork");}if(0 == pid){printf("child ID %d,parent ID %d,group id %d\n", getpid(),getppid(),getpgid(0));sleep(2);}else if(pid > 0){printf("parent id %d,parent ID %d,group id %d \n",getpid(),getppid(),getpgid(0));sleep(4);pid_t cpid;while ((cpid = wait(NULL))>0){printf("child %d is over\n",cpid);}}return 0; }會(huì)話
可以理解一組進(jìn)程組為會(huì)話。
創(chuàng)建會(huì)話
注意事項(xiàng):
getsid:
獲取進(jìn)程所屬的會(huì)話ID
? pid_t getsid(pid_t pid);成功:返回調(diào)用進(jìn)程的會(huì)話ID;失敗:-1,設(shè)置errno
pid為0表示查看當(dāng)前進(jìn)程session ID
ps ajx命令查看系統(tǒng)中的進(jìn)程。參數(shù)a表示不僅列當(dāng)前用戶的進(jìn)程,也列出所有其他用戶的進(jìn)程,參數(shù)x表示不僅列出有控制終端的進(jìn)程,也列出所有無(wú)控制終端的進(jìn)程,參數(shù)j表示列出與作業(yè)控制相關(guān)的信息。
組長(zhǎng)進(jìn)程不能成為新會(huì)話首進(jìn)程,新會(huì)話首進(jìn)程必定會(huì)成為組長(zhǎng)進(jìn)程。
setsid
創(chuàng)建一個(gè)會(huì)話,并以自己的ID設(shè)置進(jìn)程組ID,同時(shí)也是新會(huì)話的ID。
? pid_t setsid(void):成功,返回調(diào)用進(jìn)程的會(huì)話ID,失敗:-1,設(shè)置errno
調(diào)用了setsid函數(shù)的進(jìn)程,即使新的會(huì)長(zhǎng),也是新的組長(zhǎng)。
#include<stdio.h> #include<unistd.h> #include<stdlib.h> #include<signal.h> #include<errno.h> #include<sys/types.h> #include<sys/wait.h>void sys_err(char *str) {perror(str);exit(1); }int main(void) {pid_t pid;if((pid = fork()) < 0){sys_err("fork");}else if(pid == 0){sleep(2);printf("child id %d,group id %d,sid %d\n",getpid(),getpgrp(),getsid(0));setsid();printf("child id %d,group id %d,sid %d\n",getpid(),getpgrp(),getsid(0));}return 0; }守護(hù)進(jìn)程
? Daemon(精靈)進(jìn)程,是linux中的后臺(tái)服務(wù)進(jìn)程,通常獨(dú)立于控制終端并且周期性的執(zhí)行某種任務(wù)或等待處理某些發(fā)生的事件。一般采用以d結(jié)尾的名字。
? linux后臺(tái)的一些系統(tǒng)服務(wù)進(jìn)程,沒(méi)有控制終端,不能直接和用戶交互。不受用戶登錄、注銷的影響,一直在運(yùn)行著,他們都是守護(hù)進(jìn)程。如:預(yù)讀入緩輸出機(jī)制的實(shí)現(xiàn);ftp服務(wù)器;nfs服務(wù)器等。
? 創(chuàng)建守護(hù)進(jìn)程,最關(guān)鍵的一步是調(diào)用setsid函數(shù)創(chuàng)建一個(gè)新的session,并稱為session leader。
創(chuàng)建守護(hù)進(jìn)程模型
所有工作在子進(jìn)程中進(jìn)行,形式上脫離了控制終端
setsid()函數(shù)
使子進(jìn)程完全獨(dú)立出來(lái),脫離控制
chdir()函數(shù)
防止占用可卸載的文件系統(tǒng)
也可以換成其他路徑
umask()函數(shù)
防止繼承的文件創(chuàng)建屏蔽字拒絕某些權(quán)限
繼承的打開文件不會(huì)用到,浪費(fèi)系統(tǒng)資源,無(wú)法卸載
.bashrc修改設(shè)置程序自動(dòng)啟動(dòng)。
5.線程
線程的概念
什么是線程
? LWP:light weight process 輕量級(jí)的進(jìn)程,本質(zhì)仍然是進(jìn)程(在linux環(huán)境下)
? 進(jìn)程:獨(dú)立地址空間,擁有PCB
? 線程:也有PCB,但沒(méi)有獨(dú)立的地址空間(共享)
? 區(qū)別:在于是否共享地址空間 獨(dú)居(進(jìn)程);合租(線程)
? linux下:線程:最小的執(zhí)行單位
 ? 進(jìn)程:最小的分配資源單位,可看成是只有一個(gè)線程的進(jìn)程。
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-ukl7IkYQ-1639009614674)(G:\學(xué)習(xí)資料\視頻\linux服務(wù)器開發(fā)二-系統(tǒng)編程視頻\day06\day07.zip)]
linux內(nèi)核線程實(shí)現(xiàn)原理
? 類似unix系統(tǒng)中,早期沒(méi)有線程的概念,80年代引入,借助進(jìn)程機(jī)制實(shí)現(xiàn)了線程的概念。因此在這類系統(tǒng)中,進(jìn)程和線程關(guān)系密切。
查看lwp號(hào)(劃分給線程時(shí)間片的依據(jù)):ps -L pid查看指定線程的lwp號(hào)。線程ID和線程號(hào)不同。
ps -Lf 進(jìn)程ID 查看進(jìn)程內(nèi)的線程。
線程共享資源
線程非共享資源
線程優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
缺點(diǎn):
總結(jié):
? 優(yōu)點(diǎn)相對(duì)突出,缺點(diǎn)不是硬傷。linux下由于實(shí)現(xiàn)方法導(dǎo)致進(jìn)程、線程差別不是很大。
線程控制原語(yǔ)
pthread_self
獲取線程ID。其作用對(duì)應(yīng)進(jìn)程中g(shù)etpid()函數(shù)。
pthread_t pthread_self(void);返回值:成功:0;失敗:無(wú)
線程ID:pthread_t類型,本質(zhì):在linux下為無(wú)符號(hào)整數(shù)(%lu),其他系統(tǒng)中可能是結(jié)構(gòu)體實(shí)現(xiàn)。
線程ID是進(jìn)程內(nèi)部,識(shí)別標(biāo)志。(兩個(gè)進(jìn)程間,線程ID允許相同)
注意:不應(yīng)使用全局變量pthread_t tid,在子線程中通過(guò)pthread_create傳出參數(shù)來(lái)獲取線程ID,而應(yīng)使用pthread_self。gcc編譯時(shí),應(yīng)使用-pthread參數(shù)調(diào)用線程庫(kù)。
pthread_create
創(chuàng)建一個(gè)新線程。其作用,對(duì)應(yīng)進(jìn)程中的fork()函數(shù)。
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *( *start_routine)(void * ),void * arg);返回值:成功:0;失敗:錯(cuò)誤號(hào)。linux環(huán)境下,所有線程特點(diǎn),失敗均直接返回錯(cuò)誤號(hào)。
參數(shù):
? pthread_t: 當(dāng)前l(fā)inux中可理解為 typedef unsigned long int pthread_t;
? 參數(shù)1:傳出參數(shù),保存系統(tǒng)為我們分配好的線程ID
? 參數(shù)2:通常傳NULL,表示使用線程默認(rèn)屬性。若想使用具體屬性,可修改該參數(shù)。
? 參數(shù)3:函數(shù)指針,指向線程主函數(shù)(線程體),該函數(shù)運(yùn)行結(jié)束,則線程結(jié)束。
? 參數(shù)4:線程主函數(shù)執(zhí)行期間所使用的參數(shù)。
#include<stdlib.h> #include<stdio.h> #include<pthread.h> #include<unistd.h>void * thread_func(void* arg) {printf("in thread, thread_id = %lu, pid=%d\n",pthread_self(),getpid());return NULL; }int main() {pthread_t tid;int ret;printf("in main, thread_id = %lu, pid=%d\n",pthread_self(),getpid());ret = pthread_create(&tid,NULL,thread_func,NULL);if(ret != 0){printf("pthread_create error=%s\n",strerror(ret));perror("pthread_create");exit(1);}sleep(1);printf("in main, thread_id = %lu, pid=%d,son thread ID=%lu\n",pthread_self(),getpid(),tid);return 0; }編譯:gcc creat.c -o creat -pthread
線程默認(rèn)共享數(shù)據(jù)段、代碼段等地址空間,常用的全局變量。而進(jìn)程不共享全局變量,只能借助mmap。
#include<stdlib.h> #include<stdio.h> #include<pthread.h> #include<unistd.h>int var = 100; void * thread_func(void* arg) {var = 200;int i = (int)arg;//*((int *)arg) 此種情況回去主函數(shù)地址中取i值,i的值不定printf("in thread %d, thread_id = %lu, pid=%d\n",i,pthread_self(),getpid());return NULL; }int main() {pthread_t tid;int ret,i;for( i = 0; i < 5 ; i++){ret = pthread_create(&tid,NULL,thread_func,(void *)i);//(void *)&iif(ret != 0){perror("pthread_create");exit(1);}}sleep(1);printf("in main, var = %d\n",var);return 0; }pthread_exit
將當(dāng)個(gè)線程退出
void pthread_exit(void *retval);
參數(shù):retval表示線程退出狀態(tài),通常傳NULL
線程中,禁止使用exit函數(shù),會(huì)導(dǎo)致進(jìn)程內(nèi)所有線程全部退出。
? 在不添加sleep控制輸出順序情況下。pthread_create在循環(huán)中,近乎瞬間創(chuàng)建了五個(gè)線程,但只有一個(gè)線程有機(jī)會(huì)輸出(或者兩個(gè),也可能沒(méi)有,取決于內(nèi)核調(diào)度)如果第三個(gè)線程執(zhí)行了exit,將整個(gè)進(jìn)程退出了,全部的線程就退出了。
#include<stdlib.h> #include<stdio.h> #include<pthread.h> #include<unistd.h>void * thread_func(void* arg) {int i = (int)arg;printf("in thread %d, thread_id = %lu, pid=%d\n",i,pthread_self(),getpid());return NULL;//退出線程,類似 pthread_exit(NULL)// pthread_exit(NULL);//退出線程 }int main() {pthread_t tid;int ret,i;for( i = 0; i < 5 ; i++){ret = pthread_create(&tid,NULL,thread_func,(void *)i);if(ret != 0){perror("pthread_create");exit(1);}}printf("in main thread_id = %lu, pid=%d\n",pthread_self(),getpid());pthread_exit((void*)1);//main所在的線程退出// return 0;//h會(huì)使主控進(jìn)程退出//exit(1);//也是退出進(jìn)程,在其他線程中也會(huì)退出整個(gè)進(jìn)程 }? 多線程環(huán)境中,應(yīng)盡量少用,或者不適用exit函數(shù),取而代之使用pthread_exit函數(shù),將單個(gè)線程退出。任何線程里exit導(dǎo)致進(jìn)程退出,其他線程未工作結(jié)束,主控線程退出時(shí)不能return或exit。
? pthread_exit或者return返回的指針?biāo)赶虻膬?nèi)存單元必須時(shí)全局的或者是用malloc分配的,不能在線程函數(shù)的棧上分配,因?yàn)楫?dāng)其他線程得到這個(gè)返回指針時(shí),線程函數(shù)已經(jīng)退出了。
? return:返回到調(diào)用者那里去。
? pthread_exit():將調(diào)用該函數(shù)的線程退出
? exit:將進(jìn)程退出。
pthread_join
? 阻塞等待線程推出,獲取線程退出狀態(tài)。他的作用對(duì)應(yīng)進(jìn)程中的waitpid()函數(shù)。
int pthread_join(pthread_t thread, void ** retval);成功:0;失敗:錯(cuò)誤號(hào)
參數(shù):thread:線程ID(不是指針);retval:存儲(chǔ)線程結(jié)束狀態(tài)。
對(duì)比記憶:
? 進(jìn)程中:main返回值、exit參數(shù)–>int;等待子進(jìn)程結(jié)束wait函數(shù)參數(shù)–>int*
 ? 線程中:線程主函數(shù)返回值、pthread_exit–>void*;等待線程結(jié)束pthread_join函數(shù)參數(shù)–>void **
調(diào)用該函數(shù)的線程將掛起等待,知道id為thread的線程終止。通過(guò)pthread_join得到的終止?fàn)顟B(tài)是不同的,總結(jié)如下:
1. 如果thread線程通過(guò)return返回,retval所指向的單元里存放的是thread線程函數(shù)的返回值。 1. 如果thread線程被別的線程調(diào)用pthread_cancel異常終止掉,retval所指向的單元里存放的是常數(shù)PTHREAD_CANCLED 1. 如果pthread線程自己調(diào)用pthread_exit終止,retval所指向的單元里存放的是傳給pthread_exit的參數(shù)。 1. 如果thread線程的終止?fàn)顟B(tài)不感興趣,可以傳NULL給retval參數(shù)。 1. 也可以在兄弟線程中回收其他線程 #include<stdlib.h> #include<stdio.h> #include<pthread.h> #include<unistd.h>typedef struct{int a;int b; }exit_t;void * tfn(void* arg) {exit_t *ret;ret = malloc(sizeof(exit_t));ret->a = 100;ret->b = 300;pthread_exit((void *)ret); }void * tfn2(void* arg) {exit_t *ret = (exit_t *)arg;ret->a = 100;ret->b = 300;pthread_exit((void *)ret); }int main(void) {pthread_t tid;exit_t *retval;pthread_create(&tid,NULL,tfn,NULL);pthread_join(tid, (void **)&retval);printf("a = %d, b=%d\n",retval->a,retval->b);free(retval);exit_t * ret = malloc(sizeof(exit_t));pthread_create(&tid,NULL,tfn2,(void *)ret);pthread_join(tid, (void **)&ret);printf("tfn2 a = %d, b=%d\n",ret->a,ret->b);free(ret);return 0; }pthread_detach
實(shí)現(xiàn)線程分離
int pthread_detach(pthread_t thread);成功:0;失敗:錯(cuò)誤號(hào)
? 線程分離狀態(tài):指定該狀態(tài),線程主動(dòng)與主控線程斷開關(guān)系。線程結(jié)束后,其退出狀態(tài)不由其他線程獲取,而直接自己自動(dòng)釋放。網(wǎng)絡(luò)、多線程服務(wù)器常用。
? 進(jìn)程如果有該機(jī)制,將不會(huì)產(chǎn)生僵尸進(jìn)程。僵尸進(jìn)程的產(chǎn)生主要由于進(jìn)程死后,大部分資源被釋放,一點(diǎn)殘留資源仍然存在于系統(tǒng)中,導(dǎo)致內(nèi)核認(rèn)為該進(jìn)程仍存在。
? 也可以使用pthread_create 函數(shù)參數(shù)2(線程屬性)來(lái)設(shè)置線程分離。
? 一般情況下,線程終止后,其終止?fàn)顟B(tài)一直保留到其他線程調(diào)用pthread_join獲取他的狀態(tài)為止。但是線程也可以被置為detach狀態(tài),這樣的線程一旦終止就立刻回收它占用的所有資源,而不保留終止?fàn)顟B(tài)。不能對(duì)一個(gè)已經(jīng)處于detach狀態(tài)的線程調(diào)用pthread_join,這樣的調(diào)用將返回einval錯(cuò)誤。也就是說(shuō),如果已經(jīng)對(duì)一個(gè)線程調(diào)用了pthread_detach就不能再調(diào)用pthread_join了。
#include<stdlib.h> #include<stdio.h> #include<pthread.h> #include<unistd.h> #include<string.h>void * tfn(void* arg) {int n = 3;while(n--){printf("thread count %d\n",n);sleep(1);}return (void *)1; }int main(void) {pthread_t tid;void * tret;int err;#if 0pthread_attr_t attr;pthread_attr_init(&attr);pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);pthread_create(&tid, &attr, tfn,NULL); #elsepthread_create(&tid, NULL, tfn, NULL);pthread_detach(tid); #endifwhile(1){err = pthread_join(tid,&tret);printf("----------------------err=%d\n",err);if(err != 0)fprintf(stderr,"thread exit %s \n",strerror(err));elsefprintf(stderr,"thread exit code %d\n",(int)tret);sleep(1);}return 0; }優(yōu)點(diǎn):可以自動(dòng)清理pcb。
pthread_cancel
殺死(取消)線程,其作用,對(duì)應(yīng)進(jìn)程中的kill()函數(shù)
int pthread_cancel(pthread_t thread); 成功:0;失敗:錯(cuò)誤號(hào)
? 線程的取消不是實(shí)時(shí)的,而有一定的延時(shí)。需要等待線程到達(dá)某個(gè)取消點(diǎn)(檢查點(diǎn))。
? 類似于玩游戲必須存檔,必須到達(dá)指定的場(chǎng)所才能存儲(chǔ)進(jìn)度。殺死線程也不是立刻就能完成,必須要到達(dá)取消點(diǎn)。
? 取消點(diǎn):是線程檢查是否被取消,并按請(qǐng)求進(jìn)行動(dòng)作的一個(gè)位置。通常是一些系統(tǒng)調(diào)用creat,open,pause,close,read,write…執(zhí)行命令man 7 pthreads可以查看具備這些取消點(diǎn)的系統(tǒng)調(diào)用列表。也可參閱APUE.12.7取消選項(xiàng)小節(jié)。
? 可粗略認(rèn)為一個(gè)系統(tǒng)調(diào)用(進(jìn)入內(nèi)核)即為一個(gè)取消點(diǎn)。如線程中沒(méi)有取消點(diǎn),可以通過(guò)pthread_testcancel函數(shù)自行設(shè)置幾個(gè)取消點(diǎn)。
? 被取消的線程,退出值定義在linux的pthread庫(kù)中。常數(shù)PTHREAD_CANCELED的值是-1.頭文件中pthread.h中他的定義:``define PTHREAD_CANCELED((void *)-1)。因此我們對(duì)一個(gè)已經(jīng)被取消的線程使用pthread_join回收時(shí),得到的返回值是-1。
#include<stdlib.h> #include<stdio.h> #include<pthread.h> #include<unistd.h>void * tfn(void* arg) {printf("thread 1 return\n");return (void *)111; }void * tfn2(void* arg) {printf("thread 2 return\n");pthread_exit((void *)222); }void * tfn3(void* arg) {while (1){printf("thread 3 : gonging to die\n");sleep(1);//以上兩句屏蔽后,線程不會(huì)被殺死// pthread_testcancel();//函數(shù)內(nèi)如果不存在取消點(diǎn),可手動(dòng)加上這個(gè)取消點(diǎn)}return (void*)666; }int main(void) {pthread_t tid;exit_t *retval;pthread_create(&tid,NULL,tfn,NULL);pthread_join(tid, (void **)&retval);printf("pthread1 return=%d\n",(int)retval);pthread_create(&tid,NULL,tfn2,NULL);pthread_join(tid, (void **)&retval);printf("pthread2 return=%d\n",(int)retval);pthread_create(&tid,NULL,tfn2,NULL);sleep(3);pthread_cancel(tid);pthread_join(tid, (void **)&retval);printf("pthread3 return=%d\n",(int)retval);return 0; }pthread_equal
比較兩個(gè)線程ID是否相等
int pthread_equal(pthread_t t1,pthread_t t2);
有可能linux在未來(lái)線程ID pthread_t 類型被修改為結(jié)構(gòu)體實(shí)現(xiàn)。
控制原語(yǔ)對(duì)比
進(jìn)程 線程
fork pthread_create
exit pthread_exit
wait pthread_join
kill pthread_cancel
getpid pthread_self
線程屬性
? linux下線程的屬性時(shí)可以根據(jù)實(shí)際項(xiàng)目需要,進(jìn)行設(shè)置,之前我們討論的線程都是采用線程的默認(rèn)屬性,默認(rèn)屬性已經(jīng)可以解決絕大多數(shù)開發(fā)時(shí)遇到的問(wèn)題。如果我們對(duì)程序的性能提出更高的要求,那么需要設(shè)置線程屬性,比如 可以通過(guò)設(shè)置線程棧的大小來(lái)降低內(nèi)存的使用,增加最大線程個(gè)數(shù)。
typedef struct {int etachstate; //線程的分離狀態(tài)int schedpolicy; //線程調(diào)度策略struct sched_param schedparam; //線程的調(diào)度參數(shù)int inheritsched; //線程的繼承性int scope; //線程的作用域size_t guardsize; //線程棧末尾的警戒緩沖區(qū)大小int stackaddr_set; //線程的棧設(shè)置void* stackaddr; //線程棧的位置size_t stacksize; //線程棧的大小 }pthread_attr_t;主要結(jié)構(gòu)體成員:
? 屬性值不能直接設(shè)置,必須使用相關(guān)函數(shù)進(jìn)行操作,初始化的函數(shù)為 pthread_attr_init,這個(gè)函數(shù)必須在 pthread_create 函數(shù)之前調(diào)用。之后需用 pthread_attr_destory 函數(shù)來(lái)釋放資源。
? 線程屬性主要包括如下屬性:作用域(scope)、棧尺寸(stack size)、棧地址(stack address)、優(yōu)先級(jí)(priority)、分離的狀態(tài)(detached state)、調(diào)度策略和參數(shù)(scheduling policy and parameters)。默認(rèn)的屬性為非綁定、非分離、缺省的堆棧、與父進(jìn)程同樣級(jí)別的優(yōu)先級(jí)。
線程屬性初始化
注意:應(yīng)先初始化線程屬性,再pthread_create創(chuàng)建線程
初始化線程屬性
int pthread_attr_init(pthread_attr_t *attr); 成功:0;失敗:錯(cuò)誤號(hào)
銷毀線程屬性所占用的資源
int pthread_attr_destroy(pthread_attr_t *attr); 成功:0;失敗:錯(cuò)誤號(hào)
線程的分離狀態(tài)
? 線程的分離狀態(tài)決定了一個(gè)線程以什么樣的方式來(lái)終止自己。
? 非分離狀態(tài):線程的默認(rèn)屬性是非分離狀態(tài),這種情況下,原有的線程等待創(chuàng)建線程結(jié)束。只有當(dāng)pthread_join()函數(shù)返回時(shí),創(chuàng)建的線程才算終止,才能釋放自己占用的系統(tǒng)資源。
? 分離狀態(tài):分離線程沒(méi)有被其他的線程所等待,自己運(yùn)行結(jié)束了,線程也就終止了,馬上釋放系統(tǒng)資源。應(yīng)該根據(jù)自己的需要,選擇適當(dāng)?shù)姆蛛x狀態(tài)。
? 線程分離狀態(tài)的函數(shù):
? 設(shè)置線程屬性,分離or非分離
? int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
? 獲取線程屬性,分離or非分離
? int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate);
? 參數(shù):attr:已初始化的線程屬性
? detachstate:PTHREAD_CREATE_DETACHED(分離線程)PTHREAD_CREATE_JOINABLE(結(jié)合)
? 這里需要注意的一點(diǎn)是,如果設(shè)置一個(gè)線程為分離線程,而這個(gè)線程運(yùn)行又非常快,他很可能在pthread_create函數(shù)返回之前就終止了,他終止以后就可能將線程號(hào)和系統(tǒng)資源移交給其他的線程使用,這樣調(diào)用pthread_create的線程就得到了錯(cuò)誤的線程號(hào)。要避免這種情況可以采取一定的同步措施,最簡(jiǎn)單的方法之一是可以在被創(chuàng)建的線程里調(diào)用pthread_cond_timedwait函數(shù),讓這個(gè)線程等待一會(huì),留出足夠的時(shí)間讓函數(shù)pthread_create返回。設(shè)置一段等待時(shí)間,是在多線程編程里常用方法。但是注意不要使用諸如wait()之類的函數(shù),他們是使整個(gè)進(jìn)程睡眠,并不能解決同步線程的問(wèn)題。
線程的棧地址
? POSIX.1定義了兩個(gè)常量_POSIX_THREAD_ATTR_STACKADDR 和 _POSIX_THREAD_ATTR_STACKSIZE 檢測(cè)系統(tǒng)是否支持棧屬性。也可以給sysconf函數(shù)傳遞_SC_THREAD_ATTR_STACKADDR 或 _SC_THREAD_ATTR_STACKSIZE來(lái)進(jìn)行檢測(cè)。
? 當(dāng)進(jìn)程棧地址空間不夠用時(shí),指定新建線程使用由malloc分配的空間作為自己的棧空間。通過(guò)pthread_attr_setstack 和 pthread_attr_getstack 兩個(gè)函數(shù)分別設(shè)置和獲取線程的棧地址。
? int pthread_attr_setstack(pthread_attr_t *attr, void * stackaddt, size_t stacksize);成功:0;失敗:錯(cuò)誤號(hào);
? int pthread_attr_getstack(pthread_attr_t *attr, void **stackaddr, size_t *stacksize);成功:0;失敗錯(cuò)誤碼;
參數(shù):
 ? attr: 指向一個(gè)線程屬性的指針
 ? stackaddr:返回獲取的棧地址
 ? stacksize:返回獲取的棧大小。
線程的棧大小
? 當(dāng)系統(tǒng)中有很多線程時(shí),可能需要減少每個(gè)線程棧的默認(rèn)大小,防止進(jìn)程地址空間不夠用,當(dāng)線程調(diào)用的函數(shù)會(huì)分配很大的局部變量或者函數(shù)調(diào)用層次很深時(shí),可能需要增加線程棧默認(rèn)大小。
 ? 函數(shù) pthread_attr_getstacksize 和 pthread_attr_setstacksize 提供設(shè)置。
 ? int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize); 成功:0;失敗:錯(cuò)誤號(hào)
 ? int pthread_attr_getstacksize(pthread_attr_t *attr, size_t stacksize); 成功:0;失敗:錯(cuò)誤號(hào)
 ? 參數(shù):
 ? ? attr:指向一個(gè)線程屬性的指針
 ? ? stacksize:返回線程的堆棧大小
NPTL
1. 查看當(dāng)前 pthread 庫(kù)版本 `getconf GNU_LIBPTHREAD_VERSION` 2. NPTL 實(shí)現(xiàn)機(jī)制(POSIX),Native POSIX Thread Library 3. 使用線程庫(kù)時(shí)gcc指定 -lpthread線程注意事項(xiàng)
1. 主線程退出其他線程不退出,主線程應(yīng)調(diào)用 pthread_exit 2. 避免僵尸線程? ? ? pthread_join
 ? ? ? pthread_detach
 ? ? ? pthread_create指定分離屬性
 ? ? ? 被join線程可能在join函數(shù)返回前釋放完自己的所有內(nèi)存資源,所以不應(yīng)當(dāng)返回回收線程中的值;
 ? 3. malloc和mmap 申請(qǐng)的內(nèi)存可以被其他線程釋放
 ? 4. 應(yīng)避免在多線程模型中調(diào)用fork,除非馬上exec,子進(jìn)程中只有調(diào)用fork的線程存在,其他線程在子進(jìn)程中均為pthread_exit;
 ? 5. 信號(hào)的復(fù)雜語(yǔ)義很難和多線程共存,應(yīng)避免在多線程引入信號(hào)機(jī)制
線程同步
同步概念
? 所謂同步,即同時(shí)起步,協(xié)調(diào)一致。不同的對(duì)象,對(duì)同步的理解方式略有不同。如,設(shè)備同步,是指在兩個(gè)設(shè) 備之間規(guī)定一個(gè)同步的時(shí)間參考;數(shù)據(jù)庫(kù)同步,是指讓兩個(gè)或多個(gè)數(shù)據(jù)庫(kù)內(nèi)容保持一致,或者按照需要部分一致。
 ? 在編程和通信中所說(shuō)的同步,指協(xié)同、協(xié)助、互相配合。主旨在系統(tǒng)步調(diào),按預(yù)定的先后次序運(yùn)行。
線程同步
? 同步即協(xié)同步調(diào),按預(yù)定的先后次序運(yùn)行。
 ? 線程同步,指一個(gè)線程發(fā)出某一功能調(diào)用時(shí),在沒(méi)有得到結(jié)果之前,該調(diào)用不返回。同時(shí)其他線程為保證數(shù)據(jù)一致性,不能調(diào)用該功能。
 ? 競(jìng)爭(zhēng)方式訪問(wèn)共享資源。
 ? 產(chǎn)生的現(xiàn)象叫做“與時(shí)間有關(guān)的錯(cuò)誤”。為了避免這種數(shù)據(jù)混亂,線程需要同步。
 ? 同步的目的,是為了避免數(shù)據(jù)混亂,解決與時(shí)間有關(guān)的錯(cuò)誤。實(shí)際上,不僅線程間需要同步,進(jìn)程間、信號(hào)間都需要同步機(jī)制。
? 所有多個(gè)控制流,共同操作一個(gè)共享資源的情況,都需要同步
數(shù)據(jù)混亂原因
? 以上3點(diǎn)鐘,前兩點(diǎn)不能改變,想要提高效率,傳遞數(shù)據(jù),資源必須共享。只要共享資源,就一定會(huì)出現(xiàn)競(jìng)爭(zhēng)。只要存在競(jìng)爭(zhēng)關(guān)系,數(shù)據(jù)就很容易出現(xiàn)混亂。所以只能從第三點(diǎn)著手,使多個(gè)線程在訪問(wèn)共享資源的時(shí)候,出現(xiàn)互斥。
互斥量mutex(互斥鎖)
linux中提供一把互斥鎖mutex(也稱之為互斥量)。
 每個(gè)線程在對(duì)資源操作前都嘗試先加鎖,成功加鎖才能操作,操作結(jié)束解鎖。
主要應(yīng)用函數(shù)
? pthread_mutex_t 類型,其本質(zhì)是一個(gè)結(jié)構(gòu)體。為簡(jiǎn)化理解,可以忽略實(shí)現(xiàn)細(xì)節(jié),當(dāng)成整數(shù)看待。
 ? pthread_mutex_t mutex;變量mutex只有兩種取值1 、 0;
pthread_mutex_init
? 初始化一個(gè)互斥鎖(互斥量),初值可看做1
int pthread_mutex_int(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t * restrict attr);
參數(shù)1:傳出參數(shù),調(diào)用時(shí)應(yīng)傳&mutex
? restrict關(guān)鍵字:只用于限制指針,告訴編譯器,所有修改該指針指向內(nèi)存中的內(nèi)容的操作,只能通過(guò)本指針完成。不能通過(guò)除本指針以外的其他變量或指針修改。
參數(shù)2:互斥量屬性。是一個(gè)傳入?yún)?shù),通常傳NULL,選用默認(rèn)屬性(線程間共享)。
pthread_mutex_destroy
銷毀一個(gè)互斥鎖
 int pthread_mutex_destroy(pthread_mutex_t *mutex);
pthread_mutex_lock
加鎖。可理解為將mutex–(或者-1)
 int pthread_mutex_lock(pthread_mutex_t *mutex)
pthread_mutex_unlock
解鎖。可理解為將mutex++(或者+1)
 int pthread_mutex_unlock(pthread_mutex_t *mutex);
pthread_mutex_trylock
嘗試加鎖,不阻塞
 int pthread_mutex_trylock(pthread_mutex_t *mutex);
加鎖與解鎖
lock與unlock
? lock嘗試加鎖,如果加鎖不成功,線程阻塞,阻塞到持有該互斥量的其他線程解鎖為止。
 ? unlock主動(dòng)解鎖函數(shù),同時(shí)將阻塞該鎖上所有線程全部喚醒,至于哪個(gè)線程先被喚醒,取決于優(yōu)先級(jí)、調(diào)度。默認(rèn)先阻塞,先喚醒。
 ? 可假想mutex鎖init成功初值為1 。lock功能時(shí)將mutex–。unlock將mutex++
lock與trylock
? lock加鎖失敗會(huì)阻塞,等待鎖釋放
 ? trylock加鎖失敗直接返回錯(cuò)誤號(hào)(如,EBUSY),不阻塞。
加鎖步驟測(cè)試
#include<stdio.h> #include<stdlib.h> #include<pthread.h> #include<unistd.h>pthread_mutex_t mutex;void *tfn(void *arg){srand(time(NULL));while(1){pthread_mutex_lock(&mutex);printf("hello ");//模擬長(zhǎng)時(shí)間操作共享資源,導(dǎo)致cpu易主,產(chǎn)生與時(shí)間有關(guān)的錯(cuò)誤sleep(rand()%3);printf("world\n");pthread_mutex_unlock(&mutex);sleep(rand()%3);// pthread_mutex_unlock(&mutex);}return NULL; }int main(void){pthread_t tid;int flag = 5;srand(time(NULL));pthread_mutex_init(&mutex,NULL);//調(diào)用成功mutex = 1pthread_create(&tid, NULL, tfn, NULL);while(flag--){pthread_mutex_lock(&mutex);printf("HELLO ");sleep(rand()%3);printf("WORLD\n");pthread_mutex_unlock(&mutex);sleep(rand()%3);// pthread_mutex_unlock(&mutex);}pthread_cancel(tid);pthread_join(tid,NULL);pthread_mutex_destroy(&mutex);return 0; }//stdout 是共享資源線程在操作完共享資源后本應(yīng)該立即解鎖,但修改后,線程抱著鎖睡眠。睡醒解鎖后又立即加鎖,這兩個(gè)庫(kù)函數(shù)本身不會(huì)阻塞。
所以在這兩行代碼之間失去cpu的概率很小。因此,另外一個(gè)線程很難得到加鎖的機(jī)會(huì)
結(jié)論:在訪問(wèn)共享資源前加鎖,訪問(wèn)結(jié)束后立即解鎖。鎖的“粒度”應(yīng)越小越好。
死鎖
讀寫鎖
? 與互斥量類似,但讀寫鎖允許更高的并行性。其特征為:寫?yīng)氄?#xff0c;讀共享。
讀寫鎖狀態(tài)
讀寫鎖具備三種狀態(tài):
讀寫鎖特性
? 讀寫鎖也叫共享-獨(dú)占鎖。當(dāng)讀寫鎖以讀模式鎖住時(shí),他是以共享模式鎖住的;當(dāng)它以寫鎖模式鎖住時(shí),他是以獨(dú)占模式鎖住的。寫?yīng)氄?#xff0c;讀共享。
 ? 讀寫鎖非常適合對(duì)數(shù)據(jù)結(jié)構(gòu)讀的次數(shù)遠(yuǎn)大于寫的情況。
主要應(yīng)用函數(shù)
函數(shù)返回都是 成功0;失敗返回錯(cuò)誤號(hào)。
pthread_rwlock_t 類型 用于定義一個(gè)讀寫鎖變量
pthread_rwlock_t rwlock;
pthread_rwlock_init
? 初始化一把讀寫鎖
 int pthread_rwlock_init(pthread_rwlock_t * restrict rwlock, const pthread_rwlockattr_t *restrict attr);
 ? 參數(shù)2:attr表示讀寫鎖屬性,通常使用默認(rèn)屬性,傳NULL即可。
pthread_rwlock_destroy
? 銷毀一把鎖
 int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
pthread_rwlock_rdlock
? 加讀鎖
 int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
pthread_rwlock_wrlock
? 加寫鎖
 int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
pthread_rwlock_unlock
? 解鎖
 int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
pthread_rwlock_tryrdlock
? 嘗試加讀鎖,不阻塞
 int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
pthread_rwlock_trywrlock
? 嘗試加寫鎖,不阻塞
 int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
讀寫鎖實(shí)例
#include<stdio.h> #include<stdlib.h> #include<pthread.h> #include<unistd.h>int counter; pthread_rwlock_t rwlock;void *th_write(void *arg){int t;int i = (int)arg;while(1){t = counter;usleep(1000);pthread_rwlock_wrlock(&rwlock);printf("write %d:%lu counter=%d ++counter=%d\n",i,pthread_self(),t,++counter);pthread_rwlock_unlock(&rwlock);usleep(5000);}return NULL; }void * th_read(void *arg){int i = (int)arg;while (1){pthread_rwlock_rdlock(&rwlock);printf("--------------read %d:%lu:%d\n",i,pthread_self(),counter);pthread_rwlock_unlock(&rwlock);usleep(900);}return NULL; }int main(void){pthread_t tid[8];int i;pthread_rwlock_init(&rwlock,NULL);for ( i = 0; i < 3; i++){pthread_create(&tid[i],NULL,th_write,(void *)i);}for ( i = 0; i < 5; i++){pthread_create(&tid[i+3],NULL,th_read,(void *)i);}for ( i = 0; i < 8; i++){pthread_join(tid[i],NULL);}pthread_rwlock_destroy(&rwlock);return 0; }條件變量
? 條件變量本身不是鎖,但它可以造成線程阻塞。通常與互斥鎖配合使用,給多線程提供一個(gè)回合的場(chǎng)所(共享數(shù)據(jù))。
主要應(yīng)用函數(shù):
以下函數(shù)都是成功返回0,失敗直接返回錯(cuò)誤號(hào)。
pthread_cond_t類型 用于定義條件變量。
pthread_cond_t cond;
pthread_cond_init
初始化條件變量
 int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
pthread_cond_destroy
銷毀條件變量
 int pthread_cond_destroy(pthread_cond_t *cond);
pthread_cond_wait
阻塞等待一個(gè)條件變量
 int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t * restrict mutex);
函數(shù)作用
兩步為一個(gè)原子操作。
pthread_cond_timedwait
限時(shí)等待一個(gè)條件變量
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutext_t *restrict mutex, const struct timespec *restrict abstime);
參數(shù)3:
struct timespec{time_t tv_sec;//秒long tv_nsec;//納秒 }形參 abstime:絕對(duì)時(shí)間。
如:time(NULL)返回的就是絕對(duì)時(shí)間。而alarm(1)是相對(duì)時(shí)間,相對(duì)當(dāng)前時(shí)間定時(shí)1秒鐘。
 struct timespec t = {1,0};
 pthread_cond_timedwait(&cond, &mutex, &t);只能定時(shí)到1970年1月1日00:00:01秒
正確用戶:
time_t cur=time(NULL); //獲取當(dāng)前時(shí)間。 struct timespec t; //定義timespec結(jié)構(gòu)體變量t t.tv_sec = cur+1; //定時(shí)1秒 pthread_cond_timedwait(&cond, &mutex, &t);還有另外一個(gè)時(shí)間類型
struct timeval{time_t tv_sec;//秒susecond_t tv_usec;//微妙 }pthread_cond_signal
喚醒至少一個(gè)阻塞在條件變量上的線程
int pthread_cond_signal(pthread_cond_t *cond);
pthread_cond_broadcast
喚醒所有阻塞在條件變量上的線程
int pthread_cond_broadcast(pthread_cond_t * cond);
生產(chǎn)者消費(fèi)者條件變量模型
? 線程同步典型的案例即為生產(chǎn)者消費(fèi)者模型,而借助條件變量來(lái)實(shí)現(xiàn)這一模型,是比較常見(jiàn)的一種方法。假定有兩個(gè)線程,一個(gè)模擬生產(chǎn)者行為,一個(gè)模擬消費(fèi)者行為。兩個(gè)線程同時(shí)操作一個(gè)共享資源(一般稱之為匯聚),生產(chǎn)向其中添加產(chǎn)品,消費(fèi)者從中消費(fèi)掉產(chǎn)品。
#include<stdio.h> #include<stdlib.h> #include<pthread.h> #include<unistd.h>struct msg{struct msg*next;int num; };struct msg* head; struct msg* mp;//靜態(tài)初始化 一個(gè)條件變量和一個(gè)互斥量 pthread_cond_t has_product = PTHREAD_COND_INITIALIZER; pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;void *consumer(void *p) {for(;;){pthread_mutex_lock(&lock);while(head==NULL){//頭指針為空,說(shuō)明沒(méi)有節(jié)點(diǎn)printf("start wait\n");pthread_cond_wait(&has_product, &lock);printf("begin gon\n");}mp = head;head = mp->next;//模擬消費(fèi)掉一個(gè)產(chǎn)品printf("consume----%d\n",mp->num);free(mp);pthread_mutex_unlock(&lock);sleep(rand()%5);} }void *producer(void *p){for(;;){pthread_mutex_lock(&lock);mp = malloc(sizeof(struct msg));mp->num = rand()%1000 + 1;//模擬生產(chǎn)一個(gè)產(chǎn)品printf("produce ----%d\n",mp->num);mp->next = head;head = mp;pthread_mutex_unlock(&lock);pthread_cond_signal(&has_product);sleep(rand()%5);} }int main(void){pthread_t pid,cid;srand(time(NULL));pthread_create(&pid, NULL, producer, NULL);pthread_create(&cid, NULL, consumer, NULL);pthread_join(pid,NULL);pthread_join(cid,NULL);return 0; }//stdout 是共享資源? 在head為空時(shí),pthread_cond_wait 解鎖,阻塞;當(dāng)head有值時(shí),生產(chǎn)線程發(fā)出pthread_cond_signal pthread_cond_wait 喚醒加鎖,向后執(zhí)行。
? 執(zhí)行截圖:
 
條件變量的優(yōu)點(diǎn)
? 相較于mutex而言,條件變量可以減少競(jìng)爭(zhēng)。
? 如果直接使用mutex,除了生產(chǎn)者、消費(fèi)者之間要競(jìng)爭(zhēng)互斥量以外,消費(fèi)者之間也需要競(jìng)爭(zhēng)互斥量,但如果匯聚(鏈表)中沒(méi)有數(shù)據(jù),消費(fèi)者之間競(jìng)爭(zhēng)互斥鎖是無(wú)意義的。有了條件變量機(jī)制以后,只有生產(chǎn)者完成生產(chǎn),才會(huì)引起消費(fèi)者之間的競(jìng)爭(zhēng)。提升了程序效率。
信號(hào)量
進(jìn)化版互斥鎖
? 由于互斥鎖粒度比較大,如果我們希望在多個(gè)線程間對(duì)某一對(duì)象的部分?jǐn)?shù)據(jù)進(jìn)行共享,使用互斥鎖是沒(méi)有辦法實(shí)現(xiàn)的,只能將整個(gè)數(shù)據(jù)對(duì)象鎖住。這樣雖然達(dá)到了多線程操作共享數(shù)據(jù)時(shí)保證數(shù)據(jù)正確性的目的,卻在無(wú)形中導(dǎo)致線程的并發(fā)性下降。線程從并行執(zhí)行,變成了串行執(zhí)行。與直接使用單進(jìn)程無(wú)異。
? 信號(hào)量,是相對(duì)折中的一種處理方式,既能保證同步,數(shù)據(jù)不混亂,又能提高線程并發(fā)。
主要應(yīng)用函數(shù)
成功返回0,失敗返回-1,同時(shí)設(shè)置errno
sem_t 類型,本質(zhì)仍是結(jié)構(gòu)體。但應(yīng)用期間可簡(jiǎn)單看作為整數(shù),忽略實(shí)現(xiàn)細(xì)節(jié)(類似于使用文件描述符)
sem_t sem; 規(guī)定信號(hào)量sem不能小于0,頭文件<semaphore.h>
信號(hào)量基本操作
sem_init
int sem_init(sem_t *sem, int pshared, unsigned int value);
參數(shù):
sem:信號(hào)量
pshared:是否允許進(jìn)程間同步;取0用于線程間;取非0用于進(jìn)程間
value:指定信號(hào)量初值,最大線程數(shù)
sem_destroy
int sem_destroy(sem_t *sem);
銷毀信號(hào)量
sem_wait
int sem_wait(sem_t *sem);
加鎖,類比pthread_mutex_lock
sem_post
int sem_post(sem_t *sem);
將信號(hào)量++,同時(shí)喚醒阻塞在信號(hào)量上的線程,類比pthread_mutex_unlock
但是,由于sem_t的實(shí)現(xiàn)對(duì)用戶隱藏,所以所謂的++、–操作只能通過(guò)函數(shù)來(lái)實(shí)現(xiàn),而不能直接++、–符號(hào)。
信號(hào)量的初值,決定了占用信號(hào)量的線程的個(gè)數(shù)。
sem_trywait
int sem_trywait(sem_t *sem);
嘗試對(duì)信號(hào)量加鎖–(與sem_wait的區(qū)別類比lock和trylock)
sem_timedwait
生產(chǎn)者消費(fèi)者信號(hào)量模型
#include<stdio.h> #include<stdlib.h> #include<pthread.h> #include<unistd.h> #include<semaphore.h>#define NUM 5int queue[NUM]; //全局?jǐn)?shù)組實(shí)現(xiàn)環(huán)形隊(duì)列 sem_t blank_number,product_number;//空格信號(hào)量,產(chǎn)品信號(hào)量void *consumer(void *p) {int i = 0;for(;;){sem_wait(&product_number); //消費(fèi)者將產(chǎn)品--,為0則阻塞等待printf("-----donsume-----%d\n",queue[i]);queue[i]=0; //模擬消費(fèi)一個(gè)產(chǎn)品sem_post(&blank_number); //將空格數(shù)量++i = (i+1)%NUM; // 借助下標(biāo)實(shí)現(xiàn)環(huán)形sleep(rand()%3);} }void *producer(void *p){int i = 0;for(;;){sem_wait(&blank_number); //生產(chǎn)者將空格--,為0則阻塞等待queue[i] = rand()%1000 + 1; //模擬生產(chǎn)一個(gè)產(chǎn)品printf("-----Produce-----%d\n",queue[i]);sem_post(&product_number); //將產(chǎn)品數(shù)量++i = (i+1)%NUM; // 借助下標(biāo)實(shí)現(xiàn)環(huán)形sleep(rand()%1);} }int main(void){pthread_t pid,cid;srand(time(NULL));sem_init(&blank_number,0,NUM);sem_init(&product_number,0,0);pthread_create(&pid, NULL, producer, NULL);pthread_create(&cid, NULL, consumer, NULL);pthread_join(pid,NULL);pthread_join(cid,NULL);sem_destroy(&blank_number);sem_destroy(&product_number);return 0; }//stdout 是共享資源進(jìn)程間同步
互斥量mutex
? 進(jìn)程間也可以使用互斥鎖,來(lái)達(dá)到同步的目的。但應(yīng)在pthread_mutex_init初始化之前,修改其屬性為進(jìn)程間共享。mutex的屬性修改函數(shù)主要有以下幾個(gè)。
主要應(yīng)用函數(shù)
pthread_mutexattr_t mattr;//用于定義mutex鎖的屬性 int pthread_mutexattr_init(pthread_mutexattr_t *attr);//初始化一個(gè)mutex屬性對(duì)象 int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);//銷毀mutex屬性 對(duì)象 int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared);//修改mutex屬性 /* pshared取值:線程鎖:PTHREAD_PROCESS_PRIVATE(mutex的默認(rèn)屬性即為線程鎖,進(jìn)程間私有)進(jìn)程鎖:PTHREAD_PROCESS_SHARED */進(jìn)程間mutex實(shí)例
#include<stdio.h> #include<stdlib.h> #include<pthread.h> #include<unistd.h> #include<fcntl.h> #include<string.h> #include<sys/mman.h> #include<sys/wait.h>struct mt{int num;pthread_mutex_t mutex;pthread_mutexattr_t mutexattr; };int main(void){int i;struct mt *mm;pid_t pid; /*int fd = open("mttest",O_CREAT | O_RDWR,0777);ftruncate(fd, sizeof(*mm));mm = mmap(NULL,sizeof(*mm),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);close(fd);unlink("mttest"); */mm = mmap(NULL, sizeof(*mm),PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1,0);memset(mm, 0, sizeof(*mm));pthread_mutexattr_init(&mm->mutexattr);pthread_mutexattr_setpshared(&mm->mutexattr,PTHREAD_PROCESS_SHARED);pthread_mutex_init(&mm->mutex,&mm->mutexattr);pid = fork();if(pid == 0){for(i = 0;i<10;i++){pthread_mutex_lock(&mm->mutex);(mm->num)++;printf("child--------------------num++ %d\n",mm->num);pthread_mutex_unlock(&mm->mutex);sleep(1);}}else if(pid > 0){for(i = 0; i < 10 ; i++){sleep(1);pthread_mutex_lock(&mm->mutex);mm->num +=2;printf("father ------------------num+=2 %d\n",mm->num);pthread_mutex_unlock(&mm->mutex);}wait(NULL);}pthread_mutexattr_destroy(&mm->mutexattr);pthread_mutex_destroy(&mm->mutex);munmap(mm,sizeof(*mm)); //釋放映射區(qū)return 0; }文件鎖
? 借助fcntl函數(shù)來(lái)實(shí)現(xiàn)鎖機(jī)制。操作文件的進(jìn)程沒(méi)有獲得鎖時(shí),可以打開,但無(wú)法執(zhí)行read、write操作。
fcntl函數(shù):獲取、設(shè)置文件訪問(wèn)控制屬性。
int fcntl(int fd, int cmd,../ * arg * /);
參數(shù)2:
? F_SETLK(struct flock*); 設(shè)置文件鎖(trylock)
? F_SETLKW(struct flock*) 設(shè)置文件鎖(lock)w–>wait
? F_GETLK(struct flock*); 獲取文件鎖
參數(shù)3:
struct flock{short l_type; //鎖的類型:F_RDLCK F_WRLCK F_UNLCKshort l_whence; //偏移位置:SEEK_SET SEEK_CUR SEEK_ENDoff_t l_start; //起始偏移:1000off_t l_len; //長(zhǎng)度:0表示整個(gè)文件加鎖pid_t l_pid; //持有該鎖的進(jìn)程ID:(f_getlk only) }進(jìn)程間文件鎖實(shí)例
#include<stdio.h> #include<stdlib.h> #include<pthread.h> #include<unistd.h> #include<fcntl.h> #include<string.h> #include<sys/stat.h> #include<sys/types.h>void sys_err(char *str){perror(str);exit(1); };int main(int argc, char *argv[]){int fd;struct flock f_lock;if(argc < 2){printf("./a.out filename\n");exit(1);}if((fd = open(argv[1],O_RDWR))<0)sys_err("open");f_lock.l_type = F_WRLCK; //寫鎖// f_lock.l_type = F_RDLCK; //讀鎖f_lock.l_whence = SEEK_SET; //開始位置f_lock.l_start = 0;f_lock.l_len = 0; //整個(gè)文件加鎖fcntl(fd, F_SETLKW, &f_lock);printf("set lock\n");sleep(10);f_lock.l_type = F_UNLCK; //解鎖fcntl(fd, F_SETLKW, &f_lock);printf("unlock\n");close(fd);return 0; }打開兩個(gè)終端,同時(shí)操作相同的文件,可以看到第一個(gè)終端打印unlock后,第二個(gè)終端菜打印set lock。
? 多線程共享文件描述符,而給文件加鎖,是通過(guò)修改文件描述符所指向的文件結(jié)構(gòu)體中的成員變量來(lái)實(shí)現(xiàn)的。因此,多線程中無(wú)法使用文件鎖。
其他
在 vi 中對(duì)應(yīng)的函數(shù) shift + k,進(jìn)入man文檔。
ps aux查看進(jìn)程信息
ps ajx查看進(jìn)程信息,包括組ID
雙向半雙工(像微信),雙向全雙工(像電話)
? 段錯(cuò)誤追蹤,使用-g方式編譯文件,gdb方式運(yùn)行調(diào)試程序,直接run,程序停留的地方就是方式錯(cuò)誤的位置。(strcpy,沒(méi)有寫權(quán)限時(shí)會(huì)產(chǎn)生段錯(cuò)誤).
strace ./a.out查看可執(zhí)行程序,執(zhí)行過(guò)程中使用的系統(tǒng)調(diào)用。
ls > out輸出重定向,輸入內(nèi)容寫入到out文件中。
cat < t.c輸入重定向
文件存儲(chǔ)
? inode屬性,存放文件 存儲(chǔ)信息,包括大小、權(quán)限、類型、所有者、文件存儲(chǔ)指針地址(指向磁盤存儲(chǔ)的位置)。
? denty 目錄項(xiàng),包括文件名,inode編號(hào)。
? 每次創(chuàng)建一個(gè)硬鏈接,則會(huì)創(chuàng)建一個(gè)denty,這些denty里的inode編號(hào)相同,指向同一個(gè)inode節(jié)點(diǎn);
UNIX
1969 unix 肯湯姆森
? 商業(yè):IBM、APPLE、惠普、sun
 ? linux:BSD–freeBSD、紅帽、debain-ubuntu
優(yōu)化
從io入口,不打印到終端。
總結(jié)
以上是生活随笔為你收集整理的linux学习笔记 -- 系统编程的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
 
                            
                        - 上一篇: 产品经理需要掌握的十大知识模块
- 下一篇: 动态可视化十大排序算法之冒泡排序
