命令(输入 m 获取帮助):g
已创建新的 GPT 磁盘标签(GUID: 3A60FB68-50C7-FC43-A633-6F603EA6DE7A)。
The old gpt signature will be removed by a write command.
命令(输入 m 获取帮助):n
分区号 (1-128, 默认 1):
第一个扇区 (2048-100663262, 默认 2048):
Last sector, +/-sectors or +/-size{K,M,G,T,P} (2048-100663262, 默认 100663262): +512M
命令(输入 m 获取帮助):n
分区号 (2-128, 默认 2):
第一个扇区 (1050624-100663262, 默认 1050624):
Last sector, +/-sectors or +/-size{K,M,G,T,P} (1050624-100663262, 默认 100663262): +24G
创建了一个新分区 2,类型为“Linux filesystem”,大小为 24 GiB。
命令(输入 m 获取帮助):l
1 EFI System C12A7328-F81F-11D2-BA4B-00A0C93EC93B
19 Linux swap 0657FD6D-A4AB-43C4-84E5-0933C84B4F4F
20 Linux filesystem 0FC63DAF-8483-4772-8E79-3D69D8477DE4
命令(输入 m 获取帮助):t
分区号 (1,2, 默认 2): 1
分区类型(输入 L 列出所有类型):1
已将分区“Linux filesystem”的类型更改为“EFI System”。
命令(输入 m 获取帮助):w
帮助:
DOS (MBR)
a 开关 可启动 标志
b 编辑嵌套的 BSD 磁盘标签
c 开关 dos 兼容性标志
GPT
M 进入 保护/混合 MBR
常规
d 删除分区
F 列出未分区的空闲区
l 列出已知分区类型
n 添加新分区
p 打印分区表
t 更改分区类型
v 检查分区表
i 打印某个分区的相关信息
杂项
m 打印此菜单
x 更多功能(仅限专业人员)
脚本
I 从 sfdisk 脚本文件加载磁盘布局
O 将磁盘布局转储为 sfdisk 脚本文件
保存并退出
w 将分区表写入磁盘并退出
q 退出而不保存更改
新建空磁盘标签
g 新建一份 GPT 分区表
G 新建一份空 GPT (IRIX) 分区表
o 新建一份的空 DOS 分区表
s 新建一份空 Sun 分区表
#国内非官方用户仓库,两个可以任选或都开
[archlinuxcn]
SigLevel = Optional TrustedOnly
Server = https://mirrors.tuna.tsinghua.edu.cn/archlinuxcn/$arch
#Server = http://repo.archlinuxcn.org/$arch
#infinality是一个字体美化软件源,包含了美化过的大量字体(已更换清华源旧源不能用)
[infinality-bundle]
Server = https://mirrors.tuna.tsinghua.edu.cn/infinality-bundle/$arch
[infinality-bundle-multilib]
Server = https://mirrors.tuna.tsinghua.edu.cn/infinality-bundle/multilib/$arch
[infinality-bundle-fonts]
Server = https://mirrors.tuna.tsinghua.edu.cn/infinality-bundle/fonts/
#antergos linux是一个基于arch linux的衍生版系统,它的软件源包含了很多常用但是未包含在arch官方源中的工具和软件。
[antergos]
#SigLevel = PackageRequired
SigLevel = TrustAll
Usage = All
Server = http://mirrors.antergos.com/$repo/$arch
Executes a fast call to a level 0 system procedure or routine. SYSENTER is a companion instruction to SYSEXIT. The instruction is optimized to provide the maximum performance for system calls from user code running at privilege level 3 to operating system or executive procedures running at privilege level 0.
When executed in IA-32e mode, the SYSENTER instruction transitions the logical processor to 64-bit mode; otherwise, the logical processor remains in protected mode.
Prior to executing the SYSENTER instruction, software must specify the privilege level 0 code segment and code entry point, and the privilege level 0 stack segment and stack pointer by writing values to the following MSRs:
• IA32_SYSENTER_CS (MSR address 174H) — The lower 16 bits of this MSR are the segment selector for the privilege level 0 code segment. This value is also used to determine the segment selector of the privilege level 0 stack segment (see the Operation section). This value cannot indicate a null selector.
• IA32_SYSENTER_EIP (MSR address 176H) — The value of this MSR is loaded into RIP (thus, this value references the first instruction of the selected operating procedure or routine). In protected mode, only bits 31:0 are loaded.
• IA32_SYSENTER_ESP (MSR address 175H) — The value of this MSR is loaded into RSP (thus, this value contains the stack pointer for the privilege level 0 stack). This value cannot represent a non-canonical address. In protected mode, only bits 31:0 are loaded.
These MSRs can be read from and written to using RDMSR/WRMSR. The WRMSR instruction ensures that the IA32_SYSENTER_EIP and IA32_SYSENTER_ESP MSRs always contain canonical addresses.
主要信息有:
(1)sysenter与sysexit指令配套,可以以比较高的执行效率在用户态执行要在系统态执行的系统调用。
(2)在IA-32e模式下执行时,sysenter指令将逻辑处理器转换为64位模式,否则逻辑处理器保持在保护模式。
(3)执行sysenter指令之前,需要将下列MSR(Model Specific Registers)中写入值来指定Ring0代码段、代码入口点、Ring0堆栈段和堆栈指针:
- IA32_SYSENTER_CS(174H):指定要执行Ring0代码的代码段选择符,也能得出目标Ring0所用堆栈段的段选择符
- IA32_SYSENTER_EIP(176H):指定要执行的Ring0代码的起始地址
- IA32_SYSENTER_ESP(175H):指定要执行的Ring0代码所使用的栈指针
(4)使用rdmsr/wrmsr读取和写入MSR
下面基于linux-2.6.39内核进行分析:
3.1 系统调用初始化
从linux内核启动流程入手:start_kernel() -> chenk_bugs() -> identify_boot_cpu() -> sysenter_setup() & enable_sep_cpu()
3.1.1 页面初始化和映射
首先执行sysenter_setup()函数来支持之前提到的vDSO机制,
将vdso32-sysenter.so动态链接库装载进vsyscall页中,在arch/x86/vdso/vdso32-setup.c可以找到sysenter_setup()函数:
int __init sysenter_setup(void)
{
void *syscall_page = (void *)get_zeroed_page(GFP_ATOMIC);
const void *vsyscall;
size_t vsyscall_len;
vdso32_pages[0] = virt_to_page(syscall_page);
#ifdef CONFIG_X86_32
gate_vma_init();
#endif
if (vdso32_syscall()) {
vsyscall = &vdso32_syscall_start;
vsyscall_len = &vdso32_syscall_end - &vdso32_syscall_start; } else if (vdso32_sysenter()) { vsyscall = &vdso32_sysenter_start; vsyscall_len = &vdso32_sysenter_end - &vdso32_sysenter_start; } else { vsyscall = &vdso32_int80_start; vsyscall_len = &vdso32_int80_end - &vdso32_int80_start;
}
memcpy(syscall_page, vsyscall, vsyscall_len);
relocate_vdso(syscall_page);
return 0;
}
sysenter_setup()函数的主要工作:
(1)调用get_zeroed_page()获得一个被填充为0的物理页,返回该页在内核地址空间的线性地址。
(2)调用宏virt_to_page得到syscall_page地址对应的page管理结构地址并赋值给vdso32_page[0]。
(3)随后判断支持哪些指令,从而做不同处理,可以看到优先级是syscall > sysenter > int80。
(4)将vdso32_sysenter_start地址赋给vsyscall,然后用memcpy()将vsyscall拷贝到对应的页,最后用relocate_vdso()进行重定向。
在arch/x86/vdso/vdso32.S中可以看到vdso32_sysenter_start就是vdso32-sysenter.so:
vdso32_sysenter_start:
incbin "arch/x86/vdso/vdso32-sysenter.so"
即将vdso32-sysenter.so拷贝到对应的页中,在《系统调用分析(1)》的vDSO介绍中提到的arch_setup_additional_pages函数便是把拷贝到的页的内容映射到用户空间。
3.1.2 相关MSR寄存器的初始化
在arch/x86/vdso/vdso32-setup.c中的enable_sep_cpu()函数完成相关MSR寄存器的初始化:
void enable_sep_cpu(void)
{
int cpu = get_cpu();
struct tss_struct *tss = &per_cpu(init_tss, cpu);
if (!boot_cpu_has(X86_FEATURE_SEP)) { put_cpu(); eturn; } tss->x86_tss.ss1 = __KERNEL_CS; tss->x86_tss.sp1 = sizeof(struct tss_struct) + (unsigned long) tss; wrmsr(MSR_IA32_SYSENTER_CS, __KERNEL_CS, 0); wrmsr(MSR_IA32_SYSENTER_ESP, tss->x86_tss.sp1, 0); wrmsr(MSR_IA32_SYSENTER_EIP, (unsigned long) ia32_sysenter_target, 0); put_cpu(); }
主要内容:
(1) MSR_IA32_SYSENTER_*的声明在arch/x86/include/asm/msr-index.h中,可以看到对应的MSR寄存器地址:
#define MSR_IA32_SYSENTER_CS 0x00000174
#define MSR_IA32)SYSENTER_ESP 0x00000175
#define MSR_IA32_SYSENTER_EIP 0x00000176
(2)将__KERNEL_CS设置到MSR_IA_SYSENTER_CS中。
(3)将tss->x86_tss.sp1栈地址设置到MSR_IA32_SYSENTER_ESP中。
(4)将ia32_sysenter_target(sysenter指令的接口函数)设置到MSR_IA32_SYSENTER_EIP。
3.2 sysenter和sysexit的指令操作
在Ring3的代码调用sysenter指令之后,CPU做出如下操作:
-
将SYSENTER_CS_MSR的值装在到cs寄存器。
-
将SYSENTER_EIP_MSR的值装在到eip寄存器。
-
将SYSENTER_CS_MSR的值加8(Ring0的堆栈段描述符)装载到ss寄存器。
-
将SYSENTER_ESP_MSR的值装载到esp寄存器。
-
将特权级切换到Ring0。
-
如果EFLAGS寄存器的VM标志被置位,则清除该标志。
-
开始执行指定的Ring0代码。
在Ring0代码执行完毕,调用sysexit指令退回Ring3时,CPU会做出如下操作:
-
将SYSENTER_CS_MSR的值加16(Ring3的代码段描述符)装载到cs寄存器。
-
将寄存器edx的值装载到eip寄存器。
-
将SYSENTER_CS_MSR的值加24(Ring3的堆栈段描述符)装载到ss寄存器。
-
将寄存器ecx的值装载到esp寄存器。
-
将特权级切换到Ring3。
-
继续执行Ring3的代码。
3.3 sysenter的系统调用处理
3.3.1 linux2.6.39内核sysenter系统调用
正如刚才对IA32_SYSENTER_EIP寄存器中传入sysenter的系统调用函数入口地址ia32_sysenter_target。
在arch/x86/ia32/ia32entry.S中可以看到sysenter指令所要执行的系统调用处理程序ia32_sysenter_target的代码,其中执行系统调用的代码是:
sysenter_dispatch:
call *ia32_sys_call_table(,%rax,8)
...
ia32_sys_call_table:
.quad sys_restart_syscall
.quad sys_exit .quad stub32_fork .quad sys_read .quad sys_write .quad compat_sys_open /* 5 */
可以看到sysenter指令会直接到系统调用表中找到相应系统调用处理程序去执行。
3.3.2 linux4.20内核sysenter系统调用
在linux4.20内核中,对IA32_SYSENTER_EIP寄存器中传入的是entry_SYSENTER_32函数。
在arch/x86/entry/entry_32.S中可以看到entry_SYSENTER_32()函数:
-
ENTRY(entry_SYSENTER_32)
-
pushfl
-
pushl %eax
-
BUG_IF_WRONG_CR3 no_user_check=1
-
SWITCH_TO_KERNEL_CR3 scratch_reg=%eax
-
popl %eax
-
popfl
-
movl TSS_entry2task_stack(%esp), %esp
-
-
.Lsysenter_past_esp:
-
pushl $__USER_DS /* pt_regs->ss */
-
pushl %ebp /* pt_regs->sp (stashed in bp) */
-
pushfl /* pt_regs->flags (except IF = 0) */
-
orl $X86_EFLAGS_IF, (%esp) /* Fix IF */
-
pushl $__USER_CS /* pt_regs->cs */
-
pushl $0 /* pt_regs->ip = 0 (placeholder) */
-
pushl %eax /* pt_regs->orig_ax */
-
SAVE_ALL pt_regs_ax=$-ENOSYS /* save rest, stack already switched */
-
testl $X86_EFLAGS_NT|X86_EFLAGS_AC|X86_EFLAGS_TF, PT_EFLAGS(%esp)
-
jnz .Lsysenter_fix_flags
-
-
.Lsysenter_flags_fixed:
-
TRACE_IRQS_OFF
-
movl %esp, %eax
-
call do_fast_syscall_32
-
...
-
sysexit