我们知道,进程间通信的本质就是:让不同的进程,看到同一份资源
这里要介绍的同一份资源就是:内存块,即 共享内存(shared memory,简写为 shm)
内存中的每块共享内存,会有一个 struct shm 结构体
,里面放着共享内存的全部属性,OS 通过这个结构体建立链表关系来对所有的共享内存进行管理,就等于把管理 shm 的问题转化成了管理链表的问题。
故:
共享内存
=
共享内存的内核数据结构 (伪代码:struct shm)
+
真正开辟的内存空间
头文件:
#include
#include
// umask的头文件如下
#include
#include
int shmget(key_t key, size_t size, int shmflg);
参数 key:
- 使用
ftok
函数设置的唯一标识码,他虽由用户设置,却是在内核中使用的
参数 size
- 需要申请共享内存块的大小,单位为字节,不足 PAGE 页(4KB)时,会向上对齐到 PAGE 页
参数 shmflg:
选项 IPC_CREAT and IPC_EXCL
单独使用 IPC_CREAT:创建一个共享内存,如果共享内存不存在,就创建,如果已经存在就获取已经存在的共享内存并返回。
IPC_CREAT | IPC_EXCL :IPC_EXCL 必须要配合 IPC_CREAT 使用,创建一个共享内存,如果共享内存不存在,就创建,如果已经存在就出错返回
意味着,一起使用时,如果创建成功,对应的shm,一定是最新的!IPC_CREAT | IPC_EXCL | 0666 :上面的基础上,添加权限(可以配合函数 umask(0) 使用)
返回值:
- 成功会返回一个共享内存标识符,失败返回 -1
头文件
#include
#include
key_t ftok(const char *pathname, int proj_id);
参数 pathname
- 用户设置的路径
参数 proj_id
- 用户设置的项目 id
返回值:
- 根据用户传入的参数,结合一定的算法,返回一个冲突概率很低的值。ket_t 就是一个 32 位的整数,是对 int 的封装
头文件
#include
#include
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数 shmid
- 需要的共享内存块的 shmid
参数 cmd:
- 选项 IPC_STAT:把用户传入 shmid 的相应内核数据结构信息复制到 buf 中(在调用者有读权限的情况下,才能成功)
- 选项 IPC_RMID:删除 shmid 为传入值的共享内存块
输出型参数 buf:
- 需要得到 ipc 信息时传一个相应类型的值用来接收结果
返回值:
- 失败返回 -1,成功则根据 cmd 传入的选项返回对应的值
//The buf argument is a pointer to a shmid_ds structure, defined in as follows:
struct shmid_ds {
struct ipc_perm shm_perm; /* Ownership and permissions */
size_t shm_segsz; /* Size of segment (bytes) */
time_t shm_atime; /* Last attach time */
time_t shm_dtime; /* Last detach time */
time_t shm_ctime; /* Last change time */
pid_t shm_cpid; /* PID of creator */
pid_t shm_lpid; /* PID of last shmat(2)/shmdt(2) */
shmatt_t shm_nattch; /* No. of current attaches */
...
};
//The ipc_perm structure is defined as follows (the highlighted fields are settable using IPC_SET):
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 */
};
头文件
#include
#include
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数 shmid
- 需要的共享内存块的 shmid
参数 shmaddr:
- 用户可以选择虚拟地址作为共享内存块的起始地址
- 用户一般不定义,设为 nullptr 让 OS 自主定义即可
参数 shmflg:
- 选项 SHM_RDONLY:只读
- 0:可以读写
返回值:
- 挂接成功,返回共享内存块的虚拟地址的起始地址
头文件
#include
#include
int shmdt(const void *shmaddr);
参数 shmaddr:
- 共享内存块的起始地址
返回值:
- 去关联成功返回 0,失败返回 -1
ipcs 就是进程间通信(ipc)资源
ipcs
:可以查看 消息队列、共享内存亏块、信号量
-m
:查看 共享内存块(memory)
-s
:查看 信号量(semaphore)
perms:权限
nattach:当前 ipc 挂接的进程数
ipcrm
:删除一个 消息队列、共享内存亏块、信号量
-m
:删除一个共享内存块,后接 shmid
两个进程管道通信一次,需要进行两次复制。而共享内存间的通信,可以让进程们直接在自己映射的地址空间中访问,减少了拷贝次数()
管道单方面关闭读写端会有相应的保护,而共享内存没有保护机制(同步互斥)。管道通过系统接口通信,共享内存直接通信
互斥:任何一个时刻,都只允许一个执行流在进行共享资源的访问,叫做加锁
我们把任何一个时刻,都只允许一个执行流在进行访问的共享资源,叫做 临界资源。凡是访问临界资源的代码,叫做临界区,控制进出临界区的手段造就了临界资源。
头文件
#include
#include
#include
int msgget(key_t key, int msgflg)
参数 key:
- 使用
ftok
函数设置的唯一标识码
参数 msgflg:
选项 IPC_CREAT and IPC_EXCL
单独使用 IPC_CREAT:创建一个共享内存,如果共享内存不存在,就创建,如果已经存在就获取已经存在的共享内存并返回。
IPC_CREAT | IPC_EXCL :IPC_EXCL 必须要配合 IPC_CREAT 使用,创建一个共享内存,如果共享内存不存在,就创建,如果已经存在就出错返回
意味着,一起使用时,如果创建成功,对应的shm,一定是最新的!IPC_CREAT | IPC_EXCL | 0666 :上面的基础上,添加权限(可以配合函数 umask(0) 使用)
返回值:
- 成功则返回消息队列的标识符
头文件
#include
#include
#include
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
参数 msqid
- 需要的消息队列的 msqid
参数 cmd:
- 选项 IPC_STAT:把用户传入 msqid 的相应内核数据结构信息复制到 buf 中(在调用者有读权限的情况下,才能成功)
- 选项 IPC_RMID:删除 msqid 为传入值的共享内存块
输出型参数 buf:
- 需要得到 ipc 信息时传一个相应类型的值用来接收结果
返回值:
- 成功返回 >= 0 的值,失败返回 -1
The msqid_ds data structure is defined in <sys/msg.h> as follows:
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) */
};
The ipc_perm structure is defined as follows (the highlighted fields are settable using
IPC_SET):
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 */
};
头文件
#include
#include
#include
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
参数 msqid:
- 发送和接收访问的同一个消息队列
参数 msgp:
- 发送或接收的数据块
参数 msgsz:
- 发送或接收数据块的大小
参数 msgflg:
- 选项,一般填 0 即可
参数 msgtyp:
- msgbuf 里面的 mtype
// The msgp argument is a pointer to caller-defined structure of the following general form:
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[1]; /* message data */
};
信号量 / 信号灯(semaphore),本质 就是一个计数器,是一个描述资源数量的计数器。
举个例子:
比如我们任何一个执行流,像访问临界资源中的一个子资源的时候,不能直接访问,需要 先申请信号量资源(P操作),此时 count-- 。只要申请信号量成功,未来就一定能拿到一个子资源。(类似摇号)
然后进入进程自己的临界区,访问对应的临界资源。
使用完成后,进程释放信号量资源(V操作),只要将计数器增加 count++,就表示将我们对应的资源进行了归还。
至此,进程通过执行代码来申请,意味着,所有进程都得先看到信号量,信号量就是一个共享资源。(信号量保护共享资源,自己却又是一个共享资源)
故,信号量必须保证自己的 ++ - - 是原子的
也,信号量被归类到了进程间通信
信号量部分未完待续~
头文件
#include
#include
#include
int semget(key_t key, int nsems, int semflg);
参数 key:
- 使用
ftok
函数设置的唯一标识码,他虽由用户设置,却是在内核中使用的
参数 nsems:
- 申请信号量的个数(叫做信号量集)
参数 semflg:
选项 IPC_CREAT and IPC_EXCL
单独使用 IPC_CREAT:创建一个共享内存,如果共享内存不存在,就创建,如果已经存在就获取已经存在的共享内存并返回。
IPC_CREAT | IPC_EXCL :IPC_EXCL 必须要配合 IPC_CREAT 使用,创建一个共享内存,如果共享内存不存在,就创建,如果已经存在就出错返回
意味着,一起使用时,如果创建成功,对应的shm,一定是最新的!IPC_CREAT | IPC_EXCL | 0666 :上面的基础上,添加权限(可以配合函数 umask(0) 使用)
返回值:
- 成功会返回一个信号量计数器标识符,失败返回 -1
头文件
#include
#include
#include
int semctl(int semid, int semnum, int cmd, ...);
参数 semid:
- 需要的信号量的semid
参数 semnum:
- 信号量编号
参数 cmd:
- 选项 IPC_STAT:把用户传入 msqid 的相应内核数据结构信息复制到 buf 中(在调用者有读权限的情况下,才能成功)
- 选项 IPC_RMID:删除 msqid 为传入值的共享内存块
返回值:
- 成功返回 >= 0 的值,失败返回 -1
//This function has three or four arguments, depending on cmd. When there are four, the
//fourth has the type union semun. The calling program must define this union as follows:
union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};
//The semid_ds data structure is defined in as follows:
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 */
};
//The ipc_perm structure is defined as follows (the highlighted fields are settable usingIPC_SET):
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 */
};
头文件
#include
#include
#include
int semop(int semid, struct sembuf *sops, unsigned nsops);
参数 sops:
- 需要用户自己定义后传入,设置结构体内容,以此达到对信号量的 PV 操作等等
//Each semaphore in a System V semaphore set has the following associated values:
unsigned short semval; /* semaphore value */
unsigned short semzcnt; /* # waiting for zero */
unsigned short semncnt; /* # waiting for increase */
pid_t sempid; /* ID of process that did last op */
//semop() performs operations on selected semaphores in the set indicated by semid.
//Each of the nsops elements in the array pointed to by sops specifies an operation to be performed on a single semaphore.
//The elements of this structure are of type struct sembuf, containing the following members:
unsigned short sem_num; /* semaphore number */
short sem_op; /* semaphore operation */
short sem_flg; /* operation flags */
共享内存、消息队列、信号量 这三种 ipc 都有各自的内核数据结构体,而结构体的第一个成员都是 struct ipc_perm xx_perm
。
可以理解为 OS 将这三种 ipc 都放进了一个 struct ipc_perm* ipc_id_arr[]
指针数组中进行管理(这里只是做理解解释,实际上更复杂)。
OS 通过指针,可以找到每个结构体(同时也是每个结构的第一个成员,即 struct ipc_perm xx_perm),在其中找到 key 值就可以确定。
要访问里面的内容时,以共享内存举例
((struct shmid_ds*)ipc_id_arr[n])->other...
对指针进行强转,就可以访问到其中内容了,这也是一种多态。