进程间通信

进程间通信

由于进程之间不共享地址空间,所以要想实现进程间的通信,需要采取以下几种方法。

管道

管道本质上就是一个文件,前面的进程以写方式打开文件,后面的进程以读方式打开。这样前面写完后面读,于是就实现了通信,我们可以看出,管道实现的通信是单向的,如果我们想要实现双向通信,就必须创建两个管道。管道中存放的是无格式的字节流数据。实际上管道的设计也是遵循UNIX的“一切皆文件”设计原则的,它本质上就是一个文件。Linux系统直接把管道实现成了一种文件系统,借助VFS给应用程序提供操作接口。

详细参考博客https://zhuanlan.zhihu.com/p/58489873

Linux VFS(虚拟文件系统)机制参考博客https://www.cnblogs.com/jimbo17/p/10107318.html

管道的类型

Linux上的管道分两种类型:匿名管道和命名管道

(1)匿名管道

目前在任何一个shell中,都可以使用“|”连接两个命令,shell会将前后两个进程的输入输出用一个管道相连,以便达到进程间通信的目的:

[zorro@zorro-pc pipe]$ ls -l /etc/ | wc -l

“|”表示的管道就是匿名管道,它只能在父子进程中使用,父进程在产生子进程前必须打开一个管道文件,然后fork产生子进程,子进程通过拷贝父进程的进程地址空间获得同一个管道文件的描述符,以达到使用同一个管道进行通信的目的。除了父子进程外,没有其他进程知道这个管道文件描述符,因此这也保证了信息传输的安全性,但也降低了管道的通用性。shell中前后两个进程均为shell的子进程,它们通过从shell父进程中拷贝的管道文件描述符进行通信。

(2)命名管道(也被称为FIFO)

Linux内核提供了命名管道以实现不相关进程间的通信,它提前创建了一个类型为管道的设备文件,在进程里只要使用这个设备文件,就可以相互通信。

在命令行中使用命名管道

创建命名管道:

$ mkfifo myPipe(管道名字)  

该文件类型为p,表示这是一个管道文件。有了这个管道文件,系统中就有了对一个管道的全局名称,于是任何两个不相关的进程都可以通过这个管道文件进行通信了。

往管道写入数据:

$ echo "hello" > myPipe  // 将数据写进管道

此时写操作会阻塞,直到管道另一端进程开始读数据。

读数据:

$ cat < myPipe  // 读取管道里的数据
hello

可见,管道效率很低,不适合进程间频繁地交换数据

在系统编程中使用管道

我们可以称匿名管道和命名管道分别为PIPE和FIFO。

PIPE

(1)创建匿名管道

#include <unistd.h>
int pipe(int pipefd[2]);

该方法创建出两个文件描述符,数组pipefd通过引用这两个文件描述符进行文件操作。pipefd[0]以读的方式打开,作为管道的读描述符,pipefd[1]以写的方式打开,作为管道的写描述符。从管道写端写入的数据会被内核缓存直到有人从另一端读取为止。

(2)使用匿名管道

管道推荐的使用方法是其单工模式:即只有两个进程通信,一个进程只写管道,另一个进程只读管道,即父进程关闭管道的读端,只写管道。子进程关闭管道的写端,只读管道。

FIFO

命名管道在底层的实现跟匿名管道完全一致,区别只是命名管道会有一个全局可见的文件名以供别人open打开使用。

(1)创建命名管道

#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *filename, mode_t mode);
int mknod(const char *filename, mode_t mode | S_IFIFO, (dev_t)0);//比较老,尽量不用

这两个函数都能创建一个FIFO文件,注意是创建一个真实存在于文件系统中的文件,filename指定了文件名,而mode则指定了文件的读写权限。

(2)使用命名管道

创建完之后,其他进程就可以使用open()、read()、write()标准文件操作等方法进行使用了。其余所有的操作跟匿名管道使用类似。

不管是匿名管道还是命名管道,进程写入的数据都是缓存在内核中,另一个进程读取数据时候自然也是从内核中获取,同时通信数据都遵循先进先出原则,不支持 lseek 之类的文件定位操作。

消息队列

消息队列与命名管道类似,与命名管道相比消息队列的优势在于,它独立于发送和接收进程而存在,这消除了在同步命名管道的打开和关闭时可能产生的一些困难。而且,每个数据块被认为含有一个类型,接收进程可以独立地接收含有不同类型值的数据块。

A 进程要给 B 进程发送消息,A 进程把数据放在对应的消息队列后就可以正常返回了,B 进程需要的时候再去读取数据就可以了。适用于进程间的频繁沟通,但是这种通信方式也存在两个缺点,一个是通信不及时,二是对数据有大小限制

消息队列不适合比较大数据的传输,因为在内核中每个消息体都有一个最大长度的限制,同时所有队列所包含的全部消息体的总长度也是有上限。在 Linux 内核中,会有两个宏定义 MSGMAXMSGMNB,它们以字节为单位,分别定义了一条消息的最大长度和一个队列的最大长度。

消息队列通信过程中,存在用户态与内核态之间的数据拷贝开销,因为进程写入数据到内核中的消息队列时,会发生从用户态拷贝数据到内核态的过程,同理另一进程读取内核中的消息数据时,会发生从内核态拷贝数据到用户态的过程。

消息队列是保存在内核中的消息链表,在发送数据时,会分成一个一个独立的数据单元,也就是消息体(数据块),消息体是用户自定义的数据类型,消息的发送方和接收方要约定好消息体的数据类型,所以每个消息体都是固定大小的存储块,不像管道是无格式的字节流数据。如果进程从消息队列中读取了消息体,内核就会把这个消息体删除。

消息队列生命周期随内核,如果没有释放消息队列或者没有关闭操作系统,消息队列会一直存在,而前面提到的匿名管道的生命周期,是随进程的创建而建立,随进程的结束而销毁。

共享内存

共享内存的机制,就是拿出一块虚拟地址空间来,映射到相同的物理内存中。这样这个进程写入的东西,另外一个进程马上就能看到了,都不需要拷贝来拷贝去,传来传去,大大提高了进程间通信的速度

实现步骤:

(1)进程通过调用shmget(Shared Memory GET 获取共享内存)来分配一个共享内存块。

#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size,int shmflg);
//key:进程间通信键值,ftok() 的返回值。彼此无关的进程可以通过指定同一个键以获取对同一个共享内存块的访问。
//size:该共享存储段的长度(字节)。
//shmflg:标识函数的行为及共享内存的权限,取值为IPC_CREAT:如果不存在就创建,IPC_EXCL:如果已经存在则返回失败
//返回值: 成功:共享内存标识符。 失败:-1。

(2)然后每个进程通过shmat(Shared Memory Attach 绑定到共享内存块),将进程的逻辑虚拟地址空间指向共享内存块中。

#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
//shmid:共享内存标识符,shmget() 的返回值。
//shmaddr:共享内存映射地址(若为 NULL 则由系统自动指定),推荐使用 NULL。
//shmflg:共享内存段的访问权限和映射条件( 通常为 0 ),0:共享内存具有可读可写权限。SHM_RDONLY:只读SHM_RND:(shmaddr 非空时才有效)
//返回值:成功:共享内存段映射地址( 相当于这个指针就指向此共享内存 ),失败:-1

当一个进程不再使用一个共享内存块的时候应通过调用 shmdt(Shared Memory Detach,脱离共享内存块)函数与该共享内存块脱离。

#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);

(3)随后需要访问这个共享内存块的进程都必须将这个共享内存绑定到自己的地址空间中去。

(4)当一个进程往一个共享内存快中写入了数据,共享这个内存区域的所有进程就可用都看到其中的内容。

由于多个进程会对这块共享内存进行操作,所以会引入多进程竞争共享资源问题,从而造成数据错乱。为了解决这个问题,我们可以采用信号量实现进程间的互斥和同步。

信号量

为了防止多进程竞争共享资源,而造成的数据错乱,所以需要保护机制,使得共享的资源,在任意时刻只能被一个进程访问。正好,信号量就实现了这一保护机制。

信号量其实是一个整型的计数器,主要用于实现进程间的互斥与同步,而不是用于缓存进程间通信的数据

信号

信号是进程间通信机制中唯一的异步通信机制
详见另一篇博客进程间通信方式之一——信号

socket

管道、消息队列、共享内存、信号量和信号都是在同一台主机上进行进程间通信,那要想跨网络与不同主机上的进程之间通信,就需要 Socket 通信了。Socket 实际上不仅用于不同的主机进程间通信,还可以用于本地主机进程间通信,可根据创建 Socket 的类型不同,分为三种常见的通信方式,一个是基于 TCP 协议的通信方式,一个是基于 UDP 协议的通信方式,一个是本地进程间通信方式。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
资源包主要包含以下内容: ASP项目源码:每个资源包中都包含完整的ASP项目源码,这些源码采用了经典的ASP技术开发,结构清晰、注释详细,帮助用户轻松理解整个项目的逻辑和实现方式。通过这些源码,用户可以学习到ASP的基本语法、服务器端脚本编写方法、数据库操作、用户权限管理等关键技术。 数据库设计文件:为了方便用户更好地理解系统的后台逻辑,每个项目中都附带了完整的数据库设计文件。这些文件通常包括数据库结构图、数据表设计文档,以及示例数据SQL脚本。用户可以通过这些文件快速搭建项目所需的数据库环境,并了解各个数据表之间的关系和作用。 详细的开发文档:每个资源包都附有详细的开发文档,文档内容包括项目背景介绍、功能模块说明、系统流程图、用户界面设计以及关键代码解析等。这些文档为用户提供了深入的学习材料,使得即便是从零开始的开发者也能逐步掌握项目开发的全过程。 项目演示与使用指南:为帮助用户更好地理解和使用这些ASP项目,每个资源包中都包含项目的演示文件和使用指南。演示文件通常以视频或图文形式展示项目的主要功能和操作流程,使用指南则详细说明了如何配置开发环境、部署项目以及常见问题的解决方法。 毕业设计参考:对于正在准备毕业设计的学生来说,这些资源包是绝佳的参考材料。每个项目不仅功能完善、结构清晰,还符合常见的毕业设计要求和标准。通过这些项目,学生可以学习到如何从零开始构建一个完整的Web系统,并积累丰富的项目经验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值