什么是進(jìn)程間通信,就是進(jìn)程與進(jìn)程之間進(jìn)行通信,互相發(fā)送消息;可以通過(guò) 信號(hào) 或者 管道 或者 消息隊(duì)列 或者 信號(hào)量 去通信!
目錄
一、信號(hào)
1. 信號(hào)簡(jiǎn)介?
2. 都有那些信號(hào)?
3. 注冊(cè)信號(hào)的函數(shù)
1). signal
2). sigaction (項(xiàng)目中強(qiáng)烈推薦使用)
4. 信號(hào)發(fā)送
1). kill 函數(shù)
2). alarm 函數(shù)
3). raise 函數(shù)
5. 發(fā)送多個(gè)信號(hào)的情況
6. 信號(hào)集
1). sigemptyset
2). sigfillset
3). sigdelset
4). sigaddset
5). sigismember
7.?進(jìn)程的“信號(hào)屏蔽字”
1).?修改進(jìn)程的“信號(hào)屏蔽字”
2).?獲取未處理的信號(hào)
8.?阻塞式等待信號(hào)
1). pause
2). sigsuspend
二、管道
1. 管道簡(jiǎn)介?
2. 管道的創(chuàng)建
1). pipe
2). 例一?:多進(jìn)程使用管道通信
3). 例二:關(guān)閉管道的讀端/寫端
4). 例三:父進(jìn)程循環(huán)給子進(jìn)程發(fā)送消息
3. popen / pclose
1). popen
2). pclose
3). 例一:讀取命令返回的數(shù)據(jù)
4). 例二:把輸出寫到外部程序
5). popen的優(yōu)缺點(diǎn)
三、消息隊(duì)列
1. 什么是消息隊(duì)列?
2.?msgget 消息隊(duì)列的獲取
3.?msgsnd 消息的發(fā)送
4.?msgrcv 消息的接收
5.?msgctl 消息的控制
6. 示例一:兩程序間通過(guò)消息隊(duì)列去通信
7. 多進(jìn)程間消息隊(duì)列通信
8.?練習(xí)
四、信號(hào)量
1. 什么是信號(hào)量
2. semget 信號(hào)量的獲取
3. semop 信號(hào)量的操作
4. semctl 信號(hào)量的控制
5. 例
五、共享內(nèi)存機(jī)制
六、總結(jié)
一、信號(hào)
1. 信號(hào)簡(jiǎn)介?
什么是信號(hào)?
信號(hào)是給程序提供一種可以處理異步事件的方法,它利用軟件中斷來(lái)實(shí)現(xiàn)。不能自定義信號(hào),所有信號(hào)都是系統(tǒng)預(yù)定義的。
信號(hào)由誰(shuí)產(chǎn)生?
1.?由shell終端根據(jù)當(dāng)前發(fā)生的錯(cuò)誤(段錯(cuò)誤、非法指令等)Ctrl+c而產(chǎn)生相應(yīng)的信號(hào)
?? ??比如:
?? ??????? ?socket通信或者管道通信,如果讀端都已經(jīng)關(guān)閉,執(zhí)行寫操作(或者發(fā)送數(shù)據(jù)),
?? ??????? ?將導(dǎo)致執(zhí)行寫操作的進(jìn)程收到SIGPIPE信號(hào)(表示管道破裂);
????????????該信號(hào)的默認(rèn)行為:終止該進(jìn)程。
?? ??????? ????????????
2. 在shell終端,使用kill或killall命令產(chǎn)生信號(hào)
例:注冊(cè)SIGINT信號(hào),也就是鍵盤按下ctrl+c后觸發(fā)的信號(hào),讓其執(zhí)行我們代碼中自己定義的函數(shù)!
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>
// 自定義信號(hào)處理函數(shù),其有固定格式,返回值:void; 參數(shù) int
void myHandle(int sig) {
printf("catch a signal : %d\n", sig);
}
int main(int argc, char **argv) {
// 注冊(cè)SIGINT信號(hào)(ctrl+c),接收到此信號(hào)執(zhí)行myHandle方法
sighandler_t result = signal(SIGINT, myHandle);
if (SIG_ERR == result) {
printf("發(fā)生錯(cuò)誤了!\n");
perror("錯(cuò)誤原因:");
exit(-1);
}
while (1) sleep(1);
return 0;
}
當(dāng)然,也可以在另一個(gè)終端使用kill命令去發(fā)送信號(hào):?kill -SIGINT 進(jìn)程id
2. 都有那些信號(hào)?
信號(hào)名稱 | 說(shuō)明 |
---|---|
SIGABORT | 進(jìn)程異常終止 |
SIGALRM | 超時(shí)警告 |
SIGFPE | 浮點(diǎn)運(yùn)算異常 |
SIGHUP | 連接掛斷 |
SIGILL | 非法指令 |
SIGINT | 終端中斷(Ctrl+C 將產(chǎn)生該信號(hào)) |
SIGKILL | 終止進(jìn)程 |
SIGPIPE | 向沒(méi)有讀權(quán)限的進(jìn)程的管道寫數(shù)據(jù) |
SIGQUIT | 終端退出(Ctrl+\ 將產(chǎn)生該信號(hào)) |
SIGSEGV | 無(wú)效內(nèi)存段訪問(wèn) |
SIGTERM | 終止 |
SIGUSR1 | 用戶自定義信號(hào)1 |
SIGUSR2 | 用戶自定義信號(hào)2 |
---華麗的分割線--- | 在此以上的信號(hào)如果不被捕獲,則進(jìn)程自己接收到后都會(huì)終止; |
SIGCHLD | 子進(jìn)程已停止或退出 |
SIGCONT | 讓暫停的進(jìn)程繼續(xù)執(zhí)行 |
SIGSTOP | 停止執(zhí)行(即“暫?!?/td> |
SIGTTIN | 后臺(tái)進(jìn)程嘗試讀操作 |
SIGTTOU | 后臺(tái)進(jìn)程嘗試寫操作 |
?信號(hào)的處理
? ? ? ? 1. 忽略此信號(hào),signal(SIGINT, SIG_IGN);? ? ? ? // 忽略SIGINT(ctrl+c)信號(hào)
? ? ? ? 2. 捕捉信號(hào),指定的信號(hào)處理函數(shù)進(jìn)行處理,向上面??代碼那樣,注冊(cè)了之后,會(huì)自動(dòng)捕獲!
? ? ? ? 3. 執(zhí)行系統(tǒng)默認(rèn)動(dòng)作,即不用捕捉它,讓系統(tǒng)自己處理,大多數(shù)都是終止進(jìn)程的信號(hào);
信號(hào)的捕獲
? ? ? ? 信號(hào)的捕獲,是指,接收到某種信號(hào)后,去執(zhí)行指定的函數(shù);
? ? ? ? 注意:SIGKILL 和 SIGSTOP 不能被捕獲,即這兩種信號(hào)的響應(yīng)動(dòng)作不能被改變。
3. 注冊(cè)信號(hào)的函數(shù)
1). signal
#include <signal.h>
typedef void (*sighandler_t)(int);? ? ? ? // 信號(hào)處理函數(shù)定義格式
sighandler_t signal(int signum, sighandler_t handler);
描述:注冊(cè)信號(hào),使得收到對(duì)應(yīng)注冊(cè)的信號(hào)后執(zhí)行指定的函數(shù);?
參數(shù):?
????????signum
? ? ? ? ????????信號(hào);
????????handler
? ? ? ? ????????函數(shù)指針;
????????????????還可以使用以下特殊值:
??????? ????????????????SIG_IGN???? 忽略信號(hào)
??????? ????????????????SIG_DFL???? 恢復(fù)默認(rèn)行為
返回值:
? ? ? ? 成功:返回上一個(gè)信號(hào)處理函數(shù)的指針,如果是第一次執(zhí)行signal,則返回SIG_DFL;
? ? ? ? 失敗:返回SIG_ERR,并設(shè)置錯(cuò)誤標(biāo)志errno。
例:
注冊(cè)SIGINT信號(hào),使得其執(zhí)行代碼中自定義的函數(shù),然后再函數(shù)中恢復(fù)默認(rèn)行為
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>
void myHandle(int sig) {
printf("catch a signal : %d\n", sig);
// 恢復(fù)默認(rèn)函數(shù)行為
signal(SIGINT, SIG_DFL); // 等同于signal(sig, SIG_DFL);
}
int main(int argc, char **argv) {
// 注冊(cè)SIGINT信號(hào)(ctrl+c),接收到此信號(hào)執(zhí)行myHandle方法
sighandler_t result = signal(SIGINT, myHandle);
if (SIG_ERR == result) {
printf("發(fā)生錯(cuò)誤了!\n");
perror("錯(cuò)誤原因:");
exit(-1);
}
while (1) sleep(1);
return 0;
}
第一次按下ctrl+c,執(zhí)行了自定義的函數(shù);第二次再按ctrl+c?,程序就結(jié)束了!達(dá)到預(yù)期效果!
另外,SIGUSR1 和 SIGUSR2是可以給我們程序員自己使用的,他倆沒(méi)有默認(rèn)綁定任何信號(hào)操作函數(shù),可以根據(jù)自己的實(shí)際需求去注冊(cè)綁定和使用!
例:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void myHandle1(int sig) {
printf("catch a signal : %d\n", sig);
}
void myHandle2(int sig) {
printf("接收到了信號(hào) : %d\n", sig);
}
int main(int argc, char **argv) {
// 注冊(cè)SIGUSR1和SIGUSR2信號(hào),接收到此信號(hào)執(zhí)行myHandle方法
signal(SIGUSR1, myHandle1);
signal(SIGUSR2, myHandle2);
while (1) sleep(1);
return 0;
}
2). sigaction (項(xiàng)目中強(qiáng)烈推薦使用)
sigaction與signal的區(qū)別: sigaction比signal更“健壯”,建議使用sigaction;
因?yàn)閟ignal是前期很早之前的函數(shù)了,會(huì)有很多欠缺的地方;所以sigaction橫空出世!
#include <signal.h>
int sigaction(int signum, const struct sigaction *act,?struct sigaction *oldact);
描述:sigaction()系統(tǒng)調(diào)用用于改變進(jìn)程在上采取的操作接收特定信號(hào)。
參數(shù):
? ? ? ? sigumn
? ? ? ? ? ? ? ? 信號(hào);可以是除SIGKILL和SIGSTOP之外的任何有效信號(hào);
? ? ? ? act
? ? ? ? ? ? ? ? 結(jié)構(gòu)體;一些對(duì)信號(hào)的設(shè)置;
? ? ? ? oldact
? ? ? ? ? ? ? ? 結(jié)構(gòu)體;獲取上一個(gè)的信號(hào)設(shè)置act;
返回值:
? ? ? ? 成功:返回0;
? ? ? ? 失敗:返回-1,并設(shè)置錯(cuò)誤標(biāo)志errno;
結(jié)構(gòu) struct sigaction
struct sigaction {
? ? ? ? void ? ? (*sa_handler)(int);? ? ? ? /* 信號(hào)相應(yīng)的函數(shù) */
? ? ? ? void ? ? (*sa_sigaction)(int, siginfo_t *, void *);? ? ? ? // 可以不管它
? ? ? ? sigset_t ? sa_mask;? ? ? ? ????????/* 屏蔽信號(hào)集 */
? ? ? ? ?int ? ? ? ?sa_flags;? ? ? ? ? ? ????????/* 置為0即可,其他操作可以 man 2 sigaction 去查看?*/
? ? ? ? ?void ? ? (*sa_restorer)(void);? ? ? ? // 可以不管他
};
屏蔽信號(hào)集
? ? ? ? 并不是說(shuō)屏蔽該信號(hào);如果sa_mask包含了信號(hào)A,在信號(hào)處理函數(shù)期間,信號(hào)A觸發(fā)了,則阻塞信號(hào)A,直到信號(hào)處理函數(shù)結(jié)束,才開(kāi)始處理信號(hào)A;即,信號(hào)處理函數(shù)執(zhí)行完之后,再響應(yīng)該信號(hào)A。(下面 多信號(hào)發(fā)送 會(huì)有例子)
例:
使用sigaction注冊(cè)信號(hào),且sa_flags設(shè)置為SA_RESETHAND,即只會(huì)調(diào)用一次指定的處理函數(shù),之后恢復(fù)默認(rèn)行為
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>
void myHandle(int sig) {
printf("catch a signal : %d\n", sig);
}
int main(int argc, char **argv) {
struct sigaction act;
struct sigaction oldact;
act.sa_handler = myHandle; // 處理函數(shù)
sigemptyset(&act.sa_mask); // 設(shè)置為0
//act.sa_flags = 0;
act.sa_flags = SA_RESETHAND; // 只會(huì)調(diào)用一次上面指定的處理函數(shù),之后恢復(fù)默認(rèn)行為
// 注冊(cè)SIGINT信號(hào)(ctrl+c),接收到此信息執(zhí)行myHandle方法
int ret = sigaction(SIGINT, &act, &oldact);
if (-1 == ret) {
printf("sigaction error!\n");
perror("reason:");
exit(-1);
}
while (1) sleep(1);
return 0;
}
?第一次按下ctrl+c,執(zhí)行了自定義的函數(shù);第二次再按ctrl+c?,程序就結(jié)束了!達(dá)到預(yù)期效果!
4. 信號(hào)發(fā)送
在前面的例子中,使用kill可以在終端給進(jìn)程發(fā)送信號(hào);當(dāng)然,kill也有函數(shù),也可以使用函數(shù)去發(fā)送信號(hào);
信號(hào)的發(fā)送方式:
-
? ? ? ? 1. 在shell終端用快捷鍵產(chǎn)生信號(hào);
-
? ? ? ? 2. 使用kill,killall命令;
- ? ? ? ? 3. 使用kill函數(shù)和alarm函數(shù)和raise函數(shù)。
-
? ? ? ? 2. 使用kill,killall命令;
1). kill 函數(shù)
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
描述:給指定的進(jìn)程發(fā)送指定的信號(hào);
參數(shù):
? ? ? ? pid
? ? ? ? ? ? ? ? 進(jìn)程id;
? ? ? ? sig
? ? ? ? ? ? ? ? 信號(hào);
返回值:
? ? ? ? 成功:返回0;
? ? ? ? 失敗:返回-1,并設(shè)置錯(cuò)誤標(biāo)志errno;
注意:
? ? ? ? 給指定的進(jìn)程發(fā)送信號(hào)需要權(quán)限:即普通用戶只能給普通用戶的進(jìn)程發(fā)送信號(hào),root用戶可以給所有用戶的進(jìn)程發(fā)送信號(hào);
例1:
創(chuàng)建一個(gè)子進(jìn)程,子進(jìn)程每秒中輸出字符串“child process work!",父進(jìn)程等待用戶輸入,如果用戶按下字符A, 則向子進(jìn)程發(fā)信號(hào)SIGUSR1, 子進(jìn)程的輸出字符串改為大寫; 如果用戶按下字符a, 則向子進(jìn)程發(fā)信號(hào)SIGUSR2, 子進(jìn)程的輸出字符串改為小寫
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>
int workflag = 0;
void work_up_handle(int sig) {
workflag = 1;
}
void work_down_handle(int sig) {
workflag = 0;
}
int main(int argc, char **argv) {
pid_t pd;
char c;
// 創(chuàng)建一個(gè)子進(jìn)程
pd = fork();
if (-1 == pd) {
printf("fork error!\n");
exit(1);
} else if (0 == pd) {
char *msg;
struct sigaction act;
act.sa_flags = 0;
act.sa_handler = work_up_handle;
sigemptyset(&act.sa_mask);
int ret = sigaction(SIGUSR1, &act, 0); // 注冊(cè)SIGUSR1信號(hào)
if (-1 == ret) {
printf("sigaction SIGUSR1 error!\n");
perror("reason:");
exit(-1);
}
act.sa_handler = work_down_handle;
ret = sigaction(SIGUSR2, &act, 0); // 注冊(cè)SIGUSR2信號(hào)
if (-1 == ret) {
printf("sigaction SIGUSR2 error!\n");
perror("reason:");
exit(-2);
}
while (1) {
if (!workflag) {
msg = "child process work!";
} else {
msg = "CHILD PROCESS WORK!";
}
printf("%s\n", msg);
sleep(2);
}
} else {
while(1) {
c = getchar();
if ('A' == c) {
// 給子進(jìn)程發(fā)送SIGUSR1信號(hào)
int ret = kill(pd, SIGUSR1);
if (-1 == ret) {
printf("kill SIGUSR1 error!\n");
perror("reason:");
}
} else if ('a' == c) {
// 給子進(jìn)程發(fā)送SIGUSR2信號(hào)
int ret = kill(pd, SIGUSR2);
if (-1 == ret) {
printf("kill SIGUSR2 error!\n");
perror("reason:");
}
}
}
}
return 0;
}
例2:
父進(jìn)程創(chuàng)建子進(jìn)程后,注冊(cè)一個(gè)信號(hào)后舊調(diào)用函數(shù)pause()掛起(休眠),直到父進(jìn)程收到任意一個(gè)信號(hào)才會(huì)喚醒;子進(jìn)程五秒后給父進(jìn)程發(fā)送任意一個(gè)信號(hào),喚醒父進(jìn)程,使其結(jié)束程序!
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
int workflag = 0;
void wake_handle(int sig) {
workflag = 1;
}
int main(int argc, char **argv) {
pid_t pd;
pd = fork(); // 創(chuàng)建進(jìn)程
if (-1 == pd) {
printf("fork error!\n");
exit(-1);
} else if (0 == pd) {
sleep(5);
int ret = kill(getppid(), SIGALRM); // 給父進(jìn)程發(fā)送SIGALRM信號(hào)
if (-1 == ret) {
printf("kill SIGALRM error!\n");
perror("reason:");
}
} else {
struct sigaction act;
act.sa_handler = wake_handle;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
int ret = sigaction(SIGALRM, &act, 0); // 注冊(cè)SIGALRM信號(hào)
if (-1 == ret) {
printf("sigaction error!\n");
perror("reasion:");
exit(-1);
}
// 把當(dāng)前進(jìn)程掛起,直到接收到任何一個(gè)信號(hào)
pause();
if (workflag) {
printf("父進(jìn)程結(jié)束掛起!\n");
}
}
int status = 0;
wait(&status);
return 0;
}
2). alarm 函數(shù)
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
描述:在指定的時(shí)間之內(nèi)給進(jìn)程本身發(fā)送一個(gè)SIGALRM信號(hào);
參數(shù):
? ? ? ? seconds
? ? ? ? ? ? ? ? 秒數(shù);
返回值:
? ? ? ? 成功:返回上一次alarm執(zhí)行后剩余的時(shí)間(秒),如果沒(méi)有,則返回0;
? ? ? ? 失敗:返回-1;
注意:
? ? ? ? 參數(shù)單位是秒,如果參數(shù)為0,則取消已設(shè)置的鬧鐘;如果執(zhí)行了alarm,但時(shí)間還沒(méi)有到,再次調(diào)用alarm,則鬧鐘將重新定時(shí),每個(gè)進(jìn)程最多只能使用一個(gè)鬧鐘,也就是只能使用一個(gè)alarm;
也可以說(shuō)alarm是一個(gè)鬧鐘,也可以說(shuō)是定時(shí)器!
例:
進(jìn)程調(diào)用pause()函數(shù)掛起,調(diào)用alarm(3),3秒后喚醒進(jìn)程!
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
int workflag = 0;
void wake_handle(int sig) {
workflag = 1;
}
int main(int argc, char **argv) {
int ret = 0;
struct sigaction act;
act.sa_handler = wake_handle;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
sigaction(SIGALRM, &act, 0); // 注冊(cè)SIGALRM信號(hào)
ret = alarm(3); // 3秒后給自己發(fā)送SIGALRM信號(hào)
if (-1 == ret) {
printf("alarm error!\n");
exit(-1);
}
printf("進(jìn)程開(kāi)始掛起(睡眠)\n");
pause(); // 把當(dāng)前進(jìn)程掛起,直到接收到任何一個(gè)信號(hào)
if (workflag) {
printf("進(jìn)程被喚醒!\n");
}
return 0;
}
3). raise 函數(shù)
#include <signal.h>
int raise(int sig);
描述:給本進(jìn)程自身發(fā)送信號(hào);
參數(shù):
? ? ? ? sig
? ? ? ? ? ? ? ? 信號(hào);
返回值:
? ? ? ? 成功:返回0;
? ? ? ? 失敗:返回非0;
例:
使用raise給自身進(jìn)程發(fā)送SIGUSR1信號(hào);
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void wake_handle(int sig) {
printf("接收到信號(hào):%d\n", sig);
}
int main(int argc, char **argv) {
int ret = 0;
struct sigaction act;
act.sa_handler = wake_handle;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
sigaction(SIGUSR1, &act, 0); // 注冊(cè)SIGUSR1信號(hào)
// 給自身進(jìn)程發(fā)送SIGUSR1信號(hào)
ret = raise(SIGUSR1); // 相當(dāng)于 kill(getpid(), SIGUSR1);
if (0 != ret) {
printf("raise error!\n");
exit(-1);
}
return 0;
}
5. 發(fā)送多個(gè)信號(hào)的情況
情況一
????????某進(jìn)程正在執(zhí)行某個(gè)信號(hào)對(duì)應(yīng)的操作函數(shù)期間(該信號(hào)的安裝函數(shù)),如果此時(shí),該進(jìn)程又多次收到同一個(gè)信號(hào)(同一種信號(hào)值的信號(hào)),
????????則:如果該信號(hào)是不可靠信號(hào)(小于32),則只能再響應(yīng)一次(即加上正在執(zhí)行的一次,最多是響應(yīng)兩次,其他都丟棄)。
????????如果該信號(hào)是可靠信號(hào)(大于32),則能再響應(yīng)多次(不會(huì)遺漏)。但是,都是都必須等該次響應(yīng)函數(shù)執(zhí)行完之后,才能響應(yīng)下一次。
情況二
????????某進(jìn)程正在執(zhí)行某個(gè)信號(hào)對(duì)應(yīng)的操作函數(shù)期間(該信號(hào)的安裝函數(shù)),如果此時(shí),該進(jìn)程收到另一個(gè)信號(hào)(不同信號(hào)值的信號(hào));
????????如果該信號(hào)被包含在當(dāng)前信號(hào)的signaction的sa_mask(信號(hào)屏蔽集)中,則不會(huì)立即處理該信號(hào)。直到當(dāng)前的信號(hào)處理函數(shù)執(zhí)行完之后,才去執(zhí)行該信號(hào)的處理函數(shù)。(如果也是不可靠的信號(hào),那么最多也只會(huì)再響應(yīng)多一次)
????????否則:則立即中斷當(dāng)前執(zhí)行過(guò)程(如果處于睡眠,比如sleep, 則立即被喚醒)而去執(zhí)行這個(gè)新的信號(hào)響應(yīng)。新的響應(yīng)執(zhí)行完之后,再在返回至原來(lái)的信號(hào)處理函數(shù)繼續(xù)執(zhí)行。
例:
用以下代碼,就可以測(cè)試上面兩種情況的真實(shí)性;
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void myhandle1(int sig) {
printf("myhandle1收到了信號(hào):%d\n", sig);
int i = 0;
for (; i < 10; i++) {
sleep(1);
}
printf("myhandle1信號(hào)處理函數(shù)結(jié)束:%d\n", sig);
}
void myhandle2(int sig) {
printf("myhandle2 Catch a signal:%d\n", sig);
int i = 0;
for (; i < 10; i++) {
sleep(1);
}
printf("myhandle2 Catch end!:%d\n", sig);
}
int main(int argc, char **argv) {
int ret = 0;
struct sigaction act, act2;
act.sa_handler = myhandle1;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
sigaddset(&act.sa_mask, SIGUSR2); // 信號(hào)集SIGUSR2
sigaction(SIGUSR1, &act, 0); // 注冊(cè)SIGUSR1信號(hào)
act2.sa_handler = myhandle2;
sigemptyset(&act2.sa_mask);
act2.sa_flags = 0;
sigaction(SIGUSR2, &act2, 0); // 注冊(cè)SIGUSR2信號(hào)
while (1) { sleep(1); }
return 0;
}
測(cè)試情況一,使用信號(hào)SIIGUSR2去測(cè)試,連續(xù)發(fā)送多條SIGUSR2信號(hào),測(cè)試到底是不是像上面說(shuō)的那樣!
?可以看到,發(fā)送了多條SIGUSR2之后,程序也只是做了兩次響應(yīng),且是排隊(duì)響應(yīng)的!測(cè)試達(dá)到預(yù)期!
測(cè)試情況二,包含信號(hào)屏蔽集;首先發(fā)送SIGUSR1信號(hào)后,接連發(fā)送多條SIGSUR2信號(hào),因?yàn)镾IGSUR2信號(hào)在信號(hào)集中,測(cè)試是否等待排隊(duì)響應(yīng)!
可以看到,發(fā)送了多條SIGUSR2之后,程序也只是做了兩次響應(yīng),且是排隊(duì)響應(yīng)的!測(cè)試達(dá)到預(yù)期!
測(cè)試情況二,不包含信號(hào)屏蔽集;現(xiàn)在將代碼中sigaddset(&act.sa_mask, SIGUSR2);這句代碼給注釋掉,首先發(fā)送SIGUSR1信號(hào)后,接連發(fā)送多條SIGSUR2信號(hào),因?yàn)镾IGSUR2信號(hào)在信號(hào)集中,測(cè)試是否會(huì)中斷當(dāng)前的執(zhí)行,而去執(zhí)行新的信號(hào)響應(yīng);
當(dāng)發(fā)送SIGUSR1信號(hào)后,右邊程序立即大于“收到了信號(hào):10”,當(dāng)發(fā)送SIGUSR2信號(hào)后,立即中斷SIGUSR1信號(hào)的響應(yīng)操作,轉(zhuǎn)而執(zhí)行SIGUSR2信號(hào)的響應(yīng)函數(shù),執(zhí)行完畢之后,才回來(lái)繼續(xù)執(zhí)行SIGUSR1信號(hào)的響應(yīng)函數(shù)!測(cè)試達(dá)到預(yù)期!
注意:以上三個(gè)測(cè)試都是使用小于32的信號(hào)進(jìn)行測(cè)試,所以即使發(fā)送了多個(gè)相同的信號(hào),也只是執(zhí)行了一次而已!
6. 信號(hào)集
什么是信號(hào)集?
信號(hào)集,用sigset_t類型表示,實(shí)質(zhì)是一個(gè)無(wú)符號(hào)長(zhǎng)整形;用來(lái)表示包含多個(gè)信號(hào)的集合。
信號(hào)集的基本操作
1). sigemptyset
#include <signal.h>
int sigemptyset(sigset_t *set);
描述:把信號(hào)集清空;
參數(shù):
? ? ? ? set
????????????????&act.sa_mask
返回值:
? ? ? ? 成功:返回0;
? ? ? ? 失敗:返回-1,并設(shè)置erron錯(cuò)誤標(biāo)志;
例:
struct sigaction act;
int ret = sigemptyset(&act.sa_mask);
if (-1 == ret) {
printf("sigemptyset error!\n");
perror("reason:"); // # include <errno.h>
}
2). sigfillset
#include <signal.h>?
int sigfillset(sigset_t *set);
描述:把所有已經(jīng)定義的全部信號(hào)填充到指定信號(hào)集;
參數(shù):
? ? ? ? set
????????????????&act.sa_mask
返回值:
? ? ? ? 成功:返回0;
? ? ? ? 失敗:返回-1,并設(shè)置erron錯(cuò)誤標(biāo)志;
例:
struct sigaction act;
int ret = sigfillset(&act.sa_mask);
if (-1 == ret) {
printf("sigfillset error!\n");
perror("reason:"); // # include <errno.h>
}
3). sigdelset
#include <signal.h>?
int sigdelset(sigset_t *set, int signum);
描述:從指定的信號(hào)集中刪除指定的信號(hào);
參數(shù):
? ? ? ? set
????????????????&act.sa_mask
? ? ? ? signum
? ? ? ? ? ? ? ? 信號(hào);
返回值:
? ? ? ? 成功:返回0;
? ? ? ? 失敗:返回-1,并設(shè)置erron錯(cuò)誤標(biāo)志;
例:
struct sigaction act;
int ret = sigdelset(&act.sa_mask, SIGUSR2);
if (-1 == ret) {
printf("sigdelset error!\n");
perror("reason:"); // # include <errno.h>
}
4). sigaddset
#include <signal.h>?
int sigaddset(sigset_t *set, int signum);
描述:從指定的信號(hào)集中添加指定的信號(hào);
參數(shù):
? ? ? ? set
????????????????&act.sa_mask
? ? ? ? signum
? ? ? ? ? ? ? ? 信號(hào);
返回值:
? ? ? ? 成功:返回0;
? ? ? ? 失敗:返回-1,并設(shè)置erron錯(cuò)誤標(biāo)志;
例:
struct sigaction act;
int ret = sigaddset(&act.sa_mask, SIGUSR2);
if (-1 == ret) {
printf("sigaddset error!\n");
perror("reason:"); // # include <errno.h>
}
5). sigismember
#include <signal.h>?
int sigismember(const sigset_t *set, int signum);
描述:判斷指定的信號(hào)是否在指定的信號(hào)集中;
參數(shù):
? ? ? ? set
????????????????&act.sa_mask
? ? ? ? signum
? ? ? ? ? ? ? ? 信號(hào);
返回值:
? ? ? ? 成功:如果是,返回1;如果不是,返回0;
? ? ? ? 失敗:返回-1,并設(shè)置erron錯(cuò)誤標(biāo)志;
例:
struct sigaction act;
int ret = sigismember(&act.sa_mask, SIGUSR1);
if (-1 == ret) {
printf("sigismember error!\n");
perror("reason:"); // # include <errno.h>
}
7.?進(jìn)程的“信號(hào)屏蔽字”
進(jìn)程的“信號(hào)屏蔽字”是一個(gè)信號(hào)集;
向目標(biāo)進(jìn)程發(fā)送某信號(hào)時(shí),如果這個(gè)信號(hào)在目標(biāo)進(jìn)程的信號(hào)屏蔽字中,則目標(biāo)進(jìn)程將不會(huì)捕獲到該信號(hào),即不會(huì)執(zhí)行該信號(hào)的處理函數(shù)。
當(dāng)該進(jìn)程的信號(hào)屏蔽字不在包含該信號(hào)時(shí),則會(huì)捕獲這個(gè)早已收到的信號(hào)(執(zhí)行對(duì)應(yīng)的函數(shù))
1).?修改進(jìn)程的“信號(hào)屏蔽字”
使用 sigprocmask
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
描述:修改和設(shè)置信號(hào)屏蔽字
參數(shù):
? ? ? ? how
????????????????SIG_BLOCK? ? ? ? ? ? 把參數(shù)set中的信號(hào)添加到信號(hào)屏蔽字中;
? ? ? ? ? ? ? ? SIG_UNBLOCK? ? ? ?把參數(shù)set中的信號(hào)從信號(hào)屏蔽字中刪除;
????????????????SIG_SETMASK? ? ? ?把參數(shù)set中的信號(hào)設(shè)置為信號(hào)屏蔽字,之前設(shè)置的作廢;
????????set
? ? ? ? ? ? ? ? 信號(hào)屏蔽字集,&set
? ? ? ? oldset
? ? ? ? ? ? ? ? 返回之前設(shè)置的信號(hào)屏蔽字(備份之前的),&oldset
返回值:
? ? ? ? 成功:返回0;
? ? ? ? 失敗:返回-1,并設(shè)置erron錯(cuò)誤標(biāo)志;
例:
SIGINT信號(hào)注冊(cè)后,再定義信號(hào)屏蔽字,設(shè)置SIGINT信號(hào)屏蔽字,休眠5秒,這5秒內(nèi),狂按Ctrl+c都是沒(méi)反應(yīng)的,因?yàn)檫@個(gè)信號(hào)被設(shè)置了信號(hào)屏蔽字;5秒后,刪除信號(hào)屏蔽字,則會(huì)捕獲這個(gè)早已收到的信號(hào)(執(zhí)行對(duì)應(yīng)的函數(shù)),但只會(huì)執(zhí)行一次(猜測(cè)是信號(hào)小于32的原因)
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void myhandle(int sig)
{
printf("Catch a signal : %d\n", sig);
printf("Catch end.%d\n", sig);
}
int main(void)
{
struct sigaction act;
act.sa_handler = myhandle;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGINT, &act, 0);
sigset_t proc_sig_msk, old_mask;
sigemptyset(&proc_sig_msk);
sigaddset(&proc_sig_msk, SIGINT);
// 設(shè)置信號(hào)屏蔽字
sigprocmask(SIG_BLOCK, &proc_sig_msk, &old_mask);
sleep(5);
printf("had delete SIGINT from process sig mask\n");
// 刪除信號(hào)屏蔽字
sigprocmask(SIG_UNBLOCK, &proc_sig_msk, &old_mask);
while (1) {
sleep(1);
}
return 0;
}
可以看到,程序運(yùn)行后,按ctrl+c已經(jīng)沒(méi)有反應(yīng)了,5秒后,之前積累的信號(hào)只響應(yīng)了一次;再按ctrl+c,就正常響應(yīng)了;測(cè)試符合預(yù)期!
2).?獲取未處理的信號(hào)
當(dāng)進(jìn)程的信號(hào)屏蔽字中信號(hào)發(fā)生時(shí),這些信號(hào)不會(huì)被該進(jìn)程響應(yīng),可通過(guò)sigpending函數(shù)獲取這些已經(jīng)發(fā)生了但是沒(méi)有被處理的信號(hào);
#include <signal.h>
int sigpending(sigset_t *set);
描述:獲取被屏蔽未處理的信號(hào)
參數(shù):
? ? ? ? set
? ? ? ? ? ? ? ? 獲取未被處理的信號(hào)返回,&set
返回值:
? ? ? ? 成功:返回0;
? ? ? ? 失敗:返回-1,并設(shè)置erron錯(cuò)誤標(biāo)志;
例:
sigset_t proc_sig_msk;
int ret = sigpending(&proc_sig_msk);
if (0 != ret) {
printf("sigpending error!\n");
perror("reason:"); // #include <errno.h>
}
8.?阻塞式等待信號(hào)
1). pause
#include <unistd.h>
int pause(void);
描述:阻塞進(jìn)程,直到發(fā)生任一信號(hào)為止;
返回值:
? ? ? ? 當(dāng)接收到任一信號(hào)后,返回-1,且errno被設(shè)置為EINTR;
例:
pause();
2). sigsuspend
#include <signal.h>
int sigsuspend(const sigset_t *mask);
描述:用指定的參數(shù)設(shè)置信號(hào)屏蔽字,然后阻塞時(shí)等待信號(hào)的發(fā)生。即,只等待信號(hào)屏蔽字之外的信號(hào);
參數(shù):
? ? ? ? mask
? ? ? ? ? ? ? ? 信號(hào)屏蔽字集,&mask
返回值:
? ? ? ? 總是返回-1;
例:
設(shè)置信號(hào)屏蔽字,屏蔽SIGINT信號(hào),然后注冊(cè)SIGUSR1信號(hào),調(diào)用sigsuspend函數(shù)阻塞,最后在另一個(gè)終端kill -SIGUSR1?
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void myhandle(int sig)
{
printf("Catch a signal : %d\n", sig);
printf("Catch end.%d\n", sig);
}
void myhandle2(int sig)
{
printf("信號(hào) : %d\n", sig);
}
int main(void)
{
struct sigaction act, act2;
act.sa_handler = myhandle;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGINT, &act, 0);
// 防止觸發(fā)SIGUSR1時(shí)程序中斷結(jié)束,而不是自然結(jié)束
act2.sa_handler = myhandle2;
sigemptyset(&act2.sa_mask);
act2.sa_flags = 0;
sigaction(SIGUSR1, &act2, 0);
sigset_t proc_sig_msk, old_mask;
sigemptyset(&proc_sig_msk);
sigaddset(&proc_sig_msk, SIGINT);
// 設(shè)置信號(hào)屏蔽字
sigprocmask(SIG_BLOCK, &proc_sig_msk, &old_mask);
// 阻塞,等待信號(hào)屏蔽集以外的信號(hào)觸發(fā),才能喚醒
sigsuspend(&proc_sig_msk);
return 0;
}
當(dāng)接收到SIGINT以外的信號(hào)SIGUSR1時(shí),喚醒進(jìn)程,然后響應(yīng)信號(hào)操作函數(shù),最后結(jié)束進(jìn)程;測(cè)試符合預(yù)期效果!?
二、管道
1. 管道簡(jiǎn)介?
管道,就是進(jìn)程與進(jìn)程互相發(fā)送和接收消息的媒介!
管道是“半雙工”的,即是單向的。
管道是FIFO(先進(jìn)先出)的。
單進(jìn)程中的管道:
int fd[2]
使用文件描述符fd[1], 向管道寫數(shù)據(jù)
使用文件描述符fd[0], 從管道讀數(shù)據(jù)
注:?jiǎn)芜M(jìn)程中的管道無(wú)實(shí)際用處;管道用于多進(jìn)程間通信。
2. 管道的創(chuàng)建
1). pipe
#include <unistd.h>
int pipe(int pipefd[2]);
描述:pipe()創(chuàng)建一個(gè)管道,這是一個(gè)單向數(shù)據(jù)通道,可用于進(jìn)程間通信。數(shù)組pipefd用于返回兩個(gè)文件指管道兩端的描述符。pipefd[0]指的是管道的讀端;pipefd[1]指的是管道的寫端。
參數(shù):
? ? ? ? pipefd
? ? ? ? ? ? ? ? int型數(shù)組,文件描述符,用于操作讀寫,即數(shù)據(jù)發(fā)送和接收;0數(shù)據(jù)接收,1數(shù)據(jù)發(fā)送;
返回值:
? ? ? ? 成功:返回0;
? ? ? ? 失敗:返回-1,并設(shè)置erron錯(cuò)誤標(biāo)志;
注意:獲取兩個(gè)“文件描述符”;分別對(duì)應(yīng)管道的讀端和寫端。
????????fd[0]: 是管道的讀端
????????fd[1]: 是管道的寫端
????????如果對(duì)fd[0]進(jìn)行寫操作,對(duì)fd[1]進(jìn)行讀操作,可能導(dǎo)致不可預(yù)期的錯(cuò)誤。
例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char **argv) {
int fd[2]; // 定義管道fd數(shù)組
int ret = 0;
char buff1[1024];
char buff2[1024];
ret = pipe(fd); // 創(chuàng)建一個(gè)管道,一個(gè)單向數(shù)據(jù)通道,可用于進(jìn)程間通信
if (0 != ret) {
printf("create pipe failed!\n");
exit(1);
}
strcpy(buff1, "Hello World!");
write(fd[1], buff1, strlen(buff1)); // 發(fā)送信息
printf("send information:%s\n", buff1);
bzero(buff2, sizeof(buff2)); // 清零
// 如果沒(méi)有消息,會(huì)阻塞
read(fd[0], buff2, sizeof(buff2)); // 讀取消息
printf("recived information:%s\n", buff2);
return 0;
}
2). 例一?:多進(jìn)程使用管道通信
創(chuàng)建子進(jìn)程,父進(jìn)程給子進(jìn)程發(fā)送消息,子進(jìn)程接收后,給父進(jìn)程發(fā)送消息,父進(jìn)程接收消息!
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
int main(int argc, char **argv) {
int fd1[2]; // 父進(jìn)程:fd1[1] 子進(jìn)程:fd1[0]
int fd2[2]; // 父進(jìn)程:fd2[0] 子進(jìn)程:fd2[1]
int ret = 0;
char buff1[1024];
char buff2[1024];
pid_t pd; // 進(jìn)程id
ret = pipe(fd1); // 創(chuàng)建一個(gè)管道,一個(gè)單向數(shù)據(jù)通道,可用于進(jìn)程間通信
if (0 != ret) {
printf("create pipe1 failed!\n");
exit(1);
}
ret = pipe(fd2);
if (0 != ret) {
printf("create pipe2 failed!\n");
exit(2);
}
pd = fork();
if (-1 == pd) {
printf("fork error!\n");
exit(3);
} else if (0 == pd) {
/* 收消息 */
bzero(buff2, sizeof(buff2)); // 清零
read(fd1[0], buff2, sizeof(buff2)); // 讀取消息
printf("%d[Child] 進(jìn)程收到消息:%s\n", getpid(), buff2);
/* 發(fā)消息 */
strcpy(buff1, "Hello parent!");
write(fd2[1], buff1, sizeof(buff1)); // 發(fā)送消息
printf("%d[Child] 進(jìn)程發(fā)送消息:%s\n", getpid(), buff1);
} else {
/* 發(fā)消息 */
strcpy(buff1, "Hello child!");
write(fd1[1], buff1, sizeof(buff1));
printf("%d[Parent] 進(jìn)程發(fā)送消息:%s\n", getpid(), buff1);
/* 收消息 */
bzero(buff2, sizeof(buff2));
read(fd2[0], buff2, sizeof(buff2));
printf("%d[Parent] 進(jìn)程收到消息:%s\n", getpid(), buff2);
}
wait();
return 0;
}
3). 例二:關(guān)閉管道的讀端/寫端
管道關(guān)閉后的讀操作:
問(wèn)題:
對(duì)管道進(jìn)行read時(shí),如果管道中已經(jīng)沒(méi)有數(shù)據(jù)了,此時(shí)讀操作將被“阻塞”。
如果此時(shí)管道的寫端已經(jīng)被close了,則讀操作將可能被一直阻塞!
而此時(shí)的阻塞已經(jīng)沒(méi)有任何意義了。(因?yàn)楣艿赖膶懚艘呀?jīng)被關(guān)閉,即不會(huì)再寫入數(shù)據(jù)了)
解決方案:
如果不準(zhǔn)備再向管道寫入數(shù)據(jù),則把該管道的所有寫端都關(guān)閉,
則,此時(shí)再對(duì)該管道read時(shí),就會(huì)返回0,而不再阻塞該讀操作。(管道的特性)
注意,這是管道的特性。
????? 如果有多個(gè)寫端口,而只關(guān)閉了一個(gè)寫端,那么無(wú)數(shù)據(jù)時(shí)讀操作仍將被阻塞。
實(shí)際實(shí)現(xiàn)方式:
父子進(jìn)程各有一個(gè)管道的讀端和寫端;
把父進(jìn)程的讀端(或?qū)懚耍╆P(guān)閉;
把子進(jìn)程的寫端(或讀端)關(guān)閉;
使這個(gè)“4端口”管道變成單向的“2端口”管道,如圖:
?例:
子進(jìn)程沒(méi)有發(fā)送消息的需求,關(guān)閉發(fā)送端口;然后開(kāi)始接收消息,收到消息后休眠一秒后再進(jìn)行一次接收消息;
父進(jìn)程沒(méi)有接收消息的希求,關(guān)閉接收端口;然后給子進(jìn)程發(fā)送一條消息后休眠三秒,然后關(guān)閉發(fā)送端口;
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc, char **argv) {
int fd[2];
int ret = 0;
char buff1[1024];
char buff2[1024];
pid_t pd; // 進(jìn)程id
ret = pipe(fd); // 創(chuàng)建一個(gè)管道,一個(gè)單向數(shù)據(jù)通道,可用于進(jìn)程間通信
if (0 != ret) {
printf("create pipe1 failed!\n");
exit(1);
}
pd = fork();
if (-1 == pd) {
printf("fork error!\n");
exit(3);
} else if (0 == pd) {
close(fd[1]); // 如果子進(jìn)程沒(méi)有發(fā)送信息需求,關(guān)閉子進(jìn)程的發(fā)送消息管道
/* 收消息 */
bzero(buff2, sizeof(buff2)); // 清零
read(fd[0], buff2, sizeof(buff2)); // 讀取消息
printf("%d 進(jìn)程收到消息:%s\n", getpid(), buff2);
sleep(1);
/* 收消息 */
bzero(buff2, sizeof(buff2)); // 清零
ret = read(fd[0], buff2, sizeof(buff2)); // 此時(shí)這里會(huì)阻塞,直到收到消息,或者發(fā)送端被關(guān)閉
if (ret > 0) {
printf("%d[Child] 進(jìn)程收到消息:%s\n", getpid(), buff2);
} else if (0 == ret) {
printf("[%d] 發(fā)送消息端口已經(jīng)被關(guān)閉!\n", ret);
}
close(fd[0]);
} else {
close(fd[0]); // 父進(jìn)程不需要收消息,關(guān)閉收消息管道
/* 發(fā)消息 */
strcpy(buff1, "Hello child!");
write(fd[1], buff1, sizeof(buff1));
printf("%d[Parent] 進(jìn)程發(fā)送消息:%s\n", getpid(), buff1);
sleep(3);
close(fd[1]); // 關(guān)閉父進(jìn)程的發(fā)消息管道
}
wait(NULL);
return 0;
}
4). 例三:父進(jìn)程循環(huán)給子進(jìn)程發(fā)送消息
創(chuàng)建一個(gè)子進(jìn)程,父進(jìn)程通過(guò)管道向子進(jìn)程發(fā)送數(shù)據(jù)(字符串),該字符串由用戶輸入。
當(dāng)用戶輸入”exit”時(shí), 就不再向子進(jìn)程發(fā)送數(shù)據(jù),并關(guān)閉該端的管道。
子進(jìn)程從管道讀取數(shù)據(jù),并輸出。
直到父進(jìn)程關(guān)閉了管道的寫端。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc, char **argv) {
int fd[2];
int ret = 0;
char buff1[1024];
char buff2[1024];
pid_t pd; // 進(jìn)程id
ret = pipe(fd); // 創(chuàng)建一個(gè)管道,一個(gè)單向數(shù)據(jù)通道,可用于進(jìn)程間通信
if (0 != ret) {
printf("create pipe1 failed!\n");
exit(1);
}
pd = fork();
if (-1 == pd) {
printf("fork error!\n");
exit(3);
} else if (0 == pd) {
close(fd[1]); // 如果子進(jìn)程沒(méi)有發(fā)送信息需求,關(guān)閉子進(jìn)程的發(fā)送消息管道
while (1) {
/* 收消息 */
bzero(buff2, sizeof(buff2)); // 清零
ret = read(fd[0], buff2, sizeof(buff2)); // 此時(shí)這里會(huì)阻塞,直到收到消息,或者發(fā)送端被關(guān)閉
if (ret > 0) {
printf("%d[Child] 進(jìn)程收到消息:%s\n", getpid(), buff2);
} else if (0 == ret) {
printf("[%d] 發(fā)送消息端口已經(jīng)被關(guān)閉!\n", ret);
break;
}
}
} else {
while (1) {
// 父進(jìn)程不需要收消息,關(guān)閉收消息管道
close(fd[0]);
bzero(buff1, sizeof(buff1)); // 清零
scanf("%s", buff1);
if (0 == strcmp(buff1, "exit")) {
close(fd[1]); // 關(guān)閉父進(jìn)程的發(fā)消息管道
break;
} else {
/* 發(fā)消息 */
write(fd[1], buff1, sizeof(buff1));
printf("%d[Parent] 進(jìn)程發(fā)送消息:%s\n", getpid(), buff1);
}
}
}
wait(NULL);
return 0;
}
3. popen / pclose
popen的作用:
用來(lái)在兩個(gè)程序之間傳遞數(shù)據(jù):
在程序A中使用popen調(diào)用程序B時(shí),有兩種用法:
- 程序A讀取程序B的輸出(使用fread讀?。?/li>
- 程序A發(fā)送數(shù)據(jù)給程序B,以作為程序B的標(biāo)準(zhǔn)輸入。(使用fwrite寫入)
1). popen
#include <stdio.h>
FILE *popen(const char *command, const char *type);
描述:創(chuàng)建一個(gè)管道流;
參數(shù):
? ? ? ? command
? ? ? ? ? ? ? ? 可以是shell命令或者程序的名字;
? ? ? ? type
? ? ? ? ? ? ? ? 可以是r表示讀,w表示寫;
返回值:
????????如果內(nèi)存分配失敗,popen()函數(shù)不會(huì)設(shè)置errno。如果底層進(jìn)程或管道失敗,errno被適當(dāng)設(shè)置。如果類型參數(shù)無(wú)效,當(dāng)檢測(cè)到此條件時(shí),errno被設(shè)置為EINVAL;其他失敗返回空;成功返回文件指針!
2). pclose
#include <stdio.h>
int pclose(FILE *stream);
描述:關(guān)閉管道流;
參數(shù):
? ? ? ? stream
? ? ? ? ? ? ? ? 管道(文件)流;
返回值:
? ? ? ? 成功:返回0;
? ? ? ? 失敗:返回-1,并設(shè)置erron錯(cuò)誤標(biāo)志;
3). 例一:讀取命令返回的數(shù)據(jù)
#include <stdio.h>
#include <stdlib.h>
#define BUFF_SIZE 1024
int main(void) {
FILE *file;
char buff[BUFF_SIZE + 1] = { '\0' };
int cnt;
// 管道方式以讀的方式打開(kāi)這條命令
file = popen("ls -l", "r");
if (!file) {
printf("popen failed!\n");
exit(1);
}
// 讀取"ls -l"顯示的所有數(shù)據(jù)
cnt = fread(buff, sizeof(char), BUFF_SIZE, file);
if (cnt > 0) {
buff[cnt] = '\0';
printf("%s", buff);
}
// 關(guān)閉
int ret = pclose(file);
printf("ret = %d\n", ret);
return 0;
}
4). 例二:把輸出寫到外部程序
pipe_6.cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUFF_SIZE 1024
int main(void) {
FILE *file;
char buff[BUFF_SIZE + 1] = { '\0' };
int cnt;
// 管道方式以寫的方式運(yùn)行p2程序
file = popen("./p2", "w");
if (!file) {
printf("popen failed!\n");
exit(1);
}
strcpy(buff, "hello world!");
cnt = fwrite(buff, sizeof(char), strlen(buff), file);
if (cnt > 0) {
printf("send \"%s\" successed!\n", buff);
}
// 關(guān)閉
pclose(file);
return 0;
}
p2.cpp
#include <stdio.h>
#include <unistd.h>
#define BUFF_SIZE 1024
int main(void) {
char buff[BUFF_SIZE] = { '\0' };
int ret = 0;
ret = read(0, buff, sizeof(buff));
if (ret > 0) {
buff[ret] = '\0';
printf("buff = %s\n", buff);
}
return 0;
}
編譯命令:
gcc pipe_6.cpp
gcc p2.cpp -o p2
?popen的原理
先使用fork創(chuàng)建一個(gè)子進(jìn)程,
然后在子進(jìn)程中使用exec執(zhí)行指定外部程序,并返回一個(gè)文件指針FILE*給父進(jìn)程。
當(dāng)使用”r”時(shí),該FILE指向外部程序的標(biāo)準(zhǔn)輸出
當(dāng)使用”w”時(shí),該FILE指向外部程序的標(biāo)準(zhǔn)輸入。
5). popen的優(yōu)缺點(diǎn)
優(yōu)點(diǎn):可以使用shell擴(kuò)展(比如命令中可以使用通配符),使用方便。
缺點(diǎn):每調(diào)用一次popen, 將要啟動(dòng)兩個(gè)進(jìn)程(shell和被指定的程序), 資源消耗大。
如果所有管道寫端對(duì)應(yīng)的文件描述符被關(guān)閉,則read返回0
如果所有管道讀端對(duì)應(yīng)的文件描述符被關(guān)閉,則write操作會(huì)產(chǎn)生信號(hào)SIGPIPE
三、消息隊(duì)列
1. 什么是消息隊(duì)列?
????????消息隊(duì)列,用于從一個(gè)進(jìn)程向另一個(gè)進(jìn)程發(fā)送數(shù)據(jù)。
????????但僅把數(shù)據(jù)發(fā)送到一個(gè)“隊(duì)列”中,而不指定由哪個(gè)進(jìn)程來(lái)接受。
????????消息隊(duì)列,獨(dú)立于發(fā)送消息的進(jìn)程和接收消息的進(jìn)程。
????????(信號(hào)、管道、命名管道都不獨(dú)立于發(fā)送和接收進(jìn)程)
???
????????消息隊(duì)列,有最大長(zhǎng)度限制:MSGMNB
????????消息隊(duì)列中的單條消息,也有最大長(zhǎng)度限制:MSGMAX
? ? ? ? 簡(jiǎn)單來(lái)講,就是有一條隊(duì)列,進(jìn)程A把消息發(fā)送到隊(duì)列中,進(jìn)程B就可以在這個(gè)隊(duì)列中去獲取接收這條消息!
2.?msgget 消息隊(duì)列的獲取
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
描述:通過(guò)系統(tǒng)調(diào)用 創(chuàng)建 | 獲取 消息隊(duì)列;
參數(shù):
? ? ? ? key
? ? ? ? ? ? ? ? 指定創(chuàng)建或獲取消息隊(duì)列的key,唯一;
? ? ? ? msgflag
????????????????IPC_CREAT 或者?IPC_CREAT |?IPC_EXCL
????????????????IPC_CREAT 消息隊(duì)列不存在則創(chuàng)建,存在則返回;
????????????????IPC_CREAT | IPC_EXCL 消息隊(duì)列不存在則創(chuàng)建,存在則報(bào)錯(cuò)返回;?
返回值:
????????成功:返回0;
? ? ? ? 失敗:返回-1,并設(shè)置erron錯(cuò)誤標(biāo)志;
? ? ? ? 具體錯(cuò)誤原因可以使用命令:man 2 msgget 去查看.
3.?msgsnd 消息的發(fā)送
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
描述:發(fā)送一個(gè)消息,即把消息添加到消息隊(duì)列中;
參數(shù):
? ? ? ? msqid
? ? ? ? ? ? ? ? 消息隊(duì)列標(biāo)識(shí)符;
? ? ? ? msgp
????????????????消息指針,自己定義的結(jié)構(gòu)體;
????????????????注:消息的類型需要自己定義。但要求其第一個(gè)結(jié)構(gòu)成員為 long int , 例:
struct? msgbuf {
long mtype;??? /* 消息的類型,取值大于0, 接收消息時(shí)可使用該值 */
/* 其他數(shù)據(jù)變量 */?????
char mtext[1];?
}
? ? ? ? msgsz
????????????????消息的長(zhǎng)度(不包含第一個(gè)成員msg_type);
? ? ? ? msgflg
????????????????如果包含:? IPC_NOWAIT,則消息隊(duì)列滿時(shí),不發(fā)送該消息,而立即返回-1;
????????????????如果不包含:IPC_NOWAIT,則消息隊(duì)列滿時(shí),掛起本進(jìn)程,直到消息隊(duì)列有空間可用;
返回值:
????????成功:返回0;
? ? ? ? 失敗:返回-1,并設(shè)置erron錯(cuò)誤標(biāo)志;
? ? ? ? 具體錯(cuò)誤原因可以使用命令:man 2 msgsnd 去查看.
4.?msgrcv 消息的接收
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
描述:從消息隊(duì)列中接收一條消息;
參數(shù):
? ? ? ? msqid
? ? ? ? ? ? ? ? 消息隊(duì)列標(biāo)識(shí)符;
? ? ? ? msgp
????????????????消息指針,自己定義的結(jié)構(gòu)體,用于接收消息的緩存;
? ? ? ? msgsz
????????????????消息的長(zhǎng)度(不包含第一個(gè)成員msg_type);
? ? ? ? msgtyp
????????????????指定接收消息的類型:
????????????????????????0:從消息隊(duì)列中獲取第一個(gè)消息,以實(shí)現(xiàn)順序接受(先發(fā)先收);
????????????????????????>0:從消隊(duì)列中獲取相同類型的第一個(gè)消息;
????????????????????????<0:從消息隊(duì)列中獲取消息類型<=(msgtyep的絕對(duì)值)的第一個(gè)消息;
? ? ? ? msgflg
????????????????如果包含:IPC_NOWAIT,則當(dāng)消息隊(duì)列中沒(méi)有指定類型的消息時(shí),立即返回-1;
????????????????如果不包含:IPC_NOWAIT,則當(dāng)消息隊(duì)列中沒(méi)有指定類型的消息時(shí),掛起本進(jìn)程,直到收到指定類型的消息;
返回值:
? ? ? ? 成功:返回接收到的消息的長(zhǎng)度(不包含第一個(gè)成員msg_type)
????????失敗:返回-1,并設(shè)置erron錯(cuò)誤標(biāo)志;
? ? ? ? 具體錯(cuò)誤原因可以使用命令:man 2 msgrcv 去查看.
5.?msgctl 消息的控制
一般用來(lái)關(guān)閉消息隊(duì)列!
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
描述:控制消息隊(duì)列,例如關(guān)閉消息隊(duì)列;
參數(shù):
? ? ? ? msqid
? ? ? ? ? ? ? ? 消息隊(duì)列標(biāo)識(shí)符;
? ? ? ? cmd
????????????????IPC_RMID?? 刪除消息隊(duì)列;具體其他的可以使用命令:man 2 msgctl 去查看;
? ? ? ? buf
? ? ? ? ? ? ? ? 結(jié)構(gòu)體,存儲(chǔ)消息隊(duì)列的一些信息,使用IPC_RMID傳0即可;
返回值:
????????成功:返回0;
? ? ? ? 失敗:返回-1,并設(shè)置erron錯(cuò)誤標(biāo)志;
? ? ? ? 具體錯(cuò)誤原因可以使用命令:man 2 msgctl 去查看.
6. 示例一:兩程序間通過(guò)消息隊(duì)列去通信
msg1.cpp
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MSG_SIZE 80
#define MSG_TYPE long
typedef struct MY_MSG_ST {
MSG_TYPE msg_type; // 必須定義的一個(gè)變量,用于辨認(rèn)類型
char msg[MSG_SIZE]; // 數(shù)據(jù)
}my_msg_st;
int main(int argc, char **argv) {
int msgId; // 消息隊(duì)列id
int ret;
my_msg_st msg;
// IPC_CREAT 消息隊(duì)列不存在則創(chuàng)建,存在則返回;
// IPC_CREAT | IPC_EXCL 消息隊(duì)列不存在則創(chuàng)建,存在則報(bào)錯(cuò)返回;
msgId = msgget((key_t)12356, 0666 | IPC_CREAT);
if (-1 == msgId) {
printf("msgget failed!\n");
exit(1);
}
msg.msg_type = 1; // 設(shè)置要發(fā)送的消息的類型
strcpy(msg.msg, "Hello World!");
// 發(fā)送一條消息
ret = msgsnd(msgId, &msg, sizeof(my_msg_st) - sizeof(MSG_TYPE), 0);
if (-1 == ret) {
printf("msgsnd failed!\n");
exit(2);
}
return 0;
}
msg2.cpp
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MSG_SIZE 80
#define MSG_TYPE long
typedef struct MY_MSG_ST {
MSG_TYPE msg_type; // 必須定義的一個(gè)變量,用于辨認(rèn)類型
char msg[MSG_SIZE]; // 數(shù)據(jù)
}my_msg_st;
int main(int argc, char **argv) {
int msgId; // 消息隊(duì)列id
int ret;
my_msg_st msg;
// IPC_CREAT 消息隊(duì)列不存在則創(chuàng)建,存在則返回;
// IPC_CREAT | IPC_EXCL 消息隊(duì)列不存在則創(chuàng)建,存在則報(bào)錯(cuò)返回;
msgId = msgget((key_t)12356, 0666 | IPC_CREAT);
if (-1 == msgId) {
printf("msgget failed!\n");
exit(1);
}
msg.msg_type = 0; // 設(shè)置要接收的消息的類型
// 接收一條消息
ret = msgrcv(msgId, &msg, sizeof(my_msg_st) - sizeof(MSG_TYPE), 0, 0);
if (-1 == ret) {
printf("msgrcv failed!\n");
exit(2);
}
printf("recived:%s\n", msg.msg);
// 刪除消息隊(duì)列
ret = msgctl(msgId, IPC_RMID, 0);
if (-1 == ret) {
printf("msgctl(IPC_RMID) failed!\n");
exit(3);
}
return 0;
}
7. 多進(jìn)程間消息隊(duì)列通信
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <wait.h>
#include <errno.h>
#define MSG_SIZE 80
#define MSG_TYPE long
typedef struct MY_MSG_ST {
MSG_TYPE msg_type; // 必須定義的一個(gè)變量,用于辨認(rèn)類型
char msg[MSG_SIZE]; // 數(shù)據(jù)
}my_msg_st;
int main(int argc, char **argv) {
int msgId; // 消息隊(duì)列id
int ret;
my_msg_st snd_msg; // 發(fā)送數(shù)據(jù)
my_msg_st rcv_msg; // 接收數(shù)據(jù)
pid_t pd;
// IPC_CREAT 消息隊(duì)列不存在則創(chuàng)建,存在則返回;
// IPC_CREAT | IPC_EXCL 消息隊(duì)列不存在則創(chuàng)建,存在則報(bào)錯(cuò)返回;
msgId = msgget((key_t)987, 0666 | IPC_CREAT | IPC_EXCL);
if (-1 == msgId) {
printf("msgget failed!\n");
exit(1);
}
// 創(chuàng)建子進(jìn)程
pd = fork();
if (-1 == pd) {
printf("fork error!\n");
exit(2);
} else if (0 == pd) {
rcv_msg.msg_type = 0; // 設(shè)置要接收的消息的類型
// 接收一條消息
ret = msgrcv(msgId, &rcv_msg, sizeof(my_msg_st) - sizeof(MSG_TYPE), 0, 0);
if (-1 == ret) {
printf("msgrcv failed!\n");
exit(2);
}
printf("recived:%s\n", rcv_msg.msg);
} else {
snd_msg.msg_type = 999; // 設(shè)置要發(fā)送的消息的類型
strcpy(snd_msg.msg, "This is Message!");
// 發(fā)送一條消息
ret = msgsnd(msgId, &snd_msg, sizeof(my_msg_st) - sizeof(MSG_TYPE), 0);
if (-1 == ret) {
printf("msgsnd failed!\n");
exit(3);
}
if (0 < pd) wait(NULL);
// 刪除消息隊(duì)列
ret = msgctl(msgId, IPC_RMID, NULL);
if (-1 == ret) {
printf("msgctl(IPC_RMID) failed!\n");
exit(4);
}
}
return 0;
}
8.?練習(xí)
程序1, 循環(huán)等待用戶輸入字符串,每收到一個(gè)字符串,就把它發(fā)送給進(jìn)程2,直到用戶輸入exit;
程序2, 接受進(jìn)程1發(fā)過(guò)來(lái)的信息,并打印輸出;直到接受到exit。
程序一
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MSG_SIZE 80
#define MSG_TYPE long
typedef struct MY_MSG_ST {
MSG_TYPE msg_type; // 必須定義的一個(gè)變量,用于辨認(rèn)類型
char msg[MSG_SIZE]; // 數(shù)據(jù)
}my_msg_st;
int main(int argc, char **argv) {
int msgId; // 消息隊(duì)列id
int ret;
my_msg_st msg;
// IPC_CREAT 消息隊(duì)列不存在則創(chuàng)建,存在則返回;
// IPC_CREAT | IPC_EXCL 消息隊(duì)列不存在則創(chuàng)建,存在則報(bào)錯(cuò)返回;
msgId = msgget((key_t)258, 0666 | IPC_CREAT);
if (-1 == msgId) {
printf("msgget failed!\n");
exit(1);
}
msg.msg_type = 66; // 設(shè)置要發(fā)送的消息的類型
while (1) {
fgets(msg.msg, sizeof(msg.msg), stdin);
// 發(fā)送一條消息
ret = msgsnd(msgId, &msg, sizeof(my_msg_st) - sizeof(MSG_TYPE), 0);
if (-1 == ret) {
printf("msgsnd failed!\n");
exit(2);
}
if (0 == strncmp(msg.msg, "exit", 4)) break;
}
// 刪除消息隊(duì)列
ret = msgctl(msgId, IPC_RMID, NULL);
if (-1 == ret) {
printf("msgctl(IPC_RMID) failed!\n");
exit(3);
}
return 0;
}
程序二
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MSG_SIZE 80
#define MSG_TYPE long
typedef struct MY_MSG_ST {
MSG_TYPE msg_type; // 必須定義的一個(gè)變量,用于辨認(rèn)類型
char msg[MSG_SIZE]; // 數(shù)據(jù)
}my_msg_st;
int main(int argc, char **argv) {
int msgId; // 消息隊(duì)列id
int ret;
my_msg_st msg;
// IPC_CREAT 消息隊(duì)列不存在則創(chuàng)建,存在則返回;
// IPC_CREAT | IPC_EXCL 消息隊(duì)列不存在則創(chuàng)建,存在則報(bào)錯(cuò)返回;
msgId = msgget((key_t)258, 0666 | IPC_CREAT);
if (-1 == msgId) {
printf("msgget failed!\n");
exit(1);
}
msg.msg_type = 66; // 指定接收的數(shù)據(jù)類型是66的
while (1) {
// 接收一條消息
ret = msgrcv(msgId, &msg, sizeof(my_msg_st) - sizeof(MSG_TYPE), 0, 0);
if (-1 == ret) {
printf("msgrcv failed!\n");
exit(2);
}
if (0 == strncmp(msg.msg, "exit", 4)) break;
printf("recived:%s", msg.msg);
}
return 0;
}
四、信號(hào)量
問(wèn)題:
????????程序中,有時(shí)存在一種特殊代碼,最多只允許一個(gè)進(jìn)程執(zhí)行該部分代碼。
????????這部分區(qū)域,稱為“臨界區(qū)”;
????????然而在多進(jìn)程并發(fā)執(zhí)行時(shí),當(dāng)一個(gè)進(jìn)程進(jìn)入臨界區(qū),因某種原因被掛起時(shí),其他進(jìn)程就有可能也進(jìn)入該區(qū)域。
解決辦法:使用信號(hào)量。
1. 什么是信號(hào)量
信號(hào)量,是一種特殊的變量。
只能對(duì)信號(hào)量執(zhí)行P操作和V操作;
???
??? P操作, 如果信號(hào)量的值 > 0,??? 則把該信號(hào)量減1;
?????????????? 如果信號(hào)量的值? == 0,? 則掛起該進(jìn)程。
??????????????
??? V操作:? 如果有進(jìn)程因該信號(hào)量而被掛起,則恢復(fù)該進(jìn)程運(yùn)行;
?????????????? 如果沒(méi)有進(jìn)程因該信號(hào)量而掛起,則把該信號(hào)量加1。
??????????????
??? 注意:P操作、V操作都是原子操作,即其在執(zhí)行時(shí),不會(huì)被中斷。
???
注意:此指的“信號(hào)量”是指System V? IPC的信號(hào)量,與線程所使用的信號(hào)量不同。該信號(hào)量,用于進(jìn)程間通信。
2. semget 信號(hào)量的獲取
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
描述:獲取System V信號(hào)量集標(biāo)識(shí)符;
參數(shù):
? ? ? ? key
? ? ? ? ? ? ? ? 鍵值,該鍵值對(duì)應(yīng)一個(gè)唯一的信號(hào)量。類似于共享內(nèi)存的鍵值;
????????????????不同的進(jìn)程可通過(guò)該鍵值和semget獲取唯一的信號(hào)量;
????????????????特殊鍵值:IPC_PRIVAT該信號(hào)量只允許創(chuàng)建者本身, 可用于父子進(jìn)程間通信;
? ? ? ? nsems
? ? ? ? ? ? ? ? 需要的信號(hào)量數(shù)目,一般取1;
? ? ? ? semflg
????????????????IPC_CREAT 或者?IPC_CREAT |?IPC_EXCL
????????????????IPC_CREAT 消息隊(duì)列不存在則創(chuàng)建,存在則返回;
????????????????IPC_CREAT | IPC_EXCL 消息隊(duì)列不存在則創(chuàng)建,存在則報(bào)錯(cuò)返回;
返回值:
? ? ? ? 成功,返回也給非負(fù)整數(shù);
? ? ? ? 失敗,返回-1,并設(shè)置錯(cuò)誤標(biāo)志errno;
3. semop 信號(hào)量的操作
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, unsigned nsops);
描述:改變信號(hào)量的值,即對(duì)信號(hào)量執(zhí)行 P操作 或 V操作;
參數(shù):
? ? ? ? semid
? ? ? ? ? ? ? ? 信號(hào)量標(biāo)識(shí)符,即semget的返回值;
? ? ? ? sops
? ? ? ? ? ? ? ? 是一個(gè)數(shù)組,元素類型為struct sembuf;
struct sembuf {
short sem_num; // 信號(hào)量組中的編號(hào)(即指定對(duì)哪個(gè)信號(hào)量操作)
// semget實(shí)際是獲取一組信號(hào)量
// 如果只獲取了一個(gè)信號(hào)量,則該成員取0
short sem_op; // -1, 表示P操作
// 1, 表示V操作
short sem_flg; // SEM_UNDO : 如果進(jìn)程在終止時(shí),沒(méi)有釋放信號(hào)量;
// 如果不設(shè)置指定標(biāo)志,應(yīng)該設(shè)置為0;則,自動(dòng)釋放該信號(hào)量
}
? ? ? ? nsops
? ? ? ? ? ? ? ? 表示第二個(gè)參數(shù)sops所表示的數(shù)組的大小,即表示有幾個(gè)struct sembuf;
返回值:
? ? ? ? 成功:返回0;
? ? ? ? 失?。?/span>返回-1,并設(shè)置錯(cuò)誤標(biāo)志errno;
4. semctl 信號(hào)量的控制
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, ...);
描述:對(duì)信號(hào)量進(jìn)行控制;
參數(shù):
? ? ? ? semid
? ? ? ? ? ? ? ? 信號(hào)量標(biāo)識(shí)符;
? ? ? ? semnum
? ? ? ? ? ? ? ? 信號(hào)量組中的編號(hào),如果只有一個(gè)信號(hào)量,則取值0;
? ? ? ? cmd
? ? ? ? ? ? ? ? SETVAL,把信號(hào)量初始化為指定的值,具體的值由第四個(gè)參數(shù)確定;
? ? ? ? ? ? ? ? 注意:只能對(duì)信號(hào)量初始化一次,如果在個(gè)進(jìn)程中,分別對(duì)該信號(hào)量進(jìn)行初始化,則可能會(huì)導(dǎo)致錯(cuò)誤!
? ? ? ? ? ? ? ? IPC_RMID,刪除信號(hào)量;
? ? ? ? 參數(shù)四類型為:union semun {
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? int? ? ? ? val;? ? ? ? ? ? ? ? // SETVAL 命令要設(shè)置的值
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? struct? semid_ds? ? ? ? *buf;
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? unsigned? short? ? ? ? ? *array;
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? 注意:union semun 類型要求自己定義有些Linux發(fā)行版在sys/sem.h中定義,有些發(fā)行版則沒(méi)有定義。
? ? ? ? ? ? ? ? 可自己定義如下:
????????#if defined(__GNU_LIBRARY__) && !defined(_SEM_SEMUN_UNDEFINED)???????????????????? ????????????? ??
????????#else
????????????????????????union semun {
????????????????????????????????int val;????????????????????????????
????????????????????????????????struct semid_ds *buf;???
????????????????????????????????unsigned short int *array;
?????????????????????????????????struct seminfo *__buf;?
????????????????????????};
????????#endif????
返回值:
? ? ? ? 成功:大部分返回0;
? ? ? ? 失?。?/span>返回-1,并設(shè)置錯(cuò)誤標(biāo)志;
5. 例
首先看下面的代碼,父進(jìn)程創(chuàng)建一個(gè)子進(jìn)程后,一起運(yùn)行for循環(huán)...
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
int main(void) {
int i = 0;
pid_t pd = fork();
for (i = 0; i < 5; i++) {
// 模擬臨界區(qū) - begin
printf("Process(%d) In\n", getpid());
sleep(1);
printf("Process(%d) Out\n", getpid());
// 模擬臨界區(qū) - end
sleep(1);
}
return 0;
}
假設(shè)把for循環(huán)中sleep(1)比作是廁所的話,父進(jìn)程進(jìn)去了,肯定不希望子進(jìn)程也進(jìn)去,要等父進(jìn)程出來(lái),子進(jìn)程才能進(jìn)去才對(duì);但是上面的程序是都擠進(jìn)去了,所以下面使用信號(hào)量改良一下!
#include <sys/types.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#if defined(__GNU_LIBRARY__) && !defined(_SEM_SEMUN_UNDEFINED)
#else
union semun {
int val;
struct semid_ds *buf;
unsigned short int *array;
struct seminfo *__buf;
};
#endif
static int sem_initial(int semid) {
int ret;
union semun semun;
semun.val = 1; // 信號(hào)量置為1
ret = semctl(semid, 0, SETVAL, semun);
if (-1 == ret) {
fprintf(stderr, "sectl failed!\n");
}
return ret;
}
static int sem_p(int semid) {
int ret;
struct sembuf sembuf;
sembuf.sem_op = -1; // 信號(hào)量減一
sembuf.sem_num = 0;
sembuf.sem_flg = SEM_UNDO;
ret = semop(semid, &sembuf, 1);
if (-1 == ret) {
fprintf(stderr, "sem_p failed!\n");
}
return ret;
}
static int sem_v(int semid) {
int ret;
struct sembuf sembuf;
sembuf.sem_op = 1; // 信號(hào)量加一
sembuf.sem_num = 0;
sembuf.sem_flg = SEM_UNDO;
ret = semop(semid, &sembuf, 1);
if (-1 == ret) {
fprintf(stderr, "sem_v failed!\n");
}
return ret;
}
int main(int argc, char **argv) {
int i;
int ret;
int semid;
/* 獲取信號(hào)量 */
semid = semget((key_t)1234, 1, 0666|IPC_CREAT);
if (-1 == ret) {
fprintf(stderr, "semget failed!\n");
exit(1);
}
/* 初始化信號(hào)量 */
if (argc > 1) {
ret = sem_initial(semid);
if (-1 == ret) {
exit(2);
}
}
for (i = 0; i < 5; i++) {
if (-1 == sem_p(semid)) {
exit(3);
}
// 進(jìn)入臨界區(qū)
printf("Process(%d) In\n", getpid());
sleep(3);
printf("Process(%d) Out\n", getpid());
// 退出臨界區(qū)
if (-1 == sem_v(semid)) {
exit(4);
}
//sleep(1);
}
sleep(4);
if (argc > 1) {
/* 刪除信號(hào)量 */
union semun semun;
ret = semctl(semid, 0, IPC_RMID, 0);
if (-1 == ret) {
fprintf(stderr, "semctl failed! reason: %s\n", strerror(errno));
exit(5);
}
}
return 0;
}
?父進(jìn)程In后,子進(jìn)程只能等待,等到父進(jìn)程Out之后,子進(jìn)程才能In;子進(jìn)程In后,父進(jìn)程也只能等待,等到子進(jìn)程Out之后,父進(jìn)程才能In;
這就是信號(hào)量設(shè)置的臨界區(qū)的效果,阻止兩個(gè)進(jìn)程同時(shí)訪問(wèn)同一段代碼!
有點(diǎn)像線程加鎖和解鎖!
五、共享內(nèi)存機(jī)制
由于篇幅太大了,這部分留到下一篇博客中介紹;共享內(nèi)存這效率挺高的,項(xiàng)目中也常用!
六、總結(jié)
信號(hào) 與 管道 與 消息隊(duì)列 與信號(hào)量 的基本用法已經(jīng)整理完畢,實(shí)際用法也在代碼中表現(xiàn)出來(lái)了!文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-430215.html
這里只是將一些入門用法寫下來(lái),具體還得自己取深入研究!文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-430215.html
到了這里,關(guān)于Linux進(jìn)程間通信 - 信號(hào)(signal) 與 管道(pipe) 與 消息隊(duì)列的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!