进程间通信

一. 进程间通信

虽然进程间具有独立性,但其也可进行进程通信

1. 进程间通信的目的

  • 数据传输:一个进程需要将它的数据传递给另一个线程
    如:who | grep anthony,需要把who这个进程运行的结果传递给后面
  • 资源共享:多个进程之间共享同样的资源
  • 通知事件:一个进程需要向另一个或一组进程发送信息,通知它(它们)发生了某种事件(如进程终止要通知父进程)
  • 进程控制:有些进程希望完全控制另一个进程的执行(如:Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它们的状态改变(如父进程控制子进程)

2. 进程间通信发展

进程间通信简称 IPC

  • 管道
  • System V进程间通信
  • POSIX进程间通信

3. 进程间通信分类

  • 管道:
    匿名管道(pipe),命名管道
  • System V IPC
    消息队列、共享内存、信号量
  • POSIX IPC
    消息队列、共享内存、信号量、互斥量、条件变量、读写锁

4. 怎样实行通信,以及为什么会有不同的通信方式

让两个进程之间实行通信:让两个进程看到同一份资源
这个资源由谁提供,以什么方式提供的,就决定了不同的通信方式

二. 管道

这个资源(内存)由OS以文件形式提供就为管道
(在文件 inode 信息中可以判断其是否为管道)

1. 什么是管道

管道是 Unix 中最古老的进程间通信的形式
我们把从一个进程连接到另一个进程的一个数据流称为一个”管道“

一个进程拥有以下结构
task_struct、mm_struct、页表、file_struct、fd_array、inode

在这里插入图片描述
假设左为父进程,右为子进程
父进程往 page 中写入,子进程可以读取父进程写入的内容
所以这就是管道

2. 匿名管道

用于有亲缘关系的进程(如父子进程)

如 xxx | xxx …左写右读

可把管道当作文件看待(Linux中一切皆文件)
who | wc -l

who 本应写到显示器的内容写到文件(管道),再从管道输出到 wc -l 进程中

如何创建
#include <unistd.h>
int pipe(int fd[2]);

参数:
1. fd是文件描述符数组,其中fd[0]是读端,fd[1]是写端,相当于将一个文件以读写两种形式打开。相当于返回建立管道文件的文件描述符。

返回值:
成功返回0,失败返回错误码
  • 创建管道时,会先打开文件,不能用 open 方式打开,而用 pipe去打开(读写形式打开)
    (将一个文件分别以读、写两种形式打开,打开的是一个文件,以两种形式打开)
  • fd 中,fd[0]是读,fd[1]是写

能直接在进程中用一个 char buffer[1024] 作缓存,父进程写,子进程读吗?

这样是不行的,父子进程数据是独立的,父进程去写的时候,会发生写时拷贝,父子进程访问的即不是一个数据
具体实现
int main(){
	int pipefd[2] = {0};
	pipe(pipefd);
	pid_t id = fork();
	if(id == 0){
		close(fd[0]);
		const char* msg = "I am child\n";
		while(1){
			write(pipefd[1],msg,strlen(msg));
			sleep(3);
		}
	}
	else{
		close(pipefd[1]);
		char buffer[1024];
		while(1){
			size_t s = read(pipefd[0],buffer,sizeof(buffer);
			if(s > 0){
				buffer[s] = 0;
				printf("father get message:%s\n",buffer);
			}
		}
	}
	return 0;
}
  • 如果子进程不将数据写入,那么父进程将不会读取且打印。也就是说子进程如果 sleep(3) ;那么父进程也 sleep(3) ;父进程发生了 阻塞
  • 整个读写节奏,是按 的节奏进行
  • 如果写端不关闭文件描述符,读端可能会阻塞
  • 如果读写端都不 sleep 一下,或者让读端 sleep一下,写端会一下将管道写满进入 阻塞 ,等待读端读出后,写端再进行写入
    (读取和写入不是写一条,读一条,有可能是一次写入很多,也有可能是一次读取多个数据,也受传入读取大小的影响)
    所以在管道中,读取或写入条件不满足时,读取端或写入端都要被阻塞
  • 如果写端关闭文件描述符,读端在读完管道数据后,会读到文件结尾
  • 如果读端关闭,写入端的数据没有人去读取,写端可能会被 OS 直接杀掉(OS 不做浪费系统资源,降低效率的事)(用信号将其杀死)

3. 命名管道

可在不相关的进程之间交换数据,可使用 FIFO 文件来做,它叫做命名管道,命名管道是一种特殊类型的文件。

创建管道
  1. 命名管道可以从命令行上创建 (可在两个终端进行通信)
mkfifo filename
  1. 命名管道也可从程序创建
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char* filename,mode_t mode);

参数:
* 你想创建的管道对应的路径+名称
* 创建的管道的权限

返回值:
返回-1,则调用失败 
命名管道的特点
  • fifo 是一个特殊文件,大小始终为0,它是 OS 为了两个进程进行通信,在内存中创建的管道文件,所以两个进程通信的数据就不会刷新到 磁盘上
  • 通信时怎么退出管道?因为进程退出,则管道退出,所以ctrl + c关闭即可

4. 匿名管道与命名管道的区别

  • 匿名管道由 pipe 函数创建且打开
  • 命名管道由 mkfifo 函数创建,打开用 open
  • 匿名只是 亲缘关系,命名是 不同进程
  • 命名管道与匿名管道之间唯一区别在于它们打开方式不同,一旦完成这些工作,它们有相同语义。
  • 命名管道文件只是缓存区的标识,删除命名管道文件后,进程还能通信,因为管道声明周期随进程,管道文件只是标识
  • 管道只能单向通信

三. 共享内存

跳过文件,直接在系统内核层帮我们构建通信

1. 共享内存

共享内存是 最快的IPC 形式,一旦这样的内存映射到共享它的进程地址空间,这些进程间数据的传递不再涉及到内核。进程不再通过执行进入内核的系统调用来传递彼此的数据

2. 共享内存原理

在这里插入图片描述
管道存在用户到内核,内核到用户的拷贝,但此时共享内存不存在拷贝,一个进程往里面写,另一个进程拿就即可,所以只存在外设与用户的拷贝。

3. 共享内存的数据结构

共享内存也需管理,并且还有多块共享内存。
所以也需要先描述再组织,构建数据结构进行描述与组织。

struct shmid_ds{
	struct ipc_perm shm_perm; /* operation perms */ (当前共享内存与用户相关信息)
	int shm_segsz; /* size of segment (bytes) */
	__kernel_time_t shm_atime; /* last attach time */  (关联、挂接)
	__kernel_time_t shm_dtime; /* last detach time */  (取消关联)
	__kernel_time_t shm_ctime; /* last change time */
	__kernel_ipc_pid_t shm_cpid /* pid of creator */   (共享内存谁创建的)
	__kernel_ipc_pid_t shm_lpid /* pid of last operator */ (最后一个操作者)
	unsigned short shm_nattch; /* no. of current attaches */ (当前有多少个进程与其相关)
	unsigned short shm_unused; /* compatibility */
	void *shm_unused2; /* ditto - used by DIPC */
    void *shm_unused3; /* unused */
};
struct ipc_perm{
	__kernel_key_t key;
	__kernel_uid_t uid;
	__kernel_gid_t gid;
	__kernel_uid_t cuid;
	__kernel_gid_t cgid;
	__kernel_mode_t mode;
	unsigned short seq;
};

靠key区分不同共享内存,标识共享内存的唯一性。
mode代表ipc资源权限
形成共享内存的key
#include <sys/types.h>
#include <sys/ipc.h>

key_t ftok(const char* pathname,int id);

参数:
1. filename就是你指定的文件名(该文件必须是存在而且可以访问的)
2. id是子序号,虽然为int,但是只有8个比特被使用(0-255)。

返回值:
struct ipc_perm中的key

4. 共享内存函数

shmget

创建共享内存

#include <sys/ipc.h>
#include <sys/shm.h>

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

参数:
1. 这个key被填到ipc_perm中作为共享内存的唯一标识(在OS上标识)
2. 共享内存的大小(最好以页为单位,4096字节)
3. 有几个权限标志位

返回值:
成功返回这个共享内存的标识符(给用户使用的唯一标识,要与ipc_perm中的key区分开),失败返回-1

shmflg的几个标志

1. IPC_PRIVATE
2. IPC_CREAT (如果该共享内存有了,就打开返回)
3. IPC_EXCL  (如果该共享内存有了,就出错返回)

如果我们非要开4097的字节空间,但实际是开了两页的空间,但你越界照样会报错

IPC资源生命周期不随进程,随内核(如果我们不清除,将会一直占用,除非重启)

ipcs -m查看共享内存

ipcrm -m 标识符x(shmid,在用户层标识的)
删除标识符为x的共享内存

shmctl

用于控制共享内存(可删除共享内存)

#include <sys/ipc.h>
#include <sys/shm.h>

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

参数:
1. 共享内存的标识符
2. 将要采取的动作(我们控制它,只用删除:IPC_RMID)
3. 指向一个保存着共享内存模式状态和访问权限的数据结构(一般不用,设为NULL
shmat

将共享内存段连接到虚拟地址空间(挂接)

#include <sys/types.h>
#include <sys/shm.h>

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

参数:
1. 共享内存标识符
2. 挂接时,要不要指定虚拟地址,设为空即可(标识OS自己去选)
3. 默认写为0即可

返回值:
成功返回一个指针,返回该关联虚拟地址空间的其实地址
失败返回-1
shmdt

将共享内存段与当前进程脱离(取消挂接)

#include <sys/types.h>
#include <sys/shm.h>

int shmdt(const void *shmaddr);

参数:
与shmat第二个参数对应

返回值:
成功返回0
失败返回-1
要让两个进程挂接上一个共享内存

需要都调用 ftok,找到相同的key,再与其挂接

5.总

我们建立共享内存,让两个进程挂接时,先让一个进程创建一个共享内存,再将自己进程与共享内存关联;另一个进程也找到该共享内存与其关联,不必创建。

删除时,建立共享内存的进程,取消挂接,删除共享内存。另一个进程也为取消挂接

共享内存底层不提供任何同步与互斥机制,它们各自就像普通程序一样运行(不会堵塞,它们两个完全不知道对方存在)

在底层是通过key找到同一个共享内存,在用户通过标识符(shmid),这些函数找共享内存用标识符

五. 消息队列

消息队列提供一个从一个进程向另外一个进程发送一块数据的方法

每一个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值

1. 创建消息队列

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msggt(key_t key,int msgflg);

参数:
1. 消息队列的唯一标识
2. 与共享内存一致

返回值:
成功返回消息队列的标识符,出错返回-1

其他函数基本与共享内存一致
消息队列的数据结构中也有 ipc_perm

六. 信号量

信号量主要用于同步与互斥的

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

1. 进程互斥

  • 由于各进程要求共享资源,而且有些资源需互斥使用,因此各进程间竞争使用这些资源,进程的这种关系为进程的互斥
  • 系统中某些资源一次只允许一个进程使用,称这样的资源为临界资源或互斥资源
  • 在进程中涉及到互斥资源的程序段叫临界区
  • 特性方面:IPC资源必须删除,否则不会自动清除,除非重启,所以 System V IPC 资源的生命周期随内核

  • 套接字也是进程间通信
  • 管道的本质是内核的一块缓冲区
  • 共享内存只有在当前映射连接数为0,才会被真正删除
  • ipcs查看进程间通信资源/ipcrm删除进程间通信资源
    -m针对共享内存
    -q针对消息队列
    -a针对所有资源
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值