关键词:

socket是一种IPC方法,它允许位于同一主机或使用网络连接起来的不同主机上的程序之间交换数据。

关于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 = , /* Sequenced, reliable, connection-based
byte streams. */
SOCK_DGRAM = , /* Connectionless, unreliable datagrams
of fixed maximum length. */
SOCK_RAW = , /* Raw protocol interface. */
SOCK_RDM = , /* Reliably-delivered messages. */
SOCK_SEQPACKET = , /* Sequenced, reliable, connection-based,
datagrams of fixed maximum length. */
SOCK_DCCP = , /* Datagram Congestion Control Protocol. */
SOCK_PACKET = , /* 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 = , /* Atomically set close-on-exec flag for the
new descriptor(s). */
SOCK_NONBLOCK = /* 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[]; /* 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 – 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 on success, or – 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 on success, or – 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 – 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 on success, or – 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, on EOF, or – 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 – 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, , fd, type, domain);
#else
returnSOCKETCALL (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, , name, \
((long int []) { (long int) (a1) }))
...
#define __SOCKETCALL6(name, a1, a2, a3, a4, a5, a6) \
INLINE_SYSCALL (socketcall, , name, \
((long int []) { (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[] = {----------------------------------------nas[]将call id作为下标,得到对应系统调用参数的总大小。这里是上面call id和系统调用的一座桥梁。
AL(), AL(), AL(), AL(), AL(), AL(),
AL(), AL(), AL(), AL(), AL(), AL(),
AL(), AL(), AL(), AL(), AL(), AL(),
AL(), AL(), AL()
}; 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[]), a);---------------未定义CONFIG_AUDITSYSCALL直接返回0。
if (ret)
return ret; a0 = a[];
a1 = a[]; switch (call) {
case SYS_SOCKET:
ret = sys_socket(a0, a1, a[]);
break;
case SYS_BIND:
ret = sys_bind(a0, compat_ptr(a1), a[]);
break;
case SYS_CONNECT:
ret = sys_connect(a0, compat_ptr(a1), a[]);
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 < )
goto out; retval = sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));-------为新创建的struct socket分配一个文件描述符。
if (retval < )
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, );
} 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 < )
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 ;
...
}

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 = ;
}
spin_unlock(&net_family_lock);
return err;
} void sock_unregister(int family)
{
BUG_ON(family < || 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) ? : -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) > * 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, );
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, );
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 >= ) {
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 < )
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 < )) {
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 < )
goto out_fd; if (upeer_sockaddr) {----------------------------------------------------------------如果accept需要返回对端socket地址,调用newsock->ops->getname()获取struct sockaddr并返还给用户空间upeer_sockaddr。
if (newsock->ops->getname(newsock, (struct sockaddr *)&address,
&len, ) < ) {
err = -ECONNABORTED;
goto out_fd;
}
err = move_addr_to_user(&address,
len, upeer_sockaddr, upeer_addrlen);
if (err < )
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, );
}

所以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通信

《Linux/UNIX系统编程手册》第56章 SOCKET:介绍的更多相关文章

  1. Linux/Unix系统编程手册 第三章:系统编程概念

    本章介绍系统编程的基础概念和一些后续章节用到的函数及头文件,并说明了可移植性问题. 系统调用是受控的内核入口,通过系统调用,进程可以请求内核以自己的名义去执行某些动作,比如创建子进程,执行I/O操作, ...

  2. 《Linux/UNIX系统编程手册》第63章 IO多路复用、信号驱动IO以及epoll

    关键词:fasync_helper.kill_async.sigsuspend.sigaction.fcntl.F_SETOWN_EX.F_SETSIG.select().poll().poll_wa ...

  3. 《Linux/Unix系统编程手册》读书笔记5

    <Linux/Unix系统编程手册>读书笔记 目录 第8章 本章讲了用户和组,还有记录用户的密码文件/etc/passwd,shadow密码文件/etc/shadow还有组文件/etc/g ...

  4. 《Linux/Unix系统编程手册》读书笔记8 (文件I/O缓冲)

    <Linux/Unix系统编程手册>读书笔记 目录 第13章 这章主要将了关于文件I/O的缓冲. 系统I/O调用(即内核)和C语言标准库I/O函数(即stdio函数)在对磁盘进行操作的时候 ...

  5. 《Linux/Unix系统编程手册》读书笔记7 (/proc文件的简介和运用)

    <Linux/Unix系统编程手册>读书笔记 目录 第11章 这章主要讲了关于Linux和UNIX的系统资源的限制. 关于限制都存在一个最小值,这些最小值为<limits.h> ...

  6. 《Linux/Unix系统编程手册》读书笔记6

    <Linux/Unix系统编程手册>读书笔记 目录 第9章 这章主要讲了一堆关于进程的ID.实际用户(组)ID.有效用户(组)ID.保存设置用户(组)ID.文件系统用户(组)ID.和辅助组 ...

  7. 《Linux/Unix系统编程手册》读书笔记4

    <Linux/Unix系统编程手册>读书笔记 目录 第7章: 内存分配 通过增加堆的大小分配内存,通过提升program break位置的高度来分配内存. 基本学过C语言的都用过mallo ...

  8. 《Linux/Unix系统编程手册》读书笔记3

    <Linux/Unix系统编程手册>读书笔记 目录 第6章 这章讲进程.虚拟内存和环境变量等. 进程是一个可执行程序的实例.一个程序可以创建很多进程. 进程是由内核定义的抽象实体,内核为此 ...

  9. 《Linux/Unix系统编程手册》读书笔记1

    <Linux/Unix系统编程手册>读书笔记 目录 最近这一个月在看<Linux/Unix系统编程手册>,在学习关于Linux的系统编程.之前学习Linux的时候就打算写关于L ...

随机推荐

  1. Ansible-下部

    ansible-playbook  playbook是由一个或多个模块组成的,使用多个不同的模块,完成一件事情. ansible软件特点 可以实现批量管理可以实现批量部署ad-hoc(批量执行命令)- ...

  2. 解决ES报错NoNodeAvailableException[None of the configured nodes are available:问题

    elasticSearch的错误 NoNodeAvailableException[None of the configured nodes are available: [{#transport#- ...

  3. 东芝半导体最新ARM开发板——TT_M3HQ开箱评测

    前言 最近从面包板社区申请到一块东芝最新ARM Cortex-M3内核的开发板--TT_M3HQ,其实开发板收到好几天了,这几天一直在构思怎么来写这第一篇评测文章,看大家在社区也都发了第一篇评测,我也 ...

  4. C# -- LinkedList的使用

    C# -- LinkedList的使用 private static void TestLinkList() { LinkedList<Person> linkListPerson = n ...

  5. SpringMVC框架之第二篇

    6.参数绑定(重点) Springmvc作为表现层框架,是连接页面和service层的桥梁,它负责所有请求的中转.怎么从请求中接收参数是重点,这也体现了我们刚开始说的Springmvc的第一个作用:“ ...

  6. hive操作简单总结

    Hive DDL.DML操作 背景介绍 • 一.DDL操作(数据定义语言)包括:Create.Alter.Show.Drop等. • create database- 创建新数据库 • alter d ...

  7. Missing associated label more...

    1.加上placeholder,可以为空 2.放在label标签中

  8. Cesium专栏-卫星轨迹

    Cesium Cesium 是一款面向三维地球和地图的,世界级的JavaScript开源产品.它提供了基于JavaScript语言的开发包,方便用户快速搭建一款零插件的虚拟地球Web应用,并在性能,精 ...

  9. 前端开发规范:4-JS

    ESLint 使用ESLint的standard规范来编写js代码 更多参考: https://github.com/standard/standard/blob/master/docs/README ...

  10. GNN 相关资料记录;GCN 与 graph embedding 相关调研

    最近做了一些和gnn相关的工作,经常听到GCN 和 embedding 相关技术,感觉很是困惑,所以写下此博客,对相关知识进行索引和记录: 参考链接: https://www.toutiao.com/ ...