本文将围绕linux平台上socket编程,以用户程序中的socket()接口调用为例,分析该API编程接口、系统调用机制及内核中系统调用相关源代码、 相关系统调用的内核处理函数。
一、socket()接口
int socket( int domain, int type, int protocol)
功能:创建一个新的套接字,返回套接字描述符,失败返回-1。
参数说明:
domain:用于设置网络通信的域,函数socket()根据这个参数选择通信协议的族。通信协议族在文件sys/socket.h中定义。
type:用于设置套接字通信的类型,主要有SOCKET_STREAM(流式套接字)、SOCK——DGRAM(数据包套接字)等。
protocol:用于制定某个协议的特定类型,即type类型中的某个类型。通常某协议中只有一种特定类型,这样protocol参数仅能设置为0;
举例:s=socket(PF_INET,SOCK_STREAM,0)
二、系统调用机制
1.系统调用初始化
X86_64系统上电后,socket有关系统调用初始化过程为:start_kernel --> trap_init --> cpu_init --> syscall_init 。系统调用初始化syscall_init()函数在linux/arch/x86/kernel/cpu/common.c中定义,代码如下:
1 void syscall_init(void) 2 { 3 wrmsr(MSR_STAR, 0, (__USER32_CS << 16) | __KERNEL_CS); 4 wrmsrl(MSR_LSTAR, (unsigned long)entry_SYSCALL_64); 5 6 #ifdef CONFIG_IA32_EMULATION 7 wrmsrl(MSR_CSTAR, (unsigned long)entry_SYSCALL_compat); 8 /* 9 * This only works on Intel CPUs. 10 * On AMD CPUs these MSRs are 32-bit, CPU truncates MSR_IA32_SYSENTER_EIP. 11 * This does not cause SYSENTER to jump to the wrong location, because 12 * AMD doesn't allow SYSENTER in long mode (either 32- or 64-bit). 13 */ 14 wrmsrl_safe(MSR_IA32_SYSENTER_CS, (u64)__KERNEL_CS); 15 wrmsrl_safe(MSR_IA32_SYSENTER_ESP, 16 (unsigned long)(cpu_entry_stack(smp_processor_id()) + 1)); 17 wrmsrl_safe(MSR_IA32_SYSENTER_EIP, (u64)entry_SYSENTER_compat); 18 #else 19 wrmsrl(MSR_CSTAR, (unsigned long)ignore_sysret); 20 wrmsrl_safe(MSR_IA32_SYSENTER_CS, (u64)GDT_ENTRY_INVALID_SEG); 21 wrmsrl_safe(MSR_IA32_SYSENTER_ESP, 0ULL); 22 wrmsrl_safe(MSR_IA32_SYSENTER_EIP, 0ULL); 23 #endif 24 25 /* Flags to clear on syscall */ 26 wrmsrl(MSR_SYSCALL_MASK, 27 X86_EFLAGS_TF|X86_EFLAGS_DF|X86_EFLAGS_IF| 28 X86_EFLAGS_IOPL|X86_EFLAGS_AC|X86_EFLAGS_NT); 29 }
这两个函数执行系统调用入口的初始化:
wrmsr(MSR_STAR, 0, (__USER32_CS << 16) | __KERNEL_CS); wrmsrl(MSR_LSTAR, (unsigned long)entry_SYSCALL_64);
第一个特殊模块集寄存器- MSR_STAR
的 63:48
为用户代码的代码段。这些数据将加载至 CS
和 SS
段选择符,由提供将系统调用返回至相应特权级的用户代码功能的 sysret
指令使用。 同时从内核代码来看, 当用户空间应用程序执行系统调用时,MSR_STAR
的 47:32
将作为 CS
and SS
段选择寄存器的基地址。第二行代码中我们将使用系统调用入口entry_SYSCALL_64
填充 MSR_LSTAR
寄存器。
2.执行系统调用
在系统调用被处理之前, entry_SYSCALL_64将做一些准备工作。在控制器由用户态转到内核态后,并不是立即就执行内核态系统调用表中的内核函数,原因是在系统调用完成之后还要返回用户态,因此在调用内核系统调用函数之前,必须做一些准备工作,保存用户态的信息(堆栈, 寄存器)待系统调用完之后恢复现场等等。
entry_SYSCALL_64
在 arch/x86/entry/entry_64.S 汇编文件中定义,包含了系统调用整个生命周期的管理,包括系统调用前的运行环境保存,执行系统调用,系统调用之后的恢复。。当用户态程序发起系统调用,对于x86-64位程序直接跳到entry_SYSCALL_64入口处执行。entry_SYSCALL_64的源代码如下:
1 ENTRY(entry_SYSCALL_64) 2 UNWIND_HINT_EMPTY 3 /* 4 * Interrupts are off on entry. 5 * We do not frame this tiny irq-off block with TRACE_IRQS_OFF/ON, 6 * it is too small to ever cause noticeable irq latency. 7 */ 8 9 swapgs 10 /* tss.sp2 is scratch space. */ 11 movq %rsp, PER_CPU_VAR(cpu_tss_rw + TSS_sp2) 12 SWITCH_TO_KERNEL_CR3 scratch_reg=%rsp 13 movq PER_CPU_VAR(cpu_current_top_of_stack), %rsp 14 15 /* Construct struct pt_regs on stack */ 16 pushq $__USER_DS /* pt_regs->ss */ 17 pushq PER_CPU_VAR(cpu_tss_rw + TSS_sp2) /* pt_regs->sp */ 18 pushq %r11 /* pt_regs->flags */ 19 pushq $__USER_CS /* pt_regs->cs */ 20 pushq %rcx /* pt_regs->ip */ 21 GLOBAL(entry_SYSCALL_64_after_hwframe) 22 pushq %rax /* pt_regs->orig_ax */ 23 24 PUSH_AND_CLEAR_REGS rax=$-ENOSYS 25 26 TRACE_IRQS_OFF 27 28 /* IRQs are off. */ 29 movq %rax, %rdi 30 movq %rsp, %rsi 31 call do_syscall_64 /* returns with IRQs disabled */ 32 ......
三、socket()的系统调用过程