进程通信

一、管道通信

1、无名管道

1、什么是管道:
管道是单向的、先进先出的,它把一个进程的输出和另一个进程的输入连接在一起。一个进程(写进程)在管道尾部写入数据,另一个进程(读进程)从管道的头部读出数据。
2、管道的特点:
1、单向性,一端写,一段读
2、管道中数据是一次性的,读完就没有了
3、管道中没有数据的时候,读取时会阻塞,直到管道中有数据
3、管道的用途:一般用于父子进程之间的通信,因此又称无名管道
4、写管道的步骤
(1、创建管道
(2、创建子进程(先创建管道的话,子进程会复制管道,此时读写用的是同一个管道)
(3、对父进程和子进程分别进行读或者写操作
(4、进行读或者写之前,先要关闭不使用的管道口
5、pipe函数创建管道
int pipe(int filedis[2])
其中filedis[0]对应读管道
filedis10]对应写管道

在这里插入图片描述

6、父子进程之间的管道通信

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

#define SIZE 1024

//子进程进行写
void child_do(int pipefd[])		
{
	close(pipefd[0]);		//关闭读
	
	char buf[SIZE] ={0};
	while(1)
	{
		printf("子进程写入数据\n");
		fgets(buf,SIZE,stdin);
		ssize_t ret = write(pipefd[1],buf,SIZE);
		if(-1 == ret)
		{
			perror("读失败");
			break;
		}
	}

	close(pipefd[1]);		//关闭写
}

//父进程进行读
void parent_do(int pipefd[])
{
	close(pipefd[1]);		//关闭写
	
	char buf[SIZE] = {0};
	while(1)
	{
		ssize_t ret =read(pipefd[0],buf,SIZE);
		if(-1 == ret)
		{
			perror("读失败");
			break;
		}
		//buf[ret] = '\n';
		printf("父进程读到的数据:%s",buf);	
	}
	
	close(pipefd[0]);		//关闭读
}

int main()
{
	//创建管道
	int pipefd[2];
	int ret = pipe(pipefd);
	if(-1 == ret)
	{
		perror("管道创建失败");
		return -1;
	}
	
	//创建子进程
	pid_t pid = fork();
	
	switch(pid)
	{
		case 0:			//子进程
			child_do(pipefd);
			break;
		case -1:		//进程创建失败
			perror("进程创建失败");
			break;
		default:		//父进程
			parent_do(pipefd);
			break;
		
	}
	
	return 0;
}

2、命名管道

1、什么是命名管道,与无名管道的区别
命名管道(FIFO)和无名管道基本相同,但也有不同点:无名管道只能由父子进程使用;但是通过命名管道,不相关的进程也能交换数据。

2、命名管道的创建
int mkfifo(const char *pathname, mode_t mode);
第一个参数是:创建管道文件的名字(此函数只能在linux系统下创建,window下无法创建,因此不能在共享文件中创建)
第二个参数:创建文件的权限
注意:读、写端无法独立存在,一旦有一端关闭,另外一端就无法使用。

3、因为命名管道创建的是一个文件,需要用到open,read,write这些函数(默认是阻塞型的)

4、命名管道非阻塞的使用
在使用open打开的时候,添加O_NONBLOCK
注意点:当使用非阻塞的时候,一旦文件是空,会返回一个特殊的错误
它本质不是错误,是提示文件空。可以用errno == EAGAIN来判断是空。

5、创建管道文件

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>

int main()
{
	int fd = mkfifo("myfifo",0766);
	if(-1 ==fd)
	{
		perror("管道文件创建失败");
		return -1;
	}
	
	return 0;
}

6、写数据

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

#define SIZE 1024

int main()
{
	int fd = open("/home/myfifo",O_WRONLY);
	if(-1 == fd)
	{
		perror("打开文件失败");
		return -1;
	}
	
	char buf[SIZE] = {0};
	while(1)
	{	
		printf("子进程写入数据\n");
		fgets(buf,SIZE,stdin);
		ssize_t ret = write(fd,buf,strlen(buf));
		if(-1 == ret)
		{
			perror("写文件失败");
			return -1;
		}
	}
	close(fd);
	
	return 0;
}

7、读数据

#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

#define SIZE 1024

int main()
{
	int fd = open("/home/myfifo",O_RDONLY);
	if(-1 == fd)
	{
		perror("打开文件失败");
		return -1;
	}
	
	char buf[SIZE+1] = {0};
	while(1)
	{
		ssize_t ret = read(fd,buf,SIZE);
		if(-1 == ret)
		{
			perror("读文件失败");
			return -1;
		}
		buf[ret] = '\n';
		printf("父进程读到的数据:%s",buf);
	}
	close(fd);
	
	return 0;
}

8、非阻塞管道文件读对应的文件

#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

#define SIZE 1024

int main()
{
	int fd = open("/home/myfifo",O_RDONLY | O_NONBLOCK);
	if(-1 == fd)
	{
		perror("打开文件失败");
		return -1;
	}
	
	char buf[SIZE+1] = {0};
	while(1)
	{
		ssize_t ret = read(fd,buf,SIZE);
		if(-1 == ret)
		{	
			if(errno == EAGAIN)
			{
				printf("文件为空\n");
				sleep(2);
				continue;
			}
			perror("读文件失败");
			return -1;
		}
		buf[ret] = '\n';
		printf("父进程读到的数据:%s",buf);
	}
	close(fd);
	
	return 0;
}

二、共享内存

1、什么是共享内存
共享内存是被多个进程共享的一部分物理内存。共享内存是进程间共享数据的一种最快的方法,一个进程向共享内存区域写入了数据,共享这个内存区域的所有进程就可以立刻看到其中的内容。共享内存是有内核空间创建的一块空间。

2、共享内存的特点:
(1、保存在内存中的数据,不删除内存的话是一直存在的
(2、共享内存的大小不是固定的,需要多大就创建多大

3、共享内存的使用步骤
(1、创建共享内存
(2、将内存映射
(3、解除映射(不一定要)
(4、删除共享内存(不一定要)

4、共享内存的创建
int shmget(key_t key, size_t size, int shmflg);
第一个参数:内存空间对应的编号(用于区分不同的共享内存)
第二个参数:想要创建的大小
第三个参数:创建的模式,一般使用IPC_CREAT
返回值:对应内存的标识

5、共享内存的映射
void *shmat(int shmid, const void *shmaddr, int shmflg);
第一个参数:想要映射的内存的标识
第二个参数:想要开辟空间的位置(由于不清楚,一般写NULL)
第三个参数:对应的标志位:一般写0;
返回值:想要内存对应的地址

6、解除映射
int shmdt(const void *shmaddr);
想要解除映射内存的地址

7、删除内存
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
第一个参数:想要删除的内存的标识
第二个参数:command是要采取的操作,删除用IPC_RMID
第三个参数:buf是一个结构指针,它指向共享内存模式和访问权限的结构。,一般写NULL

三、信息量

1、什么是信息量
信号量就可以提供这样的一种访问机制,让一个临界区同一时间只有一个进程在访问它,也就是说信号量是用来调协进程对共享资源的访问的。信息量是一种进程同步的操作,主要用于控制临界资源。
2、临界区的概念
临界区域是指执行数据更新的代码需要独占式地执行。

3、信息量的核心操作
p操作:对信息量-1操作
v操作:对信息量+1操作

4、信息量的工作原理
由于信号量只能进行两种操作等待和发送信号,即P(sv)和V(sv),他们的行为是这样的:
P(sv):如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行
V(sv):如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1.

5、信息量的创建
int semget(key_t key, int num_sems, int sem_flags);
第一个参数:key是整数值(唯一非零),不相关的进程可以通过它访问一个信号量,它代表程序可能要使用的某个资源,程序对所有信号量的访问都是间接的,程序先通过调用semget函数并提供一个键,再由系统生成一个相应的信号标识符(semget函数的返回值),只有semget函数才直接使用信号量键,所有其他的信号量函数使用由semget函数返回的信号量标识符。如果多个程序使用相同的key值,key将负责协调工作。

第二个参数:指定需要的信号量数目,它的值几乎总是1。

第三个参数:是一组标志,当想要当信号量不存在时创建一个新的信号量,可以和值IPC_CREAT做按位或操作。设置了IPC_CREAT标志后,即使给出的键是一个已有信号量的键,也不会产生错误。而IPC_CREAT | IPC_EXCL则可以创建一个新的,唯一的信号量,如果信号量已存在,返回一个错误。

semget函数成功返回一个相应信号标识符(非零),失败返回-1.

6、该函数用来直接控制信号量信息:
int semctl(int sem_id, int sem_num, int command, …);

如果有第四个参数,它通常是一个union semum结构,定义如下:
union semun{
int val;
struct semid_ds *buf;
unsigned short *arry;
};

第一个参数是信号量集IPC标识符。
第二个参数是操作信号在信号集中的编号,第一个信号的编号是0
第三个参数 command通常是下面两个值中的其中一个
SETVAL:用来把信号量初始化为一个已知的值。p 这个值通过union semun中的val成员设置,其作用是在信号量第一次使用前对它进行设置。
IPC_RMID:用于删除一个已经无需继续使用的信号量标识符。

7、它的作用是改变信号量的值:
int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);
第一个参数 sem_id是由semget返回的信号量标识符,
第二个参数 sembuf结构的定义如下:
struct sembuf{
short sem_num;//除非使用一组信号量,否则它为0
short sem_op;//信号量在一次操作中需要改变的数据,通常是两个数,一个是-1,即P(等待)操作,
//一个是+1,即V(发送信号)操作。
short sem_flg;//通常为SEM_UNDO,使操作系统跟踪信号,
//并在进程没有释放该信号量而终止时,操作系统释放信号量
};
第三个参数 nsops:信号操作结构的数量,恒大于或等于1。

8、买票系统

1、main函数

#include <stdio.h>
#include <sys/shm.h>
#include "ticket.h"
#include "semaphore.h"
#include <time.h>

void sell_ticket(int semid,MSG *pa)
{
	srand((unsigned int)time(NULL));
	while(1)
	{
		usleep(100000*(rand()%10+1));
		//p操作
		semaphore_p(semid);
		//买票了
		if(0 == pa->ticket)
		{
			printf("票买完了,请明天再来\n");
			semaphore_v(semid);				//退出前注意要解锁
			break;
		}
		printf("出票成功,座位号为%d\n",101-(pa->ticket));
		pa->ticket -= 1;
		
		//V操作
		semaphore_v(semid);
	}
}

int main(int argc,char **argv)
{
	//创建共享内存
	int shmid = shmget((key_t)123,sizeof(MSG),IPC_CREAT);
	if(-1 == shmid)
	{
		perror("共享内存创建失败");
		return -1;
	}
	//映射
	MSG *pa = shmat(shmid,NULL,0);
	if(NULL == pa)
	{
		perror("映射失败");
		return -1;
	}
	//创建信号量
	int semid = semget((key_t)456,1,IPC_CREAT);
	
	//初始化
	if(2 == argc)
	{
		pa->ticket = 100;
		init_smph(semid);
	}
	
	//买票
	sell_ticket(semid,pa);
	
	sleep(2);
	//删除信号量和映射
	if(2 == argc )
	{
		del_smph(semid);
		shmdt(pa);
	}
	
	return 0;
}

2、.h文件

#ifndef __TICKET__H__
#define __TICKET__H__

typedef struct msg
{
	int ticket;
}MSG;


#endif // __TICKET__H__
#ifndef __SEMAPHORE_H__
#define __SEMAPHORE_H__
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <string.h>

union semun {
   int              val;    /* Value for SETVAL */
   struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
   unsigned short  *array;  /* Array for GETALL, SETALL */
   struct seminfo  *__buf;  /* Buffer for IPC_INFO(Linux specific) */
};

// 信号量初始化
int init_smph(int semid)
{
	union semun sem;
	sem.val = 1;
	
	// 给信号量 semid进行初始化
	int ret = semctl(semid, 0, SETVAL, sem);
	if(ret == -1)
	{
		perror ("semctl");
		return -1;
	}
	
	return 0;
}

// 信号量的P操作
int semaphore_p(int semid)
{
	struct sembuf sem;
	memset (&sem, 0, sizeof(sem));
	
	sem.sem_num = 0;
	sem.sem_op  = -1;
	sem.sem_flg = SEM_UNDO;
	int ret = semop(semid, &sem, 1);
	if (ret == -1)
	{
		perror ("semop_p");
		return -1;
	}
	return 0;
}

// 信号量的v操作
int semaphore_v(int semid)
{
	struct sembuf sem;
	memset (&sem, 0, sizeof(sem));
	
	sem.sem_num = 0;
	sem.sem_op  = 1;
	sem.sem_flg = SEM_UNDO;
	int ret = semop(semid, &sem, 1);
	if (ret == -1)
	{
		perror ("semop_v");
		return -1;
	}
	return 0;
}

// 信号量的删除
int del_smph(int semid)
{	
	int ret = semctl(semid, 0, IPC_RMID);
	if(ret == -1)
	{
		perror ("semctl");
		return -1;
	}
	
	return 0;
}

#endif // __SEMAPHORE_H__

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值