赞
踩
虽然进程都有相对独立性,但是还是需要进行互相通信的,比如说QQ发消息、一个进程想要给另一个进程发送数据、几个进程之间想共享一份数据等等,这些都需要进程进行通信。、
我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”。
# | 命令就是连接两个进程之间的管道命令
# who命令输出两行数据,交给wc -l 进程,统计行数,输出。
who | wc -l
功能:创建一个无名管道
int pipe(int fd [2])
参数:
fd:文件描述符数组,其中fd[0],为读端;fd[1]为写端。
返回值:成功返回0,失败返回错误代码。
使用fork(创建父子进程进行通信)来验证管道的原理:
fork之后:子进程会创建一份新的PCB,并且会复制一份父进程的文件描述符数组;像打开文件的信息,管道等都只共享的一份。
下面这段代码实现了,子进程写入管道,父进程从管道读出这一功能:
#include<iostream> #include<unistd.h> //#include <string.h> #include<cstring> #include<cerrno> #include<cassert> #include<cstdlib> #include<sys/wait.h> #include<sys/types.h> int main() { //利用fork让两个不的进程看到同一份资源 int pipefd[2] = {0}; //1.创建匿名管道 int n = pipe(pipefd); if(n<0) //创建管道失败 { std::cout<<"pipe error, "<<errno<<" : "<<strerror(errno)<<std::endl; return 1; } std::cout<<"pipefd[0]: "<<pipefd[0]<<std::endl; //0读端 std::cout<<"pipefd[1]: "<<pipefd[1]<<std::endl; //1写端 //2. 创建子进程 pid_t id = fork(); assert(id!=-1); if(id == 0) //子进程 { //3.关掉不需要得fd,实现让父进程读,子进程写得功能 close(pipefd[0]); //4.开始通信 --结合某种场景 int cnt = 0; while(true) { char x = 'X'; write(pipefd[1],&x,1); std::cout<<"Cnt: "<<cnt++<<std::endl; sleep(1); } close(pipefd[1]); exit(0); } //父进程 //3. 关闭不需要的fd,让父进程读,子进程写 close(pipefd[1]); //4.开始通信 char buffer[1024]; int cnt = 0; while(true) { int n = read(pipefd[0], buffer, sizeof(buffer)-1); if(n>0) { buffer[n]='\0'; std::cout<<"我是父进程,child give me messages:"<<buffer<<std::endl; } else if(n==0) { std::cout<<"我是父进程,读到了文件结尾"<<std::endl; break; } else{ std::cout<<"我是父进程,读管道异常"<<std::endl; break; } sleep(2); if(cnt++>5) break; } close(pipefd[0]); int status = 0; waitpid(id, &status, 0); std::cout<<" sig: "<<(status &0x7f) << std::endl; sleep(20); return 0; }
fork之后,子进程会复制一份父进程的文件描述符,指向父进程已经打开的文件资源等等。而用父进程创建匿名管道后,会在内存中,开辟一份空间,为父子进程之间提供通信,而父进程创建的这份资源会以fd的形式存在,父进程的打开文件表中。子进程复制后,子进程也能访问。
如下图:
管道(匿名管道)应用的一个限制是只能在具有共同祖先的进程间通信。
但是,如果想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,这就是命名管道(一种特殊类型的文件)。
mkfifo [filename]
int mkfifo(const char* filename, mode_t mode)
第一个参数是创建管道的路径
第二个参数是管道读写的权限,一般是0666
首先命名管道是一个特殊的文件,因此它可以被打开到内存,但是不会将内存数据刷新到磁盘,该文件在系统中具有唯一路径,因此进程可以通过该路径找到管道文件,进行通信。
下面实现一段功能:父进程创建管道文件,然后从管道读消息,子进程从管道写消息。
//1.comm.hpp #ifndef _COMM_H_ #define _COMM_H_ #include<iostream> #include<string> #include<sys/types.h> #include<sys/stat.h> #include<unistd.h> #include<stdlib.h> #include<fcntl.h> #include<cstring> #include "Log.hpp" using namespace std; #define MODE 0666 #define SIZE 128 string ipcPath = "./fifo.ipc"; #endif //2. Log.hpp #ifndef _LOG_H_ #define _LOG_H_ #include<iostream> #include<ctime> #define Debug 0 #define Notice 1 #define Warning 2 #define Error 3 const std::string msg[] = { "Debug", "Notice", "Warning", "Error" }; std::ostream &Log(std::string message, int level) { std::cout<<" | "<<(unsigned)time(nullptr)<<" | "<<msg[level]<<" | "<<message; return std::cout; } #endif //3.client.cxx #include "comm.hpp" int main() { //1.获取管道文件 int fd = open(ipcPath.c_str(), O_WRONLY); if(fd<0) { perror("open"); exit(1); } //2.ipc过程 string buffer; while (true) { cout<<"Please Enter Message Line :>"; std::getline(std::cin, buffer); write(fd, buffer.c_str(), buffer.size()); } //3.关闭文件 close(fd); return 0; } //4. server.cxx #include "comm.hpp" #include<sys/wait.h> static void getMessage(int fd) { char buffer[SIZE]; while(true) { memset(buffer, '\0', sizeof(buffer)); ssize_t s = read(fd, buffer, sizeof(buffer)-1); if(s > 0) { cout<<"[ "<<getpid()<<" ]"<<"client sat>:"<<buffer<<endl; } else if(s == 0) { //end of file cerr<<"[ "<<getpid()<<" ]"<<"read end of file, client quit, server quit too!"<<endl; break; } else{ //read error perror("read"); break; } } } int main() { //1.创建管道文件 if(mkfifo(ipcPath.c_str(), MODE)<0) { perror("mkfifo"); exit(1); } Log("管道文件创建成功", Debug)<<" step1"<<endl; //2.正常文件操作 int fd = open(ipcPath.c_str(), O_RDONLY); if(fd<0) { perror("open"); exit(2); } Log("打开管道文件成功",Debug)<<" step 2"<<endl; int nums = 3; for(int i = 0; i < nums; i++) { pid_t id = fork(); if(id == 0) { //3.编写正常的通信代码 getMessage(fd); exit(1); } } for(int i = 0; i<nums;i++) { waitpid(-1, nullptr, 0); } //4.关闭文件 close(fd); Log("关闭管道文件成功",Debug)<<" step 3"<<endl; unlink(ipcPath.c_str()); //通信完毕,删除管道文件 Log("删除管道文件成功", Debug)<<" step 4"<<endl; return 0; }
共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程地址空间,这些进程间的数据传递不再涉及到内核,换句话说,进程不再通过执行进入内核的系统调用来传递彼此的数据。
如下图是 System V的通信方式:
该方式直接在内存里建立一块空间,供进程通信(进程AB都会在自己的页表上,建立好虚拟地址到物理地址的映射),因此只需访问自己空间的地址,就可以实现进程间通信。
管道对应的公共资源是文件,而文件是OS内核对应的数据结构,需要操作系统维护,因此需要系统调用来实现。而System V只需要在物理内存上申请一块空间,而内存是用户空间里的内容,用户可以不经过系统调用直接进行访问,直接进行内存级的读写即可。
但是共享内存的提供者是OS,因为OS要管理共享内存,因为OS要先描述再组织->共享内存 = 共享内存快+对应的共享内存的内核数据结构。 申请需要OS管理,但是申请完了之后,用户可以直接访问。
第0步
功能:生成一个唯一的key,供shmget使用生成共享内存段。
int ftok(const char* pathname, int proj_id);
参数:
pathname:必须是存在的、可访问的文件路径
proj_id:至少是8bit的非0数字(自己给定)
返回值:若生成成功,返回唯一的key值。失败返回-1
1 .第一步
功能:用来创建共享内存
int shmget(key_t key, size_t size, int shmflg);
参数:
key:这个共享内存段的名字(唯一id)
size:共享内存的大小
shmfg:由九个标志权限构成,他们的用法和创建文件open使用的mode模式标志一样
返回值:成功返回一个非负整数(该段共享内存段的标识码,类似于fd);失败返回-1
2.第二步
功能:将共享内存段连接到进程地址空间(在页表上生成映射)
void shmat(int shmid, const void shmaddr, int shmflg)
参数:
shmid:共享内存标识
shmaddr:指定连接的地址(一般填nullptr)
shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回一个指针,指向共享内存的第一个地址;失败返回-1
第3步
功能:将共享内存点与当前进程脱离
int shmdt(const void* shmaddr);
参数:
shmaddr:由shmat返回的指针
返回值:成功返回0, 失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段!
第四步
功能:用来控制共享内存
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数:
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作
buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1
首先这种内存共享方式,当我们程序结束后,如果代码不主动删除,那么该内存不会被释放!
我们可以使用代码删除、也可以手动删除,手动删除使用如下命令即可。
注意:key只有在创建的时候才有,其他时候访问共享内存都是用shmid
该功能为:Server创建共享内存,并删除。Cilentt使用该共享内存传输数据。
// Log.hpp #ifndef _LOG_H_ #define _LOG_H_ #include<iostream> #include<ctime> #define Debug 0 #define Notice 1 #define Warning 2 #define Error 3 const std::string msg[] = { "Debug", "Notice", "Warning", "Error" }; std::ostream &Log(std::string message, int level) { std::cout<<" | "<<(unsigned)time(nullptr)<<" | "<<msg[level]<<" | "<<message; return std::cout; } #endif //comm.hpp #pragma once #include<iostream> #include<cstdio> #include<unistd.h> #include<sys/types.h> #include<sys/ipc.h> #include<sys/shm.h> #include<cassert> #include"Log.hpp" using namespace std; //不推荐 #define PATH_NAME "/home/xty" #define PROJ_ID 0x66 #define SHM_SIZE 4096 //共享内存大小,最好是4096的整数倍 //shmClient.cc #include "comm.hpp" int main() { key_t k = ftok(PATH_NAME, PROJ_ID); if(k<0) { Log("creat key failed", Error)<<" client key : "<< k <<endl; exit(-1); } Log("creat key done", Debug)<<" client key : "<< k <<endl; //获取共享内存 int shmid = shmget(k, SHM_SIZE, 0); if(shmid < 0) { Log("create shm failed", Error) <<" client key : "<< k <<endl; exit(2); } Log(" create shm success", Debug) <<" client key : " << k <<endl; sleep(10); char* shmaddr = (char *)shmat(shmid, nullptr, 0); if(shmaddr == nullptr) { Log(" attach shm failed", Error) <<" client key : "<< k<<endl; exit(3); } Log(" attach shm success", Debug) <<" client key : "<< k<<endl; sleep(10); // 使用 //去关联 int n = shmdt(shmaddr); assert(n!=-1); Log("detach shm success", Debug) << "client key : "<< k <<endl; sleep(10); // client 要不要chmctl删除呢?不需要 return 0; } //shmServer.cc #include "comm.hpp" string TransToHex(key_t k) { char buffer[32]; snprintf(buffer, sizeof(buffer), "0x%x", k); return buffer; } int main() { //1.创建公共的key值 key_t k = ftok(PATH_NAME, PROJ_ID); assert(k!=-1); Log("create key done", Debug)<<" server key : "<< TransToHex(k) <<endl; //2.创建共享内存 --建议要创建一个全新的共享内存--通信的发起者 int shmid = shmget(k, SHM_SIZE, IPC_CREAT|IPC_EXCL|0666); if(shmid == -1) { perror("shmget"); exit(1); } Log(" create shm done", Debug) << " shmid : "<< shmid<<endl; sleep(10); //3.将指定的共享内存,挂接到自己的地址空间 char * shmaddr = (char *)shmat(shmid, nullptr, 0); Log("attach shm done", Debug) << " shmid : " << shmid <<endl; sleep(10); //通信的逻辑... //4.将指定的共享内存,从自己的地址空间中 去 关联 int n = shmdt(shmaddr); assert(n!=-1); (void)n; Log("deatch shm done", Debug) << " shmid : " << shmid <<endl; sleep(10); //5. 删除共享内存, IPC_RMID即便是有进程和当下的shm挂接,依旧删除共享内存 n = shmctl(shmid, IPC_RMID, nullptr); assert( n!=-1); (void)n; Log("delete shm done", Debug) << " shmid : " <<shmid<<endl; return 0; } //makefile .PHONY:all all:shmClient shmServer shmClient:shmClient.cc g++ -o $@ $^ -std=c++11 shmServer:shmServer.cc g++ -o $@ $^ -std=c++11 .PHONY:clean clean: rm -f shmClient shmServer
管道通信的过程:由键盘->自己定义的缓冲区->进程A->write给内核缓冲区->内核缓冲区给管道文件->管道给内核缓冲区->read读到进程B处->自己定义的缓冲区->打印到屏幕。
共享内存通信:由键盘->自己定义的缓冲区->进程A->直接写入共享内存->进程B读贡献内存->到自己定义的缓冲区->屏幕。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。