歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> Linux教程 >> ARM Linux 系統調用過程

ARM Linux 系統調用過程

日期:2017/2/28 14:02:11   编辑:Linux教程

  系統調用是操作系統提供給用戶(應用程序)的一組接口,每個系統調用都有一個對應的系統調用函數來完成相應的工作。用戶通過這個接口向操作系統申請服務,如訪問硬件,管理進程等等。但是因為用戶程序運行在用戶空間,而系統調用運行在內核空間,因此用戶程序不能直接調用系統調用函數,我們經常看到的比如fork、open、write 等等函數實際上並不是真正的系統調用函數,他們都只是c庫,在這些函數裡將執行一個軟中斷 swi 指令,產生一個軟中斷,使CPU 陷入內核態,接著在內核中進行一系列的判斷,判斷出是哪個系統調用,再轉到真正的系統調用函數,完成相應的功能。下面舉一個簡單的例子說明從用戶態調用一個“系統調用”,到內核處理的整個執行流程。

  用戶態程序如下:

void pk()

  {

    __asm__(

    "ldr r7 =365 \n"

    "swi \n"

    :

    :

    :

    );

  }

  int main()

  {

    pk();

    retrun 0;

  }

  上面的代碼中,我自己實現了一個新的系統調用,具體怎麼做,後面再具體描述。pk()事實上就可以類比於平時我們在用戶程序裡調用的 open() 等函數,這個函數只做了一件簡單的事:將系統調用號傳給 r7 ,,然後產生一軟中斷。接著CPU陷入內核

  內核態:

  CPU相應這個軟中斷以後,PC指針會到相應的中斷向量表中取指,中斷向量表在內核代碼中:arch/arm/kernel/entry-armv.S 中定義

.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 #響應中斷後pc指向這裡
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:

當pc取到如上的指令後,會跳到 vector_swi 這個標號,這個標號在arch/arm/kernel/entry-commen.S 中定義。

.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
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

get_thread_info tsk

adr tbl, sys_call_table @ load syscall table pointer #獲取系統調用表的基地址
ldr ip, [tsk, #TI_FLAGS] @ check for syscall tracing

#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

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

從上面可以看出,當CPU從中斷向量表轉到vector_swi 之後,完成了幾件事情:1.取出系統調用號 2.根據系統調用號取出系統調用函數在系統調用表的基地址,得到一個系統調用函數的函數指針 3. 根據系統調用表的基地址和系統調用號,得到這個系統調用表裡的項,每一個表項都是一個函數指針,把這個函數指針賦給PC , 則實現了跳轉到系統調用函數。

系統調用表定義在:arch/arm/kernel/Calls.S

* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This file is included 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)
CALL(sys_ni_syscall) /* was sys_waitpid */
CALL(sys_creat)
CALL(sys_link)
/* 10 */ CALL(sys_unlink)
CALL(sys_execve_wrapper)
CALL(sys_chdir)
CALL(OBSOLETE(sys_time)) /* used by libc4 */
CALL(sys_mknod)
/* 15 */ CALL(sys_chmod)
CALL(sys_lchown16)
CALL(sys_ni_syscall) /* was sys_break */
CALL(sys_ni_syscall) /* was sys_stat */
CALL(sys_lseek)
/* 20 */ CALL(sys_getpid)
CALL(sys_mount)
CALL(OBSOLETE(sys_oldumount)) /* used by libc4 */
CALL(sys_setuid16)
CALL(sys_getuid16)
/* 25 */ CALL(OBSOLETE(sys_stime))
CALL(sys_ptrace)
CALL(OBSOLETE(sys_alarm)) /* used by libc4 */
CALL(sys_ni_syscall) /* was sys_fstat */
CALL(sys_pause)
/* 30 */ CALL(OBSOLETE(sys_utime)) /* used by libc4 */
CALL(sys_ni_syscall) /* was sys_stty */
CALL(sys_ni_syscall) /* was sys_getty */
CALL(sys_access)
CALL(sys_nice)
/* 35 */ CALL(sys_ni_syscall) /* was sys_ftime */
CALL(sys_sync)
CALL(sys_kill)
CALL(sys_rename)
CALL(sys_mkdir)
/* 40 */ CALL(sys_rmdir)
CALL(sys_dup)
CALL(sys_pipe)
CALL(sys_times)
CALL(sys_ni_syscall) /* was sys_prof */
/* 45 */ CALL(sys_brk)
CALL(sys_setgid16)
CALL(sys_getgid16)
CALL(sys_ni_syscall) /* was sys_signal */
CALL(sys_geteuid16)
/* 50 */ CALL(sys_getegid16)
CALL(sys_acct)
CALL(sys_umount)
CALL(sys_ni_syscall) /* was sys_lock */
CALL(sys_ioctl)
/* 55 */ CALL(sys_fcntl)
  .......

CALL(sys_eventfd2)
CALL(sys_epoll_create1)
CALL(sys_dup3)
CALL(sys_pipe2)
/* 360 */ CALL(sys_inotify_init1)
CALL(sys_preadv)
CALL(sys_pwritev)
CALL(sys_rt_tgsigqueueinfo)
CALL(sys_perf_event_open)
CALL(sys_pk) #我自己加的系統調用

了解了一個系統調用的執行過程就可以試著添加一個自己的系統調用了:

內核:

1. 在內核代碼實現一個系統調用函數,即 sys_xxx()函數,如我在 kernel/printk.c 中添加了

void pk()

{

  printk(KERN_WARNING"this is my first sys call !\n");

}

2. 添加系統調用號 在 arch/arm/include/asm/Unistd.h

添加 #define __NR_pk (__NR_SYSCALL_BASE+365)

3. 添加調用函數指針列表 在arch/arm/keenel/Calls.S

添加 CALL(sys_pk)

4. 聲明自己的系統調用函數 在include/linux/syscall.h

添加asmlinkage long sys_pk()

用戶空間:

void pk()

  {

    __asm__(

    "ldr r7 =365 \n"

    "swi \n"

    :

    :

    :

    );

  }

  int main()

  {

    pk();

    retrun 0;

  }

完成上面的編寫以後就可以編譯內核和應用程序了。

將生成的文件在arm開發板上運行可以打印出: This is my first sys call!

說明我添加的系統調用可以使用。

至此,描述系統調用的實現機制和添加一個新的系統調用就完成了。

Copyright © Linux教程網 All Rights Reserved