从书中我们得到,在进行sys_call时,系统会执行entry_32.S中的call *sys_call_table(,%eax,4)
语句,即调用sys_call_table
中的%eax*4
的系统调用内核函数。我们知道,eax寄存器中存放的是要调用的系统函数的调用号,那为何要*4?
要了解上述的问题,我们必须要了解的是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>
};
开头第一行注释就告诉我们,这就是系统调用表。在这里我们看到了两个关键点:
__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
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>
#define __SYSCALL_I386(nr, sym, compat) [nr] = sym
& asmlinkage void sym(void)
syscalls_32.h
头文件中的内容有关,但是我们依然无法得知具体的关系和意义,那么我们来找找这个头文件吧。找了半天发现,在相关的内核代码文件夹中根本没有发现syscalls_32.h
的详细定义。这说明syscalls_32.h
有可能是在内核启动的过程中才生成的,于是,跟着书上的提示,我们找到syscalls文件夹,发现其中内容有如下:
图
逐一打开:
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文件夹下的其他两个有关的文件:
#
# 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
#!/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,其余都保持和内核代码一样,这样更符合一般的编写习惯:
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
__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
。
2018-2019-1 20189219《Linux内核原理与分析》第六周作业
原文:https://www.cnblogs.com/archemiya/p/9977788.html