Linux SystemV通信包括:
共享内存,消息队列,信号量
其中信号量为了进程的同步与互斥而设计的
共享内存和消息队列为了进程间传递数据设计
这里讨论其中之一的共享内存
我们在创建进程时Linux操作系统会创建mm_struct进程地址空间,进程地址空间通过页表映射到物理内存上。
当两个进程的mm_struct进程地址空间通过页表指向同一块物理内存时,就实现了共享内存的过程。
注意:要和父子进程写时拷贝做区别。父子进程在没有修改数据时指向同一块物理空间,但当父子进程有数据修改时,操作系统会修改页表使其指向不同的物理空间。
原型:
int shmget(key_t key, size_t size,int shmflg);
参数:
key:这个共享内存段名字
size:共享内存大小
shmflg:创建共享内存的选项。
返回值:成功返回一个非负整数,即该共享内存段的标识码(共享内存句柄),失败返回-1。在用户层标识共享内存
为了保证共享内存的唯一性,在shemget函数传入的第一个参数key来保证共享内存唯一。这个唯一的值,表明这个是新创建的共享内存,否则说明这个共享内存存在了。
下面用这个生成唯一序列
pathname:路径名 proj_id:数据。可以任意指定
如果生成序列成功,返回这个序列,失败返回-1
调用函数ftok函数,传入路径和整形,系统会根据算法生成序列,我们用这个序列来标定共享内存。如果生成的序列不唯一,则修改路径或整数直到唯一
commen.h
1 #pragma once 2 3 #include<stdio.h> 4 #include<sys/shm.h> 5 #include<sys/types.h> 6 #include<sys/ipc.h> 7 8 #define PATH "/home/dodamce/SharePace"//任意路径 9 10 #define PROJ_ID 0x666//任意值 11 12 #define SIZE 4096//一个内存页大小
sever.c
1 #include"commen.h" 2 3 int main() 4 { 5 key_t key=ftok(PATH,PROJ_ID); 6 if(key<0) 7 { 8 printf("Error\n"); 9 } 10 else 11 { 12 printf("%d\n",key); 13 } 14 return 0; 15 }
如上图,我们生成了一个唯一的序列
#include"commen.h" int main() { key_t key=ftok(PATH,PROJ_ID); if(key<0) { perror("Error\n"); return 1; } int shm=shmget(key,SIZE,IPC_CREAT|IPC_EXCL);//创建一个全新的共享内存 if(shm<0) { perror("Shmget error\n"); return 2; } return 0;
注意:进程退出了,但是共享内存不会自动释放。
perms为共享内存权限,当在shmget没有设置时为默认值0
nattch为使用这块共享内存的进程数
当我们在运行程序时发现共享内存已经存在了
这点与管道通信不同,管道生命周期随进程,进程退出管道自动删除。
但进程退出,共享内存不会释放,它的生命周期随内核。
参数解释:
shmid:共享内存在用户区的编号。
shmaddr:将共享内存映射到进程地址空间的哪一个位置,不关心可以设为NULL由操作系统设置。
shmflg:挂接共享内存时设置的属性。
eg:SHM_RDONLY设置为只能读取共享内存的数据,设置为0表示可读可写。
返回值:
成功挂接返回映射到进程地址空间(虚拟地址)的首地址,失败返回-1;
注意:在进行共享内存挂接时共享内存一定有权限,否则挂接失败
#include"commen.h" int main() { key_t key=ftok(PATH,PROJ_ID); if(key<0) { perror("Error\n"); return 1; } int shm=shmget(key,SIZE,IPC_CREAT|IPC_EXCL|0666);//创建一个全新的共享内存 if(shm<0) { perror("Shmget error\n"); return 2; } printf("attach begin:\n"); shmat(shm,NULL,0);//挂接共享内存 sleep(5); printf("attach end\n"); sleep(1); printf("end\n"); shmctl(shm,IPC_RMID,NULL);//归还共享内存 return 0; }
运行后查看共享内存ipcs -m可以看到申请的共享内存已有一个进程挂接
通过上一步共享内存映射到虚拟空间地址就可以取消与共享内存的映射
#include"commen.h" int main() { key_t key=ftok(PATH,PROJ_ID); if(key<0) { perror("Error\n"); return 1; } int shm=shmget(key,SIZE,IPC_CREAT|IPC_EXCL|0666);//创建一个全新的共享内存 if(shm<0) { perror("Shmget error\n"); return 2; } printf("attach begin:\n"); char* begin= shmat(shm,NULL,0);//记录共享内存映射到虚拟内存上的起始地址 sleep(5); printf("attach end\n"); shmdt(begin);//去关联共享内存 sleep(1); printf("end\n"); shmctl(shm,IPC_RMID,NULL);//归还共享内存 return 0; }
运行结果下图:
可任意看到去关联后挂接数减少1
用命令删除共享内存不用key值删除,使用的是共享内存用户层shmid号删除
函数原型:
参数解释:
shmid:要归还的共享内存的用户层编号。
cmd:执行选项,IPC_RMID表示删除共享内存
返回值:
成功返回0,失败返回-1
#include"commen.h" int main() { key_t key=ftok(PATH,PROJ_ID); if(key<0) { perror("Error\n"); return 1; } int shm=shmget(key,SIZE,IPC_CREAT|IPC_EXCL);//创建一个全新的共享内存 if(shm<0) { perror("Shmget error\n"); return 2; } sleep(5); shmctl(shm,IPC_RMID,NULL);//删除共享内存 return 0; }
运行结果:
如上图,进程退出后共享内存也归还回操作系统。
共享内存的使用在共享内存挂接到进程地址空间到去关联共享内存之间
通过共享内存挂接后返回的映射地址来使用共享内存
思路:sevser进程创建共享内存后,client与sever挂接到同一块共享内存上。client发送数据,sever接受数据。只要保证两个进程看到同一个key值就可以保证两个进程可以挂接到同一个共享内存上。
将client与sever进程共有代码放到commen.h上
commen.h
#pragma once #include<stdio.h> #include<sys/shm.h> #include<sys/types.h> #include<sys/ipc.h> #include<unistd.h> #define PATH "/home/dodamce/SharePace"//任意路径 #define PROJ_ID 0x666//任意值 #define SIZE 4096
sever.c 打印client发送到共享内存的数据
#include"commen.h" int main() { key_t key = ftok(PATH, PROJ_ID); if (key < 0) { perror("Error\n"); return 1; } int shm = shmget(key, SIZE, IPC_CREAT | IPC_EXCL | 0666);//创建一个全新的共享内存 if (shm < 0) { perror("Shmget error\n"); return 2; } char* begin = shmat(shm, NULL, 0); while (1)//打印共享内存的数据 { printf("client:%s\n", begin); sleep(1); } shmdt(begin);//去关联共享内存 shmctl(shm, IPC_RMID, NULL);//归还共享内存 return 0; }
client.c
#include"commen.h" int main() { key_t key = ftok(PATH, PROJ_ID);//获取与sever一样的key值 if (key < 0) { perror("ftok error"); return 1; } int shm = shmget(key, SIZE, IPC_CREAT);//因为已经存在共享内存了,IPC_CREAT一定可以获取到 if (shm < 0) { perror("shmget error"); return 2; } //挂接共享内存 char* begin = shmat(shm, NULL, 0); //开始通信 int size = 0; while (1)//打印数字1,2,3... { begin[size] = '1' + size; size++; begin[size] = '\0';//为字符串添加结束标志 } shmdt(begin);//去关联共享内存 return 0; }
结果:
1.建立共享内存后,在使用共享内存时没有使用系统调用接口。进程挂接共享内存后,对共享内存的修改其他挂接进程立即可以收到,不用进行拷贝。
管道通信:确立完读写进程后,进程要调用write(),read()等系统接口读写。数据需要先拷贝到管道文件中在通信。
共享内存是所有进程通信间最快的。
2.共享内存不提供任何保护机制,没有互斥与同步机制。管道文件存在互斥与同步机制