本文将围绕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_STAR63:48 为用户代码的代码段。这些数据将加载至 CSSS 段选择符,由提供将系统调用返回至相应特权级的用户代码功能的 sysret 指令使用。 同时从内核代码来看, 当用户空间应用程序执行系统调用时,MSR_STAR47: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()的系统调用过程

12-13 20:23