歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> Linux技術 >> linux系統調用原理

linux系統調用原理

日期:2017/3/3 12:54:15   编辑:Linux技術

x86架構

trap_init

在系統啟動的時候start_kernel會調用trap_init來初始化異常向量表
[code]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
[code]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是在哪裡定義的:
[code]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
[code]#!/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部分內容如下:
[code]#
# 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的部分內容:
[code]__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) 編譯和安裝內核

測試自定義的系統調用

[code]#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來初始化異常向量表
[code]start_kernel
    trap_init
        set_except_vector(8, handle_sys);
            exception_handlers
 = 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文件中定義
[code]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

[code]    .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的入口地址。
[code]...
/* 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)
...

參考文章

linux系統調用的基本原理以及添加系統調用的方法
linux自定義系統調用
Copyright © Linux教程網 All Rights Reserved