1. 程式人生 > >linux系統呼叫原理

linux系統呼叫原理

x86架構

trap_init

在系統啟動的時候start_kernel會呼叫trap_init來初始化異常向量表

start_kernel
    trap_init
        set_system_trap_gate(SYSCALL_VECTOR, &system_call);
            ...
                memcpy(&idt[entry], gate, sizeof(*gate));

設定0x80號軟中斷的服務程式為system_call, system_call是所有系統呼叫的總入口.
當程序執行到使用者程式的系統呼叫命令時,實際上執行了由巨集命令_syscallN()展開的函式。系統呼叫的引數由各通用暫存器傳遞,比如通過eax暫存器傳遞系統呼叫號和系統呼叫返回值,通過ebx/ecx/edx/esi/edi傳遞系統呼叫引數,然後執行INT 0x80,以核心態進入入口地址system_call。

system_call

在arch/x86/kernel/entry_32.S檔案中定義了system_call,在system_call裡面呼叫了sys_call_table

ENTRY(system_call)
    RING0_INT_FRAME         # can't unwind into user space anyway
    ASM_CLAC
    pushl_cfi %eax          # save orig_eax
    SAVE_ALL
    GET_THREAD_INFO(%ebp)
                    # system call tracing in operation / emulation
testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp) jnz syscall_trace_entry cmpl $(NR_syscalls), %eax jae syscall_badsys syscall_call: call *sys_call_table(,%eax,4) movl %eax,PT_EAX(%esp) # store the return value syscall_exit: LOCKDEP_SYS_EXIT DISABLE_INTERRUPTS(CLBR_ANY) # make sure we don't miss an interrupt
# setting need_resched or sigpending # between sampling and the iret TRACE_IRQS_OFF movl TI_flags(%ebp), %ecx testl $_TIF_ALLWORK_MASK, %ecx # current->work jne syscall_exit_work ... ENDPROC(system_call)

sys_call_table

sys_call_table是在哪裡定義的:

const sys_call_ptr_t sys_call_table[__NR_syscall_max+1] = {
    /*
     * Smells like a compiler bug -- it doesn't work
     * when the & below is removed.
     */
    [0 ... __NR_syscall_max] = &sys_ni_syscall,
#include <asm/syscalls_32.h>
};

編譯核心的時候,當執行到檔案/usr/src/linux-3.10.21/arch/x86/syscalls/Makefile時,該檔案會執行/usr/src/linux-3.10.21/arch/x86/syscalls/目錄下的shell指令碼syscalltbl.sh,該指令碼將同目錄下的syscall_32.tbl檔案作為輸入,然後生成檔案/usr/src/linux-3.10.21/arch/x86/include/generated/asm/syscalls_32.h,這個檔案正是sys_call_table定義中包含的檔案asm/syscalls_32.h。

來看下指令碼syscalltbl.sh

#!/bin/sh

in="$1"
out="$2"

grep '^[0-9]' "$in" | sort -n | (
    while read nr abi name entry compat; do
    abi=`echo "$abi" | tr '[a-z]' '[A-Z]'`
    if [ -n "$compat" ]; then
        echo "__SYSCALL_${abi}($nr, $entry, $compat)"
    elif [ -n "$entry" ]; then
        echo "__SYSCALL_${abi}($nr, $entry, $entry)"
    fi
    done
) > "$out"

其中in和out分別代表的就是syscall_32.tbl和syscalls_32.h檔案的路徑。指令碼大概意思就是讀取syscall_32.tbl內容,然後構造語句__SYSCALL_abi(nr, entry,entry)”。

輸入檔案syscall_32.tbl部分內容如下:

#
# 32-bit system call numbers and entry vectors
#
# The format is:
# <number> <abi> <name> <entry point> <compat entry point>
#
# The abi is always "i386" for this file.
#
0   i386    restart_syscall     sys_restart_syscall
1   i386    exit            sys_exit
2   i386    fork            sys_fork            stub32_fork
3   i386    read            sys_read
4   i386    write           sys_write
5   i386    open            sys_open            compat_sys_open
6   i386    close           sys_close

輸出檔案syscalls_32.h的部分內容:

__SYSCALL_I386(0, sys_restart_syscall, sys_restart_syscall)
__SYSCALL_I386(1, sys_exit, sys_exit)
__SYSCALL_I386(2, sys_fork, stub32_fork)
__SYSCALL_I386(3, sys_read, sys_read)
__SYSCALL_I386(4, sys_write, sys_write)
__SYSCALL_I386(5, sys_open, compat_sys_open)
__SYSCALL_I386(6, sys_close, sys_close)

所以sys_call_table的定義中包含了asm/syscall_32.h,就相當於包含了上面這麼多巨集定義,sys_call_table就是採用這種方法定義的。

新增自己的系統呼叫

1) 在檔案/usr/src/linux-3.10/arch/x86/syscalls/syscall_32.tbl中加入自定義的系統呼叫號和函式名
這裡寫圖片描述

2) 在檔案/usr/src/linux-3.10/arch/x86/include/asm/syscalls.h檔案中加入sys_foo函式的宣告
這裡寫圖片描述

3) 在檔案/usr/src/linux-3.10/kernel/sys.c檔案中加入對sys_foo的定義
這裡寫圖片描述

4) 編譯和安裝核心

測試自定義的系統呼叫

#include<stdio.h>
#define __NR_foo 351

int main(void)
{
    int rs;

    rs = syscall(__NR_foo);
    if (!rs)
        printf("syscall success!\n");
    return 0;
}

程式碼中用到了syscall函式,這個函式功能就是根據給定的系統呼叫號來呼叫系統呼叫
編譯執行上面的程式碼,用dmesg可以看到系統輸出的資訊:
這裡寫圖片描述

MIPS架構

trap_init

和x86架構一樣,在系統啟動的時候start_kernel會呼叫trap_init來初始化異常向量表

start_kernel
    trap_init
        set_except_vector(8, handle_sys);
            exception_handlers[n] = handler;
        memcpy((void *) (RLX_TRAP_VEC_BASE), &rlx_trap_dispatch, RLX_TRAP_VEC_SIZE);

RLX_TRAP_VEC_BASE的地址是0x8000080,相當於是將異常處理函式rlx_trap_dispatch的地址複製到0x8000080的地方,當發生異常中斷的時候,便會跳到rlx_trap_dispatch的地方執行。

rlx_trap_dispatch

在arch/rlx/genex.S檔案中定義

NESTED(rlx_trap_dispatch, 0, sp)
    .set    push
    .set    noat
    mfc0    k1, CP0_CAUSE
    andi    k1, k1, 0x7c
    PTR_L   k0, exception_handlers(k1)
    jr  k0
    .set    pop
    END(rlx_trap_dispatch)

這個異常處理函式又會呼叫exception_handlers, 他就是之前用set_except_vector函式賦值的exception_handlers陣列,這裡我們只關心去呼叫handle_sys函式。handle_sys函式在arch/rlx/kernel/scall32-o32.S中定義,他裡面又呼叫了sys_call_table。

sys_call_table

    .macro  syscalltable
    sys sys_syscall     8   /* 4000 */
    sys sys_exit        1
    sys __sys_fork      0
    sys sys_read        3
    sys sys_write       3
    sys sys_open        3   /* 4005 */
    sys sys_close       1
    ...
    .type   sys_call_table,@object
EXPORT(sys_call_table)

unistd.h

檔案include/uapi/asm-generic/unistd.h為每個系統呼叫規定了唯一的編號,根據這個編號,可以在系統呼叫表sys_call_table中找到對應表項的內容,他正好是該系統呼叫的響應函式sys_name的入口地址。

...
/* net/socket.c */
#define __NR_socket 198
__SYSCALL(__NR_socket, sys_socket)
#define __NR_socketpair 199
__SYSCALL(__NR_socketpair, sys_socketpair)
#define __NR_bind 200
__SYSCALL(__NR_bind, sys_bind)
#define __NR_listen 201
__SYSCALL(__NR_listen, sys_listen)
#define __NR_accept 202
__SYSCALL(__NR_accept, sys_accept)
#define __NR_connect 203
__SYSCALL(__NR_connect, sys_connect)
#define __NR_getsockname 204
__SYSCALL(__NR_getsockname, sys_getsockname)
#define __NR_getpeername 205
__SYSCALL(__NR_getpeername, sys_getpeername)
#define __NR_sendto 206
__SYSCALL(__NR_sendto, sys_sendto)
#define __NR_recvfrom 207
__SC_COMP(__NR_recvfrom, sys_recvfrom, compat_sys_recvfrom)
...

參考文章