深入理解Linux网络(三):TCP对象创建
- TCP对象创建
- inet_create
- sock_init_data
TCP对象创建
常见的三句TCP编程:
int main()
{
int sk = socket(AF_INET, SOCK_STREAM, 0);
connect(sk, ...)
recv(sk, ...)
}
简单的两三⾏代码,但实际上⽤户进程和内核配合做了⾮常多的⼯作。
首先⽤户进程发起创建 socket 的指令,然后切换到内核态完成了内核对象的初始化。
接着在数据包的接收上,是硬中断和 ksoftirqd 进程在进⾏处理。
当 ksoftirqd 进程处理完以后,再通知到相关的⽤户进程。
从创建 socket,到⽹络包抵达⽹卡到被接收,流程如下:
数据结构间的调用如下:
//file:net/socket.c
SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)
{
......
retval = sock_create(family, type, protocol, &sock);
}
sock_create 继续调用 __sock_create:
//file:net/socket.c
int __sock_create(struct net *net, int family, int type, int protocol,
struct socket **res, int kern)
{
struct socket *sock;
const struct net_proto_family *pf;
......
//分配 socket 对象
sock = sock_alloc();
//获得每个协议族的操作表
pf = rcu_dereference(net_families[family]);
//调⽤每个协议族的创建函数, 对于 AF_INET 对应的是
err = pf->create(net, sock, protocol, kern);
}
net_proto_family->create 会调用 inet_create:
//file:net/ipv4/af_inet.c
static int inet_create(struct net *net, struct socket *sock, int protocol, int kern)
{
struct sock *sk;
//查找对应的协议,对于TCP SOCK_STREAM 就是获取到了
//static struct inet_protosw inetsw_array[] =
//{
// {
// .type = SOCK_STREAM,
// .protocol = IPPROTO_TCP,
// .prot = &tcp_prot,
// .ops = &inet_stream_ops,
// .no_check = 0,
// .flags = INET_PROTOSW_PERMANENT |
// INET_PROTOSW_ICSK,
// },
//}
list_for_each_entry_rcu(answer, &inetsw[sock->type], list)
{
//将 inet_stream_ops 赋到 socket->ops 上
sock->ops = answer->ops;
//获得 tcp_prot
answer_prot = answer->prot;
//(1)分配 sock 对象, 并把 tcp_prot 赋到 sock->sk_prot 上
sk = sk_alloc(net, PF_INET, GFP_KERNEL, answer_prot);
//(2)对 sock 对象进⾏初始化
sock_init_data(sock, sk);
}
}
针对上面的代码:
inet_create
inet_create 中根据类型 SOCK_STREAM 查找到对于 tcp 定义的操作⽅法实现集合 inet_stream_ops 和 tcp_prot。并把它们分别设置到 socket->ops 和 sock->sk_prot 上。
sock_init_data
sock_init_data 中将 sock 中的 sk_data_ready 函数指针进⾏了初始化,设置为默认 sock_def_readable()。
//file: net/core/sock.c
void sock_init_data(struct socket *sock, struct sock *sk)
{
sk->sk_data_ready = sock_def_readable;
sk->sk_write_space = sock_def_write_space;
sk->sk_error_report = sock_def_error_report;
}
当软中断上收到数据包时会通过调⽤ sk_data_ready 函数指针(实际被设置成了 sock_def_readable()) 来唤醒在 sock 上等待的进程。
⾄此,⼀个 tcp对象( AF_INET 协议族下 SOCK_STREAM对象)就算是创建完成了。
这⾥花费了⼀次 socket 系统调⽤的开销。