进程通信
进程通信是指进程之间的信息交换。由于进程的互斥与同步,需要在进程间交换一定的信息,所以被称为进程通信,但这只是低级的进程通信。原因在于:
(1)效率低,每次只能从缓冲区中取得一个消息。
(2)通信对用户不透明。
在进程之间要传送大量的数据时,应当利用OS提供的高级通信工具,该工具最主要的特带你的是:
(1)使用方便。OS隐藏了实现进程通信的具体细节,向用户提供了一组用于实现高级通信的命令(原语),用户可以方便的直接利用它实现进程之间的通讯。即通信过程对用户是透明的。这样就大大减少了通信程序编程上的复杂性。
(2)高效的传送大量的数据。用户可直接利用高级通信命令(原语)高效地传送大量的数据。
进程通讯的类型
1、共享存储器系统(Shared-Memory System)
(1)基于共享数据结构的通信方式
(2)基于共享存储区的通信方式
2、管道(pipe)通信系统
“管道”是只用于连接的一个读进程和一个写进程以实现的他们的之间通信的一个共享文件。因为可以在进程间传输大量的数据,所以被广泛使用。
3、消息传递系统(Message passing System)
(1)直接通信方式:指发送进程利用OS所提供的发送原语,直接把消息发送给目标进程。
(2)简介通信方式:指发送和接收进程,都共享中间实体(邮箱)的方式进行消息的发送和接收,完成进程间的通信。
4、客户机-服务器系统(Client-Server System)
(1)套接字
1)基于文件型:通信进程都运行在一台机器上,套接字是基于本地文件系统支持的,一个套接字关联到一个特殊文件,通信双方通过对这个特殊文件的读写实现通信,原理类似于管道。
2)基于网络型:通信的双方不在一个主机上,通过套接字是双方建立连接,是一种非对称通信。
(2)远程过程调用
(3)远程方法调用
Liunx进程通信的函数
1、mmap和munmap
功能:mmap可以将磁盘文件直接映射到内存中,这样就可以通过文件指针直接操作内存,而不需要read和write。munmap是关闭映射的内存。
void* mmap(void* addr,size_t length,int prot,int flags,int fd,off_t sffset);
参数说明:
(1)addr:映射到内存中的地址的起始位置(只能在用户空间中映射)。操作系统会在这个地址附近为我们分配一段空间。参数一般为NULL,表示由操作系统自动分配空间。
(2)length:申请空间的长度。
(3)prot:有四种取值
*PROT_EXEC表示映射的这一段文件,是可以运行的,例如映射共享库。
*PROT_READ表示映射的这一段可读。
*PROT_WRITE表示映射的这一段可写。
*PROT_NONE表示映射的这一段不可访问。
(4)flag:一般有两种取值
*MAP_SHARED:共享,即一个文件修改了内存后磁盘文件也会改变。
*MAP_PRIVATE:私有,即文件被修改后磁盘文件不会发生改变。
(5)fg:文件描述符,即文件必须被打开。
(6)offset:偏移量,因为一个页面的大小为4k,所以偏移量必须是4k的整数倍。
返回值:成功返回映射的首地址,出错则返回MAP_FAILED。当进程终止时,该进程的映射内存会自动解除,不过并不推荐,因为可能会造成内存泄漏。可以调用munmap()函数解除映射,munmap()成功返回0,出错返回-1。
2、管道
(1)pipe()
(2)fifo()
int pipe(int filedes[2]);
功能:
在内存在建立一条管道。(在一段进行读,另一端进行写)管道是利用循环队列创建的,当队满时,就会阻塞写端,等待读端读取数据,直到队列中有空间,写段才允许被再次写入。
参数说明:
(1)filedes[2]:传出参数,pipe函数会将文件的描述符,存在filedes在返回,其中filedes[0]为读端的文件描述符,而filedes[1]为写段的文件描述符。
返回值:0表示成功,-1表示失败。
在利用管道进行进程间通信时,必须确定管道的方向,即管道的一端只能读另一端只能写,如果允许在两端都进行读写,就会引起通讯混乱和信息丢失。那如何实现两个进程通信那?可以使用两个管道。在这里需要注意一点pipe只能在两个有血缘关系的进程间通信,即在pipe后通过fork函数,让子进程继承父进程的管道,然后进行通信。"
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
int main()
{
int fileDis1[2];
int fileDis2[2];
char buf1[1024];
char buf2[1024];
//创建一个父写子读的管道
if(pipe(fileDis1)<0||pipe(fileDis2)<0)
{
perror("pipe");
_exit(1);
}
printf("管道1的大小:%ld\n",fpathconf(fileDis1[0],_PC_PIPE_BUF));
printf("管道2的大小:%ld\n",fpathconf(fileDis2[0],_PC_PIPE_BUF));
int pid=fork();
if(pid>0)
{
//父进程
close(fileDis1[0]);
close(fileDis2[1]);
while(1)
{
if(read(fileDis2[0],buf2,1024)>=0)
{
printf("%d:%s\n",getpid(),buf2);
}
else
{
printf("CHIRD NO DATA\n");
}
scanf("%s",buf1);
write(fileDis1[1],buf1,strlen(buf1));
}
}
else if(pid==0)
{
close(fileDis1[1]);
close(fileDis2[0]);
while(1)
{
scanf("%s",buf2);
write(fileDis2[1],buf2,strlen(buf2));
//子进程回阻塞,等待父进程输入。
if(read(fileDis1[0],buf1,1024)>=0)
{
printf("%d:%s\n",getpid(),buf1);
}
else
{
printf("FATHER NO DATA\n");
}
}
}
return 0;
}
这里需要注意两个进程的读写顺序,必须是一个进程为“先读后写”,另一个为“先写后读”。否则就会到导致两个进程相互等待(阻塞)。
使用管道是需要注意以下3种情况:
(1)管道写段被关闭,读端没有关闭:管道中剩余数据被读取完后,,就会返回0。
(2)管道写段没有关闭,读端也没有关闭:管道中没有数据,read就会被阻塞。管道中数据满了,会使write阻塞。
(3)管道写段没有关闭,读端被关闭:当写段写入数据时,会使写段进程异常终止(发送SIGPIPE信号)。
可以利用fcntl函数为管道设置非阻塞(O_NONBLOCK)。通过fpathconf(int fd,int name);可以测试管道的大小。
(2)fifo()
有名管道,解决没有亲缘关系的进程之间的通讯。创建fifo管道有两种方式(1)利用mkfifo 命令创建。(2)使用mkfifo函数。
利用函数创建了和创建文件是一样。
int mkfifo(char* fileName,mode_t mode);
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
//读端
int main()
{
//可将设置为可读可写的非阻塞模式
char buf[1024];
int flags=open("./fifo",O_RDONLY);
if(flags<0)
{
perror("open\n");
_exit(1);
}
while(1)
{
int len;
len=read(flags,buf,sizeof(buf));
if(len>0)
{
//write(STDOUT_FILENO,buf,len);
buf[len]='\0';
printf("%s\n",buf);
}
}
close(flags);
return 0;
}
//写端
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
int main()
{
char buf[1024];
int flags=open("./fifo",O_WRONLY);
if(flags<0)
{
perror("open");
_exit(1);
}
while(1)
{
scanf("%s",buf);
write(flags,buf,strlen(buf));
}
close(flags);
return 0;
}
使用fifo需要注意:
*读端没有打开,写端回被阻塞。
*FIFO支持支持双向通讯。(pipe时单向通信,因为父子进程共享同一个file结构体)
*FIFO可以有一个读端,多个写段,也可以有一个写段,多个读端。