目录
作者和朋友建立的社区:非科班转码社区-CSDN社区云💖💛💙
期待hxd的支持哈🎉 🎉 🎉
最后是打鸡血环节:想多了都是问题,做多了都是答案🚀 🚀 🚀
最近作者和好友建立了一个公众号
公众号介绍:
专注于自学编程领域。由USTC、WHU、SDU等高校学生、ACM竞赛选手、CSDN万粉博主、双非上岸BAT学长原创。分享业内资讯、硬核原创资源、职业规划等,和大家一起努力、成长。(二维码在文章底部哈!)
数据传输:一个进程需要将它的数据发送给另一个进程。资源共享:多个进程之间共享同样的资源。通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。进程控制:有些进程希望完全控制另一个进程的执行(如 Debug 进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
管道System V 进程间通信POSIX 进程间通信本文主要讲述管道(System V 后面会讲,POSIX因为太老就不讲了)
管道
匿名管道 pipe命名管道System V IPCSystem V 消息队列System V 共享内存System V 信号量POSIX IPC消息队列共享内存信号量互斥量条件变量读写锁
什么是管道
管道是 Unix 中最古老的进程间通信的形式。我们把从一个进程连接到另一个进程的一个数据流称为一个 “ 管道”(网图)(这个 | 就是匿名管道,把who的输出 输入到 wc -l 进程 。同时我们也可以知道,管道是在 内核里面的)
管道的原理用fork来共享管道原理
站在文件描述符角度-深度理解管道
所以管道也是内存级文件
而且传输也是单向的
看看源码,这个里面就可以知道这个文件是管道文件还是磁盘文件还是字符设备
如果是管道文件就可以看到所以,看待管道,就如同看待文件一样!管道的使用和文件一致,迎合了“Linux一切皆文件思想”。
匿名管道
#include 功能 : 创建一无名管道原型int pipe(int fd[2]);参数fd :文件描述符数组 , 其中 fd[0] 表示读端 , fd[1] 表示写端返回值 : 成功返回 0 ,失败返回错误代码记忆技巧:
0 - 》 嘴巴 -》 读
1 -》 笔 -》写匿名管道代码
#include #include #include #include #include #include #include #include #include #include #include using namespace std; //演示pipe通信的基本过程 -- 匿名管道 int main() { // 1. 创建管道 int pipefd[2] = {0}; if(pipe(pipefd) != 0) { cerr << "pipe error" << endl; return 1; } // 2. 创建子进程 pid_t id = fork(); if(id < 0) { cerr << "fork error" << endl; return 2; } else if (id == 0) { // child // 子进程来进行读取, 子进程就应该关掉写端 close(pipefd[1]); #define NUM 1024 char buffer[NUM]; while(true) { cout<<"进入了子进程"< cout << "时间戳: " << (uint64_t)time(nullptr) << endl; // 子进程没有带sleep,为什么子进程你也会休眠呢?? memset(buffer, 0, sizeof(buffer)); //sleep(100); ssize_t s = read(pipefd[0], buffer, sizeof(buffer) - 1); if(s > 0) { //读取成功 buffer[s] = '\0'; cout << "子进程收到消息,内容是: " << buffer << endl; } else if(s == 0) { cout << "父进程写完了,我也退出啦" << endl; break; } else{ //Do Nothing } } close(pipefd[0]); exit(0); } else { // parent // 父进程来进行写入,就应该关掉读端 close(pipefd[0]); const char *msg = "你好子进程,我是父进程, 这次发送的信息编号是: "; int cnt = 0; // while(cnt < 5) while(cnt<5) { char sendBuffer[1024]; sprintf(sendBuffer, "%s : %d", msg, cnt); //sleep(30); // 这里是为了一会看现象明显 sleep(1); write(pipefd[1], sendBuffer, strlen(sendBuffer)); //要不要+1 1,0 cnt++; cout << "cnt: " << cnt << endl; } close(pipefd[1]); cout << "父进程写完了" << endl; } pid_t res = waitpid(id, nullptr, 0); if(res > 0) { cout << "等待子进程成功" << endl; } // 0 -> 嘴巴 -> 读(读书) // 1 -> 笔 -> 写的 // cout << "fd[0]: " << pipefd[0] << endl; // cout << "fd[1]: " << pipefd[1] << endl; return 0; }运行结果
但是我们运行的时候会大学子进程没有带sleep但是子进程也会随着父进程的休眠而休眠,是因为父进程和子进程读写是有顺序的!管道内部,没有数据,reader就必须阻塞等待(等待就是讲当前进程的PCB放入到等待队列中)(read),管道内部,如果数据被写满,writer就必须阻塞等待(write)。所以说,pipe内部自带访问控制机制-》同步和互斥机制(后面详细讲!)
管道读写规则
当没有数据可读时O_NONBLOCK disable: read 调用阻塞,即进程暂停执行,一直等到有数据来到为止。O_NONBLOCK enable: read 调用返回 -1 , errno 值为 EAGAIN 。当管道满的时候O_NONBLOCK disable: write 调用阻塞,直到有进程读走数据。O_NONBLOCK enable:调用返回 -1 , errno 值为 EAGAIN。如果所有管道写端对应的文件描述符被关闭,则 read 返回 0。如果所有管道读端对应的文件描述符被关闭,则 write 操作会产生信号 SIGPIPE, 进而可能导致 write 进程退出。当要写入的数据量不大于 PIPE_BUF 时, linux 将保证写入的原子性。当要写入的数据量大于 PIPE_BUF 时, linux 将不再保证写入的原子性。管道特点(是管道并不止是匿名管道)
- 只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道。
- 管道提供流式服务
- 一般而言,进程退出,管道释放,所以管道的生命周期随进程
- 一般而言,内核会对管道操作进行同步与互斥
- 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道
- 管道的生命周期
PS:两个人在交互的时候一个人说一个人听就是半双工,就是一下只要一个进行读写
命名管道
创建一个命名管道
命名管道可以从命令行上创建,命令行方法是使用下面这个命令:$ mkfifo filename命名管道也可以从程序里创建,相关函数有:int mkfifo(const char *filename,mode_t mode);创建命名管道:
int main(int argc, char *argv[]) { mkfifo("p2", 0644); return 0; }命名管道的代码
效果图
代码(进程池)
makefile
.PHONY:all all: clientFifo serverFifo clientFifo:clientFifo.cpp g++ -Wall -o $@ $^ -std=c++11 serverFifo:serverFifo.cpp g++ -Wall -o $@ $^ -std=c++11 .PHONY:clean clean: rm -rf clientFifo serverFifo .fifocomm.h
#pragma once #include #include #include #include #include #include #include #include #include #define IPC_PATH "./.fifo"clientFifo.cpp
//写入 #include "comm.h" using namespace std; int main() { int pipeFd = open(IPC_PATH, O_WRONLY); if(pipeFd < 0) { cerr << "open: " << strerror(errno) << endl; return 1; } #define NUM 1024 char line[NUM]; while(true) { printf("请输入你的消息# "); fflush(stdout); memset(line, 0, sizeof(line)); // fgets -> C -> line结尾自动添加\0 if(fgets(line, sizeof(line), stdin) != nullptr) { //abcd\n\0 line[strlen(line) - 1] = '\0'; write(pipeFd, line, strlen(line)); } else { break; } } close(pipeFd); cout << "客户端退出啦" << endl; return 0; }serverFifo.cpp
//读取 #include "comm.h" using namespace std; int main() { umask(0); if(mkfifo(IPC_PATH, 0600) != 0) { cerr << "mkfifo error" << endl; return 1; } int pipeFd = open(IPC_PATH, O_RDONLY); if(pipeFd < 0) { cerr << "open fifo error" << endl; return 2; } #define NUM 1024 //正常的通信过程 char buffer[NUM]; while(true) { ssize_t s = read(pipeFd, buffer, sizeof(buffer)-1); if(s > 0) { buffer[s] = '\0'; cout << "客户端->服务器# " << buffer << endl; } else if(s == 0) { cout << "客户退出啦,我也退出把"; break; } else { //do nothing cout << "read: " << strerror(errno) << endl; break; } } close(pipeFd); cout << "服务端退出啦" << endl; unlink(IPC_PATH); return 0; }命名管道的打开规则
如果当前打开操作是为读而打开 FIFO 时O_NONBLOCK disable:阻塞直到有相应进程为写而打开该 FIFOO_NONBLOCK enable:立刻返回成功如果当前打开操作是为写而打开 FIFO 时O_NONBLOCK disable:阻塞直到有相应进程为读而打开该 FIFOO_NONBLOCK enable:立刻返回失败,错误码为 ENXIOsystem V共享内存
共享内存区是最快的 IPC 形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。共享内存示意图
就是内存中的一块 空间,通过页表分别映射到不同的进程中,使多个进程看到同一份资源,而且不需要涉及内核,所以还很快。
共享内存函数
shmget函数
功能:用来创建共享内存原型int shmget(key_t key, size_t size, int shmflg);参数key: 这个共享内存段名字,size: 共享内存大小(建议设置成4KB的整数倍(操作系统是以4KB来分配空间的))shmflg: 由九个权限标志构成,它们的用法和创建文件时使用的 mode 模式标志是一样的关于shmflg(重要):里面有两个宏,一个是IPC_CREAT一个是IPC_EXCLIPC_CREAT:创建共享内存,如果已经存在,就获取之,不存在,就创建。IPC_EXCL:不单独使用,必须和IPC_CREAT配合,如果不存在指定的共享内存,就创建,如果存在,出错返回。一起使用(使用 | ) ---》 可以保证如果shmget函数调用成功,那么就一定是一个全新的share memory!返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1。那么共享内存存在哪里?and 我怎么知道他存不存在?1. 内核中,内核会给我们维护共享内存的结构!共享内存也要被管理起来(先描述,后组织)2. 我怎么知道他存不存在。那就一定是有方法去标识共享内存的唯一性!共享内存要被管理 -> struct shmid_ds{} -> struct ipc_perm ->key(shmget)( 共享内存的唯一值)。而这个 key 一般是由 用户提供的!(就像前面命名管道约定一定用同一个管道文件一样,这里让他们使用同一个key就可以了!)这个里面的key就是上面shmget要传的key
PS:创建共享内存时prem(权限)可以直接 |
ftok函数
ftok就是生成一个为一值,保证key的唯一性
就是传一个路径和一个整形,然后就可以通过算法去转化生成一个有唯一性的整形(key_t本质就是整形,可以自己去看哈)
但是如果有使用过的小伙伴就会发现问题
当我们运行完毕创建新的共享内存的代码后(进程退出了),但是当第二(n)次再去运行的时候,就会报错,告诉我们file是存在的即共享内存是存在的!那是因为 system V 下的共享内存,生命周期是随内核的!如果不显示去删除,那么只能通过kernel(OS)重启来解决了!
那问题又来了,我们怎么知道有哪些IPC资源呢?
ipcs -m(查看共享内存)/-q(消息队列)/-s(看信号量)
如何去删除这些
1. 命令删除共享内存 ipcrm -m shmid(q/s)
2. 代码删除 chmctl(系统命令)(共享内存控制)(不止可以删除)
(你怎么使用的malloc的空间,就怎么使用共享内存的空间(也是一样的先申请,然后得到返回的指针,然后直接使用即可))
关于共享内存的更多细节
进程间通信的前提是先让不同的进程看到同一份资源,我们把共享内存实际上是映射到了我们进程地址空间的用户空间( 堆-》栈 之间),对于每一个进程而言,挂接到自己的上下文中的共享内存,属于自己的空间,类似于堆空间或栈空间,可以直接被用户使用。
共享内存,因为他自身的特性,他没有任何访问控制,共享内存被双方之间看到,属于双方的用户空间。
shmctl 函数
shmat(a -- attach)关联共享内存
功能:将共享内存段连接到进程地址空间原型void *shmat(int shmid, const void *shmaddr, int shmflg);参数shmid: 共享内存标识shmaddr: 指定连接的地址shmflg: 它的两个可能取值是 SHM_RND 和 SHM_RDONLY返回值:成功返回一个指针,指向共享内存第一个节;失败返回 -1说明:shmaddr 为 NULL ,核心自动选择一个地址shmaddr 不为 NULL 且 shmflg 无 SHM_RND 标记,则以 shmaddr 为连接地址。shmaddr 不为 NULL 且 shmflg 设置了 SHM_RND 标记,则连接的地址会自动向下调整为 SHMLBA 的整数倍。公式: shmaddr -(shmaddr % SHMLBA)shmflg=SHM_RDONLY ,表示连接操作用来只读共享内存使用第 2 3 个参数直接默认就可以了shmdt函数
功能:将共享内存段与当前进程脱离原型int shmdt(const void *shmaddr);参数shmaddr: 由 shmat 所返回的指针返回值:成功返回 0 ;失败返回 -1注意:将共享内存段与当前进程脱离不等于删除共享内存段shmdt 的参数就是 shmat 的返回值shmctl函数
功能:用于控制共享内存原型int shmctl(int shmid, int cmd, struct shmid_ds *buf);参数shmid: 由 shmget 返回的共享内存标识码cmd: 将要采取的动作(有三个可取值)buf: 指向一个保存着共享内存的模式状态和访问权限的数据结构返回值:成功返回 0 ;失败返回 -1
使用
Comm.hpp
#pragma once #include #include #include #include #include #include #include #define PATH_NAME "/home/lml/linux" #define PROJ_ID 0x14 #define MEM_SIZE 4096 key_t CreateKey() { key_t key = ftok(PATH_NAME, PROJ_ID); if(key < 0) { std::cerr <<"ftok: "<< strerror(errno) << std::endl; exit(1); } return key; }log.hpp
#pragma once #include #include std::ostream &Log() { std::cout << "Fot Debug |" << " timestamp: " << (uint64_t)time(nullptr) << " | "; return std::cout; }Makefile
.PHONY:all all: IpcShmCli IpcShmSer IpcShmCli:IpcShmCli.cc g++ -o $@ $^ -std=c++11 IpcShmSer:IpcShmSer.cc g++ -o $@ $^ -std=c++11 .PHONY:clean clean: rm -f IpcShmCli IpcShmSerIpcShmSer.cc
#include "Comm.hpp" #include "Log.hpp" #include using namespace std; // 我想创建全新的共享内存 const int flags = IPC_CREAT | IPC_EXCL; // 充当使用共享内存的角色 int main() { key_t key = CreateKey(); Log() << "key: " << key << "\n"; Log() << "create share memory begin\n"; int shmid = shmget(key, MEM_SIZE, flags | 0666); if (shmid < 0) { Log() << "shmget: " << strerror(errno) << "\n"; return 2; } Log() << "create shm success, shmid: " << shmid << "\n"; sleep(5); // 1. 将共享内存和自己的进程产生关联attach char *str = (char *)shmat(shmid, nullptr, 0); Log() << "attach shm : " << shmid << " success\n"; sleep(5); // 用它(提供数据给Cli去使用) str[0]='1'; str[1]='2'; str[2]='3'; str[3]='4'; str[4]='\0'; int cnt=10; while(cnt--) { sleep(1); } // 2. 去关联 shmdt(str); Log() << "detach shm : " << shmid << " success\n"; sleep(5); // 删它 shmctl(shmid, IPC_RMID, nullptr); Log() << "delete shm : " << shmid << " success\n"; sleep(5); return 0; }IpcShmCli.cc
#include "Comm.hpp" #include "Log.hpp" #include using namespace std; // 充当使用共享内存的角色 int main() { // 创建相同的key值 key_t key = CreateKey(); Log() << "key: " << key << "\n"; // 获取共享内存 int shmid = shmget(key, MEM_SIZE, IPC_CREAT); if (shmid < 0) { Log() << "shmget: " << strerror(errno) << "\n"; return 2; } // 挂接 char *str = (char*)shmat(shmid, nullptr, 0); // 用它 //sleep(5); int cnt=5; while(cnt--) { //使用Ser提供的数据 cout< sleep(1); } // 去关联 shmdt(str); return 0; }结果
分析
这里可以看出三种情况,当Ser启动之后
第一种情况就是Ser还没有提供数据,Cli打印没有东西
第二种情况就是提供了数据,然后Cli可以一直拿到并打印(数据不会因为你拿了就消失或者改变)
第三种情况就是 Ser delete之后,Cli再去拿数据会发生段错误!
PS:
创建共享内存时prem(权限)可以直接 |
nattch -- 挂接共享内存
shmat(a -- attach)关联共享内存
shmdt 去关联共享内存上面的使用也可以再改一下
client 去写
server端
现象
分析(重要):
但是我们发现就算client端没有写的时候,server端也在打印,也就是说没有访问控制!属于双方的内存空间,但是不安全(与管道相比较)
对于共享内存是非常快的:
管道:先是外设写入到进程,然后进程写到内核的管道,再管道再到进程,进程再输出。(四次拷贝)
共享内存:是外设直接写入到进程的时候,其实就是写入到了共享内存,然后另一个进程之间就能看到,之间输出就可以了。所以说共享内存快!(两次拷贝)进程互斥
由于各进程要求共享资源,而且有些资源需要互斥使用,因此各进程间竞争使用这些资源,进程的这种关系为进程的互斥。系统中某些资源一次只允许一个进程使用,称这样的资源为临界资源或互斥资源。在进程中涉及到互斥资源的程序段叫临界区。特性方面IPC 资源必须删除,否则不会自动清除,除非重启,所以 system V IPC 资源的生命周期随内核。最后的最后,创作不易,希望读者三连支持💖
赠人玫瑰,手有余香💖
- 相关阅读:
Word控件Spire.Doc 【文本】教程(19) ;如何在 C#、VB.NET 中通过 Word 中的正则表达式查找和替换文本
ubuntu 配置 vino-server
Jmeter实现在请求param和body里面加入随机参数
【微信小程序】6天精准入门(第1天:小程序入门)
看我如何连夜自建网站背刺我的求职对手们
如何写好一篇学术论文
Nuxt 3组件开发与管理
activiti命令模式与责任链模式
关于Ajax的深入学习
软件测试人员如何提升自己?写给职场中迷茫的你。
- 原文地址:https://blog.csdn.net/weixin_62700590/article/details/127739719