进程间的通信---管道、system v

进程间通信(IPC通信)

IPC通信就是进程之间的沟通交流。进程具有独立性,但实际工作中往往会出现在一个系统中好几个
进程协同工作,那么就需要这些进程进行通信。

通信目的:

  • 数据传输(一个进程将它的数据发送给另外一个进程)
  • 事件通知(一个进程向另一个进程发送消息,通知它发生了某一件事情)
  • 资源共享(多个进程共享痛的资源)
  • 进程控制(有些进程希望控制另外一个进程截住另一个进程的异常等)

进程间通信的三种方式:

1.管道—最古老的通信方式

	匿名管道
	命名管道

2.System V标准接口

	消息队列
	共享内存
	信号量

3.Posix标准接口

	消息队列
	共享内存
	信号量
	互斥量
	条件变量
	读写锁

=======================================

接下来介绍前两种通信方式,管道和Ssystem v

管道(本质上是内核的一块缓冲区):传输数据资源

Linux下一切皆文件,Linux为管道提供的操作方法就是文件IO操作,即管道的使用和文件一致。
调用pipe函数的进程,系统给进程分配了两个文件描述符,即pipe函数返回的两个描述符

特性:半双工(),单向通信,所以在通信之前设置数据的流向。	(单工:数据只能在一个方向上进行,只能读或只能写;半双工:允许在数据在两个方向进行但某一时刻,只允许在一个方向进行;全双工:允许数据在两个方向上同时进程,数据可边读边写)

这里写图片描述

这里写图片描述

**匿名管道/命名管道 **

1.匿名管道

创建:

int pipe(int   fd[2])       //仅用于具有亲缘关系进程之间通信,
功能:没有名字的文件,在创建管道时,返回两个文件描述符,创建子进程时,父子进程各关闭一个fd,实现管道单向流通,如上图所示
参数:整型数组
			
			fd[0] 用于读取数据
			fd[1] 用于写入数据

			返回值:成功返回0,失败返回-1

对于管道,如果管道中没有数据,则阻塞等待

这里写图片描述

这里写图片描述

匿名管道特性:
1.只能用于具有亲缘关系间进程的通信
2.半双工单向通
3.管道的生命周期随进程,一般进程退出,管道释放,这也是它和普通文件最大的差异
4.管道是面向字节流传输数据的
面向字节流:数据无规则,没有明显边界,收发数据比较灵活。
5.管道自带同步与互斥属性
临界资源:大家都能访问到的公共资源就叫临界资源。
临界区:对临界资源进行操作的代码
同步:访问的可控时序性
互斥:对临界资源同一时间的唯一访问性 保护临界资源的安全。

###  2.命名管道

文件系统可见,也是一个特殊(管道类型)文件
命名管道可以应用于同一主机上的任意进程间通信

创建:
1.命令创建:mkfifo + pipe_filename
2.代码创建:int mkfifo(const char *pathname, mode_t mode);
参数:第一个是创建的管道名称; 第二个是赋予管道的权限
管道文件存在则创建失败返回EEXITST,创建成功返回0

命名管道的打开规则:

  • 如果以只读打开命名管道,那么open函数将会待*,直到有其他进程以写的方式打开这个命名管道
  • 如果命名管道以读写方式打开,则不会阻塞

一个命名管道打开之后则所有特性和匿名管道完全相同。


管道的读写规则:

  • 当管道中没有数据可读时:

     		如果描述符是默认的阻塞属性,那么读取将会阻塞挂起等待,直到有数据。
     		如果描述符被设置为非阻塞属性,那么读取操作将不具备条件,直接报错返回
     		错误码:EAGAIN
    
  • 当管道数据写满时:

     		如果描述符默认的阻塞属性,那么写入操作将阻塞挂起等待,直到数据被读走
     		如果描述符被设置为非阻塞等待,那么写入操作将不具备条件,直接报错返回
     		错误码:EAGAIN
    
  • 如果写端全部被关闭,这时候如果读取数据,读取完管道数据,然后返回0

  • 当读端都被关闭,则进程哪个进程写入就会触发异常,操作系统会给进程发送13号SIGPIEPE信号
    收到这个信号的进程就会退出

  • 当要写入的数据量不大于PIPE_BUF时,Linux将保证写入的原子性

  • 当要写入的数据量大于PIPE_BUF(最小是512B)时,Linux将不能保证写入原子性(所谓原子性就是指某个操作在执行期间,不被其他事件所打断)

#匿名管道和命名管道的区别

  1. 匿名管道由pipe创建并打开,而命名管道由mkfifo创建,用open函数打开
  2. 通信的两个进程结束后,匿名管道会自行消失,释放,但命名管道 的文件路径本身还存在。

system v

消息队列

消息队列实际上是操作系统在内核为我们创建的一个队列,多个进程可以向队列中添加节点获取节点来进行数据传输。
这里写图片描述

如何传输数据:
用户组织一个带有类型的数据块,其他的进程从队列中获取数据块,也就是说消息队列传输的是一个个带有类型的数据块
消息队列是一个全双工通信,可读可写。消息队列的生命周期随内核

消息队列的操作接口:

1.创建消息队列:

int msgget(key_t key, int msgflg);
key:内核中消息队列的标识(由ftok创建的key值)
msgflg:
IPC_CREAT ---------不存在则创建,存在则打开
IPC_EXCL ---------与上一个同用时,存在则报错
mode ---------- 权限
返回值:返回一个非负整数,即消息队列的标识符;失败返回:-1

2.消息队列属性控制

消息队列创建以后,可对消息队列的基本属性 进行修改(控制)

int msgctl(int msqid, int cmd,struct msqid_ds*_buf)

功能:修改消息队列属性
msqid: 消息队列标识符,即msgget的返回值

cmd(要执行的动作,有以下三个可取值):
IPC_STAT------------读取消息队列。将其存放在buf所指的结构体中
IPC_SET------------设置消息队列属性,把消息队列的当前关联值设置为msqid_ds结构中给出的值
IPC_RMID----------删除消息队列
第三个参数临时的一个结构体类型的变量,存储读取消息队列属性

3.发送数据/接收数据:
msgsnd:发送消息到消息队列中
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

	功能:将新的消息队列添加到消息队列为端msqid:消息队列标识符
	maqp:指向要发送的消息队列
	msgsz:接受信息 的大小,数据类型为size_t,即unsigned  int
	msgflg:控制当前消息队列满时或者达到系统上限时将要发生的事情,msgflag为IPC_NOWWAIT时,队列不等待,返回 EAGAIN错误

其他说明 :
消息结构在两方面必须受到 限制:
第一,它的大小必须小于系统规定的上限值
第二,它必须以long int长整数开头,接受函数将利用这个长整数确定消息的类型
消息结构参考如下
struct msgbuf
{
long mtype; // 消息类型
char mtext[1]; //存储消息的位置
}


msgrcv:从消息队列中接受数据
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

	参数:
	msqid : msgget返回的操作句柄
	msgp: 指向准备接受的数据
	msgsz:指定数据的大小,即mtext的大小,以字节为单位
	msgtyp:指定接收数据类型
			msgtyp等于0,接收队列中第一个节点,不分类型
			msgtyp大于0,接收指定类型数据块的第一个节点
			msgtyp小于0,接收小于msgtype绝对值类型的第一个节点		
	msgfalg:操作选项
	设置为IPC_NOWAIT,若消息队列没有消息,返回ENOMSG报错
	设置为MSG_NOERROR,消息大小超过msgsz时被截断。

释放消息队列:

指令:

ipcs :显示IPC资源
	-q 查看消息队列
	-m 查看共享内存
	-s 查看信号量

ipcrm	手动删除ipc

共享内存

共享内存是常用的进程间通信,两个进程可以直接共享访问同一块内存区域
进程间通信最快的方式,少了从用户态向内核态拷贝数据的过程

在这里插入图片描述
这里写图片描述
共享内存是在内存中单独开辟的一段内存空间,两个进程再使用共享内存时,需要在进程地址空间与共享内存之间建立联系,如上图所示。共享内存主要用于进程间大量数据的传输。

共享内存的使用:

1.创建共享内存
	shmget:
		int shmget(key_t key, size_t size, int shmflg);
		key:操作系统ipc标识符			
		size:要创建的共享内存的大小
		shmflg:IPC_CREAT|IPC_EXCL|0664
		返回值:成功返回操作句柄, 失败 返回-1

2.将共享内存映射到虚拟地址空间
	shmat:
		void *shmat(int shmid, const void *shmaddr, int shmflg);
		shmid:操作句柄
		shmaddr:映射起始地址,为NULL时,操作系统分配地址
		shmflg:	SHM_RDONLY--只读  否则为  读写
		返回值:映射的虚拟地址空间的首地址 
3.内存数据操作
4.完成
	1> 解除映射关系
		shmdt:
			int shmdt(const void *shmaddr);
			shmaddr : 首地址
			返回值:成功0 失败-1
	2> 删除共享内存
		如果有进程依然与共享内存保持映射练习,共享内存将不会被立即删除,而是等最后一个映射断开后再删除
		在这期间,将拒绝其他进程映射。
		int shmctl(int shmid, int cmd, struct shmid_ds *buf);
		shmid:句柄
		cmd:IPC_RMID	删除
		buf: 用于接收共享内存描述性息,不关心可以置空

信号量

进程间通信方式之一,主要用于实现进程间的同步与互斥(进程/线程安全概念),防止并发访问共享资源*
信号量本质:具有一个等待队列的计数器(代表现在还有没有资源可以使用)

1.同步:
保证对临界资源访问的时序可控性
2.互斥:
对临界资源同一时间的唯一访问性
多个进程同时操作一个临界资源的时候,就需要通过同步与互斥机制来实现进程之间对
临界资源的安全访问

当信号量没有资源可用(资源计数器为0)时,需要阻塞等待

如何实现同步?
	只有信号量资源计数从0转变为1的时候,会通知别人打断阻塞等待,竞争后再去操作临界资源
	也就是说前者释放资源(计数器+1),之后才能被后者获取资源(计数器-1)进行操作临界资源。

如何实现互斥?
	信号量想要实现互斥,指计数器只能是0或者1(一元信号量)。

进程在操作临界资源之前,先获取信号量资源,判断是否能对临界资源进行操作,如果没有信号量(计数器为0),则需要等待,当其他进程释放信号量计数器会加一,等待的所有进程会被唤醒去获取信号量
注意:信号量作为进程间通讯方式,所以信号量实际上也是一个临界资源


信号量的操作:
  • semget:创建信号量
    int semget(key_t key, int nsems, int semflg);
    参数:
    key:信号量的名字
    nsems:创建的信号的个数,用数组存放
    semflag:权限标识,与mode权限表示是一样的
    返回值:成功返回信号量标识符,非负整数,失败返回-1;
  • semctl:控制信号量集
    int semctl(int semid, int semnum, int cmd, …);
    参数:
    semid:信号量标识符,即semget的返回值
    semnum:信号量集合中信号量的序号,即信号量的下标,若操作整个信号,则此参数无意义
    cmd(要执行的动作,有以下可取值):
    IPC_STAT-----------把semid_ds结构中的的数据设置为信号量集当前的关联值
    IPC_SET------------设置信号量 属性,把消息队列的当前关联值设置为semid_ds结构中给出的值
    IPC_RMID----------删除信号量

SETVAL SETALL 设置信号量的计数器初值

semop:操作信号量集合
       int semop(int semid, struct sembuf *sops, unsigned nsops);
       参数:
       semid:要操作的信号量集合的ID
      第二个参数:
      {
      unsigned short  sem_num;            //信号量下标
      short  sem_op ;                     //信号量操作,pv操作,p操作-1,v操作+1;
      short sem_flg;                    //操作标识符,IPC_NOWAIT(信号量集合操作不能执行时,调用立即返回)/SET_UNDO(进程退出后,撤销进程对信号量集合的操作)
      }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值