0%

FreeBSD源码笔记04-系统调用解析

本文旨在分析FreeBSD系统调用的amd64平台相关部分的代码,系统调用作为系统内核与外界交互的接口,了解其原理对如何扩展以及如何使用有很大的帮助,不同于x86平台的系统调用路径,amd64的平台调用依赖于syscall指令,接下来我记录一下.

1. 系统调用硬件知识(MSR)

MSR是(Model-Specific Register)的缩写,是x86_64平台上CPU实现的一类寄存器,提供了对硬件或者软件相关功能的一些控制.具体内容参考《x86_64体系探索及编程》一书的详细介绍.这里介绍几个重点

  • 1.1 使用MSR
    读取MSR指令的汇编指令分别是rdmsr和wrmsr指令.
  • 1.2 EFER扩展功能寄存器的使用
    因为读取这个寄存器的某些位表示CPU是否支持MSR的syscal指令,x64下long-mode模式也是通过这个寄存器来开启和激活的.

    如上图所示,第0位SCE位是syscall Enable的缩写,此位置1表示syscall指令可用.LME置1表示long-mode模式开启。EFER寄存器的地址为0xC0000080H,这个地址是固定的.

  • 1.3 支持syscall的MSR寄存器
    这里主要说明一下LSTSTR寄存器是64位CPU的系统入口点.执行syscall指令时将会将这个寄存器的函数地址弹出到EIP中执行,参考这篇文章

  • 1.4 系统调用辅助指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 这段代码是对rdmsr汇编指令的封装
// freebsd/amd64.amd64/sys/MYKERNEL/machine/cpufunc.h
static __inline uint64_t
rdmsr(u_int msr)
{
uint32_t low, high;

__asm __volatile("rdmsr" : "=a" (low), "=d" (high) : "c" (msr));
return (low | ((uint64_t)high << 32));
}

// line:495
// 这一段是对wrmsr汇编指令的封装
static __inline void
wrmsr(u_int msr, uint64_t newval)
{
uint32_t low, high;

low = newval;
high = newval >> 32;
__asm __volatile("wrmsr" : : "a" (low), "d" (high), "c" (msr));
}

2. 系统调用配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// amd64/amd64/machdep.c[line:1536]
/* Set up the fast syscall stuff */
// 这一段代码是初始化时设置系统调用处理函数
// 系统调用处理函数是fast_syscall_pti或者fast_syscall
void
amd64_conf_fast_syscall(void)
{
uint64_t msr;

// 参看1.2节的说明这是EFER寄存器地址
// #define MSR_EFER 0xc0000080 /* extended features */
// #define EFER_SCE 0x000000001 /* System Call Extensions (R/W) */
msr = rdmsr(MSR_EFER) | EFER_SCE;
// 允许CPU使用syscall指令(这是扩展指令)
wrmsr(MSR_EFER, msr);
// #define MSR_LSTAR 0xc0000082 /* long mode SYSCALL target rip */
// 参看1.3节的内容
// 将fast_syscall_pti或者fast_syscall的调用地址放入到MSR_LSTAR寄存器.
wrmsr(MSR_LSTAR, pti ? (u_int64_t)IDTVEC(fast_syscall_pti) :
(u_int64_t)IDTVEC(fast_syscall));
// MSR_CSTAR是兼容模式下的系统调用
wrmsr(MSR_CSTAR, (u_int64_t)IDTVEC(fast_syscall32));
// MSR_STAR这里主要是执行syscall或者sysret指令时的CS和SS的值
msr = ((u_int64_t)GSEL(GCODE_SEL, SEL_KPL) << 32) |
((u_int64_t)GSEL(GUCODE32_SEL, SEL_UPL) << 48);
wrmsr(MSR_STAR, msr);
// 设置SFMSSK主要是用来屏蔽rflags寄存器的一些标志位
wrmsr(MSR_SF_MASK, PSL_NT | PSL_T | PSL_I | PSL_C | PSL_D | PSL_AC);
}

3. 系统调用举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 这里的$SYS_##name就是syscall.h中定义的系统调用号
// 类似
// #define SYS_exit 1
// #define SYS_fork 2
// #define SYS_read 3
// #define SYS_write 4
#define RSYSCALL(name) ENTRY(__sys_##name); \
WEAK_REFERENCE(__sys_##name, name); \
WEAK_REFERENCE(__sys_##name, _##name); \
mov $SYS_##name,%eax; KERNCALL; \
jb HIDENAME(cerror); ret; \
END(__sys_##name)
// 上面mov指令将调用号写入到%eax寄存器
// 这里将%rcx值保存到%r10中,然后使用syscall指令.
// %r10寄存器保存的是syscall的调用完成以后的返回地址。
#define KERNCALL movq %rcx, %r10; syscall

syscall指令会触发CPU加载MSR_LSTAR寄存器中的处理函数,具体调用过程可以参考这篇文章

4. 系统调用处理函数

系统调用的处理函数是汇编实现,在(amd64/amd64/exception.S,line:526)中.这段汇编代码实际是一个黏合剂,最终是为了调用amd64_syscall函数,这是c语言实现的.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
	SUPERALIGN_TEXT
IDTVEC(fast_syscall_pti)
swapgs
lfence
# ...
# 省略
IDTVEC(fast_syscall)
swapgs
lfence
fast_syscall_common:
# 正式处理函数
# ...
# 最终调用amd64_syscall函数
# amd64_syscall是c语言函数
call amd64_syscall
# ...

本文没有对系统调用的细节进行说明,比如系统调用时的参数传递和检查,系统调用时的现场环境保存和恢复.这里是说明amd64位下的系统调用和x86的32位下的系统调用的方式不同以及具体调用的硬件支持的原理,让我们清晰的看到操作系统的实现总是分成机器无关的部分和机器相关的部分,系统调用也不例外.