进程间通信:俩个进程间进行数据的交互,主要有以下类型
无名管道
有名管道
信号
共享内存
消息队列
信号量
套接字
这七种通讯方式:
一、无名管道 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 ) : 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。