套接口这个概念最先由4.2BSD(1983)引入,现在已经成为一个通用的网络应用程序编程接口,受到所有操作系统的支持。套接口层位于应用程序和 协议栈之间,对应用程序屏蔽了与协议相关实现的具体细节。
通常,应用程序中调用库函数,而库函数通过系统调用进入套接口层,Linux的套接口层实现提供了一组专门的套接口系统调用,分别在对应的库函数名之上加上"sys_"前缀。此外为了体现一切皆文件的理念,Linux也允许标准I/O系统调用通过一个套接口文件描述符来读写其对应套接口上的网络连接,就像通过文件描述符访问一个已经打开的普通文件一样。套接口在创建时,即与一个文件及一个文件描述符绑定,此后所有对该套接口的操作都是通过与其绑定的文件描述符进行的,包括专门的套接口系统调用,页包括标准的I/O系统调用对套接口的操作,与套接口绑定的文件类型为套接口文件。
套接口层的调用涉及以下文件:
include/linux/net.h 定义套接口层相关的结构、宏和函数原型
include/net/sock.h 定义基本的传输控制块结构、宏和函数原型
net/socket.c 实现套接口层的系统调用
net/ipv4/af_inet.c 网络层和传输层接口
socket结构
struct socket { socket_state state; kmemcheck_bitfield_begin(type); short type; kmemcheck_bitfield_end(type); unsigned long flags; /* * Please keep fasync_list & wait fields in the same cache line */ struct fasync_struct *fasync_list; wait_queue_head_t wait; struct file *file; struct sock *sk; const struct proto_ops *ops; };
state
用于表示所在套接口所处的状态标志。该标志有些状态只对TCP套接口有意义,因为只有TCP是面向连接的协议
typedef enum {
SS_FREE = 0,
/* not allocated */
SS_UNCONNECTED,
/* unconnected to any socket */
SS_CONNECTING,
/* in process of connecting */
SS_CONNECTED,
/* connected to socket */
SS_DISCONNECTING
/* in process of disconnecting */
} socket_state;
type
套接口类型
enum sock_type {
SOCK_STREAM
= 1, 基于连接的套接口
SOCK_DGRAM
= 2, 基于数据报的套接口
SOCK_RAW
= 3, 原始套接口
SOCK_RDM
= 4, 可靠传送报文套接口
SOCK_SEQPACKET
= 5, 顺序分组套接口
SOCK_DCCP
= 6, 数据报拥塞控制协议套接口
SOCK_PACKET
= 10,混杂模式套接口
};
flags
一组标志位
#define SOCK_ASYNC_NOSPACE
0 该套接口发送队列是否已满
#define SOCK_ASYNC_WAITDATA 1 应用程序通过recv调用时,是否在等待数据的接收
#define SOCK_NOSPACE 2 非异步情况下该套接口的发送队列是否已满
#define SOCK_PASSCRED 3 是否设置了SOCK_PASSCRED选项
#define SOCK_PASSSEC 4 是否设置了SOCK_PASSSEC
struct fasync_struct *fasync_list
存在了异步通知的队列
wait_queue_head_t wait
等待该套接口的进程队列
struct file *file
指向了与该套接口绑定的file结构的指针
struct sock *sk;
与该套接口关联的传输控制块
const struct proto_ops
*ops;
用来将套接口层的系统调用映射到相应传输层协议实现
PF_INET协议族定义了三种prote_ops结构实例
TCP inet_stream_ops
UDP inet_dgram_ops
RAW inet_sockraw_ops
proto_ops结构
整个proto_ops结构可以看作是一张套接口系统调用到传输层函数的跳转表,其中的某些操作会继续通过proto结构跳转表,进入具体的传输层或网络层的处理。
既然proto_ops结构完成的是从与协议无关的套接口层到协议相关的传输层的转接,而proto结构又将传输层映射到网络层,那么可想而知每个传输层的协议都需要定义一个特定的proto_ops实例和proto实例。在ipv4协议族中,一个传输层协议对应一个inet_protosw结构,inet_protosw结构包含了proto_ops结构和proto结构。
套接口文件系统
每一种文件都有各自的文件类型,如设备文件包括字符设备和块设备文件等,而与套接口关联的文件类型为套接口文件。
套接口文件系统类型
为了能使套接口与文件描述符关联,并支持特殊套接口层的i节点的分配和释放,系统中增加了sockfs文件系统sock_fs_type,通过sockfs文件系统的getsb接口和超级块操作集合中的alloc_inode和destroy_inode,可以分配和释放与套接口文件相关的i节点。可以通过/proc/filesystems文件查看操作系统支持的文件系统。
static const struct super_operations sockfs_ops = { .alloc_inode = sock_alloc_inode, .destroy_inode =sock_destroy_inode, .statfs = simple_statfs, }; static int sockfs_get_sb(struct file_system_type *fs_type, int flags, const char *dev_name, void *data, struct vfsmount *mnt) { return get_sb_pseudo(fs_type, "socket:", &sockfs_ops, SOCKFS_MAGIC, mnt); } static struct vfsmount *sock_mnt __read_mostly; static struct file_system_type sock_fs_type = { .name = "sockfs", .get_sb = sockfs_get_sb, .kill_sb = kill_anon_super, };
套接口文件的inode
套接口文件系统的i节点和套接口是一一对应的,因此套接口文件系统的i节点的分配时比较特殊的,分配的并不是一个单纯的i节点,而是i节点和socket结构的组合体,即socket_alloc结构,这样可以使套接口的分配及与之绑定的套接口文件i节点的分配同时进行。在应用层访问套接口要通过文件描述符,这样可以快速通过文件描述符定位与之绑定的套接口。
struct socket_alloc { struct socket socket; struct inode vfs_inode; }; static struct inode *sock_alloc_inode(struct super_block *sb) { struct socket_alloc *ei; ei = kmem_cache_alloc(sock_inode_cachep, GFP_KERNEL); if (!ei) return NULL; init_waitqueue_head(&ei->socket.wait); ei->socket.fasync_list = NULL; ei->socket.state = SS_UNCONNECTED; ei->socket.flags = 0; ei->socket.ops = NULL; ei->socket.sk = NULL; ei->socket.file = NULL; return &ei->vfs_inode; } static void sock_destroy_inode(struct inode *inode) { kmem_cache_free(sock_inode_cachep, container_of(inode, struct socket_alloc, vfs_inode)); }
套接口文件
套接口有一套独立的系统调用,包括建立套接口、连接和IO操作等,由于在建立套接口后返回的是文件描述符,因此也可以通过标准的文件IO操作进行对套接口的读写,例如用send()进行数据的发送,而事实上也可以通过write系统调用而达到同样的效果。这是由于在创建套接口文件时,使file结构中的f_op指向了socket_file_ops。通过socket_file_ops,可以看到套接口文件支持那些系统调用
static const struct file_operations socket_file_ops = { .owner = THIS_MODULE, .llseek = no_llseek, .aio_read = sock_aio_read, .aio_write = sock_aio_write, .poll = sock_poll, .unlocked_ioctl = sock_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = compat_sock_ioctl, #endif .mmap = sock_mmap, .open = sock_no_open, /* special open code to disallow open via /proc */ .release = sock_close, .fasync = sock_fasync, .sendpage = sock_sendpage, .splice_write = generic_splice_sendpage, .splice_read = sock_splice_read, };
套接口文件与套接口的绑定
应用层是通过文件描述符来访问套接口的,因此在调用socket系统调用创建套接口时,在创建后会调用sock_map_fd()绑定套接口和文件描述符
static int sock_attach_fd(struct socket *sock, struct file *file, int flags) { struct dentry *dentry; struct qstr name = { .name = "" }; dentry = d_alloc(sock_mnt->mnt_sb->s_root, &name); if (unlikely(!dentry)) return -ENOMEM; dentry->d_op = &sockfs_dentry_operations; /* * We dont want to push this dentry into global dentry hash table. * We pretend dentry is already hashed, by unsetting DCACHE_UNHASHED * This permits a working /proc/$pid/fd/XXX on sockets */ dentry->d_flags &= ~DCACHE_UNHASHED; d_instantiate(dentry, SOCK_INODE(sock)); sock->file = file; init_file(file, sock_mnt, dentry, FMODE_READ | FMODE_WRITE, &socket_file_ops); SOCK_INODE(sock)->i_fop = &socket_file_ops; file->f_flags = O_RDWR | (flags & O_NONBLOCK); file->f_pos = 0; file->private_data = sock; return 0; } int sock_map_fd(struct socket *sock, int flags) { struct file *newfile; int fd = sock_alloc_fd(&newfile, flags); if (likely(fd >= 0)) { int err = sock_attach_fd(sock, newfile, flags); if (unlikely(err < 0)) { put_filp(newfile); put_unused_fd(fd); return err; } fd_install(fd, newfile); } return fd; }
进程、文件描述符和套接口
在task_struct结构中,files指向file_struct结构,该结构的主要功能是管理fd_array指针数组指向的描述符,每一个file结构描述一个打开的文件。
套接口层的初始化
static int __init sock_init(void) { /* * Initialize sock SLAB cache. */ sk_init(); /* * Initialize skbuff SLAB cache */ skb_init(); /* * Initialize the protocols module. */ init_inodecache(); register_filesystem(&sock_fs_type); sock_mnt = kern_mount(&sock_fs_type); /* The real protocol initialization is performed in later initcalls. */ #ifdef CONFIG_NETFILTER netfilter_init(); #endif return 0; }