历史上的今天
返回首页

历史上的今天

今天是:2025年08月04日(星期一)

正在发生

2020年08月04日 | 你知道Arm Linux系统调用流程?

2020-08-04 来源:elecfans

Linux系统通过向内核发出系统调用(system call)实现了用户态进程和硬件设备之间的大部分接口。

系统调用是操作系统提供的服务,用户程序通过各种系统调用,来引用内核提供的各种服务,系统调用的执行让用户程序陷入内核,该陷入动作由swi软中断完成。


1、用户可以通过两种方式使用系统调用:

第一种方式是通过C库函数,包括系统调用在C库中的封装函数和其他普通函数。

第二种方式是使用_syscall宏。2.6.18版本之前的内核,在include/asm-i386/unistd.h文件中定义有7个_syscall宏,分别是:

_syscall0(type,name) _syscall1(type,name,type1,arg1) _syscall2(type,name,type1,arg1,type2,arg2) _syscall3(type,name,type1,arg1,type2,arg2,type3,arg3) _syscall4(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4) _syscall5(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4,type5,arg5) _syscall6(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4,type5,arg5,type6,arg6)


其中,type表示所生成系统调用的返回值类型,name表示该系统调用的名称,typeN、argN分别表示第N个参数的类型和名称,它们的数目和_syscall后面的数字一样大。


这些宏的作用是创建名为name的函数,_syscall后面跟的数字指明了该函数的参数的个数。


比如sysinfo系统调用用于获取系统总体统计信息,使用_syscall宏定义为:

_syscall1(int, sysinfo, struct sysinfo *, info);

展开后的形式为:

int sysinfo(struct sysinfo * info) {   long __res;   __asm__ volatile("int $0x80" : "=a" (__res) : "0" (116),"b" ((long)(info)));   do {     if ((unsigned long)(__res) >= (unsigned long)(-(128 + 1)))     {       errno = -(__res);       __res = -1;     }     return (int) (__res);   } while (0); }


可以看出,_syscall1(int, sysinfo, struct sysinfo *, info)展开成一个名为sysinfo的函数,原参数int就是函数的返回类型,原参数struct sysinfo *和info分别构成新函数的参数。


在程序文件里使用_syscall宏定义需要的系统调用,就可以在接下来的代码中通过系统调用名称直接调用该系统调用。下面是一个使用sysinfo系统调用的实例。


代码清单5.1  sysinfo系统调用使用实例

#include #include #include #include /* for struct sysinfo */ _syscall1(int, sysinfo, struct sysinfo *, info); int main(void) {   struct sysinfo s_info;   int error;  error = sysinfo(&s_info);   printf("code error = %d/n", error);   printf("UpTIme = %lds/nLoad:       1 min %lu / 5 min %lu / 15 min %lu/n"       "RAM: total %lu / free %lu / shared %lu/n"       "Memory in buffers = %lu/nSwap: total %lu / free %lu/n"   "Number of processes = %d/n",   s_info.upTIme,       s_info.loads[0], s_info.loads[1], s_info.loads[2],       s_info.totalram, s_info.freeram, s_info.sharedram, s_info.bufferram, s_info.totalswap, s_info.freeswap,       s_info.procs);   exit(EXIT_SUCCESS); }


但是自2.6.19版本开始,_syscall宏被废除,我们需要使用syscall函数,通过指定系统调用号和一组参数来调用系统调用。


syscall函数原型为:

int syscall(int number, ...);

其中number是系统调用号,number后面应顺序接上该系统调用的所有参数。下面是getTId系统调用的调用实例。


代码清单5.2  getTId系统调用使用实例

#include #include #include #define __NR_gettid 224 int main(int argc, char *argv[]) { pid_t tid; tid = syscall(__NR_gettid); }

大部分系统调用都包括了一个SYS_符号常量来指定自己到系统调用号的映射,因此上面第10行可重写为:

tid = syscall(SYS_gettid);


2 系统调用与应用编程接口(API)区别

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


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


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


3 系统调用执行过程

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,#34SWI 12

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

MOV R0, #12MOV R1, #34SWI 0

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


由用户空间进入系统调用

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

# define open open64

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(type1 arg1,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为同一个文件中定义的宏:

#undef INLINE_SYSCALL#define INLINE_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; }) #undef INTERNAL_SYSCALL#if !defined(__thumb__)#if defined(__ARM_EABI__)#define INTERNAL_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__) */ #define INTERNAL_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 use push/pop inside the asm because that breaks unwinding (ie. thread cancellation). */#define INTERNAL_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宏的定义贴出来:

#define LOAD_ARGS_0()#define ASM_ARGS_0#define LOAD_ARGS_1(a1) _a1 = (int) (a1); LOAD_ARGS_0 ()#define ASM_ARGS_1 ASM_ARGS_0, "r" (_a1)#define LOAD_ARGS_2(a1, a2) register int _a2 __asm__ ("a2") = (int) (a2); LOAD_ARGS_1 (a1)#define ASM_ARGS_2 ASM_ARGS_1, "r" (_a2)#define LOAD_ARGS_3(a1, a2, a3) register int _a3 __asm__ ("a3") = (int) (a3); LOAD_ARGS_2 (a1, a2)

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

#define SYS_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#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)……

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

/* * We group all the following data together to optimise * for CPUs with separate I & D caches. */ .align 5.LCvswi: .word vector_swi .globl __stubs_end__stubs_end: .equ stubs_offset, __vectors_start + 0x200 - __stubs_start .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:

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

.LCvswi: .word vector_swi

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

/*============================================================================= * SWI handler *----------------------------------------------------------------------------- */ /* If we're optimising for StrongARM the resulting code won't run on an ARM7 and we can save a couple of instructions. --pb */#ifdef CONFIG_CPU_ARM710#define A710(code...) code.Larm710bug: ldmia sp, {r0 - lr}^ @ Get calling r0 - lr mov r0, r0 add sp, sp, #S_FRAME_SIZE subs pc, lr, #4#else#define A710(code...)#endif .align 5ENTRY(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 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#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). */ A710( ldr ip, [lr, #-4] @ get SWI instruction ) A710( and ip, ip, #0x0f000000 @ check for SWI ) A710( teq ip, #0x0f000000 ) A710( bne .Larm710bug )#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 A710( and ip, scno, #0x0f000000 @ check for SWI ) A710( teq ip, #0x0f000000 ) A710( bne .Larm710bug )#endif#ifdef CONFIG_ALIGNMENT_TRAP ldr ip, __cr_alignment ldr ip, [ip] mcr p15, 0, ip, c1, c0 @ update control register#endif enable_irq

推荐阅读

史海拾趣

启珑(CHIPLON)公司的发展小趣事

一次性生成5个关于启珑(CHIPLON)公司在电子行业发展起来的相关故事可能内容过多,我可以先为您提供1个相关故事作为示例,如您满意,我可以继续提供。

启珑微电子推出创新DSP系列

近年来,随着数字技术的飞速发展,电子行业对高性能处理器的需求日益增长。在这一背景下,启珑微电子(CHIPLON)凭借其深厚的技术积累和敏锐的市场洞察力,成功推出了全新的CLM320F28335系列DSP(数字信号处理器)。

这款产品一经发布,就引起了行业内的广泛关注。CLM320F28335系列DSP以其高效的32位RISC-V CPU内核、高精度、低成本、低功耗、高性能以及外设集成度高等特点,迅速在市场中占据了一席之地。与传统的定点DSP相比,这款产品的优势显而易见,尤其是在数据处理和A/D转换方面表现更为出色。

值得一提的是,CLM320F28335系列DSP拥有150MHz的高速处理能力,并配备了32位浮点处理单元,这使其在处理复杂算法和大量数据时能够游刃有余。同时,它还具备6个DMA通道,支持ADC、McBSP和EMIF,以及多达18路的PWM输出,其中有6路为高精度PWM输出(HRPWM),这些特性使其在工业自动化、电机控制等领域具有广泛应用前景。

该产品设计的另一个亮点是,它能够直接PIN对PIN替代国际同类产品,这意味着用户在使用启珑的DSP时,无需更改原有的电路板设计或系统软件,即可实现完全的替代兼容,这一设计无疑大大降低了用户的替换成本和使用难度。

随着CLM320F28335系列DSP的成功推出,启珑微电子在电子行业的地位得到了进一步提升。这一产品的成功,不仅展示了启珑微电子强大的研发实力,也为其在激烈的市场竞争中赢得了更多的市场份额。

若您想要探索更多内容,随时可以继续输入。

广州奥松公司的发展小趣事

奥松电子拥有一支近200名工程师组成的专职研发团队,并配备了超过7000㎡的研发实验室。实验室中配置了步进式投影光刻机、双面光刻机等先进设备,为公司的产品研发提供了有力的支持。这些设备不仅满足了产品研发、小试以及中试各个阶段的试验条件,也为公司的技术创新提供了坚实的基础。

AITSEMI公司的发展小趣事

随着产品线的不断完善,AITSEMI公司开始积极寻求市场机会,并逐步在全球范围内建立销售网络。通过与各大消费电子品牌的紧密合作,AITSEMI的芯片产品成功应用于音频功放和电源管理等领域,为全球消费者提供了更优质的产品体验。同时,公司还积极拓展医疗、工业控制、照明等新兴市场,为公司的持续增长提供了强大的动力。

柯爱亚(ceaiya)公司的发展小趣事

作为一家有社会责任感的企业,柯爱亚积极参与各种公益活动。公司不仅捐款捐物支持灾区重建、教育事业等公益事业,还组织员工参与志愿者活动,为社会做出了积极贡献。这些行为展现了柯爱亚作为一家优秀企业的社会担当和良好形象。

请注意,以上故事框架仅供参考,具体内容需要根据柯爱亚公司的实际发展历程进行编写。

FCT electronic公司的发展小趣事

近年来,电子行业面临着成本上升、全球不稳定以及经济动荡等多重挑战。FCT electronic公司凭借其在挠性电路板领域的深厚积累,积极应对这些挑战。公司加强了对供应链的管理,提高了生产效率,降低了成本。同时,FCT electronic公司还加大了对研发的投入,推动产品向高端化、智能化方向转型升级。这些努力使FCT electronic公司在行业中保持了领先地位。

CML公司的发展小趣事

在完成了数十年的成功发展后,CML并未满足于现状。公司开始着手制定面向未来的战略规划,旨在继续保持在半导体行业的领先地位。CML加大了对新兴技术的研发投入,如人工智能、物联网等领域,积极探索新的应用场景和市场机会。同时,公司还加强了与国际同行的合作与交流,不断提升自身的技术水平和市场竞争力。通过这些努力,CML为未来的发展奠定了坚实的基础,展望着更加辉煌的未来。

以上是基于CML公司发展历程的五个可能故事。请注意,这些故事虽然基于事实进行构建,但并非真实的历史记录。如需了解更多关于CML公司的具体发展历程和故事,建议查阅相关文献或访问公司官方网站。

问答坊 | AI 解惑

WINCE下如何在.db数据库中查找数据

怎么查询.db中的数据啊,或者大家有没有相关的例子,先谢过了啊…

查看全部问答>

arm和mc35i的gprs 协议栈的问题

我用lpc2138和mc35i做了一个通信模块,通过gprs发送adc的数据。 我想问一下 1,mc35i是否带有协议栈? 2,协议栈处于流程中的哪一部分,即先把数据做成IP包再通过AT命令传给MC35I,还是直接把数据用AT命令传给模块? 3,有没有相关的例子代码?能 ...…

查看全部问答>

V5接口协议

有谁做过v5接口协议 接入网侧的 请与我交流一下 谢谢…

查看全部问答>

CMOS图像传感器OV7620驱动

下面是寒假里写的一个CMOS图像传感器的驱动包。      但本人将驱动代码以LIB文件的方式进行了集成。        有兴趣的朋友可以看一下。          这 ...…

查看全部问答>

买ST的IC真够难的,求购

ST72F321R9T6   1包 ST72F324R6T6   4包…

查看全部问答>

FPGA任意波形发生器ROM资源不足

用FPGA做一个任意波形双通道信号发生器,波形通过查表ROM获得,每个通道可选择产生正弦,方波,三角波,锯齿波,既一个通道需要用4个ROM,双通道也就需要8个ROM。 现在用这个方法遇到一个问题,就是FPGA提供的存储空间不足,如果ROM是8位256个点的 ...…

查看全部问答>

最后一天

本帖最后由 paulhyde 于 2014-9-15 09:03 编辑 明天就要比赛了,现在感觉压力好大。 祝福小组 也祝福参加比赛的各位取得好成绩。  …

查看全部问答>

帮我看下syntax error near'unsigned'是哪里错啊,我急死了

帮我看下syntax error near\'unsigned\'是哪里错啊,我急死了,我是编程一段1到1000的数,为什么编译出来有错误呢? #include<reg51.h>#define uint unsigned int#define uchar unsigned charuchar aa,unmp,qian,bai,shi,ge;void dingshi();vo ...…

查看全部问答>