
其中代表一条连接的 socket 内核对象更为具体一点的结构图如下。

为了避免喧宾夺主,accept 详细的源码过程这里就不介绍了,感兴趣请参考 《图解 | 深入揭秘 epoll 是如何实现 IO 多路复用的!》。一文中的第一部分。
今天我们还是把重点放到数据发送过程上。
四、发送数据真正开始
4.1 send 系统调用实现
send 系统调用的源码位于文件 net/socket.c 中。在这个系统调用里,内部其实真正使用的是 sendto 系统调用。整个调用链条虽然不短,但其实主要只干了两件简单的事情,
- 第一是在内核中把真正的 socket 找出来,在这个对象里记录着各种协议栈的函数地址。
- 第二是构造一个 struct msghdr 对象,把用户传入的数据,比如 buffer地址、数据长度啥的,统统都装进去.
剩下的事情就交给下一层,协议栈里的函数 inet_sendmsg 了,其中 inet_sendmsg 函数的地址是通过 socket 内核对象里的 ops 成员找到的。大致流程如图。

有了上面的了解,我们再看起源码就要容易许多了。源码如下:
//file: net/socket.c
SYSCALL_DEFINE4(send, int, fd, void __user *, buff, size_t, len,
unsigned int, flags)
{
return sys_sendto(fd, buff, len, flags, NULL, 0);
}
SYSCALL_DEFINE6(......)
{
//1.根据 fd 查找到 socket
sock = sockfd_lookup_light(fd, &err, &fput_needed);
//2.构造 msghdr
struct msghdr msg;
struct iovec iov;
iov.iov_base = buff;
iov.iov_len = len;
msg.msg_iovlen = 1;
msg.msg_iov = &iov;
msg.msg_flags = flags;
......
//3.发送数据
sock_sendmsg(sock, &msg, len);
}
从源码可以看到,我们在用户态使用的 send 函数和 sendto 函数其实都是 sendto 系统调用实现的。send 只是为了方便,封装出来的一个更易于调用的方式而已。
在 sendto 系统调用里,首先根据用户传进来的 socket 句柄号来查找真正的 socket 内核对象。接着把用户请求的 buff、len、flag 等参数都统统打包到一个 struct msghdr 对象中。
接着调用了 sock_sendmsg => __sock_sendmsg ==> __sock_sendmsg_nosec。在__sock_sendmsg_nosec 中,调用将会由系统调用进入到协议栈,我们来看它的源码。
//file: net/socket.c
static inline int __sock_sendmsg_nosec(...)
{
......
return sock->ops->sendmsg(iocb, sock, msg, size);
}
通过第三节里的 socket 内核对象结构图,我们可以看到,这里调用的是 sock->ops->sendmsg 实际执行的是 inet_sendmsg。这个函数是 AF_INET 协议族提供的通用发送函数。
4.2 传输层处理
1)传输层拷贝
在进入到协议栈 inet_sendmsg 以后,内核接着会找到 socket 上的具体协议发送函数。对于 TCP 协议来说,那就是 tcp_sendmsg(同样也是通过 socket 内核对象找到的)。
在这个函数中,内核会申请一个内核态的 skb 内存,将用户待发送的数据拷贝进去。注意这个时候不一定会真正开始发送,如果没有达到发送条件的话很可能这次调用直接就返回了。大概过程如图:

我们来看 inet_sendmsg 函数的源码。
//file: net/ipv4/af_inet.c
int inet_sendmsg(......)
{
......
return sk->sk_prot->sendmsg(iocb, sk, msg, size);
}
在这个函数中会调用到具体协议的发送函数。同样参考第三节里的 socket 内核对象结构图,我们看到对于 TCP 协议下的 socket 来说,来说 sk->sk_prot->sendmsg 指向的是 tcp_sendmsg(对于 UPD 来说是 udp_sendmsg)。