Linux环境编程第六天笔记
system-V IPC
消息队列、共享内存和信号量统称为system-V IPC,在系统中它们都使用key(键值)作为唯一标识,它们都是持续性资源。即被创建后不会因为进程的结束而消失,除非调用特殊的函数或命令删除。
进程每次打开一个IPC对象,都会获得表征这个对象的ID,IPC的key是唯一的,ID是可变的。
ftok()
获取一个当前未用的IPC的key
返回值:成功:返回键值;失败:返回-1;
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
-
pathname :一个合法的路径。
-
proj_id :一个整数
-
如果两个参数相同,那么获得的key值也相同。
-
如果同一个目录中的进程需要超过1个IPC对象,可以通过第2个参数来标识。
-
即便是不同类型的IPC也不能使用同一个key。
查看或删除当前系统IPC的命令
-
查看消息队列:
ipcs -q -
查看共享内存:
ipcs -m -
查看信号量:
ipcs -s -
查看所有IPC对象:
ipcs -a -
删除指定的消息队列:
ipcrm -q MSG_ID或ipcrm _Q msg_key -
删除指定共享内存:
ipcrm -m SHM_ID或ipcrm -M shm_key -
删除指定的信号量:
ipcrm -s SEM_ID或ipcrm -S sem_key
删除举例:假如某个消息队列的
msqid是12345,key是0xabcdef。
ipcrm -s 11223 ipcrm -S 0x98765
消息队列(MSG)
消息队列是一种带有数据标识的特殊管道,使得每一段被写入的数据都变成带标识的消息,读取该段消息的进程只要指定这个标识就可以正确的读取。一个代标识的消息队列,就像是多条并存的管道一样。
msgget()
获取消息队列的ID(chuang)
返回值:成功:该消息队列对的ID;失败:-1
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
-
key:消息队列的键值。如果指定为IPC-PRIVATE系统会使用一个未使用的key,来对应这个消息队列。
-
msgflg:(是一个位屏蔽字)
-
IPC_CREAT:如果key对应的MSG不存在,则创建该对象。
-
IPC_EXCL:如果key对应的MSG已存在,则报错。
-
mode:MSG的访问权限(八进制:如0666)权限只有读和写,执行权限是无效的。
-
msgsnd()和msgrcv()
msgsnd() 发送消息;msgrcv()接收消息
返回值:成功:msgsnd()返回0,msgrcv()返回读取的字节数;失败:均返回-1
#include <sys/msg.h>
// 发送消息
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:发送、接收消息的消息队列对的ID
-
msgp:要发送的数据、要接收的数据的存储区域指针
-
msgsz:要发送的数据、要接收的数据的大小
-
msgtyp:(msgrcv独有)要接收的消息标识
•
0- 接收队列中的第一条消息 •>0- 接收指定类型的第一条消息 •<0- 接收类型小于等于 |msgtyp| 的第一条消息 -
msgflg:标志位 •
0- 阻塞接收 •IPC_NOWAIT- 非阻塞读出、写入消息 •MSG_EXCEPT- 接收类型不等于 msgtyp 的消息(msgrcv独有) •MSG_NOERROR- 如果消息尺寸大于msgsz,截断而不报错(msgrcv独有)
发送消息时,消息必须被组织成以下形式
struct msgbuf
{
long mtype; // 消息标识,必须 > 0
char mtext[1]; // 消息数据,可以是任意类型
};
备注:消息的标识可以是任意长整型数值,但不能是0L。参数msgsz的数值是正文大小,不包含消息标识
msgctl()
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
-
msqid:消息队列标识符,由 msgget() 返回
-
cmd
命令 功能 IPC_STAT获取消息队列的状态信息,存储在msqid_ds()中 IPC_SET设置消息队列的参数,存储在msqid_ds()中//使用前需要 IPC_STAT获取一下原状态IPC_RMID立即删除消息队列,并且唤醒所有阻塞在消息队列上的进程,同时忽略第三个参数 IPC_INFO获取系统消息队列限制信息 MSG_INFO获取系统消息队列资源使用信息 MSG_STAT通过索引获取消息队列状态 返回值:成功:
IPC_STAT、IPC_SET、IPC_RMID返回0IPC_INFO、MSG_INFO返回系统中已分配的消息队列总数MSG_STAT返回消息队列的ID
- buf:相关消息结构体的缓冲区
//msqid_ds 结构体
struct msqid_ds {
struct ipc_perm msg_perm; /* 所有权和权限 */
time_t msg_stime; /* 最后发送时间 */
time_t msg_rtime; /* 最后接收时间 */
time_t msg_ctime; /* 最后修改时间 */
unsigned long __msg_cbytes; /* 队列中当前字节数 */
msgqnum_t msg_qnum; /* 队列中消息数量 */
msglen_t msg_qbytes; /* 队列最大字节数 */
pid_t msg_lspid; /* 最后发送进程的PID */
pid_t msg_lrpid; /* 最后接收进程的PID */
};
//ipc_perm 结构体
struct ipc_perm {
key_t __key; /* 消息队列的key值 */
uid_t uid; /* 所有者的有效用户ID */
gid_t gid; /* 所有者的有效组ID */
uid_t cuid; /* 创建者的有效用户ID */
gid_t cgid; /* 创建者的有效组ID */
unsigned short mode; /* 权限模式 */
unsigned short __seq; /* 序列号 */
};
共享内存(SHM)
共享内存是效率最高的IPC,因为它抛弃了内核这个"代理人",共享内存一般不能单独使用,需要配合信号量、互斥锁等协调机制。共享的内存是同一块内存映射到不同内存的虚拟空间。
使用共享内存的一般步骤
-
获取共享内存对象的ID
-
将共享内存映射到本进程虚拟内存空间的某个区域
-
当不在映射时,解除映射关系
-
当没有进程再需要这块内存时,删除它
shmget()
获取共享内存的ID
返回值:成功:返回该共享内存的ID;失败:返回-1;
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
-
key:共享内存的键值
-
size:共享内存的尺寸
-
shmflg:
| 宏 | 作用 |
|---|---|
IPC_CREAT |
如果键值对应的共享内存不存在,则创建 |
IPC_EXCL |
如果键值对应的共享内存存在,则报错 |
SHM_HUGETLB |
使用大页面来分配内存 |
SHM_NORESERVE |
不在交换分区中为这块共享内存保留空间 |
mode |
共享内存的访问权限(八进制:如0644) |
所谓大页面是指采用比默认尺寸4KB更大的页面,Linux内核支持以2MB作为物理分页的基本单位,以减少缺页中断。
shmat()和hshmdt()
对共享内存进行映射,或解除映射
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
int shmdt(const void *shmaddr);
-
shmid:共享内存的ID
-
shmaddr:
shmat:
-
值为NULL:系统自动选择一个合适的虚拟内存地址去映射共享内存
-
不为NULL:系统会根据shmaddr来选择一个合适的内存区域
shmdt
- 共享内存的首地址
-
-
shmflg
-
SHM_RDONLY:以只读的方式映射共享内存 -
SHM_REMAP:重新映射,此时shmaddr不能为NULL -
SHM_RND:选择比shmaddr小的最大地址来对齐地址 -
0:默认:可读写
-
备注:共享内存只能以只读或可读写的方式映射,无法以只写的方式映射。
解除映射后系统不再允许访问SHM
shmctl()
获取或设置共享内存的相关属性
返回值:成功:SHM_STAT:返回shmid的ID;失败:-1
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
struct shmid_ds {
struct ipc_perm shm_perm; /* 所有权和权限 */
size_t shm_segsz; /* 段大小(字节) */
time_t shm_atime; /* 最后附加时间 */
time_t shm_dtime; /* 最后分离时间 */
time_t shm_ctime; /* 最后修改时间 */
pid_t shm_cpid; /* 创建者PID */
pid_t shm_lpid; /* 最后操作者PID */
shmatt_t shm_nattch; /* 当前附加数量 */
// 平台特定字段可能还有更多
};
struct ipc_perm {
key_t __key; /* 共享内存的key值 */
uid_t uid; /* 所有者的有效用户ID */
gid_t gid; /* 所有者的有效组ID */
uid_t cuid; /* 创建者的有效用户ID */
gid_t cgid; /* 创建者的有效组ID */
unsigned short mode; /* 权限模式 */
unsigned short __seq; /* 序列号 */
};
-
shmid:共享内存标识符
-
cmd:
命令 功能 IPC_STAT 获取共享内存段的状态信息,放到buf中 IPC_SET 设置共享内存段的参数为buf指向的内容,需要先使用IPC_STAT获取原信息 IPC_RMID 标记共享内存段为即将被删除 IPC_INFO 获取系统共享内存限制信息 SHM_INFO 获取系统共享内存资源使用情况 SHM_STAT 通过索引获取共享内存信息 SHM_LOCK 锁定共享内存到物理内存(防止交换) SHM_UNLOCK 解锁共享内存(允许交换) -
buf:指向
shmid_ds结构体的指针,用于传入或传出信息
信号量(SEM)
信号量是一种用于进程间同步和互斥的IPC机制,可以看作是一个计数器,用于控制多个进程对共享资源的访问。
-
多个进程或线程有可能同时访问的资源(变量、链表、文件等)称为共享资源。也称为临界资源。
-
访问这些资源的代码称为临界代码,这些代码区域称为临界区。
-
P操作是申请资源,信号量-1;V操作时释放资源,信号量+1。
semget()
获取信号量ID
返回值:成功:返回该信号量ID;失败:返回-1;
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
-
key:信号量的键值。
-
nsems:信号量的元素个数。
-
semflg:
-
IPC_CREAT:如果key对应的信号量不存在,则创建。
-
IPC_EXCL:如果key对应的信号量已存在,则报错
-
mode:信号量的访问权限(八进制:如0644)。
创建信号量时,还受到以下系统信息的影响:
1,SEMMNI:系统中信号量的总数最大值。
2,SEMMSL:每个信号量中信号量元素的个数最大值。
3,SEMMNS:系统中所有信号量中的信号量元素的总数最大值。
Linux中,以上信息在/proc/sys/kernel/sem中查看
-
semop()
对信号量进行P/V操作,或0操作
返回值:成功:0;失败:-1;
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, size_t nsops);
//结构体sembuf
struct sembuf {
unsigned short sem_num; /* 信号量在信号量集中的索引(从0开始) */
short sem_op; /* 操作值(正数、负数、0) */
short sem_flg; /* 操作标志 */
};
-
semid:信号量ID;
-
sops:信号量操作结构体数组;
sem_op 操作类型 正数 释放资源(V操作) 负数 申请资源(P操作) 0 等待归零 sem_flg 说明 0 默认,阻塞操作 IPC_NOWAIT 非阻塞操作,无法立即完成则返回 EAGAIN SEM_UNDO 进程结束时自动撤销操作,防止死锁
- nsops:结构体数组元素个数;
备注:
当 sem_op 等于 0 时:进行等零操作,如果此时 semval 恰好为 0, 则 semop ( ) 立即成功返回,否则如果 IPC_NOWAIT 被设置,则立即出错返回并将 errno 设置为 EAGAIN, 否则将使得进程进入睡眠,直到以下情况发生:
-
semval 变为 0。
-
信号量被删除。(将导致 semop ( ) 出错退出,错误码为 EIDRM)
-
收到信号。(将导致 semop ( ) 出错退出,错误码为 EINTR)
semctl()
获取或设置信号量的相关属性
返回值:失败:-1
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, ...);
struct semid_ds {
struct ipc_perm sem_perm; /* 所有权和权限 */
time_t sem_otime; /* 最后semop时间 */
time_t sem_ctime; /* 最后修改时间 */
unsigned long sem_nsems; /* 信号量集中的信号量数量 */
};
struct ipc_perm {
key_t __key; /* 信号量集的key值 */
uid_t uid; /* 所有者的有效用户ID */
gid_t gid; /* 所有者的有效组ID */
uid_t cuid; /* 创建者的有效用户ID */
gid_t cgid; /* 创建者的有效组ID */
unsigned short mode; /* 权限模式 */
unsigned short __seq; /* 序列号 */
};
-
semid:信号量ID;
-
semnum:信号量元素序号(数组下标)
-
cmd:
命令 功能说明 是否需要参数四 IPC_STAT 获取当前信号量的属性信息(如权限、所有者、创建时间等) 是 IPC_SET 设置当前信号量的属性信息(需有对应权限,如修改权限、所有者) 是 IPC_RMID 立即删除该信号量,参数 semnum会被忽略(信号量被标记为删除,后续无法访问)否 IPC_INFO 获取系统级信号量的限制值(如最大信号量数、每个信号量的最大元素数) 是 SEM_INFO 获取系统中信号量的资源消耗信息(如已创建的信号量数、占用的内存等) 是 SEM_STAT 类似 IPC_STAT,但通过内核中信号量信息数组的下标(而非信号量 ID)获取系统中所有信号量的信息是 GETALL 返回当前信号量集中所有信号量元素 的值,参数 semnum被忽略是 GETNCNT 返回正阻塞在该信号量元素 P 操作(减 1) 上的进程总数 否 GETPID 返回最后一个对该信号量元素执行操作的进程 PID 否 GETVAL ``返回该信号量元素的当前值 否 GETZCNT 返回正阻塞在该信号量元素 等零操作( sem_op=0) 上的进程总数否 SETALL 设置当前信号量集中所有信号量元素 的值,参数 semnum被忽略是 SETVAL 设置该信号量元素的当前值 是 -
...:可变参数,通常是
union semun
// 必须自己定义 union semun
union semun {
int val; /* 用于 SETVAL */
struct semid_ds *buf; /* 用于 IPC_STAT, IPC_SET */
unsigned short *array; /* 用于 GETALL, SETALL */
struct seminfo *__buf; /* 用于 IPC_INFO (Linux特有) */
};
// 也可以这样简化定义(更通用)
typedef union {
int val;
struct semid_ds *buf;
unsigned short *array;
} semun_t;