小米公司网站前建设分析,wordpress 产品参数,个人网页制作成品源代码,廊坊安次区网站建设公司目录 一.IPC机制介绍二.匿名与命名管道1.匿名管道2.命名管道3.日志 三.共享内存三.System V 标准1.System V简介2.IPC在内核的数据结构设计3.信号量 一.IPC机制介绍 IPC#xff08;Inter-Process Communication#xff0c;进程间通信#xff09;是计算机系统中不同进程之间交… 目录 一.IPC机制介绍二.匿名与命名管道1.匿名管道2.命名管道3.日志 三.共享内存三.System V 标准1.System V简介2.IPC在内核的数据结构设计3.信号量 一.IPC机制介绍 IPCInter-Process Communication进程间通信是计算机系统中不同进程之间交换数据和同步操作的机制。由于现代计算机系统中程序通常会由多个进程组成这些进程可能需要相互通信以完成任务因此IPC非常重要。 IPC主要功能
数据交换允许不同进程共享数据或传递信息。同步协调多个进程之间的操作以避免竞态条件和资源冲突。互斥控制多个进程对共享资源的访问确保同一时间只有一个进程能够访问资源
IPC主要机制
管道Pipes匿名管道用于相关进程之间的单向通信如父子进程或兄弟进程。 命名管道FIFO用于不相关进程之间的双向或单向通信具有一个路径名可以在不同的进程之间共享。消息队列Message Queues允许进程以消息的形式发送和接收数据消息可以按照队列的顺序进行传递。共享内存Shared Memory允许多个进程映射同一块物理内存区域从而实现高速的数据共享和通信。信号量Semaphores用于实现进程间的同步和互斥以控制对共享资源的访问。套接字Sockets尽管最常用于网络通信套接字也可以用于同一台计算机上的进程间通信。
二.匿名与命名管道
1.匿名管道
管道是进程间通信的一种机制通常用于将一个进程的输出数据传递给另一个进程的输入。管道可以分为两种主要类型匿名管道和命名管道。 管道原理简易演示图 上图所示我们就将通信信道建立好了。那么我们具体应该如何编码实现呢 pipe函数介绍 pipe()函数创建一个管道该管道提供了一对文件描述符一个用于读操作另一个用于写操作。数据从写端流向读端。 返回值
成功返回0。失败返回-1并将errno设置为错误代码。
下面我们写一段代码用匿名管道简单实现父子进程的通信
#include iostream // 包含输入输出流库
#include cstdio // 包含标准输入输出库
#include string // 包含C字符串库
#include cstring // 包含C字符串处理库
#include cstdlib // 包含C标准库
#include unistd.h // 包含POSIX操作系统API
#include sys/types.h // 包含数据类型定义
#include sys/wait.h // 包含进程等待函数#define N 2 // 定义常量N为2
#define NUM 1024 // 定义常量NUM为1024表示缓冲区大小using namespace std; // 使用标准命名空间// Writer函数向管道写数据
void Writer(int wfd)
{string s i am father; // 定义字符串spid_t self getpid(); // 获取当前进程IDint number 0; // 定义一个计数器number初始值为0char buffer[NUM]; // 定义缓冲区bufferwhile (true) // 无限循环{sleep(1); // 休眠1秒buffer[0] 0; // 将缓冲区第一个位置置为0snprintf(buffer, sizeof(buffer), %s-%d-%d, s.c_str(), self, number); // 格式化字符串并存入缓冲区cout buffer endl; // 输出缓冲区内容write(wfd, buffer, strlen(buffer)); // 将缓冲区内容写入管道if(number5) // 如果计数器number大于等于5退出循环break;}
}// Reader函数从管道读数据
void Reader(int rfd)
{char buffer[NUM]; // 定义缓冲区bufferwhile(true) // 无限循环{buffer[0] 0; // 将缓冲区第一个位置置为0ssize_t n read(rfd, buffer, sizeof(buffer)); // 从管道读取数据存入缓冲区if(n 0) // 如果读取到的数据长度大于0{buffer[n] 0; // 将缓冲区第n个位置置为0表示字符串结束cout child get a message[ getpid() ]# buffer endl; // 输出读取到的内容}else if(n 0) // 如果读取到的数据长度为0表示管道已关闭{printf(child read file done!\n); // 输出读取完成信息break; // 退出循环}else break; // 如果读取错误退出循环}
}int main()
{int pipefd[N] {0}; // 定义一个数组pipefd用于存放管道的文件描述符int n pipe(pipefd); // 创建管道返回值n小于0表示创建失败if (n 0)return 1; // 如果创建管道失败返回1pid_t id fork(); // 创建子进程返回值id小于0表示创建失败if (id 0)return 2; // 如果创建子进程失败返回2if (id 0){// 子进程代码close(pipefd[1]); // 关闭管道的写端// IPC代码Reader(pipefd[0]); // 调用Reader函数从管道读取数据close(pipefd[0]); // 关闭管道的读端exit(0); // 退出子进程}// 父进程代码close(pipefd[0]); // 关闭管道的读端// IPC代码Writer(pipefd[1]); // 调用Writer函数向管道写入数据close(pipefd[1]); // 关闭管道的写端pid_t rid waitpid(id, nullptr, 0); // 等待子进程结束if(rid 0) return 3; // 如果等待失败返回3sleep(5); // 休眠5秒return 0; // 返回0表示程序成功结束
} 运行起来后我们可以观察到父进程每隔一秒格式化字符串输入缓冲区并打印缓冲区内容接着写进管道这时子进程就能读到管道的数据并将内容打印出来父进程写五次后退出并且关闭了写端这是读端读到结尾后退出进程后被父进程等待回收。 我们再来验证些特殊情况
void Reader(int rfd)
{char buffer[NUM]; // 定义缓冲区bufferint _count 0;while(true) // 无限循环{buffer[0] 0; // 将缓冲区第一个位置置为0ssize_t n read(rfd, buffer, sizeof(buffer)); // 从管道读取数据存入缓冲区if(n 0) // 如果读取到的数据长度大于0{buffer[n] 0; // 将缓冲区第n个位置置为0表示字符串结束cout child get a message[ getpid() ]# buffer endl; // 输出读取到的内容}else if(n 0) // 如果读取到的数据长度为0表示管道已关闭{printf(child read file done!\n); // 输出读取完成信息break; // 退出循环}else break; // 如果读取错误退出循环_count;if(_count2)break;}
}让子进程只读两次后就退出那这时候写端还是正常写的那我们运行会出现什么情况呢 可以看到读端关闭后操作系统会杀掉正在运行的写端通过信号杀掉。 还有其他的情况 1.读写端正常管道被写满写端就会阻塞等待。 2.读写端正常管道为口读端就会阻塞等待。 由上述情况我们可以总结
进程间通信的原理是让不同的进程看到同一份资源匿名管道需要在创建子进程之前创建因为只有这样才能复制到管道的操作句柄与具有亲缘关系的进程实现访问同一个管道通信匿名管道只能单向通信父子进程会进程协同同步与互斥为了保护数据安全管道基于文件文件的生命周期随进程管道面向字节流
2.命名管道
命名管道FIFO是一种特殊的文件类型用于在两个不相关的进程之间进行单向或双向通信。与匿名管道不同命名管道存在于文件系统中用路径和文件名标识唯一可以被不相关的进程通过路径名来打开和使用。命名管道具有持久性可以在创建之后长期存在直到显式删除。 创建命名管道的函数mkfifo
pathname命名管道的路径名即文件系统中创建的命名管道的路径mode设置管道的权限与 open 或 chmod 的权限位相同如 0666 表示读写权限成功时返回 0失败时返回 -1并设置 errno 来指示错误类型
下面我们就利用命名管道实现两个无关系进程间的通信 comm.hpp
#pragma once#include iostream
#include string
#include cerrno
#include cstring
#include cstdlib
#include sys/types.h
#include sys/stat.h
#include unistd.h
#include fcntl.h#define FIFO_FILE ./myfifoclass Init
{
public:Init(){// 创建管道int n mkfifo(FIFO_FILE, 0664);if (n -1){perror(mkfifo);exit(1);}}~Init(){int m unlink(FIFO_FILE);if (m -1){perror(unlink);exit(2);}}
};
管理管道文件server.cc:
#include comm.hppusing namespace std;// 管理管道文件
int main()
{Init init;// 打开管道int fd open(FIFO_FILE, O_RDONLY); // 等待写入方打开之后自己才会打开文件向后执行 open 阻塞了if (fd 0){exit(3);}// 开始通信while (true){char buffer[1024] {0};int x read(fd, buffer, sizeof(buffer));if (x 0){buffer[x] 0;cout client say# buffer endl;}elsebreak; }close(fd);return 0;
}通信进程client.cc:
#include iostream
#include comm.hppusing namespace std;int main()
{int fd open(FIFO_FILE, O_WRONLY);if(fd 0){perror(open);exit(FIFO_OPEN_ERR);}cout client open file done endl;string line;while(true){cout Please Enter ;getline(cin, line);write(fd, line.c_str(), line.size());}close(fd);return 0;
}
当进程跑起来后就能在自己指定的路径下看到p开头的命名管道文件 两个进程都跑起来后在client输入就能在server上看到读取到信息了
3.日志
在Linux下开发应用程序时加入日志记录是一种非常好的编程习惯。日志记录不仅有助于调试和维护还能提供运行时的监控和错误追踪。其中日志主要包括日志等级内容文件的名称等。我们可以在上述命名管道的文件下加上一个日志的小模组
#pragma once#include iostream
#include time.h
#include stdarg.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include unistd.h
#include stdlib.h#define SIZE 1024// 日志级别定义
#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4// 日志输出方式定义
#define Screen 1
#define Onefile 2
#define Classfile 3#define LogFile log.txt // 日志文件默认名称// 日志类定义
class Log
{
public:Log(){printMethod Screen; // 默认输出方式为屏幕输出path ./log/; // 日志文件默认路径}// 设置日志输出方式void Enable(int method){printMethod method;}// 将日志级别转换为字符串std::string levelToString(int level){switch (level){case Info:return Info;case Debug:return Debug;case Warning:return Warning;case Error:return Error;case Fatal:return Fatal;default:return None;}}// 打印日志函数根据打印方式调用相应的打印方法void printLog(int level, const std::string logtxt){switch (printMethod){case Screen:std::cout logtxt std::endl; // 屏幕输出break;case Onefile:printOneFile(LogFile, logtxt); // 输出到一个文件break;case Classfile:printClassFile(level, logtxt); // 输出到不同级别的文件break;default:break;}}// 输出日志到单一文件void printOneFile(const std::string logname, const std::string logtxt){std::string _logname path logname; // 完整日志文件路径int fd open(_logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666); // 打开日志文件if (fd 0)return;write(fd, logtxt.c_str(), logtxt.size()); // 写入日志close(fd); // 关闭文件}// 输出日志到不同级别的文件void printClassFile(int level, const std::string logtxt){std::string filename LogFile;filename .;filename levelToString(level); // 构建不同级别的文件名printOneFile(filename, logtxt); // 输出到不同级别的文件}~Log(){}// 重载()操作符方便日志记录void operator()(int level, const char *format
接着就可以打印出日志信息了
三.共享内存
在 Linux 系统中共享内存是实现进程间通信最高效机制。通过共享内存不同进程可以直接访问同一块内存区域从而实现数据的快速交换。介绍共享内存的基本概念、优点、以及在 C 程序中的实现方法。
共享内存的原理就是在物理内存中申请一块空间接着进程将其挂接到自己的进程地址空间内然后多个进程挂接到自己的进程地址空间内进程间就能进行通信了 我们先来了解 创建共享内存的函数
key是一个数字可以在内核中唯一标识能够让不同进程进行唯一性标识size代表创建共享内存的字节shmflg标识创建的权限常用的有两种IPC_CREAT(单独)如果申请的共享内存不存在就创建存在就获取并返回。 IPC_CREAT|IPC_EXCL如果申请的内存不存在就创建存在就出错返回能确保我们申请一块新的共享内存 函数ftok用来生成一个唯一的key值接着用来创建共享内存;
pathname: 一个指向现有文件的路径名。这个文件必须存在并且调用进程必须对它有读取权限。这个参数用于生成键值的一部分.proj_id: 一个项目标识符通常是一个字符类型的整数例如‘A’ 或 65用于生成键值的另一部分。proj_id 是一个 8 位的值所以它的有效范围是 0 到 255 ipcs -m 查看共享内存,ipcrm -m shmid 删除共享内存 共享内存的生命周期是随内核的用户不主动关闭他就会一直存在 shmat可将共享内存挂接到进程的地址空间内。 shmat去掉挂接。 参数说明
shmid: 共享内存段的标识符这是由 shmget 函数返回的共享内存段 IDcmd: 控制命令指定对共享内存段执行的操作。buf: 指向 shmid_ds 结构的指针用于存储或设置共享内存段的属性。如果不需要可以传递 nullptr
控制命令说明
IPC_STAT: 获取共享内存段的当前状态信息并将其存储在 buf 指向的 shmid_ds 结构中。IPC_SET: 设置共享内存段的属性使用 buf 指向的 shmid_ds 结构中的值。IPC_RMID: 标记共享内存段以便将其删除。共享内存段在没有进程附加时会被实际删除IPC_INFO: 获取系统范围内的共享内存信息仅 Linux 提供。SHM_INFO: 获取系统范围内的共享内存信息仅 Linux 提供。SHM_STAT: 获取共享内存段的状态信息仅 Linux 提供。
以下是一个共享内存代码的演示添加日志模组 comm.hpp
#ifndef __COMM_HPP__
#define __COMM_HPP__#include iostream
#include string
#include cstdlib
#include cstring
#include sys/ipc.h
#include sys/shm.h
#include sys/types.h
#include sys/types.h
#include sys/stat.h#include log.hppusing namespace std;Log log;const int size 4096;
const string pathname./;
const int proj_id 1;key_t GetKey()
{key_t k ftok(pathname.c_str(), proj_id);if(k 0){log(Fatal, ftok error: %s, strerror(errno));exit(1);}log(Info, ftok success, key is : 0x%x, k);return k;
}int GetShareMemHelper(int flag)
{key_t k GetKey();int shmid shmget(k, size, flag);if(shmid 0){log(Fatal, create share memory error: %s, strerror(errno));exit(2);}log(Info, create share memory success, shmid: %d, shmid);return shmid;
}int CreateShm()
{return GetShareMemHelper(IPC_CREAT | IPC_EXCL | 0666);
}int GetShm()
{return GetShareMemHelper(IPC_CREAT);
}#define FIFO_FILE ./myfifo
#define MODE 0664enum
{FIFO_CREATE_ERR 1,FIFO_DELETE_ERR,FIFO_OPEN_ERR
};class Init
{
public:Init(){// 创建管道int n mkfifo(FIFO_FILE, MODE);if (n -1){perror(mkfifo);exit(FIFO_CREATE_ERR);}}~Init(){int m unlink(FIFO_FILE);if (m -1){perror(unlink);exit(FIFO_DELETE_ERR);}}
};#endif
processa/processb.cc:
#include comm.hpp // 包含自定义的头文件通常包含共享内存和日志相关的声明extern Log log; // 声明一个外部的日志对象int main()
{Init init; // 初始化对象用于初始化环境具体实现细节在comm.hpp中int shmid CreateShm(); // 创建共享内存并返回共享内存段标识符char *shmaddr (char*)shmat(shmid, nullptr, 0); // 将共享内存段附加到进程的地址空间并返回指向共享内存的指针// IPC进程间通信代码在这里// 一旦有人把数据写入到共享内存中我们能立刻看到数据不需要经过系统调用直接访问共享内存int fd open(FIFO_FILE, O_RDONLY); // 以只读方式打开命名管道// 等待写入方打开管道后当前进程才会继续执行open函数会阻塞直到管道另一端被打开if (fd 0){log(Fatal, error string: %s, error code: %d, strerror(errno), errno); // 记录打开管道失败的日志exit(FIFO_OPEN_ERR); // 退出程序返回管道打开错误码}struct shmid_ds shmds; // 定义共享内存数据结构用于存储共享内存段的信息while(true){char c; // 用于读取管道中的数据ssize_t s read(fd, c, 1); // 从管道中读取一个字节的数据if(s 0) break; // 读取到文件末尾退出循环else if(s 0) break; // 读取出错退出循环cout client say shmaddr endl; // 直接从共享内存中读取数据并输出sleep(1); // 睡眠1秒shmctl(shmid, IPC_STAT, shmds); // 获取共享内存段的状态并存储在shmds结构中cout shm size: shmds.shm_segsz endl; // 输出共享内存段的大小cout shm nattch: shmds.shm_nattch endl; // 输出当前附加到共享内存段的进程数printf(shm key: 0x%x\n, shmds.shm_perm.__key); // 输出共享内存段的键值cout shm mode: shmds.shm_perm.mode endl; // 输出共享内存段的访问模式}shmdt(shmaddr); // 将共享内存段从当前进程分离shmctl(shmid, IPC_RMID, nullptr); // 标记共享内存段为删除close(fd); // 关闭管道文件描述符return 0; // 正常退出程序
}
#include comm.hppint main()
{int shmid GetShm();char *shmaddr (char*)shmat(shmid, nullptr, 0);int fd open(FIFO_FILE, O_WRONLY); // 等待写入方打开之后自己才会打开文件向后执行 open 阻塞了if (fd 0){log(Fatal, error string: %s, error code: %d, strerror(errno), errno);exit(FIFO_OPEN_ERR);}// 一旦有了共享内存挂接到自己的地址空间中你直接把他当成你的内存空间来用即可// 不需要调用系统调用// ipc codewhile(true){cout Please Enter ;fgets(shmaddr, 4096, stdin);write(fd, c, 1); // 通知对方}shmdt(shmaddr);close(fd);return 0;
}log.hpp(日志模组):
#pragma once#include iostream
#include time.h
#include stdarg.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include unistd.h
#include stdlib.h#define SIZE 1024#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4#define Screen 1
#define Onefile 2
#define Classfile 3#define LogFile log.txtclass Log
{
public:Log(){printMethod Screen;path ./;}void Enable(int method){printMethod method;}std::string levelToString(int level){switch (level){case Info:return Info;case Debug:return Debug;case Warning:return Warning;case Error:return Error;case Fatal:return Fatal;default:return None;}}void printLog(int level, const std::string logtxt){switch (printMethod){case Screen:std::cout logtxt std::endl;break;case Onefile:printOneFile(LogFile, logtxt);break;case Classfile:printClassFile(level, logtxt);break;default:break;}}void printOneFile(const std::string logname, const std::string logtxt){std::string _logname path logname;int fd open(_logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666); // log.txtif (fd 0)return;write(fd, logtxt.c_str(), logtxt.size());close(fd);}void printClassFile(int level, const std::string logtxt){std::string filename LogFile;filename .;filename levelToString(level); // log.txt.Debug/Warning/FatalprintOneFile(filename, logtxt);}~Log(){}void operator()(int level, const char *format, ...){time_t t time(nullptr);struct tm *ctime localtime(t);char leftbuffer[SIZE];snprintf(leftbuffer, sizeof(leftbuffer), [%s][%d-%d-%d %d:%d:%d], levelToString(level).c_str(),ctime-tm_year 1900, ctime-tm_mon 1, ctime-tm_mday,ctime-tm_hour, ctime-tm_min, ctime-tm_sec);va_list s;va_start(s, format);char rightbuffer[SIZE];vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);va_end(s);// 格式默认部分自定义部分char logtxt[SIZE * 2];snprintf(logtxt, sizeof(logtxt), %s %s\n, leftbuffer, rightbuffer);// printf(%s, logtxt); // 暂时打印printLog(level, logtxt);}private:int printMethod;std::string path;
};
补充知识
共享内存没有同步机制导致多个进程对共享内存进行读写操作可能导致数据的不一致性。例如进程 A 写入数据 “Hello”进程 B 同时写入数据 “World”最终共享内存中的内容可能是 “Horld” 或 “Wello”。竞态条件是指多个进程同时访问共享资源而最终结果依赖于访问的顺序和时机。共享内存没有同步机制时进程的执行顺序和速度无法预测可能导致竞态条件。例如两个进程分别增加和减少共享内存中的计数器如果没有同步机制计数器的最终值可能不正确共享内存的大小建议时4kb常见块大小是4kb的整数倍如若不是实际底层会向上取整整数倍但是实际你申请多少给多少共享内存和其他的通信机制相比拷贝比较少所以他是最快的进程间通信的方式
三.System V 标准
1.System V简介
System V 标准是 Unix 系统中的一个重要规范提供了一系列用于进程间通信IPC和同步的机制。这些机制包括消息队列、信号量、共享内存和文件锁。
2.IPC在内核的数据结构设计
首先IPC在内核的数据结构设计和多态相似在操作系统中所有的IPC资源都是整合进操作系统的IPC模块中的! 每个通信机制结构体都有struct ipc_pem 类似与上图的结构体每个通信机制的struct ipc_prem存在OS中的数组struct ipc_perm *array[];中此数组下标就是XXXid,eg:shmid、msqid而且struct ipc_prem和通信机制结构体的关系是基类与子类的关系。
3.信号量
信号量是操作系统中一种重要的同步工具用于控制多个进程或线程对共享资源的访问。在 Linux 系统中信号量不仅用于进程间的同步和互斥还能有效地协调并发操作。我们了解下其原理即可信号量的本质是进程间通信的一种是一个计数器用来描述临界资源的数量多少 如果A、B进程能同时看到一份资源如果不加保护会导致数据不一致的问题。我们将共享的资源并且任何时刻只允许一个执行流访问的资源叫做临界资源我们把访问临界资源的部分代码叫做临界区。 所以我们要访问临界资源就先要申请信号量计数器资源计数器信号量是共享资源PV操作即对信号量的申请释放操作时原子的。执行流申请资源必须先申请信号资源得到信号量之后才能访问临界资源!!信号量值1,0两态的二元信号量就是互斥功能申请信号量的本质:是对临界资源的预订机制!!!