1、进程间通讯概述
进程通讯有以下一些目的:
A、数据传输:一个进程须要将它的数据发送给另外一个进程,发送的数据量在一个字节到几M字节之间
B、共享数据:多个进程想要操做共享数据,一个进程对共享数据的修改,别的进程应该马上看到。
C、通知事件:一个进程须要向另外一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
D、资源共享:多个进程之间共享一样的资源。为了做到这一点,须要内核提供锁和同步机制。
E、进程控制:有些进程但愿彻底控制另外一个进程的执行(如Debug进程),此时控制进程但愿可以拦截另外一个进程的全部陷入和异常,并可以及时知道它的状态改变。
Linux 进程间通讯(IPC)如下以几部分发展而来:
早期UNIX进程间通讯、基于System V进程间通讯、基于Socket进程间通讯和POSIX进程间通讯。
UNIX进程间通讯方式包括:管道、FIFO、信号。
System V进程间通讯方式包括:System V消息队列、System V信号灯、System V共享内存、
POSIX进程间通讯包括:posix消息队列、posix信号灯、posix共享内存。
如今linux使用的进程间通讯方式:
(1)管道(pipe)和有名管道(FIFO)
(2)信号(signal)
(3)消息队列
(4)共享内存
(5)信号量
(6)套接字(socket)linux
2、管道通讯shell
普通的Linux shell都容许重定向,而重定向使用的就是管道。例如:
ps | grep vsftpd .管道是单向的、先进先出的、无结构的、固定大小的字节流,它把一个进程的标准输出和另外一个进程的标准输入链接在一块儿。写进程在管道的尾端写入数据,读进程在管道的道端读出数据。数据读出后将从管道中移走,其它读进程都不能再读到这些数据。管道提供了简单的流控制机制。进程试图读空管道时,在有数据写入管道前,进程将一直阻塞。一样,管道已经满时,进程再试图写管道,在其它进程从管道中移走数据以前,写进程将一直阻塞。管道主要用于不一样进程间通讯。数组
管道建立与关闭
建立一个简单的管道,可使用系统调用pipe()。它接受一个参数,也就是一个包括两个整数的数组。若是系统调用成功,此数组将包括管道使用的两个文件描述符。建立一个管道以后,通常状况下进程将产生一个新的进程。
系统调用:pipe();
原型:int pipe(int fd[2]);
返回值:若是系统调用成功,返回0。若是系统调用失败返回-1:
errno=EMFILE(没有空亲的文件描述符)
EMFILE(系统文件表已满)
EFAULT(fd数组无效)
注意:fd[0]用于读取管道,fd[1]用于写入管道。
图见附件
管道的建立网络
#include<unistd.h> #include<errno.h> #include<stdio.h> #include<stdlib.h> 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]); }
管道的读写
管道主要用于不一样进程间通讯。实际上,一般先建立一个管道,再经过fork函数建立一个子进程。图见附件。异步
子进程写入和父进程读的命名管道:图见附件socket
管道读写注意事项:
能够经过打开两个管道来建立一个双向的管道。但须要在子理程中正确地设置文件描述符。必须在系统调用fork()中调用pipe(),不然子进程将不会继承文件描述符。当使用半双工管道时,任何关联的进程都必须共享一个相关的祖先进程。由于管道存在于系统内核之中,因此任何不在建立管道的进程的祖先进程之中的进程都将没法寻址它。而在命名管道中却不是这样。管道实例见:pipe_rw.c函数
#include<unistd.h> #include<memory.h> #include<errno.h> #include<stdio.h> #include<stdlib.h> #include <sys/types.h> #include <sys/wait.h> int main() { int pipe_fd[2]; pid_t pid; char buf_r[100]; char* p_wbuf; int r_num; memset(buf_r,0,sizeof(buf_r)); //数组中的数据清0 if(pipe(pipe_fd)<0){ 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); } }
标准流管道
与linux中文件操做有文件流的标准I/O同样,管道的操做也支持基于文件流的模式。接口函数以下:
库函数:popen();
原型:FILE *open (char *command,char *type);
返回值:若是成功,返回一个新的文件流。若是没法建立进程或者管道,返回NULL。管道中数据流的方向是由第二个参数type控制的。此参数能够是r或者w,分别表明读或写。但不能同时为读和写。在Linux 系统下,管道将会以参数type中第一个字符表明的方式打开。因此,若是你在参数type中写入rw,管道将会以读的方式打开。ui
使用popen()建立的管道必须使用pclose()关闭。其实,popen/pclose和标准文件输入/输出流中的fopen()/fclose()十分类似。
库函数:pclose();
原型:int pclose(FILE *stream);
返回值:返回系统调用wait4()的状态。
若是stream无效,或者系统调用wait4()失败,则返回-1。注意此库函数等待管道进程运行结束,而后关闭文件流。库函数pclose()在使用popen()建立的进程上执行wait4()函数,它将破坏管道和文件系统。
流管道的例子。spa
#include<stdio.h> #include<unistd.h> #include<stdlib.h> #include<fcntl.h> #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、命名管道是在文件系统中做为一个特殊的设备文件而存在的。
B、不一样祖先的进程之间能够经过管道共享数据。
C、当共享管道的进程执行完全部的I/O操做之后,命名管道将继续保存在文件系统中以便之后使用。
管道只能由相关进程使用,它们共同的祖先进程建立了管道。可是,经过FIFO,不相关的进程也能交换数据。指针
命名管道建立与操做
命名管道建立
#include<sys/types.h>
#include<sys/stat.h>
int mkfifo(const char *pathname,mode_t mode);
返回:若成功则为0,若出错返回-1
一旦已经用mkfifo建立了一个FIFO,就可用open打开它。确实,通常的文件I/O函数(close,read,write,unlink等)均可用于FIFO。当打开一个FIFO时,非阻塞标(O_NONBLOCK)产生下列影响:
(1)在通常状况中(没有说明O_NONBLOCK),只读打开要阻塞到某个其余进程为写打开此FIFO。相似,为写而打开一个FIFO要阻塞到某个其余进程为读而打开它。
(2)若是指一了O_NONBLOCK,则只读打开当即返回。可是,若是没有进程已经为读而打开一个FIFO,那么只写打开将出错返回,其errno是ENXIO。相似于管道,若写一个尚无进程为读而打开的FIFO,则产生信号SIGPIPE。若某个FIFO的最后一个写进程关闭了该FIFO,则将为该FIFO的读进程产生一个文件结束标志。
FIFO相关出错信息:
EACCES(无存取权限)
EEXIST(指定文件不存在)
ENAMETOOLONG(路径名太长)
ENOENT(包含的目录不存在)
ENOSPC(文件系统余空间不足)
ENOTDIR(文件路径无效)
EROFS(指定的文件存在于只读文件系统中)
fifo_write.c
#include<sys/types.h> #include<sys/stat.h> #include <unistd.h> #include<errno.h> #include<fcntl.h> #include<stdio.h> #include<stdlib.h> #include<string.h> #define FIFO "/tmp/myfifo" int 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); }
fifo_read.c
#include<sys/types.h> #include<sys/stat.h> #include <unistd.h> #include<errno.h> #include<fcntl.h> #include<stdio.h> #include<stdlib.h> #include<string.h> #define FIFO_SERVER "/tmp/myfifo" int 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); }
3、信号
信号概述
信号是软件中断。信号(signal)机制是Unix系统中最为古老的进程之间的能信机制。它用于在一个或多个进程之间传递异步信号。不少条件能够产生一个信号。
A、当用户按某些终端键时,产生信号。在终端上按DELETE键一般产生中断信号(SIGINT)。这是中止一个已失去控制程序的方法。
B、硬件异常产生信号:除数为0、无效的存储访问等等。这些条件一般由硬件检测到,并将其通知内核。而后内核为该条件发生时正在运行的进程产生适当的信号。例如,对于执行一个无效存储访问的进程产生一个SIGSEGV。
C、进程用kill(2)函数可将信号发送给另外一个进程或进程组。天然,有些限制:接收信号进和发送信号进程的全部都必须相同,或发送信号进程的的全部者必须是超级用户。
D、用户可用Kill(ID 值)命令将信号发送给其它进程。此程序是Kill函数的界面。经常使用此命令终止一个失控的后台进程。
E、当检测到某种软件条件已经发生,并将其通知有关进程时也产生信号。这里并非指硬件产生条件(如被0除),而是软件条件。例如SIGURG(在网络链接上传来非规定波特率的数据)、SIGPIPE(在管道的读进程已终止后一个进程写此管道),以及SIGALRM(进程所设置的闹钟时间已经超时)。
内核为进程生产信号,来响应不一样的事件,这些事件就是信号源。主要信号源以下:
(1)异常:进程运行过程当中出现异常;
(2)其它进程:一个进程能够向另外一个或一组进程发送信号;
(3)终端中断:Ctrl-c,Ctro-\等;
(4)做业控制:前台、后台进程的管理;
(5)分配额:CPU超时或文件大小突破限制;
(6)通知:通知进程某事件发生,如I/O就绪等;
(7)报警:计时器到期;
Linux中的信号
一、SIGHUP 二、SIGINT(终止) 三、SIGQUIT(退出) 四、SIGILL 五、SIGTRAP 六、SIGIOT 七、SIGBUS 八、SIGFPE 九、SIGKILL 十、SIGUSER 十一、 SIGSEGV SIGUSER 十二、 SIGPIPE 1三、SIGALRM 1四、SIGTERM 1五、SIGCHLD 1六、SIGCONT 1七、SIGSTOP 1八、SIGTSTP 1九、SIGTTIN 20、SIGTTOU 2一、SIGURG 2二、SIGXCPU 2三、SIGXFSZ 2四、SIGVTALRM 2五、SIGPROF 2六、SIGWINCH 2七、SIGIO 2八、SIGPWR
经常使用的信号:
SIGHUP:从终端上发出的结束信号;
SIGINT:来自键盘的中断信号(Ctrl+c)
SIGQUIT:来自键盘的退出信号;
SIGFPE:浮点异常信号(例如浮点运算溢出);
SIGKILL:该信号结束接收信号的进程;
SIGALRM:进程的定时器到期时,发送该信号;
SIGTERM:kill命令生出的信号;
SIGCHLD:标识子进程中止或结束的信号;
SIGSTOP:来自键盘(Ctrl-Z)或调试程序的中止扫行信号
能够要求系统在某个信号出现时按照下列三种方式中的一种进行操做。
(1)忽略此信号。大多数信号均可使用这种方式进行处理,但有两种信号却决不能被忽略。它们是:SIGKILL和SIGSTOP。这两种信号不能被忽略的,缘由是:它们向超级用户提供一种使进程终止或中止的可靠方法。另外,若是忽略某些由硬件异常产生的信号(例如非法存储访问或除以0),则进程的行为是示定义的。
(2)捕捉信号。为了作到这一点要通知内核在某种信号发生时,调用一个用户函数。在用户函数中,可执行用户但愿对这种事件进行的处理。若是捕捉到SIGCHLD信号,则表示子进程已经终止,因此此信号的捕捉函数能够调用waitpid以取得该子进程的进程ID以及它的终止状态。
(3)执行系统默认动做。对大多数信号的系统默认动做是终止该进程。每个信号都有一个缺省动做,它是当进程没有给这个信号指定处理程序时,内核对信号的处理。有5种缺省的动做:
(1)异常终止(abort):在进程的当前目录下,把进程的地址空间内容、寄存器内容保存到一个叫作core的文件中,然后终止进程。
(2)退出(exit):不产生core文件,直接终止进程。
(3)忽略(ignore):忽略该信号。
(4)中止(stop):挂起该进程。
(5)继续(contiune):若是进程被挂起,刚恢复进程的动行。不然,忽略信号。
信号的发送与捕捉
kill()和raise()
kill()不只能够停止进程,也能够向进程发送其余信号。
与kill函数不一样的是,raise()函数运行向进程自身发送信号
#include<sys/types.h>
#include<signal.h>
int kill(pid_t pid,int signo);
int raise(int signo);
两个函数返回:若成功则为0,若出错则为-1。
kill的pid参数有四种不一样的状况:
(1)pid>0将信号发送给进程ID为pid的进程。
(2)pid==0将信号发送给其进程组ID等于发送进程的进程组ID,并且发送进程有许可权向其发送信号的全部进程。
(3)pid<0将信号发送给其进程组ID等于pid绝对值,并且发送进程有许可权向其发送信号的全部进程。如上所述同样,“全部进程”并不包括系统进程集中的进程。
(4)pid==-1 POSIX.1未定义种状况
kill.c
#include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <signal.h> #include <sys/types.h> #include <sys/wait.h> 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函数
使用alarm函数能够设置一个时间值(闹钟时间),在未来的某个时刻时间值会被超过。当所设置的时间被超事后,产生SIGALRM信号。若是不忽略或不捕捉引信号,则其默认动做是终止该进程。
#include<unistd.h>
unsigned int alarm(unsigned int secondss);
返回:0或之前设置的闹钟时间的余留秒数。
参数seconds的值是秒数,通过了指定的seconds秒后产生信号SIGALRM。每一个进程只能有一个闹钟时间。若是在调用alarm时,之前已为该进程设置过闹钟时间,并且它尚未超时,则该闹钟时间的余留值做为本次alarm函数调用的值返回。之前登记的闹钟时间则被新值代换。
若是有之前登记的还没有超过的闹钟时间,并且seconds值是0,则取消之前的闹钟时间,其他留值仍做为函数的返回值。
pause函数使用调用进程挂起直至捕捉到一个信号
#include<unistd.h>
int pause(void);
返回:-1,errno设置为EINTR
只有执行了一信号处理程序并从其返回时,pause才返回。
#include<unistd.h> #include<stdio.h> #include<stdlib.h> int main() { int ret; ret=alarm(5); pause(); printf("I have been waken up %d.\n",ret); }
信号的处理
当系统捕捉到某个信号时,能够忽略谁信号或是使用指定的处理函数来处理该信号,或者使用系统默认的方式。信号处理的主要方式有两种,一种是使用简单的signal函数,别一种是使用信号集函数组。
signal()
#include<signal.h>
void (*signal (int signo,void (*func)(int)))(int)
返回:成功则为之前的信号处理配置,若出错则为SIG_ERR
func的值是:(a)常数SIGIGN,或(b)常数SIGDFL,或(c)当接到此信号后要调用的的函数的地址。若是指定SIGIGN,则向内核表示忽略此信号(有两个信号SIGKILL和SIGSTOP不能忽略)。若是指定SIGDFL,则表示接到此信号后的动做是系统默认动做。当指定函数地址时,咱们称此为捕捉此信号。咱们称此函数为信号处理程序(signal handler)或信号捕捉函数(signal-catching funcgion).signal函数原型太复杂了,若是使用下面的typedef,则可使其简化。
type void sign(int);
sign *signal(int,handler *);
实例见:mysignal.c
#include <signal.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> 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); pause(); exit(0); }
信号集函数组
咱们须要有一个能表示多个信号——信号集(signal set)的数据类型。将在sigprocmask()这样的函数中使用这种数据类型,以告诉内核不容许发生该信号集中的信号。信号集函数组包含水量几大模块:建立函数集、登记信号集、检测信号集。
图见附件。
建立函数集
#include<signal.h>
int sigemptyset(sigset_t* set);
int sigfillset(sigset_t* set);
int sigaddset(sigset_t* set,int signo );
int sigdelset(sigset_t* set,int signo);
四个函数返回:若成功则为0,若出错则为-1
int sigismember(const sigset_t* set,int signo);
返回:若真则为1,若假则为0;
signemptyset:初始化信号集合为空。
sigfillset:初始化信号集合为全部的信号集合。
sigaddset:将指定信号添加到现存集中。
sigdelset:从信号集中删除指定信号。
sigismember:查询指定信号是否在信号集中。
登记信号集
登记信号处理机主要用于决定进程如何处理信号。首先要判断出当前进程阻塞能不能传递给该信号的信号集。这首先使用sigprocmask函数判断检测或更改信号屏蔽字,而后使用sigaction函数改变进程接受到特定信号以后的行为。
一个进程的信号屏蔽字能够规定当前阻塞而不能递送给该进程的信号集。调用函数sigprocmask能够检测或更改(或二者)进程的信号屏蔽字。
#include<signal.h>
int sigprocmask(int how,const sigset_t* set,sigset_t* oset);
返回:若成功则为0,若出错则为-1
oset是非空指针,进程是当前信号屏蔽字经过oset返回。其次,若set是一个非空指针,则参数how指示如何修改当前信号屏蔽字。
用sigprocmask更改当前信号屏蔽字的方法。how参数设定:
SIG_BLOCK该进程新的信号屏蔽字是其当前信号屏蔽字和set指向信号集的并集。set包含了咱们但愿阻塞的附加信号。
SIG_NUBLOCK该进程新的信号屏蔽字是其当前信号屏蔽字和set所指向信号集的交集。set包含了咱们但愿解除阻塞的信号。
SIG_SETMASK该进程新的信号屏蔽是set指向的值。若是set是个空指针,则不改变该进程的信号屏蔽字,how的值也无心义。
sigaction函数的功能是检查或修改(或二者)与指定信号相关联的处理动做。此函数取代了UNIX早期版本使用的signal函数。
#include<signal.h>
int sigaction(int signo,const struct sigaction* act,struct sigaction* oact);
返回:若成功则为0,若出错则为-1
参数signo是要检测或修改具体动做的信号的编号数。若act指针非空,则要修改其动做。若是oact指针为空,则系统返回该信号的原先动做。此函数使用下列结构:
struct sigaction{
void (*sa_handler)(int signo);
sigset_t sa_mask;
int sa_flags;
void (*sa_restore);
};
sa_handler是一个函数指针,指定信号关联函数,能够是自定义处理函数,还能够SIG_DEF或SIG_IGN;
sa_mask是一个信号集,它能够指定在信号处理程序执行过程当中哪些信号应当被阻塞。
sa_flags中包含许多标志位,是对信号进行处理的各类选项。具体以下:
SA_NODEFER\SA_NOMASK:当捕捉到此信号时,在执行其信号捕捉函数时,系统不会自动阻塞此信号。
SA_NOCLDSTOP:进程忽略子进程产生的任何SIGSTOP、SIGTSTP、SIGTTIN和SIGTOU信号
SA_RESTART:可以让重启的系统调用从新起做用。
SA_ONESHOT\SA_RESETHAND:自定义信号只执行一次,在执行完毕后恢复信号的系统默认动做。
检测信号是信号处理的后续步骤,但不是必须的。sigpending函数运行进程检测“未决“信号(进程不清楚他的存在),并进一步决定对他们作何处理。
sigpending返回对于调用进程被阻塞不能递送和当前未决的信号集。
#include<signal.h>
int sigpending(sigset_t * set);
返回:若成功则为0,若出错则为-1
信号集实例见:sigaction.c
#include <sys/types.h> #include <unistd.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <errno.h> #define SIG_DEL (void (*)())0 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(sigemptyset(&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"); else{ 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); } } }