当前位置:   article > 正文

【Linux】命名管道 && 共享内存

【Linux】命名管道 && 共享内存

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

目录

前言

一、命名管道

1.1、命名管道实现进程间通信的原理

1.2、创建和删除一个命名管道的指令

1.3、匿名管道与命名管道的区别

1.4、用命名管道实现server&client通信

二、共享内存

2.1、共享内存实现进程间通信的原理

2.2、共享内存的相关接口

2.3、实现共享内存完成进程间的通信

三、system V消息队列 - 选学了解即可

四、system V信号量 - 选学了解即可

5个概念:

对于信号量的理论理解:

信号量的操作

总结



前言

世上有两种耀眼的光芒,一种是正在升起的太阳,一种是正在努力学习编程的你!一个爱学编程的人。各位看官,我衷心的希望这篇博客能对你们有所帮助,同时也希望各位看官能对我的文章给与点评,希望我们能够携手共同促进进步,在编程的道路上越走越远!


提示:以下是本篇文章正文内容,下面案例可供参考

一、命名管道

  • 管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。
  • 如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。
  • 命名管道是一种特殊类型的文件。

1.1、命名管道实现进程间通信的原理

  • 第二个进程打开与第一个进程打开相同文件时,也一定要在创建一个struct file,原因是打开文件时,也要拿到对应的文件描述符表,也要拿到新的文件描述符,有新的文件指针,就得有新的struct file;还有一个原因:一个以写的方式打开文件,另一个以读的方式打开文件,两个进程之间为了避免相互冲突,创建两个struct file。
  • 匿名管道,两个具有血缘关系的进程,通过父子继承的方式,看到同一个被打开的文件。
  • 那么两个毫无关系的进程看到同一份资源(打开了同一个文件)呢?每一个文件,都有文件路径(唯一性);要保证两个好不相干的进程看到同一个管道文件,就要有同一个路径,要让client和server都能看到同一个管道文件的路径。

1.2、创建和删除一个命名管道的指令

  • 命名管道可以从命令行上创建,命令行方法是使用下面这个命令:
  1. man mkfifo
  2. // 查看创建一个命名管道文件的指令
  1. mkfifo myfifo
  2. // 创建一个myfifo为名的管道文件(文件的第一个属性为p)
  • 命名管道也可以从程序里创建,相关函数有:
int mkfido(const char *pathname,mode_t mode);
  • 第一个参数:创建一个文件所对应的路径 + 文件名;
  • 参数二:创建文件时所对应的权限;
  • /return val -> 查看函数的返回值,成功返回0;失败返回-1。

命名管道可以从命令行上删除,命令行方法是使用下面这个命令:

  1. man 2 unlink
  2. // 查看删除管道文件的指令
  1. unlink myfile
  2. // 删除指定的myfile特殊的管道文件

命名管道也可以从程序里删除,相关函数有:

  1. int unlink(const char* pathname);
  2. // 删除指定目录下的文件

1.3、匿名管道与命名管道的区别

  • 匿名管道由pipe函数创建并打开。
  • 命名管道由mkfifo函数创建,打开用open
  • FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完成之后,它们具有相同的语义。

1.4、用命名管道实现server&client通信

Mikefile
  • Makefile默认只能形成一个可执行程序,会自动匹配第一个目标对象(client),第二个server就不会形成。
  • 要形成两个可执行程序,就定义一个为目标all,all只有依赖关系,依赖的是client server;所以Makefile从上到下扫描时,第一个目标文件叫做all,所以就得先形成client,再形成server;可是all没有依赖方法,所以只要把依赖关系推到完毕,all什么都不做,就相当于一次形成两个可执行程序。
  1. .PHONY:all
  2. all : client server
  3. client : client.cc
  4. g++ - o $@ $ ^ -std = c++11
  5. server:server.cc
  6. g++ - o $@ $ ^ -std = c++11
  7. .PHONY:clean
  8. clean :
  9. rm - rf client server
namedPipe.hpp // 命名管道
  1. #include <iostream>
  2. #include <cstdio>
  3. #include <cerrno>
  4. #include <string>
  5. #include <unistd.h>
  6. #include <sys/types.h>
  7. #include <sys/stat.h>
  8. #include <fcntl.h>
  9. // 把管道文件的路径(公共路径)放在当前路径下,管道文件的名字叫做myfifo
  10. const std::string comm_path = "./myfifo";
  11. // 将文件描述符默认定义为-1
  12. #define DefaultFd -1
  13. // 定义管道文件的创建者和使用者
  14. #define Creater 1
  15. #define User 2
  16. // 以只读和只写的方式打开文件
  17. #define Read O_RDONLY
  18. #define Write O_WRONLY
  19. // 定义一个通信的基本大小
  20. #define BaseSize 4096
  21. class NamePiped
  22. {
  23. private:
  24. // 打开文件(方式)
  25. bool OpenNamedPipe(int mode)
  26. {
  27. _fd = open(_fifo_path.c_str(), mode);
  28. if (_fd < 0)
  29. return false;
  30. return true;
  31. }
  32. public:
  33. // 构造函数
  34. NamePiped(const std::string& path, int who)
  35. : _fifo_path(path), _id(who), _fd(DefaultFd)
  36. {
  37. // 创建者才创建管道文件
  38. if (_id == Creater)
  39. {
  40. // 初始化期间,将管道构建好了
  41. // mkfifo创建命名管道的接口
  42. int res = mkfifo(_fifo_path.c_str(), 0666);
  43. if (res != 0)
  44. {
  45. perror("mkfifo");
  46. }
  47. std::cout << "creater create named pipe" << std::endl;
  48. }
  49. }
  50. bool OpenForRead()
  51. {
  52. return OpenNamedPipe(Read);
  53. }
  54. bool OpenForWrite()
  55. {
  56. return OpenNamedPipe(Write);
  57. }
  58. // const &(输入型参数),比如:const std::string &XXX
  59. // *(输出型参数) 比如: std::string *
  60. // &(输入输出型参数) 比如: std::string &
  61. // 假如双方通信的内容是字符串
  62. int ReadNamedPipe(std::string* out)
  63. {
  64. char buffer[BaseSize];// 定义一个缓冲区
  65. // 将对应文件描述符中的数据读到buffer数组中
  66. int n = read(_fd, buffer, sizeof(buffer));
  67. if (n > 0)
  68. {
  69. buffer[n] = 0;
  70. *out = buffer;// 将buffer数组中的内容带出去了
  71. }
  72. return n;
  73. }
  74. int WriteNamedPipe(const std::string& in)
  75. {
  76. // c_str:C风格的字符串
  77. return write(_fd, in.c_str(), in.size());
  78. }
  79. // 删除管道文件
  80. ~NamePiped()
  81. {
  82. if (_id == Creater)
  83. {
  84. // unlink删除管道文件的接口
  85. int res = unlink(_fifo_path.c_str());
  86. if (res != 0)
  87. {
  88. perror("unlink");
  89. }
  90. std::cout << "creater free named pipe" << std::endl;
  91. }
  92. if (_fd != DefaultFd) close(_fd);
  93. }
  94. private:
  95. const std::string _fifo_path;// 管道文件的路径
  96. int _id;// 使用管道文件当前进程的pid
  97. int _fd;// 打开文件的方式,文件描述符
  98. };
server.cc  // 服务端
  1. #include "namedPipe.hpp"
  2. // server进程read的方式打开管道文件,而且也要管理命名管道的整个生命周期
  3. int main()
  4. {
  5. // 利用管道文件的类创建一个变量,并以创建者的身份调用构造函数创建公共的管道文件(comm_path)
  6. NamePiped fifo(comm_path, Creater);
  7. // 对于读端而言,如果我们打开文件,但是写还没来,我会阻塞在open调用中,直到对方打开
  8. // 进程同步
  9. if (fifo.OpenForRead())
  10. {
  11. std::cout << "server open named pipe done" << std::endl;
  12. sleep(3);
  13. while (true)
  14. {
  15. std::string message;
  16. int n = fifo.ReadNamedPipe(&message);// 把对应的数据读到message中
  17. if (n > 0)
  18. {
  19. std::cout << "Client Say> " << message << std::endl;
  20. }
  21. else if (n == 0)
  22. {
  23. std::cout << "Client quit, Server Too!" << std::endl;
  24. break;
  25. }
  26. else
  27. {
  28. std::cout << "fifo.ReadNamedPipe Error" << std::endl;
  29. break;
  30. }
  31. }
  32. }
  33. // 程序结束时,会自动调用析构函数,结束管道文件
  34. return 0;
  35. }
client.cc // 客户端
  1. #include "namedPipe.hpp"
  2. // write
  3. int main()
  4. {
  5. // 以使用者的身份,只使用公共管道的文件(comm_path)
  6. NamePiped fifo(comm_path, User);
  7. if (fifo.OpenForWrite())
  8. {
  9. std::cout << "client open namd pipe done" << std::endl;
  10. while (true)
  11. {
  12. std::cout << "Please Enter> ";// 请用户输入消息
  13. std::string message;
  14. std::getline(std::cin, message);// 按行从cin中获取消息,将消息输入到message中
  15. fifo.WriteNamedPipe(message);
  16. }
  17. }
  18. return 0;
  19. }

二、共享内存

  • 匿名管道和命名管道都是通过复用文件的内核数据结构的代码,来进行进程间的通信;
  • 我们实际在通信时,除了上面的文件版的;系统设计者专门为了通信,在内核当中重新从0搭一套班子,从0开始设计进程间的通信方案;

第一种通信方案:本地通信方案的代码:System V IPC
System V:标准 IPC:进程间通信

2.1、共享内存实现进程间通信的原理

  •  共享内存实现进程间通信,是操作系统在实际物理内存开辟一块空间,进程1和2都在堆和栈之间存在共享区,进程1和2都是通过各自的页表建立共享内存挂接到各自进程地址空间中的共享区并建立映射关系;
  • OS系统提供系统调用接口,让用户使用,使一个进程往该空间写入内容时,另外一进程访问该空间,读取写入的值,即实现了进程间的通信;
  • AB、CD、EF、XY----共享内存在系统中可以同时存在多份,供不同个数,不同对进程同时进行通信;
  • OS注定了要对共享内存进行管理! --- 先描述,在组织 --- 共享内存,不是简单的一段内存空间,也要有描述并管理共享内存的数据结构和匹配的算法;
  • 共享内存 = 内存空间(数据) + 共享内存的属性!

描述共享内存的结构体Struct Shm中一定要有标识共享内存唯一性的字段!

2.2、共享内存的相关接口

  1. man shmget
  2. // 查看申请一个System V版本的共享内存
int shmget(key_t key,size_t size,int shmflg);
  • 参数1:用户形成的一个key值,这个key值进程A、B都能形成,进程A在创建共享内存时,经过系统调用把key值设置为共享内存对应的结构体中唯一性的字段,那么此时进程B就能通过这个key值直接找到对应的共享内存(进程A和B不用做任何的通信,就能通过key值看到同一个共享内存);
  • 参数2:共享内存的大小;
  • 参数3:标记位。

参数3:

  • IPC_CREAT:如果你要创建的共享内存不存在,创建之;如果存在,获取该共享内存并返回;
  • IPC_EXCL:单独使用没有意义,只有和IPC_CREAT组合才有意义;
  • IPC_CREAT | IPC_EXCL:如果你要创建的共享内存不存在,创建之;如果存在,出错返回。(如果使成功返回的话,就意味着共享内存shm使全新的)

key是什么?参考参数1的解释。

为什么要有key?为了让两个进程能通过key值找同一份资源(共享内存)。

key值是用户形成的,用户如何形成的呢?使用ftok()系统调用接口形成。

int ftok(const char* pathname,int proj id);
  • 是OS提供的,但不属于系统调用;
  • 需要用户随便设一个路径,随便设一个项目id;
  • 只是做一些算法方面的设计,比如:将参数1和参数2都当作一个整数,两个数字再加一个随机数,形成一个唯一的key值;
  • 返回值:成功是返回共享内存的标识符(key);失败返回-1,并设置错误码。

b进程怎么知道A进程通过系统调用shmget()接口让OS创建共享内存呢?

  1. 进程A和B通过使用同样的ftok()函数,对ftok()函数使用同样的参数,就能得到同样的key值;
  2. 进程A将key值设置为共享内存对应的结构体中唯一性的字段,进程B通过ftok()函数,得到同样的key值,从而找到共享内存。

shmget()函数的返回值是共享内存的标识符,ftok()函数的返回值也是共享内存的标识符(key值),着两者有何区别呢?

  • key:属于用户形成,内核使用的一个字段,用户不能使用key来进行shm的管理。内核进行区分shm的唯一性的字段。(类似于文件的地址);
  • shmid:内核给用户返回的一个标识符,用来进行用户级对共享内存进行管理的id值。(类似于fd)

原先的进程创建一个文件,进程结束之后,文件会被自动释放;
可是对于共享内存来说:shmget()函数的参数3使用IPC_CREAT | IPC_EXCL这两个标记位:会看到第一次进程使用shmget()函数创建共享内存,会有对应的key值和共享内存;进程结束后,第二次进程再次使用shmget()函数创建共享内存,会发现创建共享内存失败,返回-1,因为原先的key值还存在,已经被占用了,所以得出的结论:共享内存不随着进程的结束而自动释放
共享内存不是由进程创建的,而是由OS让进程使用shmget()系统调用接口创建的。
我们要释放共享内存,不然共享内存会一直存在,直到系统重启。
手动释放(指令或者其它的系统调用)

  • 控制共享内存
  1. int shmctl(int shmid,int cmd,struct shmid_ds *buf);
  2. // 移除成功返回0,否则失败返回-1
  3. // 参数1:OS给用户的共享内存的标识符
  4. // 参数2:将cmd替换成IPC_RMID
  5. // 参数3:OS提供的一个内核级的数据结构(共享内存),来获取共享内存的属性;也可以设置共享内存的结构体,设置成nullptr就可

IPC_RMID:是一个命令,大写的形式,也是一个宏,用来进行标志共享内存当前是被删除的。

IPC_STAT:使用这个命令,使OS在调用shmctl()系统调用接口时,会把内核当中共享内存所有的属性拷贝到参数3的结构体中。

  • 将共享内存段挂接到进程地址空间
  1. // 将System V标准的共享内存挂接到对应的进程地址空间当中
  2. void *shmat(int shmid,const void *shmaddr,int shmflg);
  3. // 参数1:用户级的共享内存标识符;
  4. // 参数2:对应的共享内存挂接到哪个地址上,今天不考虑,设为nullptr;
  5. // 参数3:设置共享内存的访问权限,默认是读写的,设置为0;
  6. // 成功返回:地址空间中,共享内存的起始地址;失败:返回nullptr
  • 使共享内存在进程地址空间中分离
  1. int shmdt(const void *shmaddr);
  2. // 参数:shmat()接口的返回值(进程地址空间的起始地址)

dt:detach(分离)  shm:共享内存  at:attach(挂接、关联的意思)

  • ipcs -m:查找共享内存
  • perms:共享内存的权限
  • nattch:当前共享内存被挂接到几个进程地址空间上了
  • ipcrm -m shmid:删除共享内存(shmid是创建共享内存shmget()系统调用的返回值)

2.3、实现共享内存完成进程间的通信

shm.hpp // 共享内存的所有接口
  1. // 共享内存的所有接口 client:客户 server:服务器
  2. // 防止头文件包含
  3. #ifndef __SHM_HPP__
  4. #define __SHM_HPP__
  5. #include <iostream>
  6. #include <string>
  7. #include <cerrno>
  8. #include <cstdio>
  9. #include <cstring>
  10. #include <sys/ipc.h>
  11. #include <sys/shm.h>
  12. #include <unistd.h>
  13. // 定义两个身份标识,一个创建共享内存,一个使用共享内存
  14. const int gCreater = 1;
  15. const int gUser = 2;
  16. const std::string gpathname = "/home/whb/code/111/code/lesson22/4.shm";// 设路径为当前的路径
  17. const int gproj_id = 0x66;// 随便设一个项目id
  18. const int gShmSize = 4097; // 定义一个共享内存的大小,建议共享内存的大小为4096的整数倍
  19. class Shm
  20. {
  21. private:
  22. // 获取公共的key值
  23. key_t GetCommKey()
  24. {
  25. // string类型的对象_pathname中的C格式的字符串
  26. key_t k = ftok(_pathname.c_str(), _proj_id);
  27. if (k < 0)
  28. {
  29. perror("ftok");
  30. }
  31. return k;
  32. }
  33. // 创建一个共享内存的公共的方法
  34. int GetShmHelper(key_t key, int size, int flag)
  35. {
  36. int shmid = shmget(key, size, flag);
  37. if (shmid < 0)
  38. {
  39. perror("shmget");
  40. }
  41. return shmid;
  42. }
  43. // 将角色转换成字符串
  44. std::string RoleToString(int who)
  45. {
  46. if (who == gCreater)
  47. return "Creater";
  48. else if (who == gUser)
  49. return "gUser";
  50. else
  51. return "None";
  52. }
  53. // 将共享内存挂接到进程的地址空间中
  54. void* AttachShm()
  55. {
  56. if (_addrshm != nullptr)
  57. DetachShm(_addrshm);
  58. // shmat():挂接到进程地址空间的接口
  59. void* shmaddr = shmat(_shmid, nullptr, 0);
  60. if (shmaddr == nullptr)
  61. {
  62. perror("shmat");
  63. }
  64. std::cout << "who: " << RoleToString(_who) << " attach shm..." << std::endl;
  65. return shmaddr;// 返回进程的地址空间的起始地址
  66. }
  67. // 分离共享内存和进程地址空间的关联
  68. void DetachShm(void* shmaddr)
  69. {
  70. if (shmaddr == nullptr)
  71. return;
  72. // shmdt()接口
  73. shmdt(shmaddr);
  74. std::cout << "who: " << RoleToString(_who) << " detach shm..." << std::endl;
  75. }
  76. public:
  77. Shm(const std::string& pathname, int proj_id, int who)
  78. : _pathname(pathname), _proj_id(proj_id), _who(who), _addrshm(nullptr)
  79. {
  80. _key = GetCommKey();
  81. if (_who == gCreater)
  82. GetShmUseCreate();
  83. else if (_who == gUser)
  84. GetShmForUse();
  85. _addrshm = AttachShm();// 创建好共享内存之后,就直接挂接到进程地址空间中
  86. std::cout << "shmid: " << _shmid << std::endl;
  87. std::cout << "_key: " << ToHex(_key) << std::endl;
  88. }
  89. ~Shm()
  90. {
  91. if (_who == gCreater)
  92. {
  93. // shmctl():删除指定的共享内存
  94. int res = shmctl(_shmid, IPC_RMID, nullptr);
  95. }
  96. std::cout << "shm remove done..." << std::endl;
  97. }
  98. // 将key值转16进制
  99. std::string ToHex(key_t key)
  100. {
  101. char buffer[128];
  102. // snprintf()进行格式化输出,将输出的内容放在一个缓冲区里,将key格式化成0x16进制
  103. // 因为使用ipcs -m指令查找到的共享内存的key值是0x16进制的
  104. snprintf(buffer, sizeof(buffer), "0x%x", key);
  105. return buffer;
  106. }
  107. // 使用一个创建共享内存的公共的方法来创建一个共享内存
  108. bool GetShmUseCreate()
  109. {
  110. if (_who == gCreater)
  111. {
  112. _shmid = GetShmHelper(_key, gShmSize, IPC_CREAT | IPC_EXCL | 0666);
  113. if (_shmid >= 0)
  114. return true;
  115. std::cout << "shm create done..." << std::endl;
  116. }
  117. return false;
  118. }
  119. // 使用者使用共享内存
  120. bool GetShmForUse()
  121. {
  122. if (_who == gUser)
  123. {
  124. // 共享内存创建者创建好之后,使用者不需要再创建了
  125. _shmid = GetShmHelper(_key, gShmSize, IPC_CREAT | 0666);
  126. if (_shmid >= 0)
  127. return true;
  128. std::cout << "shm get done..." << std::endl;
  129. }
  130. return false;
  131. }
  132. // 将共享内存清0
  133. void Zero()
  134. {
  135. if (_addrshm)
  136. {
  137. // 将_addrshm共享内存全部初始化为0
  138. memset(_addrshm, 0, gShmSize);
  139. }
  140. }
  141. void* Addr()
  142. {
  143. return _addrshm;
  144. }
  145. // 获取共享内存的属性
  146. void DebugShm()
  147. {
  148. struct shmid_ds ds;
  149. // IPC_STAT:使用这个命令,使OS在调用shmctl()系统调用接口时,会把内核当中共享内存所有的属性拷贝到参数3的结构体中
  150. int n = shmctl(_shmid, IPC_STAT, &ds);
  151. if (n < 0) return;
  152. std::cout << "ds.shm_perm.__key : " << ToHex(ds.shm_perm.__key) << std::endl;
  153. std::cout << "ds.shm_nattch: " << ds.shm_nattch << std::endl;
  154. }
  155. private:
  156. key_t _key;// 共享内存中唯一性的标识符
  157. int _shmid;// OS给用户使用的共享内存的标识符
  158. std::string _pathname;// 为了ftok()函数的参数1随机设置一个共享内存的路径变量
  159. int _proj_id; // 为了ftok()函数的参数2随机设置一个共享内存的id变量
  160. int _who;// 进程的身份
  161. void* _addrshm;// 共享内存挂接到进程地址空间的起始地址的变量
  162. };
  163. #endif
server.cc // 服务端(读端)
  1. #include "Shm.hpp"
  2. #include "namedPipe.hpp"
  3. int main()
  4. {
  5. // 1. 创建共享内存
  6. // 使用Shm的类型创建一个shm的对象,调用构造函数
  7. Shm shm(gpathname, gproj_id, gCreater);
  8. char* shmaddr = (char*)shm.Addr();
  9. shm.DebugShm();
  10. // // 2. 创建管道
  11. // NamePiped fifo(comm_path, Creater);
  12. // fifo.OpenForRead();
  13. // while(true)
  14. // {
  15. // // std::string temp;
  16. // // fifo.ReadNamedPipe(&temp);
  17. // std::cout << "shm memory content: " << shmaddr << std::endl;
  18. // }
  19. sleep(5);
  20. return 0;
  21. }
client.cc // 客户端(写端)
  1. #include "Shm.hpp"
  2. #include "namedPipe.hpp"
  3. int main()
  4. {
  5. // 1. 创建共享内存
  6. Shm shm(gpathname, gproj_id, gUser);
  7. shm.Zero();
  8. char* shmaddr = (char*)shm.Addr();// 返回共享内存挂接到进程地址空间的起始地址
  9. sleep(3);
  10. // 2. 打开管道
  11. NamePiped fifo(comm_path, User);
  12. fifo.OpenForWrite();
  13. // 当成string
  14. char ch = 'A';
  15. while (ch <= 'Z')
  16. {
  17. // 将shmaddr当作一个数组,每隔两秒向共享内存中写入一个字符
  18. shmaddr[ch - 'A'] = ch;
  19. std::string temp = "wakeup";
  20. std::cout << "add " << ch << " into Shm, " << "wakeup reader" << std::endl;
  21. fifo.WriteNamedPipe(temp);
  22. sleep(2);
  23. ch++;
  24. }
  25. return 0;
  26. }

共享内存不提供对共享内存的任何保护机制,比如:写端向共享内存中写入hello world数据,读端是不会等待写端的,会直接读取数据,写端可能只写了hello数据,还来不及写入world数据,就被读端读取了,读取的数据需要解析,但是数据不完整,会解析有误。这种问题叫做数据不一致。

所以我们可以提供管道的机制,管道提供了同步机制,让客户端和服务端两个进程除了创建共享内存之外,也把管道建立好;服务器读数据之前,都得先读管道,管道里没数据,就等待;当客户端写完数据后,让管道通知服务器,使服务器从管道中读取数据。

至于管道文件里写入和读取什么样的数据,这些不重要;重要的是我们让管道文件的同步机制来变相的使两个进程在共享内存中写入和读取数据的过程也建立同步机制。

我们在上面管道文件的 namedPipe.hpp 和 Mikefile 文件就看拿下来,这里就不拿了。

我们在访问共享内存的时候,没有用任何的系统调用。

共享内存是所有进程间通信(IPC)中,速度最快的,因为,共享内存大大减少了数据的拷贝次数!

  • 命名管道:写入数据时,先将数据写入语言级的缓冲区,再通过文件描述符表找到文件的地址,将数据拷贝到文件级的内核缓冲区;读取数据时,先通过文件描述符表找到文件的地址,将文件级的内核缓冲区的数据拷贝到用户级的缓冲区。
  • 共享内存:因为物理内存中的共享内存被挂接到了进程的地址空间中的堆栈之间的共享区,所以可以直接通过进程地址空间返回的起始地址写入共享内存中,大大减少了拷贝的次数,所以说共享内存是速度最快的通信方式。

三、system V消息队列 - 选学了解即可

原理:OS能够开辟结构性的内存空间,OS会在OS内部给我们申请一个消息队列(msg_queue),刚开始队列为空,然后我们有两个进程A和B,进程A可以使用消息队列的系统调用接口,来向消息队列中放入节点,数据块会自动的来连入消息队列;因为进程间通信要让两个进程看到同样的资源,所以进程B也可以往消息队列中放入节点;它们都往公共的队列里放节点时,那么此时B可以拿A放的节点,A也可以拿B放的节点,这种方式叫消息队列。

因为进程A和B都在消息队列中放入很多的节点,为了区分这些节点,所以要求消息队列中发出去的节点都必须叫做有类型。

  • 消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法
  • 每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值
  • IPC资源必须删除,否则不会自动清除,除非重启,所以system V IPC资源的生命周期随内核

获取消息队列

控制消息队列

四、system V信号量 - 选学了解即可

5个概念:

  • 多个执行流(进程),能看到的一份资源:共享资源;
  • 被保护起来的资源 --- 临界资源 --- 用同步和互斥的方式保护共享资源 --- 临界资源;
  • 互斥:任何时刻只能有一个进程在访问资源;
  • 资源 --- 要被程序员访问 --- 资源被访问,朴素的认识,就是通过代码访问 --- 访问 = 访问共享资源的代码(临界区) + 不访问共享资源的代码(非临界区);
  • 所谓的对共享资源进行保护 --- 临界资源 --- 本质是对访问共享资源的代码和保护(临界区)!

同步:让多个执行流之间,在执行时,具有一定的顺序性。
互斥:一个公共资源时,我正在访问,你就不能访问,任何一个时刻,只允许我一个人访问这部分公共资源。

对于信号量的理论理解:

假如共享内存中有1000字节,可以把1000字节看成50字节一块一块的小的内存块,我们不想整体使用1000个字节的共享内存,那么A进程访问第一块50个字节的区域,B进程访问第5块50个字节大小的区域,C进程访问第n个50个字节大小的区域....,可以允许多个执行流同时访问共享内存中的局部性的资源。

  1. if (gcount > 0)
  2. {
  3. gcount--;
  4. }
  5. else
  6. {
  7. wait;// 等待的过程,就是++的时候(联想电影院的票数)
  8. }

信号量的本质:就是一个计数器,比如:gcount = 25.

申请信号量 -- ->就是P操作;访问公共资源(共享内存);释放信号量 ++ ->就是V操作。

申请和释放信号量,还要保证安全性,叫做PV操作。

可不可以用一个进程级别的全局变量来充当对公共资源的保护呢?不能。
原因:对于父子进程来说,会发生写实拷贝;对于两个毫不相干的进程来说,一个进程定义一个全局变量,另一个进程是看不到的;所以要用信号量来对公共资源的保护。
信号量也是一个公共资源。

信号量的操作

允许用户一次申请多个信号量,多个信号量叫信号量集(用数组来维护的)。

  1. int semget(key_t key,int nsems,int semflg);// 获取信号量
  2. // 参数2:申请一个信号量集中有几个信号量

允许用户一次申请多个信号量和一个信号量的值为n是两个概念


总结

好了,本篇博客到这里就结束了,如果有更好的观点,请及时留言,我会认真观看并学习。
不积硅步,无以至千里;不积小流,无以成江海。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/640436
推荐阅读
相关标签
  

闽ICP备14008679号