arm系统调用

一 系统调用用户接口

1 int open(const char *filename, int oflag,mode_t mode);
打开file,方式为oflag("O_RDONLY"即0,"O_WRONLY"即1,"O_RDWR"即2),mode是仅当创建新文件才使用,一般可缺省不要。(返回值:成功fd;失败-1)
2 int close(int file);
关闭一个打开的文件file。(返回值:成功0;失败-1)
3 ssize_t read(int fd,void * buf ,size_t count);
参数fd所指的文件传送count个字节到buf指针所指的内存中。文件读写位置会随读取到的字节移动。(返回值:成功返回实际读到的字节数;失败-1)
4 ssize_t write (int fd,const void * buf,size_t count);
把参数buf所指的内存写入count个字节到参数fd所指的文件内。文件读写位置也会随之移动。(返回值:成功返回实际写入的字节数;失败-1)

 

二 陷入内核

关于x86的陷入方式,一说再说,抄来一段。int $0x80指令的目的是产生一个编号为0x80的编程异常,这个编程异常对应的是中断描述符表IDT中的第128项——也就是对应的系统门描述符。门描述符中含有一个预设的内核空间地址,它指向了系统调用处理程序:system_call()(别和系统调用服务程序混淆,这个程序在entry.S文件中用汇编语言编写)。system_call()检查系统调用号,该号码告诉内核进程请求哪种服务。内核进程查看系统调用表sys_call_table找到所调用的内核函数入口地址。arm也类似,是通过软中断SWI陷入svc模式的。下面分析具体过程,前提是有一份glibc的代码。
io/fcntl.h中有open()的定义,这个open()就是应用程序的open啊。
extern int open (__const char *__file, int __oflag, ...) __nonnull ((1));
intl/loadmsgcat.c中继续找,原来是个宏;
  # define open(name, flags) open_not_cancel_2 (name, flags)
sysdeps/unix/sysv/linux/not-cancel.h
  #define open_not_cancel_2(name, flags) \
  INLINE_SYSCALL (open, 2, (const char *) (name), (flags))
sysdeps/unix/sysv/linux/arm/sysdep.h找到这里看看INLINE_SYSCALL宏是个什么东西。
/* not __ASSEMBLER__ */

/* Define a macro which expands into the inline wrapper code for a system
   call.  */
#undef INLINE_SYSCALL
#define INLINE_SYSCALL(name, nr, args...)				\
  ({ unsigned int _sys_result = INTERNAL_SYSCALL (name, , nr, args);	\
     if (__builtin_expect (INTERNAL_SYSCALL_ERROR_P (_sys_result, ), 0))	\
       {								\
	 __set_errno (INTERNAL_SYSCALL_ERRNO (_sys_result, ));		\
	 _sys_result = (unsigned int) -1;				\
       }								\
     (int) _sys_result; })

#undef INTERNAL_SYSCALL
#define INTERNAL_SYSCALL(name, err, nr, args...)		\
	INTERNAL_SYSCALL_RAW(SYS_ify(name), err, nr, args)
#undef SYS_ify
#define SYS_ify(syscall_name)	(__NR_##syscall_name)
#if defined(__thumb__)
/* We can not expose the use of r7 to the compiler.  GCC (as
   of 4.5) uses r7 as the hard frame pointer for Thumb - although
   for Thumb-2 it isn't obviously a better choice than r11.
   And GCC does not support asms that conflict with the frame
   pointer.

   This would be easier if syscall numbers never exceeded 255,
   but they do.  For the moment the LOAD_ARGS_7 is sacrificed.
   We can't use push/pop inside the asm because that breaks
   unwinding (i.e. thread cancellation) for this frame.  We can't
   locally save and restore r7, because we do not know if this
   function uses r7 or if it is our caller's r7; if it is our caller's,
   then unwinding will fail higher up the stack.  So we move the
   syscall out of line and provide its own unwind information.  */
# undef INTERNAL_SYSCALL_RAW
# define INTERNAL_SYSCALL_RAW(name, err, nr, args...)		\
  ({								\
      register int _a1 asm ("a1");				\
      int _nametmp = name;					\
      LOAD_ARGS_##nr (args)					\
      register int _name asm ("ip") = _nametmp;			\
      asm volatile ("bl      __libc_do_syscall"			\
                    : "=r" (_a1)				\
                    : "r" (_name) ASM_ARGS_##nr			\
                    : "memory", "lr");				\
      _a1; })
#else /* ARM */
# undef INTERNAL_SYSCALL_RAW
# define INTERNAL_SYSCALL_RAW(name, err, nr, args...)		\
  ({								\
       register int _a1 asm ("r0"), _nr asm ("r7");		\
       LOAD_ARGS_##nr (args)					\
       _nr = name;						\
       asm volatile ("swi	0x0	@ syscall " #name	\
		     : "=r" (_a1)				\
		     : "r" (_nr) ASM_ARGS_##nr			\
		     : "memory");				\
       _a1; })
#endif
#if defined(__thumb__)
/* We can not expose the use of r7 to the compiler.  GCC (as
   of 4.5) uses r7 as the hard frame pointer for Thumb - although
   for Thumb-2 it isn't obviously a better choice than r11.
   And GCC does not support asms that conflict with the frame
   pointer.

   This would be easier if syscall numbers never exceeded 255,
   but they do.  For the moment the LOAD_ARGS_7 is sacrificed.
   We can't use push/pop inside the asm because that breaks
   unwinding (i.e. thread cancellation) for this frame.  We can't
   locally save and restore r7, because we do not know if this
   function uses r7 or if it is our caller's r7; if it is our caller's,
   then unwinding will fail higher up the stack.  So we move the
   syscall out of line and provide its own unwind information.  */
# undef INTERNAL_SYSCALL_RAW
# define INTERNAL_SYSCALL_RAW(name, err, nr, args...)		\
  ({								\
      register int _a1 asm ("a1");				\
      int _nametmp = name;					\
      LOAD_ARGS_##nr (args)					\
      register int _name asm ("ip") = _nametmp;			\
      asm volatile ("bl      __libc_do_syscall"			\
                    : "=r" (_a1)				\
                    : "r" (_name) ASM_ARGS_##nr			\
                    : "memory", "lr");				\
      _a1; })
#else /* ARM */
# undef INTERNAL_SYSCALL_RAW
# define INTERNAL_SYSCALL_RAW(name, err, nr, args...)		\
  ({								\
       register int _a1 asm ("r0"), _nr asm ("r7");		\
       LOAD_ARGS_##nr (args)					\
       _nr = name;						\
       asm volatile ("swi	0x0	@ syscall " #name	\
		     : "=r" (_a1)				\
		     : "r" (_nr) ASM_ARGS_##nr			\
		     : "memory");				\
       _a1; })
#endif
如果是thumb,需要执行"bl __libc_do_syscall"
sysdeps/unix/sysv/linux/arm/sysdep.S中
#if defined(__thumb__)
	.thumb
	.syntax unified
	.hidden __libc_do_syscall

#undef CALL_MCOUNT
#define CALL_MCOUNT

ENTRY (__libc_do_syscall)
	.fnstart
	push	{r7, lr}
	.save	{r7, lr}
	cfi_adjust_cfa_offset (8)
	cfi_rel_offset (r7, 0)
	cfi_rel_offset (lr, 4)
	mov	r7, ip
	swi	0x0
	pop	{r7, pc}
	.fnend
END (__libc_do_syscall)

#endif /* __thumb__ */
看到swi 0x0,就看到了希望;swi是一条软中断指令,swi执行后处理器就会从usr模式切换到超级用户svc模式,svc也是一种异常模式。swi陷入svc模式的同时,会把CPSR的内容保存到SPSR_svc中,把返回地址lr保存在R14_svc中,这也是svc被称为操作系统保护模式的原因。需要注意的是,如果执行swi后再执行swi,这两个寄存器会被重新改写;所以需要先把SPSR_svc和R14_svc的内容压入栈中。现在已经陷入svc了,也就是说会跳转到异常向量表的异常入口。swi 命令状态的异常入口在哪里?还记得异常向量表吗?
	.globl	__vectors_start
__vectors_start:
 ARM(	swi	SYS_ERROR0	)
 THUMB(	svc	#0		)
 THUMB(	nop			)
	W(b)	vector_und + stubs_offset
	W(ldr)	pc, .LCvswi + stubs_offset
	W(b)	vector_pabt + stubs_offset
	W(b)	vector_dabt + stubs_offset
	W(b)	vector_addrexcptn + stubs_offset
	W(b)	vector_irq + stubs_offset
	W(b)	vector_fiq + stubs_offset

	.globl	__vectors_end
__vectors_end:
这是异常向量表,swi对应的异常向量是这样跳到的W(ldr) pc, .LCvswi + stubs_offset;还记得这是跳到哪里去吗?
	.globl	__stubs_start
__stubs_start:
......
.LCvswi:
	.word	vector_swi

	.globl	__stubs_end
__stubs_end:
这里就有软中断的入口了,在哪里?.word相当于定义了一个数据变量;整条语句的意思就是把vector_swi标号的地址放在.LCvswi中;那么W(ldr) pc, .LCvswi + stubs_offset这条指令也就是把pc指向vector_swi标号处,显然这里就是软中断的入口了。 vector和vector_irq定义在arch/arm/kernel/entry-armv.S,vector_swi定义在arch/arm/kernel/entry-common.S
 

三 系统调用号的传递

据说每个系统调用都有一个编号,这些编号组成了一个系统调用表,就是根据这个编号在表中查到对应的call函数;那么,也就是说这个号,应该是从应用程序那传过来的。很奇怪不是吗?swi 0x0就陷入内核了,系统调用号(或者叫软中断号)在哪里呢?swi指令本身就是答案。SWI的指令格式:
SWI{cond} immed_24
Cond域:是可选的条件码。
immed_24域:范围从 0 到 224-1 的表达式;用户程序可以使用该常数来进入不同的处理流程。
获取系统调用号有两种方式:
  • 1 从immed_24操作数中获取
此时的指令形式为:swi 0x5;
获取指令有两条:
ldr r0, [lr,#-4];将链接寄存器lr的内容减去4的值做为一个地址,然后把该地址的内容装进r0。
bic r0, r0, #0xff000000;将r0内容的高8为清0,也就是取低24bit。
这样就取出来了,还是很奇怪,lr-4是个什么地址?其实是swi指令的地址;其中的内容就是该指令的机器码。抄来一段具体解释:
寄存器R14(LR寄存器)有两种特殊功能:
(1)在任何一种处理器模式下,该模式对应的R14寄存器用来保存子程序的返回地址。当执行BL或BLX指令进行子程序调用时,子程序的返回地址被放置在R14中。这样,只要把R14内容拷贝到PC中,就实现了子程序的返回。
(2)当某异常发生时,相应异常模式下的R14被设置成异常返回的地址(对于某些异常,可能是一个偏移量,一个较小的常量)。异常返回类似于子程序返回,但有小小的不同。
所谓的子程序的返回地址,实际就是调用指令的下一条指令的地址,也就是BL或BLX指令的下一条指令的地址。所谓的异常的返回的地址,就是异常发生前,CPU执行的最后一条指令的下一条指令的地址。所以,在SWI异常处理子程序中执行 LDR R0,[LR,#-4]语句,实际就是把产生本次SWI异常的SWI指令的内容(如:SWI 0x5)装进R0寄存器。又因为SWI指令的低24位保存了指令的操作数(如:0x5),所以再执行BIC R0, R0, #0xFF000000语句,就可以获得immed_24操作数的实际内容。
  • 2 使用参数寄存器传递
这就是swi 0的用法了,绕开immed_24操作数,使用参数寄存器r0~r4来保存。
此时的指令形式为:
mov r0, 0x5
swi 0x0
用的时候直接读r0就行啦。
知道怎么传就好办了;再读懂怎么传之前,要学会看C内嵌的汇编。看看别人怎么说:
带有C/C++表达式的内联汇编格式为:
__asm__ __volatile__("Instruction List" : Output : Input : Clobber/Modify);
其中每项的概念及功能用法描述如下:
1、 __asm__
__asm__是GCC 关键字asm 的宏定义:
#define __asm__ asm
__asm__或asm 用来声明一个内联汇编表达式,所以任何一个内联汇编表达式都是以它开头的,是必不可少的。
2、Instruction List
Instruction List 是汇编指令序列。它可以是空的,比如:__asm__ __volatile__(""); 或 __asm__ ("");都是完全合法的内联汇编表达式,只不过这两条语句没有什么意义。但并非所有Instruction List 为空的内联汇编表达式都是没有意义的,比如:__asm__ ("":::"memory");
就非常有意义,它向GCC 声明:“内存作了改动”,GCC在编译的时候,会将此因素考虑进去。当在"Instruction List"中有多条指令的时候,可以在一对引号中列出全部指令,也可以将一条或几条指令放在一对引号中,所有指令放在多对引号中。如果是前者,可以将每一条指令放在一行,如果要将多条指令放在一行,则必须用分号(;)或换行符(\n)将它们分开。综上述:(1)每条指令都必须被双引号括起来 (2)两条指令必须用换行或分号分开。
比如:"swi 0x0 @ syscall " #name
3. __volatile__
__volatile__是GCC关键字volatile 的宏定义
#define __volatile__ volatile
__volatile__或volatile 是可选的。如果用了它,则是向GCC声明不允许对该内联汇编优化,否则当 使用了优化选项(-O)进行编译时,GCC将会根据自己的判断决定是否将这个内联汇编表达式中的指令优化掉。
4、 Output
Output用来指定当前内联汇编语句的输出;规定对输出变量,即目标操作数如何结合的约束条件。每个这样的条件称为一个“约束条件”。必要时输出部可以有多个约束条件,互相以逗号分隔。每个输出约束以“=”号开头,然后是一个字母表示对操作数类型的说明,然后是关于变量结合的约束。
比如:: "=r" (_a1)
5、 Input
Input域的内容用来指定当前内联汇编语句的输入,格式为形如“constraint”(variable)的列表(逗号分隔)。
比如:: "r" (_nr) ASM_ARGS_##nr
表示约束条件的字母有很多,主要有:
"m"、"v"和"o" ---- 表示内存单元
"r" ---- 表示任何寄存器
"q" ---- 表示寄存器eax,ebx,ecx,edx之一
"i"和"h" ---- 表示直接操作数
"E"和"F" ---- 表示浮点数
"g" ---- 表示任意
"a","b", "c" "d" ---- 分别表示要使用寄存器eax ebx ecx和edx
"S"和"D" ---- 分别表示要使用寄存器esi和edi
"I" ---- 表示常数(0至31)
6、Clobber/Modify
有时候,你想通知GCC当前内联汇编语句可能会对某些寄存器或内存进行修改,希望GCC在编译时能够将这一点考虑进去。那么你就可以在Clobber/Modify域声明这些寄存器或内存。这种情况一般发生在一个寄存器出现在"Instruction List",但却不是由Input/Output操作表达式所指定的,也不是在一些Input/Output操作表达式使用"r"约束时由GCC为其选择的,同时此寄存器被"Instruction List"中的指令修改,而这个寄存器只是供当前内联汇编临时使用的情况。例如:__asm__ ("mov R0, #0x34" : : : "R0");
寄存器R0出现在"Instruction List中",并且被mov指令修改,但却未被任何Input/Output操作表达式指定,所以你需要在Clobber/Modify域指定"R0",以让GCC知道这一点。
因为你在Input/Output操作表达式所指定的寄存器,或当你为一些Input/Output操作表达式使用"r"约束,让GCC为你选择一个寄存器时,GCC对这些寄存器是非常清楚的——它知道这些寄存器是被修改的,你根本不需要在Clobber/Modify域再声明它们。但除此之外,GCC对剩下的寄存器中哪些会被当前的内联汇编修改一无所知。所以如果你真的在当前内联汇编指令中修改了它们,那么就最好在Clobber/Modify中声明它们,让GCC针对这些寄存器做相应的处理。否则有可能会造成寄存器的不一致,从而造成程序执行错误。
如果一个内联汇编语句的Clobber/Modify域存在"memory",那么GCC会保证在此内联汇编之前,如果某个内存的内容被装入了寄存器,那么在这个内联汇编之后,如果需要使用这个内存处的内容,就会直接到这个内存处重新读取,而不是使用被存放在寄存器中的拷贝。因为这个时候寄存器中的拷贝已经很可能和内存处的内容不一致了。
这只是使用"memory"时,GCC会保证做到的一点,但这并不是全部。因为使用"memory"是向GCC声明内存发生了变化,而内存发生变化带来的影响并不止这一点。例如:
int main(int __argc, char* __argv[]) { int* __p = (int*)__argc; (*__p) = 9999; __asm__("":::"memory"); if((*__p) == 9999) return 5; return (*__p); }
本例中,如果没有那条内联汇编语句,那个if语句的判断条件就完全是一句废话。GCC在优化时会意识到这一点,而直接只生成return 5的汇编代码,而不会再生成if语句的相关代码,而不会生成return (*__p)的相关代码。但你加上了这条内联汇编语句,它除了声明内存变化之外,什么都没有做。但GCC此时就不能简单的认为它不需要判断都知道 (*__p)一定与9999相等,它只有老老实实生成这条if语句的汇编代码,一起相关的两个return语句相关代码。
另外在linux内核中内存屏障也是基于它实现的include/asm/system.h中
# define barrier() _asm__volatile_("": : :"memory")
接下来就分析代码吧!
# define INTERNAL_SYSCALL_RAW(name, err, nr, args...)		\
  ({								\
       register int _a1 asm ("r0"), _nr asm ("r7");		\
       LOAD_ARGS_##nr (args)					\
       _nr = name;						\
       asm volatile ("swi	0x0	@ syscall " #name	\
		     : "=r" (_a1)				\
		     : "r" (_nr) ASM_ARGS_##nr			\
		     : "memory");				\
       _a1; })
第1行声明了两个局部寄存器变量,该语句的意思是r0是变量_a1希望使用的寄存器。
第2行和第6行出现了两个宏,已知前面传过来的nr是2。
#define LOAD_ARGS_0()
#define ASM_ARGS_0
#define LOAD_ARGS_1(a1)				\
  int _a1tmp = (int) (a1);			\
  LOAD_ARGS_0 ()				\
  _a1 = _a1tmp;
#define ASM_ARGS_1	ASM_ARGS_0, "r" (_a1)
#define LOAD_ARGS_2(a1, a2)			\
  int _a2tmp = (int) (a2);			\
  LOAD_ARGS_1 (a1)				\
  register int _a2 asm ("a2") = _a2tmp;
#define ASM_ARGS_2	ASM_ARGS_1, "r" (_a2)
这是一个装载参数的过程,把参数依次装载到r0~r4。看到LOAD_ARGS_1没有定义 register int,就直接_a1 = _a1tmp;说明使用该宏之前要先定义好_a1。按照预想这段code执行完,会将args(2个参数)中的a1装到r0里,应该是对应file地址和oflag;a2装到gcc给选择的通用寄存器里。
第3行把name装到r7中,name是什么?说到这里还要重复贴一下代码:
#undef INTERNAL_SYSCALL
#define INTERNAL_SYSCALL(name, err, nr, args...)		\
	INTERNAL_SYSCALL_RAW(SYS_ify(name), err, nr, args)

#undef SYS_ify
#define SYS_ify(syscall_name)	(__NR_##syscall_name)
则SYS_ify(open) 等价于__NR_open,这是内核里定义的了,其实是系统调用号,或者说是软中断号。arch/arm/include/uapi/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
#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_close			(__NR_SYSCALL_BASE+  6)
第4行就是我们的跳转指令了,起汇编形式为swi 0x0 @ syscall __NR_open
第5行告诉gcc编译器为_a1选择一个通用寄存器(r0~r4)来保存_a1的值,按照期望应该选r0。有”=”表示输出,说明在内嵌汇编中只能写这个寄存器。
第6行等价于: "r" (_nr) , "r" (_a1), "r" (_a2),告诉gcc编译器为_nr、_a1、_a2选择通用寄存器来保存其值,这三个输入变量要通过选择的通用寄存器输入到当前内联汇编中,按照期望_nr应该选r7。无”=”表示输入,说明在内嵌汇编中只能读这些寄存器。
第7行声明内存有改动。
第8行表示return _a1,open成功最后返回的是文件描述符fd,所以这个_a1,就是要保存到r0里的_a1在返回之前还会被修改。
总之跟完这段code,知道了系统调用号是__NR_open被存在r7中。

 

四 如何跳到C

接收到系统调用号就知道该找哪个函数执行了;这就是软中断异常要做的事情了。看代码之前还要说说几个概念和两个宏:
ABI:application binary interfaceOABI:old application binary interfaceEABI:extended application binary interface
CONFIG_AEABI:表示内核为EABI。
CONFIG_OABI_COMPAT:表示兼容OABI。
make menuconfig Kernel Features ---> [*] Use the ARM EABI to compile the kernel [*] Allow old ABI binaries to run with this kernel (EXPERIMENTAL)
不选或只选CONFIG_AEABI,应用程序都会调用sys_call_table;只选CONFIG_OABI_COMPAT是会出问题的。都选,应用程序使用OABI时调用sys_oabi_call_table,使用EABI时调用sys_call_table。
/*=============================================================================
 * SWI handler
 *-----------------------------------------------------------------------------
 */

	.align	5
ENTRY(vector_swi)
	sub	sp, sp, #S_FRAME_SIZE
	stmia	sp, {r0 - r12}			@ Calling r0 - r12
 ARM(	add	r8, sp, #S_PC		)
 ARM(	stmdb	r8, {sp, lr}^		)	@ Calling sp, lr
 THUMB(	mov	r8, sp			)
 THUMB(	store_user_sp_lr r8, r10, S_SP	)	@ calling sp, lr
	mrs	r8, spsr			@ called from non-FIQ mode, so ok.
	str	lr, [sp, #S_PC]			@ Save calling PC
	str	r8, [sp, #S_PSR]		@ Save CPSR
	str	r0, [sp, #S_OLD_R0]		@ Save OLD_R0
	zero_fp

	/*
	 * Get the system call number.
	 */

#if defined(CONFIG_OABI_COMPAT)

	/*
	 * If we have CONFIG_OABI_COMPAT then we need to look at the swi
	 * value to determine if it is an EABI or an old ABI call.
	 */
#ifdef CONFIG_ARM_THUMB
	tst	r8, #PSR_T_BIT                  @tst位与结果为0,不是thumb,设置eq;否则设置ne
	movne	r10, #0				@ no thumb OABI emulation
	ldreq	r10, [lr, #-4]			@ get SWI instruction
#else
	ldr	r10, [lr, #-4]			@ get SWI instruction
#endif
#ifdef CONFIG_CPU_ENDIAN_BE8
	rev	r10, r10			@ little endian instruction
#endif

#elif defined(CONFIG_AEABI)

	/*
	 * Pure EABI user space always put syscall number into scno (r7).
	 */
#elif defined(CONFIG_ARM_THUMB)
	/* Legacy ABI only, possibly thumb mode. */
	tst	r8, #PSR_T_BIT			@ this is SPSR from save_user_regs
	addne	scno, r7, #__NR_SYSCALL_BASE	@ put OS number in
	ldreq	scno, [lr, #-4]

#else
	/* Legacy ABI only. */
	ldr	scno, [lr, #-4]			@ get SWI instruction
#endif

#ifdef CONFIG_ALIGNMENT_TRAP
	ldr	ip, __cr_alignment
	ldr	ip, [ip]
	mcr	p15, 0, ip, c1, c0		@ update control register
#endif
	enable_irq

	get_thread_info tsk
	adr	tbl, sys_call_table		@ load syscall table pointer

#if defined(CONFIG_OABI_COMPAT)
	/*
	 * If the swi argument is zero, this is an EABI call and we do nothing.
	 *
	 * If this is an old ABI call, get the syscall number into scno and
	 * get the old ABI syscall table address.
	 */
	bics	r10, r10, #0xff000000
	eorne	scno, r10, #__NR_OABI_SYSCALL_BASE
	ldrne	tbl, =sys_oabi_call_table
#elif !defined(CONFIG_AEABI)
	bic	scno, scno, #0xff000000		@ mask off SWI op-code
	eor	scno, scno, #__NR_SYSCALL_BASE	@ check OS number
#endif

local_restart:
	ldr	r10, [tsk, #TI_FLAGS]		@ check for syscall tracing
	stmdb	sp!, {r4, r5}			@ push fifth and sixth args

	tst	r10, #_TIF_SYSCALL_WORK		@ are we tracing syscalls?
	bne	__sys_trace

	cmp	scno, #NR_syscalls		@ check upper syscall limit
	adr	lr, BSYM(ret_fast_syscall)	@ return address
	ldrcc	pc, [tbl, scno, lsl #2]		@ call sys_* routine

	add	r1, sp, #S_OFF
2:	mov	why, #0				@ no longer a real syscall
	cmp	scno, #(__ARM_NR_BASE - __NR_SYSCALL_BASE)
	eor	r0, scno, #__NR_SYSCALL_BASE	@ put OS number back
	bcs	arm_syscall	
	b	sys_ni_syscall			@ not private func
ENDPROC(vector_swi)
先看看怎么Get the system call number,前面说的两个宏出现了,如果定义了CONFIG_OABI_COMPAT,那么我们需要看一下swi的值,来确定是EABI形式的call,还是ABI形式的call。写写code执行的细节吧。
1 定义CONFIG_OABI_COMPAT,无论CONFIG_AEABI有没有定义
(1)定义CONFIG_ARM_THUMB,前面mrs r8, spsr,r8存储的是程序状态,先tst r8是否为thumb,即用户空间是否使用了thumb指令集;如果是ne置位,只会执行movne r10, #0;因为thumb不支持OABI emulation;如果不是就取出SWI instruction到r10。
(2)没有定义CONFIG_ARM_THUMB,那就是arm了,直接取出SWI instruction到r10。
无论thumb,还是arm,这里都会取出SWI instruction,可猜测系统调用号就存在了低24bit。
2 定义CONFIG_AEABI,无论有没有定义CONFIG_OABI_COMPAT
理论上EABI用户空间总是把系统调用号放在scno r7中,arch/arm/kernel/entry-header.S中有
scno .req r7 @ syscall number
tbl .req r8 @ syscall table pointer
why .req r8 @ Linux syscall (!= 0)
tsk .req r9 @ current thread_info
这些初始化定义,说明scno就是r7,所有什么也不用做了。
3 两者都没有定义
(1)定义CONFIG_ARM_THUMB,如果tst r8是thumb,从r7中去scno;如不是依然是取出SWI instruction到r10。
(2)没有定义CONFIG_ARM_THUMB,取出SWI instruction到r10。
一般定义CONFIG_ARM_THUMB=y是说kernel支持用户空间运行thumb指令集,只有定义了该宏才会判断用户空间是什么指令,不定义都默认是arm指令。
总结:1 只要定义了CONFIG_AEABI,系统调用号就在r7里。
2 如果只定义CONFIG_OABI_COMPAT,thumb指令没有意义,arm指令系统调用号在swi指令里。
3 如果都没有定义,thumb指令时调用号在r7中,arm指令在swi指令里。
adr	tbl, sys_call_table
	.type	sys_call_table, #object
ENTRY(sys_call_table)
#include "calls.S"//将calls.S中的内容顺序链接到这里
#undef ABI
#undef OBSOLETE
arch/arm/kernel/calls.S声明了系统调用函数:
/* 0 */		CALL(sys_restart_syscall)
		CALL(sys_exit)
		CALL(sys_fork)
		CALL(sys_read)
		CALL(sys_write)
/* 5 */		CALL(sys_open)
		CALL(sys_close)
		CALL(sys_ni_syscall)		/* was sys_waitpid */
		CALL(sys_creat)
		CALL(sys_link)
关于CALL宏一会再说。
如果swi的参数是0,是EABI call就什么也不做,如果是OABI就获取调用号和OABI的系统调用表地址,这就是前面提到的那几种情况。只定义CONFIG_OABI_COMPAT不存在;不定义或者只定义CONFIG_AEABI都用sys_call_table,两个都定义,取决于用户空间。一般两个都定义了,那就是什么也不做,走下来了。
cmp	scno, #NR_syscalls	 @ check upper syscall limit
检查系统调用号有没有越界。NR_syscalls是如何算出来的?和CALL(x)的两次宏定义有关。
第一次:.equ NR_syscalls,0      @把符号NR_syscalls定义为0;不分配内存
#define CALL(x) .equ NR_syscalls,NR_syscalls+1@把符号NR_syscalls定义NR_syscalls+1
#include "calls.S" //calls.S中每调用一次CALL() NR_syscalls就会加1;最终会计算出系统调用的个数;
/*
 * Ensure that the system call table is equal to __NR_syscalls,
 * which is the value the rest of the system sees
 */
.ifne NR_syscalls - __NR_syscalls
.error "__NR_syscalls is not equal to the size of the syscall table"
.endif
第二次:
#undef CALL
#define CALL(x) .long x
这里是为了计算有多少个NR_syscalls,后面重新定义CALL 为.long x;在系统调用表中每添加一个系统调用,NR_syscalls就自动增加一。在这个地方先求出NR_syscalls,然后重新定义CALL(x)宏,这样也可以不影响文件后面系统调用表的建立。
ldrcc	 pc, [tbl, scno, lsl #2]		@ call sys_* routine
这里就要调转到call函数了,scno为什么左移2位?地址是4byte的,表中的定义是long的,1号在4byte处,2号8byte处,相应地5号在5*4byte处。tbl就是sys_call_table,此时pc = sys_call_table+scno*4;就是CALL(sys_open) == sys_open。sys_open的实现在fs/open.c中,是include/linux/syscalls.h中一个宏定义
SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)

五 通用系统调用接口

据说2.6.18时,还是定义一堆宏,来形容各种参数的系统调用;到了2.6.19,形势大变,一个函数就搞定了,变参真方便。
int syscall(int number, ...);
syscall的实现是glibc里内嵌的一段汇编。
ENTRY (syscall)
	mov	ip, sp
	stmfd	sp!, {r4, r5, r6, r7}
	cfi_adjust_cfa_offset (16)
	cfi_rel_offset (r4, 0)
	cfi_rel_offset (r5, 4)
	cfi_rel_offset (r6, 8)
	cfi_rel_offset (r7, 12)
	mov	r7, r0
	mov	r0, r1
	mov	r1, r2
	mov	r2, r3
	ldmfd	ip, {r3, r4, r5, r6}
	swi	0x0
	ldmfd	sp!, {r4, r5, r6, r7}
	cfi_adjust_cfa_offset (-16)
	cfi_restore (r4)
	cfi_restore (r5)
	cfi_restore (r6)
	cfi_restore (r7)
	cmn	r0, #4096
	RETINSTR(cc, lr)
	b	PLTJMP(syscall_error)
PSEUDO_END (syscall)
应用案例:
syscall(__NR_epoll_create, size);
syscall(__NR_epoll_ctl, epfd, op, fd, event);
syscall(__NR_epoll_wait, epfd, events, maxevents, timeout);

六 添加自己的系统调用

1 在arch/arm/include/uapi/asm/unistd.h中添加自己的系统调用号。
#define __NR_mycall	 (__NR_SYSCALL_BASE+380)
2 在arch/arm/kernel/calls.S中添加自己的CALL函数,calls.S在entry-common.S中调用了3次;1是计算__NR_syscalls的时候,2是sys_oabi_call_table,3是sys_call_table。
CALL(sys_mycall)
3 在include/linux/syscalls.h中添加自己的系统调用函数的声明,#define asmlinkage CPP_ASMLINKAGE、#define CPP_ASMLINKAGE extern "C";asmlinkage是用来实现C和C++混合编程的。
asmlinkage long sys_mycall(const char __user *info);
4 实现自己的系统调用函数体。
asmlinkage long sys_mycall(const char __user *info) 
{
    ......
  printk("__%s__%s__\n", __func__, info);
}

你可能感兴趣的:(linux驱动)