笔者在学习linux的过程中对linux进程通信进行记录学习。现在在 Linux 中使用较多的进程间通信方式主要有以下几种。
(1)管道(Pipe)及有名管道(named pipe):管道可用于具有亲缘关系进程间的通信, 有名管道,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。
(2)信号(Signal):信号是在软件层次上对中断机制的一种模拟,它是比较复杂的通信 方式,用于通知接受进程有某事件发生,一个进程收到一个信号与处理器收到一个中断请求 效果上可以说是一样的。
(3)消息队列:消息队列是消息的链接表,包括 Posix 消息队列 systemV 消息队列。它 克服了前两种通信方式中信息量有限的缺点,具有写权限的进程可以向消息队列中按照一定 的规则添加新消息;对消息队列有读权限的进程则可以从消息队列中读取消息。
(4)共享内存:可以说这是最有用的进程间通信方式。它使得多个进程可以访问同一块 内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新。这种通信方式需要 依靠某种同步机制,如互斥锁和信号量等。
(5)信号量:主要作为进程间以及同一进程不同线程之间的同步手段。
(6)套接字(Socket):这是一种更为一般的进程间通信机制,它可用于不同机器之间的 进程间通信,应用非常广泛。
管道通信管道主要有无名管道和有名管道。
无名管道:它只能用于具有亲缘关系的进程之间的通信(也就是父子进程或者兄弟进程之间)。 ·
它是一个半双工的通信模式,具有固定的读端和写端。 ·
管道也可以看成是一种特殊的文件,对于它的读写也可以使用普通的 read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。
管道的创建与关闭在管道的创建完成后。会有两个文件描述符对该管道得的读和写进行分别描述。fds[0]用于描述读管道,fds[1]用于描述写管道。
在创建管道时需要使用函数pipe。此函数的头文件包含在#include
所需头文件 #include
函数原型 int pipe(int fd[2])
函数传入值 fd[2]:管道的两个文件描述符,之后就可以直接操作这两个文件描述符 函数返回值 出错返回-1 , 成功返回0。
#include父子进程通信//头文件 #include #include #include int main() { int pipe_fd[2]; //定义函数原型 if(pipe(pipe_fd)<0) //如果pipe创建成功返回0,失败则返回-1,然后输出 { printf("pipe create errorn"); return -1; } else printf("pipe create successn"); close(pipe_fd[0]); //关闭读通道 close(pipe_fd[1]); //关闭写通道 }
原理如下:父进程先创建一个管道,在使用folk()创建子进程,子承父业。在子进程中也存父进程创建的管道。接下来我们就可以去实现父子进程的
通信了。下图可以看出在管道中分别对应着子进程和分进程的读端和写端,这里要注意。一个进程在读与写中只能选择一项进行,另一项需要使用close()进行给关闭。
管道读写的实际例子:#include#include #include #include #include int main() { int pipe_fd[2]; pid_t pid; char buf_r[100]; //用来暂时存储数据的数组 char* p_wbuf; int r_num; void *memset(void *s, int ch, size_t n); memset(buf_r,0,sizeof(buf_r)); //将buf_r数组中的内容全部使用0替换 if(pipe(pipe_fd)<0) { printf("pipe create errorn"); 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 the pipe is %sn",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 write1 success!n"); if(write(pipe_fd[1]," Pipe",5)!= -1) printf("parent write2 success!n"); close(pipe_fd[1]); sleep(3); waitpid(pid,NULL,0); exit(0); } }
运行结果为:
[root@(none) 1]# ./pipe_rw2 parent write1 success! parent write2 success! 10 numbers read from the pipe is Hello Pipe管道读写注意:
首先只有在管道的读端存在时向管道中写入数据才有意义。否则,向管道中写入数据的进 程将收到内核传来的 SIFPIPE 信号(通常 Broken pipe 错误)。
· 向管道中写入数据时,linux 将不保证写入的原子性,管道缓冲区一有空闲区域,写进程 就会试图向管道写入数据。如果读进程不读取管道缓冲区中的数据,那么写操作将会一直阻塞。 这句话也很好理解,一个杯子一直装水不放水,杯子就会被装满,在装不进去。·
父子进程在运行时,它们的先后次序并不能保证,因此,在这里为了保证父进程已经 关闭了读描述符,可在子进程中调用 sleep 函数。这里就说明了在进行读写操作时,需要使用延时确保写入完全后在进行读,才能保证数据的完整性。
标准流管道与 Linux 中文件操作有基于文件流的标准 I/O 操作一样,管道的操作也支持基于文件流 的模式。这种基于文件流的管道主要是用来创建一个连接到另一个进程的管道,这里的“另 一个进程”也就是一个可以进行一定操作的可执行文件,例如,用户执行“cat popen.c”或 者自己编写的程序“hello”等。由于这一类操作很常用,因此标准流管道就将一系列的创建 过程合并到一个函数 popen 中完成。它所完成的工作有以下几步。 · 创建一个管道。 · fork 一个子进程。 · 在父子进程中关闭不需要的文件描述符。 · 执行 exec 函数族调用。 · 执行函数中所指定的命令。 这个函数的使用可以大大减少代码的编写量,但同时也有一些不利之处,例如,它没有 前面管道创建的函数灵活多样,并且用 popen 创建的管道必须使用标准 I/O 函数进行操作, 但不能使用前面的 read、write 一类不带缓冲的 I/O 函数。 与之相对应,关闭用 popen 创建的流管道必须使用函数 pclose 来关闭该管道流。该函数 关闭标准 I/O 流,并等待命令执行结束。
函数格式见下图:
有名管道前面只是有关无名管道的一些基础知识。现在进行的是另一个主角。有名管道。
有名管道可以使互不相关的两个进程实现彼此通信。 该管道可以通过路径名来指出,并且在文件系统中是可见的。在建立了管道之后,两个进程 就可以把它当作普通文件一样进行读写操作,使用非常方便。
有名管道的创建可以使用函数 mkfifo(),该函数类似文件中的 open()操作,可以指定管 道的路径和打开的模式。用户还可以在命令行使用“mknod 管道名 p”来创建有名管道。
mkfifo函数使用
使用示例:
#include#include #include #include #include #include #include #define FIFO "/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 processn"); fd=open(FIFO_SERVER,O_WRonLY|O_NONBLOCK,0); if(argc==1) printf("Please send somethingn"); 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 latern"); } else printf("write %s to the FIFOn",w_buf); } #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 fifoservern"); 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 yetn"); } printf("read %s from FIFOn",buf_r); sleep(1); } pause(); unlink(FIFO); }
运行结果:
终端1:
[root@localhost FIFO]# ./read Preparing for reading bytes… read from FIFO read from FIFO read from FIFO read from FIFO read from FIFO read hello from FIFO read from FIFO read from FIFO read FIFO from FIFO read from FIFO read from FIFO …
终端2:
[root@localhost]# ./write hello write hello to the FIFO [root@localhost]# ./read FIFO write FIFO to the FIFO



