【Linux】进程间通信(命名管道、共享内存、消息队列、信号量)

作者主页: 作者主页
本篇博客专栏:Linux
创作时间 :2024年11月2日


命名管道:
- 如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。
- 命名管道是一种特殊类型的文件
我们可以使用mkfifo命令来创建一个管道。

然后通过echo往里面写入一段内容:
![]()
回车之后管道不会关闭,在终端2查看可以发现他的内存大小仍然是0,当我们在管道2打印出内容后,管道就自动关闭了
当我们这样执行的时候,我们就可以发现在一直不停的打印我们输入到管道的内容
下面我们在程序里面建立一个管道:

返回值为0是成功,不为0就是失败。
下面是一个运用命名管道进行通信的例子:
Pipe.hpp:(这里是一些的共享的资源,包括路径,打开管道,关闭管道)
#include <iostream> #include <string> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> using namespace std;const string gpathname = "./myfifo"; const mode_t gmode = 0600; const int gdefault = -1; const int gsize = 1024; const int gForWrite = O_WRONLY; const int gForRead = O_RDONLY;int OpenPipe(int flag){int _fd = ::open(gpathname.c_str(), flag);if (_fd == -1){std::cerr << "open errno" << std::endl;return _fd;}return _fd;}void ClosePipeHelper(int fd){if (fd >= 0)::close(fd);}// class NamePipe // { // private:// public:// };// int CreateNamePipe(string pathname) // { // umask(0); // int n = mkfifo(pathname.c_str(), 0600); // if (n != 0) // { // std::cerr << "mkfifo errno" << std::endl; // return -1; // } // return n; // }// class NamePipe // { // public: // NamePipe() // { // } // ~NamePipe() // { // }// private: // const string fifo_path; // int _id; // int _fd; // };Server.hpp及Server.cc
#include "Pipe.hpp"class Init { public:Init(){umask(0);int n = ::mkfifo(gpathname.c_str(), gmode);if (n < 0){std::cerr << "mkfifo errno" << std::endl;return;}std::cout << "mkfifo success" << std::endl;}~Init(){int n = ::unlink(gpathname.c_str());if (n != 0){std::cerr << "unlink failed" << std::endl;}std::cout << "unlink success" << std::endl;}private: };Init init;class Server { public:Server() : _fd(gdefault){}bool OpenPipeForRead(){_fd = OpenPipe(gForRead);if (_fd < 0){std::cerr << "Open cerrno" << std::endl;return false;}return true;}int RecvPipe(std::string *out){char buffer[gsize];ssize_t n = ::read(_fd, buffer, sizeof(buffer) - 1);if (n > 0){buffer[n] = 0;*out = buffer;}return n;}void ClosePipe(){ClosePipeHelper(_fd);}~Server(){}private:int _fd; };#include "Server.hpp"int main() {Server s;// 打开管道s.OpenPipeForRead();string message;while (1){if (s.RecvPipe(&message) > 0){std::cout << "Client Say:" << message << std::endl;}else{break;}}std::cout << "Client quit,me too" << std::endl;// 关闭管道s.ClosePipe();return 0; }Client.hpp及Client.cc
#include "Pipe.hpp"class Client { public:Client() : _fd(gdefault){}bool OpenPipeForWrite(){_fd = OpenPipe(gForWrite);if (_fd < 0)return false;return true;}int SendPipe(const std::string &in){return ::write(_fd, in.c_str(), in.size());}void ClosePipe(){if (_fd >= 0){::close(_fd);}}~Client(){}private:int _fd; };#include "Client.hpp"int main() {Client c;c.OpenPipeForWrite();while (1){std::string message;std::cout << "Client Enter:" << std::endl;std::getline(std::cin, message);c.SendPipe(message);}c.ClosePipe();return 0; }
system V共享内存
system V IPC是一种本地通信方案。共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据

共享内存在系统中可以同时存在多份,供不同对进程进行通信。
共享内存不是简单的一段内存空间,它也要有描述并管理共享内存的数据结构和匹配算法。
共享内存函数
shmget 函数
该函数是系统调用,操作系统提供系统调用,让我们创建共享内存。

功能:用来创建共享内存
参数:
key:这个共享内存段名字(由用户形成)
size:共享内存大小
shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
常见标志位组合和使用:
IPC_CREAT 、IPC_EXCL
IPC_CREAT:如果你要创建的共享内存不存在,就创建。如果存在,就获取该共享内存并返回。
IPC_EXCL:单独使用没意义,只有和IPC_CREAT组合才有意义
IPC_CREAT | IPC_EXCL:如果你要创建的共享内存不存在,就创建。如果存在,就出错返回。(如果成功返回,意味着这shm是全新的)
key,用来标志共享内存,可以让进程a和b找到共享内存。系统提供了随机生成key值的方法ftok
返回值:成功则返回共享内存段的标识码;失败返回-1
ftok函数:
ftok不是系统调用,通过我们提供路径和id(这两个值可以随便写)帮我们生成一个key值。我们给a、b两个进程提供同样的路径和id,调用ftok,就能形成同样的key,就能看到同一个共享内存了。
返回值:成功了就返回key值,失败就返回-1。
共享内存的释放
共享内存不随着进程的结束而自动释放,需要我们手动释放(指令或者其他系统调用),否则会一直存在,直到系统重启。
共享内存的生命周期随内核,文件的生命周期随进程。
我们可以通过指令ipcs -m 来查看共享内存
可以通过ipsrm -m shmid(自己得到)来释放共享内存
若不想通过指令释放,可以通过下面这个函数释放
shmctl 函数

shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作(有三个可取值,如下图)
buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1
传IPC_STAT可以获取共享内存的属性,传IPC_RMID可以删除共享内存。
key和shmid的比较
key:属于用户形成,内核使用的一个字段,用户不能用key来进行shm的管理。它是内核用来进行区分shm的唯一性的。
shmid:内核给用户返回的一个标识符,用来进行用户级对共享内存进行管理的id值。
shmat 函数
功能:将共享内存段连接到进程地址空间。
如果未来不想使用该共享内存,可以用shmdt去关联
参数
shmid: 共享内存标识
shmaddr:指定共享内存想挂接到哪个地址上
shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回共享内存的起始地址;失败返回-1
shmdt 函数
功能:将共享内存段与当前进程脱离
参数
shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段
接口使用例子
Client.cc
#include "Shm.hpp" #include "namedPipe.hpp"int main() {// 1. 创建共享内存Shm shm(gpathname, gproj_id, gUser);shm.Zero();char *shmaddr = (char *)shm.Addr();sleep(3);// 2. 打开管道NamePiped fifo(comm_path, User);fifo.OpenForWrite();// 当成stringchar ch = 'A';while (ch <= 'Z'){shmaddr[ch - 'A'] = ch;std::string temp = "wakeup";std::cout << "add " << ch << " into Shm, " << "wakeup reader" << std::endl;fifo.WriteNamedPipe(temp);sleep(2);ch++;}return 0; }Server.cc
#include "Shm.hpp" #include "namedPipe.hpp"int main() {// 1. 创建共享内存Shm shm(gpathname, gproj_id, gCreater);char *shmaddr = (char*)shm.Addr();shm.DebugShm();// 2. 创建管道NamePiped fifo(comm_path, Creater);fifo.OpenForRead();while(true){std::string temp;fifo.ReadNamedPipe(&temp);std::cout << "shm memory content: " << shmaddr << std::endl;}sleep(5);return 0; }Shm.hpp
#ifndef __SHM_HPP__ #define __SHM_HPP__#include <iostream> #include <string> #include <cerrno> #include <cstdio> #include <cstring> #include <sys/ipc.h> #include <sys/shm.h> #include <unistd.h>const int gCreater = 1; const int gUser = 2; const std::string gpathname = "/home/whb/code/111/code/lesson22/4.shm"; const int gproj_id = 0x66; const int gShmSize = 4097; // 4096*nclass Shm { private:key_t GetCommKey(){key_t k = ftok(_pathname.c_str(), _proj_id);if (k < 0){perror("ftok");}return k;}int GetShmHelper(key_t key, int size, int flag){int shmid = shmget(key, size, flag);if (shmid < 0){perror("shmget");}return shmid;}std::string RoleToString(int who){if (who == gCreater)return "Creater";else if (who == gUser)return "gUser";elsereturn "None";}void *AttachShm(){if (_addrshm != nullptr)DetachShm(_addrshm);void *shmaddr = shmat(_shmid, nullptr, 0);if (shmaddr == nullptr){perror("shmat");}std::cout << "who: " << RoleToString(_who) << " attach shm..." << std::endl;return shmaddr;}void DetachShm(void *shmaddr){if (shmaddr == nullptr)return;shmdt(shmaddr);std::cout << "who: " << RoleToString(_who) << " detach shm..." << std::endl;}public:Shm(const std::string &pathname, int proj_id, int who): _pathname(pathname), _proj_id(proj_id), _who(who), _addrshm(nullptr){_key = GetCommKey();if (_who == gCreater)GetShmUseCreate();else if (_who == gUser)GetShmForUse();_addrshm = AttachShm();std::cout << "shmid: " << _shmid << std::endl;std::cout << "_key: " << ToHex(_key) << std::endl;}~Shm(){if (_who == gCreater){int res = shmctl(_shmid, IPC_RMID, nullptr);}std::cout << "shm remove done..." << std::endl;}std::string ToHex(key_t key){char buffer[128];snprintf(buffer, sizeof(buffer), "0x%x", key);return buffer;}bool GetShmUseCreate(){if (_who == gCreater){_shmid = GetShmHelper(_key, gShmSize, IPC_CREAT | IPC_EXCL | 0666);if (_shmid >= 0)return true;std::cout << "shm create done..." << std::endl;}return false;}bool GetShmForUse(){if (_who == gUser){_shmid = GetShmHelper(_key, gShmSize, IPC_CREAT | 0666);if (_shmid >= 0)return true;std::cout << "shm get done..." << std::endl;}return false;}void Zero(){if(_addrshm){memset(_addrshm, 0, gShmSize);}}void *Addr(){return _addrshm;}void DebugShm(){struct shmid_ds ds;int n = shmctl(_shmid, IPC_STAT, &ds);if(n < 0) return;std::cout << "ds.shm_perm.__key : " << ToHex(ds.shm_perm.__key) << std::endl;std::cout << "ds.shm_nattch: " << ds.shm_nattch << std::endl;}private:key_t _key;int _shmid;std::string _pathname;int _proj_id;int _who;void *_addrshm; };#endif
共享内存虽然速度上占有一定优势,但是共享内存对内存不会提供任何的保护机制,会导致数据不一致的问题,即双方不存在谁等谁的情况,比如我想传一个hello,可能我刚写入一个h就已经被读走了,这样就会导致数据不一致的问题,我们在访问共享内存时,没有任何系统调用,所以速度是所有IPC中最快的。
system V消息队列
- 消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法
- 每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值
消息队列的接口的使用跟共享内存函数很像。
如果要发消息队列的数据,用:
如果要接收数据,用:
要查消息队列就用 ipcs -q ,它的指令跟共享内存就一字之差
system V信号量
信号量主要用于同步和互斥的。
System V 信号量是由内核维护的一组整数,它可以被多个进程同时访问和修改。每个信号量代表一个资源的可用数量,进程可以通过对信号量进行操作来控制对资源的访问。信号量的操作包括增加、减少和等待信号量的值达到某个特定条件等。
信号量特点:
- 原子性操作:信号量的操作是原子性的,这意味着多个进程同时对信号量进行操作时,系统会保证操作的完整性和一致性,不会出现部分操作完成而其他操作未完成的情况。
- 可用于同步和互斥:信号量可以用于实现进程间的同步和互斥。例如,可以使用信号量来确保多个进程不会同时访问同一个共享资源,或者确保一个进程在某个条件满足之前等待另一个进程完成某个任务。
- 内核维护:信号量是由内核维护的,这意味着即使进程崩溃或退出,信号量的值也不会丢失。当进程重新启动时,可以继续使用信号量来控制对共享资源的访问。
进程互斥
- 由于各进程要求共享资源,而且有些资源需要互斥使用,因此各进程间竞争使用这些资源,进程的这种关系为进程的互斥
- 系统中某些资源一次只允许一个进程使用,称这样的资源为临界资源或互斥资源。
- 在进程中涉及到互斥资源的程序段叫临界区
多个执行流(进程),都能看到的一份资源:共享资源
被保护起来的资源:临界资源。 用互斥的方式进行保护。
互斥:任何时刻只能有一个进程在访问共享资源。
信号量的主要作用是用来保护共享资源的。经过保护,共享资源就变成临界资源。

假设上面的方格是电影院的座位。看电影时,只要有了票,位置就一定是你的,而不是谁先坐到就是谁的。 所以成功申请了信号量,即使不访问共享资源,也会留着一部分资源给你。
这里的信号量也叫多元信号量。
对共享资源的整体使用,即资源只有一个,也就是有人用了,别人就用不了了,即互斥。申请信号量时,这种信号量叫二元信号量。
信号量也是一个公共资源。
信号量本质是一个计数器,申请信号量时,计数器--,也叫P操作。释放信号量时,计数器++,也叫V操作。
信号量的操作
Linux中允许用户一次申请多个信号量,用信号量集保存,信号量集用数组来维护。
如果申请了多个信号量,上面的nsems就是申请的信号量的个数。
如果信号量不需要了,就用 semctl 。 semid就是要删除的信号量集,semnum就是要删除的信号量集的下标。
要对信号量进行PV操作,就用 semop 。
查看信号量,用 ipcs -s 。删除操作跟前面类似。
最后:
十分感谢你可以耐着性子把它读完和我可以坚持写到这里,送几句话,对你,也对我:
1.一个冷知识:
屏蔽力是一个人最顶级的能力,任何消耗你的人和事,多看一眼都是你的不对。
2.你不用变得很外向,内向挺好的,但需要你发言的时候,一定要勇敢。
正所谓:君子可内敛不可懦弱,面不公可起而论之。
3.成年人的世界,只筛选,不教育。
4.自律不是6点起床,7点准时学习,而是不管别人怎么说怎么看,你也会坚持去做,绝不打乱自己的节奏,是一种自我的恒心。
5.你开始炫耀自己,往往都是灾难的开始,就像老子在《道德经》里写到:光而不耀,静水流深。
最后如果觉得我写的还不错,请不要忘记点赞✌,收藏✌,加关注✌哦(。・ω・。)
愿我们一起加油,奔向更美好的未来,愿我们从懵懵懂懂的一枚菜鸟逐渐成为大佬。加油,为自己点赞!
相关文章:
【Linux】进程间通信(命名管道、共享内存、消息队列、信号量)
作者主页: 作者主页 本篇博客专栏:Linux 创作时间 :2024年11月2日 命名管道: 如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。命名管道是一种特殊类型的文…...
[Android]从FLAG_SECURE禁止截屏看surface
在应用中,设置activity的flag为FLAG_SECURE就可以禁止截屏,截屏出来是黑色的, 试验一下, 注意事项 影响: 设置 FLAG_SECURE 标志后,用户将无法对该Activity进行截屏或录制屏幕。这个标志会影响所有屏幕录…...
python 五子棋小游戏
1. 实现效果 Python五子棋小游戏 2. 游戏规则 规则说明,五子棋人机对战游戏规则如下: Ⅰ 默认规则 - 五子棋规则 对局双方:各执一色棋子,一方持黑色棋子,另一方持白色棋子。棋盘与开局:空棋盘开局…...
JeecgBoot集成工作流实战教程
Activiti是一个轻量级的工作流程和业务流程管理(BPM)平台,它主要面向业务人员、开发人员和系统管理员。这个平台的核心是一个快速且可靠的Java BPMN 2流程引擎。Activiti是开源的,并且基于Apache许可证进行分发。它可以运行在任何…...
第三十章 章节练习商品列表组件封装
目录 一、需求说明 二、技术要点 三、完整代码 3.1. main.js 3.2. App.vue 3.3. MyTable.vue 3.4. MyTag.vue 一、需求说明 1. my-tag 标签组件封装 (1) 双击显示输入框,输入框获取焦点 (2) 失去焦点,隐藏输入框 (3) 回显标签信息 (4) 内…...
NumPy 高级索引
NumPy 高级索引 NumPy 是 Python 中用于科学计算的核心库之一,它提供了一个强大的N维数组对象和许多用于操作这些数组的函数。在 NumPy 中,除了基本的索引和切片操作外,还提供了高级索引功能,这使得您可以以更加灵活和高效的方式访问和操作数组中的数据。本文将详细介绍 N…...
C/C++常用编译工具链:GCC,Clang
目录 GNU Compiler Collection GCC的优势 编译产生的中间文件 Clang Clang的特点 什么是LLVM? Clang编译过程中产生的中间表示文件 关于Clang的调试 C 编译工具链中有几个主要的编译工具,包括: GNU Compiler Collection (GCC…...
let和war的区别
let和war的区别 看不懂图片,可以看视频教程...
[CUDA] stream使用笔记
文章目录 1. stream一般用法2. stream与event:3. stream异常的排查4. stream的异步与同步行为 1. stream一般用法 cudaStream_t stream_; cudaStreamCreate(&stream_); // create stream // some operators running on this stream_ cudaStreamSynchronize(str…...
第二课:开发工具
在本课中,我们将介绍一些常用的C开发工具,并附上下载链接,帮助你选择合适的工具进行开发。 1. DEVC DEVC 是一个轻量级的C开发工具,适合初学者使用。它提供了基本的代码编辑、编译和调试功能。 下载链接: DEVC 下载 2. Visual…...
Vue 学习随笔系列十三 -- ElementUI 表格合并单元格
ElementUI 表格合并单元格 文章目录 ElementUI 表格合并单元格[TOC](文章目录)一、表头合并二、单元格合并1、示例代码2、示例效果 一、表头合并 参考: https://www.jianshu.com/p/2befeb356a31 二、单元格合并 1、示例代码 <template><div><el-…...
对于一个含有直流和交流分量的信号,如何使用示波器正确显示并测出直流电压值和交流电压峰峰值?
对于一个含有直流(DC)和交流(AC)分量的混合信号,使用示波器来正确显示和测量其直流电压值和交流电压峰峰值需要选择适当的设置和方法。以下是详细的步骤: 所需设备 示波器电压探头 步骤一:连…...
移动混合开发面试题及参考答案
目录 什么是混合开发(Hybrid App)? 混合开发(Hybrid App)与原生开发相比有什么优缺点? 优点 缺点 混合开发(Hybrid App)的兴起原因是什么? 市场竞争和成本控制需求 技术发展和资源整合 人才资源的考量 Web App、Native App 和混合开发(Hybrid App)的区别是…...
命令行工具开发秘籍:从零开始创建实用Python脚本(如何创建Python命令行工具)
文章目录 📖 介绍 📖🏡 演示环境 🏡📒 文章内容 📒📝 创建命令行工具的基础🔖 在非模块化的环境中🔖 在模块化环境中📝 打包和安装模块📝 使用命令行工具⚓️ 相关链接 ⚓️📖 介绍 📖 如何将自己的Python模块打包成一个可在命令行中直接执行的工具?…...
Python - PDF 分割成单页、PDF 转图片(PNG)
文章目录 PDF 分割成一页页的 PDFPDF 转 PNGPDF 分割成一页页的 PDF import fitz def split_pdf(pdf_path, save_dir):source_pdf = fitz.open(pdf_path)# 遍历source_pdf中的每一页,page_number从0开始计数 for idx...
【网络】套接字编程——TCP通信
> 作者:დ旧言~ > 座右铭:松树千年终是朽,槿花一日自为荣。 > 目标:TCP网络服务器简单模拟实现。 > 毒鸡汤:有些事情,总是不明白,所以我不会坚持。早安! > 专栏选自:…...
PyTorch实践-CNN-验证码识别
1 需求 GitHub - xhh890921/cnn-captcha-pytorch: 小黑黑讲AI,AI实战项目《验证码识别》 2 接口 含义 在optim.Adam接口中,lr参数代表学习率(Learning Rate)。学习率是优化算法中的一个关键超参数,它决定了在每次迭代…...
json和pb的比较
1.介绍 在数据序列化和通信领域,schema 指的是用于定义数据结构的模式或结构描述。它描述了数据的字段、类型、嵌套结构和约束,并在数据验证和解释上发挥重要作用。常见的 schema 格式包括 Protocol Buffers (proto)、JSON Schema、XML Schema 等。 Pr…...
Redis-基本了解
一、Redis 初识 Redis 是⼀种基于键值对(key-value)的NoSQL数据库,与很多键值对数据库不同的是,Redis 中的值可以是由string(字符串)、hash(哈希)、list(列表)…...
HarmonyOS第一课 06 构建更加丰富的页面-习题解析
判断题 1. Tabs组件可以通过接口传入一个TabsController,该TabsController可以控制Tabs组件进行页签切换。T 正确(True) 错误(False) 使用 this.tabsController.changeIndex(this.currentIndex); 可以切换页签 WebviewController提供了变更Web组件显示内容的接口…...
脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)
一、数据处理与分析实战 (一)实时滤波与参数调整 基础滤波操作 60Hz 工频滤波:勾选界面右侧 “60Hz” 复选框,可有效抑制电网干扰(适用于北美地区,欧洲用户可调整为 50Hz)。 平滑处理&…...
DockerHub与私有镜像仓库在容器化中的应用与管理
哈喽,大家好,我是左手python! Docker Hub的应用与管理 Docker Hub的基本概念与使用方法 Docker Hub是Docker官方提供的一个公共镜像仓库,用户可以在其中找到各种操作系统、软件和应用的镜像。开发者可以通过Docker Hub轻松获取所…...
QMC5883L的驱动
简介 本篇文章的代码已经上传到了github上面,开源代码 作为一个电子罗盘模块,我们可以通过I2C从中获取偏航角yaw,相对于六轴陀螺仪的yaw,qmc5883l几乎不会零飘并且成本较低。 参考资料 QMC5883L磁场传感器驱动 QMC5883L磁力计…...
如何在看板中体现优先级变化
在看板中有效体现优先级变化的关键措施包括:采用颜色或标签标识优先级、设置任务排序规则、使用独立的优先级列或泳道、结合自动化规则同步优先级变化、建立定期的优先级审查流程。其中,设置任务排序规则尤其重要,因为它让看板视觉上直观地体…...
蓝牙 BLE 扫描面试题大全(2):进阶面试题与实战演练
前文覆盖了 BLE 扫描的基础概念与经典问题蓝牙 BLE 扫描面试题大全(1):从基础到实战的深度解析-CSDN博客,但实际面试中,企业更关注候选人对复杂场景的应对能力(如多设备并发扫描、低功耗与高发现率的平衡)和前沿技术的…...
虚拟电厂发展三大趋势:市场化、技术主导、车网互联
市场化:从政策驱动到多元盈利 政策全面赋能 2025年4月,国家发改委、能源局发布《关于加快推进虚拟电厂发展的指导意见》,首次明确虚拟电厂为“独立市场主体”,提出硬性目标:2027年全国调节能力≥2000万千瓦࿰…...
【JavaSE】多线程基础学习笔记
多线程基础 -线程相关概念 程序(Program) 是为完成特定任务、用某种语言编写的一组指令的集合简单的说:就是我们写的代码 进程 进程是指运行中的程序,比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配内存…...
日常一水C
多态 言简意赅:就是一个对象面对同一事件时做出的不同反应 而之前的继承中说过,当子类和父类的函数名相同时,会隐藏父类的同名函数转而调用子类的同名函数,如果要调用父类的同名函数,那么就需要对父类进行引用&#…...
从“安全密码”到测试体系:Gitee Test 赋能关键领域软件质量保障
关键领域软件测试的"安全密码":Gitee Test如何破解行业痛点 在数字化浪潮席卷全球的今天,软件系统已成为国家关键领域的"神经中枢"。从国防军工到能源电力,从金融交易到交通管控,这些关乎国计民生的关键领域…...
Oracle11g安装包
Oracle 11g安装包 适用于windows系统,64位 下载路径 oracle 11g 安装包...












要对信号量进行PV操作,就用 semop 。