sys_socket()的代码流程图如下所示:
从上面的代码及流程图中,可以看到sys_socket()主要调用sock_create()和sock_map_fd()来完成创建套接字的工作,在讨论这两个函数之前,要强调一下type参数。从linux 2.6.27开始,参数type除了指定套接字类型外,还可以通过或运算来指定SOCK_CLOEXEC和SOCK_NONBLOCK标志来改变套接字的行为。可以通过man socket命令来查看,原文是:
Since Linux 2.6.27, the type argument serves a second purpose: in addition to specifying a
socket type, it may include the bitwise OR of any of the following values, to modify the
behavior of socket():
SOCK_NONBLOCK Set the O_NONBLOCK file status flag on the new open file description. Using
this flag saves extra calls to fcntl(2) to achieve the same result.
SOCK_CLOEXEC Set the close-on-exec (FD_CLOEXEC) flag on the new file descriptor. See the
description of the O_CLOEXEC flag in open(2) for reasons why this may be
useful.
一、sock_create()函数
sock_create()函数时对__sock_create()函数的简单封装,__sock_create()函数的代码流程图如下所示:
__sock_create()调用sock_alloc()分配一个新的inode和socket对象,并设置套接字的权限位及套接字所属的用户和用户组。如果要创建的是TCP套接字,pf->create()中的pf是inet_family_ops变量,该变量的类型是net_proto_family结构,在net/ipv4/if_inet.c文件中定义。inet_family_ops的create成员设置的是inet_create()函数,因此如果是创建TCP套接字,pf->create()调用的实际是inet_create()函数。
inet_create()用来创建inet套接字,其源码及分析如下:
/*
* Create an inet socket.
*/
static int inet_create(struct net *net, struct socket *sock, int protocol)
{
struct sock *sk;
struct inet_protosw *answer;
struct inet_sock *inet;
struct proto *answer_prot;
unsigned char answer_flags;
char answer_no_check;
int try_loading_module = 0;
int err;
/*
* inet_ehash_secret用于在将除原始套接字和UDP套接字外的套接字,
* 哈希计算后插入到系统时用到的哈希因子,只设置一次。
*/
if (unlikely(!inet_ehash_secret))
if (sock->type != SOCK_RAW && sock->type != SOCK_DGRAM)
build_ehash_secret();
/*
* 设置套接字的初始状态。
*/
sock->state = SS_UNCONNECTED;
/* Look for the requested type/protocol pair. */
lookup_protocol:
err = -ESOCKTNOSUPPORT;
rcu_read_lock();
/*
* TCP套接字、UDP套接字、原始套接字的inet_protosw实例都
* 在inetsw_array数组中定义,这些实例会调用inet_register_protosw()
* 注册到inetsw中。
* 根据protocol查找要创建的套接字对应的四层传输协议。
*/
list_for_each_entry_rcu(answer, &inetsw[sock->type], list) {
err = 0;
/* Check the non-wild match. */
if (protocol == answer->protocol) {
if (protocol != IPPROTO_IP)
break;
} else {
/* Check for the two wild cases. */
if (IPPROTO_IP == protocol) {
protocol = answer->protocol;
break;
}
if (IPPROTO_IP == answer->protocol)
break;
}
err = -EPROTONOSUPPORT;
}
/*
* 如果没有找到,则调用request_module()来尝试加载
* 协议所属的模块,正常情况下不会发生。
*/
if (unlikely(err)) {
if (try_loading_module < 2) {
rcu_read_unlock();
/*
* Be more specific, e.g. net-pf-2-proto-132-type-1
* (net-pf-PF_INET-proto-IPPROTO_SCTP-type-SOCK_STREAM)
*/
if (++try_loading_module == 1)
request_module("net-pf-%d-proto-%d-type-%d",
PF_INET, protocol, sock->type);
/*
* Fall back to generic, e.g. net-pf-2-proto-132
* (net-pf-PF_INET-proto-IPPROTO_SCTP)
*/
else
request_module("net-pf-%d-proto-%d",
PF_INET, protocol);
goto lookup_protocol;
} else
goto out_rcu_unlock;
}
err = -EPERM;
/*
* 检查用户是否有创建指定协议类型的套接字的权限,
* 如果是TCP套接字,capability为-1,表示没有权限限制。
*/
if (answer->capability > 0 && !capable(answer->capability))
goto out_rcu_unlock;
err = -EAFNOSUPPORT;
/*
* 检查网络命名空间,检查指定的协议类型
* 是否已经添加,参见init_inet(),tcp协议对应
* 的net_protocol实例是tcp_protocol。
*/
if (!inet_netns_ok(net, protocol))
goto out_rcu_unlock;
/*
* 设置套接字的操作接口,如果是TCP协议,
* answer->ops的值为inet_stream_ops。
*/
sock->ops = answer->ops;
/*
* 获取套接字对应的proto实例,如果是TCP协议,
* 值为tcp_prot。
*/
answer_prot = answer->prot;
answer_no_check = answer->no_check;
answer_flags = answer->flags;
rcu_read_unlock();
WARN_ON(answer_prot->slab == NULL);
err = -ENOBUFS;
/*
* 分配sock实例并初始化,如果是TCP协议,
* 则实际分配的大小为sizeof(tcp_sock)。
*/
sk = sk_alloc(net, PF_INET, GFP_KERNEL, answer_prot);
if (sk == NULL)
goto out;
err = 0;
/*
* 设置是否需要计算校验和的值
*/
sk->sk_no_check = answer_no_check;
if (INET_PROTOSW_REUSE & answer_flags)
sk->sk_reuse = 1;
inet = inet_sk(sk);
/*
* 设置是否是基于连接的套接字
*/
inet->is_icsk = (INET_PROTOSW_ICSK & answer_flags) != 0;
if (SOCK_RAW == sock->type) {
inet->num = protocol;
if (IPPROTO_RAW == protocol)
inet->hdrincl = 1;
}
/*
* 后面都是对一些成员的初始化,不作过多的
* 分析。
*/
if (ipv4_config.no_pmtu_disc)
inet->pmtudisc = IP_PMTUDISC_DONT;
else
inet->pmtudisc = IP_PMTUDISC_WANT;
inet->id = 0;
sock_init_data(sock, sk);
/*
* 在释放传输控制块之前释放资源。
*/
sk->sk_destruct = inet_sock_destruct;
sk->sk_protocol = protocol;
/*
* 在TCP中,用于接收预备队列和后备队列中的
* TCP段,实际的函数时tcp_v4_do_rcv()。
*/
sk->sk_backlog_rcv = sk->sk_prot->backlog_rcv;
inet->uc_ttl = -1;
inet->mc_loop = 1;
inet->mc_ttl = 1;
inet->mc_all = 1;
inet->mc_index = 0;
inet->mc_list = NULL;
sk_refcnt_debug_inc(sk);
if (inet->num) {
/* It assumes that any protocol which allows
* the user to assign a number at socket
* creation time automatically
* shares.
*/
inet->sport = htons(inet->num);
/* Add to protocol hash chains. */
sk->sk_prot->hash(sk);
}
if (sk->sk_prot->init) {
/*
* TCP协议调用的tcp_v4_init_sock()。
*/
err = sk->sk_prot->init(sk);
if (err)
sk_common_release(sk);
}
out:
return err;
out_rcu_unlock:
rcu_read_unlock();
goto out;
}