System V 消息队列
1、必须让不同进程看到同一个队列
2、允许不同的进程,向内核中发送带类型的数据块,数据块的类型,用来区分这个数据块是属于哪个进程
操作系统将A进程的数据块链接到消息队列(由操作系统提供的)中,操作系统将B进程的数据块链接到消息队列中,此时A进程可以通过消息队列获取B进程的数据块,B进程也可以通过消息队列获取A进程的数据块
从而实现让双方进程以数据块的形式发送数据
总结:
消息队列和共享内存一样,消息队列的资源也必须自行删除,否则不会自动清除,因为system V IPC资源的生命周期是随内核的
msgget
单独使用ipcs命令时,会默认列出消息队列、共享内存以及信号量相关的信息,若只想查看它们之间某一个的相关信息,可以选择携带以下选项:
-q:列出消息队列相关信息。
-m:列出共享内存相关信息。
-s:列出信号量相关信息。
创建消息队列
创建消息队列也需要使用ftok函数生成一个key值,这个key值作为msgget函数的第一个参数。
msgget函数的第二个参数,与创建共享内存时使用的shmget函数的第三个参数相同。
消息队列创建成功时,msgget函数返回的一个有效的消息队列标识符(用户层标识符)。
msgflg
1、IPC_CREAT(单独使用):
如果内核中不存在键值与key相等的消息队列,则新建一个消息队列,如果存在这样的消息队列,则直接返回该消息队列的句柄
2、IPC_CREAT | IPC_EXCL:
如果内核中不存在键值与key相等的消息队列,则新建一个消息队列,如果存在这样的消息队列,则出错返回
释放消息队列
msgctl
第一个参数msqid,表示所控制消息队列的用户级标识符。
第二个参数cmd,表示具体的控制动作。
shmctl函数的第二个参数传入的常用的选项有以下三个
1、IPC_STAT,获取消息队列的当前关联值,此时参数buf作为输出型参数
2、IPC_SET ,在进程有足够权限的前提下,将消息队列的当前关联值设置为buf所指的数据结构中的值
3、IPC_RMID ,删除消息队列段
第三个参数buf,用于获取或设置所控制消息队列的数据结构
msgsnd && msgrcv
第一个参数msqid,表示消息队列的用户级标识符。
第二个参数msgp,表示待发送的数据块。
msgsnd函数的第二个参数必须为以下结构:
struct msgbuf{
long mtype; /* message type, must be > 0 */
char mtext[1]; //mtext即为待发送的信息,当我们定义该结构时,mtext的大小可以自己指定
};
第三个参数msgsz,表示所发送数据块的大小
第四个参数msgflg,表示发送数据块的方式,一般默认为0即可。
msgrcv
第一个参数msqid,表示消息队列的用户级标识符。
第二个参数msgp,表示获取到的数据块,是一个输出型参数。
第三个参数msgsz,表示要获取数据块的大小
第四个参数msgtyp,表示要接收数据块的类型
return val :
msgsnd调用成功,返回实际获取到mtext数组中的字节数。
msgsnd调用失败,返回-1。
System V信号量
当进程A正在写入,写入了一部分,就被进程B拿走了,导致进程双方发送和接受的数据不完整 (数据不一致问题)
进程A和进程B看到的同一份资源,这份共享资源,如果不加保护,可能会导致数据不一致问题
解决方案:
任何时刻,只允许一个执行流访问共享资源 (互斥)
关于临界资源
1、共享的,任何时刻只允许一个执行流访问(就是执行访问代码)的资源,这种资源叫做临界资源,临界资源一般是内存空间,
2、访问临界资源的代码,这种代码叫做临界区
信号量/信号灯的本质是一把计数器
关于计数器:
1、申请计数器成功,就表示我具有访问资源的权限了
2、申请了计数器资源,当前可以访问我要的资源了吗?
不能。申请了计数器资源只是对资源的预订机制
3、计数器可以有效保证进入共享资源的执行流的数量
4、所以每一个执行流,想访问共享资源中的一部分的时候,不是直接访问,而是先申请计数器资源。
关于信号量
1、执行流申请资源,必须先申请信号量资源,得到信号量之后,才能访问临界资源
2、申请信号量的本质:是对临界资源的预订机制
3、我们把值只能为1,0两态的计数器叫做二元信号量,二元信号量就是互斥功能,二元信号量本质就是一个锁,锁的作用就是保护
4、申请信号量,本质是对计数器–(P操作)
释放资源,释放信号量,本质是对计数器进行++操作(V操作)
信号量的申请和释放,PV操作,这种操作是原子的,如何理解原子
原子:一件事情要么不做,要做就做完,没有正在做的概念
多个信号量 :
和信号量是几
在系统当中也为信号量维护了相关的内核数据结构。
信号量的数据结构如下:
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{
__kernel_key_t key;
__kernel_uid_t uid;
__kernel_gid_t gid;
__kernel_uid_t cuid;
__kernel_gid_t cgid;
__kernel_mode_t mode;
unsigned short seq;
};
创建信号量集 semget
int semget(key_t key, int nsems, int semflg);
1、使用ftok函数生成一个key值
2、nsems,表示创建信号量的个数。
3、semflg与创建共享内存时使用的shmget函数的第三个参数相同。
4、信号量集创建成功时,semget函数返回的一个有效的信号量集标识符(用户层标识符)
删除信号量集
int semctl(int semid, int semnum, int cmd, ...);
如果如果只有一个信号量semnum设为0,
如果cmd设为SETVAL,在可变参数部分将联合体semun 的val设置出来
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) */
};
如果想要删除信号量,cmd参数不用填
如果想要获取信号量对应的属性,cmd传IPC_STAT
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 */
};
信号量集的操作
对信号量集进行操作我们需要用semop函数
int semop(int semid, struct sembuf *sops, unsigned nsops);
struct sembuf
{
unsigned short sem_num; //如果只有一个信号量 ,sem_num就传0,
short sem_op; //正1 表示实现V操作,-1表示实现P操作
short sem_flg; /* operation flags */
}