共享内存就是让两个或多个进程可以访问同一块内存,使得一个进程对这块空间的某个单元的内容改变可以为其他进程所看到;共享内存效率很高,但是它所提供的功能相对有限的,而且要特别小心;由于要考虑到内存的同步,要结合IPC的信号量机制一起使用;
(1)共享内存区的创建与寻找,由shmget完成;如果key是IPC_PRIVATE,那就使用newseg来创建新的内存共享区;与报文队列中ipc_ids,也有一个shm_ids与ipc_ids是一样的,此时msg_queue与shmid_kernel相对应;
(1.1)在newseg中,首先根据共享区的大小来计算出所需的存储页面的数量numpages,接收对资源数量进行检查,shm_tot和shm_ctlmax,全局变量,分别用来记录当前已经用于共享内存机制的页面数量和其上限;shm_ctlmax用来给出每个共享内存大小的限制;每一个共享内存区都有衣蛾区名,使用SYSV和8位16进制数构成;shmid_kernel与msq_queue类似,但是有一个file结构,这是由于共享内存区中的页面也和普通页面一样,受到内存页面机制的调度,但好似共享内存区有自己专用的映射文件,这样也就与文件映射联系在一起了;为此,内核系统中专门定义了一个新的特殊文件系统shm,其类型为shmem_fs_type;系统初始化时通过kern_mount()来安装这个特殊的文件爱你系统,并且在devfs特殊文件系统中创建一个子目录shm,这是由init_shmem_fs完成的;由于共享内存的映射文件只对当前系统有意义,一点关机也就失去了意义,因此将shm凡在了页面交换盘区,还可提高换出换入的效率,不需从磁盘读;这样建立共享内存区也就变成了额特殊文件系统shm建立映射文件的问题;在newseg中,使用shmem_file_setup来创建映射文件,每一个共享内存区都有一个区名,区名包含它的键值,这个区名也就是文件名;设置flie->f_op为shmem_file_operations,而在返回后的newseg中又将flie->f_op设置成shm_file_operations,前者支持有形的shm文件,支持open和close操作;后者不支持路径名打开,不支持open和close操作;shmem_file_setup中还要对inode有特殊的设置,是通过shmem_get_inode完成的,此时inode_irdev为0,表示没有相应的磁盘索引节点;其次这个inode指向i_mapping->a_ops指向这个address_space_operations(共享内存区的页面换出操作);
(1.2)从newseg返回的file,不属于某一个特定的进程,不代表一个具体文件的读写上下文;file保存在shmid_kernel中,使用shmaddid()建立这个shmid_kernel和shm_ids的联系;
(2)最后从sys_shmget,返回的也是所创建共享内存区的一体化标识号,是由shm_buildid来完成的;
(3)建立共享内存区的映射,由shmat完成;将取得的已创建共享内存区的标识号,还要通过shmat()将这个内存区映射到本进程的虚存空间,此外,一个已映射的共享内存区间也可以通过shmat()改变其映射;这都是由sys_shmat()完成的;参数shmaddr为当前进程所要求映射的目标地址,也就是由映射后共享内存区在这个进程的用户空间中的起始地址,一般可被SHMLBA整除,也就意味着要与页面边界对齐,若不能被整除,那就SHM_RND标志设成1,若shmaddr是0,内核就会根据当前进程的虚存空间使用情况为其分配一个;此外还要对shmflg中的SHM_READNLY标志位转换成若干用于内存映射和文件访问的标志,因为共享内存区的管理涉及这两个方面;通过shmid找到了可以代表这个共享内存区的shmid_kernel数据结构,并且使用shm_lock来加锁;当然还要使用ipcperms()来检查访问权限;下面就是使用do_mmap()建立起文件与虚存空间的映射,在do_mmap()中,先对文件和区间两方面都作一些检查,包括起始地址与长度,已经映射的次数等,然后调用do_mmap_pgoff,来分配虚存地址空间,每一个虚存空间都要有一个vm_area_struct,还要使用kmem_cache_alloc()为待映射的区间分配一个,单丝有些条件我去不得不将其撤销;首先调用do_munmap()查看目标地址在当前进程是否已经使用了,如果已经使用了,就要将老的区间释放掉;此外虚存空阿金超出了设置的上限;另一个是要求昂前进程专用的可写区间,而物理页面的数量已经供应不足了;由于调度,其他进程或线程的运行可能改变这些条件,则是linux内核中常常先看到的先分配某项资源,然后检测条件,如果条件不满足就将资源释放掉;
(4)在do_mmap_pgoff中,使用shm文件file的file_operations的shm_mmap,它将虚存空间控制结构中的指针vm_ops设置成指向数据结构shm_vm_ops;其实文件与虚存空间之间的映射包含着两个环节,一是物理页面与文件映像只奥及你的换入换出,以及物理页面与虚存页面之间的映射;然后将vm_area_struct插入到当前进程的虚存空间,就完成了额共享内存区映射机制的建立;但是具体的页面映射并未完成,是lazy computation机制;当其中的任何一个野蛮首次受到访问时就会发生缺页异常;从do_page_fault,一直到do_no_page(),如果异常的弟子所属区间的指针vm_ops的nopage非0,就调用这个函数来建立所在页面的映射表项,其实也就是shmem_nopage;
(4.1)在shmem_nopage()中,先计算出页面在所映射文件,即共享内存区的页面号idx,然后使用__find_lock_page()在杂凑队列中寻找该页面的page结构,找到了,说明页面已经在内存区了,只要重新映射就好了;否则就要使用shmem_swap_entry()来确定这个页面是从未映射,还是已经交换到了交换盘区了;如果swap_entry_t是非0的,就表示目标页面在交换设备上,所以要将页面换入,并将这个swap_entry_t清0,表示该页面在内存中,或从未映射;若swap_entry_t为0,这就要通过page_cache_alloc来分配一个空闲内存页面,并通过clear_user_highpage()将该页面全部变成0;最终都要将这个无论是找到的page还是创建的page放入到缓冲页面队列中,若不允许共享,还要使用copy_user_highpage()为目标页面,复制一份由当前进程专用的副本;建立共享内存区页面的映射以后,有关的进程就可以向对一般存储页面一样地读写了;每当访问一个页面时,CPU保证了相应页面表项中_PAGE_ACCESSED标志设成1,如果写访问,还要设置_PAGE_DIRTY标志奢侈1;通过页面表项的这些标志位,就可以知道参与共享的各个进程是否访问了这个页面,以及是否写访问;
(5)对于页面的换出,是在page_launder(),此函数被调用的地方很多;换出的操作是通过shmem_writepage()完成的;先使用__get_swap_entry()从交换设备上分配一个页面;还要根据物理页面号找到swap_entry_t表项,若表项的内容非0,说明已映射,那就释放之前的盘上页面;最后把页面从LRU队列上移除,将其page的指针mapping设置指向的swapper_space;这么一来page_launder()再次扫描到这个页面时,swapper_space就变成了swap_ops的swap_writepage(),此后就和普通页面的换出一样了;shm_open是为fork()一个进程使用的,可递增共享内存区所映射文件的共享计数;与此类似,当一个进程exit(),要释放其所有的虚存空间;如果这个虚存空间是一个共享区,那就调用shm_close()来递减这个计数;
(6)撤销共享内存区的映射在shmdt()完成的;如果一个子进程在开始执行一个新的程序时,就会通过shm_vm_ops中的指针shm_close(),与这里的动作类似;主体是调用do_munmap(),使用的判断条件之一就是vm_operations_struct中的shm_vm_ops;
(7)对共享内存区的控制与管理在shmctl完成的;可以使用SHM_LOCK和SHM_UNLOCK分别来加锁或去锁,也就是禁止和恢复该区间的各个页面的换出或换入操作;还可使用IPC_RMID来撤销该进程的共享内存活动;