Linux進(jìn)程間通信(中)之信號、信號量實踐
上節(jié)我們分享了Linux進(jìn)程間通信的其中兩種方式:管道、消息隊列,文章如下:
Linux進(jìn)程間通信(上)之管道、消息隊列實踐
這節(jié)我們就來分享一下Linux的另外兩種進(jìn)程間通信的方式:信號、信號量。
1、信號
我們使用過windows的都知道,當(dāng)一個程序被卡死的時候不管怎樣都沒反應(yīng),這樣我們就可以打開任務(wù)管理器直接強制性的結(jié)束這個進(jìn)程,這個方法的實現(xiàn)就是和Linux上通過生成信號和捕獲信號來實現(xiàn)相似的,運行過程中進(jìn)程捕獲到這些信號做出相應(yīng)的操作使最終被終止。
信號的主要來源是分為兩部分,一部分是硬件來源,一部分是軟件來源;進(jìn)程在實際中可以用三種方式來響應(yīng)一個信號:一是忽略信號,不對信號做任何操作,其中有兩個信號是不能別忽略的分別是SIGKILL和SIGSTOP。二是捕捉信號,定義信號處理函數(shù),當(dāng)信號來到時做出響應(yīng)的處理。三是執(zhí)行缺省操作,Linux對每種信號都規(guī)定了默認(rèn)操作。注意,進(jìn)程對實時信號的缺省反應(yīng)是立即終止。
發(fā)送信號的函數(shù)有很多,主要使用的有:kill()、raise()、abort()、alarm()
。
先來熟悉下kill函數(shù),進(jìn)程可以通過kill()函數(shù)向包括它本身在內(nèi)的其它進(jìn)程發(fā)送一個信號,如果程序沒有發(fā)送這個信號的權(quán)限,對kill函數(shù)的調(diào)用將會失敗,失敗的原因通常是由于目標(biāo)進(jìn)程由另一個用戶所擁有。
kill函數(shù)的原型為:
#include<sys/types.h>
#include<signal.h>
int kill(pid_t pid,int sig);
它的作用是把信號sig發(fā)送給進(jìn)程號為pid的進(jìn)程,成功時返回0。kill調(diào)用失敗返回-1,調(diào)用失敗通常有三大原因:
-
1、給定的信號無效 -
2、發(fā)送權(quán)限不夠 -
3、目標(biāo)進(jìn)程不存在
還有一個非常重要的函數(shù),信號處理signal函數(shù)。程序可以用signal函數(shù)來處理指定的信號,主要通過恢復(fù)和忽略默認(rèn)行為來操作。signal函數(shù)原型如下:
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
我們來看一個例程了解一下signal函數(shù)。signal.c
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
//函數(shù)ouch對通過參數(shù)sig傳遞進(jìn)來的信號作出響應(yīng)。
void ouch(int sig)
{
printf("signal %d\n", sig);
//恢復(fù)終端中斷信號SIGINT的默認(rèn)行為
(void) signal(SIGINT, SIG_DFL);
}
int main()
{
//改變終端中斷信號SIGINT的默認(rèn)行為,使之執(zhí)行ouch函數(shù)
(void) signal(SIGINT, ouch);
while(1)
{
printf("Hello World!\n");
sleep(1);
}
return 0;
}
運行結(jié)果:
可以看出當(dāng)我按下ctrl+c的時候并不會退出,只有當(dāng)再次按下ctrl+c的時候才會退出。造成的原因是因為SIGINT的默認(rèn)行為被signal函數(shù)改變了,當(dāng)進(jìn)程接受到信號SIGINT時,它就去調(diào)用函數(shù)ouch去處理,注意ouch函數(shù)把信號SIGINT的處理方式改變成默認(rèn)的方式,所以當(dāng)你再按一次ctrl+c時,進(jìn)程就像之前那樣被終止了。
下面是幾種常見的信號:
-
SIGHUP :從終端上發(fā)出的結(jié)束信號 -
SIGINT :來自鍵盤的中斷信號 ( ctrl + c ) -
SIGKILL :該信號結(jié)束接收信號的進(jìn)程 -
SIGTERM:kill 命令發(fā)出的信號 -
SIGCHLD:標(biāo)識子進(jìn)程停止或結(jié)束的信號 -
SIGSTOP:來自鍵盤 ( ctrl + z ) 或調(diào)試程序的停止執(zhí)行信號。
信號發(fā)送主要函數(shù)有kill和raise。上面我們知道kill函數(shù)的用法也清楚kill函數(shù)是可以向自身發(fā)送信號和其它進(jìn)程發(fā)送信號,raise與之不同的是只可以向本身發(fā)送信號。
通過raise函數(shù)向自身發(fā)送數(shù)據(jù),使子進(jìn)程暫停通過測試如下: raise.c
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
int main()
{
pid_t pid;
int ret;
if((pid=fork())<0)
{
printf("Fork error\n");
exit(1);
}
//子進(jìn)程
if(pid==0)
{
//在子進(jìn)程中使用raise()函數(shù)發(fā)出SIGSTOP信號,使子進(jìn)程暫停
printf("I am child pid:%d.I am waiting for any signal\n",getpid());
raise(SIGSTOP);
printf("I am child pid:%d.I am killed by progress:%d\n",getpid(),getppid());
exit(0);
}
//父進(jìn)程
else
{
sleep(2);
//在父進(jìn)程中收集子進(jìn)程發(fā)出的信號,并調(diào)用kill()函數(shù)進(jìn)行相應(yīng)的操作
if((waitpid(pid,NULL,WNOHANG))==0)
{
//若pid指向的子進(jìn)程沒有退出,則返回0,且父進(jìn)程不阻塞,繼續(xù)執(zhí)行下邊的語句
if((ret=kill(pid,SIGKILL))==0)
{
printf("I am parent pid:%d.I am kill %d\n",getpid(),pid);
}
}
//等待子進(jìn)程退出,否則就一直阻塞
waitpid(pid,NULL,0);
exit(0);
}
}
當(dāng)調(diào)用raise的時候子進(jìn)程就會暫停:
信號是對終端機(jī)的一種模擬,也是一種異步通信方式。
2、信號量
主要作為進(jìn)程間,以及同一進(jìn)程不同線程之間的同步手段。信號量是用來解決進(jìn)程之間的同步與互斥問題的一種進(jìn)程之間的通信機(jī)制,包括一個稱為信號量的變量和在該信號量下等待資源的進(jìn)程等待隊列,以及對信號量進(jìn)行的兩個原子操作。信號量對應(yīng)于某一種資源,取一個非負(fù)的整形值。信號量的值是指當(dāng)前可用的資源數(shù)量。
由于信號量只有兩種操作,一種是等待信號,另一種是發(fā)送信號。即P和V,它們的行為如下:
-
P(sv):如果sv的值大于零,就給它減1;如果它的值為零,就掛起該進(jìn)程的執(zhí)行。 -
V(sv):如果有其他進(jìn)程因等待sv而被掛起,就讓它恢復(fù)運行,如果沒有進(jìn)程因等待sv而掛起,就給它加1。
Linux特別提供了一組信號量接口來對信號操作,它們不只是局限的針對二進(jìn)制信號量,下面我們來對每個函數(shù)介紹,需要注意的是這些函數(shù)都是用來成對組的信號量值進(jìn)行操作的。
2.1、semget函數(shù)
它的作用是創(chuàng)建一個新信號量或取得一個已有信號量。
int semget(key_t key, int nsems, int semflg);
第一個參數(shù)是key整數(shù)型,不相關(guān)的進(jìn)程可以通過它訪問一個信號量,它代表程序可能要使用的某個資源,程序?qū)λ行盘柫康脑L問都是間接的,先通過調(diào)用semget函數(shù)并提供一個鍵,再由系統(tǒng)生成一個相應(yīng)的信號標(biāo)識符(semget函數(shù)的返回值),只有semget函數(shù)才直接使用信號量鍵,所有其他的信號量函數(shù)使用由semget函數(shù)返回的信號量標(biāo)識符。如果多個程序使用相同的key值,key將負(fù)責(zé)協(xié)調(diào)工作。
第二個參數(shù)是制定需要的信號數(shù)量,通常情況下為1。
第三個參數(shù)是一組標(biāo)志位,當(dāng)想要當(dāng)信號量不存在時創(chuàng)建一個新的信號量,可以和值IPC_CREAT做按位或操作。設(shè)置了IPC_CREAT標(biāo)志后,即使給出的鍵是一個已有信號量的鍵,也不會產(chǎn)生錯誤。而IPC_CREAT | IPC_EXCL則可以創(chuàng)建一個新的,唯一的信號量,如果信號量已存在,返回一個錯誤。
semget函數(shù)成功返回一個相應(yīng)信號標(biāo)識符(非零),失敗返回-1。
2.2、semop函數(shù)
它的作用是改變信號量的值。
int semop(int semid, struct sembuf *sops, unsigned nsops);
sops是一個指針,它指向這樣一個數(shù)組:元素用來描述對semid代表的信號量集合中第幾個信號進(jìn)行怎么樣的操作。nops規(guī)定該數(shù)組中操作的數(shù)量。
semop函數(shù)返回0表示成功,返回-1表示失敗。
2.3、semctl函數(shù)
該函數(shù)用來直接控制信號量信息。
int semctl(int semid, int semnum, int cmd, …);
semget并不會初始化每個信號量的值,這個初始化必須通過SETVAL命令或SETALL命令調(diào)用semctl來完成。
例程:semctl.c
#include <stdio.h>
#include <linux/sem.h>
#define NUMS 10
int get_sem_val(int sid,int semnum)//取得當(dāng)前信號量
{
return(semctl(sid,semnum,GETVAL,0));
}
int main(void)
{
int I ;
int sem_id;
int pid;
int ret;
struct sembuf sem_op;//信號集結(jié)構(gòu)
union semun sem_val;//信號量數(shù)值
//建立信號量集,其中只有一個信號量
sem_id = semget(IPC_PRIVATE,1,IPC_CREAT|0600);
//IPC_PRIVATE私有,只有本用戶使用,如果為正整數(shù),則為公共的;1為信號集的數(shù)量;
if (sem_id==-1)
{
printf("create sem error!\n");
exit(1);
}
printf("create %d sem success!\n",sem_id);
//信號量初始化
sem_val.val=1;
//設(shè)置信號量,0為第一個信號量,1為第二個信號量,...以此類推;SETVAL表示設(shè)置
ret = semctl(sem_id,0,SETVAL,sem_val);
if (ret < 0){
printf("initlize sem error!\n");
exit(1);
}
//創(chuàng)建進(jìn)程
pid = fork();
if (pid < 0)
{
printf("fork error!\n");
exit(1);
}
else if(pid == 0)
{
//一個子進(jìn)程,使用者
for ( i=0;i<NUMS;i++)
{
sem_op.sem_num=0;
sem_op.sem_op=-1;
sem_op.sem_flg=0;
semop(sem_id,&sem_op,1);//操作信號量,每次-1
printf("%d 使用者: %d\n",i,get_sem_val(sem_id,0));
}
}
else
{
//父進(jìn)程,制造者
for (i=0;i<NUMS;i++)
{
sem_op.sem_num=0;
sem_op.sem_op=1;
sem_op.sem_flg=0;
semop(sem_id,&sem_op,1);//操作信號量,每次+1
printf("%d 制造者: %d\n",i,get_sem_val(sem_id,0));
}
}
exit(0);
}
運行結(jié)果:
信號量的出現(xiàn)就是保證資源在一個時刻只能有一個進(jìn)程(線程),所以例子當(dāng)中只有制造者在制造(+1操作)過程中,使用者這個進(jìn)程是無法隨sem_id進(jìn)行操作的。也就是說信號量是協(xié)調(diào)進(jìn)程對共享資源操作的,起到了類似互斥鎖的作用,但卻比鎖擁有更強大的功能。
往期精彩
C語言三劍客之《C陷阱與缺陷》一書精華提煉
C語言三劍客之《C專家編程》一書精華提煉
【為宏正名】99%人都不知道的"##"里用法
【Linux系統(tǒng)編程】可重入和不可重入函數(shù)
覺得本次分享的文章對您有幫助,隨手點[在看]
并轉(zhuǎn)發(fā)分享,也是對我的支持。
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺僅提供信息存儲服務(wù)。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!