1. IPC定义
IPC技术: 内核进程通信(Inter Process Communication)
2. 共享内存
2.1 共享内存定义
shm(share memory),在主机上指定一块内存作为进程之间的共享内存, 不同的进程之间可以通过一些方式去访问这块内存。
2.2 shm(共享内存 编程模型)
- 创建key ftok函数
- 创建共享内存 shmget函数
- 挂载共享内存 shmat函数
- 卸载共享内存 shmdt函数
- 删除共享内存 shmctl函数
2.3 shm的一些函数原型
2.3.1 ftok函数
//头文件
#include <sys/types.h>
#include <sys/ipc.h>
//函数原型
key_t ftok(const char *pathname, int proj_id);
//返回值: 返回值为一个4byte的整数(返回-1失败)
//0--15 bit: pathname的st_ino属性的低16位
//16--23 bit: pathname的st_dev属性的低8位
//24--31 bit: pro_id的低8位
//参数一: 传入一个路径,一般使用当前路径"."
//参数二: 任意的一个整数,因为要做进程间通信,
//那么另一个进程需要与这个数保持一致才能找到对应的ipcid,一般只使用8个bit,因此取值范围在0--255
2.3.2 shmget函数
//头文件
#include <sys/ipc.h>
#include <sys/shm.h>
//函数原型
int shmget(key_t key, size_t size, int shmflg);
//返回值: 返回一个整数(返回-1失败)
//参数一: ftok函数的返回值
//参数二: 共享的内存大小(byte)(如果数值为1 -- 4096,实际申请4k(一页))
//参数三: 主要与一些标志有关
// IPC_CREAT 不存在共享内存就创建,否则打开
// IPC_EXCL 不存在共享内存才创建,否则错误
2.3.3 shmat函数
//头文件
#include <sys/types.h>
#include <sys/shm.h>
//函数原型
void *shmat(int shmid, const void *shmaddr, int shmflg);
//返回值: 返回申请的共享内存的首地址(返回-1则挂载失败)
//参数一: shmget函数的返回值
//参数二: 一般为0,表示连接到一个由内核选择的可用地址之上
//参数三: 一般为0
2.3.4 shmdt函数
//头文件
#include <sys/types.h>
#include <sys/shm.h>
//函数原型
int shmdt(const void *shmaddr);
//返回值: 成功返回0,失败返回-1
//参数一: shmat函数的返回值
2.3.5 shmctl函数
//头文件
#include <sys/ipc.h>
#include <sys/shm.h>
//函数原型
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
//返回值: 失败返回-1,
//参数一: shmget函数的返回值
//参数二: 设置调用者对共享内存段的权限
// IPC_STAT: 调用者必须对共享内存具有读权限
// IPC_SET: 对shmid_ds 中的某些值做一些修改
// IPC_RMID: 表示销毁某个段
// IPC_INFO: 返回共享内存限制和参数的信息
// SHM_INFO: 返回一个shm_info的结构体
// SHM_STAT: 返回该内存段在内核数组的索引,该数组记录所有共享内存段的信息
// SHM_LOCK: 防止交换共享内存段
// SHM_UNLOCK: 解除防止交换共享内存段
//参数三: 一个指向shmid_ds的结构体指针
struct shmid_ds
{ struct ipc_perm shm_perm;/*用户权限*/
size_t shm_segsz;/* segment (bytes)的大小*/
time_t shm_atime;/*最后连接时间*/
time_t shm_dtime;/*最后的分离时间时间*/
time_t ctime;/*上次更改时间*/
pid_t shm_cpid;/*创建器的PID*/
......
};
2.4 共享内存的优势与缺点
2.4.1 优势
因为两个进程可以使用一段内存, 这就构成了进程之间的双向通信, 无疑传输速率是很快的, 是最快的进程间通信方式。因为不同的进程之间是直接从这一段内存之中存放、读取数据。而且使用共享内存进行通信对数据是没有什么限制的。并且共享内存的生命周期与系统内核的生命周期是一致的。
2.4.2 缺点
共享内存并没有提供同步机制,在一个进程对共享内存进行写操作结束之前,另一个进程是不能对这块共享内存进行同步访问的,因为我们通常想要实现的是多个进程对共享内存的同步访问。
2.5 共享内存示例
使用shmA.c从共享内存中读取数据, shmB.c向共享内存中写入数据, shmctl.c删除共享内存段
2.5.1 共享内存数据读取端
//shmA.c 读取端
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <signal.h>
int* p = NULL;
void hand(){
//5.卸载功能共享内存
shmdt(p);
printf("bye bye!\n");
exit(0);
}
int main(){
signal(SIGINT,hand);
//1.创建key
key_t key = ftok(".",'m'); //参数二在256之内
if(-1 == key) printf("create key error:%m\n"),exit(-1);
printf("create key success!\n");
//2.创建共享内存 内存单位之页 1页 == 4k
int shmid = shmget(key,4096,IPC_CREAT);
if(-1 == shmid) printf("shmget error:%m\n"),exit(-1);
printf("shmget success!\n");
//3.挂载共享内存
p = (int*)shmat(shmid,NULL,0);
if((int*)-1 == p) printf("shmat error:%m\n"),exit(-1);
printf("shmat success!\n");
//4.使用共享内存
while(1){
printf("%d\n",*p);
sleep(1);
}
return 0;
}
2.5.2 共享内存数据发送端
//shmB.c 写入端
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <signal.h>
int* p = NULL;
void hand(){
//5.卸载功能共享内存
shmdt(p);
printf("bye bye!\n");
exit(0);
}
int main(){
signal(SIGINT,hand);
//1.创建key
key_t key = ftok(".",'m'); //参数二在256之内
if(-1 == key) printf("create key error:%m\n"),exit(-1);
printf("create key success!\n");
//2.获取共享内存
int shmid = shmget(key,4096,IPC_CREAT);
if(-1 == shmid) printf("shmget error:%m\n"),exit(-1);
printf("shmget success!\n");
//3.挂载共享内存
p = (int*)shmat(shmid,NULL,0);
if(NULL == p) printf("shmat error:%m\n"),exit(-1);
printf("shmat success!\n");
//4.使用共享内存
int n = 0;
while(1){
*p = n++;
sleep(1);
}
return 0;
}
2.5.3 共享内存段删除端
//shmctl.c 删除共享内存
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int main(){
//1.创建key
key_t key = ftok(".",'m'); //参数二在256之内
if(-1 == key) printf("create key error:%m\n"),exit(-1);
printf("create key success!\n");
//2.获取共享内存
int shmid = shmget(key,4096,IPC_CREAT);
if(-1 == shmid) printf("shmget error:%m\n"),exit(-1);
printf("shmget success!\n");
//3.删除共享内存
shmctl(shmid,IPC_RMID,NULL);
return 0;
}
2.5.4 运行结果
shmA读取数据进程端单独运行:
shmA读取数据进程端与shmB写入数据进程端同时运行:
ipcs -m 命令查看当前共享内存信息:
使用shmctl删除当前创建的共享内存段后:
3. 消息队列
3.1 消息队列定义
msg(message queue), 在主机上指定一个或者多个队列, 一方进程向队列之中放数据, 另一方从队列之中拿东西。
3.2 msg(消息队列 编程模型)
- 创建key ftok函数
- 创建消息队列 msgget函数
- 收发消息 msgrcv 函数 msgsnd 函数
- 删除消息队列 msgctl函数
3.3 msg的一些函数原型
3.3.1 ftok函数
//头文件
#include <sys/types.h>
#include <sys/ipc.h>
//函数原型
key_t ftok(const char *pathname, int proj_id);
//返回值: 返回值为一个4byte的整数(返回-1失败)
//0--15 bit: pathname的st_ino属性的低16位
//16--23 bit: pathname的st_dev属性的低8位
//24--31 bit: pro_id的低8位
//参数一: 传入一个路径,一般使用当前路径"."
//参数二: 任意的一个整数,因为要做进程间通信,
//那么另一个进程需要与这个数保持一致才能找到对应的ipcid,一般只使用8个bit,因此取值范围在0--255
3.3.2 msgget函数
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
//返回值: 返回一个整数(-1表示失败)
//参数一: ftok函数的返回值
//参数二: 主要是一些标志
// IPC_CREAT 不存在共享内存就创建,否则打开
// IPC_EXCL 不存在共享内存才创建,否则错误
3.3.3 msgrcv函数
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
//返回值: (失败返回-1)返回实际复制到mtext数组中的字节数
//参数一: msgget函数的返回值
//参数二: 指向消息缓冲区的指针,此位置用来暂时存储发送和接收的消息,是一个用户可定义的通用结构
struct msgbuf {
long mtype;
char mtext[1]; //可以是1,也可以是其它数
};
//参数三: 消息的大小
//参数四: 从消息队列内读取的消息形态。如果值为零,则表示消息队列中的所有消息都会被读取。
//参数五: 用来指明核心程序在队列没有数据的情况下所应采取的行动。如果msgflg和常数IPC_NOWAIT合用,则在消息队列呈空时,不做等待马上返回-1,并设定错误码为ENOMSG。
3.3.4 msgsnd函数
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
//返回值: (失败返回-1)成功返回0
//参数一: msgget函数的返回值
//参数二: 指向消息缓冲区的指针,此位置用来暂时存储发送和接收的消息,是一个用户可定义的通用结构
struct msgbuf {
long mtype;
char mtext[1]; //可以是1,也可以是其它数
};
//参数三: 消息的大小
//参数四: 用来指明核心程序在队列没有数据的情况下所应采取的行动。如果msgflg和常数IPC_NOWAIT合用,则在msgsnd()执行时若是消息队列已满,则msgsnd()将不会阻塞,而会立即返回-1。当msgflg为0时,msgsnd()及msgrcv()在队列呈满或呈空的情形时,采取阻塞等待的处理模式。
3.3.5 msgctl函数
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
//返回值: 失败返回-1,
//参数一: shmget函数的返回值
//参数二: 设置调用者对共享内存段的权限
// IPC_STAT: 调用者必须对共享内存具有读权限
// IPC_SET: 对msqid_ds 中的某些值做一些修改
// IPC_RMID: 表示销毁某个段
// IPC_INFO: 返回共享内存限制和参数的信息
// MSG_INFO: 返回一个msginfo的结构体
// MSG_STAT: 返回该内存段在内核数组的索引,该数组记录所有共享内存段的信息
//参数三: 一个指向msqid_ds 的结构体指针
struct msqid_ds {
struct ipc_perm msg_perm; /* 所有权和权限 */
time_t msg_stime; /* 最后一次 msgsnd(2) */
time_t msg_rtime; /* 最后一次 msgrcv(2) */
time_t msg_ctime; /* 最后一次修改的时间*/
unsigned long __msg_cbytes; /* 当前队列的字节数(非标准) */
msgqnum_t msg_qnum; /* 当前队列中的消息数 */
msglen_t msg_qbytes; /* 队列允许的最大字节数 */
pid_t msg_lspid; /* 上一次 msgsnd(2) 的PID*/
pid_t msg_lrpid; /* PID of last msgrcv(2) */
};
3.4 消息队列的优势与缺点
3.4.1 优势
消息队列提供了一种从进程向另一个进程发送一个数据块的方法。每个数据块都被认为是一个类型,接收进程接收的数据块可以有不同的类型值。可以通过发送消息来避免命名管道的同步和阻塞的问题。消息队列与管道不同的是,消息队列是基于消息的,而管道是基于字节流的,且消息队列的读取不一定是先入先出。
3.4.2 缺点
每个数据块有大小限制, 整个操作系统中所有的数据块总大小也有一个限制。
3.5 消息队列演示
3.5.1 消息发送端
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
//发送端进程
struct msgbuf{
long type;
char buff[20];
};
int main(){
struct msgbuf msg;
//1.创建key
key_t key = ftok(".",'q');
if(-1 == key) printf("ftok error:%m\n"),exit(-1);
printf("ftok success!\n");
//2. 创建消息队列
int msgid = msgget(key,IPC_CREAT | 0666);
if(-1 == msgid) printf("msgget error:%m\n"),exit(-1);
printf("msgget success!\n");
//3. 发消息
int r;
while(1){
printf("请输入消息类型:");
scanf("%ld",&msg.type);
printf("请输入消息内容:");
scanf("%s",msg.buff);
r = msgsnd(msgid,&msg,sizeof(msg),IPC_NOWAIT); //IPC_NOWAIT非阻塞方式
printf("r: %d\n",r);
}
return 0;
}
3.5.2 消息接收端
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
#define TYPE 2
//接收端进程
struct msgbuf{
long type;
char buff[20];
};
int main(){
struct msgbuf msg;
msg.type = TYPE;
//1.创建key
key_t key = ftok(".",'q');
if(-1 == key) printf("ftok error:%m\n"),exit(-1);
printf("ftok success!\n");
//2. 创建消息队列
int msgid = msgget(key,IPC_CREAT | 0666);
if(-1 == msgid) printf("msgget error:%m\n"),exit(-1);
printf("msgget success!\n");
//3. 接收消息
int r;
while(1){
memset(msg.buff,0,20);
r = msgrcv(msgid,&msg,sizeof(msg),TYPE,IPC_NOWAIT);
printf("r:%d msg:%s type:%ld\n", r, msg.buff, msg.type);
sleep(1);
}
return 0;
}
3.5.3 消息队列删除端
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
int main(){
//1.创建key
key_t key = ftok(".",'q');
if(-1 == key) printf("ftok error:%m\n"),exit(-1);
printf("ftok success!\n");
//2. 创建消息队列
int msgid = msgget(key,IPC_CREAT | 0666);
if(-1 == msgid) printf("msgget error:%m\n"),exit(-1);
printf("msgget success!\n");
//3. 删除当前消息队列
msgctl(msgid,IPC_RMID,NULL);
return 0;
}
3.5.6 运行结果
当发送端的消息类型是type = 2 时, 接收端将消息队列中 type为2的消息取出来处理, type为3的消息仍然在消息队列中
使用命令查看创建的消息队列 (已用字节数是刚刚的type为3的消息)
继续发送一个type为5的消息和一个type为6的消息,发现已用字节数变成了96
删除消息队列后
4. 旗语(信号量)
4.1 旗语(信号量定义)
sem(semaphore), 让多个进程不可能同时访问一块区域
4.2 sem(信号量 编程模型)
- 创建key ftok函数
- 创建旗语(信号量) semget函数
- 初始化旗语(信号量) semctl函数
- 使用旗语(信号量) semop函数
- 删除旗语(信号量) semctl函数
4.3 sem的一些函数原型
4.3.1 ftok函数
//头文件
#include <sys/types.h>
#include <sys/ipc.h>
//函数原型
key_t ftok(const char *pathname, int proj_id);
//返回值: 返回值为一个4byte的整数(返回-1失败)
//0--15 bit: pathname的st_ino属性的低16位
//16--23 bit: pathname的st_dev属性的低8位
//24--31 bit: pro_id的低8位
//参数一: 传入一个路径,一般使用当前路径"."
//参数二: 任意的一个整数,因为要做进程间通信,
//那么另一个进程需要与这个数保持一致才能找到对应的ipcid,一般只使用8个bit,因此取值范围在0--255
4.3.2 semget函数
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
//返回值: 返回一个整数(-1表示失败)
//参数一: ftok函数返回值
//参数二: 创建信号量的个数(一般创建一个)
//参数三: 主要是一些标志
// IPC_CREAT 不存在共享内存就创建,否则打开
// IPC_EXCL 不存在共享内存才创建,否则错误
4.3.3 semop函数
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, size_t nsops);
//返回值: 成功返回0, 失败返回-1
//参数一: semget函数返回值()
//参数二: 信号量的一些信息(用户可更改)
sops[0].sem_num = 0; 信号量的索引
sops[0].sem_op = 0; 加还是减以及加减的值
sops[0].sem_flg = 0; 一般设置为0
//参数三: 操作的次数
4.3.4 semctl函数
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, ...);
//返回值: 失败返回-1
//参数一: semget函数返回值(信号量的标识id)
//参数二: 信号量的标识,标识使用第几个信号量(从0开始)
//参数三: 一个指向msqid_ds 的结构体指针
// IPC_STAT: 调用者必须对共享内存具有读权限
// IPC_SET: 对semid_ds 中的某些值做一些修改
// IPC_RMID: 表示销毁某个段
// IPC_INFO: 返回共享内存限制和参数的信息
// SEM_INFO: 返回一个seminfo的结构体
// SEM_STAT: 返回该内存段在内核数组的索引,该数组记录所有共享内存段的信息
// GETALL: 获取集合中所有的信号量
// GETNCNT: 返回集合中指定semnum的信号量
// GETPID: 返回指定semnum的sempid的值
// GETVAL: 返回指定semnum的semval的值
// GETZCNT: 返回集合中第一个信号量的semzcnt值
// SETALL: 为集合中所有信号量设置值
// SETVAL: 设置一个信号量的值
//参数四: 缺省参数, union semun类型的指针
union semun {
int val; /* 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*/
};
4.4 旗语(信号量)的优势与缺点
4.4.1 优势
节省了打开文件以及关闭文件的时间,数据传输速率提高
4.4.2 缺点
一但被锁定, 在解锁之前出现程序崩溃等问题, 就会导致锁定的信号量无法恢复, 形成永久性的占用, 使用文件操作的方式则不会出现这种情况, 因为在进程退出的时候, 文件就会被关闭, 在该文件描述符上的锁定就会被自动解除。
4.5 旗语(信号量演示)
4.5.1 信号量加端
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int main(){
//1. 创建key
key_t key = ftok(".",'s');
if(-1 == key) printf("ftok error:%m\n"),exit(-1);
printf("ftok success!\n");
//2. 创建信号量
int semid = semget(key,1,IPC_CREAT | 0654);
if(-1 == semid) printf("semget error:%m\n"),exit(-1);
printf("semget success!\n");
//3. 使用旗语
struct sembuf buf;
buf.sem_num = 0;
buf.sem_op = 1; //加
buf.sem_flg = 0;
while(1){
semop(semid,&buf,1); //操作一次
sleep(1);
}
return 0;
}
4.5.2 信号量减端
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
union semun{
int val;
struct semid_ds* buf;
unsigned short* array;
struct seminfo* __buf;
};
int main(){
//1. 创建key
key_t key = ftok(".",'s');
if(-1 == key) printf("ftok error:%m\n"),exit(-1);
printf("ftok success!\n");
//2. 创建信号量
int semid = semget(key,1,IPC_CREAT | 0654);
if(-1 == semid) printf("semget error:%m\n"),exit(-1);
printf("semget success!\n");
//3. 初始化信号量
union semun u;
u.val = 5;
semctl(semid,0,SETVAL,u);
//4. 使用旗语
struct sembuf buf;
buf.sem_num = 0;
buf.sem_op = -1; //减
buf.sem_flg = 0;
int n = 0;
while(1){
printf("卖出%d辆!\n",++n);
semop(semid,&buf,1); //操作一次
}
return 0;
}
4.5.3 信号量删除
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <signal.h>
int main(){
//1. 创建key
key_t key = ftok(".",'s');
if(-1 == key) printf("ftok error:%m\n"),exit(-1);
printf("ftok success!\n");
//2. 创建信号量
int semid = semget(key,1,IPC_CREAT | 0654);
if(-1 == semid) printf("semget error:%m\n"),exit(-1);
printf("semget success!\n");
//3. 删除旗语
semctl(semid,0,IPC_RMID,NULL);
return 0;
}
4.5.4 运行结果
设置信号量 0 初始值为5, 运行A后,再运行B:
ipcs -s 查看信号量信息:
删除创建的信号量后,ipcs -s 查看信号量信息:
5. 管理IPC的ipc命令簇
5.1 ipcs 查看命令
- -m 查看shm(共享内存)
- -q 查看msg(消息队列)
- -s 查看sem(信号量)
5.2 ipcrm 删除命令
- -m 删除shm(共享内存)
- -q 删除msg(消息队列)
- -s 删除sem(信号量)
注: shm msg sem 都必须先有一个key(key是根据fd来创建的)