一、添加一个简单的系统调用的过程:
Linux 3.x版本的内核与2.x的内核添加系统调用的方式不一样,在此将操作流程记下。基于版本:ArchLinux, kernel version: 3.10,64位
1. 首先,定义系统调用服务例程。
可在多处添加系统调用服务例程的定义,只要最终配置并编译进内核镜像即可(不能作为模块)。为简便起见,在kernel/sys.c文件末尾添加服务例程sys_getjiffies定义为:
SYSCALL_DEFINE0(getjiffies) {// 获取内核jiffies变量的值,它记录了系统启动到目前的时钟节拍次数 return (long)get_jiffies_64(); }以上定义宏SYSCALL_DEFINE0实际上会扩展为:
asmlinkage long sys_getjiffies() { return (long)get_jiffies_64(); }
asmlinkage告诉编译器sys_getjiffies函数是通过栈接收参数的,尽管该函数并不接收任何参数。内核在sys.c文件中定义了SYSCALL_DEFINE0~SYSCALL_DEFINE6等七个提供便利的宏
2. 为系统调用服务例程在系统调用表中添加一个表项。
编译文件arch/x86/syscalls/syscall_64.tbl,在调用号小于512的部分追加一行:
314 64 getjiffies sys_getjiffies注意:① 上面的314是紧接着目前已定义系统调用313之后。
3. 在内核头文件中,添加服务例程的声明。
在include/linux/syscalls.h文件的最后,#endif之前加入系统调用服务例程sys_getjiffies()的声明:
asmlinkage long sys_getjiffies();注意: 本操作并非必须,通常将体系结构无关的系统调用的声明放置于此。此文件中的声明并不是给用户程序用的
二、在ArchLinux上操作流程
要测试添加的系统调用,涉及到下载内核源码,修改,编译,引导内核镜像等繁琐的过程。不过,这可以使用ArchLinux的ABS系统简化。具体操作流程如下
1. 准备ABS和编译环境:
#pacman -S abs base-devel
2. 获取内核包(core/linux)构建文件和内核源码,并修改PKGBUILD文件:
$ABSROOT=. abs core/linux ##下载到“当前”目录 $cd core/linux $makepkg -o ##仅下载源码和解压
修改PKGBUILD文件中pkgbase的值为linux-custom(修改为任意值均可),表示为自定义的内核
pkgbase=linux-custom注:若需要,还可以修改PKGBUILD文件的build()过程,开启内核参数配置。默认的配置采用Archlinux自带的config,会编译很多驱动,占用大量编译时间。
3. 按照第一部分的操作流程添加系统调用
4. 编译内核并打包:
$makepkg -e ##注意,一定要用-e参数,否则makepkg会将之间所做源码修改覆盖掉注:可在makepkg的配置文件/etc/makepkg.conf中,设置并行编译以加快编译速度。如#MAKEFLAGS="-j2"表示并行度为2,一般设置为CPU核心数目
5. 安装内核镜像:
$pacman -U linux-custom-3.10.7-1-x86_64.pkg.tar.xz若需要,如做内核模块开发,可以安装生成的内核头文件包:
$pacman -U linux-custom-headers-3.10.7-1-x86_64.pkg.tar.xz6. 添加引导grub引导项。
$grub-mkconfig -o /boot/grub/grub.cfg7. 重启,选择新增的引导项即可进入新的系统
8. 关于用户空间使用的内核API header:
上述操作流程并未将新添加的系统调用的接口及其调用号的定义宏安装到系统标准位置。若要安装,执行如下步骤即可:
$ make headers_check $ make INSTALL_HDR_PATH=dest headers_install #make headers_install会首先删除被安装目录,这不是我们想要的 $ cp -rv dest/include/* /usr/include注:① 也可通过ABS来做,对应包名为core/linux-api-headers。但是,该包是单独构建的,即会重新下载一份内核头文件与ArchLinux提供的补丁,然后构建包,用起来颇不方便,容易产生冲突。比如我目前使用的ArchLinux中,core/linux-api-headers与core/linux依赖的补丁包版本不一致,更是繁琐。② 若仅仅是添加系统调用,上述操作也可简化,只需在系统已安装的/usr/include/asm/unistd_64.h中追加一行即可:
#define __NR_getjiffies 314用户使用时,只需引入<sys/syscall.h>头文件即可。③ 下文并未重新安装用户空间使用内核API头文件
1. 方式一,直接syscall函数,不用任何封装函数:
#include <stdlib.h> #include <stdio.h> #define __NR_getjiffies 314 //sys_getjiffies()的系统调用号 int main(int argc, char **argv) { long jiffies = syscall(__NR_getjiffies); printf("jiffies = %ld\n", jiffies); exit(0); }注:① 关于syscall函数,可以使用man 2 syscall查看。该函数的声明在<unistd.h>中。② 若安装了内核API header(参见第二部分第8点),就可以不必手动定义__NR_getjiffies宏了,直接#include <sys/syscall.h>即可。
2. 方式二:使用_syscall0宏。
不知3.11版本为何没有提供_syscall0宏,搜遍整个源码树,都没有见到x86或x86_64的_syscall0宏定义,无意中搜到了x86架构的stub_64.h和stub_32.h文件,其中有内嵌汇编调用系统调用的stub_syscall0内敛函数,模拟_syscall0宏调用。
#include <stdlib.h> #include <stdio.h> #define __NR_getjiffies 314 // 系统调用号 /* 模拟_syscall0宏 */ #define _syscall0(NR) stub_syscall0(NR) /* 根据调用号调用系统调用,零个额外参数 */ #define __syscall_clobber "r11","rcx","memory" #define __syscall "syscall" static inline long stub_syscall0(long syscall) { long ret; __asm__ volatile (__syscall : "=a" (ret) : "0" (syscall) : __syscall_clobber ); return ret; } int main(int argc, char **argv) { long jiffies = _syscall0(__NR_getjiffies); printf("jiffies = %ld\n", jiffies); exit(0); }
3. 方式三:根据方式二,做一层简单的封装(未进行错误处理,也未集成进glibc中)
#include <stdlib.h> #include <stdio.h> #define __NR_getjiffies 314 //系统调用号 /* 根据调用号调用系统调用,零个额外参数 */ #define __syscall_clobber "r11","rcx","memory" #define __syscall "syscall" static inline long stub_syscall0(long syscall) { long ret; __asm__ volatile (__syscall : "=a" (ret) : "0" (syscall) : __syscall_clobber ); return ret; } long getjiffies() { return stub_syscall0(__NR_getjiffies); } int main(int argc, char **argv) { long jiffies = getjiffies(); printf("jiffies = %ld\n", jiffies); exit(0); }
四、扩展(以64系统为例)
1. 系统调用表的产生过程。
内核开发者是在syscall_64.tbl中声明系统调用号与服务例程的对应关系,以及其ABI,但系统调用表的真正定义是在arch/x86/kernel/syscall_64.c中。① arch/x86/kernel/syscall_64.c, arch/x86/kernel/syscall_32.c文件中存放了实际的系统调用表定义,以64位系统为例,其中有如下内容:
#include <asm/asm-offsets.h> #define __SYSCALL_COMMON(nr, sym, compat) __SYSCALL_64(nr, sym, compat) #ifdef CONFIG_X86_X32_ABI # define __SYSCALL_X32(nr, sym, compat) __SYSCALL_64(nr, sym, compat) #else # define __SYSCALL_X32(nr, sym, compat) /* nothing */ #endif #define __SYSCALL_64(nr, sym, compat) extern asmlinkage void sym(void) ; // 注意,是分号结尾 #include <asm/syscalls_64.h> // 引入系统调用服务例程的声明 #undef __SYSCALL_64 #define __SYSCALL_64(nr, sym, compat) [nr] = sym, //注意,是逗号结尾 typedef void (*sys_call_ptr_t)(void); extern void sys_ni_syscall(void); const sys_call_ptr_t sys_call_table[__NR_syscall_max+1] = { //系统调用表定义 /* * Smells like a compiler bug -- it doesn't work * when the & below is removed. */ [0 ... __NR_syscall_max] = &sys_ni_syscall, #include <asm/syscalls_64.h> // 系统调用服务例程地址,对应arch/x86/include/generated/asm/syscalls_64.h文件 };② arch/x86/syscalls目录中的syscall_64.tbl、syscall_32.tbl文件是系统调用表声明。syscalltbl.sh脚本负责生产syscalls_XX.h文件,由Makefile负责驱动
... ... __SYSCALL_COMMON(312, sys_kcmp, sys_kcmp) __SYSCALL_COMMON(313, sys_finit_module, sys_finit_module) __SYSCALL_64(314, sys_getjiffies, sys_getjiffies) // 自定义的系统调用 __SYSCALL_X32(512, compat_sys_rt_sigaction, compat_sys_rt_sigaction) ...
2. 系统调用号声明头文件的生成(#define __NR_xxx之类的信息)。
类似于系统调用表的产生,arch/x86/syscalls/syscallhdr.sh脚本负责generated/uapi/asm/unistd_32.h, generated/uapi/asm/unistd_64.h文件,unistd_XX.h文件又被间接include到asm/unistd.h中,后者最终被include用户空间使用的<sys/syscall.h>文件中(安装之后)。生成的generated/uapi/asm/unistd_64.h部分内容如下
... ... #define __NR_finit_module 313 #define __NR_getjiffies 314 #endif /* _ASM_X86_UNISTD_64_H */注意,这里的unistd.h文件与用户空间使用的<unistd.h>文件没有任何关系,后者声明了系统调用包装函数,包括syscall函数等