笔者在学习linux的过程中对linux进程通信进行记录学习。现在在 Linux 中使用较多的进程间通信方式主要有以下几种。
(1)管道(Pipe)及有名管道(named pipe):管道可用于具有亲缘关系进程间的通信, 有名管道,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。
(2)信号(Signal):信号是在软件层次上对中断机制的一种模拟,它是比较复杂的通信 方式,用于通知接受进程有某事件发生,一个进程收到一个信号与处理器收到一个中断请求 效果上可以说是一样的。
(3)消息队列:消息队列是消息的链接表,包括 Posix 消息队列 systemV 消息队列。它 克服了前两种通信方式中信息量有限的缺点,具有写权限的进程可以向消息队列中按照一定 的规则添加新消息;对消息队列有读权限的进程则可以从消息队列中读取消息。
(4)共享内存:可以说这是最有用的进程间通信方式。它使得多个进程可以访问同一块 内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新。这种通信方式需要 依靠某种同步机制,如互斥锁和信号量等。
(5)信号量:主要作为进程间以及同一进程不同线程之间的同步手段。
(6)套接字(Socket):这是一种更为一般的进程间通信机制,它可用于不同机器之间的 进程间通信,应用非常广泛。
管道通信
管道主要有无名管道和有名管道。
无名管道:
它只能用于具有亲缘关系的进程之间的通信(也就是父子进程或者兄弟进程之间)。 ·
它是一个半双工的通信模式,具有固定的读端和写端。 ·
管道也可以看成是一种特殊的文件,对于它的读写也可以使用普通的 read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。
管道的创建与关闭
在管道的创建完成后。会有两个文件描述符对该管道得的读和写进行分别描述。fds[0]用于描述读管道,fds[1]用于描述写管道。
在创建管道时需要使用函数pipe。此函数的头文件包含在#include<unistd.h>。
所需头文件 #include
函数原型 int pipe(int fd[2])
函数传入值 fd[2]:管道的两个文件描述符,之后就可以直接操作这两个文件描述符 函数返回值 出错返回-1 , 成功返回0。
/*pipe.c*/
#include <unistd.h> //头文件
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
int pipe_fd[2]; //定义函数原型
/*创建一无名管道*/
if(pipe(pipe_fd)<0) //如果pipe创建成功返回0,失败则返回-1,然后输出
{
printf("pipe create error\n");
return -1;
}
else
printf("pipe create success\n");
/*关闭管道描述符*/
close(pipe_fd[0]); //关闭读通道
close(pipe_fd[1]); //关闭写通道
}
父子进程通信
原理如下:父进程先创建一个管道,在使用folk()创建子进程,子承父业。在子进程中也存父进程创建的管道。接下来我们就可以去实现父子进程的
通信了。下图可以看出在管道中分别对应着子进程和分进程的读端和写端,这里要注意。一个进程在读与写中只能选择一项进行,另一项需要使用close()进行给关闭。
管道读写的实际例子:
/*pipe_rw.c*/
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
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替换
/*函数解释:将s中当前位置后面的n个字节 (typedef unsigned int size_t )用 ch 替换并返回 s 。
memset:作用是在一段内存块中填充某个给定的值,它是对较大的结构体或数组进行清零操作的一种最快方法 。
memset()函数原型是extern void *memset(void *buffer, int c, int count) buffer:为指针或是数组,c:是赋给buffer的值,count:是buffer的长度.*/
/*创建管道*/
if(pipe(pipe_fd)<0)
{
printf("pipe create error\n");
return -1;
}
/*创建一子进程*/
if((pid=fork())==0)
{
printf("\n");
/*关闭子进程写描述符,并通过使父进程暂停 2 秒确保父进程已关闭相应的读描述符*/
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 %s\n",r_num,buf_r);
}
/*关闭子进程读描述符*/
close(pipe_fd[0]);
exit(0);
}
else if(pid>0)
{
/*/关闭父进程读描述符,并分两次向管道中写入 Hello Pipe*/
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函数使用
使用示例:
/*fifo_write.c*/
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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 process\n");
/*打开 FIFO 管道,并设置非阻塞标志*/
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);
}
/*fifl_read.c*/
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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);
}
运行结果:
终端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
更多推荐