下面的分析以2.6.34为例。
内核部分
用户空间需要调用一些硬件体系相关的特殊指令陷入内核,触发内从异常中断向量表中调用系统调用例程。但是用户空间该怎么实现呢?
内核部分
ARM-Linux的系统调用列表定义在arch/arm/kernel/call.S中:
*
This file is included thrice in entry-common.S //
entry-common.S将会包含这个文件。根据偏移量,获取函数的指针
*/ /* 0 */ CALL( sys_restart_syscall ) CALL(sys_exit) CALL(sys_fork_wrapper) CALL(sys_read)
...................................
CALL(sys_pipe2) /* 360 */ CALL(sys_inotify_init1) CALL(sys_preadv) CALL(sys_pwritev) CALL(sys_rt_tgsigqueueinfo) CALL(sys_perf_event_open) /* 365 */ CALL(sys_recvmmsg) #ifndef syscalls_counted .equ syscalls_padding, ((NR_syscalls + 3) & ~3) - NR_syscalls #define syscalls_counted #endif .rept syscalls_padding CALL( sys_ni_syscall ) .endr |
可以看到2.6.34共支持367个系统调用,最后一个是一个“未实现”的系统调用。除了返回-ENOSYS不做其它工作。
所有系统调用的编号定义在arch/arm/include/asm/unistd.h中:
/*可以看到,使用不同的指令集和二进制接口,系统调号用的基数还不一样*/ #define __NR_OABI_SYSCALL_BASE 0x900000 #if defined(__thumb__) || defined(__ARM_EABI__) #define __NR_SYSCALL_BASE 0 #else #define __NR_SYSCALL_BASE __NR_OABI_SYSCALL_BASE #endif /* * This file contains the system call numbers. */ #define __NR_restart_syscall (__NR_SYSCALL_BASE+ 0) #define __NR_exit (__NR_SYSCALL_BASE+ 1) #define __NR_fork (__NR_SYSCALL_BASE+ 2) #define __NR_read (__NR_SYSCALL_BASE+ 3) #define __NR_write (__NR_SYSCALL_BASE+ 4) #define __NR_open (__NR_SYSCALL_BASE+ 5) ................. #define __NR_pipe2 (__NR_SYSCALL_BASE+359) #define __NR_inotify_init1 (__NR_SYSCALL_BASE+360) #define __NR_preadv (__NR_SYSCALL_BASE+361) #define __NR_pwritev (__NR_SYSCALL_BASE+362) #define __NR_rt_tgsigqueueinfo (__NR_SYSCALL_BASE+363) #define __NR_perf_event_open (__NR_SYSCALL_BASE+364) #define __NR_recvmmsg (__NR_SYSCALL_BASE+365) |
在上面的函数中增加的自己的系统调用之后,可以定义自己的系统调用函数了。比如在fs/open.c中是这么定义open这个系统调用的
SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, int, mode) { long ret; if (force_o_largefile()) flags |= O_LARGEFILE; ret = do_sys_open(AT_FDCWD, filename, flags, mode); /* avoid REGPARM breakage on x86: */ asmlinkage_protect(3, ret, filename, flags, mode); return ret; } |
定义一个系统调用要用到SYSCALL_DEFINEX(X代表参数个数)这个宏,这个宏的第一个参数是名字,后面的依次是参数类型和名字。
这个宏定义在include/linux/syscall.h中:
#define SYSCALL_DEFINE0(name) asmlinkage long sys_##name(void) #endif #define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__) #define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__) #define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__) #define SYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__) #define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__) #define SYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__) ........ /*所有的系统调用要在这里声明,比如open函数*/ asmlinkage long sys_open(const char __user *filename, int flags, int mode); ......... |
可以看到系统调用最多允许六个参数。
有了上面的知识,基本可以自己增加一个系统调用了,但是系统调用是怎么被调用的呢?
arch/arm/kernel/entry-armv.S
中的SWI异常向量有这么一句:
W(ldr) pc, .LCvswi + stubs_offset
在
arch/arm/kernel/entry-armv.S中
LCvswi被定义为:
.LCvswi:
.word vector_swi
.word vector_swi
vector_swi例程定义在
arch/arm/kernel/entry-common.S中。这个例程会保护现场,获取调用号,然后使用调用号作为索引查找系统调用表并调用相应的函数,最后通过
例程
ret_fast_syscall
来返回。
(不太懂ARM汇编就少说点)
用户空间部分
用户空间需要调用一些硬件体系相关的特殊指令陷入内核,触发内从异常中断向量表中调用系统调用例程。但是用户空间该怎么实现呢?
《Linux内核设计与实现》说的添加系统调用的方法过时了,起码在ubuntu11.04上不是那样的,那些_syscalln()函数怎么都找不到,只在/usr/include/unistd.h中找到下面一个接口:
extern long int syscall (long int __sysno, ...) __THROW;
真正的系统调用编号定义在/usr/include/asm/unistd_32.h。
写段程序验证一下,
#include <sys/stat.h> #include <asm/unistd.h> #include <unistd.h> int main(int argc, char *argv[]) { syscall(__NR_chmod, "/opt/test.c", S_IXUSR); return 0; } |
这段代码模拟了系统调用chmod,作用就是将/opt/test.c的权限改为只对所有者可执行,其它权限都去掉。
使用“man 2 chmod”可以查看chmod的man手册。
将上面的代码交叉编译之后复制到ARM开发板上,执行结果如下:
[root@EasyARM3250 opt]# ls test* test.c [root@EasyARM3250 opt]# ls -l test.c -rw------- 1 root root 164 Jan 1 01:11 test.c [root@EasyARM3250 opt]# ./test [root@EasyARM3250 opt]# ls -l test.c ---x------ 1 root root 164 Jan 1 01:11 test.c* [root@EasyARM3250 opt]# |
可见执行很成功。
另外,所有的系统调用都是经过C库间接调用的,《unix环境高级编程第三版》1.11小节“系统调用和库函数”中有句话很经典:Unix所使用的技术是为每个系统调用在标准C库中设置一个具有相同名字的函数。这一点在http://blog.csdn.net/hongjiujing/article/details/6831192中有所体现。
总结---如何添加arm linux的系统调用(2.6.34)
内核:
1.在内核源码中实现系统调用函数
可以参考fs/open.c中的open函数
SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, int, mode) ......
2.定义函数调用号
在arch/arm/include/asm/unistd.h中增加自己的函数调用编号
3.声明新增的系统调用函数
在include/linux/syscall.h中声明自己干刚定义的函数,如:
asmlinkage long sys_open(const char __user *filename,
int flags, int mode);
4.加入调用函数指针
列表
中
在
arch/arm/kernel/call.S最后面增加自己的函数
用户空间
#include <unistd.h>
#include <asm/unistd.h>
#define __NR_mycall xxxx //在用户空间定义自己的调用号
syscall(__NR_mycall, ....其它参数..)
============================================
作者:yuanlulu
http://blog.csdn.net/yuanlulu
版权没有,但是转载请保留此段声明
============================================