目录
一、共享内存shm
古老的System V IPC 包含:【共享内存】、【消息队列】、【信号灯集】。这类IPC对象有唯一的ID,用key值来关联,并且IPC创建后一直存在,直到被删除。此类IPC可通过 ipcs 命令来显示查看
共享内存在内核空间创建,全双工通信,最快的IPC方式,允许多个进程访问同一块物理内存区域(需要注意同步和互斥机制来配合),共享内存的数据可多次读。
使用步骤:
1.生成key值(不是必须的步骤,但通过节点号产生的key值可以确保是唯一的)
key_t ftok(const char *path, int proj_id)
功能:生成key来关联IPC的ID
@param: path 文件的节点号 习惯给 “.”
id 任取1-255
@return: 成功返回key值,失败返回-1
2.创建/打开共享内存
int shmget(key_t key, int size, int shmflg)
@param: key ftok生成的key值,没生成可以任取
size 内存大小
shmflg 创建时给IPC_CREAT|mode值,打开时给0
@return: 成功返回shmid,失败返回-1
3.连接共享内存
void *shmat(int shmid, const void *shmaddr, int shmflg)
@param: shmaddr 一般给NULL系统自动分配
shmflg 一般写0,表示可读可写
@return: 成功返回映射后的地址,失败返回(void *)-1
4.数据读写通信可通过strcpy映射后地址来写,printf来读
5.断开连接共享内存(表示当前进程不占用这块内存了)
int shmdt(shmaddr) 成功返回0失败返回-1
ipcs -m表中状态一栏 会显示当前占用共享内存的数量
此外,进程结束会自动断开连接
6.控制共享内存(常用于删除共享内存,删除前应断开所有进程的连接)
int shmctl(shmid,cmd,struct shmid_ds *buf)
@param: cmd 一般给IPC_RMID
buf 保存或设置共享内存属性的地址 一般给NULL
@return:成功返回0失败返回-1
示例
实现两个进程 全双工通信
实际上就是利用父子进程,各自执行读、写,再加上利用信号去回收子进程(虽然是不阻塞且不关心~)
shmwr.c
#include <stdio.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <signal.h>
#include <wait.h>
#include <sys/types.h>
#include <unistd.h>
void *sig_child_handler(int signo);
int main()
{
key_t key;
int shmid;
void *addr;
pid_t pid;
char buf[128];
//create key
key = ftok(".",2);
if(key == -1)
{
perror("ftok");
return 0;
}
printf("key=%x\n",key);
//create and open shared memory
shmid = shmget(key,128,IPC_CREAT|0666);
if(shmid == -1)
{
perror("shmget");
return 0;
}
printf("shmid=%d\n",shmid);
//connect shared memory
addr = shmat(shmid,NULL,0);
if(addr == (void*)-1)
{
perror("shmget");
return 0;
}
printf("connect shared memory successfully!\n");
if( (pid = fork()) < 0)
{
perror("fork");
return 0;
}
else if(pid == 0) //child process read shm
{
while(1)
{
printf("read shm from shmrd:%s\n",(char *)addr);
if(strncasecmp(addr,"quit",strlen("quit")) == 0) //输入了quit
{
break;
}
sleep(2);
}
}
else if(pid > 0) //father process write shm
{
signal(SIGCHLD,sig_child_handler);
while(1)
{
bzero(buf,128);
fgets(buf,128-1,stdin); //leave 1 char space for '\n' that added automaticcally
buf[strlen(buf) - 1] = '\0';//add end char manually
memset(addr,0,128); //clear before copy
memcpy(addr,buf,strlen(buf));
if( strncasecmp(buf,"quit",strlen("quit")) == 0) //if quit input
{
break;
}
}
}
return 0;
}
void* sig_child_handler(int signo)
{
if(SIGCHLD == signo)
{
waitpid(-1,NULL,WNOHANG);//不阻塞等待、不关心---任一子进程
}
}
shmrd.c
#include <stdio.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <signal.h>
#include <wait.h>
#include <sys/types.h>
#include <unistd.h>
void* sig_child_handler(int signo);
int main()
{
key_t key;
int shmid;
void *addr;
pid_t pid;
char buf[128];
key = ftok(".",2);
if(key == -1)
{
perror("ftok");
return 0;
}
printf("key=%x\n",key);
shmid = shmget(key,128,0);
if(shmid == -1)
{
perror("shmget");
return 0;
}
printf("shmid=%d\n",shmid);
addr = shmat(shmid,NULL,0);
if(addr == (void*)-1)
{
perror("shmget");
return 0;
}
if( (pid = fork()) < 0)
{
perror("fork");
return 0;
}
else if(pid == 0) //child process read shm
{
while(1)
{
printf("read shm from shmwr:%s\n",(char *)addr);
if(strncasecmp(addr,"quit",strlen("quit")) == 0) //输入了quit
{
break;
}
sleep(1);
}
}
else if(pid > 0) //father process write shm
{
signal(SIGCHLD,sig_child_handler);
while(1)
{
bzero(buf,128);
fgets(buf,128-1,stdin); //leave 1 char space for '\n' that added automaticcally
buf[strlen(buf) - 1] = '\0';//add end char manually
memset(addr,0,128); //clear before copy
memcpy(addr,buf,strlen(buf));
if( strncasecmp(buf,"quit",strlen("quit")) == 0) //if quit input
{
shmdt(addr);
shmctl(shmid,IPC_RMID,NULL);
break;
}
}
}
return 0;
}
void* sig_child_handler(int signo)
{
if(SIGCHLD == signo)
{
waitpid(-1,NULL,WNOHANG);//不阻塞等待、不关心---任一子进程
}
}
二、内存映射和共享内存的区别
-
内存映射需要磁盘文件(匿名映射除外);共享内存可以直接创建
-
内存映射注意事项多;共享内存步骤稍繁琐
-
内存映射实际上是进程把同个文件映射到各自地址空间,操作的是独立虚拟内存;
-
共享内存,所有进程的操作都是对同一块共享内存直接操作,避免了拷贝开销
-
使用场景上:内存映射特别适合于需要频繁读写大文件的场景,因为它可以减少磁盘 I/O 操作的次数。它也允许文件的一部分被映射到内存中,这对于处理大型文件尤为有用;共享内存通常用于进程间通信(IPC),允许多个进程访问相同的内存区域,这样可以非常高效地在进程之间交换数据
三、消息队列msg queque
消息链表,消息可随机查询,全双工通信,存放在内核中,每个队列也都有key来关联,具有特定格式(成员包含消息类型、消息内容、其他节点的结构体)和优先级,独立于发送和接收进程,即不会因进程终止而自动删除,消息的读取遵循先进先出
使用步骤:
1.生成key值(不是必须的步骤,但通过节点号产生的key值可以确保是唯一的)
key_t ftok(const char *path, int proj_id)
功能:生成key来关联IPC的ID
@param: path 文件的节点号 习惯给 “.”
id 任取1-255
@return: 成功返回key值,失败返回-1
2.创建或打开队列
int msgget(key_t key, int msgflg)
@param: key ftok成功返回的键值
msgflg IPC_CREAT|mode 没有则创建,有则打开
@return: 成功返回队列ID失败返回-1
3.发送/添加消息到队列
int msgsnd(int msgid, const void *msgp, size_t size, int msgflg)
@param: msgp 消息指针
size 消息长度 不包含消息类型的大小
msgflg 一般给0表示队列满了则阻塞等待,直到能写进去
如果给IPC_NOWAIT表示消息队列满了则不等待立即返回
@return:成功返回0失败返回-1
发送消息时要遵循一定的格式,消息格式参考如下结构体:
typedef struct{
long msg_type; //表示消息类型 这个必须包含 且为大于0的数
char buf[128]; //消息长度
}msg1;
4.从队列中读取/接收消息
int msgrcv(int msgid, const void *msgp, size_t size, long msgtype, int msgflg)
@param: msgtype =0 任意类型(遵循先进先出)的第一条消息
>0 指定类型的第一条消息
<0 小于等于|msgtype|类型(也遵循先进先出)中的第一条消息
msgflg 0 阻塞式等待接收消息
IPC_NOWAIT 没有符合的类型消息立即返回
IPC_EXCEPT 接收除msgtype指定的类型外的其他类型的第一条消息
@return:成功时返回收到的消息长度,失败返回-1
注意:消息内容读到变量的buf里,类型读到msg_type里,消息读取一条,该类型的一条消息则少一条,读完了则阻塞等待消息添加进来
5.控制队列(多用于删除)
int msgctl(int msgid, int cmd,struct msqid_ds *buf)
@param: cmd IPC_RMID删除队列
buf 一般给NULL即可