您的位置:首页 > 运维架构 > Linux

ARM Linux系统调用

2015-11-20 12:50 696 查看
首先要清楚系统调用不会导致进程切换。
下面的分析以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);

.........
可以看到系统调用最多允许六个参数。

有了上面的知识,基本可以自己增加一个系统调用了,但是系统调用是怎么被调用的呢?
下面的内容参考了这篇文章:http://blog.csdn.net/hongjiujing/article/details/6831192
arch/arm/kernel/entry-armv.S中的SWI异常向量有这么一句:
W(ldr) pc, .LCvswi + stubs_offset
在arch/arm/kernel/entry-armv.S中LCvswi被定义为:
.LCvswi:

.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最后面增加自己的函数
注意:arm64中内核的新增系统调用略有些差异:

1.在内核源码中实现系统调用函数
可以参考fs/open.c中的open函数
SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, int, mode) ......

2.声明新增的系统调用函数

include/linux/syscall.h中声明自己干刚定义的函数,如:
asmlinkage long sys_open(const char __user *filename,
int flags, int mode);
3.加入调用函数指针列表

在include/uapi/asm-generic/unistd.h中创建系统调用条目增加__NR_syscalls 数值
#define __NR_open 1024

__SYSCALL(__NR_open, sys_open)

用户空间
#include <unistd.h>
#include <asm/unistd.h>
#define __NR_mycall xxxx //在用户空间定义自己的调用号
syscall(__NR_mycall, ....其它参数..)

============================================

作者:yuanlulu

http://blog.csdn.net/yuanlulu

版权没有,但是转载请保留此段声明

============================================
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: