ARM Linux系统调用的原理

Python爬虫实战:研究Pandas,构建车质网各车型数据采集和分析系统 研究结果表明,通过网络爬虫技术可以有效地获取公开的汽车投诉数据,这些数据经过适当的清洗和处理后,可以揭示出丰富的汽车质量信息。)作为国内专业的汽车质量投诉受理平台,自 2010 年成立以来,已累计接收消费者投诉信息超过 200 万条,这些数据包含了车型、投诉时间、投诉问题、处理状态等多维度信息,具有极高的分析价值。:通过对车质网数据的分析,能够挖掘出有价值的汽车质量信息,为消费者购车提供参考,为汽车厂商改进产品质量和服务提供依据,同时也能为相关监管部门提供数据支持。 阅读详情

操作系统为在用户态运行的进程与硬件设备进行交互提供了一组接口。在应用程序和硬件之间设置一个额外层具有很多优点。首先,这使得编程更加容易,把用户从学习硬件设备的低级编程特性中解放出来。其次,这极大地提高了系统的安全性,因为内核在试图满足某个请求之前在接口级就可以检查这种请求的正确性。最后,更重要的是这些接口使得程序具有可移植性,因为只要内核所提供的一组接口相同,那么在任一内核之上就可以正确地编译和执行程序。

Unix系统通过向内核发出系统调用(system call)实现了用户态进程和硬件设备之间的大部分接口。系统调用是操作系统提供的服务,用户程序通过各种系统调用,来引用内核提供的各种服务,系统调用的执行让用户程序陷入内核,该陷入动作由swi软中断完成。

应用编程接口(API)与系统调用的不同在于,前者只是一个函数定义,说明了如何获得一个给定的服务,而后者是通过软件中断向内核发出的一个明确的请求。POSIX标准针对API,而不针对系统调用。Unix系统给程序员提供了很多API库函数。libc的标准c库所定义的一些API引用了封装例程(wrapper routine)(其唯一目的就是发布系统调用)。通常情况下,每个系统调用对应一个封装例程,而封装例程定义了应用程序使用的API。反之则不然,一个API没必要对应一个特定的系统调用。从编程者的观点看,API和系统调用之间的差别是没有关系的:唯一相关的事情就是函数名、参数类型及返回代码的含义。然而,从内核设计者的观点看,这种差别确实有关系,因为系统调用属于内核,而用户态的库函数不属于内核。

大部分封装例程返回一个整数,其值的含义依赖于相应的系统调用。返回-1通常表示内核不能满足进程的请求。系统调用处理程序的失败可能是由无效参数引起的,也可能是因为缺乏可用资源,或硬件出了问题等等。在libd库中定义的errno变量包含特定的出错码。每个出错码定义为一个常量宏。

当用户态的进程调用一个系统调用时,CPU切换到内核态并开始执行一个内核函数。因为内核实现了很多不同的系统调用,因此进程必须传递一个名为系统调用号(system call number)的参数来识别所需的系统调用。所有的系统调用都返回一个整数值。这些返回值与封装例程返回值的约定是不同的。在内核中,整数或0表示系统调用成功结束,而负数表示一个出错条件。在后一种情况下,这个值就是存放在errno变量中必须返回给应用程序的负出错码。

ARM Linux系统利用SWI指令来从用户空间进入内核空间,还是先让我们了解下这个SWI指令吧。SWI指令用于产生软件中断,从而实现从用户模式变换到管理模式,CPSR保存到管理模式的SPSR,执行转移到SWI向量。在其他模式下也可使用SWI指令,处理器同样地切换到管理模式。指令格式如下:

SWI{cond}immed_24

其中:

immed_24 24位立即数,值为从0——16777215之间的整数。

使用SWI指令时,通常使用一下两种方法进行参数传递,SWI异常处理程序可以提供相关的服务,这两种方法均是用户软件协定。SWI异常中断处理程序要通过读取引起软件中断的SWI指令,以取得24为立即数。

1)、指令中24位的立即数指定了用户请求的服务类型,参数通过通用寄存器传递。如:

MOV R0,#34

SWI 12

2)、指令中的24位立即数被忽略,用户请求的服务类型有寄存器R0的只决定,参数通过其他的通用寄存器传递。如:

MOV R0, #12

MOV R1, #34

SWI 0

在SWI异常处理程序中,去除SWI立即数的步骤为:首先确定一起软中断的SWI指令时ARM指令还是Thumb指令,这可通过对SPSR访问得到;然后取得该SWI指令的地址,这可通过访问LR寄存器得到;接着读出指令,分解出立即数(低24位)。

由用户空间进入系统调用

通常情况下,我们写的代码都是通过封装的C lib来调用系统调用的。以< XMLNAMESPACE PREFIX ="ST1" />0.9.30版uClibc中的open为例,来追踪一下这个封装的函数是如何一步一步的调用系统调用的。在include/fcntl.h中有定义:

# define openopen64

open实际上只是open64的一个别名而已。

在libc/sysdeps/linux/common/open64.c中可以看到:

extern__typeof(open64) __libc_open64;

extern__typeof(open) __libc_open;

可见open64也只不过是__libc_open64的别名,而__libc_open64函数在同一个文件中定义:

libc_hidden_proto(__libc_open64)

int__libc_open64 (const char *file, int oflag, ...)

{

mode_t mode =0;

if (oflag &O_CREAT)

{

va_list arg;

va_start (arg,oflag);

mode = va_arg(arg, mode_t);

va_end (arg);

}

return__libc_open(file, oflag | O_LARGEFILE, mode);

}

libc_hidden_def(__libc_open64)

最终__libc_open64又调用了__libc_open函数,这个函数在文件libc/sysdeps/linux/common/open.c中定义:

libc_hidden_proto(__libc_open)

int__libc_open(const char *file, int oflag, ...)

{

mode_t mode =0;

if (oflag &O_CREAT) {

va_list arg;

va_start (arg,oflag);

mode = va_arg(arg, mode_t);

va_end (arg);

}

return__syscall_open(file, oflag, mode);

}

libc_hidden_def(__libc_open)

__syscall_open在同一个文件中定义:

static__inline__ _syscall3(int, __syscall_open, const char *, file,

int, flags,__kernel_mode_t, mode)

在文件libc/sysdeps/linux/arm/bits/syscalls.h文件中可以看到:

#undef_syscall3

#define_syscall3(type,name,type1,arg1,type2,arg2,type3,arg3) \

type name(type1arg1,type2 arg2,type3 arg3) \

{ \

return (type)(INLINE_SYSCALL(name, 3, arg1, arg2, arg3)); \

}

这个宏实际上完成定义一个函数的工作,这个宏的第一个参数是函数的返回值类型,第二个参数是函数名,之后的参数就如同它的参数名所表明的那样,分别是函数的参数类型及参数名。__syscall_open实际上为:

int__syscall_open (const char * file,int flags, __kernel_mode_t mode)

{

return (int)(INLINE_SYSCALL(__syscall_open, 3, file, flags, mode));

}

INLINE_SYSCALL为同一个文件中定义的宏:

#undefINLINE_SYSCALL

#defineINLINE_SYSCALL(name, nr, args...) \

({ unsigned int_inline_sys_result = INTERNAL_SYSCALL (name, , nr, args); \

if(__builtin_expect (INTERNAL_SYSCALL_ERROR_P (_inline_sys_result, ), 0)) \

{ \

__set_errno(INTERNAL_SYSCALL_ERRNO (_inline_sys_result, )); \

_inline_sys_result= (unsigned int) -1; \

} \

(int)_inline_sys_result; })

#undefINTERNAL_SYSCALL

#if!defined(__thumb__)

#ifdefined(__ARM_EABI__)

#defineINTERNAL_SYSCALL(name, err, nr, args...) \

({unsigned int__sys_result; \

{ \

register int_a1 __asm__ ("r0"), _nr __asm__ ("r7"); \

LOAD_ARGS_##nr(args) \

_nr =SYS_ify(name); \

__asm____volatile__ ("swi 0x0 @ syscall " #name \

:"=r" (_a1) \

: "r"(_nr) ASM_ARGS_##nr \

:"memory"); \

__sys_result =_a1; \

} \

(int)__sys_result; })

#else /*defined(__ARM_EABI__) */

#defineINTERNAL_SYSCALL(name, err, nr, args...) \

({ unsigned int__sys_result; \

{ \

register int_a1 __asm__ ("a1"); \

LOAD_ARGS_##nr(args) \

__asm____volatile__ ("swi %1 @ syscall " #name \

: "=r"(_a1) \

: "i"(SYS_ify(name)) ASM_ARGS_##nr \

:"memory"); \

__sys_result =_a1; \

} \

(int)__sys_result; })

#endif

#else /*!defined(__thumb__) */

/* We can't usepush/pop inside the asm because that breaks

unwinding (ie.thread cancellation).

*/

#defineINTERNAL_SYSCALL(name, err, nr, args...) \

({ unsigned int__sys_result; \

{ \

int_sys_buf[2]; \

register int_a1 __asm__ ("a1"); \

register int*_v3 __asm__ ("v3") = _sys_buf; \

*_v3 = (int)(SYS_ify(name)); \

LOAD_ARGS_##nr(args) \

__asm__ __volatile__("str r7, [v3, #4]\n" \

"\tldr r7,[v3]\n" \

"\tswi 0 @syscall " #name "\n" \

"\tldr r7,[v3, #4]" \

:"=r" (_a1) \

: "r"(_v3) ASM_ARGS_##nr \

:"memory"); \

__sys_result =_a1; \

} \

(int)__sys_result; })

#endif/*!defined(__thumb__)*/

这里也将同文件中的LOAD_ARGS宏的定义贴出来:

#defineLOAD_ARGS_0()

#defineASM_ARGS_0

#defineLOAD_ARGS_1(a1) \

_a1 = (int)(a1); \

LOAD_ARGS_0 ()

#defineASM_ARGS_1 ASM_ARGS_0, "r" (_a1)

#defineLOAD_ARGS_2(a1, a2) \

register int_a2 __asm__ ("a2") = (int) (a2); \

LOAD_ARGS_1(a1)

#defineASM_ARGS_2 ASM_ARGS_1, "r" (_a2)

#defineLOAD_ARGS_3(a1, a2, a3) \

register int_a3 __asm__ ("a3") = (int) (a3); \

LOAD_ARGS_2(a1, a2)

这项宏用来在相应的寄存器中加载相应的参数。SYS_ify宏获得系统调用号

#defineSYS_ify(syscall_name) (__NR_##syscall_name)

也就是__NR___syscall_open,在libc/sysdeps/linux/common/open.c中可以看到这个宏的定义:

#define__NR___syscall_open __NR_open

__NR_open在内核代码的头文件中有定义。在r7寄存器中存放系统调用号,而参数传递似乎和普通的函数调用的参数传递也没有什么区别。

在这个地方,得注意那个EABI, EABI是什么东西呢?ABI,Application Binary Interface,应用二进制接口。在较新的EABI规范中,是将系统调用号压入寄存器r7中,而在老的OABI中则是执行的swi 中断号的方式,也就是说原来的调用方式(Old ABI)是通过跟随在swi指令中的调用号来进行的。同时这两种调用方式的系统调用号也是存在这区别的,在内核的文件arch/arm/inclue/asm/unistd.h中可以看到:

#define__NR_OABI_SYSCALL_BASE 0x900000

#ifdefined(__thumb__) || defined(__ARM_EABI__)

#define__NR_SYSCALL_BASE 0

#else

#define__NR_SYSCALL_BASE __NR_OABI_SYSCALL_BASE

#endif

/*

* This filecontains 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)

……

接下来来看操作系统对系统调用的处理。我们回到ARM Linux的异常向量表,因为当执行swi时,会从异常向量表中取例程的地址从而跳转到相应的处理程序中。在文件arch/arm/kernel/entry-armv.S中:

W(ldr) pc,.LCvswi + stubs_offset

而.LCvswi在同一个文件中定义为:

.LCvswi:

.wordvector_swi

也就是最终会执行例程vector_swi来完成对系统调用的处理,接下来我们来看下在arch/arm/kernel/entry-common.S中定义的vector_swi例程:

.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 thesystem call number.

*/

#ifdefined(CONFIG_OABI_COMPAT)

/*

* If we haveCONFIG_OABI_COMPAT then we need to look at the swi

* value todetermine if it is an EABI or an old ABI call.

*/

#ifdefCONFIG_ARM_THUMB

tst r8,#PSR_T_BIT

movne r10, #0 @no thumb OABI emulation

ldreq r10, [lr,#-4] @ get SWI instruction

#else

ldr r10, [lr,#-4] @ get SWI instruction

A710( and ip,r10, #0x0f000000 @ check for SWI )

A710( teq ip,#0x0f000000 )

A710( bne.Larm710bug )

#endif

#ifdefCONFIG_CPU_ENDIAN_BE8

rev r10, r10 @little endian instruction

#endif

#elifdefined(CONFIG_AEABI)

/*

* Pure EABIuser space always put syscall number into scno (r7).

*/

A710( ldr ip,[lr, #-4] @ get SWI instruction )

A710( and ip,ip, #0x0f000000 @ check for SWI )

A710( teq ip,#0x0f000000 )

A710( bne.Larm710bug )

#elifdefined(CONFIG_ARM_THUMB)

/* Legacy ABIonly, 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 ABIonly. */

ldr scno, [lr,#-4] @ get SWI instruction

A710( and ip,scno, #0x0f000000 @ check for SWI )

A710( teq ip,#0x0f000000 )

A710( bne.Larm710bug )

#endif

#ifdefCONFIG_ALIGNMENT_TRAP

ldr ip, __cr_alignment

ldr ip, [ip]

mcr p15, 0, ip,c1, c0 @ update control register

#endif

enable_irq

// tsk 是寄存器r9的别名,在arch/arm/kernel/entry-header.S中定义:// tsk .req r9 @ current thread_info

get_thread_infotsk

// tbl是r8寄存器的别名,在arch/arm/kernel/entry-header.S中定义:

// tbl .req r8 @syscall table pointer,用来存放系统调用表

// 的地址

adr tbl,sys_call_table @ load syscall table pointer

ldr ip, [tsk,#TI_FLAGS] @ check for syscall tracing

#ifdefined(CONFIG_OABI_COMPAT)

/*

* If the swiargument is zero, this is an EABI call and we do nothing.

*

* If this is anold ABI call, get the syscall number into scno and

* get the oldABI syscall table address.

*/

bics r10, r10,#0xff000000

// scno是寄存器r7的别名

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

stmdb sp!, {r4,r5} @ push fifth and sixth args

tst ip,#_TIF_SYSCALL_TRACE @ 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

// why也是r8寄存器的别名

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

bsys_ni_syscall @ not private func

ENDPROC(vector_swi)

上面的zero_fp是一个宏,在arch/arm/kernel/entry-header.S中定义:

.macro zero_fp

#ifdefCONFIG_FRAME_POINTER

mov fp, #0

#endif

.endm

而fp位寄存器r11。

像每一个异常处理程序一样,要做的第一件事当然就是保护现场了。紧接着是获得系统调用的系统调用号。然后以系统调用号作为索引来查找系统调用表,如果系统调用号正常的话,就会调用相应的处理例程来处理,就是上面的那个ldrcc pc, [tbl, scno, lsl #2]语句,然后通过例程ret_fast_syscall来返回。

在这个地方我们接着来讨论ABI的问题。现在,我们首先来看两个宏,一个是CONFIG_OABI_COMPAT 意思是说与old ABI兼容,另一个是CONFIG_AEABI 意思是说指定现在的方式为EABI。这两个宏可以同时配置,也可以都不配,也可以配置任何一种。我们来看一下内核是怎么处理这一问题的。我们知道,sys_call_table 在内核中是个跳转表,这个表中存储的是一系列的函数指针,这些指针就是系统调用函数的指针,如(sys_open)。系统调用是根据一个系统调用号(通常就是表的索引)找到实际该调用内核哪个函数,然后通过运行该函数完成的。
首先,对于old ABI,内核给出的处理是为它建立一个单独的system call table,叫sys_oabi_call_table,这样,兼容方式下就会有两个system call table, 以old ABI方式的系统调用会执行old_syscall_table表中的系统调用函数,EABI方式的系统调用会用sys_call_table中的函数指针。
配置无外乎以下4中:
第一、两个宏都配置行为就是上面说的那样。
第二、只配置CONFIG_OABI_COMPAT,那么以old ABI方式调用的会用sys_oabi_call_table,以EABI方式调用的用sys_call_table,和1实质上是相同的。只是情况1更加明确。
第三、只配置CONFIG_AEABI系统中不存在sys_oabi_call_table,对old ABI方式调用不兼容。只能 以EABI方式调用,用sys_call_table。

第四、两个都没有配置,系统默认会只允许old ABI方式,但是不存在old_syscall_table,最终会通过sys_call_table 完成函数调用

系统会根据ABI的不同而将相应的系统调用表的基地址加载进tbl寄存器,也就是r8寄存器。接下来来看系统调用表,如前面所说的那样,有两个,同样都在文件arch/arm/kernel/entry-armv.S中:

/*

* This is thesyscall table declaration for native ABI syscalls.

* With EABI acouple syscalls are obsolete and defined as sys_ni_syscall.

*/

#defineABI(native, compat) native

#ifdefCONFIG_AEABI

#defineOBSOLETE(syscall) sys_ni_syscall

#else

#defineOBSOLETE(syscall) syscall

#endif

.typesys_call_table, #object

ENTRY(sys_call_table)

#include"calls.S"

#undef ABI

#undef OBSOLETE

另外一个为:

/*

* Let's declare asecond syscall table for old ABI binaries

* using thecompatibility syscall entries.

*/

#defineABI(native, compat) compat

#defineOBSOLETE(syscall) syscall

.typesys_oabi_call_table, #object

ENTRY(sys_oabi_call_table)

#include"calls.S"

#undef ABI

#undef OBSOLETE

这样看来貌似两个系统调用表是完全一样的。这里预处理指令include的独特用法也挺有意思,在系统调用表的内容就是整个arch/arm/kernel/calls.S文件的内容这个文件的内容如下(由于太长,这里就不全部列出了):

/*

*linux/arch/arm/kernel/calls.S

*

* Copyright (C)1995-2005 Russell King

*

* This program isfree software; you can redistribute it and/or modify

* it under theterms of the GNU General Public License version 2 as

* published by theFree Software Foundation.

*

* This file isincluded thrice in entry-common.S

*/

/* 0 */CALL(sys_restart_syscall)

CALL(sys_exit)

CALL(sys_fork_wrapper)

CALL(sys_read)

CALL(sys_write)

/* 5 */ CALL(sys_open)

CALL(sys_close)

……

这个是同样在文件arch/arm/kernel/entry-armv.S中的宏CALL()的定义:

#define CALL(x).long x

最后再罗嗦一点,如果用sys_open来搜的话,是搜不到系统调用open的定义的,系统调用函数都是用宏来定义的,比如对于open,在文件fs/open.c文件中这样定义:

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);

/* avoidREGPARM breakage on x86: */

asmlinkage_protect(3,ret, filename, flags, mode);

return ret;

}

继续回到vector_swi,而如果系统调用号不正确,则会调用arm_syscall函数来进行处理,这个函数在文件arch/arm/kernel/traps.c中定义:

/*

* Handle allunrecognised system calls.

* 0x9f0000 -0x9fffff are some more esoteric system calls

*/

#define NR(x)((__ARM_NR_##x) - __ARM_NR_BASE)

asmlinkage intarm_syscall(int no, struct pt_regs *regs)

{

structthread_info *thread = current_thread_info();

siginfo_t info;

if ((no>> 16) != (__ARM_NR_BASE>> 16))

returnbad_syscall(no, regs);

switch (no& 0xffff) {

case 0: /*branch through 0 */

info.si_signo =SIGSEGV;

info.si_errno =0;

info.si_code =SEGV_MAPERR;

info.si_addr =NULL;

arm_notify_die("branchthrough zero", regs, &info, 0, 0);

return 0;

caseNR(breakpoint): /* SWI BREAK_POINT */

regs->ARM_pc-= thumb_mode(regs) ? 2 : 4;

ptrace_break(current,regs);

returnregs->ARM_r0;

/*

* Flush a regionfrom virtual address 'r0' to virtual address 'r1'

* _exclusive_.There is no alignment requirement on either address;

* user spacedoes not need to know the hardware cache layout.

*

* r2 containsflags. It should ALWAYS be passed as ZERO until it

* is defined tobe something else. For now we ignore it, but may

* the fires ofhell burn in your belly if you break this rule. ;)

*

* (at a laterdate, we may want to allow this call to not flush

* variousaspects of the cache. Passing '0' will guarantee that

* everythingnecessary gets flushed to maintain consistency in

* the specifiedregion).

*/

caseNR(cacheflush):

do_cache_op(regs->ARM_r0,regs->ARM_r1, regs->ARM_r2);

return 0;

case NR(usr26):

if (!(elf_hwcap& HWCAP_26BIT))

break;

regs->ARM_cpsr&= ~MODE32_BIT;

returnregs->ARM_r0;

case NR(usr32):

if (!(elf_hwcap& HWCAP_26BIT))

break;

regs->ARM_cpsr|= MODE32_BIT;

returnregs->ARM_r0;

caseNR(set_tls):

thread->tp_value= regs->ARM_r0;

#ifdefined(CONFIG_HAS_TLS_REG)

asm ("mcrp15, 0, %0, c13, c0, 3" : : "r" (regs->ARM_r0) );

#elif!defined(CONFIG_TLS_REG_EMUL)

/*

* User spacemust never try to access this directly.

* Expect yourapp to break eventually if you do so.

* The userhelper at 0xffff0fe0 must be used instead.

* (seeentry-armv.S for details)

*/

*((unsigned int*)0xffff0ff0) = regs->ARM_r0;

#endif

return 0;

#ifdefCONFIG_NEEDS_SYSCALL_FOR_CMPXCHG

/*

* Atomicallystore r1 in *r2 if *r2 is equal to r0 for user space.

* Return zeroin r0 if *MEM was changed or non-zero if no exchange

* happened.Also set the user C flag accordingly.

* If accesspermissions have to be fixed up then non-zero is

* returned andthe operation has to be re-attempted.

*

* *NOTE*: Thisis a ghost syscall private to the kernel. Only the

*__kuser_cmpxchg code in entry-armv.S should be aware of its

* existence.Don't ever use this from user code.

*/

caseNR(cmpxchg):

for (;;) {

extern voiddo_DataAbort(unsigned long addr, unsigned int fsr,

struct pt_regs*regs);

unsigned longval;

unsigned longaddr = regs->ARM_r2;

structmm_struct *mm = current->mm;

pgd_t *pgd;pmd_t *pmd; pte_t *pte;

spinlock_t*ptl;

regs->ARM_cpsr&= ~PSR_C_BIT;

down_read(&mm->mmap_sem);

pgd =pgd_offset(mm, addr);

if(!pgd_present(*pgd))

gotobad_access;

pmd =pmd_offset(pgd, addr);

if (!pmd_present(*pmd))

gotobad_access;

pte =pte_offset_map_lock(mm, pmd, addr, &ptl);

if(!pte_present(*pte) || !pte_dirty(*pte)) {

pte_unmap_unlock(pte,ptl);

gotobad_access;

}

val =*(unsigned long *)addr;

val -=regs->ARM_r0;

if (val == 0) {

*(unsigned long*)addr = regs->ARM_r1;

regs->ARM_cpsr|= PSR_C_BIT;

}

pte_unmap_unlock(pte,ptl);

up_read(&mm->mmap_sem);

return val;

bad_access:

up_read(&mm->mmap_sem);

/* simulate awrite access fault */

do_DataAbort(addr,15 + (1 << 11), regs);

}

#endif

default:

/* Calls9f00xx..9f07ff are defined to return -ENOSYS

if notimplemented, rather than raising SIGILL. This

way the callingprogram can gracefully determine whether

a feature issupported. */

if ((no &0xffff) <= 0x7ff)

return -ENOSYS;

break;

}

#ifdefCONFIG_DEBUG_USER

/*

* experienceshows that these seem to indicate that

* somethingcatastrophic has happened

*/

if (user_debug& UDBG_SYSCALL) {

printk("[%d]%s: arm syscall %d\n",

task_pid_nr(current),current->comm, no);

dump_instr("",regs);

if (user_mode(regs)){

__show_regs(regs);

c_backtrace(regs->ARM_fp,processor_mode(regs));

}

}

#endif

info.si_signo =SIGILL;

info.si_errno =0;

info.si_code =ILL_ILLTRP;

info.si_addr =(void __user *)instruction_pointer(regs) -

(thumb_mode(regs)? 2 : 4);

arm_notify_die("Oops- bad syscall(2)", regs, &info, no, 0);

return 0;

}

还有那个sys_ni_syscall,这个函数在kernel/sys_ni.c中定义,它的作用似乎也仅仅是要给用户空间返回错误码ENOSYS。

/*

*Non-implemented system calls get redirected here.

*/

asmlinkage longsys_ni_syscall(void)

{

return -ENOSYS;

}

系统调用号正确也好不正确也好,最终都是通过ret_fast_syscall例程来返回,同样在arch/arm/kernel/entry-common.S文件中:

/*

* This is the fastsyscall return path. We do as little as

* possible here,and this includes saving r0 back into the SVC

* stack.

*/

ret_fast_syscall:

UNWIND(.fnstart)

UNWIND(.cantunwind)

disable_irq @disable interrupts

ldr r1, [tsk,#TI_FLAGS]

tst r1,#_TIF_WORK_MASK

bnefast_work_pending

/* performarchitecture specific actions before user return */

arch_ret_to_userr1, lr

restore_user_regsfast = 1, offset = S_OFF

UNWIND(.fnend )

添加新的系统调用

第一、打开arch/arm/kernel/calls.S,在最后添加系统调用的函数原型的指针,例如:

CALL(sys_set_senda)

补充说明一点关于NR_syscalls的东西,这个常量表示系统调用的总的个数,在较新版本的内核中,文件arch/arm/kernel/entry-common.S中可以找到:

.equNR_syscalls,0

#define CALL(x).equ NR_syscalls,NR_syscalls+1

#include"calls.S"

#undef CALL

#define CALL(x).long x

相当的巧妙,不是吗?在系统调用表中每添加一个系统调用,NR_syscalls就自动增加一。在这个地方先求出NR_syscalls,然后重新定义CALL(x)宏,这样也可以不影响文件后面系统调用表的建立。

第二、打开include/asm-arm/unistd.h,添加系统调用号的宏,感觉这步可以省略,因为这个地方定义的系统调用号主要是个C库,比如uClibc、Glibc用的。例如:

#define __NR_plan_set_senda(__NR_SYSCALL_BASE+365)

为了向后兼容,系统调用只能增加而不能减少,这里的编号添加时,也必须按顺序来。否则会导致核心运行错误。

<!--[if!supportLists]-->第三,<!--[endif]-->实例化该系统调用,即编写新添加系统调用的实现例如:

SYSCALL_DEFINE1(set_senda,int,iset)

{

if(iset)

UART_PUT_CR(&at91_port[2],AT91C_US_SENDA);

else

UART_PUT_CR(&at91_port[2],AT91C_US_RSTSTA);

return 0;

}

第四、打开include/linux/syscalls.h添加函数声明

asmlinkage longsys_set_senda(int iset);

第五、在应用程序中调用该系统调用,可以参考uClibc的实现。

第六、结束。

 

1 初识智能化测试 非常感谢您可以和我一起来聊一聊智能化测试的一些事。智能化测试是一个新鲜又老旧的问题,说新鲜是因为很多人当听到智能化测试都会联想到人工智能、机器学习、深度学习等高大上的技术,很多时候觉得离我们的实际工作还很远;说老旧,是因为智能化测试的一些技术的发展在行业里面已经很久了,例如符号执行、静态分析等技术已经有很长的历史了。近些年,随着测试技术的的飞速发展,智能化测试也有了越来越多的实践,优秀的开源项目慢慢的被行业推行并且落地。那么在这里我们就一起来聊聊智能化测试以及智能化测试好的思路、实践、方法以及技术落地过程。 阅读详情

相关推荐

英飞凌AURIX™ TC3xx系列MCU的ISO 26262安全机制实战:从安全手册到代码集成的避坑指南

本文深入探讨英飞凌AURIX™ TC3xx系列MCU的ISO 26262安全机制实战应用,重点解析锁步核配置、ECC策略优化及SafeTlib库集成中的关键问题。通过代码示例和配置建议,帮助开发者实现ASIL D级别安全性,规避常见陷阱,提升汽车电子系统的功能安全合规性。

weixin_42558093的博客 405

libc_hidden_def、libc_hidden_weak、libc_hidden_proto

libc_hidden_def、libc_hidden_weak、libc_hidden_proto 在阅读glibc源码的时候,遇见了几个没见过的宏,几乎所有的函数都会使用这几个宏:libc_hidden_def、libc_hidden_weak、libc_hidden_proto 因为我比较好奇,所以特地去找了一下有关这些宏的定义(主要也是想多学一点) Linux下学习源码,最方便的地方就是它的各种命令 使用find ./ | grep -r "define libc_hidden_def",发现在in

私のBookShelf 2403

Qoder + ADB Supabase :5分钟GET超火AI手办生图APP

本文介绍如何利用Qoder、阿里云 ADB Supabase 和通义千问图像编辑模型,快速构建AI手办生图Flutter应用。无需传统后端,通过AI生成代码、BaaS服务与边缘函数调用,实现从原型到上线的敏捷开发,全流程轻量高效,助力Vibe Coding落地实践。

数据库技术 931

glibc源码-libc_hidden_def

glibc版本:2.32 在阅读scanf源码的时候,看到ldbl_hidden_def,不明这个宏的意思,于是查阅资料,进行了一番了解。( 因为涉及的知识点太过庞大,包含链接,库的加载,函数的调用等等,且当前不从事与这方面的研究,故仅了解下) scanf调用:scanf->vfscanf->__vfscanf_internal. __vfscanf_internal的实现代码有2700行。。。。恐怖。浏览了一遍,没完全读懂,和vfprintf差不多,找到格式化字符,然后调用系统接口读入信

能别闹的博客 2533

3DGS 2025实战指南:从安装到训练全流程解析

本文提供了3D Gaussian Splatting (3DGS) 从零开始的完整实战指南。详细解析了使用gsplat库进行环境配置、依赖安装(包括pycolmap和fused-ssim)、数据准备与COLMAP处理、核心训练流程及参数调优,并探讨了2025年3DGS在动态场景、可控编辑等领域的最新进展,帮助开发者快速掌握这一前沿3D重建技术。

b3n4m5q6w7的博客 359

国密(3)- 预主密钥/主密钥计算和Finished消息的加解密

国密的ECC-SM3-SM4-CBC模式下的预主密钥和主密钥的生成过程介绍,以及Finished消息的加解密介绍

weixin_43408952的博客 6510

linux系统调用_arm实现深入剖析(上下文保存、参数传递)

前言 这里我们不再赘述系统调用的基本原理以及系统调用产生时的函数调用以及系统调用表,直接看arm中的软中断产生及返回时如何保存寄存器上下文、如何陷入内核再返回、如何传参,模式切换做了哪些处理。 这里mark一下: EABI和OABI系统调用实现的方式有所不同。 陷入内核 不同于x86的int 80中断,arm中使用软中断指令swi实现系统调用,直接看swi的处理向量 vector_swi: /*=========================================================

qq_40036519的博客 1312

MATLAB PDE工具箱电磁场仿真平行电容板及电偶极子详解

首先打开matlab,在命令行输入pdetool打开PDE工具箱 工具箱的功能我就不赘述了,大概就是以有限元的数值计算方法,计算二阶偏微分方程,因此可以解决电磁场、热力学等问题。 首先点击左上角按钮,画三个矩形 画完一个后可以双击矩形编辑,参数如下: 其中R1表示上极板,R2表示下极板,R3表示整个空间区域。 在上方Set formula为R3-R2-R1,表示求解区域是整个空间区域减去两个极板。下拉框选择Electrostatics表示求解的是静电场问题。 接下来点击上方菜单栏中的这个按钮,表

weixin_42845306的博客 1万+

ABI,Legacy ABI, EABI

ABI,Legacy ABI, EABI GCC view New ABI is not only a new ABI field, it is also a new GCC target. Legacy ABI:  * ABI flags passed to binutils: -mabi=apcs-gnu -mfpu=fpa Arm EABI: * ABI flags

Felix的专栏 1558

Win10下Altera USB-Blaster驱动安装详解:禁用数字签名与排查指南

在嵌入式开发和FPGA调试过程中,驱动安装是工程师绕不开的环节,尤其是当操作系统对驱动签名有严格限制时,硬件往往无法被正常识别。数字签名是系统验证驱动程序来源和完整性的重要机制,Win10出于安全考虑,默认拒绝加载未经签名的驱动,这正是Altera USB-Blaster下载器在设备管理器中显示未知设备或黄色感叹号的根本原因。通过理解设备管理器的排查逻辑,掌握临时禁用驱动强制签名或开启测试模式的方法,配合正确的驱动文件路径选择与手动安装操作,即可解决Quartus无法检测到下载器的问题。这一流程同样适用于许

weixin_29164497的博客 245

具身智能:原理、算法与系统 第18章 模仿学习与人类示范

与基于试错机制的强化学习不同,模仿学习假设存在能够提供高质量轨迹示范的专家策略,学习者的目标是在不直接访问专家内部决策机制的情况下,复现或泛化专家的行为模式。此外,选项 GAIL(Option-GAIL)将分层强化学习与对抗模仿结合,学习选项(Options)级别的策略,能够捕捉专家示范中的多模态行为与高层意图。X-Sim 框架提出从真实到模拟再到真实的迁移路径:首先从人类视频中重建物理仿真环境,在仿真中训练状态级策略,随后通过策略蒸馏获得基于视觉的策略,最后通过在线领域自适应对齐真实与仿真观测。

VectorShift的博客 125

linux系统调用的封装格式,ARM Linux系统调用的原理

ARM Linux系统调用的原理ARM Linux系统调用的原理操作系统为在用户态运行的进程与硬件设备进行交互提供了一组接口。在应用程序和硬件之间设置一个额外层具有很多优点。首先,这使得编程更加容易,把用户从学习硬件设备的低级编程特性中解放出来。其次,这极大地提高了系统的安全性,因为内核在试图满足某个请求之前在接口级就可以检查这种请求的正确性。最后,更重要的是这些接口使得程序具有可移植性,因为只要...

weixin_30884455的博客 559

Claude Code(7):从零创建你的第一个 Skill

在前面的文章,我们介绍了 Skills 的基本概念,这篇我们深入实战,从零开始创建你的第一个 Skill,并逐步解锁所有配置项,让 Claude Code 真正熟悉你的测试规范和习惯。

blues_C的博客 649

arm linux是什么意思,ARM Linux系统调用的原理

ARM Linux系统调用的原理操作系统为在用户态运行的进程与硬件设备进行交互提供了一组接口。在应用程序和硬件之间设置一个额外层具有很多优点。首先,这使得编程更加容易,把用户从学习硬件设备的低级编程特性中解放出来。其次,这极大地提高了系统的安全性,因为内核在试图满足某个请求之前在接口级就可以检查这种请求的正确性。最后,更重要的是这些接口使得程序具有可移植性,因为只要内核所提供的一组接口相同,那么在...

weixin_32467423的博客 1356

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

syscall 系统调用陷入_ARM linux系统调用的实现原理

大家都知道linux的应用程序要想访问内核必须使用系统调用从而实现从usr模式转到svc模式。下面咱们看看它的实现过程。系统调用是os操作系统提供的服务,用户程序通过各种系统调用,来引用内核提供的各种服务,系统调用的执行让用户程序陷入内核,该陷入动作由swi软中断完成。at91rm9200处理器对应的linux2.4.19内核系统调用对应的软中断定义如下:#if defined(__thumb__...

weixin_42548893的博客 562

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

Linux ARM系统调用过程分析(三)——Linux中open系统调用实现原理

Linux ARM系统调用过程分析(三)——Linux中open系统调用实现原理 备注:   1. Kernel版本:5.4   2. 使用工具:Source Insight 4.0   3. 参考博客:   (1)arm-linux 系统调用流程   (2)ARM Linux上的系统调用代码分析 文章目录Linux ARM系统调用过程分析(三)——Linux中open系统调用实现原理sys_open的定义SYSCALL_DEFINEx宏定义SYSCALL_METADATA宏定义__SYSCALL_DEFI

枫潇潇 1088

ARM linux系统调用的实现原理

作者:刘洪涛,华清远见嵌入式学院讲师。大家都知道linux的应用程序要想访问内核必须使用系统调用从而实现从usr模式转到svc模式。下面咱们看看它的实现过程。系统调用是os操作系统提供的服务,用户程序通过各种系统调用,来引用内核提供的各种服务,系统调用的执行让用户程序陷入内核,该陷入动作由swi软中断完成。at91rm9200处理器对应的linux2.4.19内核系统调用对应的软中断定

farsightliuht的专栏 821

syscall 系统调用陷入_以源代码为例,讲解ARM Linux系统调用的实现原理

大家都知道linux的应用程序要想访问内核必须使用系统调用从而实现从usr模式转到svc模式。下面咱们看看它的实现过程。系统调用是os操作系统提供的服务,用户程序通过各种系统调用,来引用内核提供的各种服务,系统调用的执行让用户程序陷入内核,该陷入动作由swi软中断完成。at91rm9200处理器对应的linux2.4.19内核系统调用对应的软中断定义如下:#if defined(__thumb__...

weixin_32508109的博客 254

linux arm notify die,ARM Linux系统调用的原理

ted.*/591 if ((no & 0xffff) <= 0x7ff)592 return -ENOSYS;593 break;594}595 #ifdef CONFIG_DEBUG_USER596/*597* experience shows that these seem to indicate that598* something catastrophic has happ...

weixin_39900045的博客 166
上一篇: 文件系统VFS数据结构
下一篇: Linux __setup宏解析
LinuxWorking
博客等级 码龄10年 24粉丝 32原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值