VxWorks自定义动态系统调用的应用

2017-02-27 11:10解建伟赖前程曹成军张兴旺
计算机应用与软件 2017年2期
关键词:调用内核动态

解建伟 赖前程 曹成军 张兴旺

(中国电器科学研究院有限公司 广东 广州 510300)

VxWorks自定义动态系统调用的应用

解建伟 赖前程 曹成军 张兴旺

(中国电器科学研究院有限公司 广东 广州 510300)

系统调用是用户模式程序访问内核服务、硬件资源的接口。自定义的系统调用在VxWorks程序开发中有着非常重要的作用。通过自定义系统调用可以让用户模式的程序访问更多的内核函数、硬件资源,可以扩展程序的应用范围。简单论述VxWorks中自定义系统调用的概念,描述系统调用的过程,分步介绍自定义动态系统调用的实现过程。最后介绍应用程序中动态系统调用的使用方法。

VxWorks 自定义 系统调用

0 引 言

VxWorks是美国风河公司设计开发的嵌入式实时操作系统,它支持多种处理器平台,具有较高的可扩展性与安全性。VxWorks在6.0版本之前的系统只提供一个内存地址空间,用户程序和操作系统程序没有做隔离,所有的任务都运行在特权模式。尽管这种形式为程序开发提供了比较好的性能和灵活性,但也使内核与应用程序在内存空间存在相互干扰的可能。在6.0版本以后,操作系统开始支持实时进程RTP(real-time processes),它支持应用程序在用户模式运行,并且是与操作系统内核完全隔离的运行模型。这种模型是专门为满足硬实时操作系统对确定性和速度的要求而设计[1]。

VxWorks中的RTP在很多方面都与Unix和Linux的用户态的进程类似,每个进程都有自己的地址空间,包括可执行代码、数据堆栈以及自身相关的管理资源[2]。这使得系统的安全性得到极大提高,同时系统调用接口为用户态的应用程序提供了访问内核的接口[3]。系统调用常用于RTP程序访问内核服务,CPU外设或其他硬件资源。

需要说明的是应用编程接口(API)与系统调用是不同的,前者是一个函数定义,说明如何获得一个给定的服务,而后者是通过软中断向内核发出一个明确的模式切换请求[4]。由于特权模式的内核程序和用户模式程序有不同的指令集和内存管理单元MMU(memory management unit)设置,应用程序运行在用户模式时不能直接访问内核函数和数据结构。用户模式程序执行系统调用函数时系统从用户模式切换到CPU特权级别的内核模式,当执行完函数后再恢复到用户模式[5]。VxWorks中系统调用的模式切换过程对使用者来说是透明的[1]。

在RTP程序中使用实时系统原有的系统调用与内核交互时,时常会出现不满足设计需求或性能指标的情况[6]。通过增加自定义系统调用,我们可以根据自己的需求,来给内核增加特定的功能,满足性能指标和设计需求[7]。

1 自定义系统调用

在VxWorks中的系统调用一般可分为两大类,一类是操作系统定义的系统调用,像exit、create、open、semGive等都是系统调用;另外一类是用户自定义的系统调用。VxWorks中的自定义系统调用又分为静态自定义系统调用和动态自定义系统调用两种。这两种系统调用的实现方式不同,性能都基本一致,但是它们各有优缺点。

自定义的静态系统调用的优势在于它是与操作系统源码一起编译,可根据需要修改系统原有的系统调用函数;在使用时与普通的函数调用方式一致。不足之处是修改定义系统调用要对系统核心文件作修改,编译调试,这会带来比较大的工作量,一个小小的失误往往会使系统不稳定甚至崩溃,系统可靠性和稳定性不容易保证;同时增加、修改系统调用都比较麻烦,使得系统可维护性与可移植性将会降低[8]。

动态的系统调用只需要在运行时向系统注册自定义系统调用,不需要像自定义静态系统调用那样修改VxWorks源代码文件,不需要与操作系统一起重新编译生成[2]。其优势是不需要对系统核心代码做更改,减少了对内核稳定性的影响;同时对新增系统调用的修改、更新、删除都比较方便。不足之处在于一般不能通过名字直接访问动态系统调用函数,在应用的时候需要知道自定义动态系统调用的组编号和索引位置,然后统一由syscall函数调用。

2 动态系统调用的实现

2.1 分析与设计

在VxWorks6.9实时操作系统中用户自定义的系统调用主要有3个元素:

(1) 系统调用。执行系统调用的是使用汇编语言编写的一个函数,它通过C语言调用。区别不同系统调用的方式是在调用时使用系统调用函数名字和系统调用编号,这是系统开发人员定义的。

(2) 捕获处理。当用户RTP程序访问系统调用时会执行捕获指令处理,它将调用的程序从用户模式切换到特权(内核)模式执行。系统调用编号传递给内核去识别关联的函数。在内核中,捕获处理从用户堆栈或寄存器空间拷贝系统调用参数到内核堆栈空间,然后调用系统调用关联匹配的处理函数。

(3) 处理函数。系统调用的处理函数是系统开发者编写的程序,它只有一个结构体变量参数,结构体成员就是系统调用的参数[3]。当系统从处理函数返回,调用任务将从特权(内核)模式切换到用户模式。

用户自定义动态系统调用程序的实现一般可以分为以下3个步骤:

(1) 设计参数结构,编写处理函数;

(2) 添加系统调用处理表结构信息;

(3) 注册系统调用函数及相关设置。

VxWorks文档建议,对于添加动态系统调用的方法最好使用统一的命名约定,使得系统调用的逻辑一致性和代码的清晰度最好。命名约定如表1所示。

表1 命名约束

设计的实时控制系统中需要实现RTP应用程序读写FPGA中的数据动态系统调用,其中FPGA是挂载在处理器的IFC(Integrated Flash Controller)接口上通过绝对地址访问。

根据命名约束,在内核任务程序中需要实现两个自定义动态系统调用get_fpga_registerSc和set_fpga_registerSc函数,并为它们注册合适的系统调用编号,检查系统调用的注册信息。最后在RTP程序中通过使用此动态统调用访问FPGA的寄存器内容,检查执行正确性。

2.2 实现过程

第一步是设计、编写动态系统调用函数,及其参数结构。自定义读写函数的系统调用其声明如下:

int set_fpga_registerSc(SysCallSetRegScArgs *pArgs);

int get_fpga_registerSc(SysCallGetRegScArgs *pArgs);

其中动态系统调用定义必须要有整型的返回值,参数是通过指向参数结构体的指针来进行传递。自定义动态系统调用函数最多可以有8个参数,即最多可以只有8个结构体成员。每个参数长度都是本机字大小(32位处理器参数长度就是32位,64位处理器参数长度就是64位),如果在32位系统上要传递64位参数需要特别处理。get_fpga_registerSc系统调用有2个参数,其传递的参数结构体如下:

typedef struct{

unsigned int index;

unsigned short* pV;

} SysCallGetRegScArgs;

函数get_fpga_registerSc的执行是在内核中完成,执行时所有参数在使用前必须做边界值检查,验证使用的内存地址,以及检查数据结构的有效性。对内存地址的验证是实际大小的缓冲大小,而不是最大缓冲大小。也就是说RTP程序分配了一个20个字节的缓冲区,那么系统调用函数应该验证这20个字节的内存,如果系统调用函数验证这个缓冲的最大长度(比如64个字节)可能会因为只有20个字节被分配而出错。解决这个问题的最好方法是通过参数告诉系统调用函数使用的缓冲空间的大小信息。

对动态系统调用函数get_fpga_registerSc的实现如下:

int get_fpga_registerSc(SysCallGetRegScArgs *pArgs)

{

if(pArgs->index > FPGA_AGREED_MAX_SIZE)

{

errno = EMSGSIZE;

return ERROR;

}

if (scMemValidate (pArgs->pV, sizeof(unsigned short), SC_PROT_WRITE) == ERROR)

{

errno = EINVAL;

return ERROR;

}

if(FPGA_GetValue(pArgs->index,pArgs->pV)==ERROR)

{

errno = EINVAL;

return ERROR;

}

return OK;

}

在执行时首先验证读取的FPGA地址是否已经超出了可访问范围,然后使用scMemValidate函数来验证第二个参数地址写入的有效性,最后读取FPGA指定地址的数据并返回。系统调用内存验证函数scMemValidate的第一个参数是验证的起始地址,第二个是验证地址的数据或结构的长度,第三个参数是地址空间访问模式,它分为读、写、以及线程安全的读写3种模式。这3种模式中的读、写可以组合使用,而线程安全的读写不可以组合使用。

在参数检查、内存验证、以及执行过程中如果出现了错误,需要根据具体情况对errno设置一个合适的异常值,然后返回错误-1(ERROR),如果正常则返回0(OK)。返回ERROR时内核的异常值将被拷贝到调用的任务进程的errno中;如果没有错误,这时只是将值拷贝到调用用户模式的任务。

第二步是添加自定义的系统调用函数表。注册VxWorks动态系统调用前必须在程序源码文件中包含自定义动态系统调用的处理程序表SYSCALL_RTN_TBL_ENTRY。表中每一项都由SYSCALL_DESC_ENTRY()宏关联一个系统调用,在VxWorks6.9版本的系统中此宏定义有三个参数(如果是其它版本,定义会略有不同),示例如下:

LOCAL _WRS_DATA_ALIGN_BYTES(16) SYSCALL_RTN_TBL_ENTRY pRtnTbl [NUM_RTN] = {

SYSCALL_DESC_ENTRY (set_fpga_registerSc, ″set_fpga_register″, 2),

SYSCALL_DESC_ENTRY (get_fpga_registerSc, ″get_fpga_register″, 2)

};

系统调用表的_WRS_DATA_ALIGN_BYTES(16)修饰是告诉编译、链接器数组按照16字节对齐以提高性能,这个修饰是可选的。NUM_RTN定义的值等于2,表示数组中的宏元素个数。SYSCALL_DESC_ENTRY宏的第1个参数是执行系统调用的函数指针,第2个是对应的系统调用名字,第3个参数系统调用参数个数。

第三步是注册系统调用。定义的处理程序表需要关联到合适的系统调用的组中,完成注册工作后才能使用。注册的每个系统调用都必须有唯一的系统调用编号。执行动态系统调用时,编号传递给内核,然后使用它来识别和执行匹配的系统调用处理程序。每个系统调用的编号是32 bit整数,由两部分组成的:一个是具有10 bit长度的系统调用的组数,另外一个是具有6 bit的系统调用程序编号,如图1所示。

图1 系统调用编号

据定义可知,此系统最多允许有1024个组编号,每个组最多有64个程序编号,总共可以容纳65 536个系统调用。根据系统定义,从第2组到第7组是自定义使用区间,其他组为系统使用或保留区间。

系统调用的注册需要使用syscallGroupRegister函数,此函数第1个参数是指定注册组编号,第2个参数是组名字,第3个参数是函数个数,第4个参数是处理程序表地址,第5个参数是强制覆盖开关。注册示例代码如下:

syscallGroupRegister (2, ″fpgaIF_ScGroup″, NUM_RTN, pRtnTbl, FALSE);

注册函数将pRtnTbl注册到了第2组自定义系统调用中,并为之取名为fpgaIF_ScGroup,注册组内有2(NUM_RTN)个系统调用函数,即set_fpga_registerSc和get_fpga_registerSc函数,程序编号在组内分别是0和1。系统调用的编号不是必须顺序的,在多个编号之间的定义是允许留空白。注册系统调用后的fpgaIF_ScGroup系统调用接口视图如图2所示。

图2 fpgaIF_ScGroup视图

2.3 检查系统调用

为了方便检查注册的自定义系统调用,需要配置操作系统包含INCLUDE_SHOW_ROUTINES组件,使用它检查自定义系统调用。编译并执行本文中实现的自定义动态系统调用注册程序及必要的组件。操作系统正确运行后,在调试终端使用syscallShow函数命令检查系统调用的详细信息。syscallShow函数有两个参数,第一个参数是组号,第二个参数是显示级别,显示级别0表示只输出组内信息,1表示输出全部信息。

图3 系统调用信息

图3中,在调试终端使用syscallShow查看自定义系统调用的第2组,以级别1显示信息。可以看到注册的系统调用的组名为fpgaIF_ScGroup,以及组内提供的系统调用的名字、地址、参数个数等信息与设计完全符合。

3 动态系统调用的使用

在RTP程序中执行系统调用,如果是静态的自定义系统调用可以直接通过函数名调用,使用上与普通函数调用没有任何区别;如果使用自定义动态系统调用,则需要统一使用syscall接口函数来访问。不管是什么样的系统调用,在调用时产生的模式切换是透明的。

动态系统调用接口syscall函数有9个参数,其中前8个参数是系统调用传入的参数,最后1个参数是系统调用的编号,编号的计算可以使用宏SYSCALL_NUMBER指定组号与程序号替代。程序中动态系统调用的应用封装如下:

#define SC_GET_REG SYSCALL_NUMBER (2, 1)

int RtpGetRegister(int i, int *pV)

{

if(syscall (i, pV,0,0,0,0,0,0, SC_GET_REG) == ERROR)

{

printf (″syscall() returned err. errno = %#x ”, errno);

return ERROR;

}

return OK;

}

根据定义可知,RtpGetRegister 是封装使用动态系统调用函数,其中的syscall执行了第2组中的第1个系统调用(此系统调用函数就是本文中定义的get_fpga_registerSc函数)并将参数i值和pV指针传入函数,执行函数时首先验证i,pV的有效性,然后读取FPGA的i地址的值放入pV指向的内存空间,最后返回执行结果。如果执行系统调用错误将返回-1(ERROR),此时可以从errno获取到系统调用返回的错误号并根据需要处理此错误;如果执行系统调用正常将会返回0(OK)。

4 结 语

在VxWorks系统中自定义系统调用可以让用户动态地应用程序获得更多的资源访问权限,这既可以保证应用程序的安全性又扩展了其应用范围。

为操作系统增加动态系统调用,可以使开发人员专注于如何设计、完善自定义的系统调用函数,无需对操作系统源码作修改。这样不会增加操作系统内核不稳定的风险,同时又减少了开发工作量,增强了自定义系统调用的可维护性。

[1]WindRiverSystemsInc.VxWorksApplicationProgrammer’sGuide6.9[Z]. 2013.

[2]WindRiverSystemsInc.VxWorksKernelProgrammer’sGuide6.9[Z].2013.

[3] 葛仁北. 系统调用与操作系统安全[J].计算机工程与应用,2002,38(19):97-99,128.

[4]BovetDP,CesatiM.深入理解Linux内核[J].陈莉君,张琼声,张宏伟,译.3版. 北京:中国电力出版社,2007:397-398.

[5] 谢锦滨, 王晨, 张申生. 系统调用重定向的研究与应用[J].计算机应用与软件,2006,23(3):4-6.

[6]WindRiverSystemsInc.VxWorksBenchmarkDataSheetforVxWorks6.9 1.2GHzP2020RDB[DB].2011.

[7] 胡盼盼.Linux下系统调用原理解析及增加系统调用的方法[J].计算机系统应用,2007,16(8):109-112.

[8] 罗忠海, 刘心松.UNIX环境中动态扩充系统调用的功能[J].计算机工程与设计,1999,20(1):1-6.

APPLICATION OF VXWORKS DYNAMICAL CUSTOM SYSTEM CALLS

Xie Jianwei Lai Qiancheng Cao Chengjun Zhang Xingwang

(ChinaNationalElectricApparatusResearchInstituteCo.,Ltd.Guangzhou510300,Guangdong,China)

The user mode application through system calls interface can access the kernel services and hardware resources. Custom system calls in VxWorks has a very important role in application development. Applications of user mode are able to access more kernel functions or other hardware resources through custom system calls, extending the scope of application. Thus, the concept of custom system calls in VxWorks and the system calls process are described, then the implementation process of the dynamical custom system calls is introduced step by step. Finally, the method of using dynamical custom system calls in application program is introduced.

VxWorks Custom System calls

2015-12-11。解建伟,工程师,主研领域:嵌入式软件与驱动开发。赖前程,工程师。曹成军,高工。张兴旺,教授级高工。

TP316.2

A

10.3969/j.issn.1000-386x.2017.02.043

猜你喜欢
调用内核动态
国内动态
多内核操作系统综述①
国内动态
国内动态
强化『高新』内核 打造农业『硅谷』
活化非遗文化 承启设计内核
核电项目物项调用管理的应用研究
动态
微软发布新Edge浏览器预览版下载换装Chrome内核
系统虚拟化环境下客户机系统调用信息捕获与分析①