調査対象版数
linux-4.2(mainline)
どこから読むか?
本当に大きい。始めはどこを読んだら良いかで途方にくれそう。
どこから読もうかと考えたけど、ブートとかよりforkとかexecとかから入った方が、広がりそうな感じがする。
と言うかここら辺の方から読みたいかなと。
で、システムエントリがどう言う実装かを調べるところから始めることに。
ネットで検索すると、次のサイトがあった。
システムコールのエントリテーブルは、sys_call_tableらしい。
sys_call_tableの定義
#undef __SYSCALL
#define __SYSCALL(nr, call) [nr] = (call),
void *sys_call_table[NR_syscalls] = {
[0 ... NR_syscalls-1] = sys_ni_syscall,
#include <asm/unistd.h>
};
エントリテーブルの中身は#include <asm/unistd.h>
にある。これを見てみる。
その前に、sys_call_tableの定義で、[0 ... NR_syscalls-1] = sys_ni_syscall,
とある。あと__SYSCALL(nr, call) [nr] = (call),
。
これは配列の要素を初期化/設定するコードのはず。
ちょっと簡単なサンプルコードで実験してみる。
#include <stdio.h>
#define TBLSIZ 10
int tbl[TBLSIZ] = {
[0 ... TBLSIZ-1] = 123,
[7] = 777,
};
int main()
{
int i, c = sizeof(tbl) / sizeof(tbl[0]);
for (i = 0; i < c; i++) {
printf("tbl[%d]=%d\n", i, tbl[i]);
}
}
実行結果は、次の通り。
kou77@ubuntu:~/test$ gcc tes015.c
kou77@ubuntu:~/test$ ./a.out
tbl[0]=123
tbl[1]=123
tbl[2]=123
tbl[3]=123
tbl[4]=123
tbl[5]=123
tbl[6]=123
tbl[7]=777
tbl[8]=123
tbl[9]=123
sys_call_tableは、void*の配列だけど、サンプルコードでは設定値が分かり易くなるので、intの配列にした。
結果的には思った通り。(インデックス7は777が入って、それ以外は123が入っている)
調査に戻って、#include <asm/unistd.h>
の定義内容の参照から。
各エントリは__SYSCALLマクロが使われているものと予想できる。
asm/unistd.hの定義内容を確認しようとしたら、archフォルダの下とかに沢山asm/unistd.hファイルが見つかる。
./arch/unicore32/include/uapi/asm/unistd.h
./arch/powerpc/include/asm/unistd.h
./arch/powerpc/include/uapi/asm/unistd.h
./arch/tile/include/asm/unistd.h
./arch/tile/include/uapi/asm/unistd.h
./arch/nios2/include/uapi/asm/unistd.h
./arch/openrisc/include/uapi/asm/unistd.h
./arch/microblaze/include/asm/unistd.h
./arch/microblaze/include/uapi/asm/unistd.h
./arch/arm/include/asm/unistd.h
./arch/arm/include/uapi/asm/unistd.h
./arch/c6x/include/uapi/asm/unistd.h
./arch/xtensa/include/asm/unistd.h
./arch/xtensa/include/uapi/asm/unistd.h
./arch/parisc/include/asm/unistd.h
./arch/parisc/include/uapi/asm/unistd.h
./arch/mips/include/asm/unistd.h
./arch/mips/include/uapi/asm/unistd.h
./arch/x86/include/asm/unistd.h
./arch/x86/include/uapi/asm/unistd.h
./arch/m32r/include/asm/unistd.h
./arch/m32r/include/uapi/asm/unistd.h
./arch/h8300/include/uapi/asm/unistd.h
./arch/s390/include/asm/unistd.h
./arch/s390/include/uapi/asm/unistd.h
./arch/hexagon/include/uapi/asm/unistd.h
./arch/mn10300/include/asm/unistd.h
./arch/mn10300/include/uapi/asm/unistd.h
./arch/metag/include/asm/unistd.h
./arch/metag/include/uapi/asm/unistd.h
./arch/avr32/include/asm/unistd.h
./arch/avr32/include/uapi/asm/unistd.h
./arch/sparc/include/asm/unistd.h
./arch/sparc/include/uapi/asm/unistd.h
./arch/sh/include/asm/unistd.h
./arch/sh/include/uapi/asm/unistd.h
./arch/blackfin/include/asm/unistd.h
./arch/blackfin/include/uapi/asm/unistd.h
./arch/m68k/include/asm/unistd.h
./arch/m68k/include/uapi/asm/unistd.h
./arch/frv/include/asm/unistd.h
./arch/frv/include/uapi/asm/unistd.h
./arch/score/include/uapi/asm/unistd.h
./arch/arm64/include/asm/unistd.h
./arch/arm64/include/uapi/asm/unistd.h
./arch/cris/include/asm/unistd.h
./arch/cris/include/arch-v32/arch/unistd.h
./arch/cris/include/uapi/asm/unistd.h
./arch/cris/include/arch-v10/arch/unistd.h
./arch/ia64/include/asm/unistd.h
./arch/ia64/include/uapi/asm/unistd.h
./arch/alpha/include/asm/unistd.h
./arch/alpha/include/uapi/asm/unistd.h
./arch/arc/include/uapi/asm/unistd.h
./include/asm-generic/unistd.h
./include/uapi/asm-generic/unistd.h
./include/uapi/linux/unistd.h
archにあるのは、アーキテクチャに依存したファイルが置かれているのだろうから、共通的に参照されるファイルから探してみることにする。
まず、asm/unistd.hは、sys_call_tableの定義の中身で、__SYSCALLマクロを使用した記述が列挙されていると思われる。
./include/asm-generic/unistd.hを見ると、
#include <uapi/asm-generic/unistd.h>
#include <linux/export.h>
/*
* These are required system calls, we should
* invert the logic eventually and let them
* be selected by default.
*/
#if __BITS_PER_LONG == 32
#define __ARCH_WANT_STAT64
#define __ARCH_WANT_SYS_LLSEEK
#endif
./include/uapi/asm-generic/unistd.h、./include/linux/export.hを続けてみる。
システムコールのエントリが定義されているはずなので、./include/uapi/asm-generic/unistd.hはちょっと大きい。
./include/linux/export.hの中はマクロとかの定義があって、sys_call_tableの中身の記述らしいところはなさそうなので、
こちらは置いておいて、./include/uapi/asm-generic/unistd.hのコードの抜粋を以下に。
#include <asm/bitsperlong.h>
/*
* This file contains the system call numbers, based on the
* layout of the x86-64 architecture, which embeds the
* pointer to the syscall in the table.
*
* As a basic principle, no duplication of functionality
* should be added, e.g. we don't use lseek when llseek
* is present. New architectures should use this file
* and implement the less feature-full calls in user space.
*/
#ifndef __SYSCALL
#define __SYSCALL(x, y)
#endif
#if __BITS_PER_LONG == 32 || defined(__SYSCALL_COMPAT)
#define __SC_3264(_nr, _32, _64) __SYSCALL(_nr, _32)
#else
#define __SC_3264(_nr, _32, _64) __SYSCALL(_nr, _64)
#endif
#ifdef __SYSCALL_COMPAT
#define __SC_COMP(_nr, _sys, _comp) __SYSCALL(_nr, _comp)
#define __SC_COMP_3264(_nr, _32, _64, _comp) __SYSCALL(_nr, _comp)
#else
#define __SC_COMP(_nr, _sys, _comp) __SYSCALL(_nr, _sys)
#define __SC_COMP_3264(_nr, _32, _64, _comp) __SC_3264(_nr, _32, _64)
#endif
/*途中略・・・*/
#define __NR_uselib 1077
__SYSCALL(__NR_uselib, sys_uselib)
#define __NR__sysctl 1078
__SYSCALL(__NR__sysctl, sys_sysctl)
#define __NR_fork 1079
#ifdef CONFIG_MMU
__SYSCALL(__NR_fork, sys_fork)
#else
__SYSCALL(__NR_fork, sys_ni_syscall)
#endif /* CONFIG_MMU */
/*以下略・・・*/
forkのエントリの定義のところで、CONFIG_MMUのdefineを見ている。
(makeの)configがどうなるのか分からないのだけど、forkがシステムコールエントリにないのは考え難いので、
通常動くlinuxの場合、CONFIG_MMUは定義されているのだろう。
因みに、sys_ni_syscallの定義は、次の通り。
kernel\sys_ni.c(14): asmlinkage long sys_ni_syscall(void)
/*
* Non-implemented system calls get redirected here.
*/
asmlinkage long sys_ni_syscall(void)
{
return -ENOSYS;
}
上記は、エントリが未実装の場合に呼び出される関数。
上記のコードの抜粋にあるforkのエントリをサンプルに、登録している関数を調べてみる。
sys_forkの定義
sys_forkで検索してみたが、関数の定義らしいものが見つからない。
kou77@ubuntu:~/linux-4.2$ find . \( -name \*.c -o -name \*.h \) -exec grep 'sys_fork' {} /dev/null \;
./arch/openrisc/include/asm/syscalls.h:asmlinkage long __sys_fork(void);
./arch/openrisc/include/asm/syscalls.h:#define sys_fork __sys_fork
./arch/mips/kernel/syscall.c:save_static_function(sys_fork);
./arch/x86/um/sys_call_table_64.c:#define stub_fork sys_fork
./arch/x86/include/generated/asm/syscalls_32.h:__SYSCALL_I386(2, sys_fork, stub32_fork)
./arch/sparc/kernel/process_32.c: * sys_fork invocation and when we reach here
./arch/arm64/include/asm/unistd32.h:__SYSCALL(__NR_fork, sys_fork)
./include/linux/syscalls.h:asmlinkage long sys_fork(void);
./include/uapi/asm-generic/unistd.h:__SYSCALL(__NR_fork, sys_fork)
archの下に何か定義があるけど、関数本体の定義がない。archは違うだろうし。
おそらくこれはマクロ等で直接見えなくなっているのだろうと考えて検索した結果、次が見つかった。
./kernel/fork.c:SYSCALL_DEFINE0(fork)
この定義の全体は、
#ifdef __ARCH_WANT_SYS_FORK
SYSCALL_DEFINE0(fork)
{
#ifdef CONFIG_MMU
return _do_fork(SIGCHLD, 0, 0, NULL, NULL, 0);
#else
/* can not support in nommu mode */
return -EINVAL;
#endif
}
#endif
SYSCALL_DEFINE0マクロの定義は、次の通り。
./include/linux/syscalls.h:178:#define SYSCALL_DEFINE0(sname) \
#define SYSCALL_DEFINE0(sname) \
SYSCALL_METADATA(_##sname, 0); \
asmlinkage long sys_##sname(void)
SYSCALL_METADATAを除いて、asmlinkage long sys_##sname(void)の部分は、
SYSCALL_DEFINE0(fork)を例にすると、asmlinkage long sys_fork(void)に置き換わる。
やっと、sys_fork関数の定義が見つかった。
asmlinkageは、C++でコンパイルした時に、Cコンパイラで書かれたソースから呼び出し参照ができるような
シンボルが書き出されるようにする記述のようだ。
asmlinkageの定義と思われるところは、次の通り。
./tools/lib/lockdep/uinclude/linux/lockdep.h:13:#define asmlinkage
./arch/x86/include/asm/linkage.h:10:#define asmlinkage CPP_ASMLINKAGE __attribute__((regparm(0)))
./arch/mn10300/include/asm/linkage.h:15:#define asmlinkage
./arch/ia64/include/asm/linkage.h:6:#define asmlinkage CPP_ASMLINKAGE __attribute__((syscall_linkage))
./include/linux/linkage.h:21:#define asmlinkage CPP_ASMLINKAGE
./include/linux/linkage.hの定義が一般的に有効になる定義と判断。
x86の場合は、#define asmlinkage CPP_ASMLINKAGE attribute((regparm(0)))の定義が有効になるのかも
知れない。regparmは引数をレジスタ渡しにするものらしい。(確認は別途実施してみよう)
CPP_ASMLINKAGEの定義は、次の通り。
#ifdef __cplusplus
#define CPP_ASMLINKAGE extern "C"
#else
#define CPP_ASMLINKAGE
#endif
fork(本体)のコード
軽くforkの本体のコードも見てみる。
_do_forkの定義は、次の通り。
./kernel/fork.c:1679:long _do_fork(unsigned long clone_flags,
/*
* Ok, this is the main fork-routine.
*
* It copies the process, and if successful kick-starts
* it and waits for it to finish using the VM if required.
*/
long _do_fork(unsigned long clone_flags,
unsigned long stack_start,
unsigned long stack_size,
int __user *parent_tidptr,
int __user *child_tidptr,
unsigned long tls)
{
struct task_struct *p;
int trace = 0;
long nr;
/*
* Determine whether and which event to report to ptracer. When
* called from kernel_thread or CLONE_UNTRACED is explicitly
* requested, no event is reported; otherwise, report if the event
* for the type of forking is enabled.
*/
if (!(clone_flags & CLONE_UNTRACED)) {
if (clone_flags & CLONE_VFORK)
trace = PTRACE_EVENT_VFORK;
else if ((clone_flags & CSIGNAL) != SIGCHLD)
trace = PTRACE_EVENT_CLONE;
else
trace = PTRACE_EVENT_FORK;
if (likely(!ptrace_event_enabled(current, trace)))
trace = 0;
}
p = copy_process(clone_flags, stack_start, stack_size,
child_tidptr, NULL, trace, tls);
/*
* Do this prior waking up the new thread - the thread pointer
* might get invalid after that point, if the thread exits quickly.
*/
if (!IS_ERR(p)) {
struct completion vfork;
struct pid *pid;
trace_sched_process_fork(current, p);
pid = get_task_pid(p, PIDTYPE_PID);
nr = pid_vnr(pid);
if (clone_flags & CLONE_PARENT_SETTID)
put_user(nr, parent_tidptr);
if (clone_flags & CLONE_VFORK) {
p->vfork_done = &vfork;
init_completion(&vfork);
get_task_struct(p);
}
wake_up_new_task(p);
/* forking complete and child started to run, tell ptracer */
if (unlikely(trace))
ptrace_event_pid(trace, pid);
if (clone_flags & CLONE_VFORK) {
if (!wait_for_vfork_done(p, &vfork))
ptrace_event_pid(PTRACE_EVENT_VFORK_DONE, pid);
}
put_pid(pid);
} else {
nr = PTR_ERR(p);
}
return nr;
}
確かにこれが本体のようだ。
今回の記事では、forkの解析は目的ではないので、これ以上深入りしない。
forkは引数がないが、引数があるシステムコールのエントリに登録している関数はどのようになっているのだろうか。
比較的に簡単な実装と思われるsetgidをサンプルに見てみる。
/*
* setgid() is implemented like SysV w/ SAVED_IDS
*
* SMP: Same implicit races as above.
*/
SYSCALL_DEFINE1(setgid, gid_t, gid)
{
/*以下略・・・*/
#define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__)
#define SYSCALL_DEFINEx(x, sname, ...) \
SYSCALL_METADATA(sname, x, __VA_ARGS__) \
__SYSCALL_DEFINEx(x, sname, __VA_ARGS__)
#define __SYSCALL_DEFINEx(x, name, ...) \
asmlinkage long sys##name(__MAP(x,__SC_DECL,__VA_ARGS__)) \
__attribute__((alias(__stringify(SyS##name)))); \
static inline long SYSC##name(__MAP(x,__SC_DECL,__VA_ARGS__)); \
asmlinkage long SyS##name(__MAP(x,__SC_LONG,__VA_ARGS__)); \
asmlinkage long SyS##name(__MAP(x,__SC_LONG,__VA_ARGS__)) \
{ \
long ret = SYSC##name(__MAP(x,__SC_CAST,__VA_ARGS__)); \
__MAP(x,__SC_TEST,__VA_ARGS__); \
__PROTECT(x, ret,__MAP(x,__SC_ARGS,__VA_ARGS__)); \
return ret; \
} \
static inline long SYSC##name(__MAP(x,__SC_DECL,__VA_ARGS__))
これは噛みごたえがある。調べた範囲で簡単に書くと、setgidの場合だと、次のような感じでマクロが展開される。
- sys_setgidの別名で、SyS_setgidを定義。sys_setgidは実体がない。の代わりにSyS_setgidの実体が定義される。
- SyS_setgidはaliasで定義されていて他のソースから参照できない。(デフォルトがweak?)
他のソースから参照できるのは、sys_setgidの名前。 - SyS_setgidの中からSYSC_setgidが呼び出されていて、SYSC_setgidの関数の中身が、
./kernel/fork.c:SYSCALL_DEFINE0(fork)の定義に繋がる。
と言う感じ。
まだ細かく見てないところもあるが、__MAPとかの定義の仕方がとても興味深い。
ここの実装は面白いので、内容をもう少し理解した上で、別の記事で説明するかも。