您的位置: 首页 - 站长

jianshe导航网站WordPress伪静态公告404

当前位置: 首页 > news >正文

jianshe导航网站,WordPress伪静态公告404,佛山百度快照优化排名,南阳seo长尾关键词进程间通信的目的 数据传输#xff1a;一个进程许需要将它的数据发送给另外一个进程。资源共享#xff1a;多个进程之间共享同样的资源。通知事件#xff1a;一个进程需要向另一个或一组进程发送消息#xff0c;通知它们发生了某种事件#xff08;如进程终止时要通知父进程…进程间通信的目的 数据传输一个进程许需要将它的数据发送给另外一个进程。资源共享多个进程之间共享同样的资源。通知事件一个进程需要向另一个或一组进程发送消息通知它们发生了某种事件如进程终止时要通知父进程。进程控制有些进程希望完全控制另一个进程的执行如Debug进程此时控制进程希望能够拦截另一个进程的所有陷入和异常并能够及时知道它的状态改变。 进程间通信的分类 管道 匿名管道pipe命名管道FIFO Sytem V IPC System V消息队列System V共享内存System V信号量 POSIX IPC 消息队列 共享内存信号量互斥量条件变量读写锁 管道 我们把一个进程连接到另一个进程的一个数据流称为管道。 进程是具有独立性的要让进程间进行通信“成本”一定不低。要让不同进程通信首先要先让它们看到同一份资源。其次是通信。 这个公共的资源是谁提供的呢其中一个进程直接在进程内部创建资源其他进程看不到。 所以我们该如何理解进程间通信的本质问题呢 OS需要给直接或间接给通信双方进程提供“内存空间”要通信的进程必须看到同一份资源 所谓不同的通信种类本质就是上面所说的资源是OS中的哪一个模块提供的 未来学习的进程间通信的接口与其说是通信的接口不如说它是让不同的进程看到同一份资源的接口。 匿名管道 如果是一个普通文件需要将内核缓冲区里的数据刷新到磁盘中。但是进程间通信是一个进程的数据给另外一个进程是内存到内存之间的。不需要将内核缓冲区里的数据刷新到磁盘另一个进程再从磁盘中读取因为会大大降低通信的效率。 既然不需要刷新缓冲区那么OS就不需要在磁盘中创建打开文件然后在内存中创建struct file对象。OS不需要访问磁盘直接就可以在内存中创建struct file对象创建对应的缓冲区然后将对象的地址填入到文件描述符中那么再fork创建子进程时子进程会拷贝父进程的文件描述符表通过文件描述符进而父子进程就能看到同一个文件。父子进程双方就能基于这个内存级文件来进行通信了。 一般在文件里面标定一个文件使用的是文件名但是这个管道文件是一个内存级文件没有名字所以叫匿名管道。 从文件描述符角度理解管道 为什么让父进程分别以读和写的方式打开同一文件呢 如果以只读或只写方式打开文件那么子进程也会继承父进程的只读或只写方式父子进程双方打开文件的方式是一样的就完不成单向通信了。只有分别以读和写的方式打开读和写的文件描述符才会被子进程继承然后再选择对应的通信方向关闭特定的文件描述符即可。 以读和写方式打开文件的本质就是让子进程也能看到读写段端让后续能自由的选择通信方向。 必须要关闭父子进程特定的文件描述符吗例如父进程写关闭读端子进程读关闭写端。 也可以不关特定的文件描述符。但是一般都建议关掉因为这个不用的文件描述符有可能被别人用到进而就有可能修改管道数据引起程序运行出问题。 再来理解管道父进程通过调用管道特定的系统调用以读和写的方式打开一个内存级文件再通过fork创建子进程的方式被子进程继承下去再关闭对应的读写端进而形成的一条通信信道这一套通信信道是基于文件的所以叫管道。 用fork来共享管道原理 从内核角度理解管道本质 看待管道就如同看待文件一样管道的使用和文件一致迎合了“Linux下一切皆文件”的思想。 创建匿名管道 参数fd 文件描述符数组 其中 fd[0] 表示读端  fd[1] 表示写端。 返回值 成功返回 0 失败返回-1。 文件描述符fd[0]、fd[1]默认从3开始因为fd0、1、2默认被三个标准输入输出占用。 示例代码 #include iostream #include cstring #include sys/types.h #include unistd.h #include cassert #include sys/types.h #include sys/wait.h using namespace std;int main() {// 第一步创建管道文件打开读写端int fds[2];int n pipe(fds); // 成功返回0失败返回-1assert(n 0);// 第二步创建子进程pid_t id fork();assert(id 0);if (id 0){// 子进程进行写入close(fds[0]);// 子进程的通信代码int cnt 0;const char *s 我是子进程我正在给你发消息;while (true){cnt;char buffer[1024]; // 只有子进程能看到snprintf(buffer, sizeof buffer, child-parent say: %s[%d][%d], s, cnt, getpid());write(fds[1], buffer, strlen(buffer));sleep(1); // 细节子进程每隔一秒写一次}// 子进程close(fds[1]);exit(0);}// 父进程进行读取close(fds[1]);// 父进程的通信代码while (true){char buffer[1024];ssize_t s read(fds[0], buffer, sizeof(buffer) - 1); // 多留一个位置给\0if (s 0){buffer[s] 0;cout Get Message# buffer | my pid: getpid() endl;// 细节父进程没有进行sleep}}n waitpid(id, nullptr, 0);assert(n id);close(fds[0]); } 运行结果父进程每隔一秒输出一次 。 如果将子进程休眠时间改为5秒会有什么现象呢 //子进程 sleep(5); 运行结果父进程每隔5秒输出一次。 一开始子进程写入父进程读取输出。之后在子进程休眠的5秒内父进程在干什么呢 我们将代码改造一下 // 父进程的通信代码while (true){char buffer[1024];coutAAAAAAAAAAAAAAendl;ssize_t s read(fds[0], buffer, sizeof(buffer) - 1); coutBBBBBBBBBBBBBBendl;if (s 0){buffer[s] 0;cout Get Message# buffer | my pid: getpid() endl;}} 可以看到父进程在read这里阻塞了。 read这里就涉及了两个功能 等缓冲区里有数据。将数据从内核拷贝到用户层。 如果此时缓冲区里没有数据父进程就会一直阻塞等待。OS将父进程从运行状态R改为阻塞状态S放在等待队列中。等待的不就是文件吗等管道文件里有数据所以文件里也一定存在类似等待队列这样的结构将进程的PCB放入这个文件对应的等待队列中。当写了之后缓冲区有数据了OS识别到再将进程的PCB从等待队列拿到运行队列将进程状态由S改为R就可以继续被调度了。 总结如果管道中没有了数据读端再读默认会直接阻塞当前正在读取的进程 管道是一个固定大小的缓冲区。如果反过来缓冲区写满了之后写端继续写呢 将代码改造一下 //子进程不休眠//… while (true){cnt;char buffer[1024]; snprintf(buffer, sizeof buffer, child-parent say: %s[%d][%d], s, cnt, getpid());write(fds[1], buffer, strlen(buffer));cout count: cnt endl;}//…//父进程休眠1000秒//…while (true){sleep(1000);char buffer[1024];ssize_t s read(fds[0], buffer, sizeof(buffer) - 1); if (s 0){buffer[s] 0;cout Get Message# buffer | my pid: getpid() endl;}}//… 一瞬间就写满了不再继续写了。 可以看到子进程一瞬间就将缓冲区写满了不再继续写了。 总结如果管道满了之后写端再写会发生阻塞等待等待读端读取。 再将代码改造一下 //子进程不休眠//… while (true){cnt;char buffer[1024]; snprintf(buffer, sizeof buffer, child-parent say: %s[%d][%d], s, cnt, getpid());write(fds[1], buffer, strlen(buffer));cout count: cnt endl;}//…//父进程休眠2秒//…while (true){sleep(2);char buffer[1024];ssize_t s read(fds[0], buffer, sizeof(buffer) - 1); if (s 0){buffer[s] 0;cout Get Message# buffer | my pid: getpid() endl;}}//… 总结父进程读取并不是一行一行读取的而是按照指定大小读取的也就是说缓冲区里有指定字节大小的数据一次就会全部读完。 ssize_t s read(fds[0], buffer, sizeof(buffer) - 1);
如果子进程写了一次之后就将对应的写端描述符关闭呢 // …// 子进程// …while (true){cnt;char buffer[1024]; snprintf(buffer, sizeof buffer, child-parent say: %s[%d][%d], s, cnt, getpid());cout count: cnt endl;write(fds[1], buffer, strlen(buffer));break;}close(fds[1]);cout 子进程关闭写端 endl;exit(0);}// …// 父进程// …while (true){sleep(2);char buffer[1024];ssize_t s read(fds[0], buffer, sizeof(buffer) - 1); if (s 0){buffer[s] 0;cout Get Message# buffer | my pid: getpid() endl;}else if (s 0){cout read: s endl;break;}}n waitpid(id, nullptr, 0);assert(n id);close(fds[0]); 父进程将管道数据读完之后写端文件描述符也关闭了那么就意味着已经完成了管道的读写读端read读到文件末尾返回0。 如果关闭读端写端继续写呢 // …// 子进程// …while (true){cnt;char buffer[1024]; snprintf(buffer, sizeof buffer, child-parent say: %s[%d][%d], s, cnt, getpid());cout count: cnt endl;write(fds[1], buffer, strlen(buffer));}close(fds[1]);cout 子进程关闭写端 endl;exit(0);}// …// 父进程// …while (true){sleep(2);char buffer[1024];ssize_t s read(fds[0], buffer, sizeof(buffer) - 1); if (s 0){buffer[s] 0;cout Get Message# buffer | my pid: getpid() endl;}else if (s 0){cout read: s endl;break;}break;}close(fds[0]);cout 父进程关闭读端 endl;int status 0;n waitpid(id, status, 0);cout pid- n : (status 0x7F) endl;assert(n id);如果读端被关闭写就没有意义了没有意义操作系统会杀掉写的子进程是通过发送信号的方式被杀掉也就相当于子进程异常退出了。一旦父进程关闭读端子进程会立马退出父进程waipid就能获取到子进程的退出码。 OS会给子进程直接发送13号信号来终止写进程。 读写特征 读快写慢读阻塞等待写入。读慢写快写阻塞等待读取。写关闭读到0。读关闭终止写。 这四种读写特征分别对应了上述各种现象。 管道的特征 管道的生命周期随进程进程退出管道释放。只能用于具有共同祖先具有血缘关系的进程之间进行通信通常一个管道由一个进程创建然后该进程调用fork此后父子进程之间就可以应用管道。常用于父子通信。管道是面向字节流的。内核会对管道操作进行同步与互斥对共享资源进行保护的方案。管道是半双工的数据只能向一个方向流动需要双方进行通信时需要建立两个管道。 命名管道 匿名管道应用的一个限制就是只能在具有血缘关系的进程之间进行通信如果我们想在不相关的进程之间交换数据可以使用FIFO文件来完成被称为命名管道命名管道是一种特殊类型的文件。 命名管道可以从命令行上创建 \(mkfifo filename 命名管道是如何让不同的进程看到同一份资源的呢 命名管道也可以通过函数创建 创建 成功返回0失败返回-1。 删除 成功返回0失败返回-1。 命名管道与匿名管道的区别 匿名管道由pipe函数创建并打开。 命名管道由mkfififo函数创建打开用open FIFO命名管道与pipe匿名管道之间唯一的区别在它们创建与打开的方式不同一但这些工作完 成之后它们具有相同的语义。 匿名管道是通过文件描述符来让具有血缘关系的进程进行通信的。 命名管道是通过文件名来让不同的进程使用同一个管道通信的。 clientserver通信 示例代码 Makefile .PHONY:all all:server clientserver:server.ccg -o \) \(^ -stdc11 -gclient:client.ccg -o \) $^ -stdc11 -g.PHONY:clean clean:rm -f server client comm.hpp  #pragma once#include iostream #include string #include cstring #include cerrno #include cassert #include unistd.h #include sys/types.h #include sys/stat.h#define NAMED_PIPE /tmp/mypipebool createFifo(const std::string path) {umask(0);int n mkfifo(path.c_str(), 0600);if (n 0)return true;else{std::cout errno: errno err string: strerror(errno) std::endl;return false;} }void removeFifo(const std::string path) {int n unlink(path.c_str());assert(n 0);//assert只在Debug下有效在release下就没有了。(void)n; } server.cc  #include comm.hppint main() {bool r createFifo(NAMED_PIPE);assert®;(void)r;//removeFifo(NAMED_PIPE);return 0; } 此时就在tmp路径下创建了名为“mypipe”的管道。 下面来进行server和client的通信。通信的过程就是对文件的读写读写操作。 client.cc int main() {int wfd open(NAMED_PIPE,O_WRONLY);if(wfd0) exit(1);char buffer[1024];while(true){std::coutPlease say# ;fgets(buffer,sizeof buffer,stdin);ssize_t s write(wfd,buffer,strlen(buffer));assert(sstrlen(buffer));(void)s;}close(wfd);return 0; } server.cc int main() {bool r createFifo(NAMED_PIPE);assert®;(void)r;std::coutserver beginstd::endl;int rfd open(NAMED_PIPE, O_RDONLY);std::coutserver endstd::endl;if(rfd 0) exit(1);//readchar buffer[1024];while(true){ssize_t s read(rfd, buffer, sizeof(buffer)-1);if(s 0){buffer[s] 0;std::cout client-server# buffer std::endl;}//如果读端关闭写端读到0else if(s 0){std::cout client quit, me too! std::endl;break;}//读取错误else{std::cout err string: strerror(errno) std::endl;break;}}close(rfd);// sleep(10);removeFifo(NAMED_PIPE);return 0; } 这样就完成了客户端client和服务端server两个进程间的通信。 为什么服务端读取的时候会多一行空行呢 原因是我们从键盘输入的时候会摁回车键例如输入“hello world”。实际fgets读取到的为“hello world \n”。“\n”也会被读取到所以会被多打印一行空行。 再对代码做一下优化。 client.cc int main() {std::coutclient beginstd::endl;int wfd open(NAMED_PIPE,O_WRONLY);std::coutclient endstd::endl;if(wfd0) exit(1);char buffer[1024];while(true){std::coutPlease say# ;fgets(buffer,sizeof buffer,stdin);if(strlen(buffer)0) buffer[strlen(buffer)-1] 0;ssize_t s write(wfd,buffer,strlen(buffer));assert(sstrlen(buffer));(void)s;}close(wfd);return 0; } server.cc int main() {bool r createFifo(NAMED_PIPE);assert®;(void)r;std::coutserver beginstd::endl;int rfd open(NAMED_PIPE, O_RDONLY);std::coutserver endstd::endl;if(rfd 0) exit(1);//readchar buffer[1024];while(true){ssize_t s read(rfd, buffer, sizeof(buffer)-1);if(s 0){buffer[s] 0;std::cout client-server# buffer std::endl;}//如果读端关闭写端读到0else if(s 0){std::cout client quit, me too! std::endl;break;}//读取错误else{std::cout err string: strerror(errno) std::endl;break;}}close(rfd);// sleep(10);removeFifo(NAMED_PIPE);return 0; }当读端先执行时会在open阻塞。 当写端再执行时读端才继续调度执行。 总结只有当两个进程同时打开文件程序才能继续向后运行。