1. 程式人生 > >2018-2019-1 20189219《Linux核心原理與分析》第六週作業

2018-2019-1 20189219《Linux核心原理與分析》第六週作業

關於sys_call與sys_call_table的關係

從書中我們得到,在進行sys_call時,系統會執行entry_32.S中的call *sys_call_table(,%eax,4)語句,即呼叫sys_call_table中的%eax*4的系統呼叫核心函式。我們知道,eax暫存器中存放的是要呼叫的系統函式的呼叫號,那為何要*4?

sys_call_table

要了解上述的問題,我們必須要了解的是sys_call_table究竟是何方神聖。我們在/linux-3.18.6/arch/x86/kernel/syscall_32.c裡找到了sys_call_table的定義:

/* System call table for i386. */

#include <linux/linkage.h>
#include <linux/sys.h>
#include <linux/cache.h>
#include <asm/asm-offsets.h>

#define __SYSCALL_I386(nr, sym, compat) extern asmlinkage void sym(void) ;
#include <asm/syscalls_32.h>
#undef __SYSCALL_I386

#define __SYSCALL_I386(nr, sym, compat) [nr] = sym,

typedef asmlinkage void (*sys_call_ptr_t)(void);

extern asmlinkage void sys_ni_syscall(void);

__visible 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>
};

開頭第一行註釋就告訴我們,這就是系統呼叫表。在這裡我們看到了兩個關鍵點:

  • sys_call_table定義
__visible 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>
};

從這個函式中,我們看出table其實是個陣列,包含兩個部分:

  • &sys_ni_syscall
    將陣列的每個元素都定義了一個sys_ni_syscall地址,
asmlinkage long sys_ni_syscall(void);

/*
 * Non-implemented system calls get redirected here.
 */
asmlinkage long sys_ni_syscall(void)
{
    return -ENOSYS;
}

從註釋中可以看出,這個函式是用來處理未實現的系統呼叫,讓其重新進行在此處進行重定向,可以理解為是提高程式碼的容錯率而提供的一種機制。

  • #include <asm/syscalls_32.h>
    這可謂是table中的重頭戲,為什麼要包含一個頭檔案在陣列中?這個標頭檔案裡面的內容是什麼?到現在為止我們無從得知,所以我們要繼續檢視相關的其他核心程式碼。
  • #define __SYSCALL_I386(nr, sym, compat) [nr] = sym & asmlinkage void sym(void)
    這裡我們看到了這個看似奇怪的定義,直覺告訴我們,這個定義或許和syscalls_32.h標頭檔案中的內容有關,但是我們依然無法得知具體的關係和意義,那麼我們來找找這個標頭檔案吧。

sys_call_table中的重要標頭檔案

找了半天發現,在相關的核心程式碼資料夾中根本沒有發現syscalls_32.h的詳細定義。這說明syscalls_32.h有可能是在核心啟動的過程中才生成的,於是,跟著書上的提示,我們找到syscalls資料夾,發現其中內容有如下:

逐一開啟:

  • Makefile:
1   out := $(obj)/../include/generated/asm

8   syscall32 := $(srctree)/$(src)/syscall_32.tbl
9   syscall64 := $(srctree)/$(src)/syscall_64.tbl
10
11  syshdr := $(srctree)/$(src)/syscallhdr.sh
12  systbl := $(srctree)/$(src)/syscalltbl.sh

45  $(out)/syscalls_32.h: $(syscall32) $(systbl)
46  $(call if_changed,systbl)
47  $(out)/syscalls_64.h: $(syscall64) $(systbl)
48  $(call if_changed,systbl)

我們精簡一下Makefile中的內容,得到如上內容。豁然開朗!這裡不就告訴我們$(out)/syscalls_32.h的依然就是$(syscall32)$(systbl)麼!補全之後就是說$(obj)/../include/generated/asm/syscalls_32.h是由$(srctree)/$(src)/syscall_32.tbl&$(srctree)/$(src)/syscalltbl.sh生成的啊!這不就是我們苦苦尋找的syscalls_32.h麼。二話不說,我們看看syscalls資料夾下的其他兩個有關的檔案:

  • 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
7   i386    waitpid         sys_waitpid         sys32_waitpid
8   i386    creat           sys_creat
9   i386    link            sys_link
10  i386    unlink          sys_unlink
11  i386    execve          sys_execve          stub32_execve
12  i386    chdir           sys_chdir
13  i386    time            sys_time            compat_sys_time
  • 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"

可以看到tbl(tablelist)中包含即為我們所有的系統呼叫核心函式,這裡因為數量過多就沒有完全列出。看到上述程式碼之後大家應該就徹底明白了,在整個syscalls資料夾中系統完成了對$(obj)/../include/generated/asm/syscalls_32.h的生成。下面我們模擬一下,更好的理解這個標頭檔案的生成過程。

.
├── Makefile
├── syscall_32.tbl
└── syscalltbl.sh

由於核心程式碼的複雜性,其中我們改寫了Makefile,其餘都保持和核心程式碼一樣,這樣更符合一般的編寫習慣:

  • Makefile
syscall32 := ./syscall_32.tbl
systbl := ./syscalltbl.sh
./syscalls_32.h: $(syscall32) $(systbl)
    sh ./syscalltbl.sh "$(syscall32)" "./syscalls_32.h"

在當前目錄下make之後發現生成了syscalls_32.h檔案:

.
├── Makefile
├── syscall_32.tbl
├── syscalls_32.h
└── syscalltbl.sh
  • 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)
__SYSCALL_I386(7, sys_waitpid, sys32_waitpid)
__SYSCALL_I386(8, sys_creat, sys_creat)
__SYSCALL_I386(9, sys_link, sys_link)
__SYSCALL_I386(10, sys_unlink, sys_unlink)
__SYSCALL_I386(11, sys_execve, stub32_execve)
__SYSCALL_I386(12, sys_chdir, sys_chdir)
__SYSCALL_I386(13, sys_time, compat_sys_time)

這個標頭檔案包含了完整的32位庫中的357個系統呼叫函式,這裡我們只展示前13個。那麼到這裡我們可以很明朗,這個標頭檔案下包含的其實就是各個系統呼叫函式的呼叫號,函式名和程式呼叫口。看到這,我們上面的疑問也都解決了,原來在這個重要的標頭檔案中儲存的就是__SYSCALL_I386(nr, sym, compat),即每個系統呼叫函式的關鍵資訊。所以需要使用#define __SYSCALL_I386(nr, sym, compat) [nr] = sym & asmlinkage void sym(void)來把這個三個關鍵資訊存入table陣列中。
至此,我們的所有疑問都解開了。sys_call_table是一個數組型別,而此陣列中的每個元素存放著用於重定向的函式地址和每個系統呼叫函式的三個關鍵資訊,即每個元素佔用了4個位元組。這就是為什麼在呼叫table中的項時需要使用%eax*4