Linux系统与系统编程(12)——进程间通信

发布时间:2026/7/27 8:58:30

Linux系统与系统编程(12)——进程间通信 前言欢迎观看Linux系列文章第12篇主要讲述了管道、systemV共享内存和其他systemV标准的IPC原理。管道目的数据传输一个进程需要将它的数据发送给另一个进程资源进程多个进程之间共享同样的资源通知时间一个进程需要向另一个或一组进程发送消息通知发生了某种时间如进程终止时要通知父进程进程控制有些进程希望完成控制另一个进程的执行如debug过程什么是管道最古老的进程间通信的形式。它是从一个进程连接到另一个进程的一个数据流。匿名管道匿名管道Anonymous Pipe是操作系统提供的一种单向、无名称的进程间通信IPC机制主要用于父子进程之间或具有亲缘关系的进程间传输数据。#includeunistd.h int pipe(int fd[2]);fd文件描述符数组其中fd[0]表示读端fd[1]表示为写端返回值成功返回0失败返回错误代码。管道是字节流缓冲写入的数据会先进入内核缓冲区父子单向通信如图即可完成父子间单向通信如果要进行双向通信那就建立两个管道即可。因为子进程和父进程共享代码而且会继承父进程的文件描述符表所以可以保证打开同一个管道文件。管道文件没有文件名是内存级的匿名的。下文还有命名管道。是命名的。示例代码#includeiostream #includeunistd.h #includesys/types.h #includesys/wait.h #includestring.h using namespace std; void ChildWrite(int wfd) { char buffer[1024]; int cnt 0; while(true) { snprintf(buffer, sizeof(buffer), I am a Child, pid:%d, cnt: %d, getpid(), cnt); write(wfd, buffer, strlen(buffer)); sleep(1); } } void FatherRead(int rfd) { char buffer[1024]; while(true) { buffer[0] 0; //read读完之后在下一次读的时候会阻塞等待 //这是管道文件自带的同步机制 ssize_t n read(rfd, buffer, sizeof(buffer) - 1); if(n 0) { buffer[n] 0; cout child say: buffer endl; } } } int main() { //创建管道 int fds[2] {0}; int n pipe(fds); if(n 0) { cerr pipe error endl; return 1; } cout fds[0]: fds[0] endl; cout fds[1]: fds[1] endl; //创建子进程 pid_t id fork(); if(id 0) { //关闭不需要的读写端形成通信信道 //父进程读子进程写 close(fds[0]); ChildWrite(fds[1]); close(fds[1]); exit(0); } //关闭不需要的读写端形成通信信道 //父进程读子进程写 close(fds[1]); FatherRead(fds[0]); waitpid(id, nullptr, 0); close(fds[0]); return 0; }根据上述代码我们可以得到匿名管道的一些性质①匿名管道只能用来进行具有血缘关系和亲缘关系的进程间通信父子兄弟皆可。②管道文件自带同步机制。③管道是面向字节流的。void ChildWrite(int wfd) { char buffer[1024]; int cnt 0; while(true) { snprintf(buffer, sizeof(buffer), I am a Child, pid:%d, cnt: %d, getpid(), cnt); write(wfd, buffer, strlen(buffer)); printf(child:%d\n, cnt); } } void FatherRead(int rfd) { char buffer[1024]; while(true) { sleep(5); buffer[0] 0; ssize_t n read(rfd, buffer, sizeof(buffer) - 1); if(n 0) { buffer[n] 0; cout child say: buffer endl; sleep(2); } } }这里修改了代码把原本“读得快写的慢”的情况变为“读得慢写得快”运行后可以发现无论写得快还是慢不会影响读操作而如果写满了管道文件写端就会阻塞。学习完Linux网络后可以更深刻的理解什么是面向字节流这里不多说。④管道是只能单向通信的属于半双工的一个特殊情况。半双工任何时刻一个发一个收全双工任何时刻可以同时发和收。⑤管道文件的生命周期是随进程的。同时使用管道通信有4种情况❶写得慢读得快——读端进程阻塞。❷写得快读得慢——写满了写端阻塞等待读端读取之后再覆盖写。❸写端关闭读端继续读直到读完。❹读端关闭写端继续写是没有意义的所以OS会发送信号13杀掉写端进程避免浪费资源。管道容量void ChildWrite(int wfd) { char c 0; int cnt 0; while(true) { //snprintf(buffer, sizeof(buffer), I am a Child, pid:%d, cnt: %d, getpid(), cnt); write(wfd, c, 1); printf(child:%d\n, cnt); } } void FatherRead(int rfd) { char buffer[1024]; while(true) { sleep(100); buffer[0] 0; ssize_t n read(rfd, buffer, sizeof(buffer) - 1); if(n 0) { buffer[n] 0; cout child say: buffer endl; sleep(2); } } }用以上代码可以测试出管道文件的容量大小所以容量大小为65536字节也就是64KB不同的操作系统可能会不一样。基于匿名管道——进程池利用管道读写端的阻塞来暂停和唤醒子进程。同时通过管道传输一个任务码不同的任务码对应不同的任务子进程接收任务码后执行对应任务这种工作叫作池化技术这幅图就是进程池。可以减少创建某些资源的成本提高访问效率。示例代码ProcessPool.hpp#ifndef __PROCESS_POOL_HPP__ #define __PROCESS_POOL_HPP__ #include iostream #include cstdlib // stdlib.h stdio.h - cstdlib cstdio #include vector #include unistd.h #include sys/wait.h #include Task.hpp class Channel { public: Channel(int fd, pid_t id) : _wfd(fd), _subid(id) { _name channel- std::to_string(_wfd) - std::to_string(_subid); } ~Channel() { } void Send(int code) { int n write(_wfd, code, sizeof(code)); (void)n; // ? } void Close() { close(_wfd); } void Wait() { pid_t rid waitpid(_subid, nullptr, 0); (void)rid; } int Fd() { return _wfd; } pid_t SubId() { return _subid; } std::string Name() { return _name; } private: int _wfd; pid_t _subid; std::string _name; // int _loadnum; }; class ChannelManager { public: ChannelManager() : _next(0) { } void Insert(int wfd, pid_t subid) { _channels.emplace_back(wfd, subid); // Channel c(wfd, subid); // _channels.push_back(std::move(c)); } Channel Select() { auto c _channels[_next]; _next; _next % _channels.size(); return c; } void PrintChannel() { for (auto channel : _channels) { std::cout channel.Name() std::endl; } } void StopSubProcess() { for (auto channel : _channels) { channel.Close(); std::cout 关闭: channel.Name() std::endl; } } void WaitSubProcess() { for (auto channel : _channels) { channel.Wait(); std::cout 回收: channel.Name() std::endl; } } ~ChannelManager() {} private: std::vectorChannel _channels; int _next; }; const int gdefaultnum 5; class ProcessPool { public: ProcessPool(int num) : _process_num(num) { _tm.Register(PrintLog); _tm.Register(Download); _tm.Register(Upload); } void Work(int rfd) { while (true) { int code 0; ssize_t n read(rfd, code, sizeof(code)); if (n 0) { if (n ! sizeof(code)) { continue; } std::cout 子进程[ getpid() ]收到一个任务码: code std::endl; _tm.Execute(code); } else if (n 0) { std::cout 子进程退出 std::endl; break; } else { std::cout 读取错误 std::endl; break; } } } bool Start() { for (int i 0; i _process_num; i) { //创建管道 int pipefd[2] {0}; int n pipe(pipefd); if (n 0) return false; //创建子进程 pid_t subid fork(); if (subid 0) return false; else if (subid 0) { //子进程 //关闭不需要的文件描述符 close(pipefd[1]); Work(pipefd[0]); //?? close(pipefd[0]); exit(0); } else { //父进程 //关闭不需要的文件描述符 close(pipefd[0]); // 写端pipefd[1]; _cm.Insert(pipefd[1], subid); // wfd, subid } } return true; } void Debug() { _cm.PrintChannel(); } void Run() { //选择一个任务 int taskcode _tm.Code(); //选择一个信道[子进程],负载均衡的选择一个子进程完成任务 auto c _cm.Select(); std::cout 选择了一个子进程: c.Name() std::endl; //发送任务 c.Send(taskcode); std::cout 发送了一个任务码: taskcode std::endl; } void Stop() { //关闭父进程所有的wfd即可 _cm.StopSubProcess(); //回收所有子进程 _cm.WaitSubProcess(); } ~ProcessPool() { } private: ChannelManager _cm; int _process_num; TaskManager _tm; }; #endifTask.hpp#pragma once #include iostream #include vector #include ctime typedef void (*task_t)(); void PrintLog() { std::cout 我是一个打印日志的任务 std::endl; } void Download() { std::cout 我是一个下载的任务 std::endl; } void Upload() { std::cout 我是一个上传的任务 std::endl; } class TaskManager { public: TaskManager() { srand(time(nullptr)); } void Register(task_t t) { _tasks.push_back(t); } int Code() { return rand() % _tasks.size(); } void Execute(int code) { if(code 0 code _tasks.size()) { _tasks[code](); } } ~TaskManager() {} private: std::vectortask_t _tasks; };Main.cpp#include ProcessPool.hpp int main() { // 创建进程池对象 ProcessPool pp(gdefaultnum); // 启动进程池 pp.Start(); // 自动派发任务 int cnt 10; while(cnt--) { pp.Run(); sleep(1); } // 回收结束进程池 pp.Stop(); return 0; }进程池的代码存在一个问题当父子进程分别把自己的读和写端关闭之后形成了单向通信。当父进程创建了新的子进程他会继承父亲的文件描述符表其中包括了父亲的写端。所以在父亲关闭了自己的写端时管道文件还有之后创建的子进程的写端指向所以子进程read会一直阻塞而不是返回0。解决方法指定回收顺序根据创建时间从后往前回收。或者关闭子进程的写端即可使得只有父进程有文件描述符指向管道文件的写端。命名管道当两个进程之间没有关联时匿名管道就不起作用了这里就引入命名管道。#include sys/types.h #include sys/stat.h int mkfifo(const char *pathname, mode_t mode);mkfifo函数可创建一个命名管道文件。成功返回0失败返回-1并设置错误码命名管道依旧是单向的使用逻辑和匿名管道没有本质区别。写端将内容写入文件读端将内容从管道文件读出来。示例代码comm.hpp#pragma once #include iostream #include string #include sys/types.h #include sys/stat.h #include fcntl.h #include unistd.h #define FIFONAME fifo #define PATH . using namespace std; class NamedFifo { public: NamedFifo(const string path, const string name) : _path(path), _name(name) { // 新建管道 _fifoname _path / _name; umask(0); int n mkfifo(_fifoname.c_str(), 0666); if (n 0) { cerr mkfifo error endl; } else { cout mkfifo success endl; } } ~NamedFifo() { // 删除管道 int n unlink(FIFONAME); if (n 0) { cout remove fifo success endl; } else { cout remove fifo failed endl; } } private: string _path; string _name; string _fifoname; }; class FileOper { public: FileOper(const string path, const string name) : _path(path), _name(name) { _fifoname _path / _name; } // 打开文件 void OpenForRead() { _fd open(_fifoname.c_str(), O_RDONLY); if (_fd 0) { cerr open fifo error endl; } cout open fifo success endl; } // 写入文件 void OpenForWrite() { _fd open(_fifoname.c_str(), O_WRONLY); if (_fd 0) { cerr open fifo error endl; } cout open fifo success endl; } void Write() { // 写入操作 string message; int cnt 1; pid_t id getpid(); while (true) { cout Please Enter# ; getline(cin, message); message (, message number: to_string(cnt) ,[ to_string(id) ]); write(_fd, message.c_str(), message.size()); } } void Read() { while (true) { char buffer[1024]; int number read(_fd, buffer, sizeof(buffer) - 1); if (number 0) { buffer[number] 0; cout Client Say# buffer endl; ; } else if (number 0) { cout Client quit! Me, too! endl; break; } else { cerr read error endl; break; } } } void Close() { if (_fd 0) { close(_fd); } } ~FileOper() { } private: string _path; string _name; string _fifoname; int _fd; };server.cpp#includecomm.hpp using namespace std; int main() { //创建管道文件 NamedFifo fifo(., fifo); //文件操作 FileOper readerfile(., fifo); readerfile.OpenForRead(); readerfile.Read(); readerfile.Close(); }client.cpp#includecomm.hpp using namespace std; int main() { FileOper writerfile(PATH, FIFONAME); writerfile.OpenForWrite(); writerfile.Write(); writerfile.Close(); return 0; }system V共享内存system V其实是一个标准Linux内核支持了这种标准专门设计了一个IPC通信模块。而读写共享内存并没有调用系统调用。因为共享区属于用户空间可以让用户直接使用。原理内核在物理内存中划出一块区域让进程A和进程B在自己的虚拟地址空间中都映射到同一块物理内存上。数据直接由进程A写入这块内存进程B即可直接读取整个过程无需内核介入也无需数据拷贝。这一块内存就是共享内存。取消两进程的关联关系之后共享内存就会自行释放。可能会同时存在多组进程都在使用不同的共享内存来通信所以需要对共享内存进行管理。同样是先描述后组织在底层会使用结构体链表等存储共享内存的信息如哪些进程在用大小位置等等。而共享内存是进程间通信中速度最快的方式1.映射之后读写操作可以直接被对方看到2.不需要进行系统调用来获取或者写入内容但是也有对应的缺点就是通信双方没有同步机制无法满足数据一致性。接口shmget——创建共享内存#includesys/ipc.h #includesys/shm.h int shmget(key_t key, size_t size, int shmflg);参数size共享内存的大小。shmflg标志位选项。IPC_CREAT创建共享内存如果目标共享内存不存在就创建否则打开已存在的共享内存并返回。IPC_CREAT | IPC_EXCL如果创建的shm不存在就创建它否则就报错。key不同的共享内存有不同的key来标识它的唯一性。但是它不是由系统形成的而是需要用户层构建并传入OS。通信时要让两进程直接约定一个key。可以使用ftok函数形成唯一的key。#includesys/types.h #includesys/ipc.h key_t ftok(const char* pathname, int proj_id);这个函数依旧是用户层的算法没有深入到内核他会根据两个参数计算出一个key这样生成的key重复可能性小。但是也会有可能会冲突那就修改一下参数就行生成另一个key。返回值返回一个int类型的共享内存标识符用来标识共享内存的唯一性。key也能标识key给内核级使用共享内存标识符是给用户级使用的。shmctl——操作共享内存删除创建了共享内存后如果没有删除共享内存共享内存就会一直存在。共享内存的生命周期是随内核的就算进程结束共享内存的资源也会一直存在。可以通过指令删除或者代码删除。ipcs -m --查看共享内存 ipcrm -m [shmid] --释放指定shmid的共享内存#includesys/ipc.h #includesys/shm.h int shmctl(int shmid, int cmd, struct shmid_ds* buf);参数shmid指定要操作的共享内存shmid。cmd操作共享内存的命令。如IPC_RMID释放删除指定共享内存。buf用于保存描述共享内存的相关属性。shmat——挂接共享内存#includesys/ipc.h #includesys/shm.h int shmat(int shmid, const void* shmaddr, int shmflg);参数shmid指定要操作的共享内存shmid。shmaddr虚拟地址向固定地址开始进行挂接一般让系统自行设置用户传NULL即可。shmflg设置共享内存段的访问权限和连接方式填0为使用默认设置即可读可写。也可以使用SHM_RDONLY即只读方式。shmdt——断联共享内存#include sys/types.h #include sys/shm.h int shmdt(const void *shmaddr);shmaddrshmat挂接时要返回的那个指向共享内存的地址。维护共享内存因为共享内存没有同步机制所以程序员需要主动维护共享内存来保护数据一致性。实现局部保护的一种方法就是使用命名管道。这种办法只是简单的模拟进行同步之后还会学习更多的同步机制。可见下文示例代码。示例代码Shm.hpp#pragma once #include iostream #include cstdio #include string #include sys/types.h #include sys/ipc.h #include sys/shm.h #include unistd.h #include Comm.hpp const int gdefaultid -1; const int gsize 4096; const std::string pathname .; const int projid 0x66; const int gmode 0666; #define CREATER creater #define USER user class Shm { private: // 创建的一定要是一个全新的共享内存 void CreateHelper(int flg) { printf(key: 0x%x\n, _key); // 共享内存的生命周期随内核 _shmid shmget(_key, _size, flg); if (_shmid 0) { ERR_EXIT(shmget); } printf(shmid: %d\n, _shmid); } void Create() { CreateHelper(IPC_CREAT | IPC_EXCL | gmode); } void Attach() { _start_mem shmat(_shmid, nullptr, 0); if ((long long)_start_mem 0) { ERR_EXIT(shmat); } printf(attach success\n); } void Detach() { int n shmdt(_start_mem); if (n 0) { printf(detach success\n); } } void Get() { CreateHelper(IPC_CREAT); } void Destroy() { // if (_shmid gdefaultid) // return; Detach(); if (_usertype CREATER) { int n shmctl(_shmid, IPC_RMID, nullptr); if (n 0) { printf(shmctl delete shm: %d success!\n, _shmid); } else { ERR_EXIT(shmctl); } } } public: Shm(const std::string pathname, int projid, const std::string usertype) : _shmid(gdefaultid), _size(gsize), _start_mem(nullptr), _usertype(usertype) { _key ftok(pathname.c_str(), projid); if (_key 0) { ERR_EXIT(ftok); } if (_usertype CREATER) Create(); else if (_usertype USER) Get(); else { } Attach(); } void *VirtualAddr() { printf(VirtualAddr: %p\n, _start_mem); return _start_mem; } int Size() { return _size; } void Attr() { struct shmid_ds ds; int n shmctl(_shmid, IPC_STAT, ds); // ds:输出型参数 printf(shm_segsz: %ld\n, ds.shm_segsz); printf(key: 0x%x\n, ds.shm_perm.__key); } ~Shm() { std::cout _usertype std::endl; if(_usertype CREATER) Destroy(); } private: int _shmid; key_t _key; int _size; void *_start_mem; std::string _usertype; };Client.cpp#includeShm.hpp #includeFifo.hpp using namespace std; int main() { FileOper writerfile(PATH, FIFONAME); writerfile.OpenForWrite(); Shm shm(pathname, projid, USER); char* mem (char*)shm.VirtualAddr(); int index 0; for(char c A; c Z; c, index 2) { mem[index] c; sleep(1); mem[index1] c; sleep(1); mem[index2] \0; writerfile.Wakeup(); } writerfile.Close(); return 0; }Server.cpp#include Shm.hpp #include Fifo.hpp int main() { Shm shm(pathname, projid, CREATER); // sleep(5); shm.Attr(); NamedFifo fifo(PATH, FIFONAME); // 文件操作了 FileOper readerfile(PATH, FIFONAME); readerfile.OpenForRead(); char *mem (char *)shm.VirtualAddr(); // 我们读写共享内存有没有使用系统调用也没有 while (true) { if (readerfile.Wait()) { printf(%s\n, mem); } else break; } readerfile.Close(); std::cout server end normal! std::endl; // server段的析构函数没有被成功调用 return 0; }Fifo.hpp#pragma once #include iostream #include cstdio #include string #include iostream #include string #include sys/types.h #include sys/stat.h #include fcntl.h #include unistd.h #include Comm.hpp #define PATH . #define FIFONAME fifo class NamedFifo { public: NamedFifo(const std::string path, const std::string name) : _path(path), _name(name) { _fifoname _path / _name; umask(0); // 新建管道 int n mkfifo(_fifoname.c_str(), 0666); if (n 0) { ERR_EXIT(mkfifo); } else { std::cout mkfifo success std::endl; } } ~NamedFifo() { // 删除管道文件 int n unlink(_fifoname.c_str()); if (n 0) { // ERR_EXIT(unlink); // bug在这里先析构fifo导致shm的析构没有被调用 } else { std::cout remove fifo failed std::endl; } } private: std::string _path; std::string _name; std::string _fifoname; }; class FileOper { public: FileOper(const std::string path, const std::string name) : _path(path), _name(name), _fd(-1) { _fifoname _path / _name; } void OpenForRead() { // 打开, write 方没有执行open的时候read方就要在open内部进行阻塞 // 直到有人把管道文件打开了open才会返回 _fd open(_fifoname.c_str(), O_RDONLY); if (_fd 0) { ERR_EXIT(open); } std::cout open fifo success std::endl; } void OpenForWrite() { // write _fd open(_fifoname.c_str(), O_WRONLY); if (_fd 0) { ERR_EXIT(open); } std::cout open fifo success std::endl; } void Wakeup() { // 写入操作 char c c; int n write(_fd, c, 1); printf(尝试唤醒: %d\n, n); } bool Wait() { char c; int number read(_fd, c, 1); if(number 0) { printf(醒来: %d\n, number); return true; } return false; } void Close() { if (_fd 0) close(_fd); } ~FileOper() { } private: std::string _path; std::string _name; std::string _fifoname; int _fd; };Comm.hpp#pragma once #include cstdio #include cstdlib #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ } while (0)共享内存注意事项1️⃣共享内存的大小必须是4KB的整数倍就算你查询共享内存大小时返回的是4097B实际上的大小是4KB * 2。4097B只是你需要的空间大小系统开辟时会向上4KB取整。其他进程通信方式了解即可systemV消息队列提供了一种一个进程向另一个进程发送有类型数据块的方式。OS内部一样要对这个队列进行管理先描述后组织。由于系统中可能存在多个消息队列为了保证两进程之间使用的是同一个消息队列需要用到key同共享内存。接口和共享内存很相似因为都是systemV标准。msgget——创建或获取消息队列#include sys/msg.h int msgget(key_t key, int msgflg);参数key队列的外部通行证。通常用 ftok() 根据路径和字符生成或者直接传 IPC_PRIVATE值为0创建仅父子进程可见的私有队列。msgflg权限标志位按位或组合权限位0600读写属主、0666所有人读写和文件权限一样。创建标志IPC_CREAT不存在则创建存在则返回已有IDIPC_EXCL与IPC_CREAT并用如果队列已存在则报错防止误用旧队列。返回值成功返回非负整数队列ID后续所有操作都用这个ID。失败返回 -1。内核行为如果key对应的队列不存在且指定了IPC_CREAT内核会分配一个struct msg_queue结构体初始化等待队列头、计数器并将引用计数设为1。注意这个结构体的生命周期独立于创建它的进程。msgsnd——发送消息#includesys/msg.h int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);参数msqidmsgget成功时返回的队列id。msgp指向用户自定义结构体的指针。强制要求结构体第一个成员必须是 long mtype消息类型0后面跟着消息正文。struct mymsg { long mtype; // 必须 0 char mtext[100]; // 正文数据 };msgsz正文长度不包括那个long mtype。内核只拷贝这msgsz个字节。msgflg控制阻塞行为只有一个常用标志 IPC_NOWAIT阻塞规则默认msgflg0如果队列已满总字节数达到msgmnb上限进程进入睡眠状态直到有空间释放或队列被删除。IPC_NOWAIT如果队列满立即返回 -1errno设为EAGAIN不阻塞。msgrcv——接收消息#includesys/msg.h ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);参数msgsz用户缓冲区大小。如果消息正文 msgsz根据 msgflg 决定行为见下。msgtyp灵魂参数决定了取哪条消息 0取队列中第一个类型等于该值的消息不一定是队头内核会遍历查找。 0取队列队头第一条消息FIFO。 0取队列中类型小于等于该绝对值的所有消息中类型值最小的那条实现优先级调度数值越小优先级越高。msgflg三个常用标志可组合IPC_NOWAIT队列为空或找不到匹配消息时立即返回 -1errnoEAGAIN。MSG_EXCEPT仅当 msgtyp 0 时有效表示取类型不等于 msgtyp 的第一条消息。MSG_NOERROR如果消息正文长度 msgsz截断消息丢弃超出部分并返回实际拷贝长度。如果不设此标志消息过长会直接报错 E2BIG。返回值成功返回实际接收到的正文字节数不包括mtype字段。失败返回 -1。内核行为内核会从链表中摘除匹配的消息释放对应内核内存再将数据拷贝到用户空间。注意如果多个进程同时等待相同类型内核会按进程优先级唤醒而非按等待时间。msgctl——控制与销毁#includesys/msg.h int msgctl(int msqid, int cmd, struct msqid_ds *buf);参数cmd命令。IPC_RMID立即删除队列。注意内核会唤醒所有正在等待该队列的发送/接收进程让它们返回 -1 并设 errnoEIDRM标识已删除。IPC_STAT获取队列状态信息填充到 buf 中包含当前消息数、总字节数、最近发送/接收时间等。IPC_SET修改队列权限如 msg_perm.uid或队列大小上限msg_qbytes仅超级用户可扩大。buf一段数据不同的cmd对应不同作用的buf。IPC_STAT查询时内核会把队列的当前状态复制到你传入的buf中IPC_SET修改队列权限时buf相当于你的配置表提前填充好你的buff内核会根据的你的配置来设置队列属性。如下buf.msg_perm.uid 1000; // 修改所有者用户ID buf.msg_perm.gid 1000; // 修改所有组ID buf.msg_perm.mode 0644; // 修改读写权限如去掉写权限 buf.msg_qbytes 65536; // 修改队列容量上限仅root可增大IPC_RMID删除队列不需要buf填入NULL即可。systemV信号量前置概念·共享内存没有同步的保护机制所以上面我们用管道来帮助实现同步像这种需要被保护的共享资源就叫做临界资源。· 保护的方式就是互斥和同步。互斥任何时刻只允许一个执行流访问临界资源同步多个执行流按照一定顺序访问临界资源。·我们把进程中涉及到互斥临界资源的代码程序段叫做临界区。其他区域叫做非临界区。当程序访问到临界区前就要加一个互斥锁锁本身也要共享。锁保护临界区的安全为了保证锁本身的安全要让锁的申请必须是原子性的。信号量原理本身是一个计数器用来表示临界资源中资源的数量多少。我们把共享内存内部分成多个不同的区域进行部分使用只要多个进程不访问同一个位置就可以让它们并发访问临界资源且不出问题。进程要想访问临界资源的一小块就必须先申请信号量。这么做的本质是对资源的预订。申请成功时信号量--称为P操作归还资源信号量称为V操作。信号量只有0和1两种状态的信号量称为二元信号量这就是互斥。除此之外叫做多元信号量。整体使用临界资源时我们用二元信号量部分使用临界资源时我们用多元信号量。信号量的简单描述struct sem { 锁;//后面再详细解释 int count;//计数器P或V操作会改变这个值0 时则申请失败。 task_struct *waitqueue;//等待队列申请失败的进程进入该队列当有资源时再唤醒。 }与通信的关系1.先访问信号量P每个进程都得先看到同一个信号量2.不是传递数据的而是通信IPC通知同步互斥也算是IPC通信。信号量的接口和系统调用ipcs -s该命令可以获取当前存在的信号量。ipcrm -s XX改名了可以删除指定信号量集合。semget——创建信号量集合#includesys/types.h #includesys/ipc.h #includesys/sem.h int semget(key_t key, int nsems, int semflg);参数key同上通过ftok获取作用想法不多赘述共享内存、消息队列、信号量都用key来表示唯一性所以在OS中这三种IPC都当成同一种资源即systemV IPC。nsems信号量个数即设置n元信号量。semflg标志位IPC_CREAT不存在则创建存在则返回已有信号量集合IDIPC_EXCL与IPC_CREAT并用如果信号量ID已存在则报错防止误用旧信号量集合semctl——控制信号量#includesys/types.h #includesys/ipc.h #includesys/sem.h int semctl(int semid, int semnum, int cmd, ···);参数semnum当你要删除时该参数无效当你初始化时该参数表示cmdIPC_RMID删除信号量SETVAL初始化信号量。IPC_STAT获取信号量状态信息填充到 buf 中。···当初始化信号量时该处传入一个联合体semun。union semun { int val;//信号量初始值 struct semid_ds *buf; unsigned short *array; struct seminfo *__buf; };semop——操作信号量#includesys/types.h #includesys/ipc.h #includesys/sem.h int semop(int semid, struct sembuf *sops, size_t nsops);参数sopssembuf数组通过自行配置指定某个信号量进行P或V操作。nsopssembuf数组大小即对几个信号量进行操作。sembuf结构体如下struct sembuf { unsigned short sem_num;//信号量编号 short sem_op;//信号量操作-1为P操作1为V操作 short sem_flg;//操作标志位 }补充systemV底层把共享内存消息队列和信号量存储时其结构体的第一个元素都是都以kern_ipc_perm。对于内核来说他们是一样的存放在内核ipc_ids中ipc_id_ary的一个柔性数组p[0]中。不同的IPC就将这个p数组中的元素进行强制类型转换为对于的类型。简单的来说内核在实现systemV标准时使用了C语言靠强制类型转换实现了多态不同类型的IPC作为子类kern_ipc_perm作为基类。❤~~本文完结感谢观看接下来更精彩欢迎来我博客做客~~❤

相关新闻