1内存共享
1.1SYSTEM V共享
共享内存是系统出于多个进程之间通讯的考虑,而预留的的一块内存区。在/proc/sys/kernel/目录下,记录着共享内存的一些限制,如一个共享内存区的最大字节数shmmax,系统范围内最大共享内存区标识符数shmmni等,可以手工对其调整,但不推荐这样做。
共享内存的使用,主要有以下几个API:ftok()、shmget()、shmat()、shmdt()及shmctl()。
1.1.1 ftok
函数原型
key_t ftok(const char *pathname, int proj_id);
参数
pathname:文件名,必须保证在系统中存在,并且进程能够访问。
proj_id:子序号,一个1-255之间的一个整数值,典型的值是一个ASCII值。
返回值
成功:返回一个key_t值;
失败:返回-1,可以用strerror(errno)打印错误。
注:
1、 考虑到应用系统可能在不同的主机上,可以直接定义一个key_t值,而不是由ftok获取。
#define IPCKEY 0x344378
2、采用ftok来生成key的情况下,如果ftok的参数pathname指定文件被删除后重建,则文件系统会赋予这个同名文件(或目录)新的i节点信息,于是这些进程所调用的ftok虽然都能正常返回,但得到的键值却并不能保证相同。
1.1.2 shmget
函数原型
int shmget(key_t key, size_t size, int shmflg);
功能
shmget()用来获得共享内存区域的ID,如果不存在指定的共享区域就创建相应的区域。
参数
Key:是这块共享内存的标识符。如果是父子关系的进程间通信的话,这个标识符用IPC_PRIVATE来代替。如果两个进程没有任何关系,所以就用ftok()算出来一个标识符(或者自己定义一个)使用了。
Size:共享内存的大小;
Shmflg:内存的模式(mode)以及权限标识。
模式取值如下:
IPC_CREAT 新建(如果已创建则返回目前共享内存的id);
IPC_EXCL 与IPC_CREAT结合使用,如果已创建则则返回错误;
“模式” 和“权限标识”进行“或”运算,做为第三个参数。
如: IPC_CREAT | IPC_EXCL | 0640
例子中的0666为权限标识,4/2/1 分别表示读/写/执行3种权限,第一个0是UID,第一个6(4+2)表示拥有者的权限,第二个4表示同组权限,第3个0表示他人的权限。
返回值
成功:返回共享内存的ID;
失败:返回-1。
注:创建共享内存时,shmflg参数至少需要 IPC_CREAT | 权限标识,如果只有IPC_CREAT 则申请的地址都是k=0xffffffff,不能使用;获取已创建的共享内存时,shmflg不要用IPC_CREAT(只能用创建共享内存时的权限标识,如0640),否则在某些情况下,比如用ipcrm删除共享内存后,用该函数并用IPC_CREAT参数获取一次共享内存(当然,获取失败),则即使再次创建共享内存也不能成功,此时必须更改key来重建共享内存。
1.1.3 shmat
函数原型
void *shmat( int shmid , char *shmaddr , int shmflag );
功能
用来允许本进程访问一块共享内存的函数。
参数
Shmid:共享内存的ID;
Shmaddr:共享内存的起始地址,如果shmaddr为NULL,内核会把共享内存映像到调用进程的地址空间中选定位置;如果shmaddr不为NULL,内核会把共享内存映像到shmaddr指定的位置。所以一般把shmaddr设为NULL。
Shmflag:本进程对该内存的操作模式。如果是SHM_RDONLY的话,就是只读模式。其它的是读写模式。
返回值
成功:返回内存映射的地址;
失败:返回-1;
1.1.4 shmdt
函数原型
int shmdt( char *shmaddr );
功能
删除本进程对这块内存的使用,shmdt()与shmat()相反,是用来禁止本进程访问一块共享内存的函数。
参数
Shmaddr:共享内存的起始地址。
返回值
成功:返回0;
失败:返回-1;
1.1.5 shmctl
函数原型
int shmctl( int shmid , int cmd , struct shmid_ds *buf );
功能
控制对这块共享内存的使用。
参数
Shmid:共享内存的ID;
Cmd:控制命令,具体如下:
IPC_STAT 得到共享内存的状态
IPC_SET 改变共享内存的状态
IPC_RMID 删除共享内存
Buf:一个结构体指针。IPC_STAT的时候,取得的状态放在这个结构体中。如果要改变共享内存的状态,用这个结构体指定。
返回值
成功:返回0;
失败:返回-1;
1.1.6 示例
进程1:
#include <sys/ipc.h> #include <sys/shm.h> #include<stdio.h> #include<signal.h> #include<stdlib.h> #include<string.h> #define IPCKEY 0x213930 void signalHandle() { printf("exit!\n"); exit(1); } typedef struct Shm { int pid; char str[100]; }Shm_t; int main() { int d = 0; int pid; int shm_id; Shm_t *shm; pid = getpid(); shm_id = shmget(IPCKEY, sizeof(Shm_t),0640); if(shm_id != -1) { printf("shm is exit!\n"); shm = (Shm_t *)shmat(shm_id,NULL,0); if(shm != (void *)(-1)) { shmdt(shm); shmctl(shm_id,IPC_RMID,0); } } shm_id = shmget(IPCKEY, sizeof(Shm_t),0640|IPC_CREAT|IPC_EXCL); printf("shm_id:%d\n",shm_id); if(shm_id != -1) { shm = (Shm_t *)shmat(shm_id,NULL,0); if(shm != (void *)(-1)) { shm->pid = pid; strcpy(shm->str,"wangyuanxiang!"); } } signal(SIGTERM,signalHandle); printf("PID:%d\n",pid); printf("PID:%d\n",getpid()); while(1) { printf("time:%d\n",d++); sleep(1); } return 0; }
进程2:
#include <sys/ipc.h> #include <sys/shm.h> #include<stdio.h> #include<signal.h> #include<string.h> #define IPCKEY 0x213930 typedef struct Shm { int pid; char str[100]; }Shm_t; int main() { int pid; Shm_t *shm; int shm_id = shmget(IPCKEY, sizeof(Shm_t),0640); if(shm_id != -1) { printf("shm_id:%d\n",shm_id); shm = (Shm_t *)shmat(shm_id,NULL,0); if(shm != (void *)(-1)) { pid = shm->pid; printf("pid:%d\n",pid); printf("str:%s!\n",shm->str); kill(pid,SIGTERM); } } return 0; }
程序运行:
首先运行进程1,进程1在操作共享内存;
再运行进程2:读取共享内存,并发送SIGTERM信号给进程1,结束进程1.
1.2 mmap()系统调用
mmap()系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以向访问普通内存一样对文件进行访问,不必再调用read(),write()等操作。
注:实际上,mmap()系统调用并不是完全为了用于共享内存而设计的。它本身提供了不同于一般对普通文件的访问方式,进程可以像读写内存一样对普通文件的操作。而Posix或系统V的共享内存IPC则纯粹用于共享目的,当然mmap()实现共享内存也是其主要应用之一。
1.2.1mmap
函数原型
void *mmap(void *start,size_t length,int prot,int flags,int fd,off_t offsize);
功能以及参数
详情参见《Linux_C函数库参考手册2.pdf》。
1.2.2mumap
函数原型
int munmap(void *start,size_t length);
表头文件
#include<unistd.h>
#include<sys/mman.h>
功能
解除内存映射。
参数
Start:映射的起始地址;
Length:内存映射的长度。
返回值
如果解除映射成功则返回0,否则返回-1,错误原因存于errno中。
1.2.2 msync()
函数原型
int msync ( void * addr , size_t len, int flags)
表头文件
#include<unistd.h>
#include<sys/mman.h>
功能
一般说来,进程在映射空间的对共享内容的改变并不直接写回到磁盘文件中,往往在调用munmap()后才执行该操作。可以通过调用msync()实现磁盘上文件内容与共享内存区的内容一致。
参数
Addr:需要保持的起始地址;
Len:保持一致的长度;
Flag:。
返回值
1.2.3示例
系统调用mmap()用于共享内存的两种方式:
1、 使用普通文件提供的内存映射:适用于任何进程之间;此时,需要打开或创建一个文件,然后再调用mmap();
2、 使用特殊文件提供匿名内存映射:适用于具有亲缘关系的进程之间;由于父子进程特殊的亲缘关系,在父进程中先调用mmap(),然后调用 fork()。那么在调用fork()之后,子进程继承父进程匿名映射后的地址空间,同样也继承mmap()返回的地址,这样,父子进程就可以通过映射区 域进行通信了。注意,这里不是一般的继承关系。一般来说,子进程单独维护从父进程继承下来的一些变量。而mmap()返回的地址,却由父子进程共同维护。
对于具有亲缘关系的进程实现共享内存最好的方式应该是采用匿名内存映射的方式。此时,不必指定具体的文件,只要设置相应的标志即可,参见范例2。
示例1:
/*mmap1*/ #include<stdio.h> #include<string.h> #include<stdlib.h> #include<unistd.h> #include<sys/mman.h> #include<sys/types.h> #include<sys/stat.h> #include<fcntl.h> #include<errno.h> typedef struct Shm { int id; char name[100]; int time; }Shm_t; int main() { int fd = -1; int times = 10; Shm_t *shm; fd = open("./shm",O_CREAT|O_RDWR,00777); if(fd == -1) { printf("open file failed!\n"); return -1; } //lseek(fd,sizeof(Shm_t)-1,SEEK_SET); //write(fd,"",1); shm = (Shm_t *)mmap(NULL, sizeof(Shm_t), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); if(shm == (void *)(-1)) { printf("mmap failed:%s!\n",strerror(errno)); return 0; } printf("shm is ok!\n"); close(fd); while(times --) { printf("id:[%d] time[%d] name:[%s]\n",shm->id, shm->time, shm->name); sleep(1); } munmap(shm, sizeof(Shm_t)); return 0; } /*mmap2*/ #include<stdio.h> #include<string.h> #include<stdlib.h> #include<unistd.h> #include<sys/mman.h> #include<sys/types.h> #include<sys/stat.h> #include<fcntl.h> #include<errno.h> typedef struct Shm { int id; char name[100]; int time; }Shm_t; int main() { int fd = -1; Shm_t *shm = NULL; int times = 100; fd = open("./shm",O_CREAT|O_RDWR|O_TRUNC,00777); if(fd == -1) { printf("open file failed!\n"); return -1; } /* **用于确保文件的大小空间足够内存映射的大小,否则会出现总线错误 */ lseek(fd,sizeof(Shm_t)-1,SEEK_SET); write(fd,"",1); shm = (Shm_t *)mmap(NULL, sizeof(Shm_t), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); if(shm == (void *)(-1)) { printf("mmap failed:%s!\n",strerror(errno)); return 0; } printf("shm is ok!\n"); close(fd); shm->id = 10; shm->time = 55; strcpy(shm->name, "wangyuanxiang!"); while(times --) { shm->id = times; shm->time = times + 5; sleep(1); } munmap(shm, sizeof(Shm_t)); return 0; }
示例2:
#include <sys/mman.h> #include <sys/types.h> #include <fcntl.h> #include <unistd.h> #include<stdio.h> #include<stdlib.h> #include<string.h> typedef struct { char name[4]; int age; }people; main(int argc, char** argv) { int i; people *p_map; char temp; p_map=(people*)mmap(NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1,0); if(fork() == 0) { sleep(2); for(i = 0;i<5;i++) printf("child read: the %d people's age is %d\n",i+1,(*(p_map+i)).age); (*p_map).age = 100; munmap(p_map,sizeof(people)*10);//实际上,进程终止时,会自动解除映射。 exit(1); } else { printf("wangyuanxiang!\n"); } temp = 'a'; for(i = 0;i<5;i++) { temp += 1; memcpy((*(p_map+i)).name, &temp,2); (*(p_map+i)).age=20+i; } sleep(5); printf( "parent read: the first people,s age is %d\n",(*p_map).age ); printf("umap\n"); munmap( p_map,sizeof(people)*10 ); printf( "umap ok\n" ); }
1.2.4对mmap映射的地址访问
前面对范例运行结构的讨论中已经提到,linux采用的是页式管理机制。对于用mmap()映射普通文件来说,进程会在自己的地址空间新增一块空间,空间大小由mmap()的len参数指定,注意,进程并不一定能够对全部新增空间都能进行有效访问。进程能够访问的有效地址大小取决于文件被映射部分的大小。
简单的说,能够容纳文件被映射部分大小的最少页面个数决定了进程从mmap()返回的地址开始,能够有效访问的地址空间大小。超过这个空间大小,内核会根据超过的严重程度返回发送不同的信号给进程。
能够访问空间的大小 = (文件大小决定的最小页面数)*页面大小和len的之间的取小。
示例:
#include<sys/mman.h> #include <sys/types.h> #include <fcntl.h> #include <unistd.h> #include<stdio.h> typedef struct { char name[4]; int age; }people; main(int argc, char** argv) { int fd,i; int pagesize,offset; people *p_map; pagesize = sysconf(_SC_PAGESIZE); printf("pagesize is %d\n",pagesize); fd = open(argv[1],O_CREAT|O_RDWR|O_TRUNC,00777); lseek(fd,pagesize*2-100,SEEK_SET); write(fd,"",1); offset = 0; //此处offset = 0编译成版本1;offset = pagesize编译成版本2 p_map = (people*)mmap(NULL,pagesize*3,PROT_READ|PROT_WRITE,MAP_SHARED,fd,offset); close(fd); for(i = 1; i<10; i++) { (*(p_map+pagesize/sizeof(people)*i-2)).age = 100; printf("access page %d over\n",i); (*(p_map+pagesize/sizeof(people)*i-1)).age = 100; printf("access page %d edge over, now begin to access page %d\n",i, i+1); (*(p_map+pagesize/sizeof(people)*i)).age = 100; printf("access page %d over\n",i+1); } munmap(p_map,sizeof(people)*10); }
版本1:
pagesize is 4096
access page 1 over
access page 1 edge over, now begin to access page 2
access page 2 over
access page 2 over
access page 2 edge over, now begin to access page 3
总线错误 (core dumped)
版本2:
pagesize is 4096
access page 1 over
access page 1 edge over, now begin to access page 2
总线错误 (core dumped)
1.3POSIX共享内存
POSIX共享内存分为2步:
1 指定一个名字参数调用shm_open,创建一个新的共享内存对象,或者打开一个已存在的共享内存区对象。
2 调用mmap把共享内存区映射到调用进程的地址空间。
1.3.1shm_open
函数原型
int shm_open(const char *name,int oflag,mode_t mode);
功能
打开或者创建一个共享内存区
表头文件
#include <sys/mman.h>
参数
Name:共享内存区的名字;
Oflag:标志,oflag参数必须含有O_RDONLY和O_RDWR标志,还可以指定如下标志:O_CREAT,O_EXCL或O_TRUNC.
Mode:权限模式,指定O_CREAT标志的前提下使用。
返回值
成功:返回共享内存区ID;
失败:返回-1。
1.3.2shm_unlink
函数原型
int shm_unlink(const char *name);
功能
删除一个共享内存区。
表头文件
#include <sys/mman.h>
参数
Name: 共享内存区的名字。
返回值
成功返回0,出错返回-1。
注:shm_unlink函数删除一个共享内存区对象的名字,删除一个名字仅仅防止后续的open,mq_open或sem_open调用取得成功。
1.3.3ftruncate
函数原型
int ftruncate(int fd,off_t length);
功能
调整文件或共享内存区大小。
表头文件
#include <unistd.h>
参数
Fd:描述符;
Length:文件或共享内存的大小。
返回值
成功返回0,出错返回-1。
1.3.4fstat
函数原型
int fstat(const char *file_name,struct stat *buf);
功能
获得文件或共享内存区的信息。
表头文件
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
参数
File_name:文件名;
Buf :stat结构。
返回值
成功返回0,出错返回-1。
注:对于普通文件stat结构可以获得12个以上的成员信息,然而当fd指代一个共享内存区对象时,只有四个成员含有信息。
对于共享内存:
struct stat{
mode_t st_mode;
uid_t st_uid;
gid_t st_gid;
off_t st_size;
};
1.3.5示例
#include <sys/stat.h> #include <fcntl.h> #include <sys/mman.h> #include <unistd.h> #include <semaphore.h> int main(int argc,char **argv) { int shm_id; char *ptr; sem_t *sem; if(argc!=2) { printf(“usage:shm_open <pathname>\n”); exit(1); } shm_id=shm_open(argv[1],O_RDWR|O_CREAT,0644); ftruncate(shm_id,100); sem=sem_open(argv[1],O_CREAD,0644,1); ptr=mmap(NULL,100,PROT_READ|PROT_WRITE,MAP_SHARED,shm_id,0); strcpy(ptr,”\0”); while(1) { if((strcmp(ptr,”\0”))==0) continue; else { if((strcmp(ptr,”q\n”))==0) break; sem_wait(sem); printf(“server:%s”,ptr); strcpy(ptr,”\0”); sem_pose(sem); } sem_unlink(argv[1]); shm_unlink(argv[1]); } } #include <sys/stat.h> #include <fcntl.h> #include <sys/mman.h> #include <unistd.h> #include <semaphore.h> #include <stdio.h> int main(int argc,char **argv) { int shm_id; char *ptr; sem_t *sem; if(argc!=2) { printf(“usage:shm_open <pathname>\n”); exit(1); } shm_id=shm_open(argv[1],0); ptr=mmap(NULL,100,PROT_READ|PROT_WRITE,MAP_SHARED,shm_id,0); while(1) { sem_wait(sem); fgets(ptr,10,stdin); printf(“user:%s”,ptr); if((strcmp(ptr,”q\n”))==0) exit(0); sem_pose(sem); sleep(1); } exit(0); }
gcc -lrt -o posix1 posix-mmap1.c -pthread