一、進程間通信概述
進程通信有如下一些目的:
A、數(shù)據(jù)傳輸:一個進程需要將它的數(shù)據(jù)發(fā)送給另一個進程,發(fā)送的數(shù)據(jù)量在一個字節(jié)到幾M字節(jié)之間
B、共享數(shù)據(jù):多個進程想要操作共享數(shù)據(jù),一個進程對共享數(shù)據(jù)的修改,別的進程應該立刻看到。
C、通知事件:一個進程需要向另一個或一組進程發(fā)送消息,通知它(它們)發(fā)生了某種事件(如進程終止時要通知父進程)。
D、資源共享:多個進程之間共享同樣的資源。為了作到這一點,需要內核提供鎖和同步機制。
E、進程控制:有些進程希望完全控制另一個進程的執(zhí)行(如Debug進程),此時控制進程希望能夠攔截另一個進程的所有陷入和異常,并能夠及時知道它的狀態(tài)改變。
Linux 進程間通信(IPC)以下以幾部分發(fā)展而來:
早期UNIX進程間通信、基于System V進程間通信、基于Socket進程間通信和POSIX進程間通信。
UNIX進程間通信方式包括:管道、FIFO、信號。
System V進程間通信方式包括:System V消息隊列、System V信號燈、System V共享內存、
POSIX進程間通信包括:posix消息隊列、posix信號燈、posix共享內存。
現(xiàn)在linux使用的進程間通信方式:
(1)管道(pipe)和有名管道(FIFO)
(2)信號(signal)
(3)消息隊列
(4)共享內存
(5)信號量
(6)套接字(socket)
進程通信有如下一些目的:
A、數(shù)據(jù)傳輸:一個進程需要將它的數(shù)據(jù)發(fā)送給另一個進程,發(fā)送的數(shù)據(jù)量在一個字節(jié)到幾M字節(jié)之間
B、共享數(shù)據(jù):多個進程想要操作共享數(shù)據(jù),一個進程對共享數(shù)據(jù)的修改,別的進程應該立刻看到。
C、通知事件:一個進程需要向另一個或一組進程發(fā)送消息,通知它(它們)發(fā)生了某種事件(如進程終止時要通知父進程)。
D、資源共享:多個進程之間共享同樣的資源。為了作到這一點,需要內核提供鎖和同步機制。
E、進程控制:有些進程希望完全控制另一個進程的執(zhí)行(如Debug進程),此時控制進程希望能夠攔截另一個進程的所有陷入和異常,并能夠及時知道它的狀態(tài)改變。
Linux 進程間通信(IPC)以下以幾部分發(fā)展而來:
早期UNIX進程間通信、基于System V進程間通信、基于Socket進程間通信和POSIX進程間通信。
UNIX進程間通信方式包括:管道、FIFO、信號。
System V進程間通信方式包括:System V消息隊列、System V信號燈、System V共享內存、
POSIX進程間通信包括:posix消息隊列、posix信號燈、posix共享內存。
現(xiàn)在linux使用的進程間通信方式:
(1)管道(pipe)和有名管道(FIFO)
(2)信號(signal)
(3)消息隊列
(4)共享內存
(5)信號量
(6)套接字(socket)
二、管道通信
普通的Linux shell都允許重定向,而重定向使用的就是管道。例如:
ps | grep vsftpd .管道是單向的、先進先出的、無結構的、固定大小的字節(jié)流,它把一個進程的標準輸出和另一個進程的標準輸入連接在一起。寫進程在管道的尾端寫入數(shù)據(jù),讀進程在管道的道端讀出數(shù)據(jù)。數(shù)據(jù)讀出后將從管道中移走,其它讀進程都不能再讀到這些數(shù)據(jù)。管道提供了簡單的流控制機制。進程試圖讀空管道時,在有數(shù)據(jù)寫入管道前,進程將一直阻塞。同樣,管道已經(jīng)滿時,進程再試圖寫管道,在其它進程從管道中移走數(shù)據(jù)之前,寫進程將一直阻塞。管道主要用于不同進程間通信。
ps | grep vsftpd .管道是單向的、先進先出的、無結構的、固定大小的字節(jié)流,它把一個進程的標準輸出和另一個進程的標準輸入連接在一起。寫進程在管道的尾端寫入數(shù)據(jù),讀進程在管道的道端讀出數(shù)據(jù)。數(shù)據(jù)讀出后將從管道中移走,其它讀進程都不能再讀到這些數(shù)據(jù)。管道提供了簡單的流控制機制。進程試圖讀空管道時,在有數(shù)據(jù)寫入管道前,進程將一直阻塞。同樣,管道已經(jīng)滿時,進程再試圖寫管道,在其它進程從管道中移走數(shù)據(jù)之前,寫進程將一直阻塞。管道主要用于不同進程間通信。
管道創(chuàng)建與關閉
創(chuàng)建一個簡單的管道,可以使用系統(tǒng)調用pipe()。它接受一個參數(shù),也就是一個包括兩個整數(shù)的數(shù)組。如果系統(tǒng)調用成功,此數(shù)組將包括管道使用的兩個文件描述符。創(chuàng)建一個管道之后,一般情況下進程將產(chǎn)生一個新的進程。
系統(tǒng)調用:pipe();
原型:int pipe(int fd[2]);
返回值:如果系統(tǒng)調用成功,返回0。如果系統(tǒng)調用失敗返回-1:
errno=EMFILE(沒有空親的文件描述符)
????? EMFILE(系統(tǒng)文件表已滿)
????? EFAULT(fd數(shù)組無效)
注意:fd[0]用于讀取管道,fd[1]用于寫入管道。
圖見附件
管道的創(chuàng)建
#include
#include
#include
#include
int main()
{
int pipe_fd[2];
if(pipe(pipe_fd)<0){
printf("pipe create error\n");
return -1;
}
else
printf("pipe create success\n");
close(pipe_fd[0]);
close(pipe_fd[1]);
}
{
int pipe_fd[2];
if(pipe(pipe_fd)<0){
printf("pipe create error\n");
return -1;
}
else
printf("pipe create success\n");
close(pipe_fd[0]);
close(pipe_fd[1]);
}
管道的讀寫
管道主要用于不同進程間通信。實際上,通常先創(chuàng)建一個管道,再通過fork函數(shù)創(chuàng)建一個子進程。圖見附件。
管道主要用于不同進程間通信。實際上,通常先創(chuàng)建一個管道,再通過fork函數(shù)創(chuàng)建一個子進程。圖見附件。
子進程寫入和父進程讀的命名管道:圖見附件
管道讀寫注意事項:
可以通過打開兩個管道來創(chuàng)建一個雙向的管道。但需要在子理程中正確地設置文件描述符。必須在系統(tǒng)調用fork()中調用pipe(),否則子進程將不會繼承文件描述符。當使用半雙工管道時,任何關聯(lián)的進程都必須共享一個相關的祖先進程。因為管道存在于系統(tǒng)內核之中,所以任何不在創(chuàng)建管道的進程的祖先進程之中的進程都將無法尋址它。而在命名管道中卻不是這樣。管道實例見:pipe_rw.c
可以通過打開兩個管道來創(chuàng)建一個雙向的管道。但需要在子理程中正確地設置文件描述符。必須在系統(tǒng)調用fork()中調用pipe(),否則子進程將不會繼承文件描述符。當使用半雙工管道時,任何關聯(lián)的進程都必須共享一個相關的祖先進程。因為管道存在于系統(tǒng)內核之中,所以任何不在創(chuàng)建管道的進程的祖先進程之中的進程都將無法尋址它。而在命名管道中卻不是這樣。管道實例見:pipe_rw.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main()
{
int pipe_fd[2];
pid_t pid;
char buf_r[100];
char* p_wbuf;
int r_num;
{
int pipe_fd[2];
pid_t pid;
char buf_r[100];
char* p_wbuf;
int r_num;
memset(buf_r,0,sizeof(buf_r));數(shù)組中的數(shù)據(jù)清0;
if(pipe(pipe_fd)<0){
printf("pipe create error\n");
return -1;
}
printf("pipe create error\n");
return -1;
}
if((pid=fork())==0){
printf("\n");
close(pipe_fd[1]);
sleep(2);
if((r_num=read(pipe_fd[0],buf_r,100))>0){
printf("%d numbers read from be pipe is %s\n",r_num,buf_r);
}
close(pipe_fd[0]);
exit(0);
}else if(pid>0){
close(pipe_fd[0]);
if(write(pipe_fd[1],"Hello",5)!=-1)
printf("parent write success!\n");
if(write(pipe_fd[1]," Pipe",5)!=-1)
printf("parent wirte2 succes!\n");
close(pipe_fd[1]);
sleep(3);
waitpid(pid,NULL,0);
exit(0);
}
}
printf("\n");
close(pipe_fd[1]);
sleep(2);
if((r_num=read(pipe_fd[0],buf_r,100))>0){
printf("%d numbers read from be pipe is %s\n",r_num,buf_r);
}
close(pipe_fd[0]);
exit(0);
}else if(pid>0){
close(pipe_fd[0]);
if(write(pipe_fd[1],"Hello",5)!=-1)
printf("parent write success!\n");
if(write(pipe_fd[1]," Pipe",5)!=-1)
printf("parent wirte2 succes!\n");
close(pipe_fd[1]);
sleep(3);
waitpid(pid,NULL,0);
exit(0);
}
}
標準流管道
與linux中文件操作有文件流的標準I/O一樣,管道的操作也支持基于文件流的模式。接口函數(shù)如下:
庫函數(shù):popen();
原型:FILE *open (char *command,char *type);
返回值:如果成功,返回一個新的文件流。如果無法創(chuàng)建進程或者管道,返回NULL。管道中數(shù)據(jù)流的方向是由第二個參數(shù)type控制的。此參數(shù)可以是r或者w,分別代表讀或寫。但不能同時為讀和寫。在Linux 系統(tǒng)下,管道將會以參數(shù)type中第一個字符代表的方式打開。所以,如果你在參數(shù)type中寫入rw,管道將會以讀的方式打開。
使用popen()創(chuàng)建的管道必須使用pclose()關閉。其實,popen/pclose和標準文件輸入/輸出流中的fopen()/fclose()十分相似。
庫函數(shù):pclose();
原型:int pclose(FILE *stream);
返回值:返回系統(tǒng)調用wait4()的狀態(tài)。
如果stream無效,或者系統(tǒng)調用wait4()失敗,則返回-1。注意此庫函數(shù)等待管道進程運行結束,然后關閉文件流。庫函數(shù)pclose()在使用popen()創(chuàng)建的進程上執(zhí)行wait4()函數(shù),它將破壞管道和文件系統(tǒng)。
流管道的例子。
#include
#include
#include
#include
#define BUFSIZE 1024
int main(){
FILE *fp;
char *cmd="ps -ef";
char buf[BUFSIZE];
buf[BUFSIZE]='\0';
if((fp=popen(cmd,"r"))==NULL)
?perror("popen");
while((fgets(buf,BUFSIZE,fp))!=NULL)
?printf("%s",buf);
pclose(fp);
exit(0);
}
庫函數(shù):pclose();
原型:int pclose(FILE *stream);
返回值:返回系統(tǒng)調用wait4()的狀態(tài)。
如果stream無效,或者系統(tǒng)調用wait4()失敗,則返回-1。注意此庫函數(shù)等待管道進程運行結束,然后關閉文件流。庫函數(shù)pclose()在使用popen()創(chuàng)建的進程上執(zhí)行wait4()函數(shù),它將破壞管道和文件系統(tǒng)。
流管道的例子。
#include
#include
#include
#include
#define BUFSIZE 1024
int main(){
FILE *fp;
char *cmd="ps -ef";
char buf[BUFSIZE];
buf[BUFSIZE]='\0';
if((fp=popen(cmd,"r"))==NULL)
?perror("popen");
while((fgets(buf,BUFSIZE,fp))!=NULL)
?printf("%s",buf);
pclose(fp);
exit(0);
}
命名管道(FIFO)
基本概念
命名管道和一般的管道基本相同,但也有一些顯著的不同:
A、命名管道是在文件系統(tǒng)中作為一個特殊的設備文件而存在的。
B、不同祖先的進程之間可以通過管道共享數(shù)據(jù)。
C、當共享管道的進程執(zhí)行完所有的I/O操作以后,命名管道將繼續(xù)保存在文件系統(tǒng)中以便以后使用。
管道只能由相關進程使用,它們共同的祖先進程創(chuàng)建了管道。但是,通過FIFO,不相關的進程也能交換數(shù)據(jù)。
基本概念
命名管道和一般的管道基本相同,但也有一些顯著的不同:
A、命名管道是在文件系統(tǒng)中作為一個特殊的設備文件而存在的。
B、不同祖先的進程之間可以通過管道共享數(shù)據(jù)。
C、當共享管道的進程執(zhí)行完所有的I/O操作以后,命名管道將繼續(xù)保存在文件系統(tǒng)中以便以后使用。
管道只能由相關進程使用,它們共同的祖先進程創(chuàng)建了管道。但是,通過FIFO,不相關的進程也能交換數(shù)據(jù)。
命名管道創(chuàng)建與操作
命名管道創(chuàng)建
#include
#include
int mkfifo(const char *pathname,mode_t mode);
返回:若成功則為0,若出錯返回-1
一旦已經(jīng)用mkfifo創(chuàng)建了一個FIFO,就可用open打開它。確實,一般的文件I/O函數(shù)(close,read,write,unlink等)都可用于FIFO。當打開一個FIFO時,非阻塞標(O_NONBLOCK)產(chǎn)生下列影響:
(1)在一般情況中(沒有說明O_NONBLOCK),只讀打開要阻塞到某個其他進程為寫打開此FIFO。類似,為寫而打開一個FIFO要阻塞到某個其他進程為讀而打開它。
(2)如果指一了O_NONBLOCK,則只讀打開立即返回。但是,如果沒有進程已經(jīng)為讀而打開一個FIFO,那么只寫打開將出錯返回,其errno是ENXIO。類似于管道,若寫一個尚無進程為讀而打開的FIFO,則產(chǎn)生信號SIGPIPE。若某個FIFO的最后一個寫進程關閉了該FIFO,則將為該FIFO的讀進程產(chǎn)生一個文件結束標志。
FIFO相關出錯信息:
EACCES(無存取權限)
EEXIST(指定文件不存在)
ENAMETOOLONG(路徑名太長)
ENOENT(包含的目錄不存在)
ENOSPC(文件系統(tǒng)余空間不足)
ENOTDIR(文件路徑無效)
EROFS(指定的文件存在于只讀文件系統(tǒng)中)
命名管道創(chuàng)建
#include
#include
int mkfifo(const char *pathname,mode_t mode);
返回:若成功則為0,若出錯返回-1
一旦已經(jīng)用mkfifo創(chuàng)建了一個FIFO,就可用open打開它。確實,一般的文件I/O函數(shù)(close,read,write,unlink等)都可用于FIFO。當打開一個FIFO時,非阻塞標(O_NONBLOCK)產(chǎn)生下列影響:
(1)在一般情況中(沒有說明O_NONBLOCK),只讀打開要阻塞到某個其他進程為寫打開此FIFO。類似,為寫而打開一個FIFO要阻塞到某個其他進程為讀而打開它。
(2)如果指一了O_NONBLOCK,則只讀打開立即返回。但是,如果沒有進程已經(jīng)為讀而打開一個FIFO,那么只寫打開將出錯返回,其errno是ENXIO。類似于管道,若寫一個尚無進程為讀而打開的FIFO,則產(chǎn)生信號SIGPIPE。若某個FIFO的最后一個寫進程關閉了該FIFO,則將為該FIFO的讀進程產(chǎn)生一個文件結束標志。
FIFO相關出錯信息:
EACCES(無存取權限)
EEXIST(指定文件不存在)
ENAMETOOLONG(路徑名太長)
ENOENT(包含的目錄不存在)
ENOSPC(文件系統(tǒng)余空間不足)
ENOTDIR(文件路徑無效)
EROFS(指定的文件存在于只讀文件系統(tǒng)中)
fifo_write.c?
#include
#include
#include
#include
#include
#include
#include
#define FIFO "/tmp/myfifo"
#include
#include
#include
#include
#include
#include
#include
#define FIFO "/tmp/myfifo"
main(int argc,char** argv)
{
char buf_r[100];
int fd;
int nread;
if((mkfifo(FIFO,O_CREAT|O_EXCL)<0)&&(errno!=EEXIST))
printf("cannot create fifoserver\n");
printf("Preparing for reading bytes....\n");
memset(buf_r,0,sizeof(buf_r));
fd=open(FIFO,O_RDONLY|O_NONBLOCK,0);
if(fd==-1)
{
perror("open");
exit(1);
}
while(1){
memset(buf_r,0,sizeof(buf_r));
if((nread=read(fd,buf_r,100))==-1){
if(errno==EAGAIN)
printf("no data yet\n");
}
printf("read %s from FIFO\n",buf_r);
sleep(1);
}
pause();
unlink(FIFO);
}
{
char buf_r[100];
int fd;
int nread;
if((mkfifo(FIFO,O_CREAT|O_EXCL)<0)&&(errno!=EEXIST))
printf("cannot create fifoserver\n");
printf("Preparing for reading bytes....\n");
memset(buf_r,0,sizeof(buf_r));
fd=open(FIFO,O_RDONLY|O_NONBLOCK,0);
if(fd==-1)
{
perror("open");
exit(1);
}
while(1){
memset(buf_r,0,sizeof(buf_r));
if((nread=read(fd,buf_r,100))==-1){
if(errno==EAGAIN)
printf("no data yet\n");
}
printf("read %s from FIFO\n",buf_r);
sleep(1);
}
pause();
unlink(FIFO);
}
fifo_read.c
#include
#include
#include
#include
#include
#include
#include
#define FIFO_SERVER "/tmp/myfifo"
main(int argc,char** argv)
{
int fd;
char w_buf[100];
int nwrite;
if(fd==-1)
if(errno==ENXIO)
printf("open error;no reading process\n");
fd=open(FIFO_SERVER,O_WRONLY|O_NONBLOCK,0);
if(argc==1)
printf("Please send something\n");
strcpy(w_buf,argv[1]);
if((nwrite=write(fd,w_buf,100))==-1)
{
if(errno==EAGAIN)
printf("The FIFO has not been read yet. Please try later\n");
}
else?
printf("write %s to the FIFO\n",w_buf);
}
#include
#include
#include
#include
#include
#include
#include
#define FIFO_SERVER "/tmp/myfifo"
main(int argc,char** argv)
{
int fd;
char w_buf[100];
int nwrite;
if(fd==-1)
if(errno==ENXIO)
printf("open error;no reading process\n");
fd=open(FIFO_SERVER,O_WRONLY|O_NONBLOCK,0);
if(argc==1)
printf("Please send something\n");
strcpy(w_buf,argv[1]);
if((nwrite=write(fd,w_buf,100))==-1)
{
if(errno==EAGAIN)
printf("The FIFO has not been read yet. Please try later\n");
}
else?
printf("write %s to the FIFO\n",w_buf);
}
三、信號
信號概述
信號是軟件中斷。信號(signal)機制是Unix系統(tǒng)中最為古老的進程之間的能信機制。它用于在一個或多個進程之間傳遞異步信號。很多條件可以產(chǎn)生一個信號。
A、當用戶按某些終端鍵時,產(chǎn)生信號。在終端上按DELETE鍵通常產(chǎn)生中斷信號(SIGINT)。這是停止一個已失去控制程序的方法。
B、硬件異常產(chǎn)生信號:除數(shù)為0、無效的存儲訪問等等。這些條件通常由硬件檢測到,并將其通知內核。然后內核為該條件發(fā)生時正在運行的進程產(chǎn)生適當?shù)男盘枴@?,對于?zhí)行一個無效存儲訪問的進程產(chǎn)生一個SIGSEGV。
C、進程用kill(2)函數(shù)可將信號發(fā)送給另一個進程或進程組。自然,有些限制:接收信號進和發(fā)送信號進程的所有都必須相同,或發(fā)送信號進程的的所有者必須是超級用戶。
D、用戶可用Kill(ID 值)命令將信號發(fā)送給其它進程。此程序是Kill函數(shù)的界面。常用此命令終止一個失控的后臺進程。
E、當檢測到某種軟件條件已經(jīng)發(fā)生,并將其通知有關進程時也產(chǎn)生信號。這里并不是指硬件產(chǎn)生條件(如被0除),而是軟件條件。例如SIGURG(在網(wǎng)絡連接上傳來非規(guī)定波特率的數(shù)據(jù))、SIGPIPE(在管道的讀進程已終止后一個進程寫此管道),以及SIGALRM(進程所設置的鬧鐘時間已經(jīng)超時)。
內核為進程生產(chǎn)信號,來響應不同的事件,這些事件就是信號源。主要信號源如下:
(1)異常:進程運行過程中出現(xiàn)異常;
(2)其它進程:一個進程可以向另一個或一組進程發(fā)送信號;
(3)終端中斷:Ctrl-c,Ctro-\等;
(4)作業(yè)控制:前臺、后臺進程的管理;
(5)分配額:CPU超時或文件大小突破限制;
(6)通知:通知進程某事件發(fā)生,如I/O就緒等;
(7)報警:計時器到期;
(1)異常:進程運行過程中出現(xiàn)異常;
(2)其它進程:一個進程可以向另一個或一組進程發(fā)送信號;
(3)終端中斷:Ctrl-c,Ctro-\等;
(4)作業(yè)控制:前臺、后臺進程的管理;
(5)分配額:CPU超時或文件大小突破限制;
(6)通知:通知進程某事件發(fā)生,如I/O就緒等;
(7)報警:計時器到期;
Linux中的信號
1、SIGHUP 2、SIGINT(終止) 3、SIGQUIT(退出) 4、SIGILL 5、SIGTRAP 6、SIGIOT? 7、SIGBUS?? 8、SIGFPE?? 9、SIGKILL 10、SIGUSER 11、 SIGSEGV SIGUSER 12、 SIGPIPE 13、SIGALRM 14、SIGTERM 15、SIGCHLD 16、SIGCONT 17、SIGSTOP 18、SIGTSTP 19、SIGTTIN 20、SIGTTOU 21、SIGURG 22、SIGXCPU 23、SIGXFSZ 24、SIGVTALRM 25、SIGPROF 26、SIGWINCH 27、SIGIO 28、SIGPWR
1、SIGHUP 2、SIGINT(終止) 3、SIGQUIT(退出) 4、SIGILL 5、SIGTRAP 6、SIGIOT? 7、SIGBUS?? 8、SIGFPE?? 9、SIGKILL 10、SIGUSER 11、 SIGSEGV SIGUSER 12、 SIGPIPE 13、SIGALRM 14、SIGTERM 15、SIGCHLD 16、SIGCONT 17、SIGSTOP 18、SIGTSTP 19、SIGTTIN 20、SIGTTOU 21、SIGURG 22、SIGXCPU 23、SIGXFSZ 24、SIGVTALRM 25、SIGPROF 26、SIGWINCH 27、SIGIO 28、SIGPWR
常用的信號:
SIGHUP:從終端上發(fā)出的結束信號;
SIGINT:來自鍵盤的中斷信號(Ctrl+c)
SIGQUIT:來自鍵盤的退出信號;
SIGFPE:浮點異常信號(例如浮點運算溢出);
SIGKILL:該信號結束接收信號的進程;
SIGALRM:進程的定時器到期時,發(fā)送該信號;
SIGTERM:kill命令生出的信號;
SIGCHLD:標識子進程停止或結束的信號;
SIGSTOP:來自鍵盤(Ctrl-Z)或調試程序的停止掃行信號
SIGHUP:從終端上發(fā)出的結束信號;
SIGINT:來自鍵盤的中斷信號(Ctrl+c)
SIGQUIT:來自鍵盤的退出信號;
SIGFPE:浮點異常信號(例如浮點運算溢出);
SIGKILL:該信號結束接收信號的進程;
SIGALRM:進程的定時器到期時,發(fā)送該信號;
SIGTERM:kill命令生出的信號;
SIGCHLD:標識子進程停止或結束的信號;
SIGSTOP:來自鍵盤(Ctrl-Z)或調試程序的停止掃行信號
可以要求系統(tǒng)在某個信號出現(xiàn)時按照下列三種方式中的一種進行操作。
(1)忽略此信號。大多數(shù)信號都可使用這種方式進行處理,但有兩種信號卻決不能被忽略。它們是:SIGKILL和SIGSTOP。這兩種信號不能被忽略的,原因是:它們向超級用戶提供一種使進程終止或停止的可靠方法。另外,如果忽略某些由硬件異常產(chǎn)生的信號(例如非法存儲訪問或除以0),則進程的行為是示定義的。
(2)捕捉信號。為了做到這一點要通知內核在某種信號發(fā)生時,調用一個用戶函數(shù)。在用戶函數(shù)中,可執(zhí)行用戶希望對這種事件進行的處理。如果捕捉到SIGCHLD信號,則表示子進程已經(jīng)終止,所以此信號的捕捉函數(shù)可以調用waitpid以取得該子進程的進程ID以及它的終止狀態(tài)。
(3)執(zhí)行系統(tǒng)默認動作。對大多數(shù)信號的系統(tǒng)默認動作是終止該進程。每一個信號都有一個缺省動作,它是當進程沒有給這個信號指定處理程序時,內核對信號的處理。有5種缺省的動作:
(1)異常終止(abort):在進程的當前目錄下,把進程的地址空間內容、寄存器內容保存到一個叫做core的文件中,而后終止進程。
(2)退出(exit):不產(chǎn)生core文件,直接終止進程。
(3)忽略(ignore):忽略該信號。
(4)停止(stop):掛起該進程。
(5)繼續(xù)(contiune):如果進程被掛起,剛恢復進程的動行。否則,忽略信號。
(1)忽略此信號。大多數(shù)信號都可使用這種方式進行處理,但有兩種信號卻決不能被忽略。它們是:SIGKILL和SIGSTOP。這兩種信號不能被忽略的,原因是:它們向超級用戶提供一種使進程終止或停止的可靠方法。另外,如果忽略某些由硬件異常產(chǎn)生的信號(例如非法存儲訪問或除以0),則進程的行為是示定義的。
(2)捕捉信號。為了做到這一點要通知內核在某種信號發(fā)生時,調用一個用戶函數(shù)。在用戶函數(shù)中,可執(zhí)行用戶希望對這種事件進行的處理。如果捕捉到SIGCHLD信號,則表示子進程已經(jīng)終止,所以此信號的捕捉函數(shù)可以調用waitpid以取得該子進程的進程ID以及它的終止狀態(tài)。
(3)執(zhí)行系統(tǒng)默認動作。對大多數(shù)信號的系統(tǒng)默認動作是終止該進程。每一個信號都有一個缺省動作,它是當進程沒有給這個信號指定處理程序時,內核對信號的處理。有5種缺省的動作:
(1)異常終止(abort):在進程的當前目錄下,把進程的地址空間內容、寄存器內容保存到一個叫做core的文件中,而后終止進程。
(2)退出(exit):不產(chǎn)生core文件,直接終止進程。
(3)忽略(ignore):忽略該信號。
(4)停止(stop):掛起該進程。
(5)繼續(xù)(contiune):如果進程被掛起,剛恢復進程的動行。否則,忽略信號。
信號的發(fā)送與捕捉
kill()和raise()
kill()不僅可以中止進程,也可以向進程發(fā)送其他信號。
與kill函數(shù)不同的是,raise()函數(shù)運行向進程自身發(fā)送信號
#include
#include
int kill(pid_t pid,int signo);
int raise(int signo);
兩個函數(shù)返回:若成功則為0,若出錯則為-1。
kill的pid參數(shù)有四種不同的情況:
(1)pid>0將信號發(fā)送給進程ID為pid的進程。
(2)pid==0將信號發(fā)送給其進程組ID等于發(fā)送進程的進程組ID,而且發(fā)送進程有許可權向其發(fā)送信號的所有進程。
(3)pid<0將信號發(fā)送給其進程組ID等于pid絕對值,而且發(fā)送進程有許可權向其發(fā)送信號的所有進程。如上所述一樣,“所有進程”并不包括系統(tǒng)進程集中的進程。
(4)pid==-1 POSIX.1未定義種情況
kill.c?
#include
#include
#include
#include
#include
int main()
{
pid_t pid;
int ret;
if((pid==fork())<0){
perro("fork");
exit(1);
}
if(pid==0){
raise(SIGSTOP);
exit(0);
}else {
printf("pid=%d\n",pid);
if((waitpid(pid,NULL,WNOHANG))==0){
if((ret=kill(pid,SIGKILL))==0)
printf("kill %d\n",pid);
else{
perror("kill");
}
}
}
}
kill()和raise()
kill()不僅可以中止進程,也可以向進程發(fā)送其他信號。
與kill函數(shù)不同的是,raise()函數(shù)運行向進程自身發(fā)送信號
#include
#include
int kill(pid_t pid,int signo);
int raise(int signo);
兩個函數(shù)返回:若成功則為0,若出錯則為-1。
kill的pid參數(shù)有四種不同的情況:
(1)pid>0將信號發(fā)送給進程ID為pid的進程。
(2)pid==0將信號發(fā)送給其進程組ID等于發(fā)送進程的進程組ID,而且發(fā)送進程有許可權向其發(fā)送信號的所有進程。
(3)pid<0將信號發(fā)送給其進程組ID等于pid絕對值,而且發(fā)送進程有許可權向其發(fā)送信號的所有進程。如上所述一樣,“所有進程”并不包括系統(tǒng)進程集中的進程。
(4)pid==-1 POSIX.1未定義種情況
kill.c?
#include
#include
#include
#include
#include
int main()
{
pid_t pid;
int ret;
if((pid==fork())<0){
perro("fork");
exit(1);
}
if(pid==0){
raise(SIGSTOP);
exit(0);
}else {
printf("pid=%d\n",pid);
if((waitpid(pid,NULL,WNOHANG))==0){
if((ret=kill(pid,SIGKILL))==0)
printf("kill %d\n",pid);
else{
perror("kill");
}
}
}
}
alarm和pause函數(shù)
使用alarm函數(shù)可以設置一個時間值(鬧鐘時間),在將來的某個時刻時間值會被超過。當所設置的時間被超過后,產(chǎn)生SIGALRM信號。如果不忽略或不捕捉引信號,則其默認動作是終止該進程。
#include
unsigned int alarm(unsigned int secondss);
返回:0或以前設置的鬧鐘時間的余留秒數(shù)。
使用alarm函數(shù)可以設置一個時間值(鬧鐘時間),在將來的某個時刻時間值會被超過。當所設置的時間被超過后,產(chǎn)生SIGALRM信號。如果不忽略或不捕捉引信號,則其默認動作是終止該進程。
#include
unsigned int alarm(unsigned int secondss);
返回:0或以前設置的鬧鐘時間的余留秒數(shù)。
參數(shù)seconds的值是秒數(shù),經(jīng)過了指定的seconds秒后產(chǎn)生信號SIGALRM。每個進程只能有一個鬧鐘時間。如果在調用alarm時,以前已為該進程設置過鬧鐘時間,而且它還沒有超時,則該鬧鐘時間的余留值作為本次alarm函數(shù)調用的值返回。以前登記的鬧鐘時間則被新值代換。
如果有以前登記的尚未超過的鬧鐘時間,而且seconds值是0,則取消以前的鬧鐘時間,其余留值仍作為函數(shù)的返回值。
如果有以前登記的尚未超過的鬧鐘時間,而且seconds值是0,則取消以前的鬧鐘時間,其余留值仍作為函數(shù)的返回值。
pause函數(shù)使用調用進程掛起直至捕捉到一個信號
#include
int pause(void);
返回:-1,errno設置為EINTR
只有執(zhí)行了一信號處理程序并從其返回時,pause才返回。
alarm.c
#include
#include
#include
int main()
{
int ret;
ret=alarm(5);
pause();
printf("I have been waken up.\n",ret);
}
#include
#include
#include
int main()
{
int ret;
ret=alarm(5);
pause();
printf("I have been waken up.\n",ret);
}
信號的處理
當系統(tǒng)捕捉到某個信號時,可以忽略誰信號或是使用指定的處理函數(shù)來處理該信號,或者使用系統(tǒng)默認的方式。信號處理的主要方式有兩種,一種是使用簡單的signal函數(shù),別一種是使用信號集函數(shù)組。
signal()
#include
void (*signal (int signo,void (*func)(int)))(int)
返回:成功則為以前的信號處理配置,若出錯則為SIG_ERR
func的值是:(a)常數(shù)SIGIGN,或(b)常數(shù)SIGDFL,或(c)當接到此信號后要調用的的函數(shù)的地址。如果指定SIGIGN,則向內核表示忽略此信號(有兩個信號SIGKILL和SIGSTOP不能忽略)。如果指定SIGDFL,則表示接到此信號后的動作是系統(tǒng)默認動作。當指定函數(shù)地址時,我們稱此為捕捉此信號。我們稱此函數(shù)為信號處理程序(signal handler)或信號捕捉函數(shù)(signal-catching funcgion).signal函數(shù)原型太復雜了,如果使用下面的typedef,則可以使其簡化。
type void sign(int);
sign *signal(int,handler *);
實例見:mysignal.c
#include
#include
#include
void my_func(int sign_no)
{
if(sign_no==SIGINT)
?printf("I have get SIGINT\n");
else if(sign_no==SIGQUIT)
?printf("I have get SIGQUIT\n");
}
int main()
{
?printf("Waiting for signal SIGINT or SIGQUTI\n");
?signal(SIGINT,my_func);
?signal(SIGQUIT,my_func);
?pasue();
?exit(0);
}
信號集函數(shù)組
我們需要有一個能表示多個信號——信號集(signal set)的數(shù)據(jù)類型。將在sigprocmask()這樣的函數(shù)中使用這種數(shù)據(jù)類型,以告訴內核不允許發(fā)生該信號集中的信號。信號集函數(shù)組包含水量幾大模塊:創(chuàng)建函數(shù)集、登記信號集、檢測信號集。
圖見附件。
當系統(tǒng)捕捉到某個信號時,可以忽略誰信號或是使用指定的處理函數(shù)來處理該信號,或者使用系統(tǒng)默認的方式。信號處理的主要方式有兩種,一種是使用簡單的signal函數(shù),別一種是使用信號集函數(shù)組。
signal()
#include
void (*signal (int signo,void (*func)(int)))(int)
返回:成功則為以前的信號處理配置,若出錯則為SIG_ERR
func的值是:(a)常數(shù)SIGIGN,或(b)常數(shù)SIGDFL,或(c)當接到此信號后要調用的的函數(shù)的地址。如果指定SIGIGN,則向內核表示忽略此信號(有兩個信號SIGKILL和SIGSTOP不能忽略)。如果指定SIGDFL,則表示接到此信號后的動作是系統(tǒng)默認動作。當指定函數(shù)地址時,我們稱此為捕捉此信號。我們稱此函數(shù)為信號處理程序(signal handler)或信號捕捉函數(shù)(signal-catching funcgion).signal函數(shù)原型太復雜了,如果使用下面的typedef,則可以使其簡化。
type void sign(int);
sign *signal(int,handler *);
實例見:mysignal.c
#include
#include
#include
void my_func(int sign_no)
{
if(sign_no==SIGINT)
?printf("I have get SIGINT\n");
else if(sign_no==SIGQUIT)
?printf("I have get SIGQUIT\n");
}
int main()
{
?printf("Waiting for signal SIGINT or SIGQUTI\n");
?signal(SIGINT,my_func);
?signal(SIGQUIT,my_func);
?pasue();
?exit(0);
}
信號集函數(shù)組
我們需要有一個能表示多個信號——信號集(signal set)的數(shù)據(jù)類型。將在sigprocmask()這樣的函數(shù)中使用這種數(shù)據(jù)類型,以告訴內核不允許發(fā)生該信號集中的信號。信號集函數(shù)組包含水量幾大模塊:創(chuàng)建函數(shù)集、登記信號集、檢測信號集。
圖見附件。
創(chuàng)建函數(shù)集
#include
int sigemptyset(sigset_t* set);
int sigfillset(sigset_t* set);
int sigaddset(sigset_t* set,int signo );
int sigdelset(sigset_t* set,int signo);
四個函數(shù)返回:若成功則為0,若出錯則為-1
int sigismember(const sigset_t* set,int signo);
返回:若真則為1,若假則為0;
signemptyset:初始化信號集合為空。
sigfillset:初始化信號集合為所有的信號集合。
sigaddset:將指定信號添加到現(xiàn)存集中。
sigdelset:從信號集中刪除指定信號。
sigismember:查詢指定信號是否在信號集中。
#include
int sigemptyset(sigset_t* set);
int sigfillset(sigset_t* set);
int sigaddset(sigset_t* set,int signo );
int sigdelset(sigset_t* set,int signo);
四個函數(shù)返回:若成功則為0,若出錯則為-1
int sigismember(const sigset_t* set,int signo);
返回:若真則為1,若假則為0;
signemptyset:初始化信號集合為空。
sigfillset:初始化信號集合為所有的信號集合。
sigaddset:將指定信號添加到現(xiàn)存集中。
sigdelset:從信號集中刪除指定信號。
sigismember:查詢指定信號是否在信號集中。
登記信號集
登記信號處理機主要用于決定進程如何處理信號。首先要判斷出當前進程阻塞能不能傳遞給該信號的信號集。這首先使用sigprocmask函數(shù)判斷檢測或更改信號屏蔽字,然后使用sigaction函數(shù)改變進程接受到特定信號之后的行為。
一個進程的信號屏蔽字可以規(guī)定當前阻塞而不能遞送給該進程的信號集。調用函數(shù)sigprocmask可以檢測或更改(或兩者)進程的信號屏蔽字。
#include
int sigprocmask(int how,const sigset_t* set,sigset_t* oset);
返回:若成功則為0,若出錯則為-1
oset是非空指針,進程是當前信號屏蔽字通過oset返回。其次,若set是一個非空指針,則參數(shù)how指示如何修改當前信號屏蔽字。
用sigprocmask更改當前信號屏蔽字的方法。how參數(shù)設定:
SIG_BLOCK該進程新的信號屏蔽字是其當前信號屏蔽字和set指向信號集的并集。set包含了我們希望阻塞的附加信號。
SIG_NUBLOCK該進程新的信號屏蔽字是其當前信號屏蔽字和set所指向信號集的交集。set包含了我們希望解除阻塞的信號。
SIG_SETMASK該進程新的信號屏蔽是set指向的值。如果set是個空指針,則不改變該進程的信號屏蔽字,how的值也無意義。
sigaction函數(shù)的功能是檢查或修改(或兩者)與指定信號相關聯(lián)的處理動作。此函數(shù)取代了UNIX早期版本使用的signal函數(shù)。
#include
int sigaction(int signo,const struct sigaction* act,struct sigaction* oact);
返回:若成功則為0,若出錯則為-1
參數(shù)signo是要檢測或修改具體動作的信號的編號數(shù)。若act指針非空,則要修改其動作。如果oact指針為空,則系統(tǒng)返回該信號的原先動作。此函數(shù)使用下列結構:
struct sigaction{
void (*sa_handler)(int signo);
sigset_t sa_mask;
int sa_flags;
void (*sa_restore);
};
sa_handler是一個函數(shù)指針,指定信號關聯(lián)函數(shù),可以是自定義處理函數(shù),還可以SIG_DEF或SIG_IGN;
sa_mask是一個信號集,它可以指定在信號處理程序執(zhí)行過程中哪些信號應當被阻塞。
sa_flags中包含許多標志位,是對信號進行處理的各種選項。具體如下:
SA_NODEFER\SA_NOMASK:當捕捉到此信號時,在執(zhí)行其信號捕捉函數(shù)時,系統(tǒng)不會自動阻塞此信號。
SA_NOCLDSTOP:進程忽略子進程產(chǎn)生的任何SIGSTOP、SIGTSTP、SIGTTIN和SIGTOU信號
SA_RESTART:可讓重啟的系統(tǒng)調用重新起作用。
SA_ONESHOT\SA_RESETHAND:自定義信號只執(zhí)行一次,在執(zhí)行完畢后恢復信號的系統(tǒng)默認動作。
檢測信號是信號處理的后續(xù)步驟,但不是必須的。sigpending函數(shù)運行進程檢測“未決“信號(進程不清楚他的存在),并進一步?jīng)Q定對他們做何處理。
sigpending返回對于調用進程被阻塞不能遞送和當前未決的信號集。
#include
int sigpending(sigset_t * set);
返回:若成功則為0,若出錯則為-1
信號集實例見:sigaction.c
#include
#include
#include
#include
#include
void my_func(int signum){
printf("If you want to quit,please try SIGQUIT\n");
}
int main()
{
sigset_t set,pendset;
struct sigaction action1,action2;
if(sigemptyse(&set)<0)
perror("sigemptyset");
if(sigaddset(&set,SIGQUIT)<0)
perror("sigaddset");
if(sigaddset(&set,SIGINT)<0)
perror("sigaddset");
if(sigprocmask(SIG_BLOCK,&set,NULL)<0)
perror("sigprcmask");
esle{
printf("blocked\n");
sleep(5);
}
if(sigprocmask(SIG_UNBLOCK,&set,NULL)
perror("sigprocmask");
else
printf("unblock\n");
while(1){
if(sigismember(&set,SIGINT)){
sigemptyset(&action1.sa_mask);
action1.sa_handler=my_func;
sigaction(SIGINT,&action1,NULL);
}else if(sigismember(&set,SIGQUIT)){
sigemptyset(&action2.sa_mask);
action2.sa_handler=SIG_DEL;
sigaction(SIGTERM,&action2,NULL);
}
}
}
登記信號處理機主要用于決定進程如何處理信號。首先要判斷出當前進程阻塞能不能傳遞給該信號的信號集。這首先使用sigprocmask函數(shù)判斷檢測或更改信號屏蔽字,然后使用sigaction函數(shù)改變進程接受到特定信號之后的行為。
一個進程的信號屏蔽字可以規(guī)定當前阻塞而不能遞送給該進程的信號集。調用函數(shù)sigprocmask可以檢測或更改(或兩者)進程的信號屏蔽字。
#include
int sigprocmask(int how,const sigset_t* set,sigset_t* oset);
返回:若成功則為0,若出錯則為-1
oset是非空指針,進程是當前信號屏蔽字通過oset返回。其次,若set是一個非空指針,則參數(shù)how指示如何修改當前信號屏蔽字。
用sigprocmask更改當前信號屏蔽字的方法。how參數(shù)設定:
SIG_BLOCK該進程新的信號屏蔽字是其當前信號屏蔽字和set指向信號集的并集。set包含了我們希望阻塞的附加信號。
SIG_NUBLOCK該進程新的信號屏蔽字是其當前信號屏蔽字和set所指向信號集的交集。set包含了我們希望解除阻塞的信號。
SIG_SETMASK該進程新的信號屏蔽是set指向的值。如果set是個空指針,則不改變該進程的信號屏蔽字,how的值也無意義。
sigaction函數(shù)的功能是檢查或修改(或兩者)與指定信號相關聯(lián)的處理動作。此函數(shù)取代了UNIX早期版本使用的signal函數(shù)。
#include
int sigaction(int signo,const struct sigaction* act,struct sigaction* oact);
返回:若成功則為0,若出錯則為-1
參數(shù)signo是要檢測或修改具體動作的信號的編號數(shù)。若act指針非空,則要修改其動作。如果oact指針為空,則系統(tǒng)返回該信號的原先動作。此函數(shù)使用下列結構:
struct sigaction{
void (*sa_handler)(int signo);
sigset_t sa_mask;
int sa_flags;
void (*sa_restore);
};
sa_handler是一個函數(shù)指針,指定信號關聯(lián)函數(shù),可以是自定義處理函數(shù),還可以SIG_DEF或SIG_IGN;
sa_mask是一個信號集,它可以指定在信號處理程序執(zhí)行過程中哪些信號應當被阻塞。
sa_flags中包含許多標志位,是對信號進行處理的各種選項。具體如下:
SA_NODEFER\SA_NOMASK:當捕捉到此信號時,在執(zhí)行其信號捕捉函數(shù)時,系統(tǒng)不會自動阻塞此信號。
SA_NOCLDSTOP:進程忽略子進程產(chǎn)生的任何SIGSTOP、SIGTSTP、SIGTTIN和SIGTOU信號
SA_RESTART:可讓重啟的系統(tǒng)調用重新起作用。
SA_ONESHOT\SA_RESETHAND:自定義信號只執(zhí)行一次,在執(zhí)行完畢后恢復信號的系統(tǒng)默認動作。
檢測信號是信號處理的后續(xù)步驟,但不是必須的。sigpending函數(shù)運行進程檢測“未決“信號(進程不清楚他的存在),并進一步?jīng)Q定對他們做何處理。
sigpending返回對于調用進程被阻塞不能遞送和當前未決的信號集。
#include
int sigpending(sigset_t * set);
返回:若成功則為0,若出錯則為-1
信號集實例見:sigaction.c
#include
#include
#include
#include
#include
void my_func(int signum){
printf("If you want to quit,please try SIGQUIT\n");
}
int main()
{
sigset_t set,pendset;
struct sigaction action1,action2;
if(sigemptyse(&set)<0)
perror("sigemptyset");
if(sigaddset(&set,SIGQUIT)<0)
perror("sigaddset");
if(sigaddset(&set,SIGINT)<0)
perror("sigaddset");
if(sigprocmask(SIG_BLOCK,&set,NULL)<0)
perror("sigprcmask");
esle{
printf("blocked\n");
sleep(5);
}
if(sigprocmask(SIG_UNBLOCK,&set,NULL)
perror("sigprocmask");
else
printf("unblock\n");
while(1){
if(sigismember(&set,SIGINT)){
sigemptyset(&action1.sa_mask);
action1.sa_handler=my_func;
sigaction(SIGINT,&action1,NULL);
}else if(sigismember(&set,SIGQUIT)){
sigemptyset(&action2.sa_mask);
action2.sa_handler=SIG_DEL;
sigaction(SIGTERM,&action2,NULL);
}
}
}
評論