在创建socket时,会调用每个协议族family来初始化创建的socket,其中常用的ipv4,最终调用的inet_create:
static int inet_create(struct net *net, struct socket *sock, int protocol,
int kern)
{
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;
/*
# ifndef likely
# define likely(x) (__builtin_expect(!!(x), 1))
# endif
# ifndef unlikely
# define unlikely(x) (__builtin_expect(!!(x), 0))
# endif
__builtin_expect宏是编译器支持的,如果是1,表示期待x为真,那么编译器会把if的语句块放在前面,否则else放在前面。
*/
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();
/*
inetsw保存的是每个协议type对应的属性和操作,inetsw数组的初始化,是通过另外一个数组inetsw_array来初始化的(通过函数inet_register_protosw):
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,
},
{
.type = SOCK_DGRAM,
.protocol = IPPROTO_UDP,
.prot = &udp_prot,
.ops = &inet_dgram_ops,
.no_check = UDP_CSUM_DEFAULT,
.flags = INET_PROTOSW_PERMANENT,
},
{
.type = SOCK_DGRAM,
.protocol = IPPROTO_ICMP,
.prot = &ping_prot,
.ops = &inet_dgram_ops,
.no_check = UDP_CSUM_DEFAULT,
.flags = INET_PROTOSW_REUSE,
},
{
.type = SOCK_RAW,
.protocol = IPPROTO_IP,/* wild card */
.prot = &raw_prot,
.ops = &inet_sockraw_ops,
.no_check = UDP_CSUM_DEFAULT,
.flags = INET_PROTOSW_REUSE,
}
};
inetsw是一个链表数组,每一项都是一个struct inet_protosw结构体的链表,总共有SOCK_MAX项,在inet_init函数对INET域进行初始化的时候,调用函数inet_register_protosw()把数组inetsw_array中定义的套接字类型全部注册到inetsw数组中,相同套接字类型,不同协议类型的在数组的同一项,以套接字类型为索引,在系统实际使用的时候,只使用inetsw,而不使用inetsw_array,目前inet域不存在相同套接字类型的多个协议(原始套接字使用通配符,所以也不存在这个问题)。
*/
/*
根据sock的type和protocol,寻找对应的inet_protosw
*/
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;
}
/*
如果没有找到,那么尝试载入模块,来解决
*/
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;
if (sock->type == SOCK_RAW && !kern && !capable(CAP_NET_RAW))
goto out_rcu_unlock;
err = -EAFNOSUPPORT;
if (!inet_netns_ok(net, protocol))
goto out_rcu_unlock;
/*
这里需要看下inet_protosw数据结构的定义
struct inet_protosw {
struct list_head list;
/* These two fields form the lookup key. */
unsigned shorttype; /* This is the 2nd argument to socket(2). */
unsigned shortprotocol; /* This is the L4 protocol number. */
struct proto *prot;
const struct proto_ops *ops;
char no_check; /* checksum on rcv/xmit/none? */
unsigned char flags; /* See INET_PROTOSW_* below. */
};
inet_protosw->type
type是指套接字的类型,也就是系统调用socket的第二个参数,inet域支持的套接字类型有SOCK_STREAM(流套接字),它是一个有序的,可靠的,基于连接的双向字节流;SOCK_DGRAM(数据报套接字),该类型的套接字是不可靠的,无连接的,有可能乱序的;SOCK_RAW(原始套接字),所谓原始,是因为该类型的套接字不提供传输层的服务,协议栈只为该类型的套接字自动添加网络层首部,常用于网络层的附属协议(ICMP, IGMP等)。
inet_protosw->protocol
protocol是一个传输层协议号,传输层的协议包括IPPROTO_TCP,IPPROTO_UDP;或者对于SOCK_RAW来讲,它是一个通配协议号IPPROTO_IP,用于通配网络层的附属协议ICMP,IGMP等。对于传输层协议来讲,IPPROTO_TCP对应的套接字类型总是SOCK_STREAM,IPPRTO_UDP对应的套接字类型总是STREAM_DGRAM,所以在socket系统调用时,可以不必指定协议号,而直接使用通配符IPPROTO_IP。
inet_protosw->prot
prot是一个传输层协议绑定的操作集,比如对于IPPROTO_TCP,它就是tcp_prot,对于IPPROTO_UDP,它就是udp_prot。而对于类型为SOCK_RAW的套接字,它没有相应的传输层协议,而是用于通配所有的网络层附属协议,所以,prot就是所有网络层附属协议共用的一个操作集raw_prot。
inet_protosw->ops
ops是套接字类型绑定的操作集,对应于SOCK_STREAM, SOCK_DGRAM, SOCK_RAW,操作集分别为inet_stream_ops,inet_dgram_ops,inet_sockraw_ops。
inet_protosw->capability
capability是操作这类别套接字所需要的权限,除了原始套接字需要CAP_NET_RAW权限之外,其它两类套接字不需要特殊权限(-1)。
inet_protosw->flags
flags的可能取值如下:
#define INET_PROTOSW_REUSE 0x01
#define INET_PROTOSW_PERMANENT 0x02
#define INET_PROTOSW_ICSK 0x04
*/
sock->ops = answer->ops;
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;
/*
sk_alloc() -- 生成一个sock结构体,并给该结构体的sk_family、 sk_prot、 sk_prot_creator域赋值。实际获取的内存空间大小由具体协议类型决定
*/
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 = SK_CAN_REUSE;
inet = inet_sk(sk); //强制转换sock为inet_sock
inet->is_icsk = (INET_PROTOSW_ICSK & answer_flags) != 0;
inet->nodefrag = 0;
if (SOCK_RAW == sock->type) {
inet->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->inet_id = 0;
/*
每个socket数据结构都有一个sock数据结构成员,sock是对socket的扩充,两者一一对应,socket->sk指向对应的sock,sock->socket指向对应的socket
socket和sock是同一事物的两个侧面,为什么不把两个数据结构合并成一个呢?这是因为socket是inode结构中的一部分,即把inode结构内部的一个union用作socket结构。
由于插口操作的特殊性,这个数据结构中需要有大量的结构成分,如果把这些成分全部放到socket结构中,则inode结构中的这个union就会变得很大,
从而inode结构也会变得很大,而对于其他文件系统这个union是不需要这么大的,所以会造成巨大浪费,系统中使用inode结构的数量要远远超过使用socket的数量,
故解决的办法就是把插口分成两部分,把与文件系统关系密切的放在socket结构中,把与通信关系密切的放在另一个单独结构sock中;
sock_init_data初始化了sock结构的很多成员,可以看作是sock结构的初始化函数,然后设置socket的成员sk为sock
*/
sock_init_data(sock, sk);
sk->sk_destruct = inet_sock_destruct;
sk->sk_protocol = protocol;
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;
inet->rcv_tos = 0;
sk_refcnt_debug_inc(sk);
if (inet->inet_num) {
/* It assumes that any protocol which allows
* the user to assign a number at socket
* creation time automatically
* shares.
*/
inet->inet_sport = htons(inet->inet_num);
/* Add to protocol hash chains. */
sk->sk_prot->hash(sk);
}
/*
通过相应的协议来初始化sock结构,如果是tcp的话,那么调用的是tcp_v4_init_sock
*/
if (sk->sk_prot->init) {
err = sk->sk_prot->init(sk);
if (err)
sk_common_release(sk);
}
out:
return err;
out_rcu_unlock:
rcu_read_unlock();
goto out;
}
小结:
inet_create主要创建了socket结构里面的sock,并对sock进行了初始化。sock的主要初始化函数在sock_init_data。差sock_init_data函数,tcp_v4_init_sock