做网站是什么公司,网站权重排行,做网站登录页面的论文,麦包包的网站建设分析共享内存可以说是最有用的进程间通信方式#xff0c;也是最快的IPC形式。两个不同进程A、B共享内存的意思是#xff0c;同一块物理内存被映射到进程A、B各自的进程地址空间。进程A可以即时看到进程B对共享内存中数据的更新#xff0c;反之亦然。由于多个进程共享同一块内存区…共享内存可以说是最有用的进程间通信方式也是最快的IPC形式。两个不同进程A、B共享内存的意思是同一块物理内存被映射到进程A、B各自的进程地址空间。进程A可以即时看到进程B对共享内存中数据的更新反之亦然。由于多个进程共享同一块内存区域必然需要某种同步机制互斥锁和信号量都可以。采用共享内存通信的一个显而易见的好处是效率高因为进程可以直接读写内存而不需要任何数据的拷贝。对于像管道和消息队列等通信方式则需要在内核和用户空间进行四次的数据拷贝而共享内存则只拷贝两次数据[1]一次从输入文件到共享内存区另一次从共享内存区到输出文件。实际上进程之间在共享内存时并不总是读写少量数据后就解除映射有新的通信时再重新建立共享内存区域。而是保持共享区域直到通信完毕为止这样数据内容一直保存在共享内存中并没有写回文件。共享内存中的内容往往是在解除映射时才写回文件的。因此采用共享内存的通信方式效率是非常高的。Linux的2.2.x内核支持多种共享内存方式如mmap()系统调用Posix共享内存以及系统V共享内存。linux发行版本如Redhat 8.0支持mmap()系统调用及系统V共享内存但还没实现Posix共享内存本文将主要介绍mmap()系统调用及系统V共享内存API的原理及应用。一、内核怎样保证各个进程寻址到同一个共享内存区域的内存页面1、page cache及swap cache中页面的区分一个被访问文件的物理页面都驻留在page cache或swap cache中一个页面的所有信息由struct page来描述。struct page中有一个域为指针mapping它指向一个struct address_space类型结构。page cache或swap cache中的所有页面就是根据address_space结构以及一个偏移量来区分的。2、文件与address_space结构的对应一个具体的文件在打开后内核会在内存中为之建立一个struct inode结构其中的i_mapping域指向一个address_space结构。这样一个文件就对应一个address_space结构一个address_space与一个偏移量能够确定一个page cache或swap cache中的一个页面。因此当要寻址某个数据时很容易根据给定的文件及数据在文件内的偏移量而找到相应的页面。3、进程调用mmap()时只是在进程空间内新增了一块相应大小的缓冲区并设置了相应的访问标识但并没有建立进程空间到物理页面的映射。因此第一次访问该空间时会引发一个缺页异常。4、对于共享内存映射情况缺页异常处理程序首先在swap cache中寻找目标页(符合address_space以及偏移量的物理页)如果找到则直接返回地址如果没有找到则判断该页是否在交换区(swap area)如果在则执行一个换入操作如果上述两种情况都不满足处理程序将分配新的物理页面并把它插入到page cache中。进程最终将更新进程页表。注对于映射普通文件情况(非共享映射)缺页异常处理程序首先会在page cache中根据address_space以及数据偏移量寻找相应的页面。如果没有找到则说明文件数据还没有读入内存处理程序会从磁盘读入相应的页面并返回相应地址同时进程页表也会更新。5、所有进程在映射同一个共享内存区域时情况都一样在建立线性地址与物理地址之间的映射之后不论进程各自的返回地址如何实际访问的必然是同一个共享内存区域对应的物理页面。注一个共享内存区域可以看作是特殊文件系统shm中的一个文件shm的安装点在交换区上。上面涉及到了一些数据结构围绕数据结构理解问题会容易一些。二、mmap()及其相关系统调用mmap()系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后进程可以向访问普通内存一样对文件进行访问不必再调用read()write()等操作。注实际上mmap()系统调用并不是完全为了用于共享内存而设计的。它本身提供了不同于一般对普通文件的访问方式进程可以像读写内存一样对普通文件的操作。而Posix或系统V的共享内存IPC则纯粹用于共享目的当然mmap()实现共享内存也是其主要应用之一。1、mmap()系统调用形式如下void* mmap ( void * addr , size_t len , int prot , int flags , int fd , off_t offset )参数fd为即将映射到进程空间的文件描述字一般由open()返回同时fd可以指定为-1此时须指定flags参数中的MAP_ANON表明进行的是匿名映射(不涉及具体的文件名避免了文件的创建及打开很显然只能用于具有亲缘关系的进程间通信)。len是映射到调用进程地址空间的字节数它从被映射文件开头offset个字节开始算起。prot参数指定共享内存的访问权限。可取如下几个值的或PROT_READ(可读), PROT_WRITE(可写), PROT_EXEC(可执行), PROT_NONE(不可访问)。flags由以下几个常值指定MAP_SHARED , MAP_PRIVATE , MAP_FIXED其中MAP_SHARED , MAP_PRIVATE必选其一而MAP_FIXED则不推荐使用。offset参数一般设为0表示从文件头开始映射。参数addr指定文件应被映射到进程空间的起始地址一般被指定一个空指针此时选择起始地址的任务留给内核来完成。函数的返回值为最后文件映射到进程空间的地址进程可直接操作起始地址为该值的有效地址。这里不再详细介绍mmap()的参数读者可参考mmap()手册页获得进一步的信息。2、系统调用mmap()用于共享内存的两种方式(1)使用普通文件提供的内存映射适用于任何进程之间此时需要打开或创建一个文件然后再调用mmap()典型调用代码如下fdopen(name, flag, mode);if(fd0)...ptrmmap(NULL, len , PROT_READ|PROT_WRITE, MAP_SHARED , fd , 0);通过mmap()实现共享内存的通信方式有许多特点和要注意的地方我们将在范例中进行具体说明。(2)使用特殊文件提供匿名内存映射适用于具有亲缘关系的进程之间由于父子进程特殊的亲缘关系在父进程中先调用mmap()然后调用fork()。那么在调用fork()之后子进程继承父进程匿名映射后的地址空间同样也继承mmap()返回的地址这样父子进程就可以通过映射区域进行通信了。注意这里不是一般的继承关系。一般来说子进程单独维护从父进程继承下来的一些变量。而mmap()返回的地址却由父子进程共同维护。对于具有亲缘关系的进程实现共享内存最好的方式应该是采用匿名内存映射的方式。此时不必指定具体的文件只要设置相应的标志即可参见范例2。3、系统调用munmap()int munmap( void * addr, size_t len )该调用在进程地址空间中解除一个映射关系addr是调用mmap()时返回的地址len是映射区的大小。当映射关系解除后对原来映射地址的访问将导致段错误发生。4、系统调用msync()int msync ( void * addr , size_t len, int flags)一般说来进程在映射空间的对共享内容的改变并不直接写回到磁盘文件中往往在调用munmap()后才执行该操作。可以通过调用msync()实现磁盘上文件内容与共享内存区的内容一致。三、mmap()范例下面将给出使用mmap()的两个范例范例1给出两个进程通过映射普通文件实现共享内存通信范例2给出父子进程通过匿名映射实现共享内存。系统调用mmap()有许多有趣的地方下面是通过mmap()映射普通文件实现进程间的通信的范例我们通过该范例来说明mmap()实现共享内存的特点及注意事项。范例1两个进程通过映射普通文件实现共享内存通信范例1包含两个子程序map_normalfile1.c及map_normalfile2.c。编译两个程序可执行文件分别为map_normalfile1及map_normalfile2。两个程序通过命令行参数指定同一个文件来实现共享内存方式的进程间通信。map_normalfile2试图打开命令行参数指定的一个普通文件把该文件映射到进程的地址空间并对映射后的地址空间进行写操作。map_normalfile1把命令行参数指定的文件映射到进程地址空间然后对映射后的地址空间执行读操作。这样两个进程通过命令行参数指定同一个文件来实现共享内存方式的进程间通信。下面是两个程序代码/*-------------map_normalfile1.c-----------*/#include #include #include #include typedef struct{char name[4];intage;}people;main(int argc, char** argv) // map a normal file as shared mem:{int fd,i;people *p_map;char temp;fdopen(argv[1],O_CREAT|O_RDWR|O_TRUNC,00777);lseek(fd,sizeof(people)*5-1,SEEK_SET);write(fd,,1);p_map (people*) mmap( NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0 );close( fd );temp a;for(i0; i10; i){temp 1;memcpy( ( *(p_mapi) ).name, temp,2 );( *(p_mapi) ).age 20i;}printf( initialize over \n )sleep(10);munmap( p_map, sizeof(people)*10 );printf( umap ok \n );}/*-------------map_normalfile2.c-----------*/#include #include #include #include typedef struct{char name[4];intage;}people;main(int argc, char** argv)// map a normal file as shared mem:{int fd,i;people *p_map;fdopen( argv[1],O_CREAT|O_RDWR,00777 );p_map (people*)mmap(NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);for(i 0;i10;i){printf( name: %s age %d;\n,(*(p_mapi)).name, (*(p_mapi)).age );}munmap( p_map,sizeof(people)*10 );}map_normalfile1.c首先定义了一个people数据结构(在这里采用数据结构的方式是因为共享内存区的数据往往是有固定格式的这由通信的各个进程决定采用结构的方式有普遍代表性)。map_normfile1首先打开或创建一个文件并把文件的长度设置为5个people结构大小。然后从mmap()的返回地址开始设置了10个people结构。然后进程睡眠10秒钟等待其他进程映射同一个文件最后解除映射。map_normfile2.c只是简单的映射一个文件并以people数据结构的格式从mmap()返回的地址处读取10个people结构并输出读取的值然后解除映射。分别把两个程序编译成可执行文件map_normalfile1和map_normalfile2后在一个终端上先运行./map_normalfile2 /tmp/test_shm程序输出结果如下initialize overumap ok在map_normalfile1输出initialize over之后输出umap ok之前在另一个终端上运行map_normalfile2 /tmp/test_shm将会产生如下输出(为了节省空间输出结果为稍作整理后的结果)name: bage 20;name: cage 21;name: dage 22;name: eage 23;name: fage 24;name: gage 25;name: hage 26;name: Iage 27;name: jage 28;name: kage 29;在map_normalfile1输出umap ok后运行map_normalfile2则输出如下结果name: bage 20;name: cage 21;name: dage 22;name: eage 23;name: fage 24;name:age 0;name:age 0;name:age 0;name:age 0;name:age 0;从程序的运行结果中可以得出的结论1、最终被映射文件的内容的长度不会超过文件本身的初始大小即映射不能改变文件的大小2、可以用于进程通信的有效地址空间大小大体上受限于被映射文件的大小但不完全受限于文件大小。打开文件被截短为5个people结构大小而在map_normalfile1中初始化了10个people数据结构在恰当时候(map_normalfile1输出initialize over之后输出umap ok之前)调用map_normalfile2会发现map_normalfile2将输出全部10个people结构的值后面将给出详细讨论。注在linux中内存的保护是以页为基本单位的即使被映射文件只有一个字节大小内核也会为映射分配一个页面大小的内存。当被映射文件小于一个页面大小时进程可以对从mmap()返回地址开始的一个页面大小进行访问而不会出错但是如果对一个页面以外的地址空间进行访问则导致错误发生后面将进一步描述。因此可用于进程间通信的有效地址空间大小不会超过文件大小及一个页面大小的和。3、文件一旦被映射后调用mmap()的进程对返回地址的访问是对某一内存区域的访问暂时脱离了磁盘上文件的影响。所有对mmap()返回地址空间的操作只在内存中有意义只有在调用了munmap()后或者msync()时才把内存中的相应内容写回磁盘文件所写内容仍然不能超过文件的大小。范例2父子进程通过匿名映射实现共享内存#include #include #include #include typedef struct{char name[4];intage;}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;i5;i)printf(child read: the %d peoples age is %d\n,i1,(*(p_mapi)).age);(*p_map).age 100;munmap(p_map,sizeof(people)*10); //实际上进程终止时会自动解除映射。exit();}temp a;for(i 0;i5;i){temp 1;memcpy((*(p_mapi)).name, temp,2);(*(p_mapi)).age20i;}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 );}考察程序的输出结果体会父子进程匿名共享内存child read: the 1 peoples age is 20child read: the 2 peoples age is 21child read: the 3 peoples age is 22child read: the 4 peoples age is 23child read: the 5 peoples age is 24parent read: the first people,s age is 100umapumap ok四、对mmap()返回地址的访问前面对范例运行结构的讨论中已经提到linux采用的是页式管理机制。对于用mmap()映射普通文件来说进程会在自己的地址空间新增一块空间空间大小由mmap()的len参数指定注意进程并不一定能够对全部新增空间都能进行有效访问。进程能够访问的有效地址大小取决于文件被映射部分的大小。简单的说能够容纳文件被映射部分大小的最少页面个数决定了进程从mmap()返回的地址开始能够有效访问的地址空间大小。超过这个空间大小内核会根据超过的严重程度返回发送不同的信号给进程。可用如下图示说明注意文件被映射部分而不是整个文件决定了进程能够访问的空间大小另外如果指定文件的偏移部分一定要注意为页面大小的整数倍。下面是对进程映射地址空间的访问范例#include #include #include #include typedef struct{char name[4];intage;}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编译成版本1offset pagesize编译成版本2p_map (people*)mmap(NULL,pagesize*3,PROT_READ|PROT_WRITE,MAP_SHARED,fd,offset);close(fd);for(i 1; i10; i){(*(p_mappagesize/sizeof(people)*i-2)).age 100;printf(access page %d over\n,i);(*(p_mappagesize/sizeof(people)*i-1)).age 100;printf(access page %d edge over, now begin to access page %d\n,i, i1);(*(p_mappagesize/sizeof(people)*i)).age 100;printf(access page %d over\n,i1);}munmap(p_map,sizeof(people)*10);}如程序中所注释的那样把程序编译成两个版本两个版本主要体现在文件被映射部分的大小不同。文件的大小介于一个页面与两个页面之间(大小为pagesize*2-99)版本1的被映射部分是整个文件版本2的文件被映射部分是文件大小减去一个页面后的剩余部分不到一个页面大小(大小为pagesize-99)。程序中试图访问每一个页面边界两个版本都试图在进程空间中映射pagesize*3的字节数。版本1的输出结果如下pagesize is 4096access page 1 overaccess page 1 edge over, now begin to access page 2access page 2 overaccess page 2 overaccess page 2 edge over, now begin to access page 3Bus error//被映射文件在进程空间中覆盖了两个页面此时进程试图访问第三个页面版本2的输出结果如下pagesize is 4096access page 1 overaccess page 1 edge over, now begin to access page 2Bus error//被映射文件在进程空间中覆盖了一个页面此时进程试图访问第二个页面结论采用系统调用mmap()实现进程间通信是很方便的在应用层上接口非常简洁。内部实现机制区涉及到了linux存储管理以及文件系统等方面的内容可以参考一下相关重要数据结构来加深理解。在本专题的后面部分将介绍系统v共享内存的实现。共享内存(下)在共享内存(上)中主要围绕着系统调用mmap()进行讨论的本部分将讨论系统V共享内存并通过实验结果对比来阐述两者的异同。系统V共享内存指的是把所有共享数据放在共享内存区域(IPC shared memory region)任何想要访问该数据的进程都必须在本进程的地址空间新增一块内存区域用来映射存放共享数据的物理内存页面。系统调用mmap()通过映射一个普通文件实现共享内存。系统V则是通过映射特殊文件系统shm中的文件实现进程间的共享内存通信。也就是说每个共享内存区域对应特殊文件系统shm中的一个文件(这是通过shmid_kernel结构联系起来的)后面还将阐述。1、系统V共享内存原理进程间需要共享的数据被放在一个叫做IPC共享内存区域的地方所有需要访问该共享区域的进程都要把该共享区域映射到本进程的地址空间中去。系统V共享内存通过shmget获得或创建一个IPC共享内存区域并返回相应的标识符。内核在保证shmget获得或创建一个共享内存区初始化该共享内存区相应的shmid_kernel结构注同时还将在特殊文件系统shm中创建并打开一个同名文件并在内存中建立起该文件的相应dentry及inode结构新打开的文件不属于任何一个进程(任何进程都可以访问该共享内存区)。所有这一切都是系统调用shmget完成的。注每一个共享内存区都有一个控制结构struct shmid_kernelshmid_kernel是共享内存区域中非常重要的一个数据结构它是存储管理和文件系统结合起来的桥梁定义如下struct shmid_kernel /* private to the kernel */{struct kern_ipc_permshm_perm;struct file *shm_file;intid;unsigned longshm_nattch;unsigned longshm_segsz;time_tshm_atim;time_tshm_dtim;time_tshm_ctim;pid_tshm_cprid;pid_tshm_lprid;};该结构中最重要的一个域应该是shm_file它存储了将被映射文件的地址。每个共享内存区对象都对应特殊文件系统shm中的一个文件一般情况下特殊文件系统shm中的文件是不能用read()、write()等方法访问的当采取共享内存的方式把其中的文件映射到进程地址空间后可直接采用访问内存的方式对其访问。这里我们采用[1]中的图表给出与系统V共享内存相关数据结构正如消息队列和信号灯一样内核通过数据结构struct ipc_ids shm_ids维护系统中的所有共享内存区域。上图中的shm_ids.entries变量指向一个ipc_id结构数组而每个ipc_id结构数组中有个指向kern_ipc_perm结构的指针。到这里读者应该很熟悉了对于系统V共享内存区来说kern_ipc_perm的宿主是shmid_kernel结构shmid_kernel是用来描述一个共享内存区域的这样内核就能够控制系统中所有的共享区域。同时在shmid_kernel结构的file类型指针shm_file指向文件系统shm中相应的文件这样共享内存区域就与shm文件系统中的文件对应起来。在创建了一个共享内存区域后还要将它映射到进程地址空间系统调用shmat()完成此项功能。由于在调用shmget()时已经创建了文件系统shm中的一个同名文件与共享内存区域相对应因此调用shmat()的过程相当于映射文件系统shm中的同名文件过程原理与mmap()大同小异。2、系统V共享内存API对于系统V共享内存主要有以下几个APIshmget()、shmat()、shmdt()及shmctl()。#include #include shmget()用来获得共享内存区域的ID如果不存在指定的共享区域就创建相应的区域。shmat()把共享内存区域映射到调用进程的地址空间中去这样进程就可以方便地对共享区域进行访问操作。shmdt()调用用来解除进程对共享内存区域的映射。shmctl实现对共享内存区域的控制操作。这里我们不对这些系统调用作具体的介绍读者可参考相应的手册页面后面的范例中将给出它们的调用方法。注shmget的内部实现包含了许多重要的系统V共享内存机制shmat在把共享内存区域映射到进程空间时并不真正改变进程的页表。当进程第一次访问内存映射区域访问时会因为没有物理页表的分配而导致一个缺页异常然后内核再根据相应的存储管理机制为共享内存映射区域分配相应的页表。3、系统V共享内存限制在/proc/sys/kernel/目录下记录着系统V共享内存的一下限制如一个共享内存区的最大字节数shmmax系统范围内最大共享内存区标识符数shmmni等可以手工对其调整但不推荐这样做。在[2]中给出了这些限制的测试方法不再赘述。4、系统V共享内存范例本部分将给出系统V共享内存API的使用方法并对比分析系统V共享内存机制与mmap()映射普通文件实现共享内存之间的差异首先给出两个进程通过系统V共享内存通信的范例/***** testwrite.c *******/#include #include #include #include typedef struct{char name[4];int age;} people;main(int argc, char** argv){int shm_id,i;key_t key;char temp;people *p_map;char* name /dev/shm/myshm2;key ftok(name,0);if(key-1)perror(ftok error);shm_idshmget(key,4096,IPC_CREAT);if(shm_id-1){perror(shmget error);return;}p_map(people*)shmat(shm_id,NULL,0);tempa;for(i 0;i10;i){temp1;memcpy((*(p_mapi)).name,temp,1);(*(p_mapi)).age20i;}if(shmdt(p_map)-1)perror( detach error );}/********** testread.c ************/#include #include #include #include typedef struct{char name[4];int age;} people;main(int argc, char** argv){int shm_id,i;key_t key;people *p_map;char* name /dev/shm/myshm2;key ftok(name,0);if(key -1)perror(ftok error);shm_id shmget(key,4096,IPC_CREAT);if(shm_id -1){perror(shmget error);return;}p_map (people*)shmat(shm_id,NULL,0);for(i 0;i10;i){printf( name:%s\n,(*(p_mapi)).name );printf( age %d\n,(*(p_mapi)).age );}if(shmdt(p_map) -1)perror( detach error );}testwrite.c创建一个系统V共享内存区并在其中写入格式化数据testread.c访问同一个系统V共享内存区读出其中的格式化数据。分别把两个程序编译为testwrite及testread先后执行./testwrite及./testread则./testread输出结果如下name: bage 20;name: cage 21;name: dage 22;name: eage 23;name: fage 24;name: gage 25;name: hage 26;name: Iage 27;name: jage 28;name: kage 29;通过对试验结果分析对比系统V与mmap()映射普通文件实现共享内存通信可以得出如下结论1、系统V共享内存中的数据从来不写入到实际磁盘文件中去而通过mmap()映射普通文件实现的共享内存通信可以指定何时将数据写入磁盘文件中。注前面讲到系统V共享内存机制实际是通过映射特殊文件系统shm中的文件实现的文件系统shm的安装点在交换分区上系统重新引导后所有的内容都丢失。2、系统V共享内存是随内核持续的即使所有访问共享内存的进程都已经正常终止共享内存区仍然存在(除非显式删除共享内存)在内核重新引导之前对该共享内存区域的任何改写操作都将一直保留。3、通过调用mmap()映射普通文件进行进程间通信时一定要注意考虑进程何时终止对通信的影响。而通过系统V共享内存实现通信的进程则不然。注这里没有给出shmctl的使用范例原理与消息队列大同小异。Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId1006744posted on 2007-08-11 01:00 旅途 阅读(1544) 评论(0) 编辑 收藏 引用 所属分类: Linux开发