OS学习之路——进程间通信

张开发
2026/4/13 11:14:05 15 分钟阅读

分享文章

OS学习之路——进程间通信
OS学习之路——进程间通信前言为什么需要进程间通信什么是进程间通信进程间通信数据拷贝类型基于共享内存基于信号前言之前我们学习了进程的相关话题这次我们来学习和进程相关的一个话题——进程间通信为什么需要进程间通信假设你想从你的朋友那里获取一些信息你就会想在微信或者现实中去询问他反之亦然。你可以把你和你的朋友想象成两个进程此时你们为了获取信息就需要和对方通信。这就是一个进程间通信的例子还有在网络世界中我们从浏览器中获取信息首先需要从浏览器发请求浏览器也是一个进程服务器收到我们的请求后他会有专门的程序处理这也是一个进程。这也是一个进程间通信的例子还有许多的例子在计算机的世界中也是这样同一台机器上的一个进程可能也需要另一个进程提供的信息完成工作什么是进程间通信通过前面的文章我们知道进程实际上是计算机中正在运行程序的一个代表一个进程内部的pcb包含了这个程序的程序计数器、打开的文件、地址空间等信息。一个进程不可能自己完成所有的 工作总会有一些进程完成工作需要配合外部进程进程间通信顾名思义发生在进程间进行通信的过程那么又有哪些形式呢基于数据拷贝管道消息队列套接字基于共享内存共享内存基于信号信号信号量下面我们来逐个介绍上述内容进程间通信数据拷贝类型管道匿名管道只可以发生在有亲缘关系的进程父子进程依赖于pipe函数一个大小为2的数组1代表写端0代表读端可以使用read和write函数进行读写数据。原理pipe调用成功后内核会创建一个管道对象这里包含了缓冲区 、读写指针、等待队列等并返回两个文件描述符当前进程以及和当前进程有亲缘关系的进程都可以继承这两个描述符这时就可以使用这两个描述符通过read和write函数进行读写了匿名管道是单向的读写方式在程序编写是就固定了确定哪方读/写后就不可以改变intfd[2];pipe(fd);if(fork()0){write(fd[1],hello,5);}else{charbuf[10];read(fd[0],buf,5);}命名管道可以发生在无亲缘关系的进程可以使用mkfifo(const char *pathname, mode_t mode)函数或者使用mkfifo filename创建一个管道文件管道文件使用p进行标识原理在内核创建一个缓冲区表现形式为一个管道文件这个管道文件仍然可以使用系统调用进行openread和write但是其内部的数据并不会持久化到磁盘内但是仍然可以使用ls -i命令查询到其inode号且不能使用cat或者、查看文件或者写入内容。需要注意的是不能仅以读或者写方式打开这个文件在只有一种方式访问文件的时候在另一种方式未到来前当前操作是阻塞的读者可以打开两个终端一个使用cat命令查看文件一个使用echo hello pipefile在没有第二步前第一个终端是一直阻塞的命名管道是半双工的双方都可以担任读/写的角色但是同一时刻数据只能向一端流动套接字可以发生在任何进程可以跨主机使用socket函数创建一个套接字描述符通信双方依赖套接字处理读写事件原理数据会存储在内核的socket缓冲区当要读取数据时会从内核缓冲区拷贝到用户自定义的缓冲区当发送数据时从用户的缓冲区拷贝到内核的socket缓冲区准备发送。套接字是全双工的数据可以同时双向流动基于共享内存共享内存使用于进程间通信上面讲的几个至少要经过两次拷贝共享内存可以减少至0次也叫零拷贝。原理使用shm_open创建一个共享内存 使用mmap进行映射当需要读取或写入内容时直接对这块内存操作即可共享内存是全双工的但是因为任何知道这个共享内存存在的进程都可以读取其内容所以需要注意使用同步机制保护数据的完整性#includesys/mman.h#includefcntl.h#includeunistd.h#includestring.hintmain(){constchar*name/myshm;constsize_t SIZE4096;// 1. 创建共享内存对象读写如果存在则打开模式0644intfdshm_open(name,O_CREAT|O_RDWR,0644);// 2. 调整大小ftruncate(fd,SIZE);// 3. 映射到进程地址空间char*ptrmmap(0,SIZE,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);// 4. 写入数据strcpy(ptr,Hello from writer);// 5. 解除映射munmap(ptr,SIZE);// 6. 关闭描述符数据仍然保留在共享内存中close(fd);// 注意不要 shm_unlink否则读进程无法打开return0;}基于信号信号实际上是内核向某个进程发送了中断通知然后进程响应中断做出的答复使用signal注册信号以及信号的处理方式这个方法是异步的不需要缓冲区#includesignal.h#includeiostreamvoidhandler(intsig){std::coutreceived signal\n;}intmain(){signal(SIGUSR1,handler);while(1){}}信号量本质上是一个原子的计数器可以用来控制资源访问和实现进程间同步。当需要获取资源时自减计数器归还资源时自增计数器下面这个程序得到的结果是990001#includeiostream#includesys/semaphore.h#includethreadintmain(){volatilestd::size_t idx1;// int num 1;sem_t x;sem_init(x,0,1);std::threadth1([](){for(inti0;i1000000;i){sem_wait(x);idx;sem_post(x);}});std::threadth2([](){for(inti0;i10000;i){sem_wait(x);--idx;sem_post(x);}});th1.join();th2.join();sem_destroy(x);std::coutidxstd::endl;return0;}通过这些介绍你对进程间通信是否有了一个全新的认识如果觉得写的还不错的话欢迎点赞关注如果有不对的地方欢迎批评指正。

更多文章