进程间通信

进程间的通信

进程间通信的四种方式

管道 、 信号量 、共享内存、消息队列

一、管道:

​ 管道分为有名管道和无名管道,

有名管道:可用于任意两个进程间的通信;

可以通过mkfifo命令创建,

也可以通过系统调用创建

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

int mkfifo(const char*filename,mode_t mode);
无名管道:用父子进程间的通信

无名管道只能通过系统调用创建:

#include<unistd.h>

/*
pipe()成功返回0,失败返回-1;
fds[0]是管道读端的描述符
fds[1]是管道写端的描述符
*/
int pipe(int fds[2]);
管道的特点

无论是有名还是无名管道,写入的数据都在内存上

管道是一种半双工通信方式

有名和无名的区别是,有名可以在任意两个进程间通信,而无名管道只能在父子进程之间进行通信。

二、信号量:

信号量主要是用来同步进程的。信号量是一个特殊的变量一般只取正值,其代表允许访问资源数目。

分类

二值信号量:其取值只有0和1;

计数信号量:信号量的值大于1;

当信号量的值为0时,表示没有资源可以使用,P操作会被阻塞。

操作:

P操作:获取资源时,需要对信号量的值减1.

V操作:释放资源时,需要对信号量的值加1.

**相关知识 **:

临界资源:同一时间只能有一个进程进行访问的资源

临界区:访问临界资源的那段代码

信号量由内核提供的接口帮我们进行创建,传建完后也有内核帮我们维护,与使用它的进程的生存周期无关。信号量的销毁需要我们主动手动进行销毁,不然其会一直存在。

在这里插入图片描述

信号量函数的定义:头文件“#include<sys/sem.h>”

semget:

//semget()创建或者获取已存在的信号量,成功返回信号量id,失败返回-1
int semget(key_t key, int nsems, int semflg);
//key:两个进程使用相同的 key 值,就可以使用同一个信号量 
//nsems:内核维护的是一个信号量集,在新建信号量时,其指定信号量集中信号量的个数
//semflg 可选: IPC_CREAT(创建信号量) IPC_EXCL(判断信号量是否已存在)

semctl:

//semop()对信号量进行改变,做 P 操作或者 V 操作,semop()成功返回 0,失败返回-1
 int semop(int semid, struct sembuf *sops, unsigned nsops);
struct sembuf
 {
  unsigned short sem_num; //指定信号量集中的信号量下标
  short sem_op; //其值为-1,代表 P 操作,其值为 1,代表 V 操作
  short sem_flg; //SEM_UNDO
};

semctl:

//semctl()控制信号量 semctl()成功返回 0,失败返回-1
int semctl(int semid, int semnum, int cmd, ...);
/*
cmd 选项: SETVAL IPC_RMID
union semun
{
 int val;
 struct semid_ds *buf;
 unsigned short *array;
 struct seminfo *_buf;
 };
 */

创建一个信号量实例

static int semid = -1;

void sem_init()//创建/或者已存在的信号量
 {
	 semid = semget((key_t)1234,1,IPC_CREAT|IPC_EXCL|0600);
	 if ( semid == -1 )//已存在
 	{
	 semid = semget((key_t)1234,1,0600);
	}
	 else//初始化信号量
	 {
 		union semun a;
 		a.val = 1;//信号量的初始值
		if ( semctl(semid,0,SETVAL,a) == -1 )
		 {
			 perror("semctl error");
 		 }
	 }
}

void sem_p()//p 减一
{
 struct sembuf buf;
 buf.sem_num = 0;
 buf.sem_op = -1;//p
 buf.sem_flg = SEM_UNDO;
 if ( semop(semid,&buf,1) == -1 )
 	{
		 perror("semop p error");
 	}
}

void sem_v()//v 加一
{
 struct sembuf buf;
 buf.sem_num = 0;
 buf.sem_op = 1;//v
 buf.sem_flg = SEM_UNDO;
 if ( semop(semid,&buf,1) == -1 )
 	{
 		perror("semop v error");
 	}
}

void sem_destroy()//销毁
{
if ( semctl(semid,0,IPC_RMID) == -1 )
{
	 perror("semctl del error");
}
}

经典习题:三个进程a,b,c分别输入A,B,C,要求输出ABCABCABC…

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/sem.h>
union semun{
    int val;
};
//sem.c
static int semid = 0;

void sem_init()
{
    semid=semget((key_t)1234,3,IPC_CRATE|IPC_EXECl|0600);
    //如果创建失败看能否直接获取
    if(semid==-1){
        semid=semget((key_t)1234,3,0600);
    }
    else{
        int arr[]={1,0,0};
         for(int i=0;i<3;++i){
             union semun a;
             a.val=arr[i];
             if(semctl(semid,i,SETVAL,a) == -1){
                 perror("semctl SEtval error\n");
             }
		}
    }    
}


void sem_p(int index)
{
    assert(index>0&&index<3);
    
    struct sembuf buf[3];
    for(int i=0;i<3;++i){
        buf[i].sem_num=index;
        buf[i].sem_op = -1;
        buf[i].sem_flg = SEM_UNDO;
        
        if(semop(semid,&buf[i],1) == -1){
            perror("semop p error\n");
        }
    }
}


void sem_v(int index)
{
    assert(index>0&&index<3);
    
    struct sembuf buf[3];
    for(int i=0;i<3;++i){
        buf[i].sem_num=index;
        buf[i].sem_op = 1;
        buf[i].sem_flg = SEM_UNDO;
        
        if(semop(semid,&buf[i],1) == -1){
            perror("semop v error\n");
        }
    }
}


void sem_des()
{
    if(semctl(semid,0,IPC_RMID) == -1){
        perror("semctl destory error\n");
    }
}


//a.c
#include"sem.h"

int main()
{
    sem_init();
    
    for(int i=0;i<5;++i){
        sem_p(0);
        printf("A");
        fflush(stdout):
        sleep(1);
        sem_v(1);
    }
}

//b.c
#include"sem.h"

int main()
{
    sem_init();
    
    for(int i=0;i<5;++i){
        sem_p(1);
        printf("B");
        fflush(stdout):
        sleep(1);
        sem_v(2);
    }
}

//c.c
#include"sem.h"

int main()
{
    sem_init();
    
    for(int i=0;i<5;++i){
        sem_p(2);
        printf("C");
        fflush(stdout):
        sleep(1);
        sem_v(0);
    }
}

三、共享内存

共享内存就是,多个进程都可以访问的内存空间。

在这里插入图片描述

既然共享内存空间多个进程都可访问,那么我们需要一些方法去控制这些访问内存的进程,是的访问的过程变得有序,不至于出现错误。

共享内存的定义和使用

头文件#include<sys/shm.h>

shmget:

int shmget(key_t key, size_t size, int shmflg);
shmget();用于创建或者获取共享内存
shmget();成功返回共享内存的 ID, 失败返回-1
key: 不同的进程使用相同的 key 值可以获取到同一个共享内存
size: 创建共享内存时,指定要申请的共享内存空间大小
shmflg: IPC_CREAT IPC_EXCL

shmat:

 void* shmat(int shmid, const void *shmaddr, int shmflg);
shmat()将申请的共享内存的物理内存映射到当前进程的虚拟地址空间上
shmat()成功返回返回共享内存的首地址,失败返回 NULL
shmaddr:一般给 NULL,由系统自动选择映射的虚拟地址空间
shmflg: 一般给 0, 可以给 SHM_RDONLY 为只读模式,其他的为读写

shmdt:

int shmdt(const void *shmaddr);
 shmdt()断开当前进程的 shmaddr 指向的共享内存映射
 shmdt()成功返回 0, 失败返回-1

shmctl:

int shmctl(int shmid, int cmd, struct shmid_ds *buf);
 shmctl()控制共享内存
 shmctl()成功返回 0,失败返回-1
 cmd: IPC_RMID  //删除共享内存

四、消息队列

消息队列的原理:

在这里插入图片描述

消息队列的定义和使用的:

头文件#include<sys/msg.h>

msgget:

int msgget((key_t) key, int msqflg); 

msgget()创建或者获取一个消息队列
msgget()成功返回消息队列 ID,失败返回-1
msqflg: IPC_CREAT

msgsnd:

int msgsnd(int msqid, const void *msqp, size_t msqsz, int msqflg); 
msgsnd()发送一条消息,消息结构为:
struct msgbuf
 {
 long mtype; // 消息类型, 必须大于 0
 char mtext[1]; // 消息数据
 };
 msgsnd()成功返回 0, 失败返回-1
 msqsz: 指定 mtext 中有效数据的长度
 msqflg:一般设置为 0 可以设置 IPC_NOWAIT

msgrcv:

 ssize_t msgrcv(int msqid, void *msgp, size_t msqsz, long msqtyp, int msqflg); 

 msgrcv()接收一条消息
 msgrcv()成功返回 mtext 中接收到的数据长度, 失败返回-1
 msqtyp: 指定接收的消息类型,类型可以为 0
 msqflg: 一般设置为 0 可以设置 IPC_NOWAIT

msgctl:

 int msgctl(int msqid, int cmd, struct msqid_ds *buf); 
 msgctl()控制消息队列
 msgctl()成功返回 0,失败返回-1
 cmd: IPC_RMID

a.c:

在这里插入图片描述

b.c:

在这里插入图片描述

总结:

四种进程间通信的方法其实实现的原理都很像,都是申请一个空间将所要传递的信息暂时保存,然后等待另一个进程将信息取出来,实现进程间的通信。

不论是管道,信号量,共享内存,消息队列都是有限的资源,所以用完之后要回收,否则当资源用完就无法再申请了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值