文章目录
- 一、共享内存 shared memory(shm)
- (一)特点
- (二) 相关API
- 1. 创建共享内存
- 2. 映射共享内存到当前的进程空间
- 3. 取消地址映射
- 4. 共享内存控制
- (三)使用示例
- (四) 属性
- 二、信号灯集---控制进程间同步
- (一)特点
- (二) 相关API
- 1. 创建一个信号灯集
- 2. 信号灯集控制函数
- 3. 信号灯集的操作函数
- (三)封装函数
一、共享内存 shared memory(shm)
(一)特点
在内核中创建共享内存,让进程A和进程B都能够访问到,通过这段内存进行数据的传递。
共享内存是所有进程间通信方式中效率最高的(不需要来回进行数据的拷贝)
(二) 相关API
1. 创建共享内存
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
功能:
创建共享内存
参数:
key:键值
key 通过ftok获取
IPC_PRIVATE:只能用于亲缘进程间的通信
size:共享内存的大小 PAGE_SIZE(4k)的整数倍
shmflg:共享的标志位
IPC_CREAT|0666
或
IPC_CREAT|IPC_EXCL|0666
返回值:
成功 共享内存编号
失败 -1 重置错误码
- 注:
- 共享内存大小必须要4k的整数倍,因为一页是4k。如果申请时不要求4
的整数倍,分配时也是分配4k的整数倍。 - 内核空间越界会直接段错误
- 同一个key值可以同时用于消息队列,共享内存,信号灯集
2. 映射共享内存到当前的进程空间
#include <sys/ipc.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
功能:
映射共享内存到当前的进程空间
参数:
shmid:共享内存编号
shmaddr:指定共享内存出现在进程内存地址的什么位置,直接指定为NULL,让系统自动分配
shmflg:共享内存操作方式
0 读写
SHM_RDONLY 只读
返回值:
成功 指向共享内存的地址
失败 (void *)-1 重置错误码
3. 取消地址映射
#include <sys/ipc.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
功能:
取消地址映射
参数:
shmaddr:指向共享内存的指针
返回值:
成功 0
失败 -1 重置错误码
4. 共享内存控制
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
功能:
共享内存控制的函数
参数:
shmid:共享内存编号
cmd:操作的命令码
IPC_STAT:获取
IPC_SET:设置
IPC_RMID:删除共享内存
标记要销毁的段。实际上,只有在最后一个进程将其分离之后
(也就是说,关联结构shmid_ds的shm_nattch成员为零时),
段才会被销毁。
调用者必须是段的所有者或创建者,或具有特权。buf参数被忽略。
buf:共享内存属性结构体指针
返回值:
成功 0
失败 -1 重置错误码
(三)使用示例
read.c
#include <my_head.h>
#define SHM_PAGE 1024*4
int main(int argc, char const *argv[])
{
//获取键值
key_t key = ftok("/home/linux/05work",'A');
if(-1 == key)
ERR_LOG("ftok error");
//创建共享内存
int shmid = shmget(key, SHM_PAGE,IPC_CREAT|0666);
if(-1 == shmid)
ERR_LOG("shmget error");
//映射共享内存
char *addr = (char *)shmat(shmid,NULL,SHM_RDONLY);
while(1){
getchar();//防止刷屏,回车一次打印一次
if(!strcmp(addr,"quit")){
break;
}
printf("%s",addr);
}
//解除映射
if(-1 == shmdt(addr)){
ERR_LOG("shmid error");
}
//第三个参数会被忽略
if(-1 == shmctl(shmid,IPC_RMID,NULL)){
if(EINVAL == errno){
return 0;
}
ERR_LOG("shmctl error");
}
return 0;
}
write.c
#include <my_head.h>
#define SHM_PAGE 1024*4
int main(int argc, char const *argv[])
{
//获取键值
key_t key = ftok("/home/linux/05work",'A');
if(-1 == key)
ERR_LOG("ftok error");
//创建共享内存
int shmid = shmget(key, SHM_PAGE,IPC_CREAT|0666);
if(-1 == shmid)
ERR_LOG("shmget error");
//映射共享内存
char *addr = (char *)shmat(shmid,NULL,0);
while(1){
printf("请输入要发送的内容:");
scanf("%s",addr);
if(!strcmp(addr,"quit")){
break;
}
}
//解除映射
if(-1 == shmdt(addr)){
ERR_LOG("shmid error");
}
//销毁
if(-1 == shmctl(shmid,IPC_RMID,NULL)){
if(EINVAL == errno){
return 0;
}
ERR_LOG("shmctl error");
}
return 0;
}
(四) 属性
struct shmid_ds{
struct ipc_perm shm_perm; //权限结构体
size_t shm_segsz; //共享内存大小,单位是字节
__time_t shm_atime; //最后一次映射的时间
__pid_t shm_cpid; //创建共享内存进程的pid
__pid_t shm_lpid; //最后一次操作共享内存进程的pid
shmatt_t shm_nattch; //共享内存映射的次数
};
struct ipc_perm{
__key_t __key; //ftok获取的key
__uid_t uid; //用户的ID
__gid_t gid; //组ID
__uid_t cuid; //创建共享内存的用户的ID
__gid_t cgid; //创建共享内存的组的ID
unsigned short int mode; //消息队列的权限
};
二、信号灯集—控制进程间同步
(一)特点
信号灯集:又叫做信号量数组,他是实现进程间同步的机制
在一个信号灯集中可以有很多个信号灯,这些信号灯之间工作相互互不干扰。
一般使用时使用的都是二值信号灯
(二) 相关API
1. 创建一个信号灯集
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
功能:
创建一个信号灯集
参数:
key:键值
IPC_PRIVATE/key
nsems:信号灯集合中信号灯的个数
semflag:创建的标志位
IPC_CREAT|0666 或 IPC_CREAT|IPC_EXCL|0666
返回值:
成功 semid
失败 -1 重置错误码
- 补充:使用IPC_PRIVATE创建的IPC对象, key值属性为0,和IPC对象的编号就没有了对应关系。这样毫无关系的进程,就不能通过key值来得到IPC对象的编号(因为这种方式创建的IPC对象的key值都是0)。因此,这种方式产生的IPC对象,和无名管道类似,不能用于毫无关系的进程间通信。但也不是一点用处都没有,仍然可以用于有亲缘关系的进程间通信。
2. 信号灯集控制函数
int semctl(int semid, int semnum, int cmd, ...);
功能:信号灯集的控制函数
参数:
semid信号灯集的ID
senum:信号灯的编号 从0开始
cmd:命令码
SETVAL:设置信号灯的值 --->第四个参数val选项
GETVAL:获取信号灯的值 --->不需要第四个参数
IPC_STAT:获取信号灯集的属性--->第二参数被忽略,第四个参数buf选项
IPC_SET :设置信号灯集的属性--->第二参数被忽略,第四个参数buf选项
IPC_RMID:删除信号灯集 第二参数被忽略,第4个参数不用填写
@...:可变参
union semun{
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
};
返回值:
成功:
GETVAL:成功返回信号灯的值
其余的命令码成功返回0
失败 -1 重置错误码
- 注:*初始化操作在两个进程中必须只能进行一次,因为进程之间运行是没有顺序的,可以出现其中一个进程已经进行了信号灯集的相关操作后,另一个进程进行初始化而导致出错。
3. 信号灯集的操作函数
int semop(int semid, struct sembuf *sops, size_t nsops);
功能:
信号灯集中信号灯的操作函数
参数:
semid:信号灯集的编号
sops:操作方式
struct sembuf{
unsigned short sem_num; //信号灯的编号
short sem_op; //操作方式(PV)-1:P操作,申请资源 1:V操作,释放资源
short sem_flg; //操作的标志位 0:阻塞 IPC_NOWAIT:非阻塞方式操作
}
nsops:本次操作信号灯的个数
返回值:
成功 0
失败 -1 重置错误码
- 注:同时对多个信号灯进行操作时,可以定义一个结构体数组
(三)封装函数
原生函数直接使用会比较繁琐,因此会做二次封装。
初始化(解决可能重复初始化的问题):
int sem_init_pack(key_t key, int nsem){
int semid = 0;
//不存在创建,存在退出返回semid
if(-1 == (semid = semget(key,nsem,IPC_CREAT|IPC_EXCL|0666))){
if(EEXIST == errno){//已存在导致的错误
if((-1 == (semid = semget(key,nsem,IPC_CREAT|0666)))){
return -1;
}
return semid;
}else{//不是已存在导致的错误,说明出错
return -1;
}
}
//初始化,第一个置1,其余置0
if(semctl(semid,0,SETVAL,1)){
return -1;
}
for(int i = 1; i < nsem; i++){
if(semctl(semid,i,SETVAL,0)){
return -1;
}
}
return semid;
}
单信号灯PV操作
int sem_p_pack(int semid,int semnum){
struct sembuf sembuff={
.sem_num=semnum,
.sem_op=-1,
.sem_flg=0};
if(-1 == semop(semid,&sembuff,1)){
return errno;
}
return 0;
}
int sem_v_pack(int semid,int semnum){
struct sembuf sembuff={
.sem_num=semnum,
.sem_op=1,
.sem_flg=0
};
if(-1 == semop(semid,&sembuff,1)){
return errno;
}
return 0;
}
多信号灯PV操作
传入一个int型数组指针,数组成员就是要进行p操作的信号灯编号
int sem_p_set_pack(int semid, int *semnum, int num){
struct sembuf sembuff[num];
for(int i=0; i<num; i++){
sembuff[i].sem_flg=1;
sembuff[i].sem_op=-1;
sembuff[i].sem_num=semnum[i];
if(-1 == semop(semid,sembuff+i,1)){
return errno;
}
}
return 0;
}
int sem_v_set_pack(int semid, int *semnum, int num){
if(NULL==semnum) return -1;
struct sembuf sembuff[num];
for(int i=0; i<num; i++){
sembuff[i].sem_flg=1;
sembuff[i].sem_op=1;
sembuff[i].sem_num=semnum[i];
if(-1 == semop(semid,sembuff+i,1)){
return errno;
}
}
return 0;
}
销毁
int sem_destroy_pack(int semid){
if(-1 == semctl(semid,0,IPC_RMID))
{
//如果出现这个错误说明是已经销毁过了,无视这个错误
if(EINVAL == errno){
return 0;
}
//否则就是出错了,返回错误信息
printf("semop error:%s",strerror(errno));
return -1;
}
return 0;
}