一、什么是进程间通信

进程间通信是指在不同进程之间传播或交换信息;每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程A把数据从用户空间拷到内核缓冲区,进程B再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信

二、linux进程通信的目的

进程间通信有如下的目的:
1、数据传输,一个进程需要将它的数据发送给另一个进程,发送的数据量在一个字节到几M之间;
2、共享数据,多个进程想要操作共享数据,一个进程对数据的修改,其他进程应该立刻看到;
3、通知事件,一个进程需要向另一个或一组进程发送消息,通知它们发生了某件事情;
4、资源共享,多个进程之间共享同样的资源。为了做到这一点,需要内核提供锁和同步机制;
5、进程控制,有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

三、进程间通信的几种方式

  • 管道(匿名管道、命名管道)
  • 共享内存
  • 消息队列
  • 信号量

1、管道

本质:在内核中开辟一块缓冲区;若多个进程拿到同一个管道(缓冲区)的操作句柄,就可以访问同一个缓冲区,就可以进行通信

读写特性:
若管道中没有数据,则调用read读取数据会阻塞
若管道中数据满了(约64k),则调用write写入数据会阻塞
若管道的所有读端pipefd[0]被关闭,则继续调用write会产生异常导致进程退出 ----父子进程都调用close(pipefd[0])
若管道的所有写端pipefd[1]被关闭,则继续调用read,read读完管道中的数据后不再阻塞,而返回0 ----父子进程都调用close(pipefd[1])

2、共享内存

特性:
共享内存是最快的进程间通信方式
生命周期不随进程,随内核(没有人为情况下)
共享内存没有自带同步与互斥,多个进程进行访问的时候存在安全问题

3、队列
内核中的一个优先级队列,多个进程通过访问同一个队列,进行添加结点或者获取结点实现通信

msgget 创建 msgsnd 获取 msgctl 删除

4、信号量
信号量是用于实现进程间的同步与互斥(如共享内存不提供同步与互斥,存在安全隐患,可以使用信号量搭配共享内存使用)

四、共享内存

操作流程

  • 获取共享内存对象的ID
  • 将共享内存映射至本进程虚拟空间的某个区域
  • 不同进程通过对这块共享内存进行读写、传输数据
  • 当进程不再使用这块共享内存时,解除映射关系
  • 当没有进程再需要这块共享内存时,删除它

使用共享内存
相关API

  • 获取共享内存对象的ID:int shmget(key_t key, size_t size, int shmflg);
  • 映射共享内存:void *shmat(int shmid, const void *shmaddr, int shmflg);
  • 解除内存映射:int shmdt(const void *shmaddr);
  • 设置内存对象:int shmctl(int shmid, int cmd, struct shmid_ds *buf);
  • 查看IPC对象信息:ipcs -m

相关API

获取共享内存对象的ID
shmget函数负责创建或打开共享内存段

  • 函数原型:int shmget(key_t key, size_t size, int shmflg);
  • 函数功能:创建或打开一个共享内存对象
  • 函数参数:
  • key:IPC对象的键值,一般为IPC_PRIVATE或ftok返回的key值
  • Size:共享内存大小,一般为内存物理页的整数倍
  • shmflg:
  • IPC_CREAT:如果不存在与指定的key对应的段,那么就创建一个新段
  • IPC_EXCL:若key指定的内存存在且指定了IPC_CREAT,返回EEXIST错误
  • SHM_HUGETLB:使用巨页(huge page)
  • 返回值:共享内存的标识符ID

attach共享内存——使用共享内存
shmget函数,不过是在茫茫内存中创建了或找到了一块共享内存区域,但是这块内存和进程尚没有任何关系。要

想使用该共享内存,必须先把共享内存引入进程的地址空间,这就是attach操作。

  • 函数原型:void *shmat(int shmid, const void *shmaddr, int shmflg);
  • 函数功能:将shmid标识的共享内存引入到当前进程的虚拟地址空间
  • 函数参数
  • shmid:共享内存的IPC对象ID
  • shmaddr:
  • 若为NULL:共享内存会被attach到一个合适的虚拟地址空间,建议使用NULL
  • 不为NULL:系统会根据参数及地址边界对齐等分配一个合适的地址
  • shmflg:
  • IPC_RDONLY:附加只读权限,不指定的话默认是读写权限
  • IPC_REMAP: 替换位于shmaddr处的任意既有映射:共享内存段或内存映射
  • SHM_RND : 将shmaddr四舍五入为SHMMLBA字节的倍数

第二个参数是用来指定将共享内存放到虚拟地址空间的什么位置的。
大部分的普通青年都会将第二个参数设置为NULL,表示用户并不在意,一切交由内核做主。

  • 返回值:共享内存段的地址

shmat如果调用成功,则返回进程虚拟地址空间内的一个地址。如果失败,就会返回(void*)-1

detach共享内存——分离共享内存

shmdt函数仅仅是使进程和共享内存脱离关系,并未删除共享内存。shmdt函数的作用是将共享内存的引用计数减1。如前所述,只有共享内存的引用计数为0时,调用shmctl函数的IPC_RMID命令才会真正地删除共享内存。

  • 函数原型:int shmdt(const void *shmaddr);
  • 函数功能:解除内存映射,将共享内存分离出当前进程的地址空间
  • 函数参数:
  • shmaddr:共享内存地址

TIPS: 通过fork创建的子进程会继承父进程所附加的共享内存段,父子进程可以通过共享内存进行IPC通信。
在exec系统调用中,所有附加的共享内存段都会被分离
函数shmdt仅仅是使进程和共享内存脱离关系,将共享内存的引用计数减1,并未删除共享内存。
当共享内存的引用计数为0时,调用shmctl的IPC_RMID命令才会删除共享内存

设置共享内存属性

  • 函数原型:int shmctl(int shmid, int cmd, struct shmid_ds *buf);
  • 函数功能:获取/设置共享内存对象属性
  • 当cmd为IPC_STAT和IPC_SET时,需要用到第三个参数
  • 函数参数:
  • shmid:共享内存对象ID
  • cmd:
  • 当cmd为IPC_STAT和IPC_SET时,需要用到第三个参数
  • IPC_STAT: 用于获取shmid对应的共享内存的信息。
  • IPC_SET:IPC_SET也只能修改shm_perm中的uid、gid及mode
  • IPC_RMID:可以通过如下方式删除共享内存段:如果共享内存的引用计数shm_nattch等于0,则可以立即删除共享内存。
  • buf:
  • 将该内存对象关联的shmid_ds数据结构拷贝到参数buf中

编程方式

  1. 创建
  2. 映射
  3. 读写
  4. 解除映射
  5. 销毁

共享内存share memory写端

int main(int argc, char *argv[])
{
	key_t key = ftok(".", 588);
	int shm_id = shmget(key, 4096, IPC_CREAT|0666 );
	printf("shm_id:%d\n", shm_id);
 
	/* Automatic allocation */
	char *shm_p = shmat(shm_id, NULL, 0);
	memset(shm_p, 0, sizeof(shm_p));
 
	fgets(shm_p, 4096, stdin);
 
	sleep(30);
	shmctl(shm_id, IPC_RMID, NULL);
 
	return 0;
}

共享内存share memory读端

int main(int argc, char *argv[])
{
 	key_t key = ftok(".", 588);
	int shm_id= shmget(key, 4096, 0666);
 
	char *shm_p = shmat(shm_id, NULL, 0);
	printf("from share memory:%s\n", shm_p);
 
	shmdt(shm_p);
 
	return 0;
}

共享内存的通信限制

  • SHMMNI:系统所能创建的共享内存的最大个数,IPCMIN:32768
  • SHMMIN:一个共享内存段的最小字节数4096
  • SHMMAX:一个共享内存段的最大字节数33554432
  • SHMALL:系统中共享内存的分页总数2097152
  • SHMSEG:一个进程允许attch的共享内存段的大小个数

共享内存通信特点

  • 共享内存抛弃了“内核代理人”角色,提升了系统性能
  • 需要进程本身维护共享内存的各种问题:同步、互斥…
  • 一般需要信号量、互斥锁、文件锁等配合使用,在各个进程之间在高效通信的同时,防止发送数据的践踏,破坏