#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MSG_FIFE "./msgque"
#define MSG_LENGTH 200
int msgfid=0;//全局变量
int create_or_get_msgque(void){
key_t key=0;
int msgfid=0;
/*创建文件目录*/
open(MSG_FIFE,O_RDWR|O_CREAT,0664);
/*生成唯一key*/
key=ftok(MSG_FIFE,'a');
/*get:消息队列*/
msgfid=msgget(key,0664|IPC_CREAT);
return msgfid;
}
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[MSG_LENGTH]; /* message data */
};
void signal_fun(int signo){
msgctl(msgfid,IPC_RMID,NULL);//不需要存放属性的结构体
exit(-1);
}
int main(int argc,char** argv){
msgfid=create_or_get_msgque();
if(argc!=2){ exit(-1);}
long msg_type=atol(argv[1]);
signal(SIGINT,signal_fun);
/*创建父子进程进行通信*/
pid_t ret=0;
ret=fork();
if(ret>0){//父进程发消息
struct msgbuf msg_buf={0};
while(1){
memset(&msg_buf,0,sizeof(msg_buf));
printf("输入要发送的消息:\n");
scanf("%s",msg_buf.mtext);
printf("要通信的对象\n");
scanf("%ld",&msg_buf.mtype);
//第四个参数:0阻塞接收,IPC_NOWAIT非阻塞接收
msgsnd(msgfid,&msg_buf,MSG_LENGTH,0);
}
}
else if(ret==0){//子进程接收消息
struct msgbuf msg_buf={0};
while(1){
//msgrcv(msqid, 结构体指针, 结构体长度,\
编号类型,是否阻塞接收);
bzero(&msg_buf,sizeof(msg_buf));
msgrcv(msgfid,&msg_buf,MSG_LENGTH,msg_type,0);
write(1,msg_buf.mtext,sizeof(msg_buf.mtext));
}
}
return 0;
}
- 消息队列是OS创建的链表
- 链表的所有节点都是保存在
物理内存上的
消息队列这个链表其实也是OS在物理内存上所开辟的缓存
共享内存就是OS在物理内存中开辟一大段缓存空间,与系统API来读写所不同的是,使用共享内存通信时,进程是直接使用地址来共享读写的
直接使用地址来读写缓存时,效率会更高
调用API来读写的话:中间必须经过重重的OS函数调用之后,直到调用到最后一个函数时,该函数才会通过地址去读写共享的缓存.
- 中间的调用过程会降低效率
只要能够共享操作同一段缓存,就都可以实现进程间的通信
------每个进程的虚拟内存严格对应自己的那片物理内存空间
------虚拟空间的虚拟地址,只和自己的那片物理内存空间的物理地址建立映射关系,和其它进程的物理内存空间没有任何的交集
------进程空间之间完全独立
共享内存的思路:让进程空间之间有交集
以两个进程使用共享内存来通信为例
缓存空间建立映射关系
虚拟地址和实际物理地址建立一对一的对应关系,使用虚拟地址读写缓存时,虚拟地址最终是要转为物理地址的,转换时就必须参考这个映射关系。
1.当多个进程映射并共享同一个空间时,在写数据的时候可能会出现相互干扰
2.这时往往需要加保护措施,让每个进程在没有操作时不要被别人干扰
-----文件锁或信号量
shm是share memory的缩写
shmget函数创建新的或获取已有共享内存shmat函数,将物理内存映射到自己的进程空间
一一对应的映射关系直接使用虚拟地址来读写共享的内存空间了shmdt函数,取消映射shmctl函数释放开辟的那片物理内存空间
多个进程使用共享内存通信时,创建者只需要一个
同样的,一般都是谁先运行谁创建,其它后运行的进程直接获取共享使用
大家共享操作同一个内存,即可实现通信。
#include
#include
//功能:创建新的,或者获取已有的共享内存
int shmget(key_t key, size_t size, int shmflg);
/*
返回值
(a)成功:返回共享内存的标识符,以后续操作
(b)失败:返回-1,并且errno被设置。
*/
创建的过程其实就是os在物理内存上划出(开辟出)一段物理内存空间出来
参数:
key:用于生成共享内存的标识符
- IPC_PRIVATE:指定这个后,每次调用shmget时都会创建一个新共享内存。
- 自己指定一个长整型数
- 使用ftok函数,通过路径名和一个8位的整形数来生成key值
size:指定共享内存的大小,我们一般要求size是虚拟页大小的整数倍
一般来说虚拟页大小是
4k(4096字节)如果你指定的大小不是虚拟页的整数倍,也会自动补成整数倍
semflg:与消息队列一样,指定原始权限和IPC_CREAT
比如0664|IPC_CREAT
shmget(ftok生成的key,指定shm的大小,0664|IPC_CREAT);
使用ipcrm命令删除共享内存
- 按照标识符删除 ipcrm
-m msgid- 按照key值删除 ipcrm
-M key
#include
#include
/*功能:
将shmid所指向的共享内存空间映射到进程空间(虚拟内存空间),并返回映射后的起始地址(虚拟地址)*/
//有了这个地址后,就可以通过这个地址对共享内存进行读写操作
void *shmat(int shmid, const void *shmaddr, int shmflg);
/*返回值
a)成功:则返回映射地址
b)失败:返回(void *)-1,并且errno被设置*/
(a)shmid:共享内存标识符
(b)shmaddr:指定映射的起始地址
一般不会这么做,因为我们自己都搞不清哪些虚拟地址被用了,哪些没被用
(c)shmflg:指定映射条件
可读可写的方式映射共享内存只读方式映射共享内存#include
#include
//功能:取消建立的映射
int shmdt(const void *shmaddr);
//返回值:调用成功返回0,失败返回-1,且errno被设置
参数
shmaddr:映射的起始地址(虚拟地址)
#include
#include
//功能:根据cmd的要求,对共享内存进行相应控制
//返回值:调用成功0,失败则返回-1,并且errno被设置
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
IPC_STAT:从内核获取共享内存属性信息到第三个参数(应用缓存)。
IPC_SET: 修改共享内存的属性。
IPC_RMID:删除共享内存,不过前提是只有当所有的映射取消后,才能删除共享内存
删除时,用不着第三个参数,所以设置为NULL
1:cmd为IPC_STAT时: buf用于存储原有的共享内存属性,以供查看。
2:cmd为IPC_SET时: buf中放的是新的属性设置,用于修改共享内存的属性。
struct shmid_ds {
struct ipc_perm shm_perm; /* Ownership and permissions:权限 */
size_t shm_segsz; /* Size of segment (bytes):共享内存大小 */
time_t shm_atime; /* Last attach time:最后一次映射的时间 */
time_t shm_dtime; /* Last detach time:最后一次取消映射的时间 */
time_t shm_ctime; /* Last change time:最后一次修改属性信息的时间 */
pid_t shm_cpid; /* PID of creator:创建进程的PID */
pid_t shm_lpid; /* PID of last shmat(2)/shmdt(2) :当前正在使用进程的PID*/ shmatt_t shm_nattch; /* No. of current attaches:映射数量*/
..};
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SHMFILE "./shmfile"
#define SHMSIZE 4096//4K
int shmId=-1;
void share_memory_get(){
//int shmget(key_t key, size_t size, int shmflg);
open(SHMFILE,O_RDWR|O_CREAT,0664);
key_t key=ftok(SHMFILE,'a');
shmId=shmget(key,SHMSIZE,0664|IPC_CREAT);
}
char buf[300]={"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\
cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\
ddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"};
int main(void){
void* shmaddr=NULL;
share_memory_get();
//建立映射
shmaddr=shmat(shmId,NULL,0);//可读可写
if(shmaddr==(void*)-1){
perror("shmat fails\n");
exit(-1);
}
while(1){
//参数:目的地址,源地址,内容大小
memcpy(shmaddr,buf,sizeof(buf));
sleep(2);
}
return 0;
}
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SHMFILE "./shmfile"
#define SHMSIZE 4096//4K
int shmId=-1;
void share_memory_get(){
//int shmget(key_t key, size_t size, int shmflg);
open(SHMFILE,O_RDWR|O_CREAT,0664);
key_t key=ftok(SHMFILE,'a');
shmId=shmget(key,SHMSIZE,0664|IPC_CREAT);
}
int main(void){
void* shmaddr=NULL;
share_memory_get();
//建立映射
shmaddr=shmat(shmId,NULL,0);//可读可写
if(shmaddr==(void*)-1){
perror("shmat fails\n");
exit(-1);
}
while(1){
if(strlen((char *)shmaddr)!=0){//有数据
printf("%s\n",(char*)shmaddr);
bzero(shmaddr,SHMSIZE);
}
}
return 0;
}
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SHMFILE "./shmfile"
#define SHMSIZE 4096//4K
int shmId=-1;
void* shmaddr=NULL;
void share_memory_get(){
//int shmget(key_t key, size_t size, int shmflg);
open(SHMFILE,O_RDWR|O_CREAT,0664);
key_t key=ftok(SHMFILE,'a');
shmId=shmget(key,SHMSIZE,0664|IPC_CREAT);
}
char buf[300]={"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\
cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\
ddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"};
/*信号捕获,取消映射*/
void signal_fun(int signo){
//删除前必须取消映射
shmdt(shmaddr);
//删除映射(所有映射全删除,内存生效)
shmctl(shmId,IPC_RMID,NULL);
exit(-1);
}
int main(void){
share_memory_get();
signal(SIGINT,signal_fun);
//建立映射
shmaddr=shmat(shmId,NULL,0);//可读可写
if(shmaddr==(void*)-1){
perror("shmat fails\n");
exit(-1);
}
while(1){
//参数:目的地址,源地址,内容大小
memcpy(shmaddr,buf,sizeof(buf));
sleep(2);
}
return 0;
}
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SHMFILE "./shmfile"
#define SHMSIZE 4096//4K
int shmId=-1;
void* shmaddr=NULL;
void share_memory_get(){
//int shmget(key_t key, size_t size, int shmflg);
open(SHMFILE,O_RDWR|O_CREAT,0664);
key_t key=ftok(SHMFILE,'a');
shmId=shmget(key,SHMSIZE,0664|IPC_CREAT);
}
/*信号捕获,取消映射*/
void signal_fun(int signo){
//删除前必须取消映射
shmdt(shmaddr);
//删除映射(所有映射全删除,内存生效)
shmctl(shmId,IPC_RMID,NULL);
exit(-1);
}
int main(void){
share_memory_get();
signal(SIGINT,signal_fun);
//建立映射
shmaddr=shmat(shmId,NULL,0);//可读可写
if(shmaddr==(void*)-1){
perror("shmat fails\n");
exit(-1);
}
while(1){
if(strlen((char *)shmaddr)!=0){//有数据
printf("%s\n",(char*)shmaddr);
bzero(shmaddr,SHMSIZE);
}
}
return 0;
}
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l2Qbqaha-1669187824066)(/home/guojiawei/.config/Typora/typora-user-images/image-20221122224158285.png)]](https://1000bd.com/contentImg/2024/04/19/2d5721742588f2d9.png)
状态数清零后,才可以把共享内存删了,也就是取消映射只会取消自己的
while(1){
if(strlen((char *)shmaddr)!=0){//有数据
printf("%s\n",(char*)shmaddr);
bzero(shmaddr,SHMSIZE);
}
}
读共享内存的代码:
没有数据时,cpu会一直循环的判断,非常浪费cpu资源。
strlen函数只能用于判断字符串
如果对方通过共享内存发送不是字符串,而是结构体、整形、浮点型数据,strlen将无法正确判断。
保证写完后再读数据,当共享内存没有数据时,读进程休眠;
当写进程把数据写完后,将读进程唤醒
多个进程在操作时,涉及到一个谁先谁后的问题,其实就是
同步问题
这里利用有名管道,来实现读取进程符号的目的
#include
#include
#include
#include
#include
#include
#include
void print_error(char* str){
perror(str);
exit(-1);
}
/*利用有名管道发送进程号*/
void send_pid_mkfifo(void){
//创建有名管道
int ret=0;
ret =mkfifo("./fifo",0664);
if(ret==-1&& errno != EEXIST) print_error("mkfifo fails\n");
//以只写方式打开有名管道
int fd=0;
fd=open("./fifo",O_WRONLY);
if(fd==-1) print_error("open fifo fails\n");
//在管道里写自己的进程号
int my_pid=getpid();
ret=write(fd,&my_pid,sizeof(my_pid));
if(ret==-1) print_error("write fails\n");
}
int main(void){
send_pid_mkfifo();
return 0;
}
#include
#include
#include
#include
#include
#include
#include
void print_error(char* str){
perror(str);
exit(-1);
}
/*利用有名管道发送进程号*/
int get_pid_mkfifo(void){
//创建有名管道
int ret=0;
ret =mkfifo("./fifo",0664);
if(ret==-1&& errno != EEXIST) print_error("mkfifo fails\n");
//以只读方式打开有名管道
int fd=0;
fd=open("./fifo",O_RDONLY);
if(fd==-1) print_error("open fifo fails\n");
//在管道里写自己的进程号
int pid_in_fifo;
ret=read(fd,&pid_in_fifo,sizeof(pid_in_fifo));
if(ret==-1) print_error("read fails\n");
return pid_in_fifo;
}
int main(void){
int pid=get_pid_mkfifo();
printf("%d\n",pid);
return 0;
}

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SHMFILE "./shmfile"
#define SHMSIZE 4096//4K
//全局变量
int shmId=-1;
void* shmaddr=NULL;
char buf[300]={"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\
cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\
ddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"};
//创建共享内存
void share_memory_get(){
//int shmget(key_t key, size_t size, int shmflg);
open(SHMFILE,O_RDWR|O_CREAT,0664);
key_t key=ftok(SHMFILE,'a');
shmId=shmget(key,SHMSIZE,0664|IPC_CREAT);
}
/*信号捕获,取消映射*/
void signal_fun(int signo){
//删除前必须取消映射
shmdt(shmaddr);
//删除映射(所有映射全删除,内存生效)
shmctl(shmId,IPC_RMID,NULL);
//删除管道文件
remove("./fifo");
//删除共享内存
remove(SHMFILE);
exit(-1);
}
void print_error(char* str){
perror(str);
exit(-1);
}
/*利用有名管道接收进程号*/
int get_pid_mkfifo(void){
//创建有名管道
int ret=0;
ret =mkfifo("./fifo",0664);
if(ret==-1&& errno != EEXIST) print_error("mkfifo fails\n");
//以只读方式打开有名管道
int fd=0;
fd=open("./fifo",O_RDONLY);
if(fd==-1) print_error("open fifo fails\n");
//在管道里写自己的进程号
int pid_in_fifo;
ret=read(fd,&pid_in_fifo,sizeof(pid_in_fifo));
if(ret==-1) print_error("read fails\n");
return pid_in_fifo;
}
int main(void){
int get_pid=get_pid_mkfifo();
share_memory_get();
signal(SIGINT,signal_fun);
//建立映射
shmaddr=shmat(shmId,NULL,0);//可读可写
if(shmaddr==(void*)-1) print_error("shmaddr fails\n");
while(1){
//参数:目的地址,源地址,内容大小
memcpy(shmaddr,buf,sizeof(buf));
kill(get_pid,SIGUSR1);//唤醒读进程
sleep(1);
}
return 0;
}
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SHMFILE "./shmfile"
#define SHMSIZE 4096//4K
int shmId=-1;
void* shmaddr=NULL;
void share_memory_get(){
//int shmget(key_t key, size_t size, int shmflg);
open(SHMFILE,O_RDWR|O_CREAT,0664);
key_t key=ftok(SHMFILE,'a');
shmId=shmget(key,SHMSIZE,0664|IPC_CREAT);
}
/*信号捕获,取消映射*/
void signal_fun(int signo){
//删除前必须取消映射
shmdt(shmaddr);
//删除映射(所有映射全删除,内存生效)
shmctl(shmId,IPC_RMID,NULL);
//删除管道文件
remove("./fifo");
//删除共享内存文件
remove(SHMFILE);
exit(-1);
}
void signal_fun_user(int signo){
}
void print_error(char* str){
perror(str);
exit(-1);
}
/*利用有名管道发送进程号*/
void send_pid_mkfifo(void){
//创建有名管道
int ret=0;
ret =mkfifo("./fifo",0664);
if(ret==-1&& errno != EEXIST) print_error("mkfifo fails\n");
//以只写方式打开有名管道
int fd=0;
fd=open("./fifo",O_WRONLY);
if(fd==-1) print_error("open fifo fails\n");
//在管道里写自己的进程号
int my_pid=getpid();
ret=write(fd,&my_pid,sizeof(my_pid));
if(ret==-1) print_error("write fails\n");
}
int main(void){
signal(SIGINT,signal_fun);
signal(SIGUSR1,signal_fun_user);
send_pid_mkfifo();
share_memory_get();
//建立映射
shmaddr=shmat(shmId,NULL,0);//可读可写
if(shmaddr==(void*)-1) print_error("shamaddr fails\n");
while(1){
pause();//读之前就休眠
printf("%s\n",(char*)shmaddr);
bzero(shmaddr,SHMSIZE);
}
return 0;
}