本项目为Linux系统调用代理框架,将用户进程的系统调用交由系统调用代理守护程序代为执行。
方案主要包含的角色有用户程序(user)、lsca动态链接库(agent_lib.so)和lsca系统调用代理服务程序(daemon)。方案执行流程大体如下:
- 通过LD_PRELOAD引导用户程序使用LSCA动态链接库替代glibc标准库,使得用户程序将系统调用交由LSCA框架代理执行系统调用,避免陷入内核。
- 动态链接库根据用户程序调用的库函数将参数包装后发往指定的共享内存区(RingBuffer,环形队列缓冲区),等待被daemon代理执行。
- daemon检测到任务,调用相应的服务执行并将结果返回至缓冲区,供用户程序读取。
- 在agent_lib.so帮助下user从缓冲区得到返回值,调用代理流程结束。
1.在lsca项目顶层目录执行命令make即可在daemon和lib文件夹下分别生成daemon可执行程序和agent_lib.so动态链接库。 2.调用代理(以hello_world程序为例):在lsca目录(根目录)下首先执行./daemon log(nlog)开启代理服务,再执行命令LD_PRELOAD=./lib/agent_lib.so ./examples/hello_world 3.在顶层目录下执行命令 make clean可清楚源码之外的临时文件、可执行程序和动态链接库。 4.可以查看/video下演示视频了解使用方法。
daemon:守护进程
headers:各部分的头文件
lib:用于代理的动态链接库
socket_echo:项目要求的echo server和echo client程序
syscall_agents:守护进程代理系统调用所需对应的代理程序
syscall_wrapeprs:参数包装函数文件
tools:项目所需的工具程序,为守护进程与动态链接库共用
test:前期测试所使用的代码文件
examples:用于测试系统调用代理服务的例程
video:操作演示
框架的主要代码分布在daemon、lib、tools、syscall_wrappers和syscall_agents几个文件夹中,分别包含了运行框架、链接库、工具类、包装和代理函数。 若要改进框架则需要对这些文件夹下的代码都比较了解,但若只是添加新的系统调用,那么只需要在wrappers和agents下面添加相应文件,并在daemon中做极少的修改即可。 下面是各部分的详细说明。
这一部分负责拦截和代理用户程序的系统调用。文件的命名和文件中包含的功能参考Linux系统调用的实现。
- 新建文件 首先是新建文件,例如需要些write调用,参照Linux系统调用的实现习惯,将write、read等相关的系统调用放在一个叫做write_read的文件夹下面,并不是每个系统调用都单独一个文件。所以如果需要加的新的系统调用所属文件已经在项目中存在就无需再加。
- 新建函数 新建包装函数,函数的命名和实现都可以参照write和read。其中write要向daemon传送数据,read要接收daemon发来的数据(这里的数据指没有标准类型,用指针传递的内存块)。当然后续会改进包装方法,使得这个过程更简单,这是下一步的计划。
这一部分负责代理用户程序执行系统调用。从缓冲区中获取参数,运行相应的系统调用接口后将结果回传。
- 新建文件 类似上文描述的syscall_wrapers,例如需要些write调用,参照Linux系统调用的实现习惯,将write、read等相关的系统调用放在一个叫做write_read的文件夹下面,并不是每个系统调用都单独一个文件。所以如果需要加的新的系统调用所属文件已经在项目中存在就无需再加。
- 新建函数 新建代理函数,函数的命名和实现都可以参照write和read。其中write要向daemon传送数据,read要接收daemon发来的数据(这里的数据指没有标准类型,用指针传递的内存块)。
为了加速代理函数的查找过程,所有agent函数都会在daemon初始化时被记录在一个数组里,数组下标就是代理函数对应系统调用的调用号。
- 添加依赖 include“syscall_agents_xxx.h”
- 添加宏 位于lsca_syscall.h,其中编号和AArch64下Linux系统调用号一致。
- 添加初始化项 在框架开始运行后会执行init_syscall_agent_table()函数将所有系统agent函数放在数组里,以便后期根据系统调用号调用。因此新加的代理函数需要在此处进行初始化。
若新建了wrapper文件或agent文件则需要将同名.h文件放到headers文件夹下。若是在已有.c文件里添加函数则无需再添加头文件。
总结了一下后续需要改进和优化的地方,作为后期的目标。
在daemon接收到请求后,会根据请求的系统调用号调用函数数组中对应的代理函数,但是这个过程是单线程的,因此如果有一个像echo程序server端一样需要阻塞、等待的程序调用了daemon的服务,则会导致daemon无法为其他函数提供服务。因此需要在进入代理函数数组之前开辟新的线程或进程代理执行,方便其他进程请求daemon服务。
当前的缓冲区和序列化部分是一个简单的、方便上手的设计,但是很不精致,对于新加系统调用代理来说是不够友好。使用的便捷性方面,一个理想的序列化器应该是能够识别参数数量和类型,一个函数打包好所有需求。性能方面,由于系统调用很常用,因此打包过程重复次数很多,需要尽可能精简打包过程,保障速度。空间占用方面,尽量保障缓冲区是弹性的,随着代理系统调用的量对大小进行增减,可以牺牲部分空间占用换取性能,不用一直调整,但整体上看起来应该是均衡的。
目前的活动日志更多只是一个示意,直接写入到文件中的。但是对于系统调用这种高频操作,文件读写越少越好。我想或许应该先写入到一片内存缓冲区内,等待系统调用代理任务不繁忙时再持久化到硬盘。当然这个方案如果是在本地单机上运行可能还需要考虑突然断电和设备损坏的影响,但是云上的虚拟化环境中应该比较有保障。另外我觉得应该有对应这种需求的中间件存在了,后期调研一下。
值得优化的地方是输入输出的重定向,试了一堆方法目前还没有好用的。对于类似echo_server程序是没有影响的。但是对于需要输入的程序,例如client或者其他和daemon不运行在一个终端上的程序,daemon默认去自己的stdin下读数据,但是输入却是在被代理的程序的终端,因此会导致daemon空等。凑合的办法是在daemon的终端输入,但是不够体面,后面再探索一下重定向方法。
- 编译环境 本程序开发和编译环境为鲲鹏云(AArch64) Ubuntu 18.04.3 LTS (GNU/Linux 4.15.0-70-generic aarch64) make后可执行程序经测试在openEuler 下可正常使用
- 中间文件 考虑到编译环境问题,为了方便下载运行和尝试就没有清理中间文件和目标文件。若不需要,在顶层目录(lsca)下执行程序make clean即可清除全部中间文件和目标文件。
- 开发日志 由于目前只有一个人在做因此没有什么邮件列表。因此缺少一些记录开发过程中想法、遇到的困难及解决方法的文件,可能对后面的合作者会不太友好。因此上传记录这三个月开发过程的日志,未删减,因此有点像日记。开发日志链接