作为网站门牌号的域名,域名是如何创造的?现在企业网站需要配合域名使用,企业邮箱等等系统也要陪配合域名使用,域名与企业办公息息相关,域名不仅仅指一个网址...
2024-11-18 13
Linux:进程间通信(二.共享内存详细讲解以及小项目使用和相关指令、消息队列、信号量)
上次结束了进程间通信一:Linux:进程间通信(一.初识进程间通信、匿名管道与命名管道、共享内存)
实现进程间通信的前提就是如何让不同的进程看到同一份资源
- 匿名管道我们是通过子进程继承父进程打开的资源
- 命名管道是通过两个进程都打开具有较早性标识的命名管道文件(路径+文件名)
- 共享内存其实是通过OS创建一块shm
System V共享内存(Shared Memory)是一种Linux中用于进程间通信(IPC)的机制。它允许多个进程访问同一块物理内存区域,从而实现数据的快速共享和交换。
原理:
使用方式:
特性:
无需内核参与:在共享内存中,多个进程可以直接访问同一块物理内存区域,而无需通过内核进行数据的拷贝和传输。这样可以避免了进程间切换和内核态和用户态之间的数据拷贝,从而提高了通信的效率。
ftok()
函数 Linux中用于生成一个较早的键值(key)的系统调用,这个键值通常用于在进程间通信(IPC)中标识共享内存段、消息队列或信号量集。ftok()
函数基于一个已经存在的文件路径和一个非零的标识符(通常是一个小的正整数)来生成这个键值。
#include <sys/ipc.h> #include <sys/types.h> key_t ftok(const char *pathname, int proj_id);
参数:
pathname
:指向一个已经存在的文件路径的指针。这个文件通常被用作生成键值的“种子”或“基础”。proj_id
:一个非零的标识符,通常是一个小的正整数。这个值将与文件路径一起被用于生成键值。返回值:如果成功,ftok()
函数返回一个较早的键值(key_t
类型),该键值可以在后续的 IPC 调用(如 shmget()
, msgget()
, semget()
等)中用作参数。如果失败,则返回 (key_t) -1
并设置 errno
以指示错误。
shmget()
:创建或获取共享内存shmget()
系统调用用于创建一个新的共享内存对象,或者如果它已存在,则返回该对象的标识符。
函数原型:
int shmget(key_t key, size_t size, int shm***);
参数:
key
:一个键,用于较早标识共享内存对象。通常使用ftok()
函数生成。
- 共享内存在内核中同时可以存在很多个,OS必须要管理所有的共享内存
- 如何管理呢?先描述,在组织
- 系统中会存在很多共享内存,怎么保证,多个不同的进程看到的是同共享内存呢? 要给共享内存提供较早性的标识
key
便是那个较早性标识符。那么为什么这个key要由我们用户来传入呢?
- 如果然系统生成,将值返回让我们得到。那我们如何给另外一个进程呢?要做到就要有进程间通信,这不倒反天罡了?
size
:共享内存的大小(以字节为单位)。
shm***
:权限标志和选项。通常设置为IPC_CREAT
(如果对象不存在则创建,存在的话直接获取)和权限(如0666
)。
若设置为IPC_CREAT|IPC_EXCL
(如果对象不存在则创建,存在的话出错返回)
返回值:成功时返回共享内存对象的标识符;失败时返回-1并设置errno
。
shmctl()
:控制共享内存shmctl()
系统调用用于获取或设置共享内存的属性,或者删除共享内存对象。
函数原型:
int shmctl(int shmid, int cmd, strUCt shmid_ds *buf);
参数:
shmid
:共享内存对象标识符。cmd
:要执行的操作。例如,IPC_RMID
用于删除共享内存对象,IPC_STAT
用于获取其状态。buf
:指向shmid_ds
结构的指针,用于传递或接收共享内存的状态信息。返回值:成功时返回0;失败时返回-1并设置errno
。
shmat()
:将共享内存关联到进程的地址空间shmat()
(attach)系统调用用于将共享内存对象关联到调用进程的地址空间。
函数原型:
void *shmat(int shmid, const void *shmaddr, int shm***);
参数:
shmid
:shmget()
返回的共享内存对象标识符。shmaddr
:希望将共享内存附加到的进程的地址。如果设置为NULL,则系统选择地址(一般都这样)。shm***
:通常设置为0或SHM_RND
(使附加地址向下舍入到最接近的SHMLBA边界)。返回值:成功时返回共享内存附加到进程的地址;失败时返回(void *)-1并设置errno
。
shmdt()
:取消共享内存的关联shmdt()
系统调用用于取消之前通过shmat()
附加到进程的共享内存的关联。
函数原型:
int shmdt(const void *shmaddr);
参数:
shmaddr
:shmat()
返回的共享内存附加到进程的地址。返回值:成功时返回0;失败时返回-1并设置errno
。
makefile:
.PHONY:All all:shm_client shm_server shm_server:ShmServer.cc g++ -o $@ $^ -std=c++11 shm_client:ShmClient.cc g++ -o $@ $^ -std=c++11 .PHONY:clean clean: rm -f shm_client shm_server
#pragma once #include <iostream> #include <cerrno> #include <cstring> #include <cstdlib> #include <string> #include <sys/ipc.h> #include <sys/shm.h> #include <sys/types.h> using namespace std; const char *pathname = "/home/zc/study/lesson26/5.4_shm_ipc"; const int proj_id = 0x1; const int defaultsize = 4096; // 单位是字节 key_t GetShmKeyOrDie() { key_t key = ftok(pathname, proj_id); if (key == -1) { cerr << "ftok error, errno : " << errno << ", error string: " << strerror(errno) << std::endl; exit(1); // 出错就直接退出 } return key; } int CreateShmOrDie(key_t key, int size, int flag) { int shmid = shmget(key, size, flag); if (shmid < 0) { std::cerr << "shmget error, errno : " << errno << ", error string: " << strerror(errno) << std::endl; exit(2); // 出错就直接退出 } return shmid; } int CreateShm(key_t key, int size) { return CreateShmOrDie(key, size, IPC_CREAT | IPC_EXCL | 0666); // 没有就创建,有就报错,权限666 } int GetShm(key_t key, int size) { return CreateShmOrDie(key, size, IPC_CREAT); // 不存在则创建,存在的话直接获取 } void DeleteShm(int shmid) { int n = shmctl(shmid, IPC_RMID, nullptr); if (n == -1) { cerr << "shmctl error" << errno << endl; } else { cout << "delete successfully" << endl; } } void *ShmAttach(int shmid) { void *addr = shmat(shmid, nullptr, 0); if ((long long int)addr == -1) { std::cerr << "shmat error" << std::endl; return nullptr; } return addr; } void ShmDt(void *addr) { int n = shmdt(addr); if (n < 0) { cerr << "shmdt error" << endl; } }
#include "Comm.hpp" int mAIn() { // 获取key key_t key = GetShmKeyOrDie(); cout << "key:" << key << endl; // 获取shmid int shmid = GetShm(key, defaultsize); cout << "shmid:" << shmid << endl; // 进行挂接 void *tem = ShmAttach(shmid); char *addr = (char *)tem; cout << "Attch successfully" << endl; // 进行通信,这里进行写入 for (char ch = 'A'; ch <= 'Z'; ch++) { addr[ch - 'A'] = ch; } // 取消挂接 ShmDt(tem); return 0; }
#include "Comm.hpp" #include <unistd.h> int main() { // 获取key key_t key = GetShmKeyOrDie(); cout << "key:" << key << endl; // 创建共享内存 int shmid = CreateShm(key, defaultsize); cout << "shmid:" << shmid << endl; // 进行挂接 void *tem = ShmAttach(shmid); char *addr = (char *)tem; cout << "Attch successfully" << endl; // 进行通信,这里进行读 for (;;) { cout << "shm content: " << addr << std::endl; } // 取消挂接 ShmDt(tem); // 删除共享内存 sleep(50); DeleteShm(shmid); return 0; }
ipcs -m
显示的以下内容:
- 键值 (key):共享内存段的键值,用于较早标识一个共享内存段。在创建共享内存段时可以指定一个键值,其他进程可以通过这个键值来访问同一个共享内存段。
- shmid:共享内存段的标识符,是系统为每个共享内存段分配的较早标识符。其他进程可以通过
shmid
来操作同一个共享内存段。- 权限 (perms):共享内存段的权限信息,包括读、写、执行权限。通常以八进制形式表示,比如
0600
表示用户具有读写权限,其他用户没有权限。- 拥有者 (owner):共享内存段的拥有者,即创建该共享内存段的用户或进程。
- 大小 (bytes):共享内存段的大小,以字节为单位。表示该共享内存段所分配的内存空间大小。
- 附加进程数 (nattch):当前附加到共享内存段的进程数目。当一个进程附加到共享内存段时,
nattch
数加一;当一个进程分离(detach)时,nattch
数减一。
ipcrm -m <shmid>
<shmid>
是要删除的共享内存段的标识符。通过这个命令可以删除指定的共享内存段,释放其资源。
shmid与key分辨:
在共享内存的设计中,
key
和shmid
的使用确实是为了实现内核层和用户层之间的解耦,从而使它们在宏观层面上互不影响,具有独立性。下面详细解释一下这种设计的好处和原因:
System V消息队列是一种进程间通信的机制,允许进程之间通过消息进行通信。消息队列提供了一个消息缓冲区,进程可以向消息队列发送消息,也可以从消息队列接收消息。下面我们来详细讲解消息队列的原理以及相关函数。
消息队列结构:消息队列是一个由内核维护的消息缓冲区,通常由消息类型和消息数据组成。每个消息都包含一个消息类型和消息数据,进程可以根据消息类型选择接收特定类型的消息。
消息发送:进程可以调用系统调用函数向消息队列发送消息,将消息写入消息队列中。发送消息时,需要指定消息队列的标识符、消息类型以及消息数据等信息。
消息接收:进程可以调用系统调用函数从消息队列接收消息,读取消息队列中的消息。接收消息时,可以选择阻塞式接收或非阻塞式接收,进程可以根据需要选择适合的接收方式。
消息类型:每个消息都有一个消息类型,进程可以根据消息类型选择接收特定类型的消息。这样可以实现不同类型的消息传递和处理。
消息队列管理:消息队列由内核管理,进程可以通过系统调用函数对消息队列进行创建、删除、发送消息、接收消息等操作。
当使用System V消息队列相关函数时,需要了解函数的原型、参数和返回值。以下是这些函数的介绍:
msgget
:int msgget(key_t key, int msg***);
key
为消息队列的键值,msg***
为权限标志和操作标志的组合。errno
。msgctl
:int msgctl(int msqid, int cmd, struct msqid_ds *buf);
msqid
为消息队列的标识符,cmd
为控制命令,buf
为消息队列信息结构体指针。errno
。msgsnd
:int msgsnd(int msqid, const void *msgp, size_t msgsz, int msg***);
msqid
为消息队列的标识符,msgp
为消息缓冲区指针,msgsz
为消息长度,msg***
为标志位。errno
。msgrcv
:ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msg***);
msqid
为消息队列的标识符,msgp
为消息缓冲区指针,msgsz
为消息长度,msgtyp
为消息类型,msg***
为接收标志。errno
。信号量通常被用来保护公共资源,确保多个进程或线程能够有序地访问和使用这些资源
信号量是一个整型变量,通常用于控制对共享资源的访问。信号量的值可以表示可用资源的数量或者某个资源的占用情况
信号量:信号量本质是一个计数器,描述临界资源数量的计数器
为什么不能直接用一个int来进行呢?
无法在进程间共享 而且一个int
++
和- -
不是原子的
信号量:公共资源中,对局部性资源进行预定的机制。可以被用于对局部性资源进行预定,实现对资源的合理分配和控制
信号量是让不同的进程看到同一个计数器资源,而进程间通信是让不同的进程看到一份资源。所以信号量也属于进程间通信
进程申请信号量一旦成功:就一定有这个进程的资源了(相当于去看电影买票,一定有我们的座位了,而且别人也买不了这个座位)
当我们释放信号量后,这份资源才能给别人(看完电影后,这个座位才能接着被下一个买)
申请信号量和释放信号量来保护临界资源, 是大家都要遵守的规则(我们程序员)
==>
信号量本身就是共享资源信号量操作:
那谁来保护信号量呢? 通过使用原子的PV操作来保障
互斥和同步:
临界资源和临界区:
原子性:指的是操作对象的操作是不可分割的,要么整个操作完成,要么没有开始。在多线程或多进程环境中,保证操作的原子性是非常重要的,可以通过锁或原子操作等机制来实现。
保护临界区的本质:程序员需要保护临界区,确保在任何时刻只有一个执行流可以访问临界资源,避免数据竞争和不一致性。这通常通过使用锁、信号量或其他同步机制来实现。
内核中,所有的描述管理IPC资源的结构体,靠前个成员大家都一样 kern_ipc_perm。我们可以用指针数组来进行管理
在内核中,对IPC资源的管理也是转变为对数组的增删查改
类型不同我们怎么解决呢?——直接强转
- (msg_ queue*)ipc[0]> q _Time;
- (sem_array)ipc[1]->q_time;
- (shmid kernel)ipc[2]-> 。。。
那怎么知道是什么类型呢?
kern_ipc_perm里有一个有个mode变量,能来表示类型
#define IPC TYPE SHM Ox1 #define IPC TYPE MSG (0x1<<1) #define IPC TYPE SEM(0x1 << 2)//定义这三个宏后 shmid kernel* (kern ipc_perm* p) { if (p->mode & IPC TYPE SHM) { return (shmid kernel)ipc } else if (p->mode & IPC TYPE MSG) { /// } else { /// } }
好啦,我也是结束了实训,才到家!!!
感谢大家的支持
TAG:进程间通信
标签: 当前位置: 首页 > 问答
相关文章
作为网站门牌号的域名,域名是如何创造的?现在企业网站需要配合域名使用,企业邮箱等等系统也要陪配合域名使用,域名与企业办公息息相关,域名不仅仅指一个网址...
2024-11-18 13
说起建设网站,不得不提的就是网站绑定的域名,域名作为网站的门牌号,不仅仅要代表网站和企业的形象,更加要让访问者用起来方便快捷。小编就教大家如何为网站注...
2024-11-18 12
做一名企业家是多么匆忙啊! 从零开始创业是一件伤脑筋又有挑战性的事情,但同时也是一件非常有益且令人兴奋的事情。当您创立起您自己的事业,您将充满营销、...
2024-11-18 9
其实很多朋友们认为,注册域名的时候就需要选择自己喜欢的域名在进行注册,但是小编表示这样其实是不对的,域名注册则是被分为域名和域名交易平台两个部分,今天...
2024-11-18 7
我们在建造一个网站前,都会首先先思考挑选一个实用、好记的域名。可是实事是当咱们在查询域名时都会发现,有意义的域名,简直都被抢注一空。这时咱们就要改变一...
2024-11-17 9
作为中国最早的域名投资者之一,58同城的创始人姚劲波很早就把赶集网的同音域名ganjinwang.com收归旗下,现在打开该网址,仍然链接到58同城。...
2024-11-17 10
想必每一个做网站的朋友在注册域名的时候都是费尽脑汁的,好不容易想好了一个域名却发现已经被注册了,这个时候有一个批量域名查询工具是不是很方便,今天小编就...
2024-11-17 10