【Linux】进程间通信——system V(共享内存 | 消息队列 | 信号量)(上) https://developer.aliyun.com/article/1565752
💫 共享内存的特点
概念:
共享内存的生命周期是随OS的,而不是随进程的,这是所有System V进程间通信的共性。
共享内存的优点:
共享内存是所有进程间通信速度是最快的,因为共享内存是被双方所共享,只要写入对方就能立即看到,能大大减少数据的拷贝次数。
总结:
综合考虑管道和共享内存,考虑键盘输入,和显示器输出,对于同一份数据:共享内存有几次数据拷贝,管道有几次数据拷贝。
管道:需要通过键盘输入到自己定义的缓冲区char buffer[],将数据拷贝到buffer中,调用write接口在把buffer里的数据拷贝到管道里,
另一进程也有定义buffer缓冲区,调用read读取把数据从管道里读取到buffer里,在把数据显示到显示器上:
图解:
共享内存的缺点:
不给我们进行同步和互斥的操作,没有对数据做任何保护。客户端和服务端没做保护,如果想做保护要用到信号量,对共享内存进行保护,写完通过读端进行读取。
🌙 消息队列(了解)
💫 消息队列的概念
消息队列是OS提供的内核级队列,消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法,每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值。
💫 消息队列数据结构
结构如下:
struct msqid_ds { struct ipc_perm msg_perm; /* Ownership and permissions */ time_t msg_stime; /* Time of last msgsnd(2) */ time_t msg_rtime; /* Time of last msgrcv(2) */ time_t msg_ctime; /* Time of last change */ unsigned long __msg_cbytes; /* Current number of bytes in queue (nonstandard) */ msgqnum_t msg_qnum; /* Current number of messages in queue */ msglen_t msg_qbytes; /* Maximum number of bytes allowed in queue */ pid_t msg_lspid; /* PID of last msgsnd(2) */ pid_t msg_lrpid; /* PID of last msgrcv(2) */ };
消息队列数据结构的第一个成员是msg_perm
,它和shm_perm
是同一个类型的结构体变量,ipc_perm
结构体的定义如下:
struct ipc_perm { key_t __key; /* Key supplied to msgget(2) */ uid_t uid; /* Effective UID of owner */ gid_t gid; /* Effective GID of owner */ uid_t cuid; /* Effective UID of creator */ gid_t cgid; /* Effective GID of creator */ unsigned short mode; /* Permissions */ unsigned short __seq; /* Sequence number */ };
💫 消息队列相关函数
msgget:获取消息队列
#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> int msgget(key_t key, int msgflg); RETURN VALUE If successful, the return value will be the message queue identifier (a nonnegative integer), otherwise -1 with errno indicating the error.
参数讲解:
- key:ftok函数生成一个key值,这个key值作为msgget函数的第一个参数
- msgflg:与创建共享内存时使用的shmget函数的第三个参数相同。
返回值:msgget函数返回的一个有效的消息队列标识符
msgctl:控制消息队列
#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> int msgctl(int msqid, int cmd, struct msqid_ds *buf);
msgsnd:发数据
#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
- msqid:表示消息队列的用户级标识符。
- msgp:表示待发送的数据块。
- msgsz:表示所发送数据块的大小
- msgflg:表示发送数据块的方式,一般默认为0即可
成功返回0,失败返回-1
msgrcv:读取消息队列
#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
- msqid:表示消息队列的用户级标识符。msgp:表示获取到的数据块,是一个输出型参数。
- msgsz:表示要获取数据块的大小
- msgtyp:表示要接收数据块的类型,msgflg:表示发送数据块的方式,一般默认为0即可
- 成功返回实际获取到mtext数组中的字节数,失败返回-1。
🌙 信号量(了解)
💫 信号量相关概念
概念:
信号量的本质是一个计数器,通常用来表示公共资源中,资源数的多少问题。信号量主要用于同步和互斥的。
公共资源:
能被多个进程同时访问的资源,访问没有保护的公共资源:数据不一致问题。要让不同的进程看到同一份资源是为了通信,通信是为了让进程间实现协同,而进程之间具有独立性,所以为了解决独立性问题要让进程看到同一份资源,但是会导致数据不一致的问题。
被保护起来的公共资源称为临界资源,而进程要使用资源一定是该进程有对应的代码来访问这部分临界资源称为临界区,但是多个进程看到同一份资源是少数情况,大部分申请自己的资源用自己的代码区访问。有临界区自然就有非临界区,不访问公共资源的代码。
如何保护公共资源:互斥&&同步
互斥:由于各进程要求共享资源,而且有些资源需要互斥使用,因此各进程间竞争使用这些资源,进程的这种关系为进程的互斥
原子性:要么不做、要么做完两态的这种情况。比如支付转账
如果用全局的整数来替代信号量?
全局的整数在父子关系的进程上都看不到,要发生写时拷贝,而不同的进程更看不到,所以进程间想看到同一个计数器得让进程看到同一个计数器。
为什么要信号量?
当我们想要某种资源的时候可以通过信号量进行预;,共享资源被使用的方式:作为一个整体使用;划分成为一个一个的资源部分
图解:
💫 信号量数据结构
结构:
struct semid_ds { struct ipc_perm sem_perm; /* Ownership and permissions */ time_t sem_otime; /* Last semop time */ time_t sem_ctime; /* Last change time */ unsigned long sem_nsems; /* No. of semaphores in set */ };
信号量数据结构的第一个成员也是ipc_perm
类型的结构体变量,ipc_perm
结构体的定义如下:
struct ipc_perm { key_t __key; /* Key supplied to semget(2) */ uid_t uid; /* Effective UID of owner */ gid_t gid; /* Effective GID of owner */ uid_t cuid; /* Effective UID of creator */ gid_t cgid; /* Effective GID of creator */ unsigned short mode; /* Permissions */ unsigned short __seq; /* Sequence number */ };
💫 信号量pv操作
概念:
所有的进程在访问公共资源之前,都必须申请sem信号量,而申请sem信号量的前提是所有进程必须先看到同一个信号量,所以信号量本身就是公共资源,同时,信号量必须保证自身操作的安全性,++,–操作是原子
💫 信号量相关函数
semget:申请信号量
#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int semget(key_t key, int nsems, int semflg); RETURN VALUE If successful, the return value will be the semaphore set identifier (a nonnegative integer), otherwise -1 is returned, with errno indicating the error.
参数讲解:
- key:使用ftok函数生成一个key值,这个key值作为semget函数的第一个参数。
- nsems:表示创建信号量的个数。
第三个参数,与创建共享内存时使用的shmget函数的第三个参数相同。
返回值:信号量集创建成功时,semget函数返回的一个有效的信号量集标识符
semctl:信号量的删除
#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int semctl(int semid, int semnum, int cmd, ...);
semop:信号量操作
#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int semop(int semid, struct sembuf *sops, unsigned nsops);
我们可以发现,共享内存、消息队列、信号量接口相似度非常高,获取与删除,都是system V标准的进程间通信。
OS如何管理:先描述,在组织,对相关资源的内核数据结构做管理,对于共享内存、消息队列、信号量的第一个成员都是ipc_perm:
struct ipc_perm { key_t __key; /* Key supplied to shmget(2) */ uid_t uid; /* Effective UID of owner */ gid_t gid; /* Effective GID of owner */ uid_t cuid; /* Effective UID of creator */ gid_t cgid; /* Effective GID of creator */ unsigned short mode; /* Permissions + SHM_DEST and SHM_LOCKED flags */ unsigned short __seq; /* Sequence number */ };
虽然内部的属性差别很大,但是维护它们的数据结构的第一个成员确实一样的,都是ipc_perm类型的成员变量,都可以通过key来标识唯一性。这样设计的好处:在操作系统内可以定义一个struct ipc_perm类型的数组,此时每当我们申请一个IPC资源,就在该数组当中开辟一个这样的结构。((struct shmid_ds*)perms[0],强转,此时就可以访问其他剩下的属性)
🌟结束语
今天内容就到这里啦,时间过得很快,大家沉下心来好好学习,会有一定的收获的,大家多多坚持,嘻嘻,成功路上注定孤独,因为坚持的人不多。那请大家举起自己的小手给博主一键三连,有你们的支持是我最大的动力💞💞💞,回见。