进程间通讯

管道

有名管道
进程间传递数据
    在磁盘上有一个管道文件标识,创建管道文件,但是,交互的数据并不会存储到磁盘中,管道文件不占磁盘 block 空间,只占据了磁盘上的一个 inode,数据缓存在内存上。
    管道文件标识符通过操作系统操作,应用层,用户层对运行的要求是:要在同一个操作系统,有权限访问的任意 n 个进程间的通讯。

    管道文件表示不需要存储在磁盘的原因:

  1. 进程间通讯需要效率,如果放在磁盘上,不论是读数据还是写数据都要通过 I/O,I/O 的效率太慢;如果放在内存上,进程本身在内存上,可以直接访问内存,效率就会很快
  2. 进程间通讯的数据不需要保存,磁盘是永久保存。
    在这里插入图片描述
        A 进程 open 打开管道文件,管道文件上只有 inode 节点,有管道文件映射到内存上的空间,B 进程 open 打开管道文件,管道文件映射到内存上的空间。A 进程将 hello 写到内存上,B 进程通过映射读取内存上的内容。

操作
    创建管道文件:
          命令:mkfifo filename
          函数:int mkfifo (char *filename, int mode)
    打开:open 会阻塞运行 至少有一个读进程,一个写进程
    写:write 阻塞运行 管道空间满
    读:read write 会阻塞 有数据可读或者所有写端关闭
    关闭:close

写端

#include <stdio.h>
#include <assert.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
int main()
{
	int fd=open("FIFO",O_WRONLY);
	assert(fd!=-1);

	printf("write fd=%d\n",fd);

	while(1)
	{
		char buff[128]={0};
		fgets(buff,128,stdin);
		if(strncmp(buff,"end",3)==0)
		{
			break;
		}
		write(fd,buff,128);
	}
	close(fd);
	exit(0);
}

读端

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <fcntl.h>
#include <unistd.h>

int main()
{
	int fd=open("FIFO",O_RDONLY);
	assert(fd!=-1);
	printf("read fd=%d\n",fd);

	while(1)
	{
		char buff[128]={0};
		int n=read(fd,buff,127);
		if(0==n)
		{
			break;
		}
		printf("read(n=%d):%s",n,buff);
	}
	close(fd);
	exit(0);
}

在这里插入图片描述

无名管道
    借助于父子进程共享文件描述符,通过无名管道完成通讯,不会创建管道文件,只能应用于父子进程之间共享 fork 之前打开的文件描述符。无名管道的数据内容在内 vfrv 存中缓存。
无论是有名管道还是无名管道,在同一时刻,只能一端读,一端写

     规定:父子进程执行之前,必须分别关闭一对读写
操作
     创建并打开无名管道:
     int pipe (int fds[2]); fds[0]读文件描述符 fds[1]写文件描述符
在这里插入图片描述
    A 进程创建并打开无名管道文件,在内存上开辟一个内存空间。A 进程中的两个文件描述符分别以读和写指向内存。A进程调用 fork 生成 B 进程。B 进程 是 A 进程的子进程,子进程继承父进程打开的文件描述符,A 进程通过 fd[1] 将 hello 写到内存上,B 进程通过 fd[0] 读取内存上的内容。

#include <stdio.h>
#include <assert.h>
#include <fcntl.h>
#include <unistd.h>

int main(int Argc,char *Argv[])
{
	int fds[2];
	pipe(fds);//创建并打开无名管道
	pid_t n=fork();
	assert(n!=-1);

	if(n==0)
	{
		//关闭子进程中的写文件描述符
		close(fds[1]);
		char buff[128]={0};
		read(fds[0],buff,127);
		printf("child\n");
		printf("buff=%s\n",buff);
	}
	else
	{
		close(fds[0]);
		char buff[128]={0};
		fgets(buff,128,stdin);
		write(fds[1],buff,127);
		printf("father\n");
		close(fds[1]);
	}
}

在这里插入图片描述
    当读到一个写端已被关闭的管道时,在所有的数据都被读取后,read 返回 0,以示到达了文件结束处。
    如果写一个读端已被关闭的管道,则产生信号 SIGPIPE。如果忽略该信号或者捕捉该信号并从其处理程序返回,则 write 返回 -1,error 设置为 EPIPE。
    如果管道有一个写端两个读端:两个读端谁抢到算谁的。
在这里插入图片描述
    管道进行写一次,读一次管道是否还在

信号量

    信号量不负责发送数据,只是进程同步控制。类似于一个计数器,当其值大于 0 时,记录临界资源的个数。等于 0 时,没有临界资源可用,申请 P 操作,则进程会被阻塞。其他需要临界资源的进程就必须阻塞,控制进程同步运行的一种机制
     进程同步:进程协同工作,进程必须按照规定的顺序一步步执行
     进程异步:进程独立运行,互不干扰,当一个进程异步调用发出时,调用者不能立刻得到结果,进程不会阻塞。需要内核机制来做通知
     临界资源:同一时刻只能被一个进程访问的资源,临界资源必须有多份
     临界区:访问临界资源的代码区域
     原子操作:不能被打断的操作,一旦开始操作,必须等待他结束
     P,V操作:P-1 申请资源 V+1 释放资源
     当进程访问临界资源前,对信号量进行 P 操作,访问临界资源后,对信号量进行 V 操作,P 操作 V 操作必须是原子操作
     信号量的内核对象–》是多个进程能访问到同一个信号量
     内核维护的内核对象记录的是一个信号量集。

操作
1、创建或者获取信号量:
     int semget ((key_t) key, int nsems, int flag);
        nsems: 信号量集中信号量的个数
2、如果是新创建的信号量,必须做初始化设置:
     int semctl (int semid, int semnum, int cmd, union semun arg)
          semnum: 操作信号量集中的那一个信号量
          cmd = SETVAL
          arg.val = 信号量的初始值
3、如果是获取的,则可以直接使用:

id=semget();//仅仅是获取信号量
if(id==-1)
{
	id=semget();//创建
	//初始化
}
id

4、P 操作:
      V 操作:
      int semop (semid, struct sembuf[ ], size_t size)

struct sembuf
{
	short sem_num;指定本变量操作的信号量集中信号量的下标
	short sem_op;  -1 P操作  1 V操作:
	short sem_flg; SEM_UNDO
};

5、释放内核对象:
        int semctl (int semid, int semnum, int cmd); //cmd = IPC_RMID
        //立即删除
         获取:int SemGet (int key, int val[ ], int nsems);
         P 操作:int SemWait (int semid, int sems[ ], int len);
         V 操作:int SemPost (int semid, int sems[ ], int len);
         释放:int SemDel (int semid);
单方面 A 进程给 B 进程提供服务
    进程对普通文件的 read 是非阻塞的,A 进程将数据写入到文件中,B 进程从文件中读取写入的数据,并将其打印
    A 进程不需要阻塞,信号初始值为 0,B 进程 read 之前必须阻塞,等待 A 进程将数据写入文件

sem.h

#pragma once

typedef union semun
{
	int val;
}SemUn;

int SemGet(int key,  int initval[], int nsems);
//key 获取的信号量
//nsems 信号量的个数
//initval 信号量的初始值

int SemP(int semid,  int sems[], int len);
//信号量
//信号量下标

int SemV(int semid,  int  sems[],  int len);

int SemDel(int semid);

sem.c

#include "sem.h"
#include <stdio.h>
#include <malloc.h>
#include <assert.h>
#include <sys/sem.h>


int SemGet(int key,  int initval[], int nsems)
{
	//获取信号量
	int semid = semget((key_t)key, 0, 0664);

	//创建并初始化信号量
	if(-1 == semid)
	{
		semid = semget((key_t)key, nsems, 0664 | IPC_CREAT);
		if(-1 == semid)
		{
			perror("create sem fail ");
			return -1;
		}

		int i = 0;
		for(; i < nsems; ++i)
		{
			SemUn  sem;
			sem.val = initval[i];
			semctl(semid, i, SETVAL, sem);
		}
	}

	return semid;
}


int SemP(int semid,  int sems[], int len)
{
	struct sembuf *buf = (struct sembuf*)malloc(sizeof(struct sembuf) * len);
	assert(buf != NULL);

	int i = 0;
	for(; i < len; ++i)
	{
		buf[i].sem_num = sems[i];
		buf[i].sem_op = -1;
		buf[i].sem_flg = SEM_UNDO;
	}

	if(-1 == semop(semid, buf, len))
	{
		perror("P fail ");
		return -1;
	}

	free(buf);

	return 0;
}

int SemV(int semid,  int  sems[],  int len)
{
	struct sembuf *buf = (struct sembuf*)malloc(sizeof(struct sembuf) * len);
	assert(buf != NULL);

	int i = 0;
	for(; i < len; ++i)
	{
		buf[i].sem_num = sems[i];
		buf[i].sem_op = 1;
		buf[i].sem_flg = SEM_UNDO;
	}

	if(-1 == semop(semid, buf, len))
	{
		perror("V fail ");
		return -1;
	}

	free(buf);

	return 0;
}


int SemDel(int semid)
{
	if(-1 == semctl(semid, 0, IPC_RMID))
	{
		perror("Delete sem fail ");
		return -1;
	}

	return 0;
}

A进程
A 进程获取用户输入当输入完成后,B 进程开始运行,接收用户输入 P

#include "sem.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <fcntl.h>
#include <string.h>

int main()
{
	int sem = 0;
	int semid = SemGet(1234, &sem, 1);
	assert(semid != -1);

	int fd = open("file.txt", O_WRONLY | O_CREAT, 0664);
	assert(fd != -1);

	while(1)
	{
		printf("please input: ");
		char buff[128] = {0};
		fgets(buff, 128, stdin);

		if(strncmp(buff, "end", 3) == 0)
		{
			break;
		}

		write(fd, buff, strlen(buff) - 1);
	}

	close(fd);
	int index = 0;
	SemV(semid, &index, 1);
}

B进程

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <fcntl.h>
#include "sem.h"

int main()
{
	int sem = 0;
	int semid = SemGet(1234, &sem, 1);
	assert(semid != -1);

	int index = 0;
	SemP(semid, &index, 1);

	int fd = open("file.txt", O_RDONLY);
	assert(fd != -1);

	while(1)
	{
		char buff[128] = {0};
		int n = read(fd, buff, 127);
		if(n <= 0)
		{
			break;
		}

		printf("%s", buff);
	}

	printf("\n");
	close(fd);
}

FCPrintStr.c

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>

#include "sem.h"

int main()
{
	int sem = 1;
	int semid = SemGet(1234, &sem, 1);
	assert(semid != -1);

	pid_t pid = fork();
	assert(-1 != pid);

	if(0 == pid)
	{
		int index = 0;
		int i = 0;
		for(; i < 5; ++i)
		{
			SemP(semid, &index, 1);
			printf("i am child\n");
			sleep(1);
			SemV(semid, &index, 1);
		}

	}
	else
	{
		int index = 0;
		int i = 0;
		for(; i < 5; ++i)
		{
			SemP(semid, &index, 1);
			printf("i am father\n");
			sleep(1);
			SemV(semid, &index, 1);
		}

	}
}

消息队列

消息:类型(标识符)+ v 数据 一条消息 发送的消息都是独立的
队列:一种先进先出的数据结构
消息队列:

  1. 在同一类型上符合先进先出的规定。
  2. 对于整个消息队列,是类优先级队列
    在这里插入图片描述

       内核对象:进程间通讯需要内核对象来共享东西。内核对象是一个结构体变量,里面存储的是一些属性,并且指向了一个内存空间,该内存空间在内核中,有内核维护,把内存画在用户空间是因为内核空间不够,就把内存映射到用户空间。
       标识符:内核对 IPC 结构(内核对象)的标识 id
       键:key,是用户层次对于内核中的对象的标识
       两个进程需要访问同一个内核对象,必须给一个相同的 key 值来找到 IPC 结构。

通过键值来找到对应的内核对象的标识符(ID)
      进程 A 和进程 B 通过消息队列通讯。两个进程通过内核空间的内核对象同时访问。如果 A 进程发送信息时,先访问内核对象,如果内核对象不存在,就创建内核对象,该内核对象指向内存空间,A 进程的信息直接存入内存空间中。B 进程直接获取内核对象,通过内核对象访问内存空间,读取 A 发送的数据。
数据的定向发送与接受
     (1)接收消息的进程只接受指定类型的消息其他消息一概不管。发送端就可以指定数据发送给哪个进程。
     (2)消息队列不是准确的先进先出,在特定类型上是先进先出。

操作
ipcs -q 查看消息队列
ipcrm -q id 删除消息队列
1、创建或者获取内核对象 msgget
     int msgget ((key_t)key, int flag)
          key:用户标识符
          flag:权限 IPC_CREAT
返回值:失败返回 -1,成功返回内核对象的内核 ID

2、通过内核对象完成通讯 A 进程发送数据 msgsnd B进程接受数据 msgrcv
     (1) A进程发送数据 msgsnd
          int msgsnd (int msgid, void *ptr, size_t nbytes, int flag);
             ptr 指向的结构:

	struct msgdata
	{
		long mtype;       类型
		char mtext[128];  数据
	}

            btytes:数据部分的实际长度
            flag:一个标记

(2) B进程接收数据 msgrcv
     int msgrcv (int msgid, void *ptr, int nbytes, long type, int flag);
         ptr 指向 msgdata 结构,用于保存接收到的消息
         nbytes:指定接收数据的缓冲区的大小
         type:本次接收数据的类型。

3、释放内核对象 msgctl
     int msgctl (int msgid, int cmd, struct msgid_ds *buf)
        cmd: IPC_STAT IPC_SET IPC_RMID

发送方

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <string.h>

#include <sys/msg.h>

typedef struct msgdata
{
	long mtype;
	char mtext[128];
}MsgData;

int main()
{
	int msgid = msgget((key_t)1234, 0664 | IPC_CREAT);
	assert(msgid != -1);

	MsgData  data;
	memset(&data, 0, sizeof(data));
	data.mtype = 100;
	strcpy(data.mtext, "hello");

	msgsnd(msgid,  &data, strlen(data.mtext), 0);

	memset(&data, 0, sizeof(data));
	data.mtype = 200;
	strcpy(data.mtext, "world");
	msgsnd(msgid, &data, strlen(data.mtext), 0);

	//不能发送完后直接删除消息队列
	//msgctl();   删除立即生效
}

接收方

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <string.h>

#include <sys/msg.h>

typedef struct msgdata
{
	long mtype;
	char mtext[128];
}MsgData;

int main()
{
	int msgid = msgget((key_t)1234, 0664 | IPC_CREAT);
	assert(msgid != -1);

	MsgData  data;
	memset(&data, 0, sizeof(data));

	msgrcv(msgid, &data, 127, 100, 0);
	printf("data.type: %d\n", data.mtype);
	printf("data.text: %s\n", data.mtext);
}

共享内存

     是最快的IPC(进程间通讯),系统上的进程,每个进程都有 4G 虚拟地址空间,而进程的真实的物理空间都是独立的。在物理内存上找一块空间,使得多个进程都能访问这块空间。

  1. 创建内核对象,并且申请物理内存
  2. 各进程中,分别将自己的虚拟地址通过内核对象映射到开辟物理内存上。链接
  3. 分别访问这块内存 通过 ptra ptrb
  4. 删除内核对象
    在这里插入图片描述

     A 进程和 B 进程分别保存两个虚拟地址,这两个地址可以相同,也可以不相同但是其映射的物理内存是相同的

消息队列和共享内存的区别
    消息队列:创建和后期发送接收数据时访问通过内核对象访问内存空间
    共享内存创建的时候创建内核对象,内核对象直接指向内存上的一块区域(物理内存),后期通过一系列的操作将 ptra 返回虚拟地址,A 的虚拟地址通过二级页面映射直接映射到共享内存,B 同理。

     共享内存是一种最快的 IPC

  1. 共享内存一旦使进程映射到此共享内存区域,后续操作时,不需要内核态切换用户态
  2. 共享内存在使用时,会比其他的通讯方式少两次数据的拷贝。管道、消息队列等发送数据时先在自己的进程空间里定义一个 buff,把数据写在 buff 里面,然后通过 write 等函数将数据写到内存上,因此把用户态切换内核态

     共享内存的缺点
     消息队列操作时必须通过操作系统的控制,共享内存时指针直接指向内存,意味着没有通过操作系统控制
     指针 A 和指针 B 直接指向内存,B 读取数据时不知道 A 是否把数据写入内存。因此,使用共享内存进行进程间通讯必须要使用信号量进行进程同步,使 B 进程的执行在 A 进程之后。

     共享内存对于多进程就是一块临界资源。多进程使用共享内存空间时就必须做到同步控制—信号量
管道代码:

	char buff[128];
	fgets();
	write(fd,buff,strlen(buff));

共享内存:
     ptra 指向物理内存空间
     fgets (ptra, 128, stdin);

操作
创建或获取
     int shmget ((key_t)key, int size, int flag)
         size: 申请的共享内存空间大小

将进程中虚拟地址映射到共享的内存区域
     void *shmat (int shmid, const void *attr, int flag);
         返回链接的虚拟地址 失败返回 -1

断开链接
     int shmdt (void *addr);

删除内核对象
     int shmctl (int shmid, int cmd, struct shmid_ds *buf);
         不会立即删除,但是其他进程就不能通过 shmat 与该段链接

A

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>

#include <sys/shm.h>
#include "sem.h"

int main()
{
	int shmid = shmget((key_t) 1234, 128, IPC_CREAT | 0664);
	assert(shmid != -1);

	int semVal = 1;
	int semid = SemGet(1000, &semVal, 1);

	char *ptr = (char*)shmat(shmid, NULL, 0);
	assert(ptr != (char*)-1);

	while(1)
	{
		int index = 0;
		SemP(semid, &index, 1);
		printf("please input: ");
		fgets(ptr, 128, stdin);

		SemV(semid, &index, 1);

		if(strncmp(ptr, "end", 3) == 0)
		{
			break;
		}
	}

	shmdt(ptr);
}

B

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>

#include <sys/shm.h>

#include "sem.h"

int main()
{
	int shmid = shmget((key_t) 1234, 128, IPC_CREAT | 0664);
	assert(-1 != shmid);

	int semVal = 1;
	int semid = SemGet(1000, &semVal, 1);

	char *ptr = (char *)shmat(shmid, NULL, 0);
	assert((char*)-1 != ptr);

	while(1)
	{
		int index = 0;
		SemP(semid, &index, 1);

		if(strncmp(ptr, "end", 3) == 0)
		{
			break;
		}

		printf("lenth = %d :  %s", strlen(ptr) - 1, ptr);
		sleep(2);
		SemV(semid, &index, 1);
	}

	shmdt(ptr);
}

内容效率共享实质进程数量阻塞机制
有名管道数据磁盘上文件标识两个read
无名管道数据父子进程共享文件描述符父子两个read
消息队列消息内核对象nmsgrcv
信号量同步-内核对象nP操作
共享内存数据内核对象nP操作

有名管道
    通过管道文件,完成多进程的通讯,一般用于两个进程间的通讯

无名管道
    借助于父子进程共享 fork 之前打开的文件描述符,只能应用于父子进程之间

消息队列
    发送带有类型的数据,可以真正实现多进程间通讯

共享内存
    使得两进程间共享同一块物理内存区域,实现数据传递,但是必须使用信号量控制进程同步
    共享内存是最快的一种IPC

信号量
    完成进程同步控制,用于多进程访问同临界资源时

信号
    完成进程间消息通知的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值