Linux进程通信
一.概述
进程通信的方式有如下几种:
管道、共享内存、消息队列、信号量、信号、套接字( socket )
管道:
内存共享:
消息队列:最快的通信方式
二.管道
管道:本质是文件的形式,是内核的一块缓冲区
匿名管道只能用于具有亲缘关系的进程间通信
命名管道可以用于任意的进程间通信
管道自带同步与互斥功能(读写操作数据大小不超过PIPE_BUF大小,读写操作受保护)
1.无名管道
(1)概述
是一种内存文件,
只能用于具有亲缘关系的进程间通信,fork的方式。
是单向的,往往是 父写子读。
读写特性:
管道中若没有数据,则read会阻塞,直到读取到数据(有数据写入管道了)
管道中若数据满了,则write会阻塞,直到有空闲空间(有数据被读出了)
若所有读端被关闭,则write会触发异常—SIGPIPE(导致进程退出)
若所有写端被关闭,则read读完数据之后不会阻塞,而是返回0
参考1
【1】、管道是UNIX系统的IPC的最古老方式,并且多数unix系统都提供此种通信方式。
【2】、管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
【3】、虽然管道是半双工的,但是我们可以通过创建两个无名管道实现父子进程间的相互通信。我们来看下图常用的编程模型
(2) 函数介绍
#include <unistd.h>
【1】int pipe (int filedes[2])
pipe()函数的功能就是创建一个内存文件
filedes[0]用于读出数据,读取时必须关闭写入端,即close(filedes[1]);
filedes[1]用于写入数据,写入时必须关闭读取端,即close(filedes[0])。
【2】ssize_t write (int fd,const void * buf,size_t count);
【3】ssize_t read (int fd,void * buf ,size_t count);
【4】int close (int fd);
(3).代码示例
这里是 半双工的无名管道的通信方式。
所谓全双工的测试代码,就是在创建进程之前创建两个管道,在父子进程中分别关掉一个读写端,这样就可以实现父子进程间通信。虽然这样实现了父子进程间的通信,我们也能看到一个问题,有名管道只能用到有亲缘关系的父子进程间通信。这样是很不方便的。
#g++ -g pipe01.cc -o pipe01.out
#include <unistd.h>
#include <iostream>
#include <string.h>
using namespace std;
#define BUFF_SIZE 128
int main()
{
// 1.create pipe
int pipeFd[2];
pipe(pipeFd);
// 2. father process copy fd to child process
pid_t pid = fork();
if(pid > 0)
{
cout<<"father process running"<<endl;
char str[BUFF_SIZE] = {"It's fater process send msg"};
close(pipeFd[0]); // close read
write(pipeFd[1],str,strlen(str));
//while(1);
}
else if(pid == 0)
{
cout<<"child process running"<<endl;
char str[BUFF_SIZE] = {0};
memset(str,0,BUFF_SIZE);
close(pipeFd[1]); // close write
read(pipeFd[0],str,BUFF_SIZE);
cout<<str<<endl;
//while(1);
}
return 0;
}
2.有名管道
(1)概述
通过创建 管道文件,通过对该文件的读写 进行通信。该文件在文件系统中可见。
管道原理:操作系统在内核提供一块缓冲区(只要进程能够访问到这块缓冲区就可以实现通信)
可以用于任意的进程间通信
打开特性:
若管道文件没有被写的方式打开,则以只读打开会阻塞
若管道文件没有被读的方式打开,则以只写打开会阻塞
读写特性雷同于匿名管道
(2) 函数介绍
#include <unistd.h>
#include <sys/stat.h>
【1】int mkfifo (const char *pathname, mode_t mode)
创建了一个FIFO文件,是一种文件类型,可当做一个常见的文件进行读写操作。
【2】ssize_t write (int fd,const void * buf,size_t count);
【3】ssize_t read (int fd,void * buf ,size_t count);
【4】int close (int fd);
(3)代码示例
#g++ fifo_read.cc -o fifo_read.out
#include <iostream>
#include <fcntl.h>
#include <sys/stat.h>
using namespace std;
#define PATH "./a.fifo"
int main(void)
{
int ret = mkfifo(PATH,0777);
//if(ret < 0) { cout<<"mkfifo error"<<endl; return -1; }
int fd = open(PATH,O_RDONLY);
if(fd < 0) { cout<<"open error"<<endl; return -1; }
while(1)
{
cout<<"-------------"<<endl;
sleep(1);
char buff[1024] = { 0 };
int ret = read(fd,buff,1023);
if(ret < 0) { cout<<"read failed"<<endl; return -1;}
else if( ret == 0) { cout<<"write close"<<endl; return -1;}
cout<<"buff:"<<endl<<buff<<endl;
}
close(ret);
return 0;
}
#g++ fifo_write.cc -o fifo_write.out
#include <iostream>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
using namespace std;
#define PATH "./a.fifo"
int main()
{
int ret = mkfifo(PATH,0777);
// if(ret < 0) { cout<<"mkfifo error"<<endl; return -1; }
int fd = open(PATH,O_WRONLY);
if(fd < 0) { cout<<"open error"<<endl; return -1; }
while(1)
{
char buff[1024] = { 0 };
bzero(buff,sizeof(buff));
cin>>buff;
cout<<buff<<endl;
write(fd,buff,strlen(buff));
}
close(ret);
return 0;
}
三.共享内存
1.概述
共享内存 是多个进程都可以共享访问的一段内存区域。是linux下最快的进程间通信方式。
共享内存是直接将一块内存区域映射到虚拟地址空间中,因此在数据通信传输的过程中,相比较其他的通信方式少了将数据从用户态到内核态的数据拷贝过程。
2.函数介绍
#include<sys/shm.h>
(1)创建共享内存
int shmget (key_t key,size_t size,int shmflg);
//参数:key是共享内存段名字,进程间通信的标识符,和我们消息队列定义的一样。需要自己define 定义
//size 是共享内存的大小
//shmflg是创建权限,共九种,类似于创建文件时的mode权限
//返回值:成功返回一个非负整数是操作句柄,失败返回-1
(2)将共享内存 映射 到虚拟地址空间
void * shmat (int shmid, const void * shmaddr, int shmflg);
//参数:shmid 是shmget返回的操作句柄,
//shmaddr 是映射第起始地址,如果参数给NULL则系统分配
//shmflg 是权限只读、只写、读写等,系统给出类一些宏定义,比如SHM_PDONLY 表示只读,无标志标识读写
SHM_RND表示地址向下调职SHMLBA的整数倍
//返回值,映射的虚拟地址的首地址
(3)解除映射
int shmdt (const void * shmaddr);
(4)删除共享内存
int shmctl(int shmid, int cmd, struct shmid_ds * buf);
//参数:shmid 是句柄,依然是shmget返回的那个
//cmd 是要执行的操作,系统给出了一些宏定义,和权限类似。IPC_RMID代表删除
//buf 用于接收共享内存描述信息大小,时间信息等,若用于不关心则传NULL过去。
//返回值 成功返回0 ,失败返回-1
特别注意:当共享内存依然还和其他内存保有映射关系的时候,shmctl并不会直接删除共享内存,而是等待全部进程和该共享内存解除映射后才会删除,但是这段时间会拒绝新的映射建立
终端下命令:
ipcs -m //显示当前有哪些共享内存
ipcs -m -i 32768 //显示当前ID的共享内存
ipcrm -m 0x12345678 //删除共享内存
3.代码实例
#g++ sharemem_write.cc -o sharemem_write.out
#include <iostream>
#include <string.h>
#include <sys/shm.h>
#include <unistd.h>
using namespace std;
#define IPC_KEY 0x12345678
int main()
{
int shmid = shmget(IPC_KEY,128,IPC_CREAT | 664);
if(shmid < 0) { cout<<"shmget error"<<endl;}
void *shm_start = shmat(shmid,NULL,0);
if(shm_start == (void *)-1) { cout<<"shmat error"<<endl; }
while(1)
{
memset(shm_start,0,128);
cout<<"Please input "<<endl;
cin>>(char *)shm_start;
sleep(1);
}
shmdt(shm_start);
shmctl(shmid,IPC_RMID,NULL);
return 0;
}
#g++ sharemem_read.cc -o sharemem_read.cc
#include <iostream>
#include <string.h>
#include <sys/shm.h>
#include <unistd.h>
using namespace std;
#define IPC_KEY 0x12345678
int main()
{
int shmid = shmget(IPC_KEY,128,IPC_CREAT | 0664);
if(shmid < 0) { cout<<"shmget error"<<endl;}
void *shm_start = shmat(shmid,NULL,0);
if(shm_start == (void *)-1){ cout<<"shmat error"<<endl;}
while(1)
{
cout<<(char *)shm_start<<endl;
sleep(1);
}
shmdt(shm_start);
shmctl(shmid,IPC_RMID,NULL);
return 0;
}
四、消息队列
1.概述
(1)什么是消息队列
消息队列是消息的链表,存放在内核中并由消息队列标识符表示。
消息队列提供了一个从一个进程向另一个进程发送数据块的方法,每个数据块都可以被认为是有一个类型,接受者接受的数据块可以有不同的类型。
但是同管道类似,它有一个不足就是每个消息的最大长度是有上限的(MSGMAX)
(2).特点
生命周期随内核,消息队列会一直存在,需要我们显示的调用接口删除或使用命令删除
消息队列可以双向通信
克服了管道只能承载无格式字节流的缺点
2.函数介绍
#include <sys/ipc.h>
#include <sys/msg.h>
(1)生成一个key(键值)
key_t ftok (const char *pathname, int proj_id);
//pathname,可选用一个已经存在的文件路径
【1】ftok根据路径名,提取文件信息,再根据这些文件信息及project ID合成key,该路径可以随便设置。
【2】该路径是必须存在的,ftok只是根据文件inode在系统内的唯一性来取一个数值,和文件的权限无关。
【3】proj_id是可以根据自己的约定,随意设置。这个数字,有的称之为project ID; 在UNIX系统上,它的取值是1到255;
(2)创建或取得一个消息队列对象
int msgget (key_t key, int msgflg);
返回:消息队列对象的id 同一个key得到同一个对象
格式:msgget(key,flag|mode);
flag:可以是0或者IPC_CREAT(不存在就创建)
mode:同文件权限一样
(3)消息发送
int msgsnd (int msqid, const void msgp, size_t msgsz, int msgflg);
功能:将msgp消息写入标识为msgid的消息队列
msgp:
struct msgbuf {
long mtype; / message type, must be > 0 /消息的类型必须>0
char mtext[1]; / message data */长度随意
};
msgsz:要发送的消息的大小 不包括消息的类型占用的4个字节
msgflg: 如果是0 当消息队列为满 msgsnd会阻塞
如果是IPC_NOWAIT 当消息队列为满时 不阻塞 立即返回
返回值:成功返回id 失败返回-1
(4)消息接收
ssize_t msgrcv (int msqid, void *msgp, size_t msgsz, long msgtyp,
int msgflg);
功能:从标识符为msgid的消息队列里接收一个指定类型的消息 并 存储于msgp中 读取后 把消息从消息队列中删除
msgtyp:为 0 表示无论什么类型 都可以接收
msgp:存放消息的结构体
msgsz:要接收的消息的大小 不包含消息类型占用的4字节
msgflg:如果是0 标识如果没有指定类型的消息 就一直等待
如果是IPC_NOWAIT 则表示不等待
(5)消息控制
int msgctl (int msqid, int cmd, struct msqid_ds *buf);
msgctl(msgid,IPC_RMID,NULL);//删除消息队列对象
3.代码实例
这里需要学习两个命令
ipcs:显示IPC资源
ipcrm:手动删除IPC资源
ipcs -q //显示消息队列
ipcrm -q 524287 //删除消息队列
#g++ msgQueueSend.cc -o msgQueueSend
#include <iostream>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <fcntl.h>
#include <string.h>
using namespace std;
#define MSG_TYPE 6
typedef struct msg{
long type;
char info[64];
}Msg_t;
int main()
{
key_t key = ftok("/msgQtest",'6');
cout<<"key:"<<hex<<key<<endl;
int msgid = msgget(key,IPC_CREAT | O_WRONLY | 0777);
if(msgid < 0) { cout<<"msgget error"<<endl; return -1; }
while(1)
{
Msg_t m;
bzero(&m,sizeof(m));
m.type = MSG_TYPE;
cout<<"Please input"<<endl;
cin>>m.info;
msgsnd(msgid,&m,sizeof(m)-sizeof(m.type),0);
}
return 0;
}
#g++ msgQueueRecv.cc -o msgQueueRecv
#include <iostream>
#include <fcntl.h>
#include <string.h>
#include <sys/msg.h>
using namespace std;
#define MSG_TYPE 6
typedef struct msg{
long type;
char info[64];
}Msg_t;
int main()
{
key_t key = ftok("/msgQtest",'6');
cout<<"key: "<<hex<<key<<endl;
int msgid = msgget(key,O_RDONLY);
if(msgid < 0) { cout<<"msgget error"<<endl; return -1; }
while(1)
{
Msg_t m;
bzero(&m,sizeof(m));
msgrcv(msgid,&m,sizeof(m)-sizeof(m.type),MSG_TYPE,0);
cout<<m.info<<endl;
}
msgctl(msgid,IPC_RMID,NULL);
return 0;
}
//==========================