目录
必备IPCS命令解析
ipcs
ipcrm
Linux IPC消息队列
msgget
msgsnd
msgrcv
msgctl
Linux IPC信号量
理解信号量
semget
semop
semctl
Linux IPC共享内存
shmget
shmat
shmdt
shmctl
本文纯粹就是小杰对于自己学完Linux操作系统之后回过头来对于Linux中的核心重点知识的一个梳理.
小杰会尽量地将其梳理清楚, 大家一起学习,共同进步, 知识不分高低, 计算机的学习小杰认为也是一个 量变 ---> 质变 的过程
天道酬勤, 水滴石穿, 在不同的阶段就干好自己当前阶段力所能及之事, 至少是没有在寝室的床上瘫着消磨时光 -------- 愿大家都学有所成,所获
功能 : 查看 system V IPC进程间通信设施的信息
常用选项
-a : 查看所有通信设施信息, 不加选项默认-a
-m : 指定查看共享内存
-q : 指定查看消息队列
-s : 指定查看信号量
功能:删除System V进程间通信(IPC)对象和关联的数据结构
使用方式: ipcrm 选项 id号
常用选项
-m : 删除共享内存
-q : 删除消息队列
-s : 删除信号量
eg : 指定删除shmid = 18 和 msqid = 8的 IPC对象.
消息队列提供了一个进程向另外一个进程传输一个数据块的方法
消息队列优势: 对比管道通信来看
函数功能: 创建一个全局消息队列IPC对象. (内核数据结构)
参数分析: key 唯一标识, 唯一标识一个全局的消息队列. msgflg指定权限
返回值: 成功返回一个正整数, 代表消息队列的句柄, 失败返回 -1,并且设置errno
插一个权限介绍: r : 读权限 w : 可写权限 X : 可执行权限. 所谓的9位权限标识位, 就是针对: 文件拥有者, 文件拥有组人员, 其他人员的访问权限的限制
函数功能:发送一条消息到消息队列中
参数分析:
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[1]; /* message data */
};
//我们必须按照这个形式设置msgbuff
返回值:成功返回 0, 失败返回 -1 并且设置errno
函数功能:从消息队列中接收一条消息
参数分析:
msgtyp 指定接受哪种类型的消息:
返回值: 成功返回接收的消息正文长度, 失败返回 -1 并且设置 errno
函数功能: 控制消息队列, 对于消息队列IPC进行设置, 操作
参数分析:
返回值:成功返回 0, 失败返回 -1 并且设置 errno
实际案例, 父子进程之间通信, 父进程向消息队列中写入消息, 子进程按照消息的类型接收消息
#include
#include
#include
#include
#include
#include
#include
#include
#define ERR_EXIT(m) \
do { perror(m); exit(EXIT_FAILURE); } while (0);
#define TEXTSIZE 1024
//消息结构体
typedef struct msgbuff {
long int type;
char text[TEXTSIZE];
} msgbuff;
int main(int argc, char* argv[]) {
if (2 != argc) {
fprintf(stderr, "usage: %s ", argv[0]);
exit(EXIT_FAILURE);
}
key_t key = ftok(argv[1], 'q');
pid_t pid = fork();
int msgid;
if (pid == -1) {
ERR_EXIT("fork");
}
if (pid == 0) {
//son process
//获取消息队列
msgid = msgget(key, 0666 | IPC_CREAT);
msgbuff msg;
//然后接收类型为1的消息并且打印
int read_size = msgrcv(msgid, &msg, TEXTSIZE, 1, 0);
if (-1 == read_size) {
ERR_EXIT("msgrcv");
}
printf("type: %d, text: %s\n", msg.type, msg.text);
memset(&msg, 0, sizeof(msg));
//再接收一下类型为2的消息并且打印
read_size = msgrcv(msgid, &msg, TEXTSIZE, 2, 0);
if (-1 == read_size) {
ERR_EXIT("msgrcv");
}
printf("type: %d, text: %s\n", msg.type, msg.text);
} else {
//fa process
//创建消息队列
msgid = msgget(key, 0666 | IPC_CREAT);
//创建消息结构体
msgbuff msg1, msg2;
memset(&msg1, 0, sizeof(msg1));
memset(&msg1, 0, sizeof(msg2));
msg1.type = 1;
memcpy(msg1.text, "hello msg queue I am type1", strlen("hello msg queue I am type1"));
msgsnd(msgid, &msg1, TEXTSIZE, 0);
msg2.type = 2;
memcpy(msg2.text, "hello msg queue, I am type2", strlen("hello msg queue, I am type2"));
msgsnd(msgid, &msg2, TEXTSIZE, 0);
waitpid(pid, NULL, 0);
if (-1 == msgctl(msgid, IPC_RMID, NULL)) {
ERR_EXIT("msgctl");
exit(1);
}
}
return 0;
}
信号量是一种资源, 临界资源, 是为了多进程间或者多线程间的同步互斥问题而存在的.
信号量本身也是一个IPC对象,是临界共享资源, 利用它使得多进程或者多线程之间可以实现通信.
信号量本身就是具有原子性的计数器, 意思就是说对于信号量的操作是自带原子性的, 就不用担心多进程或者多线程互斥写操作的问题了.
何为同步问题: 同步, 两个事件之间具有同步的性质, 意味着两个事件之间是存在条件约束的, 是有等待关系的. eg B 的要发生必须先发生 A ,否则B只能挂起等待, 则称 A, B是同步的
Linux最常见的同步问题: 条件变量, 满足一个条件, 才能执行一个操作. 在满足条件之前挂起等待, 条件的满足其实依赖的是 另一个事件的发生.
通信的时候通信双方也必须保持同步,比如说共享内存就需要借助信号量这样的同步机制,维护通信双方进程之间的同步.
何为互斥问题: 多个进程或者线程对于同一份资源进行写操作, 写操作一定是互斥的, 即为同一时刻只能允许一个进程或者线程对这份资源进行一个写操作.
函数功能:创建或者获取一个信号量集合IPC对象
参数分析:
插一个权限介绍: r : 读权限 w : 可写权限 X : 可执行权限. 所谓的9位权限标识位, 就是针对: 文件拥有者, 文件拥有组人员, 其他人员的访问权限的限制
返回值:成功返回信号集合句柄, 失败返回 -1 并且设置errno
函数功能:对于信号量资源进行 P V 操作,改变信号量的值
参数分析:
struct sembuf
{
unsigned short int sem_num;/* semaphore number */
short int sem_op; /* semaphore operation */
short int sem_flg;/* operation flag */
};
if (sem_op is positive integer)
add the val to semval
if (sem_op is zero)
wait-for-zero" operation
if (sem_op is less than zero)
add the val to semval (加负数, 相当于减)
sem_flg 可选值是 IPC_NOWAIT, SEM_UNDO,IPC_NOWAIT指,无论信号量集操作是否成功,semop调用都立刻返回。并且设置errno = EAGAIN , SEM_UNDO含义是,当进程退出时,取消正在进行的semop操作
返回值:成功返回 0, 失败返回 -1 并且设置 errno
函数功能:对于指定信号量IPC进行操作
参数分析:
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常用列举:
SETVAL 将信号量的semval值设置为semun.val
IPC_RMID 立即移除信号量集,唤醒所有等待信号量集的进程
返回值:成功返回 0, 失败返回 -1 并且设置 errno
实际案例:利用信号量实现一个简单的父子进程之间的同步等待, 通信, 也就是利用P V实现了跟锁一样的效果, 实现了父子进程同步打印, 不会出现乱入(汇编指令中间重入)问题, 保证原子性操作
#include
#include
#include
#include
#include
#include
#include
#include
#define ERR_EXIT(m) \
do { perror(m); exit(EXIT_FAILURE); } while (0);
//联合结构体
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*/
};
int initsem(key_t key) {
int semid = -1;
//创建一个信号量集合IPC, 其中包含一个信号量
if (-1 == (semid = semget(key, 1, 0666 | IPC_CREAT))) {
ERR_EXIT("semget");
}
//设置信号量val
union semun sem_un;
sem_un.val = 1;
if (-1 == semctl(semid, 0, SETVAL, sem_un)) {
ERR_EXIT("semctl");
}
return semid;
}
//销毁
void destorysem(int semid) {
if (-1 == semctl(semid, 0, IPC_RMID)) {
ERR_EXIT("semctl destorysem");
}
}
void P(int semid) {
//因为信号量集合中就只是一个
//所以sembuf无需设置为vector
struct sembuf op;//操作
op.sem_num = 0;
op.sem_op = -1;// - 1
op.sem_flg = SEM_UNDO;
if (-1 == semop(semid, &op, 1)) {
ERR_EXIT("semop P");
}
}
void V(int semid) {
struct sembuf op;//操作
op.sem_num = 0;
op.sem_op = 1;// + 1
op.sem_flg = SEM_UNDO;
if (-1 == semop(semid, &op, 1)) {
ERR_EXIT("semop V");
}
}
int main(int argc, char* argv[]) {
if (2 != argc) {
fprintf(stderr, "usage: %s ", argv[0]);
exit(EXIT_FAILURE);
}
//1.获取唯一标识key
key_t key = ftok(argv[1], 'a');
//2. 创建信号量集合
int semid = initsem(key);
int cnt = 10;
pid_t pid = fork();
if (pid == -1) {
ERR_EXIT("fork");
}
if (pid == 0) {
//son process
while (cnt > 0) {
// P(semid);
printf("I am son process\n");
sleep(1);
//V(semid);
cnt -= 1;
}
} else {
//fa process
while (cnt > 0) {
//P(semid);
printf("I am fa process\n");
sleep(1);
//V(semid);
cnt -= 1;
}
waitpid(pid, NULL, 0);
destorysem(semid);
}
return 0;
}
共享内存是最快的IPC机制, 因为它不涉及任何的数据传输. 而是采取利用一块公共物理内存映射, 挂载到进程地址空间, 使得多个进程对于同一块物理内存可视的方法实现通信.
但是也存在一定的弊端, 就是无法支持同步, 我们就需要借助其他通信机制手段来同步进程对共享内存的访问
eg : 我 A 进程对于共享内存写入了数据, 需要跟 B 进程通信, 建立同步, 可以这个时候存在一个很大的问题, B 进行如何可以得知 A 进行已经写入了数据了. 此时我们就需要借助semaphore来实现同步. --- 这一点很重要, 理解为何使用共享内存就需要配合其他通信机制来完成同步, 因为共享内存本身就不支持同步访问. 不同于信号量是本身支持原子操作的.
功能:申请共享内存
shmflag : 9位权限标志位: 我们通常使用 0x666 | IPC_CREAT | IPC_EXCL
插一个权限介绍: r : 读权限 w : 可写权限 X : 可执行权限. 所谓的9位权限标识位, 就是针对: 文件拥有者, 文件拥有组人员, 其他人员的访问权限的限制
功能:将物理内存连接,挂载,关联到进程地址空间中去
shmaddr :NULL 自动选取连接地址, 不为NULL, 人为指定到希望挂载的地址上
我们还是填写 NULL
Return Val : 返回一个ptr, 指向共享物理内存的首地址. 虚拟空间中的连接首地址
功能:解除挂载, 将共享内存和当前进程进行脱离.
shmaddr : 之前 shmat 的 return val, 共享内存映射在虚拟地址空间上的首地址.
Return Val: success return 0 failure return -1
功能:对于共享内存IPC进行操作
cmd: IPC_RMID 删除共享内存
buf 传 NULL 即可
实际案例:利用共享内存实现 父子进程之间的 通信, 父进程向共享内存中写入数据, 子进程在写入时候同步打印写入的数据即可.
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define ERR_EXIT(m) \
do { perror(m); exit(EXIT_FAILURE); } while (0);
#define SHMSIZE 1024
//联合结构体
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*/
};
int initsem(key_t key) {
int semid = -1;
if (-1 == (semid = semget(key, 1, 0666 | IPC_CREAT))) {
ERR_EXIT("semid");
}
//设置信号量集合中编号为0号的信号量val 为 2
union semun sem_un;
sem_un.val = 1;
if (-1 == semctl(semid, 0, SETVAL, sem_un)) {
ERR_EXIT("semop set val");
}
return semid;
}
void destorysem(int semid) {
if (-1 == semctl(semid, 0, IPC_RMID)) {
ERR_EXIT("semctl destorysem");
}
}
void P(int semid) {
struct sembuf op;
op.sem_num = 0;
op.sem_op = -1;
op.sem_flg = SEM_UNDO;
if (-1 == semop(semid, &op, 1)) {
ERR_EXIT("semop P");
}
}
void V(int semid) {
struct sembuf op;
op.sem_num = 0;
op.sem_op = 1;
op.sem_flg = SEM_UNDO;
if (-1 == semop(semid, &op, 1)) {
ERR_EXIT("semop V");
}
}
void waitforzero(int semid) {
struct sembuf op;
op.sem_num = 0;
op.sem_op = 0;
op.sem_flg = SEM_UNDO;
if (-1 == semop(semid, &op, 1)) {
ERR_EXIT("semop zero");
}
}
int main(int argc, char* argv[]) {
if (2 != argc) {
fprintf(stderr, "usage: %s ", argv[0]);
exit(EXIT_FAILURE);
}
key_t key1 = ftok(argv[1], 'c');
key_t key2 = ftok(argv[1], 'C');
//创建信号量集合IPC
int semid = initsem(key1);
int shmid = -1;
pid_t pid = fork();
if (pid < 0) {
ERR_EXIT("fork");
}
if (pid == 0) {
//son process
waitforzero(semid);
if (-1 == (shmid = shmget(key2, SHMSIZE, 0666 | IPC_CREAT))) {
ERR_EXIT("shmget");
}
void* p = shmat(shmid, NULL, 0);
if ((void*)-1 == p)
{
ERR_EXIT("shmat");
}
char buf[SHMSIZE] = {0};
memcpy(buf, p, SHMSIZE);
printf("%s\n", buf);
//卸载共享内存
if (-1 == shmdt(p))
{
ERR_EXIT("shmdt in parent");
}
V(semid);//为父进程归还这个资源.
} else {
// fa process
if (-1 == (shmid = shmget(key2, SHMSIZE, 0666 | IPC_CREAT))) {
ERR_EXIT("shmget");
}
void* p;
p = shmat(shmid, NULL, 0);
if ((void*)-1 == p)
{
ERR_EXIT("shmat");
}
char buff[SHMSIZE];
printf("请输入要传输给子进程的数据\n");
scanf("%s", buff);
memcpy(p, buff, strlen(buff));
P(semid);
waitpid(pid, NULL, 0);
//通信完成, 销毁所有IPC
destorysem(semid);
//卸载内存, 销毁shmid共享内存
if (-1 == shmdt(p)) {
ERR_EXIT("shmdt");
}
if (-1 == shmctl(shmid, IPC_RMID, NULL)) {
ERR_EXIT("shmctl destoryshm");
}
}
return 0;
}