进程间通讯

管道     信号量     消息队列     共享内存     socket套接字

一般情况下,系统中运行着大量的进程,而每个进程之间并不是相互独立的,有些进程之间经常需要互相传递消息。但是每个进程在系统中都有自己的地址空间,操作系统通过页表和实际物理内存所关联,不允许其他进程随意进入。因此,就必须有一种机制既能保证进程之间的通信,又能保证系统的安全,即进程间通信机制——I P C (Inter_Process Communication)。

Linux中的内存空间分为系统空间和用户空间。在系统空间中,由于各个线程的地址空间都是共享的,即一个线程能够随意访问kernel中的任意地址,所以无需进程通信机制的保护。而在用户空间中,每个进程都有自己的地址空间,一个进程为了与其他进程通信,必须陷入到有足够权限访问其他进程空间的kernel中,从而与其他进程进行通信。在Linux中支持System V 进程通信的手段有三种:消息队列(Message queue)、信号量(Semaphore)、共享内存(Shared memory)。

管道

原理:

特点:

有名管道:可以在任意进程间进行通讯,通讯为双向,任意一端都可读可写,但同一时间只能一端读,一端写。

无名管道:只能在具有亲缘关系的进程间通讯(父子进程),单向,只能一端读一端写。

管道能在父子进程间传递数据,利用的是fork调用之后两个管道文件描述符fd[0],fd[1]都保持打开。一堆这样的文件描述符只能保证父子进程间同一个方向的传输,父进程和子进程必须有一个关闭fd[0],另一个关闭fd[1],要想实现父子间的双向传输,就必须用两个管道。

管道的默认大小:4k

管道的数据存放在哪里:内存中

创建管道失败的原因:  内存不够 ;文件描述符已达到上限,无法再打开文件描述符

信号量

原子操作:原子操作意为不可被中断的一个或一系列操作,也可以理解为就是一件事情要么做了,要么没做。

同步与互斥:同步:在访问资源的时候,以某种特定顺序的方式去访问资源 
                        互斥:一个资源每次只能被一个进程所访问。 

临界资源:不同进程能够看到的一份公共的资源(如:打印机,磁带机等),且一次仅允许一个进程使用的资源称为临界资源。

临界区:临界区是一段代码,在这段代码中进程将访问临界资源(例如:公用的设备或是存储器),当有进程进入临界区时,其他进程必须等待,有一些同步的机制必须在临界区段的进入点和离开点实现,确保这些共用资源被互斥所获得。

相关命令:ipcs -s 显示已存在的信号量;ipcrm -s 删除指定信号量。注意:有时候因为权限问题需要在root下查看与删除。

概念:信号量(Semaphore)可以被看做是一种具有原子操作的计数器,它控制多个进程对共享资源的访问,通常描述临界资源当中,临界资源的数目,常常被当做锁(lock)来使用,防止一个进程访问另外一个进程正在使用的资源。

信号量本身不具有数据交换的功能,而是控制其他资源来实现进程间通信,在此过程中负责数据操作操作的互斥、同步等功能。

简言之:信号量的主要目的是为了保护临界资源。

使用信号量的原因:为了防止出现因多个进程同时访问一个共享资源而引发的问题,我们需要一种方法,可以通过生成并使用令牌来授权,在任一时刻只能有一个执行流访问代码的临界区域。而信号量就可以提供这样的一种访问机制,让一个临界区同一时刻只有一个执行流在访问它。

原理:

操作方式:

(1)semget函数(创建信号量)

(2)semop函数(改变信号量的值)

对sem_op的补充:如果sem_op大于0,则对信号量的值semval增加sem_op,该进程对被操作信号量用有写权限;

                                 如果sem_op等于0,这是一个等待0操作,拥有读权限如果此时限号量的值为0,立即成功返回;

                                 如果sem_op小于0,表示对信号量的值进行减操作,拥有写权限

(3)semctl函数(删除和初始化信号量)

(4)ftok 函数把一个已存在的路径名和一个整数标识转换成一个key_t值,即IPC关键字

消息队列

原理:消息队列是SystemV版本中三种进程通信机制之一,另外两种是信号量和共享存储段。消息队列提供了进程间发送数据块的方法,而且每个数据块都有一个类型标识。消息队列是基于消息的,而管道是基于字节流。创建的消息队列,生命周期随内核,只有内核重启或用户主动去删除,才可以真正关闭消息队列。

特点:是消息的链表,具有特定的格式,存放在内存当中,由消息队列标识符标识

            允许一个或者多个进程向他写入与读取消息

            可实现消息的随即查询,不一定要以先进先出的顺序存取。

函数:

(1)msgget函数

(2)msgsnd函数

(3)msgrcv函数

(4)msgctl函数

共享内存:

原理:共享内存允许两个或更多进程访问同一块内存。当一个进程改变了这块内存中的内容的的时候,其他进程都会察觉到这个更改。

特点:因为所有进程共享同一块内存,共享内存在各种进程间通信方式中具有最高的效率。就像访问进程独有的内存区域一样快,并不需要通过系统调用或其他需要切入内核的过程来完成。同时还能避免对数据的各种不必要的复制。

要使用一块共享内存,进程必须先分配它。其他需要访问这个共享内存块的每一个进程都必须将这个共享绑定(attach)到自己的地址空间中(系统维护一个对该内存的引用计数器,通过ipcs -s 命令可查看有几个进程在使用该共享内存块) 
。当通信完毕后,所有进程从共享内存块脱离,由一个进程释放该共享内存块。要注意的是,所有用户申请的共享内存块最终大小都必须是向上取整为系统页面大小的整数倍。在Linux系统中,内存页面大小默认是4KB。

函数:

(1)shmget(得到一个共享内存标识符或创建一个共享内存对象并返回共享内存标识符。)

int shmget(key_t key, size_t size, int shmflg);

key ftok函数返回的I P C键值
size 大于0的整数,新建的共享内存大小,以字节为单位,获取已存在的共享内存块标识符时,该参数为0,
shmflg IPC_CREAT||IPC_EXCL 执行成功,保证返回一个新的共享内存标识符,附加参数指定IPC对象存储权限,如|0666
返回值:成功返回共享内存的标识符,出错返回-1,并设置error错误位。

(2)shmat(连接共享内存标识符为shmid的共享内存,连接成功后把共享内存区对象映射到调用进程的地址空间)

void *shmat(int shmid, const void shmaddr, int shmflg);

shmid: 共享内存标识符
shmaddr: 指定共享内存出现在进程内存地址的什么位置,通常指定为NULL,让内核自己选择一个合适的地址位置
shmflg: SHM_RDONLY 为只读模式,其他参数为读写模式

(3)shmdt(与shmat函数相反,是用来断开与共享内存附加点的地址,禁止本进程访问此片共享内存)

void *shmdt(const void* shmaddr);

函数并不删除所指定的共享内存区,而是将之前用shmat函数连接好的共享内存区脱离目前的进程

传入形参:起始地址

(4)shmctl(控制共享内存块)

int shmctl(int shmid, int cmd, struct shmid_ds* buf);

shmid:共享内存标识符
cmd: 
IPC_STAT:得到共享内存的状态,把共享内存的shmid_ds结构赋值到buf所指向的buf中
IPC_SET:改变共享内存的状态,把buf所指向的shmid_ds结构中的uid、gid、mode赋值到共享内存的shmid_ds结构内
IPC_RMID:删除这块共享内存
buf:共享内存管理结构体

猜你喜欢

转载自blog.csdn.net/kkkwinter/article/details/81264942