王 煜,林 浒,陶耀东,郑一麟
(1.中国科学院 研究生院,北京 100049;2.中国科学院 沈阳计算技术研究所,沈阳 110168)
随着数控加工精度不断提高,对数控系统软件的可靠性要求不断加强,这就要求必须对数控系统内部执行情况的实时监测,将数控系统内部的各种信息发送给界面实时显示,对发现错误做出及时响应。
数控系统一般主要包含四个主要模块,人机接口(HMI)、运动模块(MOTION)、可编程逻辑模块(PLC)和任务模块(TASK)。HMI 运行在用户空间,而其他模块运行在内核空间,这些模块之间利用共享内存传输需要的命令、状态和错误信息。
本文通过对RTAI 提供的共享内存机制研究,设计并实现一种高效的共享内存管理机制,减少系统内核调用次数,提高了RTAI 的共享内存分配和释放速率,将其应用于现有的数控系统,实现了数控系统的高可靠性。
RTAI 是Linux 操作系统的一种实时扩展,它通过设置保留一块物理内存,并将其映射到使用的进程地址空间,用来提供共享内存机制可用于内核和用户空间的通信,维护了内核和用户所申请内存的空间映射。在RTAI 中,rtai_kmalloc 是从内核空间分配共享内存的接口函数,rtai_malloc 是从用户空间分配共享内存的接口函数。rtai_kmalloc 从内核申请小于KMALLOC_LIMIT(128K)的空间使用系统内核函数kmalloc 分配共享内存空间,大于KMALLOC_LIMIT 使用内核系统函数__get_free_pages 分配空间。rtai_malloc 从用户空间申请时,直接使用系统函数vmalloc 分配。无论从内核或者是用户分配完,都要设置页面保留位,标识出该页不被置换出。对于已经被分配的,则函数查找维护的全局变量,将共享内存地址返回[1]。
共享内存机制也存在不足,其自身不能实现多进程的读写同步,需要应用程序自己解决复杂的同步互斥问题[2]。同时,每次新申请内存空间都要通过系统调用,这样所需要的页表分配的开销比较大,同时增加了系统的负担。
为解决同步互斥和多次系统调用的问题,本文研究并实现一个共享内存管理层,完成对共享内存的高效管理。
该层的设计就是实现一个共享内存的管理器,用户不再使用由RTAI 提供的共享内存接口函数,而使用由该层实现的接口函数。主要包含数据结构设计,共享内存的分配算法设计和释放算法设计。
依据共享内存块的相关属性,从以下五个方面的数据特性考虑数据结构的设计:
(1)共享内存块的性质,模块主要有两种,内核模块或者是用户模块,申请共享内存空间的模块选择涉及到实际分配所使用的RTAI 函数。
(2)共享内存块的并发访问,不同的模块对一块数据的并发访问,保证各个部分对同一个数据的一致性访问。可采用互斥量,来保证同一时间只有一个进程来访问一个关键区域,防止产生脏数据。若对访问的数据不是严格要求,可以使访问的进程获取上次产生的旧数据,不必忙等,减少系统的负担,增加系统的吞吐率。
(3)共享内存块的分配和访问效率。确定共享内存分配的大小,以及维护内存块的个数,不使用某一块,将占用计数减一,为零时并不立即释放,只是标明未使用,再次申请使用只用占用计数加一,就可继续使用,减少系统函数的反复调用,增加整个系统的稳定性。在此基础上,增加共享内存池的概念,它是已经由底层分配函数分配成功的大块的共享内存,当上层若要申请的共享内存大小小于该内存池的大小,则直接从中分配空间给用户。同时也要维护共享内存块的链表,在释放的时候能将合适的块合并。
(4)统计数据。利于系统对数据的统计分析,以便于对当前系统的性能分析和问题排查。同时也是最终是否能够释放共享内存的标志。
(5)共享内存块标识。唯一的确定一块内存,在内核空间和用户空间维护共享内存块地址与使用者之间的映射。
主要使用共享内存池和共享内存块这两种结构来实现管理层的设计。
共享内存池:由共享内存层利用系统分配函数分配的大块共享内存,使用双向循环链表连接,由共享内存层负责地址映射。若存在的内存池空间不能满足需要,内存池可再利用底层系统函数进行分配;共享内存池的可用空间大小是指被申请使用的共享内存块使用余下的可被再分配的空间,同时考虑到边界对齐;使用互斥量来保证对内存的并发访问。
共享内存块:在已存在的内存池里分配的用户申请的内存空间,采用双向循环链表连接,存在空闲链表,是被用户释放的,但并没有被共享管理层实际释放,只是置位该块可以继续使用,同时要考虑相邻空闲块的合并,和非空闲的共享内存块链表。
为了保证共享内存块的分配效率,共享内存管理层要维护一定数量的空闲内存池,使得系统免于重复的内存分配和释放,该数量的设置要考虑到系统的负载,可由实际系统需要设置。
整个数据模型如图1 所示。
图1 共享内存管理层数据结构模型
2.3.1 共享内存池分配算法
共享内存空间的实际分配,在内核空间利用底层共享内存接口函数rtai_kmalloc 分配共享内存,在用户空间使用rtai_malloc。
2.3.2 共享内存块分配算法
首先,在共享内存标识符表中,查找是否已经分配了申请字符串名的共享内存,若已经分配,修改相应计数并返回地址;否则,采用首次适配算法,首先在第一个共享内存池的空闲链表中,查看是否有符合要求的内存块,若有直接将占用计数加一,同时加入到非空闲链表的合适位置。否则,依次查看存在的共享内存池,从共享内存池的可用空间判断是否符合用户要求,若符合,从中减去用户申请的大小,注意字节对齐,并同时加入到非空闲内存块链表的合适位置,返回地址。若不符合,则重新申请一个符合大小规则的新的共享内存池,分配适合用户使用的内存块,同时加入到非空闲块链表的合适位置,按照可用空闲块地址将内存池插入到合适的位置,返回地址,见图2。
2.4.1 共享内存池释放算法
共享内存池的释放是共享内存空间的实际释放,如果当前使用计数为0,在内核空间利用底层共享内存接口函数rtai_kfree,在用户空间利用底层共享内存接口函数rtai_free。
2.4.2 共享内存块释放算法
利用共享内存标示符表查找此名标示的共享内存块,若找到,将共享内存块的占用计数减一,若未找到,则说明未分配共享内存,失败返回,为零放入到合适的空闲块链表,若有相邻的空闲块,将其连成一个连续的空闲块,放入合适的位置,否则,直接返回。若空闲块地址与内存池可用地址邻接,将其归还给共享内存池。最后,清除共享内存标示符表保存的名和地址的映射,见图3。
图2 共享内存块分配算法流程图
图3 共享内存块释放算法
void * get_shmem_addr(char * name,unsigned int size),获得大小为size,指针名为name 的共享内存地址。
int free_shmem_addr(char * name),释放指针名为name 的共享内存。
void * _get_shmem_addr(unsigned int size),实际进行共享内存分配。
shmem_block * find_with_name(const char *name),查找是否已经存在name 指向的字符串名的共享内存。
int get_shmem_pool(int pol),获得一个共享内存池。
int free_shmem_pool(shmem_pool * p),释放内存池分配的共享内存空间。
shmem_block* get_shmem_block(unsigned int size,module_type mod),获取一个size 大小,性质为mod 的内存块。
int free_shmem_block(shmem_block * b),将内存块释放,就是将它放入空闲块链表里
shmem_block* search_in_shmem_pool(shmem_pool * p,unsigned int size),在空闲块链表里面查找是否有合适的空闲块
int insert_shmem_block_into_pool(shmem_block* b,shmem_pool* p),将分配好的内存块插入到内存池的内存块链表里。
int insert_free_shmem_block_into_pool(shmem_block* b,shmem_pool* p),将空闲块插入到内存池的空闲块链表里
int free_shmem_pool_block(shmem_pool* p),将和内存池可用空间地址毗邻的空闲内存块返回给内存池。
void * get_addr_shmem_block(shmem_block *b),获取共享块所指向的地址。
void init_shmem_block(shmem_block * b,unsigned int size,module_type mod,unsigned int pol),初始化内存块。
shmem_block* alloc_shmem_block_from_pool(shmem_pool * p,unsigned int size),从合适的内存池里分配内存块。
API 接口调用如下图4 和图5 所示。
图4 分配过程API 调用
硬件平台为龙芯CPU,主频800MHZ,内存256M,软件环境为linux2.4 操作系统,RTAI-24.1.13实时扩展。整个共享内存管理层使用C 语言实现,具有很强的移植性。
实验计时方法,利用MIPS 体系结构的协处理器0(Coprocessor 0)的count(9)寄存器[8]。在分配之前获取时间戳t1,在分配之后获取时间戳t2,两者之差就是所需要的时间Δt。
图5 释放过程API 调用
实验结果如下图6 和图7 所示。
图6 rtai 和shmem 分配和释放4 字节的时间
图7 rtai 和shmem 分配和释放400 字节的时间
由图6 和图7 可知,利用shmem 共享内存管理,能够有效的减少共享内存的分配时间和释放时间。
共享内存在需要内核和用户空间数据传输的情况下,有较高的灵活性,所以在数控系统中应用广泛,因此提高共享内存的管理效率对各种使用共享内存的应用具有很重要的意义。本文采用两级数据管理结构,能够有效的分配和释放共享内存,使用C语言实现的该共享内存管理层具有很好的可移植性,能够有效的减少共享内存分配和释放时间,同时互斥量的使用也能保证共享内存的并发访问。实验结果证明,本设计优化了RTAI 共享内存管理的相关功能,提高了数控系统的工作效率。
[1]RTAI documentation,available from:https://www. rtai.org/documentation/magma/html/api/
[2]高甜容,于东,等. 数控系统中模块间通信方法的设计与实现[J]. 计算机工程,2010,36(12):238-241.
[3]Mauerer W. Professional Linux Kernel Architecture[M].[S. l.]:John Wiley & Sons Inc.,2008.
[4]Yu Dong,Hu Yi,Huang Yan,et al. An Open CNC System Based on Component Technology[J]. IEEE,2009,6(2):302-311.
[5]Scott S,Christos D A,Dimitrios S N. Scalable Locality-conscious Multithreaded Memory Allocation[C]. ACM,2006:84-94.
[6]鲁比尼(Rubini,A.),等,著,魏永明,等,译. LINUX 设备驱动程序[M]. 北京:中国电力出版社,2002.
[7]魏海涛,姜昱明,李建武,等. 内存管理机制的高效实现研究[J]. 计算机工程与设计,2009,30(16):3708-3712.
[8]吴文江,秦承刚,陶耀东. 基于MIPS 处理器和RTAI 的数控系统中调度抖动的研究[J]. 小型微型计算机系统,2010(7):1342-1345.