信號概述
- 信號是UNIX中所使用的進程通信的一種最古老的方法升酣。它是在軟件層次上對中斷機制的一種模擬,是一種異步通信方式态罪。信號可以直接進行用戶空間進程和內(nèi)核進程之間的交互噩茄,內(nèi)核進程也可以利用它來通知用戶空間進程發(fā)生了哪些系統(tǒng)事件。它可以在任何時候發(fā)給某一進程向臀,而無需知道該進程的狀態(tài)。如果該進程當(dāng)前并未處于執(zhí)行態(tài)诸狭,則該信號就由內(nèi)核保存起來券膀,直到該進程恢復(fù)執(zhí)行再傳遞給它為止君纫。
-
一個完整的信號生命周期可以分為3個重要階段,這3個階段由4個重要事件來刻畫的:包括信號產(chǎn)生芹彬、信號在進程中注冊蓄髓、信號在進程中注銷、執(zhí)行信號處理對應(yīng)的函數(shù) 舒帮。
信號常見的處理函數(shù)
-
信號的處理包括信號的發(fā)送会喝、捕捉和處理,它們有各自相對應(yīng)的常見函數(shù):
發(fā)生信號的函數(shù): kill()玩郊、raise()肢执。
捕捉信號的函數(shù): alarm()、pause()译红。
處理信號的函數(shù): signal()预茄、sigaction()。
- 信號發(fā)送函數(shù)
- kill()函數(shù)同讀者熟知的kill系統(tǒng)命令一樣侦厚,可以發(fā)送信號給進程或進程組(實際上耻陕,kill系統(tǒng)命令只是kill()函數(shù)的一個用戶接口)。這里需要注意的是刨沦,它不僅可以中止進程(實際上發(fā)出SIGKILL信號)诗宣,也可以向進程發(fā)送其他信號。
-
與kill()函數(shù)所不同的是想诅,raise()函數(shù)允許進程向自身發(fā)送信號召庞。
- 代碼實戰(zhàn)
kill_raise.c
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<signal.h>
int main()
{
int ret;
pid_t pid,pid_wait;
pid=fork();
if(pid<0)
{
printf("fork error!\n");
return -1;
}
else if(pid==0)
{
printf("In child(%d) process,waiting for any signal\n",getpid());
raise(SIGSTOP);
exit(0);
}
else
{
if(pid_wait=waitpid(pid,NULL,WUNTRACED))
{
printf("Child(%d) is stop\n",pid_wait);
if((ret=kill(pid,SIGKILL))==0)
printf("In father process:\nParent kill process(%d)\n",pid);
}
/*
if((waitpid(pid,NULL,WNOHANG))==0)
{
if((ret=kill(pid,SIGKILL))==0)
printf("In father process:\nParent kill process(%d)\n",pid);
}
*/
waitpid(pid,NULL,0);
exit(0);
}
return 0;
}
-
信號捕捉函數(shù)
alarm()也稱為鬧鐘函數(shù),它可以在進程中設(shè)置一個定時器侧蘸,當(dāng)定時器指定的時間到時裁眯,它就向進程發(fā)送SIGALARM信號。如果不忽略或不捕捉此信號讳癌,則其默認動作是終止該進程穿稳。要注意的是,一個進程只能有一個鬧鐘時間晌坤,如果在調(diào)用alarm()之前已設(shè)置過鬧鐘時間逢艘,則任何以前的鬧鐘時間都被新值所代替。
pause()函數(shù)是用于將調(diào)用進程掛起直至捕捉到信號為止骤菠。這個函數(shù)很常用它改,通常可以用于判斷信號是否已到商乎。
-
信號處理函數(shù)
信號處理的主要方法有兩種央拖,一種是使用簡單的signal()函數(shù),另一種是使用信號集函數(shù)組。
信號響應(yīng)方式
- 用戶進程對信號的響應(yīng)可以有3種方式鲜戒。
忽略信號专控,即對信號不做任何處理,但是有兩個信號不能忽略遏餐,即SIGKILL及SIGSTOP伦腐。
捕捉信號,定義信號處理函數(shù)失都,當(dāng)信號發(fā)生時柏蘑,執(zhí)行相應(yīng)的自定義處理函數(shù)。
執(zhí)行缺省操作粹庞,Linux對每種信號都規(guī)定了默認操作咳焚。
共享內(nèi)存
- 共享內(nèi)存是一種最為高效的進程間通信方式,進程可以直接讀寫內(nèi)存信粮,而不需要任何數(shù)據(jù)的拷貝
- 為了在多個進程間交換信息黔攒,內(nèi)核專門留出了一塊內(nèi)存區(qū),可以由需要訪問的進程將其映射到自己的私有地址空間
- 進程就可以直接讀寫這一內(nèi)存區(qū)而不需要進行數(shù)據(jù)的拷貝强缘,從而大大提高的效率督惰。
- 由于多個進程共享一段內(nèi)存,因此也需要依靠某種同步機制旅掂,如互斥鎖和信號量等
-
Shell的ipcs命令可以查看共享內(nèi)存情況
-
共享內(nèi)存使用步驟
1)創(chuàng)建/打開共享內(nèi)存赏胚;
2)映射共享內(nèi)存,即把指定的共享內(nèi)存映射到進程的地址空間用于訪問商虐;
3)分離/撤銷共享內(nèi)存映射觉阅;
4)刪除共享內(nèi)存對象;
-
共享內(nèi)存創(chuàng)建
key : 和信號量一樣秘车,程序需要提供一個參數(shù)key,它有效地為共享內(nèi)存段命名典勇。有一個特殊的鍵值IPC_PRIVATE, 它用于創(chuàng)建一個只屬于創(chuàng)建進程的共享內(nèi)存,僅用于有親緣關(guān)系的進程間通信叮趴。
size: 以字節(jié)為單位指定需要共享的內(nèi)存容量割笙。
shmflag: 包含9個比特的權(quán)限標(biāo)志,它們的作用與創(chuàng)建文件時使用的mode標(biāo)志是一樣眯亦。
讀寫的權(quán)限還有IPC_CREAT或IPC_EXCL對應(yīng)文件的O_CREAT或O_EXCL
權(quán)限標(biāo)志對共享內(nèi)存非常有用伤溉,因為它允許一個進程創(chuàng)建的共享內(nèi)存可以被共享內(nèi)存的創(chuàng)建者所擁有的進程寫入,同時其它用戶創(chuàng)建的進程只能讀取共享內(nèi)存妻率。
我們可以利用這個功能來提供一種有效的對數(shù)據(jù)進行只讀訪問的方法乱顾,通過將數(shù)據(jù)放共享內(nèi)存并設(shè)置它的權(quán)限,就可以避免數(shù)據(jù)被其他用戶修改宫静。 - ftok
同一段程序走净,為保證兩個不同用戶下的兩組相同程序獲得互不干擾的IPC鍵值券时,通常利用ftok函數(shù)將文件節(jié)點和一個id值組合生成一個唯一的IPC鍵值。
ftok原型如下:key_t ftok( char * fname, int id )
fname就時你指定的文件名(該文件必須是存在而且可以訪問的)伏伯,
id是子序號革为,雖然為int,但是只有8個比特被使用(0-255)舵鳞。
當(dāng)成功執(zhí)行的時候,一個key_t值將會被返回琢蛤,否則 -1 被返回蜓堕。
使用 ftok創(chuàng)建共享內(nèi)存,毫無關(guān)系的進程也可以通過得到同樣的key博其,來操作同一個共享內(nèi)存套才,在對共享內(nèi)存進行讀寫時,需要利用信號量進行同步或互斥慕淡。
使用IPC_PRIVATE創(chuàng)建的IPC對象, 和無名管道類似背伴,只可以用于有親緣關(guān)系的進程間通信。 -
共享內(nèi)存映射函數(shù)
作用:將共享內(nèi)存映射到本進程地址空間峰髓,以實現(xiàn)本進程對該共享內(nèi)存區(qū)的訪問傻寂。
注意:共享內(nèi)存的讀寫權(quán)限由它的屬主(共享內(nèi)存的創(chuàng)建者)決定,它的訪問權(quán)限由當(dāng)前進程的屬主決定携兵。
-
共享內(nèi)存分離
作用:將共享內(nèi)存從當(dāng)前進程空間分離疾掰。
注意:共享內(nèi)存分離并未刪除它,只是使得該共享內(nèi)存對當(dāng)前進程不再可用徐紧。
-
共享內(nèi)存控制
函數(shù)作用:實現(xiàn)對共享內(nèi)存的控制
shmid_ds結(jié)構(gòu)至少包含以下成員:
struct shmid_ds
{
uid_t shm_perm.uid;
uid_t shm_perm.gid;
mode_t shm_perm.mode;
};
- 共享內(nèi)存代碼實戰(zhàn):
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#define BUFFER_SIZE 1024
struct share_mm//共享內(nèi)存結(jié)構(gòu)體
{
int flag_wrote;
char buf[BUFFER_SIZE];
};
int main()
{
int shmid;
pid_t pid;
struct share_mm * shmaddr;//保存映射地址
//創(chuàng)建共享內(nèi)存
shmid = shmget(IPC_PRIVATE,sizeof(struct share_mm),0666);
if(shmid==-1)
{
printf("shmget error\n");
exit(1);
}
else
{
printf("Shmid is %d\n",shmid);
system("ipcs -m");
}
//創(chuàng)建子進程
pid = fork();
if(pid==-1)
{
printf("fork error\n");
exit(1);
}
else if(pid==0)//in child process
{
shmaddr=shmat(shmid,0,0);//映射静檬,并獲得映射地址
if(shmaddr==(void *)-1)
{
printf("shmat error\n");
exit(1);
}
else
{
printf("Child attach shm is %p\n",shmaddr);
system("ipcs -m");
}
do
{
if(shmaddr->flag_wrote!=1)//判斷父進程是否寫數(shù)據(jù)到共享內(nèi)存
{
printf("Wait father write message!!\n");
while(shmaddr->flag_wrote!=1);//等待父進程寫數(shù)據(jù)
printf("From father message:%s\n",shmaddr->buf);
shmaddr->flag_wrote=0;//標(biāo)記數(shù)據(jù)已讀走
}
}while(strncmp(shmaddr->buf,"quit",4));
printf("Father byebye\n");
if((shmdt(shmaddr))<0)//刪除地址映射
{
printf("shmdt error\n");
exit(1);
}
exit(0);
}
else //in fater process
{
shmaddr=shmat(shmid,0,0);
if(shmaddr==(void *)-1)
{
printf("shmat error\n");
exit(1);
}
else
{
printf("Father attach shm is %p\n",shmaddr);
system("ipcs -m");
}
shmaddr->flag_wrote=0;
do
{
if(shmaddr->flag_wrote==0)//判斷數(shù)據(jù)是否被子進程讀走
{
memset((void *)shmaddr->buf,0,BUFFER_SIZE);
printf("In father process:\nPlease write message\n");
gets(shmaddr->buf);//從鍵盤寫入數(shù)據(jù)到共享內(nèi)存
shmaddr->flag_wrote=1;//標(biāo)記已寫數(shù)據(jù)
}
}while(strncmp(shmaddr->buf,"quit",4));
waitpid(pid,NULL,0);//等待子進程先退出
printf("Child byebye\n");
if((shmdt(shmaddr))<0)
{
printf("shmdt error\n");
exit(1);
}
if((shmctl(shmid,IPC_RMID,NULL))<0)//刪除內(nèi)核中的共享內(nèi)存
{
printf("shmctl error\n");
exit(1);
}
exit(0);
}
return 0;
}