套接口层之socket系统调用实现

这篇笔记记录了AF_INET协议族在套接口层对scoket()系统调用的实现,注意这里只介绍了套接口层的实现,相比于完整的socket()系统调用实现,这里缺少两部分内容:

  1. 文件系统相关的部分,比如文件描述符的分配等;
  2. 传输层的实现,套接字的创建肯定是要传输层参与的,但是不同的传输层处理方式又不同,这种协议差异会单独在相关笔记中介绍。

socket()系统调用涉及的核心函数调用关系如下图:
在这里插入图片描述

1. 系统调用入口

//三个入参就是socket()系统调用的三个入参
asmlinkage long sys_socket(int family, int type, int protocol)
{
	int retval;
	struct socket *sock;

	//核心操作,创建一个struct socket,该结构是套接字在套接口层的实例,在创建该
	//结构过程中,相关的传输层控制块也会被创建
	retval = sock_create(family, type, protocol, &sock);
	if (retval < 0)
		goto out;

	//将struct socket结构与文件系统描述符绑定,实际上就是为该套接字分配一个文件
	//描述符,使得应用程序可以使用标准的文件系统调用接口操作套接字
	retval = sock_map_fd(sock);
	if (retval < 0)
		goto out_release;

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

out_release:
	//socket与文件描述符映射失败时调用sock_release()关闭socket
	sock_release(sock);
	return retval;
}

2. 套接口层通用处理

int sock_create(int family, int type, int protocol, struct socket **res)
{
	//新增网络命名空间参数和用户空间创建套接字标志。关于网络命名空间的概念,先忽略它
	return __sock_create(current->nsproxy->net_ns, family, type, protocol, res, 0);
}

/* * @kern: 如果是kernel自己创建一个套接字,那么置标记为1;否则是用户空间创建套接字,设置为0 */
static int __sock_create(struct net *net, int family, int type, int protocol,
			 struct socket **res, int kern)
{
	int err;
	struct socket *sock;
	const struct net_proto_family *pf;

	//检查地址族参数和协议类型参数,使得它们不超过当期内核支持的最大值
	/* * Check protocol is in range */
	if (family < 0 || family >= NPROTO)
		return -EAFNOSUPPORT;
	if (type < 0 || type >= SOCK_MAX)
		return -EINVAL;

	//如果要基于数据链路层收发数据,那么应该使用socket(PF_PACKET, SOCK_RAW|SOCK_DGRAM, protocol)
	//来创建套接字。但是老版本内核是使用socket(PF_IENT, SOCK_PACKET, protocol),这里为了向后兼容,
	//强制修改地址族为PF_PACKET
	/* Compatibility. This uglymoron is moved from INET layer to here to avoid deadlock in module load. */
	if (family == PF_INET && type == SOCK_PACKET) {
		static int warned;
		if (!warned) {
			warned = 1;
			printk(KERN_INFO "%s uses obsolete (PF_INET,SOCK_PACKET)\n",
			       current->comm);
		}
		family = PF_PACKET;
	}

	//SELinux相关的安全检查
	err = security_socket_create(family, type, protocol, kern);
	if (err)
		return err;

	//分配套接口层套接字struct socket.该函数涉及文件系统INODE相关内容,比较复杂,
	//但是不影响协议栈分析,这里忽略不再深入探究
	/* * Allocate the socket and allow the family to set things up. if * the protocol is 0, the family is instructed to select an appropriate * default. */
	sock = sock_alloc();
	if (!sock) {
		if (net_ratelimit())
			printk(KERN_WARNING "socket: no more sockets\n");
		return -ENFILE;	/* Not exactly a match, but its the closest posix thing */
	}
	//协议类型记录到struct socket的type字段中
	sock->type = type;

	//如果协议相关的模块尚未加载进内核,则尝试加载,涉及内核模块可动态加载特性,目前一般来讲,
	//内核支持的协议族都是直接编译到内核中的,所以这种情况可以忽略,暂不深究
#if defined(CONFIG_KMOD)
	/* Attempt to load a protocol module if the find failed. * * 12/09/1996 Marcin: But! this makes REALLY only sense, if the user * requested real, full-featured networking support upon configuration. * Otherwise module support will break! */
	if (net_families[family] == NULL)
		request_module("net-pf-%d", family);
#endif

	//从系统全局数组net_families中获取指定协议族定义的struct net_proto_family结构,每个
	//协议族在初始化过程中都会向系统注册一个这样的结构,IPv4在inet_init()中完成注册
	rcu_read_lock();
	pf = rcu_dereference(net_families[family]);
	err = -EAFNOSUPPORT;
	if (!pf)
		goto out_release;

	/* * We will call the ->create function, that possibly is in a loadable * module, so we have to bump that loadable module refcnt first. */
	if (!try_module_get(pf->owner))
		goto out_release;

	/* Now protected by module ref count */
	rcu_read_unlock();

	//调用协议族提供的create()函数完成协议族相关的套接字创建工作,对于AF_INET,该函数为inet_create()
	err = pf->create(net, sock, protocol);
	if (err < 0)
		goto out_module_put;

	/* * Now to bump the refcnt of the [loadable] module that owns this * socket at sock_release time we decrement its refcnt. */
	if (!try_module_get(sock->ops->owner))
		goto out_module_busy;

	/* * Now that we're done with the ->create function, the [loadable] * module can have its refcnt decremented */
	module_put(pf->owner);
	err = security_socket_post_create(sock, family, type, protocol, kern);
	if (err)
		goto out_sock_release;
	*res = sock;

	return 0;

out_module_busy:
	err = -EAFNOSUPPORT;
out_module_put:
	sock->ops = NULL;
	module_put(pf->owner);
out_sock_release:
	sock_release(sock);
	return err;

out_release:
	rcu_read_unlock();
	goto out_sock_release;
}

注:上面的流程实际上属于套接口层对socket()系统调用的通用实现,该过程是所有协议族公用的,从代码中可以看到,只有pf->create()函数指针是协议族相关的,该函数才是各个协议族实现自己独有特性的入口。

3. AF_INET协议族套接字创建

AF_INET协议族支持多个传输层协议,自然而然的,不同的传输层协议,在套接字创建过程中也会有一些自己独有的流程要执行,为了实现这种能力,协议族在初始化过程中就建立了一个数据结构该支持这种需求,见如下介绍。

3.1 AF_INET协议族支持的套接字定义

/* Upon startup we insert all the elements in inetsw_array[] into * the linked list inetsw. */
//AF_INET协议族定义了其支持如下类型的套接字
static struct inet_protosw inetsw_array[] =
{
	{
		.type =       SOCK_STREAM,
		.protocol =   IPPROTO_TCP,
		//TCP协议特化的操作
		.prot =       &tcp_prot,
		//AF_INET协议族对于流类型套接字的通用操作
		.ops =        &inet_stream_ops,
		.capability = -1,
		.no_check =   0,
		//ICSK表示是面向连接的;PERMANET表示不可被卸载
		.flags =      INET_PROTOSW_PERMANENT |
				INET_PROTOSW_ICSK,
	},
    {
		.type =       SOCK_DGRAM,
		.protocol =   IPPROTO_UDP,
		//UDP协议特有的操作
		.prot =       &udp_prot,
		//AF_INET协议族对于数据报类型套接字的通用操作
		.ops =        &inet_dgram_ops,
		.capability = -1,
		.no_check =   UDP_CSUM_DEFAULT,
		.flags =      INET_PROTOSW_PERMANENT,
     },
     {
		.type =       SOCK_RAW,
		//RAW套接字比较特殊,这种套接字跨过了传输层,直接给予网络层IP编程
		.protocol =   IPPROTO_IP,	/* wild card */
		.prot =       &raw_prot,
		.ops =        &inet_sockraw_ops,
		.capability = CAP_NET_RAW,
		.no_check =   UDP_CSUM_DEFAULT,
		.flags =      INET_PROTOSW_REUSE,
     }
};

static int __init inet_init(void)
{
	...
	//协议族初始化过程中将支持的三个套接字添加到AF_INET协议族维护的全局变量inetsw数组中
	for (q = inetsw_array; q < &inetsw_array[INETSW_ARRAY_LEN]; ++q)
		inet_register_protosw(q);
	...
}

注:实际上,AF_INET协议族支持的套接字远不止上面这三个,只是这三个最常用。其它套接字的注册分散在各个模块的初始化过程中,用到时再研究,这里不再深入。

3.2 inet_create()

inet_create()是AF_INET协议族的套接字创建函数入口,它由前面的__sock_create()通过pf->create()调用。

/* * Create an inet socket. */
static int inet_create(struct net *net, struct socket *sock, int protocol)
{
	struct sock *sk;
	struct list_head *p;
	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;

	//网络命名空间校验
	if (net != &init_net)
		return -EAFNOSUPPORT;

	//对于TCP&UDP,如果inet_ehash_seret变量未初始化,则调用build_ehash_secret()进行初始化。目前
	//还没有看到该变量的作用,但实际上build_ehash_seret()实际上就是生成一个非零随机数而已
	if (sock->type != SOCK_RAW &&
	    sock->type != SOCK_DGRAM &&
	    !inet_ehash_secret)
		build_ehash_secret();

	//将套接字状态设置为未连接状态,注意这里的状态仅仅是套接口层记录的socket状态,
	//并非传输层的状态,传输层状态由传输层控制块记录
	sock->state = SS_UNCONNECTED;

	//以参数type(套接字类型)为索引,遍历数组inetsw[sock->type],寻找匹配的协议。
	//IPv4协议族将所有支持的协议保存到数组inetsw中,相同类型的协议组织成一个双链表
	/* Look for the requested type/protocol pair. */
	answer = NULL;
lookup_protocol:
	err = -ESOCKTNOSUPPORT;
	rcu_read_lock();
	list_for_each_rcu(p, &inetsw[sock->type]) {
		answer = list_entry(p, struct inet_protosw, list);

		//在指定类型的协议中进行精确匹配,所谓精确匹配是指socket()的protocol参数不为0(即IPPROTO_IP)
		/* Check the non-wild match. */
		if (protocol == answer->protocol) {
			if (protocol != IPPROTO_IP)
				break;
		} else {
			//进行模糊模糊匹配,分为两种情况:
			//1. 应用程序指定的协议为通配协议即0,这时直接选用指定类型协议链表中的第一个协议
			/* Check for the two wild cases. */
			if (IPPROTO_IP == protocol) {
				protocol = answer->protocol;
				break;
			}
			//2.应用程序指定的是具体的协议,但是指定类型协议链表中有通配协议,也可以匹配
			if (IPPROTO_IP == answer->protocol)
				break;
		}
		err = -EPROTONOSUPPORT;
		answer = NULL;
	}
	//有些协议是以模块的形式实现的,如果第一遍没有找到符合要求的协议,则尝试加载相应的模块后重新进行匹配
	if (unlikely(answer == NULL)) {
		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 (answer->capability > 0 && !capable(answer->capability))
		goto out_rcu_unlock;

	//将协议族提供给套接口层的操作函数集ops记录到套接字结构中
	sock->ops = answer->ops;
	//临时获取指定协议提供的一些参数
	answer_prot = answer->prot;
	answer_no_check = answer->no_check;
	answer_flags = answer->flags;
	rcu_read_unlock();

	BUG_TRAP(answer_prot->slab != NULL);

	//根据传输层协议结构answer_prot创建相应的传输层控制块并进行一定的初始化,见下文分析
	err = -ENOBUFS;
	sk = sk_alloc(net, PF_INET, GFP_KERNEL, answer_prot);
	if (sk == NULL)
		goto out;

	//将是否需要校验标记记录到传输控制块中
	err = 0;
	sk->sk_no_check = answer_no_check;
	//如果传输层协议指定了地址复用标记,则将传输控制块中的sk_reuse字段置1,表示可以进行地址和端口复用。
	//TCP和UDP默认均未设置该标记
	if (INET_PROTOSW_REUSE & answer_flags)
		sk->sk_reuse = 1;

	//根据是否有INET_PROTOSW_ICSK标记设置传输控制块的is_icsk变量,该标记表示该协议是否是面向连接的协议,
    //显然,TCP会设置该标记,UDP不会
	inet = inet_sk(sk);
	inet->is_icsk = (INET_PROTOSW_ICSK & answer_flags) != 0;

	//RAW类型套接字相关处理,暂时忽略
	if (SOCK_RAW == sock->type) {
		inet->num = protocol;
		if (IPPROTO_RAW == protocol)
			inet->hdrincl = 1;
	}
	//根据系统参数no_pmtu_disc设置传输控制块是否支持路径MTU发现机制
	if (ipv4_config.no_pmtu_disc)
		inet->pmtudisc = IP_PMTUDISC_DONT;
	else
		inet->pmtudisc = IP_PMTUDISC_WANT;

	//进一步初始化通用传输控制块相关成员
	inet->id = 0;
	//初始化通用结构struct sock的一些成员
	sock_init_data(sock, sk);
	//struct sock结构的析构函数
	sk->sk_destruct	   = inet_sock_destruct;
	sk->sk_family	   = PF_INET;
	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_index	= 0;
	inet->mc_list	= NULL;

	sk_refcnt_debug_inc(sk);

	//如果传输控制块中设置了端口号,则调用传输层的hash()接口将该端口信息加入到传输层的管理结构中。
	//TCP和UDP不会有这种行为,它们的端口绑定都是在socket()调用之后显式或者隐式绑定的。端口的绑定
	//在bind()系统调用中再仔细研究
	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);
	}

	//如果传输层协议提供了init()接口,则调用它完成传输层特有的初始化。对于TCP,
	//该函数为tcp_v4_init_sock(),UDP没有定义该接口
	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;
}

3.2.1 传输控制块的分配

/** * sk_alloc - All socket objects are allocated here * @net: the applicable net namespace * @family: protocol family * @priority: for allocation (%GFP_KERNEL, %GFP_ATOMIC, etc) * @prot: struct proto associated with this new sock instance * @zero_it: if we should zero the newly allocated sock */
struct sock *sk_alloc(struct net *net, int family, gfp_t priority,
		      struct proto *prot)
{
	struct sock *sk;

	//调用sk_prot_alloc()完成传输控制块的分配
	sk = sk_prot_alloc(prot, priority | __GFP_ZERO, family);
	if (sk) {
		sk->sk_family = family;
		//将传输层协议接口记录到传输控制块的sk_prot中
		/* * See comment in struct sock definition to understand * why we need sk_prot_creator -acme */
		sk->sk_prot = sk->sk_prot_creator = prot;
		//传输控制块中的锁相关初始化
		sock_lock_init(sk);
		sk->sk_net = get_net(net);
	}

	return sk;
}

static struct sock *sk_prot_alloc(struct proto *prot, gfp_t priority,
		int family)
{
	struct sock *sk;
	struct kmem_cache *slab;

	//如果传输层协议提供了高速缓存,则从高速缓存中分配传输控制块;
	//如果没有提供,则调用普通的内存分配函数分配
	slab = prot->slab;
	if (slab != NULL)
		sk = kmem_cache_alloc(slab, priority);
	else
		sk = kmalloc(prot->obj_size, priority);

	if (sk != NULL) {
		if (security_sk_alloc(sk, family, priority))
			goto out_free;

		if (!try_module_get(prot->owner))
			goto out_free_sec;
	}

	return sk;

out_free_sec:
	security_sk_free(sk);
out_free:
	if (slab != NULL)
		kmem_cache_free(slab, sk);
	else
		kfree(sk);
	return NULL;
}

3.2.2 通用传输控制块的初始化

void sock_init_data(struct socket *sock, struct sock *sk)
{
	//初始化接收队列
	skb_queue_head_init(&sk->sk_receive_queue);
	//初始化发送队列
	skb_queue_head_init(&sk->sk_write_queue);
	//初始化错误队列
	skb_queue_head_init(&sk->sk_error_queue);
#ifdef CONFIG_NET_DMA
	skb_queue_head_init(&sk->sk_async_wait_queue);
#endif
	//发送指针置空,表示当前没有任何数据要发送
	sk->sk_send_head	=	NULL;

	init_timer(&sk->sk_timer);

	sk->sk_allocation	=	GFP_KERNEL;
	//初始化读写缓冲区上限为系统默认值(可以通过SO_SNDBUF/SO_RECVBUF修改,如果不修改,三次握手成功之后
	//系统还会执行一次调整)
	sk->sk_rcvbuf		=	sysctl_rmem_default;
	sk->sk_sndbuf		=	sysctl_wmem_default;
	sk->sk_state		=	TCP_CLOSE;
	sk->sk_socket		=	sock;

	sock_set_flag(sk, SOCK_ZAPPED);

	if (sock) {
		sk->sk_type	=	sock->type;
		sk->sk_sleep	=	&sock->wait;
		sock->sk	=	sk;
	} else
		sk->sk_sleep	=	NULL;

	rwlock_init(&sk->sk_dst_lock);
	rwlock_init(&sk->sk_callback_lock);
	lockdep_set_class_and_name(&sk->sk_callback_lock,
			af_callback_keys + sk->sk_family,
			af_family_clock_key_strings[sk->sk_family]);

	sk->sk_state_change	=	sock_def_wakeup;
	sk->sk_data_ready	=	sock_def_readable;
	sk->sk_write_space	=	sock_def_write_space;
	sk->sk_error_report	=	sock_def_error_report;
	sk->sk_destruct		=	sock_def_destruct;

	sk->sk_sndmsg_page	=	NULL;
	sk->sk_sndmsg_off	=	0;

	sk->sk_peercred.pid 	=	0;
	sk->sk_peercred.uid	=	-1;
	sk->sk_peercred.gid	=	-1;
	sk->sk_write_pending	=	0;
	sk->sk_rcvlowat		=	1;
	sk->sk_rcvtimeo		=	MAX_SCHEDULE_TIMEOUT;
	sk->sk_sndtimeo		=	MAX_SCHEDULE_TIMEOUT;

	sk->sk_stamp = ktime_set(-1L, 0);

	atomic_set(&sk->sk_refcnt, 1);
	atomic_set(&sk->sk_drops, 0);
}

上面的初始化过程对传输控制块中的很多成员进行了赋值,很繁琐但是非常的重要,涉及成员较多,无法一一介绍,但是这里需要把握初始化的层次结构,这样在真正需要查询某个字段的初始值时可以迅速准确的定位:

  1. 创建后(即内存分配后)立马进行的通用初始化,这部分是指sock_init_data(),而且这部分是对通用传输控制块struct sock中各个字段的初始化;
  2. AF_INET协议族对传输控制块的一些成员进行初始化,这部分是在inet_create()中完成的;
  3. 传输层协议特有的初始化,这部分是通过在inet_create()中调用协议特有的init()接口实现的。通常这部分主要是初始化各个传输层控制块特有的成员,如TCP的tcp_v4_init_sock()用于初始化struct tcp_sock的各个字段。

至此,AF_INET协议族在套接口层对系统调用socket()的实现部分就分析完了,还有各个传输层所实现的特有的过程在各个传输层协议的代码分析中会体现。