如何设置LINUX的linux 查看共享内存存

Hi,欢迎来到中国嵌入式培训第一品牌 - 华清远见嵌入式学院,专注嵌入式工程师培养13年!
全国免费报名电话:400-706-1880
当前位置: >
> linux实现共享内存同步的四种方法一
linux实现共享内存同步的四种方法一
时间:作者:华清远见
本文主要对实现共享内存同步的四种方法进行了介绍。
共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝。它是IPC对象的一种。
为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程将其映射到自己的私有地址空间。进程就可以直接读写这一内存区而不需要进行数据的拷贝,从而大大提高的效率。
同步(synchronization)指的是多个任务(线程)按照约定的顺序相互配合完成一件事情。由于多个进程共享一段内存,因此也需要依靠某种同步机制,如互斥锁和信号量等 。
信号灯(semaphore),也叫信号量。它是不同进程间或一个给定进程内部不同线程间同步的机制。信号灯包括posix有名信号灯、 posix基于内存的信号灯(无名信号灯)和System V信号灯(IPC对象)
方法一、利用POSIX有名信号灯实现共享内存的同步
有名信号量既可用于线程间的同步,又可用于进程间的同步。
两个进程,对同一个共享内存读写,可利用有名信号量来进行同步。一个进程写,另一个进程读,利用两个有名信号量semr, semw。semr信号量控制能否读,初始化为0。 semw信号量控制能否写,初始为1。
读共享内存的程序示例代码如下
semr = sem_open("mysem_r", O_CREAT | O_RDWR , 0666, 0);
&&&&&&&&if (semr == SEM_FAILED)
&&&&&&&&&&&&&&&&printf("errno=%d\n", errno);
&&&&&&&&&&&&&&&&return -1;
&&&&&&&&semw = sem_open("mysem_w", O_CREAT | O_RDWR, 0666, 1);
&&&&&&&&if (semw == SEM_FAILED)
&&&&&&&&&&&&&&&&printf("errno=%d\n", errno);
&&&&&&&&&&&&&&&&return -1;
&&&&&&&&if ((shmid = shmget(key, MAXSIZE, 0666 | IPC_CREAT)) == -1)
&&&&&&&&&&&&&&&&perror("semget");
&&&&&&&&&&&&&&&&exit(-1);
&&&&&&&&if ((shmadd = (char *)shmat(shmid, NULL, 0)) == (char *)(-1))
&&&&&&&&&&&&&&&&perror("shmat");
&&&&&&&&&&&&&&&&exit(-1);
&&&&&&&&while (1)
&&&&&&&&&&&&&&&&em_wait(semr);
&&&&&&&&&&&&&&&&printf("%s\n", shmadd);
&&&&&&&&&&&&&&&&sem_post(semw);
写共享内存的程序示例代码如下
。。。。。。
&&&&&&&&//同读的程序
&&&&&&&&while (1)
&&&&&&&&&&&&&&&&sem_wait(semw);
&&&&&&&&&&&&&&&&printf(">");
&&&&&&&&&&&&&&&&fgets(shmadd, MAXSIZE, stdin);
&&&&&&&&&&&&&&&&sem_post(semr);
方法二、利用POSIX无名信号灯实现共享内存的同步
POSIX无名信号量是基于内存的信号量,可以用于线程间同步也可以用于进程间同步。若实现进程间同步,需要在共享内存中来创建无名信号量。
因此,共享内存需要定义以下的结构体。
typedef struct
&&&&&&&&&&&&&&&&sem_
&&&&&&&&&&&&&&&&sem_
&&&&&&&&&&&&&&&&char buf[MAXSIZE];
&&&&&&&&}SHM;
读、写程序流程如下图所示。
方法三、利用System V的信号灯实现共享内存的同步
System V的信号灯是一个或者多个信号灯的一个集合。其中的每一个都是单独的计数信号灯。而Posix信号灯指的是单个计数信号灯
System V 信号灯由内核维护,主要函数semget,semop,semctl 。
一个进程写,另一个进程读,信号灯集中有两个信号灯,下标0代表能否读,初始化为0。 下标1代表能否写,初始为1。
程序流程如下:
写的流程和前边的类似。
方法四、利用信号实现共享内存的同步
信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式。利用信号也可以实现共享内存的同步。
reader和writer通过信号通信必须获取对方的进程号,可利用共享内存保存双方的进程号。
reader和writer运行的顺序不确定,可约定先运行的进程创建共享内存并初始化。
利用pause, kill, signal等函数可以实现该程序(流程和前边类似)。
评论列表(网友评论仅供网友表达个人看法,并不表明本站同意其观点或证实其描述)
学院最新动态linux c学习笔记----共享内存(shmget,shmat,shmdt,shmctl) - 知知为知知 - ITeye技术网站
博客分类:
shmgetint shmget(key_t key, size_t size, int flag);key: 标识符的规则size:共享存储段的字节数flag:读写的权限返回值:成功返回共享存储的id,失败返回-1key_t key-----------------------------------------------
key标识共享内存的键值: 0/IPC_PRIVATE。 当key的取值为IPC_PRIVATE,则函数shmget()将创建一块新的共享内存;如果key的取值为0,而参数shmflg中设置了IPC_PRIVATE这个标志,则同样将创建一块新的共享内存。
在IPC(InterProcess Communication)的通信模式下,不管是使用消息队列还是共享内存,甚至是信号量,每个IPC的对象(object)都有唯一的名字,称为“键”(key)。通过“键”,进程能够识别所用的对象。“键”与IPC对象的关系就如同文件名称之于文件,通过文件名,进程能够读写文件内的数据,甚至多个进程能够共用一个文件。而在IPC的通讯模式下,通过“键”的使用也使得一个IPC对象能为多个进程所共用。
Linux系统中的所有表示System V中IPC对象的数据结构都包括一个ipc_perm结构,其中包含有IPC对象的键值,该键用于查找System V中IPC对象的引用标识符。如果不使用“键”,进程将无法存取IPC对象,因为IPC对象并不存在于进程本身使用的内存中。
通常,都希望自己的程序能和其他的程序预先约定一个唯一的键值,但实际上并不是总可能的成行的,因为自己的程序无法为一块共享内存选择一个键值。因此,在此把key设为IPC_PRIVATE,这样,操作系统将忽略键,建立一个新的共享内存,指定一个键值,然后返回这块共享内存IPC标识符ID。而将这个新的共享内存的标识符ID告诉其他进程可以在建立共享内存后通过派生子进程,或写入文件或管道来实现。int size(单位字节Byte)-----------------------------------------------
size是要建立共享内存的长度。所有的内存分配操作都是以页为单位的。所以如果一段进程只申请一块只有一个字节的内存,内存也会分配整整一页(在i386机器中一页的缺省大小PACE_SIZE=4096字节)这样,新创建的共享内存的大小实际上是从size这个参数调整而来的页面大小。即如果size为1至4096,则实际申请到的共享内存大小为4K(一页);,则实际申请到的共享内存大小为8K(两页),依此类推。int shmflg-----------------------------------------------
shmflg主要和一些标志有关。其中有效的包括IPC_CREAT和IPC_EXCL,它们的功能与open()的O_CREAT和O_EXCL相当。
如果共享内存不存在,则创建一个共享内存,否则打开操作。
只有在共享内存不存在的时候,新的共享内存才建立,否则就产生错误。
如果单独使用IPC_CREAT,shmget()函数要么返回一个已经存在的共享内存的操作符,要么返回一个新建的共享内存的标识符。如果将IPC_CREAT和IPC_EXCL标志一起使用,shmget()将返回一个新建的共享内存的标识符;如果该共享内存已存在,或者返回-1。IPC_EXEL标志本身并没有太大的意义,但是和IPC_CREAT标志一起使用可以用来保证所得的对象是新建的,而不是打开已有的对象。对于用户的读取和写入许可指定SHM_R和SHM_W,(SHM_R&3)和(SHM_W&3)是一组读取和写入许可,而(SHM_R&6)和(SHM_W&6)是全局读取和写入许可。需要注意的是,使用参数要加上 | 0666 作为校验,在有些Linux系统中,如果不加此校验,则不能顺利获取共享空间的值(如Ubuntu)。此外,有两个常用参数,一般要同时出现,他们是:S_IRUSH | S_IWUSR 。由于这两个参数非常常用,程序员一般做这样的操作  #define PERM S_IRUSR | S_IWUSR | IPC_CREAT  这样一来,第三个参数就可以直接用PERM来表示了!返回值-----------------------------------------------成功返回共享内存的标识符;不成功返回-1,errno储存错误原因。
参数size小于SHMMIN或大于SHMMAX。
预建立key所致的共享内存,但已经存在。
参数key所致的共享内存已经删除。
超过了系统允许建立的共享内存的最大值(SHMALL )。
参数key所指的共享内存不存在,参数shmflg也未设IPC_CREAT位。
没有权限。
核心内存不足。struct shmid_ds-----------------------------------------------
shmid_ds数据结构表示每个新建的共享内存。当shmget()创建了一块新的共享内存后,返回一个可以用于引用该共享内存的shmid_ds数据结构的标识符。include/linux/shm.h
struct shmid_ds {
struct ipc_perm
/* operation perms */
/* size of segment (bytes) */
__kernel_time_t
/* last attach time */
__kernel_time_t
/* last detach time */
__kernel_time_t
/* last change time */
__kernel_ipc_pid_t shm_
/* pid of creator */
__kernel_ipc_pid_t shm_
/* pid of last operator */
unsigned short
/* no. of current attaches */
unsigned short
/* compatibility */
*shm_unused2; /* ditto - used by DIPC */
*shm_unused3; /* unused */
};struct ipc_perm-----------------------------------------------
对于每个IPC对象,系统共用一个struct ipc_perm的数据结构来存放权限信息,以确定一个ipc操作是否可以访问该IPC对象。
struct ipc_perm {
__kernel_key_
__kernel_uid_
__kernel_gid_
__kernel_uid_
__kernel_gid_
__kernel_mode_
};//----------------------------------------shmatvoid *shmat(int shmid, const void *addr, int flag);shmid:共享存储的idaddr:一般为0,表示连接到由内核选择的第一个可用地址上,否则,如果flag没有指定SHM_RND,则连接到addr所指定的地址上,如果flag为SHM_RND,则地址取整flag:如前所述,一般为0返回值:如果成功,返回共享存储段地址,出错返回-1共享存储器的执行方式是将一个储存器区段标记为共用,这时各进程可以把这个区段映射到该进程本身的虚拟地址里。建立共享存储器可通过shmget系统调用,shmget执行后,核心程序就保留一块指定大小的空间,同时关于此共享存储器的一切数据,如区段的长度,区段的存取权,区段建立者的进程识别码等存入一个叫shmid_ds的结构。现在共享存储器虽然已经建立了,可是仍无法连上它,这时就须通过shmat系统调用得到一个指向共享存储器基址的指针,通过此指针,就可以如同于操作一般存储器似的取用共享存储器。shmdt进行相反的工作,用来脱离已连上的共享存储器。shmdtint shmdt(void *addr);addr:共享存储段的地址,以前调用shmat时的返回值shmdt将使相关shmid_ds结构中的shm_nattch计数器值减1
当一个进程不再需要共享内存段时,它将调用shmdt()系统调用取消这个段,但是,这并不是从内核真正地删除这个段,而是把相关shmid_ds结构的 shm_nattch域的值减1,当这个值为0时,内核才从物理上删除这个共享段
shmctlint shmctl(int shmid,int cmd,struct shmid_ds *buf)shmid:共享存储段的idcmd:一些命令
IPC_STAT 得到共享内存的状态
IPC_SET 改变共享内存的状态
IPC_RMID 删除共享内存
IPC_RMID 命令实际上不从内核删除一个段,而是仅仅把这个段标记为删除,实际的删除发生在最后一个进程离开这个共享段时。 请注意,共享内存不会随着程序结束而自动消除,要么调用shmctl删除,要么自己用手敲命令去删除,否则永远留在系统中。
#include &stdio.h&
#include &string.h&
#include &stdlib.h&
#include &errno.h&
#include &unistd.h&
#include &sys/stat.h&
#include &sys/types.h&
#include &sys/ipc.h&
#include &sys/shm.h&
#define PERM S_IRUSR|S_IWUSR
int main(int argc,char **argv){
char *p_addr,*c_
if(argc!=2){
fprintf(stderr,"Usage:%s\n\a",argv[0]);
if((shmid=shmget(IPC_PRIVATE,1024,PERM))==-1){
fprintf(stderr,"Create Share Memory Error:%s\n\a",strerror(errno));
if(fork()){
p_addr=shmat(shmid,0,0);
memset(p_addr,'\0',1024);
strncpy(p_addr,argv[1],1024);
c_addr=shmat(shmid,0,0);
printf("Client get %s",c_addr);
这个程序是父进程将参数写入到共享内存,然后子进程把内容读出来.最后我们要使用ip
crm 释放资源的.先用ipcs 找出ID 然后用ipcrm shm ID 删除.
浏览 18167
home198979
浏览: 716165 次
来自: 深圳
浏览量:47724
浏览量:27796
浏览量:40238
[size=x-small][/size]
q 写道还是佩服写c的用其它语言一样可以实现 ...
还是佩服写c的
不错啊,强
浏览器安全模式下应当禁止标签中的有注释的,大小写混用标识符 非 ...博客访问: 739481
博文数量: 155
博客积分: 65
博客等级: 民兵
技术积分: 3111
注册时间:
互联网行业新兵
APP发帖 享双倍积分
IT168企业级官微
微信号:IT168qiye
系统架构师大会
微信号:SACC2013
分类: LINUX
(一)IPC共享内存和文件映射的区别 & & & 1
(二)共享内存实现流程总结 & & &1
(三)存储映射I/O(包含实现原理说明) & 2
& & & & 文件映射API补充 4
(四)IPC共享存储(包含实现原理说明) & 6
(五)共享内存实现基本原理 & & &10
(六)IPC共享内存实现机制 & & & 11
(七)文件映射的实现机制 & & & &13
(一)IPC共享内存和文件映射的区别
& & & & 1. 文件映射的页框是磁盘文件高速缓存中的页框,内核线程pdflush会将页框中的内容回写进磁盘, 如果是私有映射类型,将会进行写时复制。而IPC共享内存映射的是一种特殊文件系统中的文件高速缓存,它没有相应的磁盘映像。
& & & & 2. IPC共享内存只存在于内存中,系统重新启动,数据将会丢失。而文件共享映射会将数据写回磁盘。
& & & & 3. IPC共享内存的大小是在创建的时候指定,而且大小不能改变,而文件在创建时大小为0,此时还不能建立映射,文件的大小会间接的决定映射区的大小。例如文件的大小是123,而要求映射的区域大小是4096*2,但实际只会分配4096的映射空间,此时引用4096以后的线性空间将引起缺页异常。
& & & & 4. 当第一次读取共享内存时IPC共享内存对象将分配一个新的页框,而文件映射分配新页框的同时会将磁盘中的数据写入新页框。
& & & & 5. IPC共享内存不需要写回磁盘操作,完全是为共享内存而设计,所以使用效率会更高。
& & & & 6. IPC共享内存对象必须调用shmctl()显示的撤销,否则会一直保留着,使用key或者id号定位一个共享内存对象,key和id号的对应关系并不是固定的。例如,第一次使用key建立一个共享内存对象为shm1对应的id为id1,之后系统重新启动,然后再使用key建立一个共享内存对象shm2,对应的id是id2,此时id2和id1是不同的。而文件映射使用相同的路径将会定位相同的磁盘文件。
& & & & 总结:IPC共享内存和文件映射的实现机制是一样的,文件映射的目的是加快对文件的读写速度,而IPC共享内存就是为了共享内存而设计的,所以效率会高一些。
(二)共享内存实现流程总结
& & & & 1. 建立一个线性区对象struct vm_area_struct 并加入进程的内存描述符current->mm中。
函数mmap()和shmat()就是用于建立并注册线性区对象,这个对象中的struct file *vm_file指向映射文件的文件对象,vm_page_prot是线性区中页框的访问许可权。但此时并未修改进程的页表,而是注册相应的缺页异常回调函数,注册在对象的vm_ops。
& & & & 2. 当进程第一次访问共享内存区时,由于相应的页表还未填写,将产生缺页异常,并根据线性地址找到对应的线性区对象,然后调用前边注册过的缺页异常回调函数,并根据vm_file文件对象和vm_page_prot的信息来填写相应的页表项,最后重新执行产生缺页异常的代码。
& & & & 说明:文件映射和IPC共享内存映射的物理页框都是磁盘文件的页高速缓存中的,IPC共享内存使用一种特殊文件系统,这个文件系统并没有对应的磁盘映像,只是复用了文件系统的框架。更详细的内容参见后边的五,六,七节。
& & & & 下面3,4节是《UNIX环境高级编程》对文件映射和IPC共享内存的讲解,已经说明的很详细了,我在它的基础上附加了一些内核实现原理的说明,实现原理说明部分放在括号内。
(三)存储映射I/O(包含实现原理说明)
& & & & 存储映射I/O使一个磁盘文件与存储空间中的一个缓存相映射。于是当从缓存中取数据,就相当于读文件中的相应字节。与其类似,将数据存入缓存,则相应字节就自动地写入文件。这样,就可以在不使用read和write的情况下执行I/O。
& & & & 为了使用这种功能,应首先告诉内核将一个给定的文件映射到一个存储区域中。这是由mmap函数实现的。
void * mmap(void addr, size_t len, int prot, int flag, int fd, off_t off) ;
返回:若成功则为映射区的起始地址,若出错则为- 1
& & & & addr参数用于指定映射存储区的起始地址。通常将其设置为0,这表示由系统选择该映射区的起始地址。此函数的返回地址是:该映射区的起始地址。fd指定要被映射文件的描述符(fd用于定位是哪个磁盘文件的页高速缓存)。在映射该文件到一个地址空间之前,先要打开该文件。len是映射的字节数。off是要映射字节在文件中的起始位移量(下面将说明对off值有某些限制)。
& & & & 在说明其余参数之前,先看一下存储映射文件的基本情况。图12 - 12显示了一个存储映射文件。
在此图中,“起始地址”是mmap的返回值。在图中,映射存储区位于堆和栈之间:这属于实现
细节,各种实现之间可能不同。
& & & & prot参数说明映射存储区的保护要求。见表12 - 8。
& & & & 对于映射存储区所指定的保护要求与文件的open方法匹配。例如,若该文件是只读打开的,那么对映射存储区就不能指定PROT _WRITE。(对存储映射区的保护是通过设置页表项的保护标志来实现的,如果页表项的read/write标志位为0,说明页是只读的,如果进程试图修改页的内容,将产生段错误,这些保护方案都是由CPU硬件控制的)
& & & & flag参数影响映射存储区的多种属性:
& & & & ? MAP_FIXED 返回值必须等于addr。因为这不利于可移植性,所以不鼓励使用此标志。如果未指定此标志,而且addr非0,则内核只把addr视为何处设置映射区的一种建议。通过将addr指定为0可获得最大可移植性。
& & & & ? MAP_SHARED 这一标志说明了本进程对映射区所进行的存储操作的配置。此标志指定存储操作修改映射文件—也就是,存储操作相当于对该文件write。(这里映射的页是包含在文件的页高速缓存中,用户态进程在读写磁盘的时候,内核先在页高速缓存中增加一个新页,将所请求的磁盘块写入新页,用户态进程从页高速缓存中取出数据。如果要写入数据,也是要添加一个页将磁盘中的数据写入该页,然后再将数据写入该页,内核会在一定的时机对磁盘进行更新。)
& & & & (以上的页高速缓存是组织在inode的i_mmaping对象中,对于一个磁盘文件唯一对应一个磁盘inode,每个磁盘inode也唯一对应一个内核inode,也就是,每个磁盘文件只有一个页高速缓存,如果两个进程映射的是同一个文件的页高数缓存,则它们共享相同的物理页)
& & & & ? MAP_PRIVATE 本标志说明,对映射区的存储操作导致创建该映射文件的一个副本。所有后来对该映射区的存访都是存访该副本,而不是原始文件。(这里内核用到了写时复制技术,在相应的页表项中设置写时复制标志,当进程试图修改该页,内核将会产生缺页异常,内核就把该页框进行复制,并在进程页表中用复制的页来替换原来的页框,显然这个新的页框已经不在页高速缓存中了,对页框的内容进行修改将不会写回文件,其它进程将无法共享这个页框。如果本进程还未进行写复制,而其它进程修改了页的内容,本进程是可以获得更新后的数据)
& & & & 因为映射文件的起动位移量受系统虚存页长度的限制,那么如果映射区的长度不是页长度的整数倍时,将如何呢?假定文件长12字节,系统页长为512字节,则系统通常提供512字节的映射区,其中后500字节被设0。可以修改这50字节,但任何变动都不会在文件中反映出来。(这是由于内核分配线性区和分配物理内存都是以页为单位)
& & & & 与映射存储区相关有两个信号: SIGSEGV和SIGBUS。信号SIGSEGV通常用于指示进程试图存取它不能存取的存储区。如果进程企图存数据到用mmap指定为只读的映射存储区,那么也产生此信号。如果存取映射区的某个部分,而在存取时这一部分已不存在,则产生SIGBUS信号。例如,用文件长度映射一个文件,但在存访该映射区之前,另一个进程已将该文件截短。此时,如果进程企图存取对应于该文件尾端部分的映射区,则接收到SIGBUS信号。(对信号的实现机制有待进一步分析)
& & & & 在fork之后,子进程继承存储映射区(因为子进程复制父进程地址空间,而存储映射区是该地址空间中的一部分),但是由于同样的理由,exec后的新程序则不继承此存储映射区。(关闭文件描述符也不影响存储映射区,磁盘文件的页高速缓存并不会因为进程的撤销而撤销,如果有足够的空闲内存,页高速缓存中的页将长期存在,使其它进程再使用该页时不再访问磁盘。)
& & & & 进程终止时,或调用了munmap之后,存储映射区就被自动去除。关闭文件描述符fd并不解除映射区。(关闭存储映射区,只是撤销进程页表中的相应目录项,并不影响页高速缓存。)
int munmap(void addr,size_t len) ;
返回:若成功则为0,若出错则为- 1
munmap 并不影响被映射的对象—也就是说,调用munmap并不使映射区的内容写到磁盘文件上。对于MAP_SHARED区磁盘文件的更新,在写到存储映射区时按内核虚存算法自动进行。(pdflush内核线程用于刷新脏页)
例子程序:
int main(int argc, char *argv[])
& & & & int fd, i,
& & & & pid_
& & & & char *area = NULL;
& & & & if((fd = open("test", O_RDWR) )<= 0)
& & & & & & & & printf("open error\n");
& & & & area = (char *)mmap(0, sizeof(long), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
& & & & printf("area:%p\n", area);
& & & & close(fd);
& & & & *(area + 1) = 'c';
文件映射API补充
msync函数的使用原型:?? #include ?? int msync(const void *start, size_t length, int flags);msync函数用来把映像的文件写入磁盘。调用msync可以用对内存中的映像的更新写入一个被映像的文件,被强行写入到磁盘的内存取从start指定的地址开始,写入length个字节的数据。flags可以是下面的一个值或多个的逻辑“或”:?? 1、MS_ASYNC 调度一次写入操作然后返回?? 2、MS_SYNC 在msync返回前写入数据?? 3、MS_INVALIDATE 让映像到同一文件的映像无效,以便用新数据更新它们(MS_INVALIDATE的作用是使映射的页高速缓存中的内容无效,重新从磁盘写入数据到映射的页高速缓存。
& & & & 可以使用MS_INVALIDATE来测试内核是否进行页高速缓存数据的回写磁盘操作,测试过程:写一个字符到映射区,然后使用MS_INVALIDATE使映射区的数据失效,并从磁盘写入数据,从测试结果看字符会被写入磁盘,也就是说内核几乎在对映射区进行写入操作的同时就进行了回写磁盘操作)
mprotect函数的使用mprotect函数修改在内存映像上的保护模式。函数原型:?? #include ?? #include ?? int mprotect(const void *start, size_t len, int prot);mprotect把自start开始的内存区的保护模式修改为prot指定的值,如果执行成功返回0,如果执行失败,mprotect返回-1,并且设置errno变量。port可以是PROT_READ(可读)、PROT_WRITE(可写)、PROT_EXEC(可执行)、PROT_NONE(不可访问)中的一个或多个(这里只是修改本进程页表中的访问控制标志,并不涉及物理页,对其它进程没有影响)
锁定内存原型:??? #include int mlock(const void *start, size_t len);??? int munlock(void *start, size_t len);??? int mlockall(int flags);??? int munlockall(void);以4上个函数是对指定的内存映像加锁和解锁,其中mlockall的flags包括MCL_CURRENT和MCL_FUTURE。只有root权限才能使用它们。start指出被加锁或解锁的内存区,len指出加锁或解锁的内存区大小。flags的值可以是MCL_CURRENT和MCL_FUTURE之一或者两个都有。MCL_CURRENT在调用返回前请求锁住所有内存页面,MCL_FUTURE指出锁住所有增加到进程的地址空间的内存页面。(mlock的操作结果是vma的属性,VM_LOCKED. 作用是被lock的内存不参加swap,保证一直存在于内存中.内核中具体的策略执行可以看函数 swap_out_vma.当使用exec的时候,lock失效. fork的子进程也不继承此属性.
& & & & 对于实时进程和安全程序,此调用很有意义.对于加密程序,密码不会被dump到磁盘上. 但是lock并不能阻止系统休眠的时候内存被存储到磁盘.
& & & & mlock,munlock的操作不可堆叠. 多次调用mlock的一段内存也会被一次unlock操作解锁.
& & & & mlock/munlock指定的地址会被round down到一个page的边界.)
mremap函数的使用mremap函数用于改变一个被映像的文件大小。原型:?? #include ?? void *mremap(void *old_addr, size_t old_len, size_t new_len, unsigned long flags);mremap用指定的flags把地址在old_addr的内存映像大小从old_len调整为new_len,flags如果为MREMAP_MAYMOVE则调整此内存映像的地址。成功返回新地址,失败返回NULL。
(内核要做事情是:改变线性区对象的长度。
& & & & 内核会检查是否可以直接扩大或者缩小线性区的大小,如果线性对象相邻的线性空间已经被使用了,此时将没法扩大了,如果此时设置了MREMAP_MAYMOVE标志,将会重新分配一块新的线性空间,显然这个空间的起始地址已经改变)
(四)IPC共享存储(包含实现原理说明)
& & & & IPC共享存储允许两个或多个进程共享一给定的存储区。因为数据不需要在客户机和服务器之间复制,所以这是最快的一种IPC。使用共享存储的唯一窍门是多个进程之间对一给定存储区的同步存取。若服务器将数据放入共享存储区,则在服务器做完这一操作之前,客户机不应当去取这些数据。通常,信号量被用来实现对共享存储存取的同步。
调用的第一个函数通常是shmget,它获得一个共享存储标识符。
int shmget(key_t key, size_t size, int shmflg);
& & & & 如果参数key为 IPC_PRIVATE。则会建立新的共享内存对象其大小由size(单位字节Byte)指定,如果key不为IPC_PRIVATE,并且存在键值为key的共享内存对象,则返回所关联的id号,如果不存在键值为key的共享内存对象,那么系统会视参数shmflg是否有IPC_CREAT来决定是否新建一个共享内存对象。(每个共享内存对象都对应一个目录对象和一个inode对象,每个inode对象都包含一个address_space &i_mapping对象,但是这些对象并没有磁盘映像,而是为了可以重复利用文件映射中提供的代码,每个进程映射的物理页就存储在i_mapping对象里。)
& & & & (以上是一种特殊的文件系统,它并没有挂载在某个目录下,只是为了方便实现共享内存)
& & size是要建立共享内存的长度。所有的内存分配操作都是以页为单位的。实际的大小是((bytes进位到4096整数倍)/4096 + 4) * 4096。(因为线性区和物理内存的分配都是以页为单位)
& & shmflg主要和一些标志有关,其中有效的包括IPC_CREAT和IPC_EXCL,它们的功能与open()的O_CREAT和O_EXCL相当。
& & IPC_CREAT & 如果共享内存不存在,则创建一个共享内存,否则打开操作。
& & IPC_EXCL & &只有在共享内存不存在的时候,新的共享内存才建立,否则就产生错误。
如果单独使用IPC_CREAT,shmget()函数要么返回一个已经存在的共享内存的操作符,要么返回一个新建的共享内存的标识符。如果将IPC_CREAT和IPC_EXCL标志一起使用,shmget()将返回一个新建的共享内存的标识符;如果该共享内存已存在,或者返回-1。IPC_EXEL标志本身并没有太大的意义,但是和IPC_CREAT标志一起使用可以用来保证所得的对象是新建的,而不是打开已有的对象。
成功返回共享内存的标识符;不成功返回-1,errno储存错误原因。
& & EINVAL & & & &参数size小于SHMMIN或大于SHMMAX。
& & EEXIST & & & &预建立key所致的共享内存,但已经存在。
& & EIDRM & & & & 参数key所致的共享内存已经删除。
& & ENOSPC & & & &超过了系统允许建立的共享内存的最大值(SHMALL )。
& & ENOENT & & & &参数key所指的共享内存不存在,参数shmflg也未设IPC_CREAT位。
& & EACCES & & & &没有权限。
& & ENOMEM & & & 核心内存不足。
struct shmid_ds
& & shmid_ds数据结构表示每个新建的共享内存。当shmget()创建了一块新的共享内存后,返回一个可以用于引用该共享内存的shmid_ds数据结构的标识符。
include/linux/shm.h
& & struct shmid_ds {&
& & & & struct ipc_perm & &shm_ & & &/* operation perms */&
& & & & int & & shm_ & & /* 共享内存的大小 */&
& & & & __kernel_time_t & &shm_ &/* 最后一次附加这个共享内存的时间 */&
& & & & __kernel_time_t & &shm_ &/*最后一次分离这个共享内存的时间 */&
& & & & __kernel_time_t & &shm_ &/*最后一次改变这个共享内存结构的时间*/&
& & & & __kernel_ipc_pid_t shm_ & /* 建立这个共享内存的进程识别码 */&
& & & & __kernel_ipc_pid_t shm_ & /*最后一个操作共享内存的进程识别码*/&
& & & & unsigned short & & shm_ & /* 附加这个共享内存的进程个数 */&
& & & & unsigned short & & shm_ &/* compatibility */&
& & & & void & & & & & *shm_unused2; /* ditto - used by DIPC */&
& & & & void & & & & *shm_unused3; /* unused */&
struct ipc_perm
& & 对于每个IPC对象,系统共用一个struct ipc_perm的数据结构来存放权限信息,以确定一个ipc操作是否可以访问该IPC对象。
& & struct ipc_perm {&
& & & & __kernel_key_t & //共享内存对象的key
& & & & __kernel_uid_t & //共享内存所属的用户识别码(可以修改)
& & & & __kernel_gid_t & //共享内存所属的组识别码(可以修改)
& & & & __kernel_uid_t & //建立共享内对象的用户识别码
& & & & __kernel_gid_t & //建立共享内对象的组识别码
& & & & __kernel_mode_ //这个共享内存的读写权限(可以修改)
& & & & unsigned short & //序号
shmctl函数对共享存储段执行多种操作。
int shmctl(int &shmid, int cmd, struct shmid_ds * buf) ;
& & & & 返回:若成功则为0,若出错则为- 1
& & & & cmd参数指定下列5种命令中一种,使其在shmid指定的段上执行。
& & & & ? IPC_STAT 对此段取shmid_ds结构,并存放在由buf指向的结构中。
& & & & ? IPC_SET按buf指向的结构中的值设置与此段相关结构中的下列三个字段:(只能修改这3个字段)shm_perm.uid、shm_perm.gid以及shm_perm.mode。此命令只能由下列两种进程执行:一种是其有效用户ID等于shm_perm.cuid或shm_perm.uid的进程;另一种是具有超级用户特权的进程。
& & & & ? IPC_RMID 从系统中删除该共享存储段。因为每个共享存储段有一个连接计数(shm_nattch在shmid_ds结构中),所以除非使用该段的最后一个进程终止或与该段脱接,否则不会实际上删除该存储段。不管此段是否仍在使用,该段标识符立即被删除,所以不能再用shmat与该段连接。此命令只能由下列两种进程执行:一种是其有效用户ID等于shm_perm.cuid或shm_perm.uid的进程;另一种是具有超级用户特权的进程。
& & & & ? SHM_LOCK锁住共享存储段。此命令只能由超级用户执行。
& & & & ? SHM_UNLOCK解锁共享存储段。此命令只能由超级用户执行。
& & & & 一旦创建了一个共享存储段,进程就可调用shmat将其连接到它的地址空间中。
void *shmat(int shmid, void *addr, int flag) ;
& & & & 返回:若成功则为指向共享存储段的指针,若出错则为- 1。
& & & & 共享存储段连接到调用进程的哪个地址上与addr参数以及在flag中是否指定SHM_RND位有关。
& & & & (1) 如果addr为0,则此段连接到由内核选择的第一个可用地址上。
& & & & (2) 如果addr非0,并且没有指定SHM_RND,则此段连接到addr所指定的地址上。
& & & & (3) 如果addr非0,并且指定了SHM_RND,则此段连接到( addr-(addr mod SHMLBA))所表示的地址上。SHM_RND命令的意思是:取整。SHMLBA的意思是:低边界地址倍数,它总是2的乘方。该算式是将地址向下取最近1个SHMLBA的倍数。
& & & & 除非只计划在一种硬件上运行应用程序(这在当今是不大可能的),否则不用指定共享段所连接到的地址。所以一般应指定addr为0,以便由内核选择地址。
& & & & 如果在f l a g中指定了SHM_RDONLY位,则以只读方式连接此段。否则以读写方式连接此段。(在进程页表项中设置只读标志,试图修改该页时将产生缺页异常,这些都是由CPU的页寻址硬件控制的)
& & & & shmat的返回值是该段所连接的实际地址,如果出错则返回-1。
& & & & 当对共享存储段的操作已经结束时,则调用shmdt脱接该段。注意,这并不从系统中删除其标识符以及其数据结构。该标识符仍然存在,直至某个进程(一般是服务器)调用shmctl(带命令IP C_RMID)特地删除它。(连接是进程将共享内存的物理页加入进程页表,脱离是从页表中撤销该物理页的信息,并不改变实际的物理页)
& & & & #include
& & & & #include
& & & & #include
& & & & int shmdt(void * addr);
& & & & 返回:若成功则为0,若出错则为- 1
& & & & addr参数是以前调用shmat时的返回值。
& & & & 下面是和IPC共享内存有关的内核参数,可以修改的。
& & & & 其中shmall是所有共享内存段可以使用的最大页个数
& & & & shmmni是一个共享内存段的最小字节数
& & & & shmmax是一个共享内存段的最大字节数
& & & & 下面是显示系统中已近建立的共享内存对象
& & & & 其中bytes是申请共享内存时使用的大小参数,实际的大小是((bytes进位到4096整数倍)/4096 + 4) * 4096。
& & & & nattch是附加此共享内存对象的进程数。
例子程序:
#define KEY 4&
#define SIZE 4096*3&
int main(int argc, char *argv[])
& & & & int shmid = 0, ret = 0;
& & & & char *shmaddr = 0;
& & & & struct shmid_
& & & & shmid = shmget(KEY, SIZE, IPC_CREAT | SHM_R);
& & & & printf("id: %d\n", shmid);
& & & & shmaddr = (char *)shmat(shmid, NULL, 0);
& & & & *(shmaddr + 4095*3) = 'c';
& & & & shmdt(shmaddr); & & & &
使用ipcs –m 查看建立的共享内存对象:
& & & & 要理解IPC共享内存和文件映射的实现机制,先要理解什么是共享内存,共享内存实现的基本原理是什么。
(五)共享内存实现基本原理
& & & & CPU要访问某块内存,必须要获得内存的物理地址。
& & & & CPU集成有寻址硬件,会根据机器语言指令中提供的地址,执行地址转换,获得的物理地址。
& & & & CPU有两种转换模式:1.实模式 2.保护模式。
& & & & 实模式下的物理地址 = 线性地址。
& & & & 保护模式下的物理地址 = 线性地址通过分页机制转化为物理地址。
& & & & 启动保护模式:把CPU控制寄存器CR0中的最高位置1。
& & & & CPU保护模式寻址方式:
图1 分页机制寻址
& & & & 说明:CR3控制寄存器的值是物理地址。由于寻找的是页框的物理地址,所以CR3,页目录和页表中存储的物理地址后12位都为0。也就是这12位的空间不存储物理地址,而是用于访问控制(可读/可写/CPU特权级别),指示对应的页是否存在等作用。
& & & & 在转换过程中,出现以下情况之一将会引起也异常:
涉及的页目录表内的表项或页表内的表项中的P=0,即涉及到的页不在内存;
违反也保护属性的规定而对页进行访问。
& & & & 注意:从2.6.11版本开始,采用了四级分页模型,但基本原理是一样的。
& & & & 每个进程的CR3的值是不同,进程切换时保存或者恢复CR3的值。只要设置了CR3的值CPU将自动进行寻址。
& & & & 要将某个物理页加入进程的地址空间,要做的事情就是将物理页的物理地址填写进程页目录表内的表项和页表内的表项。
& & & & 共享内存实现原理就是:将相同的物理页加入不同进程的地址空间。
& & & & 显然进程中要加入一块物理页,就必须对应一块线性空间,于是就要先申请一块线性空间,然后根据这块线性空间填写页目录表内的表项和页表内的表项。(这里可以说明为什么不同进程中的不同线性地址可以对应相同的物理地址)。
& & & & 由于linux会先分配线性空间,页表的修改会推后进行。Linux通过vm_area_struct对象实现线性区,当产生缺页异常时,会根据vm_area_struct对象来修改页表,然后重新执行产生缺页异常的代码。
& & & & 总的来说,内核实际要做的事情是很多的,但是,内核也提供了很多接口,所以我们要做的事情还是比较少的。
& & & & 以上就是共享内存实现的基本原理,下面分析一下IPC共享内存和内存映射实现机制。实际上IPC共享内存的实现是基于内存映射,原因是:内存映射提供了一些接口,基于内存映射来实现IPC共享内存可以复用这些代码。但是,两者最终的实现原理还是修改页表。
(六)IPC共享内存实现机制
& & & & 先看一下内核是如何组织共享内存对象的,如图2所示:
图2 共享内存对象组织
& & & & 其中struct shmid_kernel就是一个共享内存对象,使用id radix tree来组织所有的共享内存对象。使用id号查找一个共享内存对象。
& & & & 我们现在最关心的问题是:如何根据struct shmid_kernel结构获得对象所拥有的物理内存。
是根据file->f_path.dentry->d_inode
struct address_space *mapping = inode->i_mapping
mapping存储着共享内存拥有的物理页,如图3所示:
图3 共享内存物理页存储方式
& & & & 其中page_tree用于存储物理页,每个节点的值类型是struct page *
每个共享内存对象对应一个inode对象,这个对象是被多个进程共享,也就是说,进程是通过inode对象获得物理页。这里借用了文件映射的框架,i_mmaping对象也就是文件的页高速缓存。
& & & & 进程映射共享内存区域的过程:
& & & & 1.需要申请一块线性地址空间,也就是生成一个vm_area_struct对象,并将对象加入到自己的地址空间,当此时并不修改进程页表,而是把struct file对象加入到vm_area_struct对象中,执行以下代码:
& & & & & & & & vma->vm_file =
& & & & & & & & get_file(file);
& & & & & & & & error = file->f_op->mmap(file, vma);
注意:这里的file是根据shm_file生成的一个新的对象,相当于shm_file的复制。
& & & & 2. 当第一次访问共享内存块时,由于相应的页表项还未填写,将产生缺页异常,内核根据产生异常的线性地址找到对应的vm_area_struct对象,最后将执行以下函数:
static int shmem_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
& & & & struct inode *inode = vma->vm_file->f_path.dentry->d_
& & & & if (((loff_t)vmf->pgoff <= i_size_read(inode))
& & & & & & & & return VM_FAULT_SIGBUS;
& & & & error = shmem_getpage(inode, vmf->pgoff, &vmf->page, SGP_CACHE, &ret);
& & & & if (error)
& & & & & & & & return ((error == -ENOMEM) ? VM_FAULT_OOM : VM_FAULT_SIGBUS);
& & & & return ret | VM_FAULT_LOCKED;
& & & & 本函数的功能是:根据产生缺页异常的线性地址找到对应的物理页,并将这个物理页加入页表。
以上说明的也就是内存映射实现机制,现在做个总结:
调用do_mmap()函数生成并注册一个线性对象,同时也注册缺页异常回掉函数。
当第一次访问共享内存空间时,产生缺页异常,将会调用先前注册的回调函数,在回调函数中将相应的物理页加入进程的页表。
(七)文件映射的实现机制
& & & & 一个磁盘文件唯一对应一个磁盘inode,每个磁盘inode也唯一对应一个内核inode,每个内核inode都包含一个页高速缓存对象struct address_space &i_mmap,建立一个文件映射的步骤如下:
& & & & 第一步和IPC共享内存一样还是生成并注册一个线性对象,同时也注册缺页异常回掉函数。
& & & & 第二步当产生缺页异常时会执行先前注册的回调函数,在回调函数中将请求的物理页的物理地址写入进程页表项,并重新执行产生缺页异常的代码。
下面以EXT3文件系统为例子:
注册回调函数:
int generic_file_mmap(struct file * file, struct vm_area_struct * vma)
& & & & struct address_space *mapping = file->f_
& & & & if (!mapping->a_ops->readpage)
& & & & & & & & return -ENOEXEC;
& & & & file_accessed(file);
& & & & vma->vm_ops = &generic_file_vm_
& & & & vma->vm_flags |= VM_CAN_NONLINEAR;
& & & & return 0;
const struct vm_operations_struct generic_file_vm_ops = {
& & & & .fault & & & & &= filemap_fault,
filemap_fault()回调函数被多种文件系统使用。
在函数内总是从struct address_space *mapping = file->f_获得物理页。
从以下函数看出file->f_mapping指向inode的i_mapping对象。
struct file *alloc_file(struct path *path, fmode_t mode,
& & & & & & & & const struct file_operations *fop)
。。。。。。。。。。。。。。
& & & & file->f_path = *
& & & & file->f_mapping = path->dentry->d_inode->i_
。。。。。。。。。。。。。。。
& & & & 每个磁盘文件都唯一对应一个内核inode对象,如果多个进程同时打开同一个磁盘文件,inode对象将被多个进程共享。每个文件的inode包含了一个address_space结构,通过inode->i_mapping来访问。address_space结构中维护了一棵radix树,用于磁盘高速缓存的内存页面就挂在这棵树上。打开这个文件的每个进程都共用同一份页高速缓存。
& & & & 如果是私有映射类型则相应的页表项会设置写时复制,也就是,当进程试图修改一个私有映射内存的页时,内核将会产生缺页异常,内核就把该页框进行复制,并在进程页表中用复制的页来替换原来的页框,显然这个新的页框已经不在页高速缓存中了,对页框的内容进行修改将不会写回文件,其它进程将无法共享这个页框。
& & & & pdflush内核线程用于刷新脏页。
& & & & 注意:如果还没修改私有映射内存的页,也就是还未进行写时复制,则对应的页框还是页高速缓存中的页,如果其他进程修改了这个页框的数据,本进程还是可以读取修改后的数据。
阅读(4480) | 评论(0) | 转发(1) |
相关热门文章
给主人留下些什么吧!~~
请登录后评论。}

我要回帖

更多关于 linux 共享内存 mmap 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信