system V 消息队列信息量(了解)
目录
system V 消息队列
消息队列的基本原理
消息队列数据结构
消息队列接口介绍
消息队列相关函数
消息队列的释放
向消息队列发送数据
向消息队列接收消息
System V 信号量
信号量相关概念
信号量的数据结构
信号量相关函数
进程互斥
system V IPC联系
system V 消息队列
消息队列的基本原理
消息队列实际上就是在系统当中创建了一个队列,队列当中的每个成员都是一个数据块,这些数据块都由类型和信息两部分构成,两个互相通信的进程通过某种方式看到同一个消息队列,这两个进程向对方发数据时,都在消息队列的队尾添加数据块,这两个进程获取数据块时,都在消息队列的队头取数据块。
其中消息队列当中的某一个数据块是由谁发送给谁的,取决于数据块的类型。
总结以上分析:
- 消息队列提供了一个从一个进程向另一个进程发送数据块的方法并且允许不同进程向内核中发送带类型的数据块。
- 每个数据块都需要有一个类型供操作系统识别,接收者进程接收的数据块可以是不同的类型值。
- 和共享内存一样,消息队列的资源也必须自行删除,否则不会自动清除,因为system V IPC资源的生命周期是随内核的。
- 消息队列的本质也是让不同的进程看到同一个队列
消息队列数据结构
当然,系统当中也可能会存在大量的消息队列,系统一定也要为消息队列维护相关的内核数据结构。
消息队列的数据结构如下:
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函数 创建消息队列
int msgget(key_t key, int msgflg);
对msgget函数参数及返回值进行说明:
- 创建消息队列也需要使用ftok函数生成一个key值,这个key值作为msgget函数的第一个参数。
- msgget函数的第二个参数,与创建共享内存时使用的shmget函数的第三个参数相同。
- 消息队列创建成功时,msgget函数返回的一个有效的消息队列标识符(用户层标识符),否则,返回-1,错误原因存于error中。
shmget有设置大小的参数,而msgget没有
消息队列的释放
msgctl函数 释放消息队列
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
msgctl函数的参数与释放共享内存时使用的shmctl函数的三个参数相同,只不过msgctl函数的第三个参数传入的是消息队列的相关数据结构。
向消息队列发送数据
msgsnd函数 向消息队列发送数据
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
msgsnd函数的参数说明:
- 第一个参数msqid,表示消息队列的用户级标识符。
- 第二个参数msgp,表示待发送的数据块。
- 第三个参数msgsz,表示所发送数据块的大小
- 第四个参数msgflg,表示发送数据块的方式,一般默认为0即可。
msgsnd函数的返回值说明:
- msgsnd调用成功,返回0。
- msgsnd调用失败,返回-1,错误原因存于error中。
其中msgsnd函数的第二个参数必须为以下结构,需要自己自定义:
struct msgbuf{
long mtype; /* message type, must be > 0 */
char mtext[1]; /* message data */
};
注意: 该结构当中的第二个成员mtext即为待发送的信息,当我们定义该结构时,mtext的大小可以自己指定。
向消息队列接收消息
msgrcv函数 向消息队列接收消息
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
msgrcv函数的参数说明:
- 第一个参数msqid,表示消息队列的用户级标识符。
- 第二个参数msgp,表示获取到的数据块,是一个输出型参数。
- 第三个参数msgsz,表示要获取数据块的大小
- 第四个参数msgtyp,表示要接收数据块的类型。
- 第五个参数msgflg,表示发送数据块的方式,一般默认为0即可
msgrcv函数的返回值说明:
- msgsnd调用成功,返回实际获取到mtext数组中的字节数。
- msgsnd调用失败,返回-1,错误原因存于error中。
System V 信号量
信号量相关概念
- 由于进程要求共享资源,而且有些资源需要互斥使用,因此各进程间竞争使用这些资源,进程的这种关系叫做进程互斥。
- 系统中某些资源一次只允许一个进程使用,称这样的资源为临界资源或互斥资源。
- 在进程中涉及到临界资源的程序段叫临界区。
- IPC资源必须我们来删除,否则不会自动删除,因为system V IPC的生命周期随内核。
信号量的数据结构
在系统当中也为信号量维护了相关的内核数据结构。
信号量的数据结构如下:
struct semid_ds {
struct ipc_perm sem_perm; /* permissions .. see ipc.h */
__kernel_time_t sem_otime; /* last semop time */
__kernel_time_t sem_ctime; /* last change time */
struct sem *sem_base; /* ptr to first semaphore in array */
struct sem_queue *sem_pending; /* pending operations to be processed */
struct sem_queue **sem_pending_last; /* last pending operation */
struct sem_undo *undo; /* undo requests on this array */
unsigned short sem_nsems; /* no. of semaphores in array */
};
信号量数据结构的第一个成员也是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 */
};
信号量相关函数
信号量集的创建
int semget(key_t key, int nsems, int semflg);
对msgget函数参数及返回值进行说明:
- 创建信号量集也需要使用ftok函数生成一个key值,这个key值作为semget函数的第一个参数。
- semget函数的第二个参数nsems,表示创建信号量的个数。
- semget函数的第三个参数,与创建共享内存时使用的shmget函数的第三个参数相同。
- 信号量集创建成功时,semget函数返回的一个有效的信号量集标识符(用户层标识符)。
信号量集的释放
int semctl(int semid, int semnum, int cmd, ...);
- semctl第一个参数semid是该信号量集在用户层的唯一标识符
- semctl第二个参数semnum表示要操作的信号量数组的信号量下标
- semctl第三个参数cmd表示需要对该信号量进行什么样的操作
对信号量集的操作
int semop(int semid, struct sembuf *sops, size_t nsops);
semop第一个参数是semid信号量集在用户层的唯一标识符
semop第二个参数sops指向进行操作的信号量集结构体数组的首地址,每个结构体描述了一个操作。此结构的具体说明如下:
struct sembuf {
unsigned short sem_num; /* semaphore number */
short sem_op; /* semaphore operation */
short sem_flg; /* operation flags */
};
short sem_num:要操作的信号量在集合中的索引,从0开始计数。
short sem_op:进行的操作。可以是正数、负数或零。通常情况下,正数表示释放资源,负数表示请求资源,零表示等待资源。
short sem_flg:控制操作的标志位。常用的标志有:
- SEM_UNDO:使操作可以被撤销,即在进程意外终止时自动撤销操作。
- IPC_NOWAIT:如果操作无法立即执行,不要阻塞进程,立即返回并报告错误。
- SEM_NOERROR:如果出现错误(如信号量不存在),不要报告错误。
semop第三个参数nsops表示进行操作信号量的个数,即sops结构变量的个数,需大于或等于1。最常见设置此值等于1,只完成对一个信号量的操作
semop函数返回值
成功:返回信号量集的标识符出错:-1,错误原因存于error中
进程互斥
进程间通信通过共享资源来实现,这虽然解决了通信的问题,但是也引入了新的问题,多个执行流访问同一个资源,若是不对临界资源进行保护,就可能产生各个进程从临界资源获取的数据不一致等问题,像我们之前见过的管道就不会存在这样的问题,因为管道具有原子性
保护临界资源的本质是保护临界区,我们把进程代码中访问临界资源的代码称之为临界区,信号量就是用来保护临界区的,信号量分为二元信号量和多元信号量。
信号量本质是一个计数器,用来保护临界资源
核心思想:
while(cnt <= 0) //当临界资源被申请完之后,OS就不会再继续分配了
{
int cnt = 15;
int number = cnt--;
}
执行流使用临界资源时,一般都是先申请计数器,若成功,则该执行流具有访问临界资源的权限,否则,申请失败。申请成功之后你就具有了该临界资源的使用权限
注意:执行流在访问共享资源,并不是直接访问,而是先申请计数器,也就是信号量
计数器的意义:
1.申请计数器并没有访问资源,而是对资源进行了预订机制,就像买电影票一样,你可能上午买的,下午才使用
2.计数器可以有效保证进入共享资源的执行流数量,这也是间接的保护了临界资源
多元信号量
比如当前有一块大小为100字节的资源,我们若是一25字节为一份,那么该资源可以被分为4份,那么此时这块资源可以由4个信号量进行标识,这就是多元信号量。
二元信号量
我们将值只能为 1,0两态的计数器叫做 “二元信号量” ,本质上就是临界资源,不会分成很多快了,而是当做一个整体,整体申请,整体释放
实际上,申请信号量本质就是对计数器减减,我们将这种操作称为:P操作
释放资源,释放信号量,本质是对计数器进行加加操作,我们将这种操作称为:V操作
system V IPC联系
通过对system V系列进程间通信的学习,可以发现共享内存、消息队列以及信号量,虽然它们内部的属性差别很大,但是维护它们的数据结构的第一个成员确实一样的,都是ipc_perm类型的成员变量。
这样设计的好处就是,在操作系统内可以定义一个struct ipc_perm类型的数组,此时每当我们申请一个IPC资源,就在该数组当中开辟一个这样的结构。
也就是说,在内核当中只需要将所有的IPC资源的ipc_perm成员组织成指针数组的样子,然后用切片(多态,例如:((struct shmid_ds*)adderss)->你想访问的成员)的方式获取到该IPC资源的起始地址,然后就可以访问该IPC资源的每一个成员了。