進程與線程(三)
- 進程間通信
- 傳統間的進程間通信機制
- 無名管道
- 無名管道的特征
- 無名管道的創建
- 父子進程通信
- 測試管道的大小
- 管道讀寫易出現的問題
- 有名管道
- 創建有名管道
- 有名管道的寫端代碼
- 有名管道的讀端代碼
- 信號
- 信號的特征
- 產生信號
- 硬件來源
- 軟件來源
- 發送信號的函數:kill() raise()
- 定時器信號
- 響應信號
- 信號安裝:
- 忽略信號
- 執行缺省操作
- 捕捉信號
進程間通信
常用的進程間通信方式
1、傳統的進程間通信方式
無名管道(pipe)、有名管道(fifo)和信號(signal)
2、System V IPC對象
共享內存(share memory)、消息隊列(message queue)和信號燈(semaphore)
3、BSD
套接字(socket)
傳統間的進程間通信機制
無名管道
無名管道的特征
這里所說的管道主要指無名管道,它具有如下特點:
1、只能用于具有親緣關系的進程之間的通信
2、半雙工的通信模式,具有固定的讀端和寫端
3、管道可以看成是一種特殊的文件,對于它的讀寫可以使用文件IO如read、write函數。
無名管道的創建
父子進程通信
(1)創建無名管道
(2)創建子進程
(3)完成父子通信業務
通信流程:子進程發消息給父進程
思路:fd[0]讀端 fd[1]寫端 ----》固定的
子進程:發消息(fd[1]有效) fd[0]關閉掉
父進程:收消息(fd[0]有效) fd[1]關閉掉
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
int main(int argc, const char *argv[])
{//1,創建無名管道//定義數組,存儲讀寫的文件描述符int fd[2];if(pipe(fd) < 0){perror("pipe error");return -1;}printf("pipe ok!\n");//2,創建子進程pid_t pid = fork();if(pid < 0){perror("fork error");close(fd[0]);close(fd[1]);return -1;}else if(0 == pid){//子進程的地址空間//關閉讀端close(fd[0]);//發消息(write)//定義消息文本char Message[100] = {0};printf("請輸入需要發送的消息:");fgets(Message, sizeof(Message), stdin);//使用write寫入到管道中write(fd[1], Message, strlen(Message));printf("send ok!\n");}else{//父進程的地址空間//關閉寫端close(fd[1]);//收消息(read)//定義接收消息的地址空間char Message[100] = {0};read(fd[0], Message, sizeof(Message));printf("來自于子進程:%s\n",Message);}return 0;
}
測試管道的大小
思路:往管道中不管寫入,直到寫不進去,此時寫入的字節數就是管道容量大小
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
int main()
{//測試管道大小//創建管道int fd[2];if(pipe(fd) < 0){return -1;}printf("create pipe ok!\n");//寫入數據,不斷寫入//1KB == 1024bytechar buf[1024] = {0};int size = 0;while(1){int wr_count = write(fd[1], buf, 1024);//每次寫入1024個字節 即:1KB大小if(wr_count < 0){perror("write error");break;}else if(0 == wr_count){printf("nothing wai be written...\n");break;}else{size++;printf("%dKB\n",size);}}close(fd[0]);close(fd[1]);return 0;
}
管道讀寫易出現的問題
當管道中無數據時,讀操作會阻塞
向管道中寫入數據時,linux將不保證寫入的原子性,管道緩沖區一有空閑區域,寫進程就會試圖向管道寫入數據。如果讀進程不讀走管道緩沖區中的數據,那么寫操作將會一直阻塞。
只有在管道的讀端存在時,向管道中寫入數據才有意義。否則,向管道中寫入數據的進程將收到內核傳來的SIGPIPE信號(通常Broken pipe錯誤)。
案例:驗證管道斷裂
#include <stdio.h>
#include <sys/types.h>
#inckude <unistd.h>
#include <string.h>
int main()
{int fd[2];if(pipe(fd) < 0){return -1;}printf("pipe ok!\n");//關閉讀端 (保證寫入管道時,讀端不存在---》才會出現管道斷裂)close(fd[0]);pid_t pid = fork();if(pid < 0){return -1;}else if(0 == pid){//子進程//寫入數據到管道char data[1024];printf("Please input:");fgets(data, sizeof(data), stdin);write(fd[1], data, strlen(data));printf("寫入管道成功!\n");exit(100);}else{//父進程//使用wait阻塞等待子進程退出時回收其資源int wstatus;//保存子進程的退出狀態值wait(&wstatus);//當執行該40行時,說明子進程已經退出(收到信號異常退出還是正常執行exit(100)退出?)//分類討論if(WIFSIGNALED(wstatus)){//收到信號導致子進程退出printf("導致子進程退出的信號是:%d\n",WTERMSIG(wstatus));//SIGPIPE==13號}else if(WIFEXITED(wstatus)){//正常退出---》執行了exit(100)printf("子進程的退出狀態數值為:%d\n",WEXITSTATUS(wstatus));//父進程char buf[1024] = {0};read(fd[0], buf, sizeof(buf));printf("讀取的結果是:%s\n",buf);}}return 0;
}
有名管道
無名管道只能用于具有親緣關系的進程之間,這就限制了無名管道的使用范圍
1、有名管道可以使互不相關的兩個進程互相通信。
2、有名管道可以通過路徑名來指出,并且在文件系統中可見
3、進程通過文件IO來操作有名管道
4、有名管道遵循先進先出規則,不支持如lseek() 操作
創建有名管道
有名管道的寫端代碼
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{//創建有名管道(同時也創建好了管道文件)if(mkfifo("./fifo.txt", 0664) < 0 && errno != EEXIST){perror("mkfifo error");return -1;}printf("mkfifo ok!\n");//以只寫方式打開創建好的管道文件int fw = open("./fifo.txt", O_WRONLY);if(fw < 0){perror("open_write error");return -1;}printf("open_write ok!\n");char buf[1024] = {0};while(1){//對于buf做清空bzero(buf, sizeof(buf));printf("客戶端:");fgets(buf, sizeof(buf), stdin);//判斷客戶端是否辦理完畢業務if(0 == strncasecmp("quit", buf, 4)){printf("業務辦理結束!\n");break;}//發送消息int wr_count = write(fw, buf, strlen(buf));if(wr_count < 0){perror("write error");break;}else if(0 == wr_count){printf("未寫入任何內容...\n");break;}else{printf("實際發送%d字節內容!\n",wr_count);printf("send ok!\n");}}//關閉寫端close(fw);return 0;
}
有名管道的讀端代碼
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{//創建有名管道(同時也創建好了管道文件)if(mkfifo("./fifo.txt", 0664) < 0 && errno != EEXIST){perror("mkfifo error");return -1;}printf("mkfifo ok!\n");//以只讀方式打開創建好的管道文件int fr = open("./fifo.txt", O_RDONLY);if(fr < 0){perror("open_read error");return -1;}printf("open_read ok!\n");char buf[1024] = {0};while(1){//對于buf做清空bzero(buf, sizeof(buf));//接收消息int rd_count = read(fr, buf, sizeof(buf));if(rd_count < 0){perror("read error");break;}else if(0 == rd_count){printf("客戶端業務處理結束!\n");break;}else{printf("成功接收%d個字節!\n",rd_count);printf("來自于客戶端:%s\n",buf);}}close(fr);return 0;
}
信號
信號的特征
信號是在軟件層次上對中斷機制的一種模擬,是一種異步通信方式
信號可以直接進行用戶空間進程和內核進程之間的交互,內核進程也可以利用它來通知用戶空間進程發生了哪些系統事件。
如果該進程當前并未處于執行態,則該信號就由內核保存起來,直到該進程恢復執行再傳遞給它;如果一個信號被進程設置為阻塞,則該信號的傳遞被延遲,直到其阻塞被取消時才被傳遞給進程
同步:發送方發送數據,等待接收方響應之后才發下一個數據包的通訊方式
異步:發送方發送數據,不等待接收方發回響應,接著發送下一個數據包的通訊方式
產生信號
硬件來源
從鍵盤輸入:
ctrl c : SIGINT信號 ,默認處理方式為進程終止
ctrl : SIGQUIT信號,作用與SIGINT類似,默認處理方式為進程終止
ctrl z: SIGTSTP信號,默認處理方式為進程暫停
SIGSTOP信號:只能通過kill產生,默認處理方式進程暫停
軟件來源
發送信號的函數:kill() raise()
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
功能:不僅可以終止進程,也可以向進程發送其他信號
#include <signal.h>
int raise(int sig);
功能:只允許進程向自身發送信號
案例:首先使用fork創建子進程,在子進程中使用raise()向自身發送SIGSTOP信號,使子進程暫停;接下來在父進程中當時間戳超過10秒之后,調用kill()向子進程發送SIGKILL信號殺死子進程并回收子進程的退出
資源,且檢查導致子進程退出是否因為收到了信號,如果是,打印出該信號是幾號?如果子進程是正常退出,則打印出子進程退出的狀態值是多少?(模擬正常退出:例如在終端給子進程發送SIGCONT信號來喚醒子進程,此時子進程繼續執行,直到執行了exit()退出)
#include <stdio.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/wait.h>
int main(int argc, const char *argv[])
{//功能:練習kill()和raise()pid_t pid = fork();if(0 == pid){//子進程printf("child PID = %d\tparent PID = %d\n", \getpid(), getppid());//使用raise()函數給子進程自己發送SIGSTOP,讓其暫停raise(SIGSTOP);printf("I am wakeup...\n");printf("hahahah\n");exit(88);}else{//父進程//定義保存時間的變量time_t start_time, current_time;time(&start_time);int wstatus;//回收子進程退出資源while(1){//獲取當前的系統時間(更新的)time(¤t_time);if(current_time - start_time > 10){//使用kill()函數給子進程發送SIGKILL信號,讓其終止kill(pid, SIGKILL);}int exitID = waitpid(-1, &wstatus, WNOHANG);if(exitID < 0){perror("waitpid error");break;}else if(0 == exitID){printf("子進程還未結束...\n");}else{//已成功回收子進程printf("已成功回收子進程的退出資源...\n");if(WIFSIGNALED(wstatus)){printf("異常退出,造成子進程退出的信號是:%d號信號!\n",WTERMSIG(wstatus));}else if(WIFEXITED(wstatus)){printf("子進程正常退出,退出狀態數值為:%d\n",WEXITSTATUS(wstatus));}break;}//每隔1秒 輪詢一次sleep(1);}}return 0;
}
定時器信號
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
參數:秒數
當定時器指定的時間到了時,它就向進程發送SIGALARM信號,信號的默認操作是結束進程.
注意:每個進程只能有一個鬧鐘時間。如果在調用alarm時,以前已為該進程設置過鬧鐘時間,而且它還沒有超時,則該鬧鐘時間的剩余時間值作為本次alarm函數調用的值返回,以前登記的鬧鐘時間則被新值代換。如果有以前登記的尚未超過的鬧鐘時間,而新設的鬧鐘時間值為0,則取消以前的鬧鐘時間,其剩余時間值仍作為函數的返回值。
擴充:將調用進程掛起函數:
#include <unistd.h>
int pause(void);
功能:會造成進程主動掛起(處于阻塞狀態,并主動放棄CPU),并且等待信號將其喚醒
案例:使用alarm()和pause()函數
#include <stdio.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{printf("hello!\n");//設定鬧鐘 5秒alarm(5);printf("I will.....\n");//進程掛起pause();printf("java!\n");printf("exit!\n");return 0;
}
響應信號
信號安裝:
signal函數原型:
#include <signal.h>
void (*signal(int signum, void (*handler)(int)))(int);
優化原型:看的更清楚。
//給函數指針類型取別名
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
功能:進行信號安裝
參數:
參數1:需要安裝的信號名稱
參數2:對于該信號的處理函數(該函數是用戶自定義的,且返回值類型void ,形參是一個
int的函數的地址)
PS:參數2的位置可以傳入:忽略(SIG_IGN) 執行缺省操作(SIG_DFL),信號處理函數的地址
返回值:成功安裝完畢的信號處理函數的地址。
信號的處理方式可以有:
1、忽略信號
2、執行缺省操作(默認操作)
3、捕捉信號(自定義信號處理函數)
忽略信號
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{//安裝信號signal(SIGINT, SIG_IGN);//忽略信號//執行一段代碼while(1){printf("hello!\n");sleep(1);}return 0;
}
執行缺省操作
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{//安裝信號//signal(SIGINT, SIG_IGN);//忽略信號signal(SIGINT, SIG_DFL);//缺省操作(信號默認處理方式)//執行一段代碼while(1){printf("hello!\n");sleep(1);}return 0;
}
捕捉信號
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void handler_func(int signum)
{printf("不處理哦!\n");
}
int main(int argc, const char *argv[])
{//安裝信號//signal(SIGINT, SIG_IGN);//忽略信號//signal(SIGINT, SIG_DFL);//缺省操作(信號默認處理方式)signal(SIGINT, &handler_func);//執行一段代碼vwhile(1){printf("hello!\n");sleep(1);}return 0;
}
注意:一個信號處理函數響應多個信號
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void handler_func(int signum)
{//按照信號的類別進行控制switch(signum){case SIGINT:{printf("paly music...\n");break;}case SIGTSTP:{printf("paly vedio...\n");break;}case SIGQUIT:{printf("quit...\n");break;}default:{printf("信號無法響應...\n");}}return;
}
int main(int argc, const char *argv[])
{//安裝信號//signal(SIGINT, SIG_IGN);//忽略信號//signal(SIGINT, SIG_DFL);//缺省操作(信號默認處理方式)//signal(SIGINT, &handler_func);//捕捉信號--》自定義信號處理函數//安裝3個信號signal(SIGINT, &handler_func);//播放音樂signal(SIGTSTP, &handler_func);//播放視頻signal(SIGQUIT, &handler_func);//退出app//執行一段代碼while(1){printf("hello!\n");sleep(1);}return 0;
}