linux内核协议栈 socket 创建流程

目录

1、socket 概述

2、socket 创建入口

2.1、sys_socketcall

2.2、sys_socket

3、socket 创建过程

3.1、sock_create(__sock_create)

3.2、socket 节点分配以及初始化(sock_alloc)

3.3、根据指定协议族继续初始化

3.3.1、inet_create注册(inet_init)

3.3.2、inet_create调用(核心)


1、socket 概述

为了建立Socket,程序可以调用Socket函数,函数返回一个文件描述符,原型为: 

int socket(int domain, int type, int protocol); 

domain:指定协议簇,常见的有AF_IENT, AF_INET6, AF_UNIX/AF_LOCAL, AF_NETLINK等。
type:指定类型,常见的有SOCK_STREAM、SOCK_DGRAM、SOCK_RAW等。
protocol:指定传输协议,如TCP/UDP等,不同协议簇都对应了默认的协议,可以填充 0 使用默认值。

关于 socket 函数的用法,参见《socket编程:socket接口创建套接字(原始套接字)使用说明》

以 linux 内核 2.6.38 为例,简要分析socket创建内核的操作流程,完整调用栈如下:

sys_socketcall
sys_socket
sock_create
__sock_create
inet_create//err = pf->create(net, sock, protocol);

当用户态执行socket创建时,内核从sys_socketcall开始执行到inet_create执行完,差不就完成了socket的内核创建,返回给用户态。当然其中还会涉及一些参数检查、安全检查以及映射等操作。socket创建相关函数基本都在socket.c文件中

2、socket 创建入口

2.1、sys_socketcall

在 sys_socketcall 函数中,通过判断call指令,来统一处理 socket 相关函数的事务,对于socket(…)函数,实际处理是在 sys_socket 中。

/*
 *	System call vectors.
 *
 *	Argument checking cleaned up. Saved 20% in size.
 *  This function doesn't need to set the kernel lock because
 *  it is set by the callees.
 */

asmlinkage long sys_socketcall(int call, unsigned long __user *args)
{
	unsigned long a[6];
	unsigned long a0, a1;
	int err;

	if (call < 1 || call > SYS_ACCEPT4)
		return -EINVAL;

	/* copy_from_user should be SMP safe. */
	if (copy_from_user(a, args, nargs[call]))
		return -EFAULT;

	err = audit_socketcall(nargs[call] / sizeof(unsigned long), a);
	if (err)
		return err;

	a0 = a[0];
	a1 = a[1];

	switch (call) {
	case SYS_SOCKET:
		err = sys_socket(a0, a1, a[2]);
		break;
	case SYS_BIND:
		err = sys_bind(a0, (struct sockaddr __user *)a1, a[2]);
		break;
	case SYS_CONNECT:
		err = sys_connect(a0, (struct sockaddr __user *)a1, a[2]);
		break;
	case SYS_LISTEN:
		err = sys_listen(a0, a1);
		break;
	case SYS_ACCEPT:
		err = sys_accept4(a0, (struct sockaddr __user *)a1,
				  (int __user *)a[2], 0);
		break;
	....
	default:
		err = -EINVAL;
		break;
	}
	return err;
}

2.2、sys_socket

进入 sys_socket 函数就开始了 socket 的真正创建过程。这里主要完成三件事情:

  1. 校验type的合法性,如果设置了除基本sock_type,SOCK_CLOEXEC和SOCK_NONBLOCK之外的其他参数,则直接返回err。
  2. 调用 sock_create 创建 socket 结构。
  3. 使用 sock_map_fd 将socket 结构映射为文件描述符并返回给用户空间。

sys_socket 函数如下:

asmlinkage long sys_socket(int family, int type, int protocol)
{
	int retval;
	struct socket *sock;
	int flags;
    ...
	 // SOCK_TYPE_MASK: 0xF; SOCK_STREAM等socket类型位于type字段的低4位
    // 将flag设置为除socket基本类型之外的值
	flags = type & ~SOCK_TYPE_MASK;
	
	// 如果flags中有除SOCK_CLOEXEC或者SOCK_NONBLOCK之外的其他参数,则返回EINVAL
	if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))
		return -EINVAL;
		
	// 取type中的后4位,即sock_type,socket基本类型定义	
	type &= SOCK_TYPE_MASK;

	// 如果设置了SOCK_NONBLOCK,则不论SOCK_NONBLOCK定义是否与O_NONBLOCK相同,
    // 均将flags中的SOCK_NONBLOCK复位,将O_NONBLOCK置位
	if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK))
		flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK;

	//根据协议族、套接字类型、协议栈类创建socket
	retval = sock_create(family, type, protocol, &sock);
	if (retval < 0)
		goto out;
	// 将socket结构映射为文件描述符retval并返回,
	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;
}

3、socket 创建过程

3.1、sock_create(__sock_create)

socket结构体中定义了socket 的基本状态,类型,标志,等待队列,文件指针,操作函数集等,利用 sock 结构,将 socket 操作与真正处理网络协议相关的事务分离。具体结构如下:

/**
 *  struct socket - general BSD socket
 *  @state: socket state (%SS_CONNECTED, etc)
 *  @type: socket type (%SOCK_STREAM, etc)
 *  @flags: socket flags (%SOCK_ASYNC_NOSPACE, etc)
 *  @ops: protocol specific socket operations
 *  @fasync_list: Asynchronous wake up list
 *  @file: File back pointer for gc
 *  @sk: internal networking protocol agnostic socket representation
 *  @wait: wait queue for several uses
 */
struct socket {
	socket_state		state;// 连接状态:SS_CONNECTING, SS_CONNECTED 等
	short			type;     // 类型:SOCK_STREAM, SOCK_DGRAM 等
	unsigned long		flags;// 标志位:SOCK_ASYNC_NOSPACE(发送队列是否已满)等
	const struct proto_ops	*ops;// socket操作函数集:bind, connect, accept 等
	struct fasync_struct	*fasync_list;
	struct file		*file; // 该socket结构体对应VFS中的file指针
	struct sock		*sk;   // socket网络层表示,真正处理网络协议的地方
	wait_queue_head_t	wait;// 等待队列
};

回到 sock_create 继续看socket创建过程,sock_create 实际调用的是__sock_create。该接口主要完成以下几件事:

  1. 校验协议族类型、套接字类型的合法性。
  2. 操作安全权限检查(SElinux),如果该scoket是由用户态创建来触发创建则需要检查,内核态触发创建则跳过。
  3. 调用sock_alloc配scoket节点,从socket_fs文件系统中申请。
  4. 根据传入的协议族类型创建哪一层的套接字(ipv4、ipv6、以太网层)。
  5. 通过RCU机制来保证,创建过程的同步互斥。

__sock_create代码如下:

int sock_create(int family, int type, int protocol, struct socket **res)
{
	return __sock_create(current->nsproxy->net_ns, family, type, protocol, res, 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;

	if (family < 0 || family >= NPROTO)// 检查是否是支持的地址族,即检查协议
		return -EAFNOSUPPORT;
	if (type < 0 || type >= SOCK_MAX) // 检查是否是支持的socket类型
		return -EINVAL;

    // 检查权限,并考虑协议集、类型、协议,以及 socket 是在内核中创建还是在用户空间中创建
    // 可以参考:https://www.ibm.com/developerworks/cn/linux/l-selinux/
	err = security_socket_create(family, type, protocol, kern);
	if (err)
		return err;
	...
	sock = sock_alloc();// 分配socket结构,这其中创建了socket和关联的inode (重点分析)
	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 */
	}

	...
	rcu_read_lock();//socket通过RCU机制实现同步互斥操作。
	根据API传入的family信息确定是那个协议簇,很据协议族在socket创建此处分流
	pf = rcu_dereference(net_families[family]);
	...
	rcu_read_unlock();
	
	/*
	 * Now to bump the refcnt of the [loadable] module that owns this
	 * socket at sock_release time we decrement its refcnt.
	 */
    /*调用协议簇对应的create函数
	  在IPv4协议族中调用inet_create()对已创建的socket继续进行初始化,同时创建网络层socket*/
	err = pf->create(net, sock, protocol);
	if (err < 0)
		goto out_module_put;

	/*如果proto_ops结构实例所在模块以内核模块方式动态加载进内核,
	 则增加该模块的引用计数,在sock_release时,减小该计数。*/
	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
	 */
	//调用完inet_create函数后,对此模块的引用计数减一。
	module_put(pf->owner);
	//安全模块对创建后的socket做安全检查
	err = security_socket_post_create(sock, family, type, protocol, kern);
	if (err)
		goto out_sock_release;
	*res = sock;

	return 0;

	....
out_release:
	rcu_read_unlock();
	goto out_sock_release;
}

3.2、socket 节点分配以及初始化(sock_alloc)

sock_mnt 是在 socket 初始化向系统挂载sock_fs时保存的vfsmount结构,其mnt_sb指向了该文件系统对应的超级块。new_inode 函数用于从sock_fs文件系统中申请一个节点。然后通过SOCKET_I(inode)获取节点指针返回,实现是取地址偏移。

static struct socket *sock_alloc(void)
{
    struct inode *inode;
    struct socket *sock;

    inode = new_inode(sock_mnt->mnt_sb);//向文件系统申请节点
    if (!inode)
        return NULL;

    sock = SOCKET_I(inode);//获取指针返回

    kmemcheck_annotate_bitfield(sock, type);
    inode->i_mode = S_IFSOCK | S_IRWXUGO;
    inode->i_uid = current_fsuid();
    inode->i_gid = current_fsgid();

    percpu_add(sockets_in_use, 1);
    return sock;
}

3.3、根据指定协议族继续初始化

调用了sock_alloc申请socket后,申请socket的过程已经全部结束,继续分析__sock_create函数,则是根据API传入的family信息确定是那个协议簇,然后调用协议簇对应的create函数:err = pf->create(net, sock, protocol)这里不同的协议簇走的流程开始不一样了。

3.3.1、inet_create注册(inet_init)

static int __init inet_init(void)
{
    ......
    (void)sock_register(&inet_family_ops);//注册协议簇

    for (q = inetsw_array; q < &inetsw_array[INETSW_ARRAY_LEN]; ++q)
        inet_register_protosw(q);//注册 inet_protosw
    ......
}

inet_init是在网络模块初始化要调用的初始化函数,这个函数完成的任务不少,暂时我们先关注上面两个部分,其余部分后续还会再回头来看。

static struct net_proto_family inet_family_ops = {
    .family = PF_INET,
    .create = inet_create,
    .owner    = THIS_MODULE,
};
int sock_register(const struct net_proto_family *ops)
{
    ......
    spin_lock(&net_family_lock);
    if (net_families[ops->family])
        err = -EEXIST;
    else {
        net_families[ops->family] = ops;
        err = 0;
    }
    ......
}

3.3.2、inet_create调用(核心)

sock_register中会把inet_family_ops注册到net_families数组中,根据具体协议簇的类型进行索引调用,我们回想一下在__sock_create时,按照如下方式调用

pf = rcu_dereference(net_families[family]);
err = pf->create(net, sock, protocol);

inet_create 函数是内核创建socket的核心,代码如下:

static int inet_create(struct net *net, struct socket *sock, int protocol)
{
    ......
    sock->state = SS_UNCONNECTED;
    ......
    list_for_each_entry_rcu(answer, &inetsw[sock->type], list) {
    ......
    }
    ......
    sock->ops = answer->ops;
    answer_prot = answer->prot;
    answer_no_check = answer->no_check;
    answer_flags = answer->flags;
    ......
    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;
    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_ipv4:tcp_v4_init_sock
        err = sk->sk_prot->init(sk);
        if (err)
            sk_common_release(sk);
    }
}

首先、sock->state设置为未连接状态SS_UNCONNECTED。

次之、根据用户指定的 type() 在 inetsw 查找对应的 inet_protosw 结构。这里需要先确认一下inetsw里面对应了哪些内容以及使如何注册上的,现在可以回头看看 inet_init了,本文的最上面部分代码,先遍历 inetsw_array 数组,然后对每个元素调用inet_register_protosw 函数进行注册。看看 inetsw_array 存储了什么。

/* Upon startup we insert all the elements in inetsw_array[] into
 * the linked list inetsw.
 */
static struct inet_protosw inetsw_array[] =
{
	{
		.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,
	},

	{
		.type =       SOCK_DGRAM,
		.protocol =   IPPROTO_UDP,
		.prot =       &udp_prot,
		.ops =        &inet_dgram_ops,
		.capability = -1,
		.no_check =   UDP_CSUM_DEFAULT,
		.flags =      INET_PROTOSW_PERMANENT,
       },


       {
	       .type =       SOCK_RAW,
	       .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,
       }
};

对应了常用的三种类型,(SOCK_STREAM/IPPROTO_TCP), (SOCK_DGRAM/IPPROTO_UDP), (SOCK_RAW/IPPROTO_IP),用户通过type指定。以TCP为例看一下相关的socket 操作接口集合 tcp_prot 和inet_stream_ops,该集合中包含了后期tcp建链、传输、断链等系统调用对应的内核操作。具体如下:

struct proto tcp_prot = {
	.name			= "TCP",
	.owner			= THIS_MODULE,
	.close			= tcp_close,
	.connect		= tcp_v4_connect,
	.disconnect		= tcp_disconnect,
	.accept			= inet_csk_accept,
	.ioctl			= tcp_ioctl,
	.init			= tcp_v4_init_sock,
	.destroy		= tcp_v4_destroy_sock,
	.shutdown		= tcp_shutdown,
	.setsockopt		= tcp_setsockopt,
	.getsockopt		= tcp_getsockopt,
	.recvmsg		= tcp_recvmsg,
	.backlog_rcv		= tcp_v4_do_rcv,
	.hash			= inet_hash,
	.unhash			= inet_unhash,
	.get_port		= inet_csk_get_port,
	.enter_memory_pressure	= tcp_enter_memory_pressure,
	.sockets_allocated	= &tcp_sockets_allocated,
	.orphan_count		= &tcp_orphan_count,
	.memory_allocated	= &tcp_memory_allocated,
	.memory_pressure	= &tcp_memory_pressure,
	.sysctl_mem		= sysctl_tcp_mem,
	.sysctl_wmem		= sysctl_tcp_wmem,
	.sysctl_rmem		= sysctl_tcp_rmem,
	.max_header		= MAX_TCP_HEADER,
	.obj_size		= sizeof(struct tcp_sock),
	.twsk_prot		= &tcp_timewait_sock_ops,
	.rsk_prot		= &tcp_request_sock_ops,
	.h.hashinfo		= &tcp_hashinfo,
#ifdef CONFIG_COMPAT
	.compat_setsockopt	= compat_tcp_setsockopt,
	.compat_getsockopt	= compat_tcp_getsockopt,
#endif
};

const struct proto_ops inet_stream_ops = {
	.family		   = PF_INET,
	.owner		   = THIS_MODULE,
	.release	   = inet_release,
	.bind		   = inet_bind,
	.connect	   = inet_stream_connect,
	.socketpair	   = sock_no_socketpair,
	.accept		   = inet_accept,
	.getname	   = inet_getname,
	.poll		   = tcp_poll,
	.ioctl		   = inet_ioctl,
	.listen		   = inet_listen,
	.shutdown	   = inet_shutdown,
	.setsockopt	   = sock_common_setsockopt,
	.getsockopt	   = sock_common_getsockopt,
	.sendmsg	   = tcp_sendmsg,
	.recvmsg	   = sock_common_recvmsg,
	.mmap		   = sock_no_mmap,
	.sendpage	   = tcp_sendpage,
	.splice_read	   = tcp_splice_read,
#ifdef CONFIG_COMPAT
	.compat_setsockopt = compat_sock_common_setsockopt,
	.compat_getsockopt = compat_sock_common_getsockopt,
#endif
};

以以获取cp的全链接系统调用accept为例,当任务阻塞在accept处时,内核堆栈如下:

[root@localhost tcp]# cat /proc/2591/stack
[<ffffffff8147a431>] inet_csk_accept+0x1c1/0x260
[<ffffffff814a07e4>] inet_accept+0x34/0xf0
[<ffffffff81427a95>] sys_accept4+0x155/0x2b0
[<ffffffff81427c00>] sys_accept+0x10/0x20
[<ffffffff8100b0d2>] system_call_fastpath+0x16/0x1b
[<ffffffffffffffff>] 0xffffffffffffffff

inet_init 中的循环遍历数组 inetsw_array 已经 分别把这三种type进行注册,再来看看注册到哪里去了。

void inet_register_protosw(struct inet_protosw *p)
{
    ......
    answer = NULL;
    last_perm = &inetsw[p->type];
    list_for_each(lh, &inetsw[p->type]) {
        answer = list_entry(lh, struct inet_protosw, list);

        /* Check only the non-wild match. */
        if (INET_PROTOSW_PERMANENT & answer->flags) {
            if (protocol == answer->protocol)
                break;
            last_perm = lh;
        }
        answer = NULL;
    }
}

很明显,注册到了 inetsw 中,现在可以回头继续分析 inet_create 函数了,answer实际对应了TCP/UDP/RAW的inet_protosw结构,进行一系列赋值,然后调用sk_alloc函数。

struct sock *sk_alloc(struct net *net, int family, gfp_t priority, struct proto *prot)
{
    struct sock *sk;

    sk = sk_prot_alloc(prot, priority | __GFP_ZERO, family);
    if (sk) {
        sk->sk_family = family;
        sk->sk_prot = sk->sk_prot_creator = prot;
        sock_lock_init(sk);
        sock_net_set(sk, get_net(net));
        atomic_set(&sk->sk_wmem_alloc, 1);
    }
    return sk;
}

sk_prot_alloc 实际上是在 prot 对应的 slab 中或者直接 kmalloc 申请一个sock结构,然后设置协议簇,以及 proto 结构信息。这里需要注意一下,目前返回的是一个struct sock的结构,而实际上并不仅仅是这个简单的结构。我们先回到inet_create函数中继续向下分析。可以看到申请完sock之后,inet_create函数中使用了inet = inet_sk(sk)对sock进行了强制转换,然后对转换得到的inet结构进行一系列赋值。而inet_sock是INET域专用的一个sock表示,在struct sock上扩展而来,除基本sock属性,提供了INET域专用的属性,如TTL、IP地址、端口等。这里是怎么对应上的呢。还得再回头研究一下,申请sock的最终实现,申请sock时最终走的是下面的函数,前面简单提到过,但没深入。

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) {
        .......
    else
        sk = kmalloc(prot->obj_size, priority);

sock的申请是通过 kmalloc 实现的,申请的大小由prot->obj_size指定,而prot->obj_size是由上面static struct inet_protosw inetsw_array[] 数组成员结构中的 proto 结构中指定的,而这个Proto结构区分了TCP/UDP/RAW协议,见上面数组定义。其中tcp_prot的.obj_size = sizeof(struct tcp_sock), 
udp_prot的.obj_size   = sizeof(struct udp_sock), raw_prot的.obj_size   = sizeof(struct raw_sock)。再看一下这几个结构的具体定义:

struct tcp_sock {
    /* inet_connection_sock has to be the first member of tcp_sock */
    struct inet_connection_sock    inet_conn;
    u16    tcp_header_len;    /* Bytes of tcp header to send        */
    u16    xmit_size_goal_segs; /* Goal for segmenting output packets */

/*
 *    Header prediction flags
 *    0x5?10 << 16 + snd_wnd in net byte order
 */
    __be32    pred_flags;
........
struct udp_sock {
    /* inet_sock has to be the first member */
    struct inet_sock inet;
    int         pending;    /* Any pending frames ? */
    unsigned int     corkflag;    /* Cork is required */
      __u16         encap_type;    /* Is this an Encapsulation socket? */
    /*
......
struct raw_sock {
    /* inet_sock has to be the first member */
    struct inet_sock inet;
    struct icmp_filter filter;
};

除了tcp_sock第一个对应的是inet_connection_sock外,udp和raw对应的都是inet_sock。我们看一下inet_connection_sock

struct inet_connection_sock {
    /* inet_sock has to be the first */
    struct inet_sock     icsk_inet;
    struct request_sock_queue icsk_accept_queue;
    struct inet_bind_bucket     *icsk_bind_hash;
    unsigned long         icsk_timeout;
    ......
}

struct inet_sock {
    /* sk and pinet6 has to be the first two members of inet_sock */
    struct sock        sk;
    ......
}

inet_connection_sock 第一个成员也是inet_sock,而 inet_sock 的第一个成员是struct sock。这样就比较清晰了,可以这么理解struct sock 是一个基类是 Linux socket 的最基础的面向协议栈的结构,而 inet_sock 在继承 sock 的基础上进行了扩展,除了基本socket属性外提供了INET域专用的属性,如TTL等。简单的,udp_sock和raw_sock在inet_sock的基础上进行了私有扩展。inet_connection_sock在继承inet_sock的基础上,扩展了所有面向连接相关的一些协议信息,而tcp_sock则在inet_connection_sock基础上进一步扩展,增加了TCP的一些私有属性,比如滑动窗口,拥塞控制等。至此,上述inet_create函数中inet = inet_sk(sk);的转换就没有任何疑问了。

回到 inet_create,除了对inet_sk结构的赋值外,也对struct sock结构进行了一系列的初始化,主要涉及收发包队列/缓冲区等。最后调用具体协议类型init函数,这里主要是对应了tcp_v4_init_sock的函数,而udp没有定义init,raw的init函数仅仅是把filter字段给清0了。即inet_create中的结尾部分:

static int inet_create(struct net *net, struct socket *sock, int protocol)
{
    ...
   if (sk->sk_prot->init) {//具体协议的初始化,tcp_ipv4:tcp_v4_init_sock
        err = sk->sk_prot->init(sk);
        if (err)
            sk_common_release(sk);
    }
    ...
}

猜你喜欢

转载自blog.csdn.net/wangquan1992/article/details/108526898