网站域名要钱吗,郴州旅游攻略,无锡企业做网站,福州建设公司名单进程间通信 进程间通信进程间通信的基本概念进程间通信的目的 管道匿名管道进程池 命名管道 system V进程间通信system V进程间通信基本概念system V共享内存共享内存和管道的对比 system V 信号量信号量同步和互斥 进程间通信
进程间通信的基本概念
进程间通信就是在不同进程… 进程间通信 进程间通信进程间通信的基本概念进程间通信的目的 管道匿名管道进程池 命名管道 system V进程间通信system V进程间通信基本概念system V共享内存共享内存和管道的对比 system V 信号量信号量同步和互斥 进程间通信
进程间通信的基本概念
进程间通信就是在不同进程之间传播或交换信息。
进程间通信的目的 数据传输一个进程需要将它的数据发送给另一个进程 资源共享多个进程之间共享同样的资源。 通知事件一个进程需要向另一个或一组进程发送消息通知它它们发生了某种事件如进程终止 时要通知父进程。 进程控制有些进程希望完全控制另一个进程的执行如Debug进程此时控制进程希望能够拦截另一个进程的所有陷入和异常并能够及时知道它的状态。 进程通信的本质 进程通信的本质就是让不同的进程看到同一个资源。 进程的通信是通过管道的只能单向通信。
管道
我们在之前用过很多次管道|现在我们在用一次用管道链接两个不同的进程。
匿名管道
匿名管道是管道的一种匿名管道是一个单向通信管道是父子进程通信的管道。 pipe函数
int pipe(int pipefd[2]);
pipe函数可以为进程创建两个端口一个读端口一个是写端口。 创建管道流程
父进程打开双通道 父进程创建子进程 父进程和子进程关闭读写端 注意 这里不能共用一个端口。读端和写端指向不同不能用读端写入写端读出。 传输简单使用 首先介绍两个系统调用 read和write ssize_t write (int fd,const void * buf,size_t count);write( ) 会把参数buf所指的内存写入count个字节到参数fd所指的文件内。当然文件读写位置也会随之移动。
#include unistd.h
ssize_t read(int fd, void *buf, size_t count);
接下来我们实际操作以一个简单的模型 注意必须严格按照fd[0]读取 fd[1]写入。否则就会出现下面这种情况。
#includeiostream
#include stdio.h
#include unistd.h
#include cstring
#include stdlib.h
#include sys/types.h
#include sys/wait.h
#include string
#includevector
using namespace std;int main()
{int fd[2]{0};
//pipe创建两个端口 其中fd[0]为读端fd[1]为写端
if(pipe(fd)0)
{perror(worring);return 1;
}pid_t idfork();
//子进程写入 父进程读出
if(id0)
{//子进程关闭读端close(fd[1]);
string mes(I am son,hello..);
int con10;
while(con)
{coutdoingendl;write(fd[0],mes.c_str(),mes.size());con--;sleep(1);
}
exit(0);
}
close(fd[0]);
char buffer[64];
string x;
while(1)
{
size_t sread(fd[1],buffer,sizeof(buffer));
if(s0)
{couti am fatherbufferendl;}
else if(s0)
{couthave doneendl;break;
}
else
{coutworringendl;break;
}
}return 0;
}错误示范 我们在接收的时候开辟的buffer也是要注意不能太小。 正确的实现代码
#includeiostream
#include stdio.h
#include unistd.h
#include cstring
#include stdlib.h
#include sys/types.h
#include sys/wait.h
#include string
#includevector
using namespace std;int main()
{int fd[2]{0};
//pipe创建两个端口 其中fd[0]为读端fd[1]为写端
if(pipe(fd)0)
{perror(worring);return 1;
}pid_t idfork();
//子进程写入 父进程读出
if(id0)
{//子进程关闭读端close(fd[0]);
string mes(I am son,hello..);
int con2000;
while(con)
{coutdoing:conendl;write(fd[1],mes.c_str(),mes.size());con--;}
exit(0);
}
close(fd[1]);
char buffer[17];
string x;
sleep(5);
while(1)
{
size_t sread(fd[0],buffer,sizeof(buffer)-1);//读的尺寸小一个可以填充\0
if(s0)
{buffer[s]\0;
couti am fatherbuffersize:sendl;}
else if(s0)
{couthave doneendl;break;
}
else
{coutworringendl;break;
}
}return 0;
}管道的特点
写端进程不写读端进程一直读那么此时会因为管道里面没有数据可读对应的读端进程会被挂起直到管道里面有数据后读端进程才会被唤醒。读端进程不读写端进程一直写那么当管道被写满后对应的写端进程会被挂起直到管道当中的数据被读端进程读取后写端进程才会被唤醒。写端进程将数据写完后将写端关闭那么读端进程将管道当中的数据读完后就会继续执行该进程之后的代码逻辑而不会被挂起。读端进程将读端关闭而写端进程还在一直向管道写入数据那么操作系统会将写端进程杀掉。
测试管道大小 子进程一直写父进程不读。则子进程会写满缓冲区。可以看到缓冲区大小是4096 4KB。 管道通信的过程 管道的4种情况
正常情况如果管道没有数据了读端必须等待直到有数据为止(写端写入数据了)正常情况如果管道被写满了写端必须等待直到有空间为止(读端读走数据)写端关闭读端一直读取, 读端会读到read返回值为0 表示读到文件结尾读端关闭写端一直写入OS会直接杀掉写端进程通过想目标进程发送SIGPIPE(13)信号终止目标进程
进程池
接下来我们实现一个进程池。
命名管道
在上面中我们理解了匿名管道匿名管道是父子进程通信的管道。然而不是父子又该如何呢。这时命名管道应运而生。命名管道实际上就是一个管道文件。他的注意事项
普通文件是很难做到通信的即便做到通信也无法解决一些安全问题。命名管道和匿名管道一样都是内存文件只不过命名管道在磁盘有一个简单的映像但这个映像的大小永远为0因为命名管道和匿名管道都不会将通信数据刷新到磁盘当中。 创建管道指令mkfifo 此时我们像管道中写入:
while : ; do echo hello fifo ; sleep 1 ; done fifo cat重定向到我们的屏幕就可以了。 创建一个命名管道 接下来我们用代码创建一个管道并且让两个不同的进程进行通信。 在程序中创建命名管道使用mkfifo函数mkfifo函数的函数原型如下
int mkfifo(const char *pathname, mode_t mode);mkfifo函数的第一个参数是pathname表示要创建的命名管道文件。
若pathname以路径的方式给出则将命名管道文件创建在pathname路径下。若pathname以文件名的方式给出则将命名管道文件默认创建在当前路径下。注意当前路径的含义 mkfifo函数的第二个参数是mode表示创建命名管道文件的默认权限。 我们首选创建一个管道
bool MakeFifo()// 创建管道
{int n mkfifo(FILENAME, 0666);if(n 0){std::cerr errno: errno , errstring: strerror(errno) std::endl;return false;}std::cout mkfifo success... read std::endl;return true;
}如果创建失败则返回false。后面则是吧管道当成一个普通文件往里面读往里面写就行了。就这么简单。
// server.cpp
#include iostream
#include cstring
#include cerrno
#include unistd.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#includecomm.h//#include comm.husing namespace std;bool MakeFifo()// 创建管道
{int n mkfifo(FILENAME, 0666);if(n 0){std::cerr errno: errno , errstring: strerror(errno) std::endl;return false;}std::cout mkfifo success... read std::endl;return true;
}
int main()
{start:
int rfd open(FILENAME, O_RDONLY);if(rfd 0){std::cerr errno: errno , errstring: strerror(errno) std::endl;if(MakeFifo()){goto start;}else{ return 2;}}std::cout open fifo success... std::endl;char buffer[1024];//rfd是管道.fifo的fdwhile(1){size_t sread(rfd,buffer,sizeof(buffer)-1);if(s0){buffer[s]0;coutclient say#bufferendl;} }close(rfd);return 0;
}接下来是写端
//client.cpp
#include iostream
#include cstring
#include cerrno
#include unistd.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#includecomm.husing namespace std;
int main()
{
int wfdopen(FILENAME,O_WRONLY);//写端口 wfd
if(wfd 0){std::cerr errno: errno , errstring: strerror(errno) std::endl;return 1;}string meassage;
while(1)
{coutplease Enter#;getline(cin,meassage);size_t swrite(wfd,meassage.c_str(),meassage.size());//往管道的文件描述符wfd写。只需要吧管道当成文件就行了。
if(s0)
{std::cerr errno: errno , errstring: strerror(errno) std::endl;break;
}}
return 0;
}接下来我们利用这一机制进行进程间通信将命令写入管道然后提取命令。再利用execlp函数进行程序替换。
if(s0){buffer[s]0;if (fork() 0){//childcoutclient say#bufferendl;execlp(buffer, buffer, NULL); //进程程序替换exit(1);}waitpid (-1, NULL, 0); //等待子进程} 我们只需要加入这几句代码让子进程帮我们进行代码置换。 命名管道和匿名管道的区别
匿名管道由pipe函数创建并打开。命名管道由mkfifo函数创建由open函数打开。FIFO命名管道与pipe匿名管道之间唯一的区别在于它们创建与打开的方式不同一旦这些工作完成之后它们具有相同的语义。 那么在命令行当中的管道“|”到底是匿名管道还是命名管道呢 匿名管道喔。他们都是bash的子进程。
system V进程间通信
system V进程间通信基本概念
管道通信本质是基于文件的也就是说操作系统并没有为此做过多的设计工作而system V IPC是操作系统特地设计的一种通信方式。但是不管怎么样它们的本质都是一样的都是在想尽办法让不同的进程看到同一份由操作系统提供的资源。 system V 进程通信三种模式
system V共享内存system V消息队列system V信号量 接下来我们将详细谈论三种模式
system V共享内存
共享内存让不同进程看到同一份资源的方式就是在物理内存当中申请一块内存空间然后将这块内存空间分别与各个进程各自的页表之间建立映射再在虚拟地址空间当中开辟空间并将虚拟地址填充到各自页表的对应位置使得虚拟地址和物理地址之间建立起对应关系至此这些进程便看到了同一份物理内存这块物理内存就叫做共享内存。 进程在通信之时在堆栈之间的共享内存开辟一块物理内存该物理内存就是通信的场所。这个场所就是快递站你把消息放快递站我把快递拿出来。 共享内存的建立与释放 共享内存的建立大致包括以下两个过程 在物理内存当中申请共享内存空间。 将申请到的共享内存挂接到地址空间即建立映射关系。 共享内存的释放大致包括以下两个过程 将共享内存与地址空间去关联即取消映射关系。 释放共享内存空间即将物理内存归还给系统 共享内存的创建 创建共享内存我们需要用shmget函数shmget函数的函数原型如下
int shmget(key_t key, size_t size, int shmflg);shmget函数的参数说明
第一个参数key表示待创建共享内存在系统当中的唯一标识。 第二个参数size表示待创建共享内存的大小。 第三个参数shmflg表示创建共享内存的方式。 shmget函数的返回值说明
shmget调用成功返回一个有效的共享内存标识符用户层标识符。 shmget调用失败返回-1。 接下来我们介绍key值key为了保证唯一性所以有了系统函数 传入shmget函数的第一个参数key需要我们使用ftok函数进行获取
ftok函数的函数原型如下
key_t ftok(const char *pathname, int proj_id);ftok函数的作用就是将一个已存在的路径名pathname和一个整数标识符proj_id转换成一个key值称为IPC键值在使用shmget函数获取共享内存时这个key值会被填充进维护共享内存的数据结构当中。 接下来我们用这两个函数产生一个共享内存接口
#includeiostream
#includestring
#includecstdlib
#includesys/ipc.h
#includesys/shm.h
#include sys/ipc.h
#include sys/shm.h#includesys/types.h
#includestring.h
using namespace std;
const string pathname/home/zsc/lesson15;
const int proj_id0x11223344;
const size_t size 4096;
key_t Getkey()
{key_t keyftok(pathname.c_str(),proj_id);if(key 0){std::cerr errno: errno , errstring: strerror(errno) std::endl;exit(1);}
coutkey:keyendl;return key;}我们将getkey封装ftok运行完毕就可以获得一个key。
#includeiostream#includecommond.hppint main()
{
//IPC_CREAT不存在就创建 存在就获取并返回
//IPC——CREAT|IPC_EXCL shm不存在就创建存在就返回错误key_t keyGetkey();int shmidshmget(key,size,IPC_CREAT|IPC_EXCL);if(shmid0){std::cerr errno: errno , errstring: strerror(errno) std::endl;return 1;}coutshmid:shmidendl;return 0;
}此时我们就创建了一个共享内存通过指令
ipcs -m此时我们就查询到了刚刚开辟的共享内存。 当我们关闭之后再次启动xshell发现共享内存依然存在 key和shmid的特点比较 key不在应用层使用只用来在内核中标识shm的唯一性 shmid应用这个共享内存是我们用shmid进行操作共享内存。 shmid共享内存的删除
ipcrm -m shmid共享内存的权限 共享内存的权限可以在shmget中加上 此时查看便给共享内存获得了权限。perms就是权限 共享内存的挂载 共享内存的挂载就是将虚拟内存映射到物理内存。 这是用到的函数shmat
char *s(char*)shmat(shmid,nullptr,0);如此我们便可以把内存挂载到系统分配的物理内存之中。 nattch是检测多少个进程链接到共享内存上。 进程结束则链接断开。 那么如何将shm从进程中移除呢
shmdt那我想彻底从OS删除怎么办呢 shmctl(shmid,IPC_RMID,nullptr);所以共享内存的程序级别操作是以下流程 接下来我们进行进程通信 我们可以看到 写完了但是另一边一直在读取。说明这种通信方式进程不是同步的。 删除线格式 共享内存特点
共享内存的生命周期是随内核的共享内存不同步通信共享内存是所有进程通信速度最快的
共享内存和管道的对比
共享内存的速度是进程通信最快的。当共享内存创建好后就不再需要调用系统接口进行通信了而管道创建好后仍需要read、write等系统接口进行通信。实际上共享内存是所有进程间通信方式中最快的一种通信方式。 我们先来看看管道通信 从这张图可以看出使用管道通信的方式将一个文件从一个进程传输到另一个进程需要进行四次拷贝操作
服务端将信息从输入文件复制到服务端的临时缓冲区中。将服务端临时缓冲区的信息复制到管道中。客户端将信息从管道复制到客户端的缓冲区中。将客户端临时缓冲区的信息复制到输出文件中。 我们再来看看共享内存通信
从这张图可以看出使用共享内存进行通信将一个文件从一个进程传输到另一个进程只需要进行两次拷贝操作
从输入文件到共享内存。从共享内存到输出文件。 所以共享内存是所有进程间通信方式中最快的一种通信方式因为该通信方式需要进行的拷贝次数最少。
system V 信号量
信号量
信号量本质是一个计数器。 目的是让多个执行流看到同一个资源。
由于进程要求共享资源而且有些资源需要互斥使用因此各进程间竞争使用这些资源进程的这种关系叫做进程互斥。系统中某些资源一次只允许一个进程使用称这样的资源为临界资源或互斥资源。 在进程中涉及到临界资源的程序段叫临界区。IPC资源必须删除否则不会自动删除因为system V IPC的生命周期随内核。
同步和互斥 特殊的信号量二元信号量只有0和1也就是互斥锁。完成互斥功能 信号量信号量表示对资源数目的计数器每一个执行流访问公共资源内的一份资源不应该让执行流直接访问而是先申请信号资源只要信号量减一成功完成了对资源的预定机制。 如果申请不成功就挂起 申请资源称为p操作释放资源称为v操作 p操作申请资源 V操作释放资源 进程互斥 进程间通信通过共享资源来实现这虽然解决了通信的问题但是也引入了新的问题那就是通信进程间共用的临界资源若是不对临界资源进行保护就可能产生各个进程从临界资源获取的数据不一致等问题。
保护临界资源的本质是保护临界区我们把进程代码中访问临界资源的代码称之为临界区信号量就是用来保护临界区的信号量分为二元信号量和多元信号量。
比如当前有一块大小为100字节的资源我们若是一25字节为一份那么该资源可以被分为4份那么此时这块资源可以由4个信号量进行标识。
system V IPC联系 通过对system V系列进程间通信的学习可以发现共享内存、消息队列以及信号量虽然它们内部的属性差别很大但是维护它们的数据结构的第一个成员确实一样的都是ipc_perm类型的成员变量。
这样设计的好处就是在操作系统内可以定义一个struct ipc_perm类型的数组此时每当我们申请一个IPC资源就在该数组当中开辟一个这样的结构。
也就是说在内核当中只需要将所有的IPC资源的ipc_perm成员组织成数组的样子然后用切片的方式获取到该IPC资源的起始地址然后就可以访问该IPC资源的每一个成员了