注:本文分析基于3.10.107内核版本
1、函数原型 1 2 3 4 5 6 int socket(int domain , int type , int protocol); 参数: domain :指定通信协议族。常用的协议族有AF_INET、AF_UNIX等,对于TCP协议,该字段应为AF_INET(ipv4)或AF_INET6(ipv6)。 type :指定socket类型。常用的socket类型有SOCK_STREAM、SOCK_DGRAM等。SOCK_STREAM针对于面向连接的TCP服务应用。SOCK_DGRAM对应于无连接的UDP服务应用。 protocol:指定socket所使用的协议,一般我们平常都指定为0 ,使用type 中的默认协议。严格意义上,IPPROTO_TCP(值为6 )代表TCP协议。
2、内核实现源码分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 SYSCALL_DEFINE3(socket , int , family , int , type , int , protocol ) { int retval; struct socket *sock; int flags; ... 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: return retval; out_release: sock_release(sock ) ; return retval; }
可以看到,除去一些参数合法性校验,socket函数主要由 sock_create
和 sock_map_fd
这两个函数完成,因此我们主要分析这两个函数,看它们是怎么在内核一步步创建和管理我们使用的 socket。
2.1 sock_create函数 sock_create()
实际调用的是 __sock_create()
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 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; ... err = security_socket_create(family, type , protocol, kern); sock = sock_alloc(); sock->type = type ; ... pf = rcu_dereference(net_families[family]); ... err = pf->create(net, sock, protocol, kern); if (err < 0 ) goto out_module_put; ... err = security_socket_post_create(sock, family, type , protocol, kern); *res = sock; ... }
这里比较重要的是 sock_alloc()
和 pf->create()
这两个函数。其中 sock_alloc()
里体现了 linux 一切皆文件(Everything is a file)理念,即使用文件系统来管理 socket,这也是 VFS 所要达到的效果。
2.1.1 sock_alloc() 函数 总的来说,sock_alloc()
函数分配一个 struct socket_alloc
结构体,将 sockfs 相关属性填充在 socket_alloc 结构体的 vfs_inode 变量中,以限定后续对这个 sock 文件允许的操作。同时 sock_alloc() 最终返回 socket_alloc 结构体的 socket 变量,用于后续操作。
2.1.2 pf->create()函数 下面我们继续看下 pf->create()
这个函数。
pf 由 net_families[]
数组获得,我们先看下 net_families[]
数组的定义:
1 2 3 4 #define AF_MAX 41 #define NPROTO AF_MAX static const struct net_proto_family __rcu *net_families [NPROTO ] __read_mostly ;
net_families[]
数组的初始化在 inet_init()
函数,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 static const struct net_proto_family inet_family_ops = { .family = PF_INET, .create = inet_create, .owner = THIS_MODULE, }; static int __init inet_init(void ) { ... (void)sock_register(&inet_family_ops ) ; ... }int sock_register(const struct net_proto_family * ops ) { ... rcu_assign_pointer(net_families [ops ->family ], ops ) ; ... }
因此,net_families[]
数组里存放的是各个协议族的信息,以 family 字段作为下标。此处我们针对TCP协议分析,因此我们 family 字段是 AF_INET ,另外一般会有
所以 pf->create
调用的就是 inet_create()
函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 static int inet_create(struct net *net, struct socket *sock, int protocol, int kern) { struct sock *sk; 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; ... sock->state = SS_UNCONNECTED; lookup_protocol: err = -ESOCKTNOSUPPORT; rcu_read_lock(); list_for_each_entry_rcu(answer, &inetsw[sock->type], list ) { err = 0 ; if (protocol == answer->protocol) { if (protocol != IPPROTO_IP) break ; } else { if (IPPROTO_IP == protocol) { protocol = answer->protocol; break ; } if (IPPROTO_IP == answer->protocol) break ; } err = -EPROTONOSUPPORT; } ... sock->ops = answer->ops; answer_prot = answer->prot; ... sk = sk_alloc(net, PF_INET, GFP_KERNEL, answer_prot); if (sk == NULL ) goto out; ... sock_init_data(sock, sk); if (sk->sk_prot->init) { err = sk->sk_prot->init(sk); if (err) sk_common_release(sk); } ... } }
inet_create()
函数里有几处是比较重要的,一个是通过 inetsw[]
数组获取对应协议类型的接口操作集信息,另一个是创建 struct sock 类型的变量,最后是对创建的 sock 进行初始化。
2.1.2.1 inetsw[]数组 inetsw[]
数组存放的是各个 sock_type 的信息,它也是在 inet_init()
函数中初始化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 enum sock_type { SOCK_STREAM = 1 , SOCK_DGRAM = 2 , SOCK_RAW = 3 , SOCK_RDM = 4 , SOCK_SEQPACKET = 5 , SOCK_DCCP = 6 , SOCK_PACKET = 10 , }; #define SOCK_MAX (SOCK_PACKET + 1 ) static int __init inet_init(void ) { ... 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); ... }
其中 inetsw_array[]
存放的就是具体的每种 sock 的信息,包括操作函数,协议号等,其中 prot 和 ops 两个成员是比较重要的,后续很多操作依赖于这两个成员。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 static struct inet_protosw inetsw_array[] = { { .type = SOCK_STREAM, .protocol = IPPROTO_TCP, .prot = &tcp_prot, .ops = &inet_stream_ops, .no_check = 0, .flags = INET_PROTOSW_PERMANENT | INET_PROTOSW_ICSK, }, { .type = SOCK_DGRAM, .protocol = IPPROTO_UDP, .prot = &udp_prot, .ops = &inet_dgram_ops, .no_check = UDP_CSUM_DEFAULT, .flags = INET_PROTOSW_PERMANENT, }, { .type = SOCK_DGRAM, .protocol = IPPROTO_ICMP, .prot = &ping_prot, .ops = &inet_dgram_ops, .no_check = UDP_CSUM_DEFAULT, .flags = INET_PROTOSW_REUSE, },... };
然后通过 inet_register_protosw()
函数,将上述 inetsw_array[]
里的元素,按照 type 字段挂在 inetsw[]
数组的链表上。因为 type 字段是有重复的,所以每个 inetsw[]
元素的链表上可能会有多个成员。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 void inet_register_protosw(struct inet_protosw * p ) { struct list_head *lh; struct inet_protosw *answer; int protocol = p->protocol; struct list_head *last_perm; ... answer = NULL; last_perm = &inetsw[p ->type ] ; list _for_each(lh , &inetsw [p ->type ]) { answer = list _entry(lh , struct inet_protosw , list ) ; if (INET_PROTOSW_PERMANENT & answer->flags) { if (protocol == answer->protocol) break; last_perm = lh; } answer = NULL; } if (answer) goto out_permanent; list _add_rcu(&p ->list , last_perm ) ; ... }
2.1.2.2 创建struct sock类型变量 通过 sk_alloc()
函数创建 struct sock 类型变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 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; ... } 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 & ~__GFP_ZERO); ... }
由此可见,最后是通过 prot->slab
,也就是TCP的slab高速缓存里分配的。
关于TCP协议的slab高速缓存建立,详见 。
2.1.2.3 sock初始化 创建完 sock 结构体,那么自然要做初始化。初始化又分为两个大块,一个在 sock_init_data()
函数,另一个在 sk->sk\_prot->init()
。
a. sock_init_data()
sock_init_data()
函数我认为比较重要的一点就是将之前分配的 struct socket 和 struct sock 联系在一起,把逻辑填充完整。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 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; sk -> sk_rcvbuf = sysctl_rmem_default; sk -> sk_sndbuf = sysctl_wmem_default; sk -> sk_state = TCP_CLOSE; sk_set_socket(sk, sock); sock_set_flag(sk, SOCK_ZAPPED); if (sock) { sk ->sk_type = sock-> type; sk ->sk_wq = sock-> wq; sock -> sk = sk; } else sk -> sk_wq = NULL; 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; ... } static inline void sk_set_socket(struct sock *sk, struct socket *sock) { sk_tx_queue_clear(sk); sk -> sk_socket = sock; }
b. sk->sk_prot->init()
对于 TCP 协议,这个 init 成员指向的是 tcp_v4_init_sock()
函数,具体参看 struct proto tcp_prot
结构体的成员。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 static int tcp_v4_init_sock(struct sock * sk ) { struct inet_connection_sock *icsk = inet_csk(sk ) ; tcp_init_sock(sk ) ; icsk->icsk_af_ops = &ipv4_specific; #ifdef CONFIG_TCP_MD5SIG tcp_sk(sk ) ->af_specific = &tcp_sock_ipv4_specific; #endif return 0 ; }
tcp_v4_init_sock()
函数里调用 tcp_init_sock()
函数完成主要的初始化动作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 void tcp_init_sock(struct sock * sk ) { struct inet_connection_sock *icsk = inet_csk(sk ) ; struct tcp_sock *tp = tcp_sk(sk ) ; skb_queue_head_init(&tp ->out_of_order_queue ) ; tcp_init_xmit_timers(sk ) ; tcp_prequeue_init(tp ) ; INIT_LIST_HEAD(&tp ->tsq_node ) ; icsk->icsk_rto = TCP_TIMEOUT_INIT; ... tcp_enable_early_retrans(tp ) ; icsk->icsk_ca_ops = &tcp_init_congestion_ops; tp->tsoffset = 0 ; sk->sk_state = TCP_CLOSE; ... sk->sk_sndbuf = sysctl_tcp_wmem[1 ] ; sk->sk_rcvbuf = sysctl_tcp_rmem[1 ] ; ... }
至此,我们已经创建好了socket,也完成了相关的初始化,但是我们知道socket系统调用返回的是一个int型的文件描述符,而目前我们好像还没涉及到。
下面就进入第二部分,fd的创建。
2.2 sock_map_fd() 这个函数主要有两个部分,一个是创建 file 文件结构,fd 文件描述符,另一部分是将file 文件结构和 fd 文件描述符关联,同时将上一步返回的 socket 也一起绑定,形成一个完整的逻辑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 static int sock_map_fd(struct socket * sock , int flags ) { struct file *newfile; int fd = get_unused_fd_flags(flags ) ; if (unlikely(fd < 0 )) return fd; newfile = sock_alloc_file(sock , flags , NULL) ; if (likely(!IS_ERR(newfile ) )) { fd_install(fd , newfile ) ; return fd; } ... }
2.2.1 sock_alloc_file()函数 file 结构体的创建及初始化,以及将 socket 和 file 结构体关联的操作主要在sock_alloc_file()
函数中完成,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 struct file *sock_alloc_file(struct socket *sock, int flags, const char *dname) { struct qstr name = { .name = "" }; struct path path; struct file *file; ... path.dentry = d_alloc_pseudo(sock_mnt->mnt_sb, &name); if (unlikely(!path.dentry)) return ERR_PTR(-ENOMEM); path.mnt = mntget(sock_mnt); d_instantiate(path.dentry, SOCK_INODE(sock)); SOCK_INODE(sock)->i_fop = &socket_file_ops; file = alloc_file(&path, FMODE_READ | FMODE_WRITE, &socket_file_ops); ... sock->file = file; file->f_flags = O_RDWR | (flags & O_NONBLOCK); file->private_data = sock; return file; }
在 d_instantiate()
函数里我们可以看到 dentry 和之前创建 socket 结构体时分配的 struct socket_alloc
的联系。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void d_instantiate(struct dentry * entry , struct inode * inode ) { ... __d_instantiate(entry , inode ) ; ... } static void __d_instantiate(struct dentry * dentry , struct inode * inode ) { ... dentry->d_inode = inode; ... }
而 alloc_file()
函数则是将 file dentry inode socket 四者柔和到一起,形成sockfs的整套逻辑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 struct file *alloc_file(struct path *path , fmode_t mode, const struct file_operations *fop) { struct file *file; file = get_empty_filp(); if (IS_ERR(file)) return file; file -> f_path = *path ; file ->f_inode = path ->dentry -> d_inode; file ->f_mapping = path ->dentry ->d_inode -> i_mapping; file -> f_mode = mode; file -> f_op = fop; ... }
最后就是返回用户使用的文件描述符fd和file的关联了。
2.2.2 fd_install()函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void fd_install(unsigned int fd, struct file *file) { __fd_install(current->files, fd, file); } void __fd_install(struct files_struct *files, unsigned int fd, struct file *file) { struct fdtable *fdt; spin_lock(&files->file_lock); fdt = files_fdtable(files); BUG_ON(fdt->fd[fd] != NULL); rcu_assign_pointer(fdt->fd[fd], file); spin_unlock(&files->file_lock); }
至此,socket系统调用就结束了,将fd返回用户使用。
我们大概概述一下,socket系统调用的操作:
经过这一连串操作,用户只要使用fd,内核就能根据这个fd进行网络连接管理的各种操作。
最后上张图,描述从fd到内核sock各个结构体的关系。
Reference