Linux网络栈协议无关层--BSD socket

一. 从socket api看协议无关层

前面的系列已经说了系统调用接口层,在应用层使用socket api,填充对应的参数,就能创建出想要使用的socket类型。这个过程就是协议无关层完成的。简单的说过程就是:根据参数,匹配注册的协议族,使用对应的协议。接下来重点分析几个socket api的BSD无关层实现,来更深一步理解这个问题。

1.1 socket()

在应用层调用socket()后,就会触发sys_socket系统调用。那我们就去看看在这个系统调用函数中都做了什么事:net/socket.c文件

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);

	flags = type & ~SOCK_TYPE_MASK;
	if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))
		return -EINVAL;
	type &= SOCK_TYPE_MASK;

	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;

	retval = sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));
	if (retval < 0)
		goto out_release;

out:
	/* It may be already another descriptor 8) Not kernel problem. */
	return retval;

out_release:
	sock_release(sock);
	return retval
}

省略前面的检查,实际上只有2个函数:sock_create()sock_map_fd()。接着看sock_create(),sock_create()->__sock_create().

  1. security_socket_create()这是创建安全的socket,没定义的话,就是空操作。
  2. sock_alloc()创建了一个struct socket,注意这个结构,后面还有其他的很相似的结构。struct socket就是BSD层维护的socket对象。因为UNIX秉承一切皆文件的思想,所以,这个socket对象也是和inode结构一起创建并绑定到一起的。创建之后,填充socket的类型:sock->type = type;
  3. 根据协议族和类型,创建对应类型的socket(struct sock)。如下代码有删减。
	pf = rcu_dereference(net_families[family]);

	err = pf->create(net, sock, protocol);
	if (err < 0)
		goto out_module_put;

pf->create()到这里才是真正的无关层体现的地方,即根据协议族和类型,创建socket。但这里的socket实际指的是struct sock结构,这个结构贯穿了整个协议栈。那它和struct socket有啥区别和联系呢?这里先卖个关子,到数据结构那节再具体说。现在接着说pf->create,pf是从net_families[family]中取出来的,pf的结构如下:

struct net_proto_family {
	int		family;
	int		(*create)(struct net *net, struct socket *sock, int protocol);
	struct module	*owner;
};

这实际上是代表了一个协议族,每个协议族有自己的create函数,如inet就是inet_create()。那肯定要问了,那这些协议族是什么时候注册的呢?答案是在协议族初始化的时候。还拿inet族举例,就是在inet_init()时,再往上追就是fs_initcall(inet_init);
我们看到这么一句话:

(void)sock_register(&inet_family_ops);

你瞧,inet_family_ops(struct net_proto_family)把自己注册进"活着"的协议族列表,仿佛在说:“hi,BSD,我INET协议族来报道啦!”

int sock_register(const struct net_proto_family *ops)
{
	int err;

	if (ops->family >= NPROTO) {
		printk(KERN_CRIT "protocol %d >= NPROTO(%d)\n", ops->family,
		       NPROTO);
		return -ENOBUFS;
	}

	spin_lock(&net_family_lock);
	if (net_families[ops->family])
		err = -EEXIST;
	else {
		net_families[ops->family] = ops;
		err = 0;
	}
	spin_unlock(&net_family_lock);

	printk(KERN_INFO "NET: Registered protocol family %d\n", ops->family);
	return err;
}

看那net_families[ops->family]不就是前面提到的么?这就是“活着”的协议族的集合。

4.inet_create(),创建socket。

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;
	}

先根据sock->type找到要创建的socket的模板,模板是什么,怎么来的呢?对于inet协议族而言,模板就是都在inetsw这个链表里存着啦,自然会想到是不是也是在inet_init()时添加进去的呢?没错,我们回过头去看:

for (r = &inetsw[0]; r < &inetsw[SOCK_MAX]; ++r)
		INIT_LIST_HEAD(r);

for (q = inetsw_array; q < &inetsw_array[INETSW_ARRAY_LEN]; ++q)
		inet_register_protosw(q);

到这里不得不说一下socket类型这个东西,站在BSD的角度,抽象了几种socket类型出来:

enum sock_type {
	SOCK_STREAM	= 1,
	SOCK_DGRAM	= 2,
	SOCK_RAW	= 3,
	SOCK_RDM	= 4,
	SOCK_SEQPACKET	= 5,
	SOCK_DCCP	= 6,
	SOCK_PACKET	= 10,
};

对于具体的协议族怎么实现这些对应的类型,BSD是不关心的,比如在inet协议族,tcp对应的是SOCK_STREAM类型,udp对应的是SOCK_DGRAM等。对于其他的协议族而言,要对应起自己的协议。再看上面的两句,初始化了每种inet每种socket类型的链表。然后inet_register_protosw()inetsw_array数组中对应的类型注册到inetsw对应类型的链表中。这个inetsw_array数组其实就是每个协议族连接BSD和自己的传输层的桥梁。就拿其中一个元素来看:

{
		.type =       SOCK_STREAM,
		.protocol =   IPPROTO_TCP,
		.prot =       &tcp_prot,
		.ops =        &inet_stream_ops,
		.capability = -1,
		.no_check =   0,
		.flags =      INET_PROTOSW_PERMANENT |
			      INET_PROTOSW_ICSK,
},

就是说,如果BSD要创建SOCK_STREAM类型的套接字,那么就对应于inet族的tcp协议,自然对于BSD层的操作,也有抽象出来的操作集,就是struct proto_ops表示。那么为什么还会有一个struct ops的结构呢?很明显,这个是用于inet层的操作集,上文提到,每一种类型的socket都有自己的协议链表,也就是对于inet的流式套接字而言,也可能有多种协议(当然,现在只注册了tcp一种)。

接着刚才的创建往下看,找到对应的类型后,就填充其中的成员:

sock->ops = answer->ops;
answer_prot = answer->prot;
answer_no_check = answer->no_check;
answer_flags = answer->flags;

然后就分配了struct sock对象和struct inet_sock对象,
sk = sk_alloc(net, PF_INET, GFP_KERNEL, answer_prot);。初始化sock对象和inet_sock对象。最后调用sock对象的init。主要是针对tcp和raw的,需要初始化,udp就没有init操作。

if (sk->sk_prot->init) {
		err = sk->sk_prot->init(sk);
		if (err)
			sk_common_release(sk);
	}

5.映射socket到文件描述符。在完成socket创建后,就把这个socket和一个文件描述符fd关联起来,以后就可以直接用读写文件的方式访问这个socket了。sock_map_fd()。这一步操作过后,socket创建就算完成了。

1.2 sendto()

1.3 recvfrom()

这两个接口的介绍,就放到下一篇中进行,选着两个主要就是想说一下数据包从内核态到用户态的过程。本篇已经很长了,原谅我先挖个坑吧 😃

二. 相关数据结构

  1. struct socketstruct sockstruct inet_sock
    这两个数据结构实际上是分工协作的,不单纯是分层的关系。struct socket代表的是BSD层上对所有socket的抽象,而struct sock代表的是每个协议族对socket的抽象。这是两个很重要的层次关系,我们说协议无关层下面就是传输层,但并不是说sock是对传输层的socket的抽象,所以,无论是对BSD层的抽象还是对每个协议族的抽象,都应该属于协议无关层,只是对协议无关层两个方面的抽象。这时候就有疑问了:既然都属于协议无关层的抽象,为什么不合并这两个结构呢?因为struct socket是使用inode关联的,我们也看到struct sock结构庞大,如果把struct sock合并到struct socket中,岂不是建每个文件inode都需要占用很大空间咯?所以,把套接字中的跟文件有关的部分拿出来就是struct socket,而把剩余的跟socket数据有关的都放到struct sock中。而struct inet_sock则是为了更加方便使用inet族,在struct sock的基础上,又封装而成的。

  2. struct proto_opsstruct proto
    自然,BSD层既然抽象了socket,就会有抽象的操作集,struct proto_ops就是抽象的操作集,它操作的就是协议族;struct proto就是对协议族层面的操作集的抽象,它操作的就是实际的传输层协议。这个可以跟上面的struct socket和struct sock进行类比。

这些结构共同实现了网络栈协议无关层。所以,可以看到,这一篇没有说到具体传输层的东西,比如tcp,udp操作等。

三. 文件分布

协议无关层的文件分布主要在:

  • net/socket.c
  • net/core/sock.c

四. 小结

linux网络栈非常庞大,包罗万象,支持各种各样的协议,为了能够让应用层简单方便的使用socket,linux提供了抽象层,把跟协议无关的东西拿出来,让应用层仅仅提供参数,就能在适配合适的协议族和使用的协议。大大减小了应用层的复杂度。

posted @ 2017-06-04 21:07  AISEED  阅读(1806)  评论(0编辑  收藏  举报