最近在开发一个内核模块,主要的功能是在集群的节点之间迁移TCP连接,从而实现基于内容的调用。因此,花了很多时间和精力研究linux的网络协议栈,但是还是有很多地方没有串起来。网络协议栈是为用户层的应用开发服务的,因此决定从用户层常用的编程接口入手,通过学习这些接口的实现,来理清整个过程,加深对网络协议栈的理解。
网络编程通常是基于客户端-服务端模型。首先启动服务器,稍后的某个时刻启动客户,它要连接到此服务器上。假设客户给服务器发送一个请求,服务器处理这个请求,并且给客户发送一个响应。为了执行网络I/O,第一件事情就是调用socket()函数来创建套接字。socket()函数对应的系统调用时sys_socket(),其源码及分析如下所示:
SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)
{
int retval;
struct socket *sock;
int flags;
/* Check the SOCK_* constants for consistency. */
/*
* 下面的检查是在编译的时候进行的,如果这些
* 变量的值不一致,编译时会报错。
*/
BUILD_BUG_ON(SOCK_CLOEXEC != O_CLOEXEC);
BUILD_BUG_ON((SOCK_MAX | SOCK_TYPE_MASK) != SOCK_TYPE_MASK);
BUILD_BUG_ON(SOCK_CLOEXEC & SOCK_TYPE_MASK);
BUILD_BUG_ON(SOCK_NONBLOCK & SOCK_TYPE_MASK);
/*
* 从linux 2.6.27开始,参数type除了指定套接字类型外,还
* 可以通过或运算来指定SOCK_CLOEXEC和SOCK_NONBLOCK标志
* 来改变套接字的行为。可以通过man socket命令查看详情。
* 首先通过SOCK_TYPE_MASK掩码来获取type中设置的标志(当然
* 也可能没有设置)。如果type中有标志设置,但是不是
* SOCK_CLOEXEC和SOCK_NONBLOCK对应的位,则返回EINVAL错误。
*/
flags = type & ~SOCK_TYPE_MASK;
if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))
return -EINVAL;
/*
* 获取套接字类型
*/
type &= SOCK_TYPE_MASK;
/*
* 如果SOCK_NONBLOCK不等于O_NONBLOCK并且设置了SOCK_NONBLOCK
* 标志,则将flags中的SOCK_NONBLOCK替换为O_NONBLOCK。
*/
if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK))
flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK;
/*
* 根据协议族类型和套接字类型创建套接字。
*/
retval = sock_create(family, type, protocol, &sock);
if (retval < 0)
goto out;
/*
* 创建一个新的文件描述符,将新创建的
* socket实例关联上去。
*/
retval = sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));
if (retval < 0)
goto out_release;
out:
/* It