天天看點

Linux系統調用過程

1 系統調用的作用

系統調用是作業系統提供給使用者(應用程式)的一組接口,每個系統調用都有一個對應的系統調用函數來完成相應的工作。使用者通過這個接口向作業系統申請服務,如通路硬體,管理程序等等。

應用程式和檔案系統的接口是系統調用。

Linux系統調用過程

我們經常看到的比如fork、open、write 等等函數實際上并不是真正的系統調用函數,他們都隻是c庫,在這些函數裡将執行一個軟中斷 swi 指令,産生一個軟中斷,使CPU 陷入核心态,接着在核心中進行一系列的判斷,判斷出是哪個系統調用,再轉到真正的系統調用函數,完成相應的功能。

2 系統調用過程

http://www.linuxidc.com/Linux/2015-04/116546.htm

系統調用是作業系統提供給使用者(應用程式)的一組接口,每個系統調用都有一個對應的系統調用函數來完成相應的工作。使用者通過這個接口向作業系統申請服務,如通路硬體,管理程序等等。但是因為使用者程式運作在使用者空間,而系統調用運作在核心空間,是以使用者程式不能直接調用系統調用函數,我們經常看到的比如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

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

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

 /* Legacy ABI only. */

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

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

#ifdef CONFIG_ALIGNMENT_TRAP

 ldr ip, __cr_alignment

 ldr ip, [ip]

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

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

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

使用者空間:

     retrun 0;

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

将生成的檔案在arm開發闆上運作可以列印出: This is my first sys call!

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

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

3 添加自己的系統調用

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

在 arch/arm/include/asm/Unistd.h添加  #define __NR_pk    (__NR_SYSCALL_BASE+365)