Linux kernel protocol stack socket creation process

table of Contents

1. Socket overview

2. Socket creation entry

2.1、sys_socketcall

2.2、sys_socket

3. Socket creation process

3.1、sock_create(__sock_create)

3.2. Socket node allocation and initialization (sock_alloc)

3.3. Continue to initialize according to the specified protocol family

3.3.1, inet_create registration (inet_init)

3.3.2, inet_create call (core)


1. Socket overview

In order to establish a Socket, the program can call the Socket function, the function returns a file descriptor, the prototype is: 

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

domain: Specify the protocol cluster, common ones are AF_IENT, AF_INET6, AF_UNIX/AF_LOCAL, AF_NETLINK, etc.
type: Specify the type, common ones are SOCK_STREAM, SOCK_DGRAM, SOCK_RAW, etc.
protocol: Specify the transmission protocol, such as TCP/UDP, etc. Different protocol clusters correspond to the default protocol. You can fill in 0 to use the default value.

For the usage of socket function, please refer to "Socket Programming: Socket Interface Creation Socket (Original Socket) Instructions"

Take linux kernel 2.6.38 as an example, briefly analyze the operation process of socket creation kernel, the complete call stack is as follows:

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

When the user mode executes the socket creation, the kernel starts executing from the sys_socketcall to the inet_create execution, and the kernel creation of the socket is shortly completed and returns to the user mode. Of course, some parameter checks, security checks, and mapping operations are also involved. Socket creation related functions are basically in the socket.c file

2. Socket creation entry

2.1、sys_socketcall

In the sys_socketcall function, the transaction of socket related functions is processed uniformly by judging the call instruction. For the socket(...) function, the actual processing is in 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

Entering the sys_socket function begins the real process of socket creation. Three things are mainly completed here:

  1. Check the validity of the type. If other parameters except the basic sock_type, SOCK_CLOEXEC and SOCK_NONBLOCK are set, err will be returned directly.
  2. Call sock_create to create the socket structure.
  3. Use sock_map_fd to map the socket structure to a file descriptor and return it to user space.

The sys_socket function is as follows:

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 creation process

3.1、sock_create(__sock_create)

The socket structure defines the basic status, type, flag, waiting queue, file pointer, operation function set, etc. of the socket. Using the sock structure, the socket operation is separated from the transaction related to the actual processing of the network protocol. The specific structure is as follows:

/**
 *  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;// 等待队列
};

Back to sock_create to continue to see the socket creation process, sock_create actually calls __sock_create. This interface mainly accomplishes the following things:

  1. Verify the legality of the protocol family type and socket type.
  2. Operation security permission check (SElinux), if the scoket is created by the user mode to trigger the creation, it needs to be checked, and the kernel mode is triggered to skip the creation.
  3. Call sock_alloc to configure the scoket node and apply from the socket_fs file system.
  4. Which layer of socket (ipv4, ipv6, Ethernet layer) is created according to the type of the incoming protocol family.
  5. The RCU mechanism is used to ensure the synchronization and mutual exclusion of the creation process.

__sock_create code is as follows:

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 node allocation and initialization (sock_alloc)

sock_mnt is the vfsmount structure saved when the socket is initialized to mount the sock_fs to the system, and its mnt_sb points to the super block corresponding to the file system. The new_inode function is used to apply for a node from the sock_fs file system. Then get the node pointer back through SOCKET_I (inode) , the realization is to take the address offset.

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. Continue to initialize according to the specified protocol family

After calling sock_alloc to apply for a socket, the process of applying for a socket has all ended. Continue to analyze the __sock_create function, and determine which protocol cluster is based on the family information passed in by the API, and then call the create function corresponding to the protocol cluster: err = pf-> The process of create(net, sock, protocol) here is different for different protocol clusters.

3.3.1, inet_create registration (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 is the initialization function to be called in the initialization of the network module. This function accomplishes a lot of tasks. For the time being, we will pay attention to the above two parts first, and the rest will come back later.

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 call (core)

In sock_register, inet_family_ops will be registered in the net_families array, and index calls will be made according to the type of the specific protocol cluster. Let’s recall that when __sock_create is called, it is called as follows

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

The inet_create function is the core of the socket creation by the kernel. The code is as follows:

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

First, sock->state is set to SS_UNCONNECTED.

Secondly, find the corresponding inet_protosw structure in inetsw according to the type() specified by the user. Here you need to confirm what content corresponds to inetsw and how to register it. Now you can look back at inet_init. In the top part of this article, first traverse the inetsw_array array, and then call the inet_register_protosw function for each element to register. See what is stored in 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,
       }
};

Corresponding to the three commonly used types, (SOCK_STREAM/IPPROTO_TCP), (SOCK_DGRAM/IPPROTO_UDP), (SOCK_RAW/IPPROTO_IP), the user specifies by type. Take TCP as an example to look at the related socket operation interface sets tcp_prot and inet_stream_ops. This set contains the kernel operations corresponding to the system calls such as late tcp link building, transmission, and link disconnection. details as follows:

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

Take the full link system call accept for obtaining cp as an example. When the task is blocked at accept, the kernel stack is as follows:

[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 loop through the array inetsw_array have separately these three type register, look at registered gone.

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

Obviously, after registering in  inetsw  , you can go back and continue to analyze the inet_create function. The answer actually corresponds to the inet_protosw structure of TCP/UDP/RAW, performs a series of assignments, and then calls the sk_alloc function.

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 actually applies for a sock structure in the slab corresponding to prot or kmalloc directly, and then sets the protocol cluster and proto structure information. It should be noted here that what is currently returned is a struct sock structure, but in fact it is not just this simple structure. Let's return to the inet_create function to continue the downward analysis. It can be seen that after applying for sock, inet_create function uses inet = inet_sk(sk) to force conversion of sock, and then performs a series of assignments on the converted inet structure. And inet_sock is a sock representation dedicated to the INET domain, which is extended from the struct sock. In addition to the basic sock attributes, it provides attributes specific to the INET domain, such as TTL, IP address, port, etc. How does this correspond? I have to go back and study again. The final implementation of applying for sock is the following function. I mentioned it briefly before, but didn't go deep.

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

The sock application is implemented through kmalloc. The application size is specified by prot->obj_size, and prot->obj_size is specified by the proto structure in the static struct inet_protosw inetsw_array[] array member structure above, and this Proto structure distinguishes it TCP/UDP/RAW protocol, see the above array definition. Among them, tcp_prot's .obj_size = sizeof(struct tcp_sock), 
udp_prot's .obj_size = sizeof(struct udp_sock), raw_prot's .obj_size = sizeof(struct raw_sock). Look at the specific definitions of these structures:

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

Except that tcp_sock first corresponds to inet_connection_sock, udp and raw correspond to inet_sock. Let's take a look at 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;
    ......
}

The first member of inet_connection_sock is also inet_sock, and the first member of inet_sock is struct sock. This makes it clearer. It can be understood that struct sock is a base class that is the most basic protocol stack-oriented structure of Linux socket. Inet_sock is extended on the basis of inheriting sock. In addition to basic socket attributes, it provides INET domain specific Attributes, such as TTL, etc. Simply, udp_sock and raw_sock are privately extended on the basis of inet_sock. inet_connection_sock inet_sock on the basis of inheritance, all expanded some connection-oriented protocol related information, and is further expanded inet_connection_sock tcp_sock based on the increased number of TCP private attributes, such as sliding window congestion control . So far, there is no doubt about the conversion of inet = inet_sk(sk); in the above inet_create function.

Back to inet_create, in addition to the assignment of the inet_sk structure, a series of initializations were also carried out on the struct sock structure, mainly involving the sending and receiving packet queue/buffer. Finally, the init function of the specific protocol type is called. This is mainly the function corresponding to tcp_v4_init_sock, and udp does not define init. The init function of raw just clears the filter field to 0. That is, the ending part in 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);
    }
    ...
}

 

Guess you like

Origin blog.csdn.net/wangquan1992/article/details/108526898