进程间通信

进程间通信:俩个进程间进行数据的交互,主要有以下类型
无名管道
有名管道
信号
共享内存
消息队列
信号量
套接字
这七种通讯方式:

一、无名管道 pipe

#include <unistd.h>
int pipe(int pipefd[2]);
pipe的参数是一个数组,数组里面有放的是两个pidfd进程描述符。
pidfd[0]:读端
pidfd[1]:写端
write(pidfd[1] , buf,sizeof(buf));
就可以通过写端,把数据写到无名管道里面。
read(pid[0],buf,sizeof(buf));
就可以把无名管道的数据读道buf里面
读写的进程必须是亲缘进程

无名管道是在内核中开辟的一段空间,(内核本来就有这个空间,不是我们调用pipe才开辟的)
无名管道限制较大:只能用于亲缘进程间进行通信。即父子进程和兄弟进程间进行通信。
使用无名管道pipe就会在进程开辟一个读端和一个写端。且无名管道只能十单工通信的,就是,你自己定义好两个进程间谁是读端,谁是写端。
原本就存在内核里面的无名管道pipe有固定大小,这设置内核的时候就已经固定好了,linux下为64k,最多能存放65535个字节。
在这里插入图片描述
二、有名管道:FIFO

#include <sys/types.h>
#include <sys/state.h>
int mkfifi(const char *filename ,mode_t mode); 创建有名管道filename 为fifo的管道 mode为管道权限,一般为可读可写0666

然后你再通标准io去读写管道
读进程先open const char *filename这个文件且为只读模式。
然后read();到buf。
写进程:open const char *filename这个文件且为只写模式。
然后把buf wrinte到buf.

既然无名管道有这么多的限制,那么为了解决进程间通信的这些问题,引入了有名管道。
有名管道有以下特点:
1)适用于任何进程间进行通信
2)它没有大小的限制
以上特性的原因是因为有名管道的通信机制。他是通过把数据写在一个文件中,然后另一个进程再去读这个文件来实现数据的交互,读写文件都是通过write read 参数跟绝对路径名来完成。这个文件就是一个管道。
有名管道以文件的形式存在于文件系统中。文件系统也是内核的一部分。

3、信号signal
signal是内核中原本就存在的命令。可以利用kill -l 来查看
在这里插入图片描述
那我们是讲进程间通信的,你给我整这么多命令干嘛??
信号的处理方式:缺省,忽略,捕获

#include <signal.h>
sighandler_t signal(int signum, sighandler_t handler);
signum信号的类型    handler:信号处理函数/也可以写SIG_DFL缺省,SIG_INT默认

当其它进程使用了signum信号的类型 ,它就会执行 handler:信号处理函数/也可以写SIG_DFL缺省,SIG_INT默认 函数。
实现进程间通信?我不理解。。。
————————————————————————————————

重点:掌握共享内存、消息队列、信号量 这几个都是基于System V IPC的。
IPC 对象包含: 共享内存、消息队列和信号灯集
每个IPC对象有唯一的ID
IPC对象创建后一直存在,直到被显式地删除
每个IPC对象有一个关联的KEY
ipcs / ipcrm

我们在使用共享内存和消息队列信号量的时候,需要关联一个关键字KEY值,来实现同一组key值下面的进程使用
sharememary,Message Queues,signal 方式来实现进程间的通信。
创建关联的key

#include  <sys/types.h>
 #include <sys/ipc.h>
 key_t  ftok(const char *path,  int proj_id);
 成功时返回合法的key值,失败时返回EOF
 path  存在且可访问的文件的路径
 proj_id  用于生成key的数字,范围1-255

在使用共享内存,消息队列,信号量之前都要创建关联的key。
————————————————————————————————————————————
4、共享内存shareMemary : shmget

共享内存在内核空间创建,被进程的函数接口shmat映射到内存中。在内存中它的访问速度就非常快,进程直接读写内存
由于可能存在多个进程同时访问的请况,因此需要同步和互斥机制。

共享内存的使用步骤:

1)创建共享内存:shareMemary get
 int shmget(key_t key, int size, int shmflg);
 成功时返回共享内存的id,失败时返回EOF
 key  和共享内存关联的key值,IPC_PRIVATE 或 ftok生成
 size 共享内存的大小
 shmflg 共享内存标志位  IPC_CREAT|0666
 
 2}映射共享内存,即把指定的共享内存映射到进程的地址空间用于访问 :shareMemary attch:触摸粘贴
  void  *shmat(int shmid, const void *shmaddr, int shmflg);
  成功时返回映射后的地址,失败时返回(void *)-1
  shmid   要映射的共享内存id
  shmaddr   映射后的地址, NULL表示由系统自动映射
  shmflg   标志位  0表示可读写;SHM_RDONLY表示只读
  
3)读写共享内存
通过映射的地址段指针访问共享内存

4)使用完之后:撤销共享内存映射  或者进程结束时,会自动撤销映射 shareMemary delete
int  shmdt(void *shmaddr);

5)删除共享内存对象 .不用了时一定要删除的要不然会一直存在
 int  shmctl(int shmid, int cmd, struct shmid_ds *buf); 	//这是共享内存控制函数,把cmd写成删除remid就可以删除共享内存
 shmctl(shmid, IPC_RMID, NULL)

代码示例:

wirte.c
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>

int main(){
	
	key_t key;		//ipc节点
	int shmid;		//共享内存id
	char *addr;		//映射地址
	
	
	key = ftok(".",23);    创建ipc节点 在.(当前目录,你也可以随便选一个绝对路径) key编号为23.
	if(key==-1){
		perror("ftok");
		return -1;
	}
	shmid = shmget(key,1024,IPC_CREAT|0666);	创建共享内存,关联在key值下,共享内存大小为1024权限0666读写
	if(shmid==-1){
		perror("shmget");
		return -1;
	}
	
	addr = shmat(shmid,NULL,0);				映射到内存当中去,0代表自动映射,返回映射地址的指针
	
	fget(addr,32,stdin);					往共享内存当中存放数据
	//strcpy(addr,"this my share memeory");
	
	shmdt(addr);				撤销共享内存
	————————————————————————}
read.c

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

int main(){
	key_t key;
	int shmid;
	char *addr;	
	
	key = ftok(".",23);
	if(key==-1){
		perror("ftok");
		return -1;
	}
	shmid = shmget(key,1024,IPC_CREAT|0666);    
	if(shmid==-1){
		perror("shmget");
		return -1;
	}
	由于我们不知道返回的共享内存id是什么,我们可以重新映射遍,对于同一个ftok它会映射在相同的地方。不过你如果
	知道共享内存id(你可以在write的时候打印一下)就可以直接不用上面的操作。不过一般我们是不知道的
	addr = shmat(shmid,NULL,0);
	
	//strcpy(addr,"this my share memeory");
	printf("get share mem=%s\n",addr);  读取共享内存中的数据
	
	
	shmdt(addr);

	shmctl(shmid,IPC_RMID,NULL);
	}

}

————————————————————————————————————
5、消息队列 Message Queues :msgget
消息队列是存在与内核中的一个队列,这个队列msqid_ds,有唯一的id标识符,并且也要创建ipc,在key值下。
进程可以传递msg_ts的数据结构体放入消息队列中,并通过msqid_ds.msg_first、msg_last维护一个先进先出的msg数据链表。
msg_ts里面有个msg_typ可以用来接收消息的时候也是从msg链表队列尾部查找到一个msg_type匹配的msg节点。
总的来说内核中的队列msqid_ds:里面有msqid_ds.msg_first、msg_last来指向和管理进程传来的msg_ts数据结构体
在这里插入图片描述

消息队列创建方式:
打开/创建消息队列   msgget
msgget(key, IPC_CREAT|0666))
向消息队列发送消息   msgsnd
int msgsnd(int msgid, const void *msgp, size_t size,int msgflg);
从消息队列接收消息   msgrcv
 int msgrcv(int msgid, void *msgp, size_t size, long msgtype,int msgflg);
控制消息队列   msgctl
 int msgctl(int msgid, int cmd, struct msqid_ds *buf);
 cmd    要执行的操作  IPC_STAT / IPC_SET / IPC_RMID

消息队列注意点。
通信双方首先定义好统一的消息格式
用户根据应用需求定义结构体类型
首成员类型必须为long,代表消息类型(正整数)
其他成员都属于消息正文
消息长度不包括首类型 long

演示代码:

msg_send.c
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
定义通信结构体msg_ts
typedef struct{
	
	long type;
	char txt[64];
}MSG;

#define LEN sizeof(MSG)-sizeof(long) 数据大小

int main(){
	
	key_t ipkey;   ipc的key值
	int msgid;  	消息队列id

	MSG msg_t;		实例化通信结构体
	
    ipkey = ftok(".",23);    创建ipckey值
	if(ipkey==-1){
		perror("ftok");
		return -1;
	}
	msgid = msgget(ipkey,IPC_CREAT|0666);    创建消息队列
	if(msgid ==-1){
		perror("msgget");
		return -1;
	}
	msg_t.type = 1; 		 定义消息类型为1
	strcpy(msg_t.txt,"msg type one");		写数据到通信结构体中
	msgsnd(msgid,&msg_t,LEN,0);		把通信数据结构体写入到消息队列中

	msg_t.type = 2;				定义消息类型为2
	strcpy(msg_t.txt,"msg type two");
	msgsnd(msgid,&msg_t,LEN,0);
	
	msg_t.type = 3;
	strcpy(msg_t.txt,"msg type three");
	msgsnd(msgid,&msg_t,LEN,0);
	
	msg_t.type = 4;
	strcpy(msg_t.txt,"msg type four");
	msgsnd(msgid,&msg_t,LEN,0);
	
	msg_t.type = 5;
	strcpy(msg_t.txt,"msg type five");
	msgsnd(msgid,&msg_t,LEN,0);

写了5个通信结构体在消息队列中
}_________________
msg_recv.c
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <linux/msg.h>
#include <string.h>
#include <stdlib.h>
定义通信结构体msg_ts
typedef struct{
	long type;
	char txt[64];
}MSG;
int msgid;   消息队列id

#define LEN sizeof(MSG)-sizeof(long)
删除结构体
void rmmsg(int sig){
	
	msgctl(msgid,IPC_RMID,NULL);
	exit(0);
	
}

int main(){
	
	key_t ipkey;
	int re;
	MSG msg_t;
	
    ipkey = ftok(".",23);		构建ipkey值
	if(ipkey==-1){
		perror("ftok");
		return -1;
	}
	msgid = msgget(ipkey,IPC_CREAT|0666);    构造消息队列
	if(msgid ==-1){
		perror("msgget");
		return -1;
	}	
	signal(SIGINT,rmmsg);			信号默认从系统内核中删除消息对列
	
	while(1){
		re = msgrcv(msgid,&msg_t,LEN,3,MSG_EXCEPT);   接收msgid的消息队列 参数二缓冲区地址 接收信息的长度 标志位
		printf("receive msg:type=%d,txt=%s\n",(int)msg_t.type,msg_t.txt);
		if(re<=0){
			break;
		}
	}
	

我们可以用type来定义不同的通信数据结构体,然后在接收端通过对比type判断是否是我要的数据。
这和内核中iocl的魔数类似。

————————————————————————————————————————————
6、信号量
在前面的内核中共享资源的同步和互斥中里面有锁,也有一个信号量,其实那里的信号量和这里讲的信号量是一个东西。

信号量用于进程间的同步和互斥。给的信号大于1同步,等于1互斥

System V信号灯使用步骤
打开/创建信号灯   semget
 int semget(key_t key, int nsems, int semflg);
  成功时返回信号灯的id,失败时返回-1
  key   和消息队列关联的key  IPC_PRIVATE 或 ftok
  nsems   集合中包含的计数信号灯个数
  semflg   标志位  IPC_CREAT|0666   IPC_EXCL
  
信号灯初始化   semctl
int semctl(int semid, int semnum, int cmd,);
  成功时返回0,失败时返回EOF
  semid    要操作的信号灯集id
  semnum   要操作的集合中的信号灯编号
  cmd   执行的操作  SETVAL  IPC_RMID
  union  semun      取决于cmd
  
P/V操作   semop

P操作  	信号量减
int semop(int semid, struct sembuf *sops, unsigned nsops);
  成功时返回0,失败时返回-1
  semid    要操作的信号灯集id
  sops    描述对信号灯操作的结构体(数组)
  nsops   要操作的信号灯的个数
  
v操作		信号量加

删除信号灯  semctl

当有进程使用信号量 信号量减一‘ 当有进程释放信号量 信号量加一

示例代码:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <errno.h>
#include <sys/sem.h>
#include <string.h>

union  semun{
	int val;
};

#define SEM_READ 0
#define SEM_WRITE 0

poperation(int index,int semid){
	struct  sembuf sop;
	sop.sem_num = index;
	sop.sem_op = -1;
	sop.sem_flg = 0;
	semop(semid,&sop,1);
	
}

voperation(int index,int semid){
	struct  sembuf sop;
	sop.sem_num = index;
	sop.sem_op = 1;
	sop.sem_flg = 0;
	semop(semid,&sop,1);	
	
}


int main(){
	
	key_t key;
	int semid;
	int shmid;
	pid_t pid;
	
	char *shmaddr;
	
	key  = ftok(".",123);
	 
	/*创建2个信号灯*/
	semid = semget(key,2,IPC_CREAT|0777);
	if(semid<0){
		perror("semget");
		return -1;
	}

	
	/*创建共享内存*/
	shmid = shmget(key,256,IPC_CREAT|0777);
	if(shmid<0){
		perror("shmget");
		return -1;
	}	
	

7、socket
套接字,在网络上有讲

—————————————————————————————————————————————
进程通信对比:

管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。

有名管道 (named pipe) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。

信号量( semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。

消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。

信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。

共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。

套接字( socket ) : 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值