# 系统调用的实现

系统调用是用户程序和内核之间沟通的桥梁，没有他内核跑起来也根本用不上。不过我们好像从来就没有正眼看过他。今天我们就来瞧瞧这位一直默默付出的幕后英雄。

## 从调用方式开始

以前在课本上我们学到系统调用是通过int 0x80来实现的。也就是用户程序通过IDT 0x80的这个中断向量和内核发生联系。只不过那已经是很久以前的事情了。温故而知新，那就从新旧两种调用方式入手吧。

### 从前的调用方式

```
.data                   # section declaration

msg:
	.string "Hello, world!\n"
	len = . - msg   # length of our dear string

.text                   # section declaration

                        # we must export the entry point to the ELF linker or
  .global main          # loader. They conventionally recognize _start as their
                        # entry point. Use ld -e foo to override the default.

main:

# write our string to stdout

	movl    $len,%edx   # third argument: message length
	movl    $msg,%ecx   # second argument: pointer to message to write
	movl    $1,%ebx     # first argument: file handle (stdout)
	movl    $4,%eax     # system call number (sys_write)
	int     $0x80       # call kernel

# and exit

	movl    $0,%ebx     # first argument: exit code
	movl    $1,%eax     # system call number (sys_exit)
	int     $0x80       # call kernel
```

可以看到，这种方式就是通过int 0x80来实现的。这段代码在x86平台上还能运行，用如下方式进行编译：

```
# gcc -c hello-ia32.s
# ld -e main -o hell hello-ia32.o
```

### 现在的调用方式

接下来看看新的方式：

```
.data

msg:
	.ascii "Hello World!\n"
	len = . - msg

.text
	.global _start

_start:
	movq $1, %rax
	movq $1, %rdi
	movq $msg, %rsi
	movq $len, %rdx
	syscall

	movq $60, %rax
	xorq %rdi, %rdi
	syscall
```

怎么样，看着是不是没啥大区别？关键是这里使用的是syscall这个指令来完成系统调用。既然如此，那就从这个指令开始吧。

## 从syscall到sys\_call\_table

了解了当前系统调用的实现方式，接着我们就想要了解它是如何同内核中的系统调用函数结合的，如何一步步调用到那些熟悉的系统调用函数的。

想要了解syscall这个指令这一切还要从手册开始。在SDM Volume 2中就有这个指令的详尽解释，这里摘抄一段。

> SYSCALL invokes an OS system-call handler at privilege level 0. It does so by loading RIP from the IA32\_LSTAR MSR (after saving the address of the instruction following SYSCALL into RCX).

原来这次不是通过IDT找到跳转地址，而是通过了一个msr来保存。而这个msr在内核中定义为 MSR\_LSTAR。由此我们找到了内核中这段代码：

```
  wrmsrl(MSR_LSTAR, (unsigned long)entry_SYSCALL_64);
```

在entry\_SYSCALL\_64这段汇编中，又看到了这么一行醒目的代码：

```
  call	do_syscall_64		/* returns with IRQs disabled */
```

当我们进一步打开这个函数时，一切或许就有些明朗了：

```
__visible void do_syscall_64(unsigned long nr, struct pt_regs *regs)
{
	struct thread_info *ti;

	enter_from_user_mode();
	local_irq_enable();
	ti = current_thread_info();
	if (READ_ONCE(ti->flags) & _TIF_WORK_SYSCALL_ENTRY)
		nr = syscall_trace_enter(regs);

	/*
	 * NB: Native and x32 syscalls are dispatched from the same
	 * table.  The only functional difference is the x32 bit in
	 * regs->orig_ax, which changes the behavior of some syscalls.
	 */
	nr &= __SYSCALL_MASK;
	if (likely(nr < NR_syscalls)) {
		nr = array_index_nospec(nr, NR_syscalls);
		regs->ax = sys_call_table[nr](regs);
	}

	syscall_return_slowpath(regs);
}
```

在函数中最后一段的if语句中，可以看到我们通过nr索引了sys\_call\_table的相关项并调用。

这就是我们要找的**系统调用函数表**了。

由此我们一路走来，终于找到了目标。在接着往下探索之前，先来回顾一下我们是怎么走到这里的。

> syscall -> entry\_SYSCALL\_64 -> do\_syscall\_64 -> sys\_call\_table

## sys\_call\_table的构造

走到了这，基本上万里长征完成了大半。接下来就是查看这张表是如何构成的。

这个就是sys\_call\_table的定义了。

```
asmlinkage 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_64.h>
};
```

这个定义中的关键就在最后那个包含的头文件中，而这个文件是编译时生成的

> ./arch/x86/include/generated/asm/syscalls\_64.h

生成的规则是

```
syscall64 := $(srctree)/$(src)/syscall_64.tbl
systbl := $(srctree)/$(src)/syscalltbl.sh

quiet_cmd_systbl = SYSTBL  $@
      cmd_systbl = $(CONFIG_SHELL) '$(systbl)' $< $@

$(out)/syscalls_64.h: $(syscall64) $(systbl)
	$(call if_changed,systbl)
```

如果可以用一个为代码表示这个过程的话，那就是

> syscalls\_64.h = syscalltbl.sh (syscall\_64.tbl)

所以这个头文件syscalls\_64.h是脚本syscalltbl.sh处理文件syscall\_64.tbl的结果。

当你打开syscall\_64.tbl的时候，会有种云开雾散的感觉。

```
#
# 64-bit system call numbers and entry vectors
#
# The format is:
# <number> <abi> <name> <entry point>
#
# The __x64_sys_*() stubs are created on-the-fly for sys_*() system calls
#
# The abi is "common", "64" or "x32" for this file.
#
0	common	read			__x64_sys_read
1	common	write			__x64_sys_write
2	common	open			__x64_sys_open
3	common	close			__x64_sys_close
```

这里就保存这整个系统调用的对应关系。

看到了生成头文件的原料，那来看看生成的头文件是什么样子。打开生成的syscalls\_64.h，看到syscall\_64.tbl中的每一行都展开成如下的形式：

```
#ifdef CONFIG_X86
__SYSCALL_64(0, __x64_sys_read, )
#else /* CONFIG_UML */
__SYSCALL_64(0, sys_read, )
#endif
```

而这个\_\_SYSCALL\_64的宏定义如下：

```
#define __SYSCALL_64(nr, sym, qual) [nr] = sym,
```

这样可能还不是很清楚，我们再把这个代入到sys\_call\_table的定义中再看一眼。

```
asmlinkage const sys_call_ptr_t sys_call_table[__NR_syscall_max+1] = {
    [0 ... __NR_syscall_max] = &sys_ni_syscall,
    [0] = __x64_sys_read,
    [1] = __x64_sys_write,
    [2] = __x64_sys_open,
    ...
    ...
    ...
};
```

## 最后一环

表也有了，现在就差最后一环了，函数\_\_x64\_sys\_read()在哪里？这事我们还得倒过来看。

正着找找不到，那我们就反过来找。从read系统调用的定义入手。

```
SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
{
	return ksys_read(fd, buf, count);
}
```

这其中有个宏SYSCALL\_DEFINE3。

```
#define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)

#define SYSCALL_DEFINEx(x, sname, ...)				\
	SYSCALL_METADATA(sname, x, __VA_ARGS__)			\
	__SYSCALL_DEFINEx(x, sname, __VA_ARGS__)
```

最后的最后，还有一个\_\_SYSCALL\_DEFINEx。

```
#define __SYSCALL_DEFINEx(x, name, ...)					\
	asmlinkage long __x64_sys##name(const struct pt_regs *regs);	\
	ALLOW_ERROR_INJECTION(__x64_sys##name, ERRNO);			\
	static long __se_sys##name(__MAP(x,__SC_LONG,__VA_ARGS__));	\
	static inline long __do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__));\
	asmlinkage long __x64_sys##name(const struct pt_regs *regs)	\
	{								\
		return __se_sys##name(SC_X86_64_REGS_TO_ARGS(x,__VA_ARGS__));\
	}								\
	__IA32_SYS_STUBx(x, name, __VA_ARGS__)				\
	static long __se_sys##name(__MAP(x,__SC_LONG,__VA_ARGS__))	\
	{								\
		long ret = __do_sys##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 __do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__))
```

这玩意确实有点长，不过你是不是看到了\_\_x64\_sys##name的字样呢？

好了，我相信你已经懂了。在sys\_call\_table表中填入的函数入口，就是由\_\_SYSCALL\_DEFINEx定义出来的。

经过了一番探索，我们终于理清了系统调用在linux中是如何定义和关联的。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://richardweiyang-2.gitbook.io/kernel-exploring/00-start_from_hardware/03-syscall.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
