10.XSI的IPC对象
1)IPC对象的标识符(ID)和键(KEY)
IPC对象在系统内核中的唯一名称用键(KEY)表示。不同的进程可以通过键来引用该IPC对象。一旦进程获得了该IPC对象,即通过其标识(ID)来称谓该对象。
IPC对象(键) 内核空间
--- / --- | --- \ ---------------
/键 |键 \键 用户空间
进程1 进程2 进程3
ID1 ID2 ID3
#include <sys/ipc.h>
//创建IPC对象
key_t ftok(const char* pathname, int proj_id);
成功返回IPC对象的键,失败返回-1。
pathname - 一个真实存在的路径,使用该路径的i节点号
proj_id - 项目ID,仅低8为有效,-128~127/0~255
相同项目使用相同的pathname和proj_id,保证key的一致性
不同项目使用不同的pathname或proj_id,避免key发生冲突
2)IPC对象的编程接口
A.创建或获取IPC对象(存在即获取,不存在就创建)
int shmget(key_t key, size_t size, int shmflg); // 共享内存
int msgget(key_t key, int msgflg); // 消息队列
int semget(key_t key, int nsems, int semflg); // 信号量集
成功返回IPC对象的ID,失败返回-1
B.控制或销毁IPC对象
int shmctl(int shmid, int cmd, struct shmid_ds* buf); // 共享内存
int msgctl(int msqid, int cmd, struct msqid_ds* buf); // 消息队列
int semctl(int semid, int semnum, int cmd, union semun arg); // 信号量集
cmd - 控制命令,可取以下值:
IPC_STAT: 获取IPC对象的属性
IPC_SET: 设置IPC对象的属性
IPC_RMID: 销毁IPC对象
C.IPC对象的权限结构
struct ipc_perm {
key_t __key; // 键
uid_t uid; // 拥有者用户
gid_t gid; // 拥有者组
uid_t cuid; // 创建者用户
gid_t cgid; // 创建者组
unsigned short mode; // 权限
unsigned short __seq; // 序号
};
其中只有uid、gid和mode三个字段可以在创建完成以后被修改。
3)共享内存
#include <sys/shm.h>
两个或者更多进程,共享一块由系统内核负责维护的物理内存,其地址空间通常被映射到每个进程虚拟内存堆和栈之间的不同区域。
共享内存的属性结构:
struct shmid_ds {
struct ipc_perm shm_prem; // 权限结构
size_t shm_segsz; // 字节数
time_t shm_atime; // 最后加载时间
time_t shm_dtime; // 最后卸载时间
time_t shm_ctime; // 最后改变时间
pid_t shm_cpid; // 创建进程的PID
pit_t shm_lpid; // 最后加(卸)载进程的PID
shmatt_t shm_nattch; // 当前加载计数
...
};
创建或获取共享内存
int shmget(key_t key, size_t size, int shmflg);
size - 共享内存的字节数,按页向上取整。获取已有共享内存对象时可置0。
shmflg - 创建标志,可取以下值:
0: 获取时用,不存在即失败。
IPC_CREAT - 创建兼获取,不存在即创建,已存在直接获取
IPC_EXCL - 不存在即创建,已存在直接报错。
以及读写权限(例如0644)
加载共享内存到虚拟内存,建立虚拟内存和物理内存间的映射
void* shmat(int shmid, const void* shmaddr, int shmflags);
成功返回共享内存起始地址,失败返回void*类型的-1。
shmid - 共享内存标识
shmaddr - 共享内存起始地址,置NULL由系统内核选择
shmflags - 加载标志,可取以下值:
0: 可读可写
SHM_RDONLY: 只读
SHM_RND: 若shmaddr非空且不是页边界,则将其调整至页边界。
卸载共享内存,解除虚拟内存和物理内存间的映射
int shmdt(const void* shmaddr);
成功返回0,失败返回-1。
shmat函数负责将给定共享内存映射到调用进程的虚拟内存空间,返回映射区的起始地址,同时将系统内核中共享内存对象的加载计数(shm_nattch)加1。调用进程在获得shmat函数返回的共享内存起始地址以后,就可以像访问普通内存一样访问该共享内存中的数据。shmdt函数负责从调用进程的虚拟内存中解除shmaddr所指向的映射区到共享内存的映射,同时将系统内核中共享内存对象的加载计数(shm_nattch)减1。因此加载计数为0的共享内存必定是没有任何进程使用的。shmctl(...,IPC_RMID, ...)调用可以用于销毁共享内存,但并非真的销毁,而只是做一个销毁标记,禁止任何进程对该共享内存形成新的加载,但已有的加载依然保留。只有当其使用者们纷纷卸载,直至其加载计数降为0时,共享内存才会真的被销毁。
代码:wshm.c、rshm.c
/* wshm.c */
#include <stdio.h>
#include <unistd.h>
#include <sys/shm.h>
int main(void) {
printf("创建共享内存...\n");
key_t key = ftok(".", 100);
if (key == -1) {
perror("ftok");
return -1;
}
int shmid = shmget(key, 4096, 0644 | IPC_CREAT | IPC_EXCL);
if (shmid == -1) {
perror("shmget");
return -1;
}
printf("加载共享内存...\n");
void* shmaddr = shmat(shmid, NULL, 0);
if (shmaddr == (void*)-1) {
perror("shmat");
return -1;
}
printf("写入共享内存...\n");
sprintf(shmaddr, "我是%d进程写入的数据。", getpid());
printf("按<回车>卸载共享内存(0x%08x/%d)...\n", key, shmid);
getchar();
if (shmdt(shmaddr) == -1) {
perror("shmdt");
return -1;
}
printf("按<回车>销毁共享内存(0x%08x/%d)...\n", key, shmid);
getchar();
if (shmctl(shmid, IPC_RMID, NULL) == -1) {
perror("shmctl");
return -1;
}
printf("完成!\n");
return 0;
}
/* rshm.c */
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <sys/shm.h>
void shmstat(int shmid) {
struct shmid_ds shm;
if (shmctl(shmid, IPC_STAT, &shm) == -1) {
perror("shmctl");
exit(-1);
}
printf(" 键:0x%08x\n", shm.shm_perm.__key);
printf(" 用户ID:%u\n", shm.shm_perm.uid);
printf(" 组ID:%u\n", shm.shm_perm.gid);
printf(" 创建者用户ID:%u\n", shm.shm_perm.cuid);
printf(" 创建者组ID:%u\n", shm.shm_perm.cgid);
printf(" 权限:%#o\n", shm.shm_perm.mode);
printf(" 序列号:%u\n", shm.shm_perm.__seq);
printf(" 大小(字节):%u\n", shm.shm_segsz);
printf(" 最后加载时间:%s", ctime(&shm.shm_atime));
printf(" 最后卸载时间:%s", ctime(&shm.shm_dtime));
printf(" 最后改变时间:%s", ctime(&shm.shm_ctime));
printf(" 创建进程PID:%u\n", shm.shm_cpid);
printf(" 最后加载/卸载进程PID:%u\n", shm.shm_lpid);
printf(" 当前加载计数:%ld\n", shm.shm_nattch);
}
int main(void) {
printf("获取共享内存...\n");
key_t key = ftok(".", 100);
if (key == -1) {
perror("ftok");
return -1;
}
int shmid = shmget(key, 0, 0);
if (shmid == -1) {
perror("shmget");
return -1;
}
printf("加载共享内存...\n");
void* shmaddr = shmat(shmid, NULL, 0);
if (shmaddr == (void*)-1) {
perror("shmat");
return -1;
}
printf("读取共享内存...\n");
printf("%s\n", (char*)shmaddr);
shmstat(shmid);
printf("按<回车>卸载共享内存...\n");
getchar();
if (shmdt(shmaddr) == -1) {
perror("shmdt");
return -1;
}
shmstat(shmid);
printf("完成!\n");
return 0;
}
编程框架:1、使用ftok函数创建IPC对象,得到IPC的键(KEY)
例如:key_t key = ftok(".", 100);
2、使用shmget函数 创建/获取 共享内存,得到共享内存ID
例如:int shmid = shmget(key, 4096, 0644 | IPC_CREAT | IPC_EXCL);(创建)
int shmid = shmget(key, 0, 0);(获取)
3、使用shmat函数加载共享内存,得到共享内存起始地址,加载计数+1
例如:void* shmaddr = shmat(shmid, NULL, 0);
4、向共享内存写入或读取数据,实现进程间通信
5、使用shmdt函数卸载共享内存,加载计数-1
例如:shmdt(shmaddr);
6、使用shmctl销毁共享内存
例如:shmctl(shmid, IPC_RMID, NULL);
总结:通过共享内存实现进程间通信,可以直接访问由系统内核维护的公共内存区域,不需要额外构建用户缓冲区,也不需要在用户缓冲区和内核缓冲区之间来回复制数据。因此共享内存是速度最快的进程间通信机制。但是共享内存因为缺乏必要的同步机制,往往需要借助其它进程间通信策略提供某种形式的停等机制。
4)消息队列
消息队列是由单个的类型各异的一系列消息结构组成的链表。
消息 +--> 消息
--------- | ----------
消息类型 | 消息类型
数据长度 | 数据长度
消息数据 | 消息数据
消息指针---+ 消息指针-->
尾 首
->2|1|2|3|1|2|2|1|3|3->
^ ^ ^ ^
最大可发送消息字节数:8192(8K)
最大全队列消息字节数:16384(16K)
最大全系统消息队列数:16
最大全系统消息总字节数:262144(16384 X 16)
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
|
0/IPC_CREAT/IPC_EXCL
发送消息函数
int msgsnd(
int msqid, // 消息队列标识
const void* msgp, // ->| 消息类型(4字节) | 消息数据 |
size_t msgsz, // 期望发送字节数 <--长度-->
int msgflg // 0阻塞/IPC_NOWAIT非阻塞
);
成功返回0,失败返回-1。
msgsnd函数的msgp参数所指向的内存中包含4个字节大小的消息类型,其值必须大于0,但该函数的msgsz参数所表示的期望发送字节数却不包含消息类型所占的4个字节。
如果系统内核中的消息未达上限,则msgsnd函数会将欲发送消息加入消息队列并立即返回0,否则该函数会阻塞,直到系统内核允许加入新消息为止(比如有消息因被接收而离开消息队列)。若msgflg参数中包含IPC_NOWAIT位,则msgsnd函数在系统内核中的消息已达上限的情况下不会阻塞,而是返回-1,并置errno为EAGAIN。
接收消息
int msgrcv(
int msqid, // 消息队列标识
void* msgp, // ->| 消息类型(4字节) | 消息数据缓冲区 |
size_t msgsz, //期望接收字节数 <-----长度----->
long msgtyp, // 消息类型
int msgflg // 0阻塞/IPC_NOWAIT非阻塞/MSG_EXCEPT/MSG_NOERROR
);
成功返回实际接收到的消息数据的长度,失败返回-1。
msgtyp可取以下值:
0 - 提取消息队列中的第一条消息而无论其类型。
>0 - msgflg不含MSG_EXCEPT位,提取第一条类型为msgtyp的消息,msgflg含MSG_EXCEPT位,提取第一条类型不为msgtyp的消息。
<0 - 提取队列中类型小于或等于|msgtyp|的消息,类型越小的越先被提取。
注意msgrcv函数的msgp参数所指向的内存块中包含4字节的消息类型,其值由该函数输出,但该函数的msgsz参数所表示的期望接收字节数以及该函数所返回的实际接收字节数都不包含消息类型4个字节。
若存在与msgtyp参数匹配的消息,但是数据长度大于msgsz参数,且msgflg参数包含MSG_NOERROR位,则置截取该消息数据的前msgsz字节返回,剩余部分直接丢弃;但如果msgflg参数不包含MSG_NOERROR位,则不处理该消息,直接返回-1,并置errno为E2BIG。
msgrcv函数根据msgtyp参数对消息队列中消息有选择地接收,只有满足条件的消息才会被复制到应用程序缓冲区并从内核中删除。如果满足msgtyp条件的消息不只一条,则按照先进先出的规则提取。
若消息队列中有可接收消息,则msgrcv函数会将该消息移出消息队列,并立即返回所接收到的消息数据字节数,表示接收成功,否则此函数会阻塞,直到消息队列中有可接收消息为止。若msgflg参数包含IPC_NOWAIT位,则msgrcv函数在消息队列中没有可接收消息的情况下不会阻塞,而是返回-1,并置errno为ENOMSG。
若消息队列已销毁,则msgrcv函数会返回-1,并设置errno为EIDRM。
代码:wmsg.c、rmsg.c
/* wmsg.c */
#include <stdio.h>
#include <string.h>
#include <sys/msg.h>
int main(void) {
printf("创建消息队列...\n");
key_t key = ftok(".", 100);
if (key == -1) {
perror("ftok");
return -1;
}
int msqid = msgget(key, 0644 | IPC_CREAT | IPC_EXCL);
if (msqid == -1) {
perror("msgget");
return -1;
}
printf("向消息队列(0x%08x/%d)发送数据...\n", key, msqid);
for (;;) {
printf("> ");
struct {
long mtype;
char mtext[1024];
} msgbuf = {1234, ""};
fgets (msgbuf.mtext, sizeof(msgbuf.mtext) / sizeof(msgbuf.mtext[0]), stdin);
if (!strcmp(msgbuf.mtext, "!\n"))
break;
if (msgsnd(msqid, &msgbuf, strlen(msgbuf.mtext) * sizeof(msgbuf.mtext[0]), 0) == -1) {
perror("msgsend");
return -1;
}
}
printf("销毁消息队列(0x%08x/%d)...\n",
key, msqid);
if (msgctl(msqid, IPC_RMID, NULL) == -1) {
perror("msgctl");
return -1;
}
printf("完成!\n");
return 0;
}
/* rmsg.c */
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <sys/msg.h>
int main(void) {
printf("获取消息队列...\n");
key_t key = ftok(".", 100);
if (key == -1) {
perror("ftok");
return -1;
}
int msqid = msgget(key, 0);
if (msqid == -1) {
perror("msgget");
return -1;
}
printf("从消息队列(0x%08x/%d)接收消息...\n", key, msqid);
for (;;) {
struct {
long mtype;
char mtext[1024];
} msgbuf = {};
ssize_t msgsz = msgrcv(msqid, &msgbuf,
sizeof(msgbuf.mtext) - sizeof(msgbuf.mtext[0]), 1234,
MSG_NOERROR | IPC_NOWAIT);
if (msgsz == -1)
if (errno == EIDRM) {
printf("消息队列(0x%08x/%d)已销毁!\n", key, msqid);
break;
}
else
if (errno == ENOMSG) {
printf("现在没有消息,干点别的...\n");
sleep(1);
}
else {
perror("msgrcv");
return -1;
}
else
printf("%04d< %s", msgsz, msgbuf.mtext);
}
printf("完成!\n");
return 0;
}
5)信号量集
资源的需求者多于资源本身,如何协调有限资源在多数需求者之间的分配,以使每个资源需求者都有相对均衡的几率获得其所要求的资源。
系统内核中为每一种资源维护一个资源计数器,其值为当前空闲资源的数量,每当一个进程试图获取该种资源时,会先尝试减少其计数器的值,如果该计数器的值够减(计数器>=0),则说明空闲资源足够,该进程即获得资源,如果该计数器的值不够减,则说明空闲资源不够,该进程即进入等待模式,等候其它拥有该种资源的进程释放资源。任何一个拥有该种资源的进程,一旦决定释放该资源,都必须将其计数器的值予以增加,以表示空闲资源量的增加,为其它等候该资源的进程提供条件。
用一个信号量表示一种类型资源,其值为该类型资源的空闲数量。用由多个信号量组成的信号量集表示多种类型的资源。
信 / 信号量0->《三国演义》=2
号 / 信号量1->《水浒传》=4
量 \ 信号量2->《红楼梦》=11
集 \ 信号量3->《四游记》=8
创建或获取信号量集
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
成功返回信号量集标识符,失败返回-1。
key - 信号量集键
nsems - 信号量个数,即资源的种类数,获取可置0
semflg - 创建标志,可取以下值:
0 - 获取时用,不存在即失败
IPC_CREAT - 创建,不存在即创建,已存在即获取
IPC_EXCL - 排斥,已存在即失败
操作信号量集:拥有资源->减操作...释放资源->加操作
int semop(int semid, struct sembuf* sops, unsigned nsops);
成功返回0,失败返回-1。
semid - 信号量集标识
sops - 操作结构数组
nsops - 操作结构数组长度
struct sembuf {
unsigned short sem_num; // 信号量编号(集合索引)
short sem_op; // 操作数(-获取/+释放)
short sem_flg; // 操作标志
// (0/IPC_NOWAIT)
};
sops指针指向一个struct sembuf类型的结构体数组,其中每个元素都是一个struct sembuf类型的结构体,该结构体包含三个字段,用于表示针对信号量集中的一个特定信号量的特定操作。
semid=0 semid=0
---------- --semop()-> ----------
0: 15 -1 0: 14
1: 21 1: 21
2: 33 +1 2: 34
3: 42 -1 3: 41
sops
-------------
0: {0, -1, 0}
1: {2, 1, 0}
2: {3, -1, 0}
如果sem_op字段的值为负,则从semid信号量集第sem_num个信号量的值中减去|sem_op|,以表示对资源的获取;如果不够减(信号量的值不能为负),则此函数会阻塞,直到够减为止,以表示对资源的等待,但如果sem_flg包含IPC_NOWAIT位,则即使不够减也不会阻塞,而是返回-1,并置errno为EAGAIN。
销毁或控制信号量集
int semctl(int semid, int semnum, int cmd, ...);
成功返回0或其它与cmd有关的值,失败返回-1。
A.销毁信号量集
int semctl(int semid, 0, IPC_RMID); -> 0/-1
B.获取信号量集中每个信号量的值
unsigned short array[4]; // 每个信号量的值
int semctl(int semid, 0, GETALL, array); -> 0/-1
C.设置信号量集中每个信号量的值
unsigned short array[4] = {5, 5, 5, 5}; // 每个信号量的值
int semctl(int semid, 0, SETALL, array); -> 0/-1
D.获取信号量集中特定信号量的值
int semctl(int semid, int semnum, GETVAL);
->semid信号量集中第semnum个信号量的值/-1
E.设置信号量集中特定信号量的值
int semctl(int semid, int semnum, SETVAL, int val);
->0/-1
将semid信号量集中第semnum个信号量的值设置为val
代码:csem.c、gsem.c
/* csem.c */
#include <stdio.h>
#include <errno.h>
#include <sys/sem.h>
int pmenu(void) {
printf("--------------------\n");
printf(" 迷你图书馆\n");
printf("--------------------\n");
printf("[1] 借《三国演义》\n");
printf("[2] 还《三国演义》\n");
printf("[3] 借《水浒传》\n");
printf("[4] 还《水浒传》\n");
printf("[5] 借《红楼梦》\n");
printf("[6] 还《红楼梦》\n");
printf("[7] 借《西游记》\n");
printf("[8] 还《西游记》\n");
printf("[0] 退出\n");
printf("--------------------\n");
printf("请选择:");
int sel = -1;
scanf("%d", &sel);
return sel;
}
int pleft(int semid, unsigned short semnum) {
int val = semctl(semid, semnum, GETVAL);
if (val == -1) {
perror("semctl");
return -1;
}
printf("还剩%d册。\n", val);
return 0;
}
int borrow(int semid, unsigned short semnum) {
struct sembuf sops = {semnum, -1, /*0*/IPC_NOWAIT};
if (semop(semid, &sops, 1) == -1) {
if (errno != EAGAIN) {
perror("semop");
return -1;
}
printf("暂时无书,下回再试。\n");
return 0;
}
printf("恭喜恭喜,借阅成功。\n");
return pleft(semid, semnum);
}
int revert(int semid, unsigned short semnum) {
struct sembuf sops = {semnum, 1, 0};
if (semop(semid, &sops, 1) == -1) {
perror("semop");
return -1;
}
printf("好借好还,再借不难。\n");
return pleft(semid, semnum);
}
int main(void) {
printf("创建信号量集...\n");
key_t key = ftok(".", 100);
if (key == -1) {
perror("ftok");
return -1;
}
int semid = semget(key, 4, 0644 | IPC_CREAT | IPC_EXCL);
if (semid == -1) {
perror("semget");
return -1;
}
printf("初始化信号量...\n");
unsigned short semarr[] = {5, 5, 5, 5};
if (semctl(semid, 0, SETALL, semarr) == -1) {
perror("semctl");
return -1;
}
int quit = 0;
while (!quit) {
int sel = pmenu();
switch (sel) {
case 0:
quit = 1;
break;
case 1: // 借《三国演义》-> 0
case 3: // 借《水浒传》 -> 1
case 5: // 借《红楼梦》 -> 2
case 7: // 借《西游记》 -> 3
if (borrow(semid, sel / 2) == -1)
return -1;
break;
case 2: // 还《三国演义》-> 0
case 4: // 还《水浒传》 -> 1
case 6: // 还《红楼梦》 -> 2
case 8: // 还《西游记》 -> 3
if (revert(semid,
(sel - 1) / 2) == -1)
return -1;
break;
default:
printf("无效选择!\n");
scanf("%*[^\n]");//忽略不合法字符
scanf("%*c");//忽略换行字符
break;
}
}
printf("销毁信号量集...\n");
if (semctl(semid, 0, IPC_RMID) == -1) {
perror("semctl");
return -1;
}
printf("完成!\n");
return 0;
}
/* gsem.c */
#include <stdio.h>
#include <errno.h>
#include <sys/sem.h>
int pmenu(void) {
printf("--------------------\n");
printf(" 迷你图书馆\n");
printf("--------------------\n");
printf("[1] 借《三国演义》\n");
printf("[2] 还《三国演义》\n");
printf("[3] 借《水浒传》\n");
printf("[4] 还《水浒传》\n");
printf("[5] 借《红楼梦》\n");
printf("[6] 还《红楼梦》\n");
printf("[7] 借《西游记》\n");
printf("[8] 还《西游记》\n");
printf("[0] 退出\n");
printf("--------------------\n");
printf("请选择:");
int sel = -1;
scanf("%d", &sel);
return sel;
}
int pleft(int semid, unsigned short semnum) {
int val = semctl(semid, semnum, GETVAL);
if (val == -1) {
perror("semctl");
return -1;
}
printf("还剩%d册。\n", val);
return 0;
}
int borrow(int semid, unsigned short semnum) {
struct sembuf sops = {semnum, -1, /*0*/IPC_NOWAIT};
if (semop(semid, &sops, 1) == -1) {
if (errno != EAGAIN) {
perror("semop");
return -1;
}
printf("暂时无书,下回再试。\n");
return 0;
}
printf("恭喜恭喜,借阅成功。\n");
return pleft(semid, semnum);
}
int revert(int semid, unsigned short semnum) {
struct sembuf sops = {semnum, 1, 0};
if (semop(semid, &sops, 1) == -1) {
perror("semop");
return -1;
}
printf("好借好还,再借不难。\n");
return pleft(semid, semnum);
}
int main(void) {
printf("获取信号量集...\n");
key_t key = ftok(".", 100);
if (key == -1) {
perror("ftok");
return -1;
}
int semid = semget(key, 0, 0);
if (semid == -1) {
perror("semget");
return -1;
}
int quit = 0;
while (!quit) {
int sel = pmenu();
switch (sel) {
case 0:
quit = 1;
break;
case 1: // 借《三国演义》-> 0
case 3: // 借《水浒传》 -> 1
case 5: // 借《红楼梦》 -> 2
case 7: // 借《西游记》 -> 3
if (borrow(semid, sel / 2) == -1)
return -1;
break;
case 2: // 还《三国演义》-> 0
case 4: // 还《水浒传》 -> 1
case 6: // 还《红楼梦》 -> 2
case 8: // 还《西游记》 -> 3
if (revert(semid,
(sel - 1) / 2) == -1)
return -1;
break;
default:
printf("无效选择!\n");
scanf("%*[^\n]");
scanf("%*c");
break;
}
}
printf("完成!\n");
return 0;
}
进程间通信总结
1、命令行参数和环境变量(给main函数传参):初始化设置
2、回收进程退出码(接收main函数的返回值或者exit函数的参数):获得终止信息
3、内存映射文件:通过内存的方式操作共享文件,读写磁盘文件数据,速度慢但持久性好
4、信号:简单,异步,信息量有限,效率不高,可靠性不佳
5、有名管道或本地套接字:非近亲进程之间的中等规模数据通信
6、无名管道:近亲进程之间的中等规模数据通信
7、共享内存:大数据量的快速数据通信,缺乏同步机制,需要依赖其它IPC机制实现同步
8、消息队列:天然的同步性,根据类型做细分,适用于中等规模数据通信
9、信号量集:多数进程竞争少数资源
6)IPC命令
查看IPC对象
ipcs -m, m=memory, 共享内存对象
ipcs -q, q=queue, 消息队列对象
ipcs -s, s=semphore, 信号量集对象
ipcs -a, a=all, 全部IPC对象
删除IPC对象
ipcrm -m <共享内存对象标识>
ipcrm -q <消息队列对象标识>
ipcrm -s <信号量集对象标识>