《Linux/UNIX系统编程手册》第56章 SOCKET:介绍
关键词:
socket是一种IPC方法,它允许位于同一主机或使用网络连接起来的不同主机上的程序之间交换数据。
关于Socket及后续章节介绍Socket的用法:
- 《Linux/UNIX系统编程手册》第56章 SOCKET:介绍 :本章将对socket API进行一个全面地介绍:socket()/bind()/listen()/accept()/connect()/close()/sendto()/recvfrom(),以及通用数据结构struct sockaddr等。
- 《Linux/UNIX系统编程手册》第57章 SOCKET:UNIX DOMAIN:将介绍UNIX domain socket,它允许位于统一主机系统上的应用程序之间通信。
- 《Linux/UNIX系统编程手册》第58章 SOCKET:TCP/IP网络基础:将介绍各种计算机联网概念并描述TCP/IP联网协议的关键特性。
- 《Linux/UNIX系统编程手册》第59章 SOCKET:Internet Domain:将描述Internet domain socket,它允许位于不同主机上的应用程序之间通过一个TCP/IP网络进行通信。
- 《Linux/UNIX系统编程手册》第60章 SOCKET:服务器设计:将讨论使用socket的服务设计。
- 《Linux/UNIX系统编程手册》第61章 SOCKET:高级主题 :将介绍一些高级主题,包括socket I/O的其他特性、TCP协议的细节信息以及如何使用socket选项来获取和修改socket的各种特性。
1. socket基础
一个典型的客户端/服务器场景中,应用程序使用socket进行通信的方式如下:
- 各个应用程序创建一个socket。socket是一个允许通信的设备,两个应用程序都需要用到它。
- 服务器将自己的socket绑定到一个众所周知的地址上是的客户端能够定位到它的位置。
关键socket API包括以下下几种:
- socket()创建一个新的socket。
- bind()将一个socket绑定到一个地址上。通常服务器需要使用这个调用来将其socket绑定到一个众所周知的地址上使得客户端能够定位到该socket上。
- listen()允许一个流socket接受来自其他socket的接入连接。
- accept()在一个监听流上接受来自一个对等应用程序的连接,并可选地返回对等socket的地址。
- connect()建立与另一个socket之间的连接。
- read()/write()/close()基于socket的读写和关闭。
- recv()/send()/recvfrom()/sendto()分别通过socket发送或接收数据,类似read()/write()但是功能更丰富。
1.1 socket API介绍
1.1.1 socket domain
socket存在于一个通信domain中:识别出一个socket的方法(socket地址格式);通信范围(是统一主机不同应用之间;还是一个网络连接的不同主机上应用之间)。
domain都是以AF_开头,表示Address Family;PF_开头的表示Protocol Family。
在socket.h中定义如下,可以看出AF_和PF_基本一对一。
/* Protocol families. */ #define PF_UNSPEC 0 /* Unspecified. */ #define PF_LOCAL 1 /* Local to host (pipes and file-domain). */ #define PF_UNIX PF_LOCAL /* POSIX name for PF_LOCAL. */ #define PF_FILE PF_LOCAL /* Another non-standard name for PF_LOCAL. */ #define PF_INET 2 /* IP protocol family. */ #define PF_AX25 3 /* Amateur Radio AX.25. */ #define PF_IPX 4 /* Novell Internet Protocol. */ #define PF_APPLETALK 5 /* Appletalk DDP. */ #define PF_NETROM 6 /* Amateur radio NetROM. */ #define PF_BRIDGE 7 /* Multiprotocol bridge. */ #define PF_ATMPVC 8 /* ATM PVCs. */ #define PF_X25 9 /* Reserved for X.25 project. */ #define PF_INET6 10 /* IP version 6. */ ... #define PF_MAX 44 /* For now.. */ /* Address families. */ #define AF_UNSPEC PF_UNSPEC #define AF_LOCAL PF_LOCAL #define AF_UNIX PF_UNIX #define AF_FILE PF_FILE #define AF_INET PF_INET #define AF_AX25 PF_AX25 #define AF_IPX PF_IPX #define AF_APPLETALK PF_APPLETALK #define AF_NETROM PF_NETROM #define AF_BRIDGE PF_BRIDGE #define AF_ATMPVC PF_ATMPVC #define AF_X25 PF_X25 #define AF_INET6 PF_INET6 ... #define AF_MAX PF_MAX
常用的AF_有AF_UNIX、AF_INET、AF_INET6三种。
- AF_UNIX domain允许在同一主机上的应用程序之间进行通信。
- AF_INET domain允许在使用IPv4网络连接起来的主机上的应用程序之间进行通信。
- AF_INET6 domain允许在使用IPv6网络连接起来的主机上的应用程序之间进行通信。
1.1.2 socket type
每个socket实现都至少提供了两种socket:流和数据报。
/* Types of sockets. */ enum __socket_type { SOCK_STREAM = 1, /* Sequenced, reliable, connection-based byte streams. */ SOCK_DGRAM = 2, /* Connectionless, unreliable datagrams of fixed maximum length. */ SOCK_RAW = 3, /* Raw protocol interface. */ SOCK_RDM = 4, /* Reliably-delivered messages. */ SOCK_SEQPACKET = 5, /* Sequenced, reliable, connection-based, datagrams of fixed maximum length. */ SOCK_DCCP = 6, /* Datagram Congestion Control Protocol. */ SOCK_PACKET = 10, /* Linux specific way of getting packets at the dev level. For writing rarp and other similar things on the user level. */ /* Flags to be ORed into the type parameter of socket and socketpair and used for the flags parameter of paccept. */ SOCK_CLOEXEC = 02000000, /* Atomically set close-on-exec flag for the new descriptor(s). */ SOCK_NONBLOCK = 00004000 /* Atomically mark descriptor(s) as non-blocking. */ };
流socket(SOCK_STREAM)提供了一个可靠的双向的字节流通信信道。
流socket的正常工作需要一对相互连接的socket,因此流socket通常被称为面向连接的。
数据报socket(SOCK_DGRAM)允许数据以被称为数据报的消息的形式进行交换。
数据报socket是更一般的无连接socket概念,一个数据报socket在使用时无需与另一个socket连接。
在Internet domain中,数据报socket使用UDP,流socke通则使用TCP。
1.1.3 struct sockaddr
各种socket domain使用了不同的地址格式,对于各种socket domain都需要定义一个不同的结构类型来存储socket地址。
然而由于bind()调用适用于所有socket domain,因此他们必须要能够接受任意类型的地址结构。
为此,socket API定义了一个通用的地址结构struct sockaddr。
这个类型的唯一用途是将各种domain特定的地址结构转换成单个类型以供socket各个参数使用。
/* Structure describing a generic socket address. */ struct sockaddr { __SOCKADDR_COMMON (sa_); /* Common data: address family and length. */ char sa_data[14]; /* Address data. */ };
这个结构是所有domain特定的地址结构的模板,其中每个地址结构均以与sockadr结构中的sa_family打头。
通过sa_family字段值足以确定存储在这个结构的剩余部分中地址大小和格式了。
1.1.4 socket():创建一个socket
#include <sys/socket.h> int socket(int domain, int type, int protocol); Returns file descriptor on success, or –1 on error
domain指定了socket通信domain,常用的有AF_UNIX、AF_INET、AF_INET6;type指定了socket类型,常用的有SOCK_STREAM、SOCK_DGRAM;protocol一般被指为0。
socket()在成功时会返回一个引用在后续调用中会用到的新创建的socket文件描述符;错误则返回-1。
1.1.5 bind():将socket绑定到地址
#include <sys/socket.h> int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); Returns 0 on success, or –1 on error
sockfd是由socket()返回的文件描述符。
addr是指向指定socket绑定到的地址的结构体指针,传输参数的类型取决于socket domain。
addrlen参数指定了地址结构的大小。
其中addr传入到内核,最终被不同socket domain的proto_ops->bind()调用的时候,会被强制转换成不同数据结构。
比如AF_UNIX、AF_INET、AF_INET6对应的地址结构体分别为struct sockaddr_un、struct sockaddr_in、struct sockaddr_in6:
#define UNIX_PATH_MAX 108 struct sockaddr_un { __kernel_sa_family_t sun_family; /* AF_UNIX */---------------------填入AF_UNIX。 char sun_path[UNIX_PATH_MAX]; /* pathname */--------------------本地socket的路径。 }; #define __SOCK_SIZE__ 16 /* sizeof(struct sockaddr) */ struct sockaddr_in { __kernel_sa_family_t sin_family; /* Address family */---填入AF_INET。 __be16 sin_port; /* Port number */--------------端口号。 struct in_addr sin_addr; /* Internet address */---------IPv4的地址。 /* Pad to size of `struct sockaddr'. */ unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) - sizeof(unsigned short int) - sizeof(struct in_addr)]; }; #define sin_zero __pad /* for BSD UNIX comp. -FvK */ struct sockaddr_in6 { unsigned short int sin6_family; /* AF_INET6 */---------------填入AF_INET6。 __be16 sin6_port; /* Transport layer port # */ __be32 sin6_flowinfo; /* IPv6 flow information */ struct in6_addr sin6_addr; /* IPv6 address */ __u32 sin6_scope_id; /* scope id (new in RFC2553) */ };
1.1.6 listen():监听接入连接
#include <sys/socket.h> int listen(int sockfd, int backlog); Returns 0 on success, or –1 on error
listen()将sockfd引用的流socket标记为被动,这个socket后面会被用来接受来自其他socket连接。
如果客户端在服务器调用accept()之前调用connect(),这将会产生一个未决的连接。
内核必须记录所有未决的连接请求,在后续accept()就能够处理这些请求。
backlog参数允许限制这种未决连接数量。在这个限制内的连接请求会立即成功。之外的连接请求就会阻塞直到一个未决连接被接受,并从未决队列中删除为止。
1.1.7 accept():接收连接
#include <sys/socket.h> int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); Returns file descriptor on success, or –1 on error
accept()用在sockfd引用的监听流socket上接受一个接入连接。
如果在accept()时不存在未决的连接,那么调用就会阻塞直到有连接请求到达为止。
理解accept()的关键点是它会创建一个新socket,并且这是这个新socket会与执行connect()的对等socket进行连接。
accept()返回结果是已经连接的socket文件描述符,其会保持打开状态,并且可以被用来接受后续的连接。
addr参数指向了一个用来返回socket地址的结构。
addrlen在调用之前必须要将其初始化为addr指向的缓冲区大小;返回之后被设置成实际被复制进缓冲区中的数据的字节数。
1.1.8 connect():(客户端)连接到(服务器)对等socket
#include <sys/socket.h> int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); Returns 0 on success, or –1 on error
connect()将sockfd引用的socket连接到地址通过addr和addrlen指定的监听socket上。
其中addr和addrlen参数指定方式与bind()对应参数指定方式相同。
1.1.9 sendto()/recvfrom():交换数据报
#include <sys/socket.h> ssize_t recvfrom(int sockfd, void *buffer, size_t length, int flags,struct sockaddr *src_addr, socklen_t *addrlen); Returns number of bytes received, 0 on EOF, or –1 on error ssize_t sendto(int sockfd, const void *buffer, size_t length, int flags,const struct sockaddr *dest_addr, socklen_t addrlen); Returns number of bytes sent, or –1 on error
flags是一个位掩码,控制着特定的IO特性。
src_addr和addrlen用来获取或指定与之同行的对等socket地址。
对于recvfrom()来说,src_addr和addlen会返回用来发送数据报的远程socket地址。如果不关心,可以将src_addr和addrlen都指定为NULL。
对于sendto()来说,dest_addr和addrlen制定了数据报发送到的socket地址。
1.1.10 read()/write()/close():读、写、关闭socket文件
如果作为客户端,socket()返回的sockfd,在connect之后就就可以对sockfd进行read()/write()/close()操作。
如果作为服务端,在accept()之后产生新的sockfd,之后sockfd就保持打开状态。可以对其进行read()/write()/close()操作。
1.2 流socket
1.3 数据报socket
2. 从socket API到系统调用socketcall()
上面介绍了一系列socket相关API,但是这些C函数并没有对应的系统调用。
下面就看看这些scoket API是如何转到内核调用的,以socket()为例。
int __socket (int fd, int type, int domain) { #ifdef __ASSUME_SOCKET_SYSCALL return INLINE_SYSCALL (socket, 3, fd, type, domain); #else return SOCKETCALL (socket, fd, type, domain);----------根据SOCKETCALL()定义可知,socket通过连接符号便变成其对应的call,作为socketcall()的第一个参数。 #endif } libc_hidden_def (__socket)-------------------------------对libc之外屏蔽__socket()函数访问。 weak_alias (__socket, socket)----------------------------如果没有定义socket()函数,那么对socket()的调用将会转到调用__socket()。
2.1 SOCKETCALL():
从下面SOCKETCALL()宏定义可知,最终是通过socketcall()系统调用实现的。
具体对应connect()对应的是SOCKOP_socket,即socketcall()系统调用的第一个参数为1。
在socketcall()系统调用中,根据第一个参数执行对应的操作。
所以下面SOCKOP_对应的socket API都是通过socketcall()实现的,然后在socketcall()里面进行处理。
#define SOCKOP_invalid -1 #define SOCKOP_socket 1 #define SOCKOP_bind 2 #define SOCKOP_connect 3 #define SOCKOP_listen 4 #define SOCKOP_accept 5 #define SOCKOP_getsockname 6 #define SOCKOP_getpeername 7 #define SOCKOP_socketpair 8 #define SOCKOP_send 9 #define SOCKOP_recv 10 #define SOCKOP_sendto 11 #define SOCKOP_recvfrom 12 #define SOCKOP_shutdown 13 #define SOCKOP_setsockopt 14 #define SOCKOP_getsockopt 15 #define SOCKOP_sendmsg 16 #define SOCKOP_recvmsg 17 #define SOCKOP_accept4 18 #define SOCKOP_recvmmsg 19 #define SOCKOP_sendmmsg 20 #define __SOCKETCALL1(name, a1) \ INLINE_SYSCALL (socketcall, 2, name, \ ((long int [1]) { (long int) (a1) })) ... #define __SOCKETCALL6(name, a1, a2, a3, a4, a5, a6) \ INLINE_SYSCALL (socketcall, 2, name, \ ((long int [6]) { (long int) (a1), (long int) (a2), (long int) (a3), \ (long int) (a4), (long int) (a5), (long int) (a6) })) #define SOCKETCALL(name, args...) \ ({ \ long int sc_ret = __SOCKETCALL (SOCKOP_##name, args); \ sc_ret; \ })
2.2 weak_alias()
weak_alias()是一个宏,其目的是为函数添加一个“弱”别名,与“强”符号进行区分。
如果调用函数对应的函数无“强”符号对应的函数,则会调用该别名对应的函数。所谓“强”符号的函数名就是普通声明定义的函数对应的函数名。
这里如果没有定义connect()函数,调用connect()实际就会转到__socket()。
# define weak_alias(name, aliasname) _weak_alias (name, aliasname) # define _weak_alias(name, aliasname) \ extern __typeof (name) aliasname __attribute__ ((weak, alias (#name)));
2.3 libc_hidden_def()
libc_hidden_def()的定义在libc-symbols.h中。
# define libc_hidden_def(name) hidden_def (name)
3. socketcall及socket系统调用分析
可以说socketcall()是所有socket调用的入口,socketcall()根据call的值switch-case到对应的函数中。这些函数和单独系统调用基本一致。
下面就先来分析一下socketcall()函数。
#define SYS_SOCKET 1 /* sys_socket(2) */ #define SYS_BIND 2 /* sys_bind(2) */ #define SYS_CONNECT 3 /* sys_connect(2) */ ... #define SYS_SENDMMSG 20 /* sys_sendmmsg(2) */ /* Argument list sizes for compat_sys_socketcall */ #define AL(x) ((x) * sizeof(u32)) static unsigned char nas[21] = {----------------------------------------nas[]将call id作为下标,得到对应系统调用参数的总大小。这里是上面call id和系统调用的一座桥梁。 AL(0), AL(3), AL(3), AL(3), AL(2), AL(3), AL(3), AL(3), AL(4), AL(4), AL(4), AL(6), AL(6), AL(2), AL(5), AL(5), AL(3), AL(3), AL(4), AL(5), AL(4) }; COMPAT_SYSCALL_DEFINE2(socketcall, int, call, u32 __user *, args) { u32 a[AUDITSC_ARGS]; unsigned int len; u32 a0, a1; int ret; if (call < SYS_SOCKET || call > SYS_SENDMMSG)-----------------------判断call范围,从SYS_SOCKET到SYS_SOCKET。 return -EINVAL; len = nas[call];----------------------------------------------------根据call id获取args大小。 if (len > sizeof(a)) return -EINVAL; if (copy_from_user(a, args, len)) return -EFAULT; ret = audit_socketcall_compat(len / sizeof(a[0]), a);---------------未定义CONFIG_AUDITSYSCALL直接返回0。 if (ret) return ret; a0 = a[0]; a1 = a[1]; switch (call) { case SYS_SOCKET: ret = sys_socket(a0, a1, a[2]); break; case SYS_BIND: ret = sys_bind(a0, compat_ptr(a1), a[2]); break; case SYS_CONNECT: ret = sys_connect(a0, compat_ptr(a1), a[2]); break; ... default: ret = -EINVAL; break; } return ret; }
结合socketcall()系统调用的实现和API单独系统调用,可以看出两者的实现是一致的。
3.1 sys_socket()
struct socket在内核中表示一个socket,struct sock在网络层表示一个socket。
struct socket { socket_state state;-----------------------------表示当前socket的状态。 kmemcheck_bitfield_begin(type); short type;---------------------------------对应enum socket_type,等于sys_socket()传入的type参数。 kmemcheck_bitfield_end(type); unsigned long flags; struct socket_wq __rcu *wq; struct file *file; struct sock *sk; const struct proto_ops *ops;------------------------是family和type两者综合的操作函数集。 };
sys_socket()创建一个struct socket,根据family从net_families[]找到对应协议族;然后在从type找到具体使用哪种类型struct proto_ops。
SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol) { int retval; struct socket *sock; int flags; ... flags = type & ~SOCK_TYPE_MASK; if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK)) return -EINVAL; type &= SOCK_TYPE_MASK;---------------------------------------------通过SOCK_TYPE_MASK将传入的type分开,一部分是flags,另一部分是0~15之间的socket type。 if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK)) flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK; retval = sock_create(family, type, protocol, &sock);----------------根据family/type/protocol创建一个struct socket。 if (retval < 0) goto out; retval = sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));-------为新创建的struct socket分配一个文件描述符。 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; } int sock_create(int family, int type, int protocol, struct socket **res) { return __sock_create(current->nsproxy->net_ns, family, type, protocol, res, 0); } 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 == PF_INET && type == SOCK_PACKET) { pr_info_once("%s uses obsolete (PF_INET,SOCK_PACKET)\n", current->comm); family = PF_PACKET; } err = security_socket_create(family, type, protocol, kern); if (err) return err; sock = sock_alloc();--------------------------------------------------创建一个struct inode和struct socket,并将两者绑定起来。 if (!sock) { net_warn_ratelimited("socket: no more sockets\n"); return -ENFILE; /* Not exactly a match, but its the closest posix thing */ } sock->type = type;----------------------------------------------------设置socket类型。 #ifdef CONFIG_MODULES if (rcu_access_pointer(net_families[family]) == NULL) request_module("net-pf-%d", family); #endif rcu_read_lock(); pf = rcu_dereference(net_families[family]);---------------------------net_families使用下标对应AF_XXX,通过net_families[family]可以获得对应struct net_proto_family。 err = -EAFNOSUPPORT; if (!pf) goto out_release; if (!try_module_get(pf->owner)) goto out_release; rcu_read_unlock(); err = pf->create(net, sock, protocol, kern);--------------------------调用具体AF_XX对应的create成员,比如AF_UNIX对应unix_create()。 if (err < 0) goto out_module_put; if (!try_module_get(sock->ops->owner)) goto out_module_busy; module_put(pf->owner); err = security_socket_post_create(sock, family, type, protocol, kern); if (err) goto out_sock_release; *res = sock; return 0; ... }
net_families[]保存了所有AF_XXX对应的struct net_proto_family。
这些struct net_proto_family通过sock_register()注册,通过sock_unregister()去注册。
static const struct net_proto_family __rcu *net_families[NPROTO] __read_mostly; int sock_register(const struct net_proto_family *ops) { int err; ... spin_lock(&net_family_lock); if (rcu_dereference_protected(net_families[ops->family], lockdep_is_held(&net_family_lock))) err = -EEXIST; else { rcu_assign_pointer(net_families[ops->family], ops);---------------主要就是讲struct net_proto_family赋给net_families[]。 err = 0; } spin_unlock(&net_family_lock); return err; } void sock_unregister(int family) { BUG_ON(family < 0 || family >= NPROTO); spin_lock(&net_family_lock); RCU_INIT_POINTER(net_families[family], NULL); spin_unlock(&net_family_lock); synchronize_rcu(); }
下面以AF_UNIX为例,看看不同type的处理。
static int __init af_unix_init(void) { ... sock_register(&unix_family_ops); ... } static const struct net_proto_family unix_family_ops = { .family = PF_UNIX, .create = unix_create, .owner = THIS_MODULE, }; static int unix_create(struct net *net, struct socket *sock, int protocol, int kern) { if (protocol && protocol != PF_UNIX) return -EPROTONOSUPPORT; sock->state = SS_UNCONNECTED; switch (sock->type) {-------------------------------------------------可以看出AF_UNIX仅支持SOCK_STREAM、SOCK_STREAM、SOCK_STREAM、SOCK_STREAM几种形式type。 case SOCK_STREAM: sock->ops = &unix_stream_ops; break; case SOCK_RAW: sock->type = SOCK_DGRAM; case SOCK_DGRAM: sock->ops = &unix_dgram_ops; break; case SOCK_SEQPACKET: sock->ops = &unix_seqpacket_ops; break; default: return -ESOCKTNOSUPPORT; } return unix_create1(net, sock, kern) ? 0 : -ENOMEM;--------------------创建并初始化struct sock。 } static const struct proto_ops unix_stream_ops = { .family = PF_UNIX, .owner = THIS_MODULE, .release = unix_release, ... .set_peek_off = unix_set_peek_off, }; static const struct proto_ops unix_dgram_ops = { .family = PF_UNIX, .owner = THIS_MODULE, .release = unix_release, ... .set_peek_off = unix_set_peek_off, }; static const struct proto_ops unix_seqpacket_ops = { .family = PF_UNIX, .owner = THIS_MODULE, .release = unix_release, ... .set_peek_off = unix_set_peek_off, };
unix_create1()函数分配并且初始化struct sock,然后作为struct socket的成员sk。
static struct sock *unix_create1(struct net *net, struct socket *sock, int kern) { struct sock *sk = NULL; struct unix_sock *u; atomic_long_inc(&unix_nr_socks); if (atomic_long_read(&unix_nr_socks) > 2 * get_max_files()) goto out; sk = sk_alloc(net, PF_UNIX, GFP_KERNEL, &unix_proto, kern); if (!sk) goto out; sock_init_data(sock, sk); lockdep_set_class(&sk->sk_receive_queue.lock, &af_unix_sk_receive_queue_lock_key); sk->sk_allocation = GFP_KERNEL_ACCOUNT; sk->sk_write_space = unix_write_space; sk->sk_max_ack_backlog = net->unx.sysctl_max_dgram_qlen; sk->sk_destruct = unix_sock_destructor; u = unix_sk(sk); u->path.dentry = NULL; u->path.mnt = NULL; spin_lock_init(&u->lock); atomic_long_set(&u->inflight, 0); INIT_LIST_HEAD(&u->link); mutex_init(&u->iolock); /* single task reading lock */ mutex_init(&u->bindlock); /* single task binding lock */ init_waitqueue_head(&u->peer_wait); init_waitqueue_func_entry(&u->peer_wake, unix_dgram_peer_wake_relay); unix_insert_socket(unix_sockets_unbound(sk), sk); out: if (sk == NULL) atomic_long_dec(&unix_nr_socks); else { local_bh_disable(); sock_prot_inuse_add(sock_net(sk), sk->sk_prot, 1); local_bh_enable(); } return sk; }
综上所述,sys_socket() 主要完善内核中struct socket结构体,尤其是struct proto_ops结构体。然后返回对应文件描述符给用户空间。
后续关于socket的API都是通过文件描述符找到内核中对应的struct socket,然后调用struct proto_ops中成员来完成工作。
3.2 sys_bind()
sys_bind()通过入参fd找到内核中表示socket对应的struct socket。
然后调用struct socket->ops->bind()进行umyaddr和fd绑定。在AF_UNIX和SOCK_STREAM情况下,调用unix_bind()。
SYSCALL_DEFINE3(bind, int, fd, struct sockaddr __user *, umyaddr, int, addrlen) { struct socket *sock; struct sockaddr_storage address; int err, fput_needed; sock = sockfd_lookup_light(fd, &err, &fput_needed);-------------------------根据fd找到对应的struct socket结构体。以fs为索引从当前进程的文件描述符表files_struct中找到对应的file实例,然后从file实例中的private_data成员中获取socket实例。 if (sock) { err = move_addr_to_kernel(umyaddr, addrlen, &address); if (err >= 0) { err = security_socket_bind(sock, (struct sockaddr *)&address, addrlen); if (!err) err = sock->ops->bind(sock, (struct sockaddr *) &address, addrlen);-------------------------------调用struct socket->ops->bind()完成地址与socket的绑定,进而和fd绑定。 } fput_light(sock->file, fput_needed); } return err; } static struct socket *sockfd_lookup_light(int fd, int *err, int *fput_needed) { struct fd f = fdget(fd); struct socket *sock; *err = -EBADF; if (f.file) { sock = sock_from_file(f.file, err); if (likely(sock)) { *fput_needed = f.flags; return sock; } fdput(f); } return NULL; }
3.3 sys_listen()
类似sys_bind(),sys_listen()也是同样的通过fd找到struct socket,然后调用struct socket->ops->listen()完成主要工作。
在AF_UNIX和SOCK_STREAM情况下,调用unix_listen()。
SYSCALL_DEFINE2(listen, int, fd, int, backlog) { struct socket *sock; int err, fput_needed; int somaxconn; sock = sockfd_lookup_light(fd, &err, &fput_needed); if (sock) { somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn; if ((unsigned int)backlog > somaxconn) backlog = somaxconn; err = security_socket_listen(sock, backlog); if (!err) err = sock->ops->listen(sock, backlog); fput_light(sock->file, fput_needed); } return err; }
3.4 sys_connect()
服务器端socket使用bind()来绑定IP和端口,客户端使用connect()让系统自动选择IP和端口。
核心也是调用struct socket->ops->connect()。
SYSCALL_DEFINE3(connect, int, fd, struct sockaddr __user *, uservaddr, int, addrlen) { struct socket *sock; struct sockaddr_storage address; int err, fput_needed; sock = sockfd_lookup_light(fd, &err, &fput_needed);----------------------------------通过文件描述符fd找到对应的socket实例。 if (!sock) goto out; err = move_addr_to_kernel(uservaddr, addrlen, &address); if (err < 0) goto out_put; err = security_socket_connect(sock, (struct sockaddr *)&address, addrlen);--------------将socket地址从用户空间拷贝到内核。 if (err) goto out_put; err = sock->ops->connect(sock, (struct sockaddr *)&address, addrlen,-----------------调用connect()成员函数,对于AF_UNIX和SOCK_STREAM即调用unix_stream_connect()。 sock->file->f_flags); out_put: fput_light(sock->file, fput_needed); out: return err; }
3.5 sys_accept()/sys_accept4()
sys_accept()作为accept()在内核中的实现,返回一个新的句柄,建立新的操作上下文。
SYSCALL_DEFINE4(accept4, int, fd, struct sockaddr __user *, upeer_sockaddr, int __user *, upeer_addrlen, int, flags) { struct socket *sock, *newsock; struct file *newfile; int err, len, newfd, fput_needed; struct sockaddr_storage address; if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))--------------------------------------不允许使用这两个flags。 return -EINVAL; if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK)) flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK; sock = sockfd_lookup_light(fd, &err, &fput_needed);--------------------------------根据fd找到struct socket。 if (!sock) goto out; err = -ENFILE; newsock = sock_alloc();------------------------------------------------------------创建一个新的struct socket。 if (!newsock) goto out_put; newsock->type = sock->type;--------------------------------------------------------新的socket类型和socket层操作。 newsock->ops = sock->ops; /* * We don't need try_module_get here, as the listening socket (sock) * has the protocol module (sock->ops->owner) held. */ __module_get(newsock->ops->owner); newfd = get_unused_fd_flags(flags);-------------------------------------------------分配一个空闲的文件句柄。 if (unlikely(newfd < 0)) { err = newfd; sock_release(newsock); goto out_put; } newfile = sock_alloc_file(newsock, flags, sock->sk->sk_prot_creator->name);---------为新创建的struct socket分配一个文件描述符。 if (IS_ERR(newfile)) { err = PTR_ERR(newfile); put_unused_fd(newfd); sock_release(newsock); goto out_put; } err = security_socket_accept(sock, newsock); if (err) goto out_fd; err = sock->ops->accept(sock, newsock, sock->file->f_flags);-------------------------对于AF_UNIX和SOCK_STREAM则是调用unix_accept()。 if (err < 0) goto out_fd; if (upeer_sockaddr) {----------------------------------------------------------------如果accept需要返回对端socket地址,调用newsock->ops->getname()获取struct sockaddr并返还给用户空间upeer_sockaddr。 if (newsock->ops->getname(newsock, (struct sockaddr *)&address, &len, 2) < 0) { err = -ECONNABORTED; goto out_fd; } err = move_addr_to_user(&address, len, upeer_sockaddr, upeer_addrlen); if (err < 0) goto out_fd; } /* File flags are not inherited via accept() unlike another OSes. */ fd_install(newfd, newfile);---------------------------------------------------------以newfd为索引,把newfile加入当前进程的文件描述符标files_struct中。 err = newfd; out_put: fput_light(sock->file, fput_needed); out: return err; out_fd: fput(newfile); put_unused_fd(newfd); goto out_put; } SYSCALL_DEFINE3(accept, int, fd, struct sockaddr __user *, upeer_sockaddr, int __user *, upeer_addrlen) { return sys_accept4(fd, upeer_sockaddr, upeer_addrlen, 0); }
所以sys_accept ()主要作用就是:创建新的socket和inode并初始化完成;调用原socket->ops->accept();保存新创建socket的地址到用户空间。
3.6 其他API
sys_getsockname()/sys_getpeername()调用相应proto_ops->getname()。
sys_send()/sys_sendto()/sys_sendmsg()/sys_sendmmsg()最终都是通过___sys_sendmsg()实现。
sys_recv()/sys_recvfrom()/sys_recvmsg()/sys_recvmmsg()最终都是通过___sys_recvmsg实现。
sys_setsockopt()/sys_getsockopt()分别调用proto_ops->setsockopt()和proto_ops->getsockopt()。
sys_socketpair()调用proto_ops->socketpair(),sys_shutdown()调用proto_ops->shutdown()。
4. 简单基于Socket的Server/Client通信
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
2017-07-11 Linux内核编程、调试技巧小集