一、通过socket看系统调用过程
在Linux操作系统中,系统调用是用户空间与内核空间之间交互的一种方式。当一个应用程序需要执行操作系统级别的任务时,比如创建一个网络套接字(socket),它必须通过系统调用请求内核来执行这些操作。下面是通过`socket`系统调用从用户空间到内核空间再映射到内核源码的详细过程:
1. 用户空间的API调用:
用户程序通常会调用glibc(GNU C库,Linux系统上的C标准库)提供的`socket`函数。这个函数定义通常在`<sys/socket.h>`头文件中。
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
2. 封装成系统调用:
在glibc中,`socket`函数会封装成一个系统调用。系统调用是通知操作系统核心执行任务的一种机制。每个系统调用都有一个唯一的数字标识符。
// glibc层面的封装,实际上会经过一些复杂的宏定义和内联函数处理
int socket(int domain, int type, int protocol) {
return syscall(SYS_socket, domain, type, protocol);
}
3. 从用户空间到内核空间的切换:
根据计算机的体系结构,在用户程序发起系统调用后,会通过一些机制(比如软件中断、陷阱指令或特定的系统调用指令)将控制权转移给内核。这通常涉及到一些寄存器设置系统调用号和参数,然后执行一个中断指令,例如在x86架构中的`int 0x80`或`syscall`指令。
4. 系统调用处理程序:
内核内部有一个系统调用处理程序,这个处理程序根据传入的系统调用编号识别并分派对应的内核函数进行处理。在x86架构Linux系统中,系统调用入口点通常在`entry_64.s`汇编文件中找到。
5. 内核空间中的函数调用:
内核将对应的系统调用号映射到具体的内核函数。在内核源码中,这个过程通过一张系统调用表完成,这张表把系统调用号映射到对应的处理函数。`socket`系统调用会最终映射到内核中的`sys_socket`函数。
// 系统调用号到处理函数的映射可能在一个类似下面的结构中:
// sys_socket函数可能在net/socket.c文件中定义
SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol) {
return __sys_socket(family, type, protocol);
}
6. 内核函数执行和返回:
sys_socket会执行必要的操作来创建一个新的socket,这包括分配一个socket结构体并初始化它,以及任何与协议相关的设置。执行完毕后,系统调用处理程序将把结果(比如新创建的socket文件描述符或错误码)返回到用户程序。
以上描述了`socket`函数从用户空间到内核空间的调用过程。需要注意的是,在不同的操作系统、不同的体系结构和不同的内核版本中,这一过程的具体细节可能会有所不同。
二、EXPORT_SYMBOL(sock_create)/EXPORT_SYMBOL(sock_create_kern)
int sock_create(int family, int type, int protocol, struct socket **res)
{
return __sock_create(current->nsproxy->net_ns, family, type, protocol, res, 0);
}
EXPORT_SYMBOL(sock_create);
int sock_create_kern(struct net *net, int family, int type, int protocol, struct socket **res)
{
return __sock_create(net, family, type, protocol, res, 1);
}
EXPORT_SYMBOL(sock_create_kern);
在Linux内核中,`sock_create`和`sock_create_kern`函数是用于创建一个新的socket的函数。虽然一个是专为用户空间设计(sock_create),而另一个是专为内核空间设计(sock_create_kern),但它们都最终调用`__sock_create`函数来执行创建socket的实际工作。
下面是每个函数的目的:
1. sock_create:这个函数主要用于用户空间的请求来创建一个新的socket。它接受参数`family`(例如,AF_INET表示IPv4协议),`type`(例如,SOCK_STREAM表示流式套接字),和`protocol`(具体的协议,如TCP或UDP),它还需要一个指向socket结构体指针的指针`res`来存储新创建的socket。`sock_create`通过进程的`nsproxy`字段中的网络命名空间(net_ns)来调用`__sock_create`,并且将 user 标志设置为0,表示这一创建操作来自用户空间。
2. sock_create_kern:与`sock_create`相似,`sock_create_kern`被用于内核空间的socket创建。它也需要相同类型的参数,但是不同的是它接收一个网络命名空间`net`的指针作为其第一个参数,表示socket将属于该网络命名空间,还将`user`标志设置为1,表示这一创建操作来自内核空间。
3. EXPORT_SYMBOL宏:它用于将函数导出,以便这些函数可以被内核的其他模块调用。例如,如果一个内核模块想要创建一个socket,它可以调用`sock_create`或者`sock_create_kern`,前提是这个模块与这些函数定义在同一个内核编译范围内。
最终的创建工作是通过调用`__sock_create`来完成的,它是一个内部函数,不直接暴露给用户空间或其他内核模块。`__sock_create`实际上负责在给定的网络命名空间、地址族、socket类型和协议下分配和初始化一个新的socket。
这是一个非常简化的说明,真正的socket创建过程涉及更多的步骤,例如,分配内存给新的socket,初始化socket结构,以及可能将socket与某些数据结构相关联等等。