命名管道
概念
匿名管道应用的一个限制就是只能在具有公共祖先的进程间通信;如果想要在不相关的进程之间交换数据,就可以使用命名管道,命名管道是一种特殊类型的文件
假如进程a指向一个名为named_pipe的文件(保存在磁盘上),此时又有一个进程b也要指向文件named_pipe;由于两个进程指向同一个文件,则操作系统便只会创建一个对应的文件结构体struct file供进程使用;进程a此时要向文件内写入,进程b要向文件中读取,此情此景是不是和上面匿名管道一致呢?
不同的是这次的进程没有任何关系,通过打开指定文件(路径+文件名),给两进程提供一份共同的资源,也称作命名管道
图解:
创建命名管道
先介绍创建命名管道的函数
int mkfifo(const char *pathname, mode_t mode);
返回值:
On success mkfifo() returns 0. In the case of an error, -1 is returned
创建两个进程,进行通信
student.cpp
学生对老师说话
#include"test.hpp" int main() { cout<<"student begin: "<<endl; int wfd=open(NAME_PIPE,O_WRONLY); cout<<"student end: "<<endl; if(wfd<0) { exit(1); } char buffer[1024]; while(true) { cout<<"Please say: "; fgets(buffer,sizeof(buffer),stdin); if(strlen(buffer)>0) { buffer[strlen(buffer)-1]=0; } ssize_t s=write(wfd,buffer,strlen(buffer)); assert(s==strlen(buffer)); (void)s; } close(wfd); return 0; }
teacher.cpp
老师读取学生说的话
#include"test.hpp" int main() { bool r=creatfifo(NAME_PIPE); assert(r==true); (void)r; cout<<"teacher begin: "<<endl; int rfd=open(NAME_PIPE,O_RDONLY); cout<<"teacher end: "<<endl; if(rfd<0) { exit(1); } char buffer[1024]; while(true) { ssize_t s=read(rfd,buffer,sizeof(buffer)-1); if(s>0) { buffer[s]=0; cout<<"student->teacher: "<<buffer<<endl; } else if(s==0) { cout<<"teacher quit"<<endl; break; } else { cout<<"errno"<<strerror(errno)<<endl; break; } } close(rfd); removefifo(NAME_PIPE); return 0; }
test.hpp
#pragma once #include<iostream> #include<string> #include<cstring> #include<cerrno> #include<cassert> #include<unistd.h> #include<sys/types.h> #include<sys/stat.h> #include<fcntl.h> using namespace std; #define NAME_PIPE "/tmp/mypipe.named" bool creatfifo(const string &path) { int n=mkfifo(path.c_str(),0600); if(n==0) { return true; } else { cout<<"errno: "<<errno<<strerror(errno)<<endl; return false; } } void removefifo(const string&path) { int n=unlink(path.c_str()); assert(n==0); (void)n; }
判断语句assert和if的区别,前者是明确结果进行判断,后者是不清楚结果进行判断
这里解释一下(void)n的作用,首先在debug版本下,该语句和上一条判断会被执行;在release版本下,判断语句assert(n==0)是不会被执行的,当执行下一条语句是,程序会报错因为变量n产生之后并没有被使用,所以(void)n的作用就是使变量被使用一次
简单的命名管道就完成了!!!
system v共享内存
概念
共享内存的概念:通过让不同的进程看到同一个内存块的方式称作共享内存
上面两种管道实现进程间通信都是基于文件完成的,共享内存是基于内存完成的,原理解释如下:
假设存在两个进程,分别通过自己的进程空间在页表中映射到内存的不同区域
图解:
如果要让这两个进程进行通信,首先操作系统需要在内存中提供一份资源,有了资源,还需要让这两个进程都能看到,所以将创建好的内存资源通过两页表映射到进程中
图解:
在将来如果不想进行通信可以取消进程和内存的关系也称去关联,然后再去释放共享内存
上面只是假设两个进程进行通信,其实系统中只要是想进行通信的进程都可以是共享内存,毕竟这只是一个方式,还有操作系统中一定是存在着许多个共享内存的
代码实现
首先,创建一块共享内存
int shmget(key_t key, size_t size, int shmflg);
shmglg:本质就是宏,存在两个选项:IPC_CREAT表示如果共享内存不存在,则创建,如果已经存在则进行获取;IPC_EXCL需要和第一个宏一起使用,IPC_CREAT|IPC_EXCL如果共享内存不存在,进行创建,如果存在,返回错误
size_t size:共享内存的大小
key_t key:此参数是为了确保进程可以看到同一内存
key_t ftok(const char *pathname, int proj_id);:进行通过路径名称和项目id便可以到内存中找到同一区域,返回key标识这一内存,也就做到了让不同的内存看到同一资源
返回值On success, a valid shared memory identifier is returned. On errir, -1 is returned, and errno is set to indicate the error.:创建成功,返回内存标识
返回值和key都是标识内存的,那么有什么区别呢???
操作系统中存在着许多的共享内存,肯定不能是杂乱无章的,一定需要进行管理,方法还是:先描述,再组织;所以共享内存其实是由两部分组成的:物理内存块和共享内存的相关属性也就是结构体struct shm;在创建共享内存时,为了保证唯一性使用key标识,也就是将其写入结构体中,当其他进程便可根据这一标识找到共享内存;总之,key标识内存时内核级别的,返回值标识内存是用户级的,都是标识,只是面对的对象不同罢了
代码实现创建共享内存
key_t getKey() { key_t k=ftok(PATHNAME,PROJ_ID); if(k<0) { cerr<<errno<<" "<<strerror(errno)<<endl; exit(1); } return k; } int getShmhelper(key_t k,int flags) { int shmid=shmget(k,MAX_SIZE,flags); if(shmid<0) { cerr<<errno<<" "<<strerrno(errno)<<endl; exit(2); } return shmid; } int getShm(key_t k) { return getShmhelper(k,IPC_CREAT); } int creatShm(key_t k) { return getShmhelper(k,IPC_CREAT|IPC_EXCL|0600); }
查看生成的共享内存 指令ipcs -m
一般而言,当进程执行完毕比那会退出,生命周期也就结束;这里很奇怪,当第二次生成共享内存时,进程报错,并且报错原因还是文件已经存在,由此可知:共享内存的生命周期是随操作系统的,不是随进程的
这里就引入了一个函数来结束共享内存的生命
int shmctl(int shmid, int cmd, struct shmid_ds *buf)
shmid:标识共享内存
cmd:也就宏,这里只介绍一个选项IPC_RMID将共享内存杀掉
共享内存的内核结构体
包含共享内存的许多属性,包括标识共享内存的
key也在其中
代码实现 shmctl
void delShm(int shmid) { if(shmctl(shmid,IPC_RMID,nullptr)==-1) { cerr<<errno<<" "<<strerror(errno)<<endl; } }
共享内存在进程结束之后便也被删除了
共享内存创建之后,接下来就是将进程与共享内存建立联系
void *shmat(int shmid, const void *shmaddr, int shmflg);
shmid:标识共享内存
shmaddr:指定共享内存通过页表映射到进程空间的某一区域,一般设置为空
shmflg:设置进程的读写权限,一般设置为空
返回值:共享内存通过页表映射到进程空间某一区域的起始地址
void*attachShm(int shmid) { void*mem=shmat(shmid,nullptr,0); if((long long)mem==-1L) { cerr<<"shmat: "<<errno<<" "<<strerror(errno)<<endl; exit(3); } return mem; }
进程与共享内存建立联系之后,可以进行查看
当前存在一个进程与共享内存建立联系
建立联系之后,紧接着就是进行通信
代码实现两进程实现通信
client.cpp
int main() { key_t k=getKey(); printf("key: %x\n",k); //获取共享内存 int shmid=getShm(k); printf("shmid: %d\n",shmid); //建立联系 char*start=(char*)attachShm(shmid); printf("attach success,address start: %p\n",start); const char*message="hello server,我是另一个进程,咱俩正在通信"; pid_t id=getpid(); int cnt=0; //通信 while(true) { snprintf(start,MAX_SIZE,"%s[pid:%d][消息编号:%d]",message,id,cnt++); } //去关联 detachShm(start); return 0; }
server.cpp
int main() { key_t k=getKey(); printf("key:%x\n",k); //创建共享内存 int shmid=creatShm(k); printf("shmid: %d\n",shmid); //建立联系 char*start=(char*)attachShm(shmid); printf("attach success,address start: %p\n",start); //通信 while(true) { printf("client say: %s\n",start); } //去关联 detachShm(start); //删除共享内存 delShm(shmid); return 0; }
完成通信之后,先将两进程去关联,再删除共享内存
int shmdt(const void *shmaddr);
shmaddr是共享内存映射到进程空间地址的起始地址
返回值:失败返回-1
void detachShm(void* start) { if(shmdt(start)==-1) { cerr<<"shmdt: "<<errno<<" "<<strerror(errno)<<endl; } }
总结
共享内存的优点:
在所有进程间通信中,速度是最快的,能够大大地减少拷贝次数
对比:
管道
共享内存
共享内存的缺点:对数据没有包含操作