首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > 编程 >

套接字socket 的地址族跟类型、工作原理、创建过程

2013-10-08 
套接字socket 的地址族和类型、工作原理、创建过程注:本分类下文章大多整理自《深入分析linux内核源代码》一书

套接字socket 的地址族和类型、工作原理、创建过程

注:本分类下文章大多整理自《深入分析linux内核源代码》一书,另有参考其他一些资料如《linux内核完全剖析》、《linux c 编程一站式学习》等,只是为了更好地理清系统编程和网络编程中的一些概念性问题,并没有深入地阅读分析源码,我也是草草翻过这本书,请有兴趣的朋友自己参考相关资料。此书出版较早,分析的版本为2.4.16,故出现的一些概念可能跟最新版本内核不同。

此书已经开源,阅读地址 http://www.kerneltravel.net


一、套接字socket
(一)、套接字在网络中的地位和作用
套接字socket 的地址族跟类型、工作原理、创建过程
套接字socket 的地址族跟类型、工作原理、创建过程
socket 在网络系统中的作用如下。(1)socket 位于网络协议之上,屏蔽了不同网络协议之间的差异。(2)socket 是网络编程的入口,它提供了大量的系统调用,构成了网络程序的主体。(3)在Linux 系统中,socket 属于文件系统的一部分,网络通信可以被看作是对文件的读取,使得我们对网络的控制和对文件的控制一样方便。
(二)、套接字接口的种类
Linux 支持多种套接字种类,不同的套接字种类称为“地址族”,这是因为每种套接字种类拥有自己的通信寻址方法。Linux 所支持的套接字地址族见表12.3。Linux 将上述套接字地址族抽象为统一的 BSD 套接字接口,应用程序关心的只是 BSD 套接字接口,而 BSD 套接字由各地址族专有的软件支持。一般而言,BSD 套接字可支持多种套接字类型,不同的套接字类型提供的服务不同,Linux 所支持的部分 BSD 套接字类型见表12.4,但表12.3 中的套接字地址族并不一定全部支持表12.4 中的这些套接字类型。
套接字socket 的地址族跟类型、工作原理、创建过程
套接字socket 的地址族跟类型、工作原理、创建过程
(三)、套接字的工作原理
INET 套接字就是支持 Internet 地址族的套接字,它位于TCP 之上,BSD 套接字之下,如图12.8 所示,这里也体现了Linux 网络模块分层的设计思想。套接字socket 的地址族跟类型、工作原理、创建过程
套接字socket 的地址族跟类型、工作原理、创建过程INET 和 BSD 套接字之间的接口通过 Internet 地址族套接字操作集实现,这些操作集实际是一组协议的操作例程,在include/linux/net.h 中定义为struct proto_ops:
 C++ Code 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
 struct proto_ops
{
    int family;
    int (*release) (struct socket *sock);
    int (*bind) (struct socket *sock, struct sockaddr *umyaddr,
                 int sockaddr_len);
    int (*connect) (struct socket *sock, struct sockaddr *uservaddr,
                    int sockaddr_len, int flags);
    int (*socketpair) (struct socket *sock1, struct socket *sock2);
    int (*accept) (struct socket *sock, struct socket *newsock,
                   int flags);
    int (*getname) (struct socket *sock, struct sockaddr *uaddr,
                    int *usockaddr_len, int peer);
    unsigned int (*poll) (struct file *file, struct socket *sock, struct poll_table_struct
                          *wait);
    int (*ioctl) (struct socket *sock, unsigned int cmd,
                  unsigned long arg);
    int (*listen) (struct socket *sock, int len);
    int (*shutdown) (struct socket *sock, int flags);
    int (*setsockopt) (struct socket *sock, int level, int optname,
                       char *optval, int optlen);
    int (*getsockopt) (struct socket *sock, int level, int optname,
                       char *optval, int *optlen);
    int (*sendmsg) (struct socket *sock, struct msghdr *m, int total_len, struct
                    scm_cookie *scm);
    int (*recvmsg) (struct socket *sock, struct msghdr *m, int total_len, int flags,
                    struct scm_cookie *scm);
    int (*mmap) (struct file *file, struct socket *sock, struct vm_area_struct *vma);
    ssize_t (*sendpage) (struct socket *sock, struct page *page, int offset, size_t size, int flags);
};
这个操作集类似于文件系统中的file_operations 结构。BSD 套接字层通过调用proto_ops 结构中的相应函数执行任务。BSD 套接字层向 INET 套接字层传递 socket 数据结构来代表一个 BSD 套接字,socket 结构在include/linux/net.h 中定义如下:
 C++ Code 1
2
3
4
5
6
7
8
9
10
11
12
13
 struct socket
{
    socket_state state;
    unsigned long flags;
    struct proto_ops *ops;
    struct inode *inode;
    struct fasync_struct *fasync_list; /* Asynchronous wake up list */
    struct file *file; /* File back pointer for gc */
    struct sock *sk;
    wait_queue_head_t wait;
    short type;
    unsigned char passcred;
};
但在 INET 套接字层中,它利用自己的 sock 数据结构来代表该套接字,因此,这两个结构之间存在着链接关系,sock 结构定义于include/net/sock.h。在 BSD 的 socket 数据结构中存在一个指向sock 的指针sk,而在sock 中又有一个指向socket 的指针,这两个指针将 BSD socket 数据结构和sock 数据结构链接了起来。通过这种链接关系,套接字调用就可以方便地检索到 sock 数据结构。实际上,sock 数据结构可适用于不同的协议,它也定义有自己的协议操作集proto_ops。在建立套接字时,sock数据结构的协议操作集指针指向所请求的协议操作集。如果请求 TCP,则 sock 数据结构的协议操作集指针将指向 TCP 的协议操作集。
BSD 套接字上的详细操作与具体的底层地址族有关,底层地址族的不同实际意味着寻址方式、采用的协议等的不同。Linux 利用 BSD 套接字层抽象了不同的套接字接口。在内核的初始化阶段,内建于内核的不同地址族分别以 BSD 套接字接口在内核中注册。然后,随着应用程序创建并使用 BSD 套接字。内核负责在 BSD 套接字和底层的地址族之间建立联系。这种联系通过交叉链接数据结构以及地址族专有的支持例程表建立。在内核中, 地址族和协议信息保存在inet_protos 向量中, 其定义于include/net/protocol.h:
 C++ Code 1
2
3
4
5
6
7
8
9
10
11
12
 struct inet_protocol *inet_protos[MAX_INET_PROTOS];
/* This is used to register protocols. */
struct inet_protocol
{
    int (*handler)(struct sk_buff *skb);//The Linux kernel uses an sk_buff data structure to describe each packet.
    void (*err_handler)(struct sk_buff *skb, u32 info);
    struct inet_protocol *next;
    unsigned char protocol;
    unsigned char copy: 1;
    void *data;
    const char *name;
};
每个地址族由其名称以及相应的初始化例程地址代表。在引导阶段初始化套接字接口时,内核调用每个地址族的初始化例程,这时,每个地址族注册自己的协议操作集。协议操作集实际是一个例程集合,其中每个例程执行一个特定的操作。
(四)、套接字的创建过程
Linux 在利用socket()系统调用建立新的套接字时,需要传递套接字的地址族标识符、套接字类型以及协议,其函数定义于net/socket.c 中:
 C++ Code 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 asmlinkage long sys_socket(int family, int type, int protocol)
{
    int retval;
    struct socket *sock;
    retval = sock_create(family, type, protocol, &sock);
    if (retval < 0)
        goto out;
    retval = sock_map_fd(sock);
    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;
}
实际上,套接字对于用户程序而言就是特殊的已打开的文件。内核中为套接字定义了一种特殊的文件类型,形成一种特殊的文件系统sockfs,其定义于net/socket.c:
 C++ Code 1
2
3
 static struct vfsmount *sock_mnt;
static DECLARE_FSTYPE(sock_fs_type, "sockfs", sockfs_read_super, FS_NOMOUNT);

在系统初始化时,要通过kern_mount()安装这个文件系统。安装时有个作为连接件的vfsmount 数据结构,这个结构的地址就保存在一个全局的指针sock_mnt 中。所谓创建一个套接字,就是在sockfs 文件系统中创建一个特殊文件,或者说一个节点,并建立起为实现套接字功能所需的一整套数据结构。所以,函数sock_create()首先是建立一个socket 数据结构,然后将其“映射”到一个已打开的文件中,进行socket 结构和sock 结构的分配和初始化。
新创建的 BSD socket 数据结构包含有指向地址族专有的套接字例程的指针,这一指针实际就是 proto_ops 数据结构的地址。
BSD 套接字的套接字类型设置为所请求的 SOCK_STREAM 或 SOCK_DGRAM 等。然后,内核利用 proto_ops 数据结构中的信息调用地址族专有的创建例程。之后,内核从当前进程的 fd 向量中分配空闲的文件描述符,该描述符指向的 file 数据结构被初始化。初始化过程包括将文件操作集指针指向由 BSD 套接字接口支持的 BSD 文件操作集。所有随后的套接字(文件)操作都将定向到该套接字接口,而套接字接口则会进一步调用地址族的操作例程,从而将操作传递到底层地址族,如图12.10 所示。
实际上,socket 结构与sock 结构是同一事物的两个方面。如果说socket 结构是面向进程和系统调用界面的,那么sock 结构就是面向底层驱动程序的。可是,为什么不把这两个数据结构合并成一个呢?我们说套接字是一种特殊的文件系统,因此,inode 结构内部的union 的一个成分就用作socket 结构,其定义如下:
 C++ Code 1
2
3
4
5
6
7
8
 struct inode {
        .......
        union
    {
        ........
        struct socket socket_i;
    }
}
套接字socket 的地址族跟类型、工作原理、创建过程套接字socket 的地址族跟类型、工作原理、创建过程

由于套接字操作的特殊性,这个结构中需要大量的结构成分。可是,如果把这些结构成分全都放在socket 结构中,则inode 结构中的这个union 就会变得很大,从而inode 结构也会变得很大,而对于其他文件系统,这个union 成分并不需要那么庞大。因此,就把套接字所需的这些结构成分拆成两部分,把与文件系统关系比较密切的那一部分放在socket 结构中,把与通信关系比较密切的那一部分则单独组成一个数据结构,即sock 结构。由于这两部分数据在逻辑上本来就是一体的,所以要通过指针互相指向对方,形成一对一的关系。

热点排行