目次
3.1、sock_create(__ sock_create)
3.2。ソケットノードの割り当てと初期化(sock_alloc)
3.3。指定されたプロトコルファミリに従って初期化を続行します
3.3.1、inet_create登録(inet_init)
1.ソケットの概要
ソケットを確立するために、プログラムはソケット関数を呼び出すことができます。関数はファイル記述子を返します。プロトタイプは次のとおりです。
int socket(int domain, int type, int protocol);
ドメイン:プロトコルクラスターを指定します。一般的なものはAF_IENT、AF_INET6、AF_UNIX / AF_LOCAL、AF_NETLINKなどです。
タイプ:タイプを指定します。一般的なタイプは、SOCK_STREAM、SOCK_DGRAM、SOCK_RAWなどです。
プロトコル:TCP / UDPなどの送信プロトコルを指定します。さまざまなプロトコルクラスターがデフォルトのプロトコルに対応します。0を入力すると、デフォルト値を使用できます。
ソケット機能の使い方については、「ソケットプログラミング:ソケットインターフェース作成ソケット(オリジナルソケット)の説明」を参照してください。
例としてlinuxカーネル2.6.38を取り上げ、ソケット作成カーネルの操作プロセスを簡単に分析します。完全な呼び出しスタックは次のとおりです。
sys_socketcall
sys_socket
sock_create
__sock_create
inet_create//err = pf->create(net, sock, protocol);
ユーザーモードがソケット作成を実行すると、カーネルはsys_socketcallからinet_create実行まで実行を開始し、ソケットカーネルの作成が完了してユーザーモードに戻ります。もちろん、いくつかのパラメータチェック、セキュリティチェック、およびマッピング操作も含まれます。ソケット作成関連の関数は基本的にsocket.cファイルにあります
2.ソケット作成エントリ
2.1、sys_socketcall
sys_socketcall関数では、ソケット関連関数のトランザクションは、呼び出し命令を判断することによって一律に処理されます。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関数を入力すると、ソケット作成の実際のプロセスが開始されます。ここでは主に3つのことが達成されます。
- タイプの有効性を確認してください。基本的なsock_type、SOCK_CLOEXEC、SOCK_NONBLOCK以外のパラメータが設定されている場合は、errが直接返されます。
- sock_createを呼び出して、ソケット構造を作成します。
- sock_map_fdを使用して、ソケット構造をファイル記述子にマップし、それをユーザースペースに返します。
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.ソケット作成プロセス
3.1、sock_create(__ sock_create)
ソケット構造は、ソケットの基本的なステータス、タイプ、フラグ、待機キュー、ファイルポインター、操作関数セットなどを定義します。sock構造を使用すると、ソケット操作は、ネットワークプロトコルの実際の処理に関連するトランザクションから分離されます。具体的な構造は次のとおりです。
/**
* 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に戻ってソケット作成プロセスを引き続き確認すると、sock_createは実際に__sock_createを呼び出します。インターフェイスは主に次のことを完了します。
- プロトコルファミリタイプとソケットタイプの正当性を確認します。
- 操作セキュリティ許可チェック(SElinux)、作成をトリガーするためにスコケットがユーザーモードで作成された場合、それをチェックする必要があり、カーネルモードがトリガーされてそれをスキップします。
- sock_allocを呼び出して、scoketノードを構成し、socket_fsファイルシステムから適用します。
- 着信プロトコルファミリのタイプに応じて、ソケットのどの層(ipv4、ipv6、イーサネット層)が作成されます。
- 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。ソケットノードの割り当てと初期化(sock_alloc)
sock_mntは、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を呼び出してソケットを申請した後、ソケットを申請するプロセスはすべて終了しました。引き続き__sock_create関数の分析を行い、APIから渡されたファミリ情報に基づいてどのプロトコルクラスタを決定し、プロトコルクラスタに対応する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は、ネットワークモジュールの初期化で呼び出される初期化関数です。この関数は多くのタスクを実行します。当面は、上記の2つの部分に注意を払い、残りは後で戻ります。
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関数は、カーネルによるソケット作成の中核です。コードは次のとおりです。
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,
}
};
一般的に使用される3つのタイプ(SOCK_STREAM / IPPROTO_TCP)、(SOCK_DGRAM / IPPROTO_UDP)、(SOCK_RAW / IPPROTO_IP)に対応して、ユーザーはタイプ別に指定します。TCPを例として取り上げて、関連するソケット操作インターフェイスコレクション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
配列inetsw_arrayを介したinet_initループには、これら3つのタイプのレジスタが個別にあります。
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関数の分析を続けることができます。答えは、実際には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または直接kmallocに対応するスラブ内の靴下構造に適用され、プロトコルクラスターとproto構造情報を設定します。ここで、現在返されるのはstruct sock構造ですが、実際にはこの単純な構造だけではないことに注意してください。inet_create関数に戻って、下方分析を続けましょう。sockを申請した後、inet_create関数はinet = inet_sk(sk)を使用してsockの変換を強制し、変換されたinet構造に対して一連の割り当てを実行することがわかります。また、inet_sockは、struct sockから拡張されたINETドメイン専用のsock表現であり、基本的なsock属性に加えて、TTL、IPアドレス、ポートなど、INETドメインに固有の属性を提供します。これはどのように対応しますか?もう一度勉強しなければなりません。靴下を申請する最終的な実装は次の機能です。前に簡単に触れましたが、深くは行きませんでした。
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は上記の静的構造inet_protoswinetsw_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の最初のメンバーはstructsockです。struct sockは、Linuxソケットの最も基本的なプロトコルスタック指向構造である基本クラスであることが理解できます。Inet_sockは、sockの継承に基づいて拡張されます。基本的なソケット属性に加えて、INETドメイン固有を提供します。 TTLなどの属性。単純に、udp_sockとraw_sockはinet_sockに基づいてプライベートに拡張されます。inet_connection_sock inet_sockは継承に基づいており、すべて接続指向のプロトコル関連情報を拡張し、スライドウィンドウの輻輳制御などのTCPプライベート属性の数の増加に基づいてinet_connection_socktcp_sockをさらに拡張しています。これまでのところ、上記のinet_create関数でのinet = inet_sk(sk);の変換については疑いの余地がありません。
inet_createに戻ると、inet_sk構造の割り当てに加えて、主に送信および受信パケットキュー/バッファを含む一連の初期化もstructsock構造で実行されます。最後に、特定のプロトコルタイプのinit関数が呼び出されます。これは主にtcp_v4_init_sockに対応する関数であり、udpはinitを定義しません。rawのinit関数は、フィルターフィールドを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);
}
...
}