Linux 内核源码分析---套接字

套接字通信

ISO 设计一种参考模型,定义组成网络的各个层,该模型由7层组成,称为OSI(开放
系统互连)模型
如下:
在这里插入图片描述
应用层:网络服务与最终用户的接口;
表示层:数据的表示、安全及压缩;格式(jpeg,ascii等);
传话层:建立、管理及终止传话;
传输层:定义传输数据的协议商品,以及流控和差错校验;(数据包一旦离开网卡进入网络传输层);
网络层:进行逻辑地址建起、实现不同网络之间的路由选择;
数据链路层:建立逻辑连接、进行硬件地址寻址、差错检验等等这些功能(由底层网络定义协议),将比特组合成字节进而组合成帧,用MAC地址访问介质,错误发现但不能纠正。
物理层:。。。

核心基本术语
(1) 数据帧(Frame):指起始点和目的点都是数据链路层的信息单元
(2) 数据包(Packet):指起始点和目的地是网络层的信息单元
(3) 数据报(Datagram):指起始点和目的地都使用无连接网络服务的网络层的信息单元
(4) 段(Segment):指起始点和目的地都是传输层的信息单元
(5) 消息(Message):指起始点和目的地都在网络层以上的信息单元(经常在应用层)
(6) 元素(Cell):指的是一种固定长度的信息,它的起始点和目的地都是数据链路层。元素通常用于异步传输模式(ATM)和交换多兆位数据服务(SMDS)网络等交换环境。
(7) 数据单元(Data unit):常用的数据单元有服务数据单元(SDU)、协议数据单元(PDU)。
(8) 数据帧:帧数据由两部分组成:帧头部和帧数据,帧头部包括接收方主机物理地址的定位及其它网络信息,帧数据区含有一个数据体。
(9) IP数据体由两个部分组成:数据体头部和数据体的数据区,数据体头部包括IP源地址和IP目标地址及其它信息,数据体的数据区包括用户数据协议、传输控制协议,还有数据包及其它信息。

各层执行任务
主机到网络层负责将信息从一台计算机传输到远程计算机。它处理传输介质的物理性质,并将数据流划分为定长的帧,以便在发生传输错误时重传数据块。假设几台电脑共享 同一传输线程,网络接口卡必须有一个唯一的ID号,MAC 地址。
IP 使用一定格式的地址来寻址计算机,比如:192.168.1.1,这些地址由正式注册权威机构或提供者分配(有时为动态的)。

1、创建套接字
套接字不仅可以用于各种传输协议的IP连接,也可以用于内核支持的所有其他地址和协议类型(例如:IPX、Appletalk、本地UNIX套接字,还有在<socket.h>中列出的许多其他类型)。

在这里插入图片描述
sockaddr 的缺陷是:sa_data把目标地址和端口信息混在一起了

  • sa_family 是地址家族,一般以”AF_xxx”形式存在,通常为AF_INET,代表TCP/IP协议簇
  • sa_data 是14字节协议地址:
    • 4个字节的无符号整数(IP地址)
    • 2个字节的无符号整数(端口号)
    • sa_data 之所以被定义成14个字节,因为有的协议族使用较长的地址格式

从 sockaddr 的定义中,无法确定 IP 地址和端口号在这 14 个字节地址空间中的存放位置。因此,进行参数传递时,还需知道这14个字节的空间是如何利用,即哪里放IP地址、哪里放端口号、哪里是空白。
于是,在此基础上,构造了sockaddr_in 的结构体。

/* Structure describing an Internet socket address. */
struct sockaddr_in
{
    __SOCKADDR_COMMON (sin_family);         /* Address family */
    in_port_t sin_port;                     /* Port number. */
    struct in_addr sin_addr;            /* Internet address. */
    /* Pad to size of `struct sockaddr'. */
    unsigned char sin_zero[sizeof (struct sockaddr) -
                           __SOCKADDR_COMMON_SIZE -
                           sizeof (in_port_t) -
                           sizeof (struct in_addr)];     
                           /* 字符数组sin_zero[8]的存在是为了保证结构体struct sockaddr_in的大小和结构体struct sockaddr的大小相等 */
};
typedef unsigned short int sa_family_t;
#define __SOCKADDR_COMMON(sa_prefix) \
   sa_family_t sa_prefix##family /*此为整型变量占2字节,主要用于指明地址类型,取值为AF_UNIX|AF_INET|AF_INET6|AF_PACKET等*/

sin_family 指代协议族,在 socket 编程中只能是 AF_INET
sin_port 存储端口号(使用网络字节顺序);
sin_addr 存储 IP 地址,使用 in_addr 这个数据结构;
sin_zero 是为了让 sockaddrsockaddr_in 两个数据结构保持大小相同而保留的空字节。

2、使用套接字
如简单的并发服务器模型如下图所示。
在服务器端,主程序提前构建多个子进程,当客户端的请求到来的时候,系统从进程池中选取一个子进程处理客户端的连接,每个子进程处理一个客户端的请求,在全部子进程的处理能力得到满足之前,服务器的网络负载是基本不变的

BSD网络软件中包含两个重要的函数:inet_addrinet_ntoa。用来在二进制地址格式和点分十进制字符串格式之间转换,仅支持IPv4。也有两个函数同时支持 IPv4 和 IPv6:inet_ntop,inet_pton
在这里插入图片描述

3、数据报套接字
UDP 是建立在 IP 连接之上的第二种广泛使用的传输协议。UDP 表示 User Datagram Protocol(用户数据报协议)。UDP通常用于视频会议、音频流及类似的服务。

  • UDP是面向分组的,在发送数据之前,无须建立显式的连接
  • 分组可以在传输期间丢失,不保证数据的一定能够到达其目的地
  • 分组接收的次序不一定与发送的次序相同

4、各协议层的数据划分为首部和数据
在这里插入图片描述
首部部分包含了与数据部分有关的元数据(目标地址、长度、传输协议类型等);
数据部分包含有用数据(或净荷)。
传输的基本单位是帧,网卡以帧为单位发送数据。帧首部部分的主要数据项是目标系统的硬件地址,这是数据传输的目的地,通过电缆传输数据时也需要该数据项。
高层协议的数据在封闭到以太网帧时,将协议产生的首部和数据元二组封装到帧的数据部分。
在这里插入图片描述

网络分层模型

内核网络子系统的实现与TCP/IP参考模型非常相似。相关的C语言代码划分为不同层次,各层次都有明确定义的任务,各个层次只能通过明确定义的接口与上下紧邻的层次通信。这种做法的好处在于,可以组合使用各种设备、传输机制和协议。
在这里插入图片描述
Linux 网络核心架构分为三层:用户空间的应用层,内核空间的网络协议栈层,物理硬件层。
其中最重要的核心是内核空间的协议格层。在整个栈按照严格分层设计思想可分为五层:系统调用接口层–>协议无关的接口层–>网络协议实现层–>驱动接口层–>驱动程序层。

ixgb 驱动:http://t.csdnimg.cn/HV15O

Linux 内核网络栈涉及3层,用 L2 L3 L4三层分别对应OSI(数据链路层 网络层 传输层)。
内核栈的任务就是将接收到的数据包从L2(网络设备驱动程序)传递给L3(网络层,通常为IPv4或IPv6),接下来,如果数据包目的地为当前设备,Linux 内核网络栈就将其传递给 L4(传输层,应用 TCP 或 UDP 协议侦听套接字)。如果数据包需要转发,就将其交给 L2 进行传输。有可能产生:数据包可能被丢失、可能需要重组数据包、需要计算数据包的检验和等等。

套接字缓冲区

在内核分析(收到)网络分组时,底层协议的数据将传递到更高的层。发送数据时顺序相反,各种协议产生的数据(首部和净荷)依次向更低的层传递,直至最终发送。这些操作的速度对网络子系统的性能有决定性的影响,因此内核使用一种特殊的结构,称为套接字缓冲区(socket buffer)

Socket Buffer 主要由两部分组成。
1、数据包:存放了在网络中实际流通的数据。
2、管理数据结构(struct sk_buff):当在内核中对数据包进行时,内核还需要一些其他的数据来管理数据包和操作数据包,例如协议之间的交换信息,数据的状态,时间等。

Socket Buffer 有什么作用呢?
struct sk_buff 数据结构中存放了套接字接收 / 发送的数据。
在发送数据时,在套接字层创建了 Socket Buffer 缓冲区与管理数据结构,存放来自应用程序的数据。
在接收数据包时,Socket Buffer 则在网络设备的驱动程序中创建,存放来自网络的数据。

在发送和接受数据的过程中,各层协议的头信息会不断从数据包中插入和去掉,sk_buff 结构中描述协议头信息的地址指针也会被不断地赋值和复位。

一个网络数据包,它由双向链表构成,sk_buff 结构表示一个包含报头的入站或出站数据包在内核中 sk_buff 表示(SKB表示套接字缓冲区)。这个结构体并不直接存储网络数据包,而是存放了数据包的指针
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

套接字缓冲区的基本思想是,通过操作指针来增删协议首部
headend 指向数据在内存中的起始和结束位置;
datatail 指向协议数据区域的起始和结束位置;
mac_header 指向 MAC 协议首部的起始;
network_headertransport_header 分别指向网络层和传输层协议首部的起始。
在这里插入图片描述
在套接字缓冲区传递到互联网层时,必须增加一个新层。只需要向已经分配但尚未占用的那部分内存空间定稿的数据即可,除了 data 之外所有的指针都不变,data 现在指向 IP 首部的起始处。
对接收分组进行分析过程类似:分组数据复制到内核分配的一个内存区中,并在整个分析期间一直处于该内存区中。与该分组关联的套接字缓冲区在各层之间顺序传递。
如果是从 L4 传输到 L2,则是通过往sk_buff结构体中增加该层协议头来操作;如果是从L4到L2,则是通过移动sk_buff结构体中的data指针来实现,不会删除各层协议头。 这样做可以提高CPU的工作效率!

net_device 网络设备

net_device 结构体存储着网络设备的所有信息,每个设备都有这种结构。所有设备的 net_device 结构放在一个全局变量dev_base所有全局列表中
sk_buff 一样,整体结构相当庞大的。结构体中有一个 next 指针,用来连接系统中所有网络设备。内核把这些连接起来的设备组成一个链表,并由全局变量 dev_base 指向链表的第一个元素。

struct net_device {
    /* 设备名称,如eth0 */
    char            name[IFNAMSIZ];
    /* 名称hash */
    struct hlist_node    name_hlist;
    char             *ifalias;
    /*
     *    I/O specific fields
     *    FIXME: Merge these and struct ifmap into one
     */
    /*
        描述设备所用的共享内存,用于设备与内核沟通
        其初始化和访问只会在设备驱动程序内进行
    */
    unsigned long        mem_end;
    unsigned long        mem_start;

    /* 设备自有内存映射到I/O内存的起始地址 */
    unsigned long        base_addr;

    /*
        设备与内核对话的中断编号,此值可由多个设备共享
        驱动程序使用request_irq函数分配此变量,使用free_irq予以释放
    */
    int            irq;

    /* 侦测网络状态的改变次数 */
    atomic_t        carrier_changes;

    /*
     *    Some hardware also needs these fields (state,dev_list,
     *    napi_list,unreg_list,close_list) but they are not
     *    part of the usual set specified in Space.c.
     */

    /*
        网络队列子系统使用的一组标识
        由__LINK_STATE_xxx标识
    */
    unsigned long        state;

    struct list_head    dev_list;
    struct list_head    napi_list;
    struct list_head    unreg_list;
    struct list_head    close_list;

    /* 当前设备所有协议的链表 */
    struct list_head    ptype_all;
    /* 当前设备特定协议的链表 */
    struct list_head    ptype_specific;

    struct {
        struct list_head upper;
        struct list_head lower;
    } adj_list;

    /*
        用于存在其他一些设备功能
        可报告适配卡的功能,以便与CPU通信
        使用NETIF_F_XXX标识功能特性
    */
    netdev_features_t    features;
    netdev_features_t    hw_features;
    netdev_features_t    wanted_features;
    netdev_features_t    vlan_features;
    netdev_features_t    hw_enc_features;
    netdev_features_t    mpls_features;
    netdev_features_t    gso_partial_features;

    /* 网络设备索引号 */
    int            ifindex;

    /* 设备组,默认都属于0组 */
    int            group;

    struct net_device_stats    stats;

    atomic_long_t        rx_dropped;
    atomic_long_t        tx_dropped;
    atomic_long_t        rx_nohandler;

#ifdef CONFIG_WIRELESS_EXT
    const struct iw_handler_def *wireless_handlers;
    struct iw_public_data    *wireless_data;
#endif
    /* 设备操作接口 */
    const struct net_device_ops *netdev_ops;
    /* ethtool操作接口 */
    const struct ethtool_ops *ethtool_ops;
#ifdef CONFIG_NET_SWITCHDEV
    const struct switchdev_ops *switchdev_ops;
#endif
#ifdef CONFIG_NET_L3_MASTER_DEV
    const struct l3mdev_ops    *l3mdev_ops;
#endif
#if IS_ENABLED(CONFIG_IPV6)
    const struct ndisc_ops *ndisc_ops;
#endif

#ifdef CONFIG_XFRM
    const struct xfrmdev_ops *xfrmdev_ops;
#endif

    /* 头部一些操作,如链路层缓存,校验等 */
    const struct header_ops *header_ops;

    /* 标识接口特性,IFF_XXX,如IFF_UP */
    unsigned int        flags;

    /*
        用于存储用户空间不可见的标识
        由VLAN和Bridge虚拟设备使用
    */
    unsigned int        priv_flags;

    /* 几乎不使用,为了兼容保留 */
    unsigned short        gflags;

    /* 结构对齐填充 */
    unsigned short        padded;

    /* 与interface group mib中的IfOperStatus相关 */
    unsigned char        operstate;
    unsigned char        link_mode;

    /*
        接口使用的端口类型
    */
    unsigned char        if_port;

    /*
        设备使用的DMA通道
        并非所有设备都可以用DMA,有些总线不支持DMA
    */
    unsigned char        dma;

    /*
        最大传输单元,标识设备能处理帧的最大尺寸
        Ethernet-1500
    */
    unsigned int        mtu;
    /* 最小mtu,Ethernet-68 */
    unsigned int        min_mtu;
    /* 最大mut,Ethernet-65535 */
    unsigned int        max_mtu;

    /*     设备所属类型
        ARP模块中,用type判断接口的硬件地址类型
        以太网接口为ARPHRD_ETHER
    */
    unsigned short        type;
    /*
        设备头部长度
        Ethernet报头是ETH_HLEN=14字节
    */
    unsigned short        hard_header_len;
    unsigned char        min_header_len;

    /* 必须的头部空间 */
    unsigned short        needed_headroom;
    unsigned short        needed_tailroom;

    /* Interface address info. */
    /* 硬件地址,通常在初始化过程中从硬件读取 */
    unsigned char        perm_addr[MAX_ADDR_LEN];
    unsigned char        addr_assign_type;
    /* 硬件地址长度 */
    unsigned char        addr_len;
    unsigned short        neigh_priv_len;
    unsigned short          dev_id;
    unsigned short          dev_port;
    spinlock_t        addr_list_lock;
    /* 设备名赋值类型,如NET_NAME_UNKNOWN */
    unsigned char        name_assign_type;
    bool            uc_promisc;
    struct netdev_hw_addr_list    uc;
    struct netdev_hw_addr_list    mc;
    struct netdev_hw_addr_list    dev_addrs;

#ifdef CONFIG_SYSFS
    struct kset        *queues_kset;
#endif
    /* 混杂模式开启数量 */
    unsigned int        promiscuity;

    /* 非零值时,设备监听所有多播地址 */
    unsigned int        allmulti;


    /* Protocol-specific pointers */
/* 特定协议的指针 */
#if IS_ENABLED(CONFIG_VLAN_8021Q)
    struct vlan_info __rcu    *vlan_info;
#endif
#if IS_ENABLED(CONFIG_NET_DSA)
    struct dsa_switch_tree    *dsa_ptr;
#endif
#if IS_ENABLED(CONFIG_TIPC)
    struct tipc_bearer __rcu *tipc_ptr;
#endif
    void             *atalk_ptr;
    /* ip指向in_device结构 */
    struct in_device __rcu    *ip_ptr;
    struct dn_dev __rcu     *dn_ptr;
    struct inet6_dev __rcu    *ip6_ptr;
    void            *ax25_ptr;
    struct wireless_dev    *ieee80211_ptr;
    struct wpan_dev        *ieee802154_ptr;
#if IS_ENABLED(CONFIG_MPLS_ROUTING)
    struct mpls_dev __rcu    *mpls_ptr;
#endif

/*
 * Cache lines mostly used on receive path (including eth_type_trans())
 */
    /* Interface address info used in eth_type_trans() */
    unsigned char        *dev_addr;

#ifdef CONFIG_SYSFS
    /* 接收队列 */
    struct netdev_rx_queue    *_rx;

    /* 接收队列数 */
    unsigned int        num_rx_queues;
    unsigned int        real_num_rx_queues;
#endif

    struct bpf_prog __rcu    *xdp_prog;
    unsigned long        gro_flush_timeout;

    /* 如网桥等的收包回调 */
    rx_handler_func_t __rcu    *rx_handler;
    /* 回调参数 */
    void __rcu        *rx_handler_data;

#ifdef CONFIG_NET_CLS_ACT
    struct tcf_proto __rcu  *ingress_cl_list;
#endif
    struct netdev_queue __rcu *ingress_queue;
#ifdef CONFIG_NETFILTER_INGRESS
    /* netfilter入口 */
    struct nf_hook_entry __rcu *nf_hooks_ingress;
#endif

    /* 链路层广播地址 */
    unsigned char        broadcast[MAX_ADDR_LEN];
#ifdef CONFIG_RFS_ACCEL
    struct cpu_rmap        *rx_cpu_rmap;
#endif
    /* 接口索引hash */
    struct hlist_node    index_hlist;

/*
 * Cache lines mostly used on transmit path
 */
     /* 发送队列 */
    struct netdev_queue    *_tx ____cacheline_aligned_in_smp;
    /* 发送队列数 */
    unsigned int        num_tx_queues;
    unsigned int        real_num_tx_queues;
    /* 排队规则 */
    struct Qdisc        *qdisc;
#ifdef CONFIG_NET_SCHED
    DECLARE_HASHTABLE    (qdisc_hash, 4);
#endif
    /*
        可在设备发送队列中排队的最大数据包数
    */
    unsigned long        tx_queue_len;
    spinlock_t        tx_global_lock;

    /*     网络层确定传输超时,
        调用驱动程序tx_timeout接口的最短时间
    */
    int            watchdog_timeo;

#ifdef CONFIG_XPS
    struct xps_dev_maps __rcu *xps_maps;
#endif
#ifdef CONFIG_NET_CLS_ACT
    struct tcf_proto __rcu  *egress_cl_list;
#endif

    /* These may be needed for future network-power-down code. */
    /* watchdog定时器 */
    struct timer_list    watchdog_timer;

    /* 引用计数 */
    int __percpu        *pcpu_refcnt;

    /*     网络设备的注册和除名以两步进行,
        该字段用于处理第二步
    */
    struct list_head    todo_list;

    struct list_head    link_watch_list;

    /* 设备的注册状态 */
    enum { NETREG_UNINITIALIZED=0,
           NETREG_REGISTERED,    /* completed register_netdevice */
           NETREG_UNREGISTERING,    /* called unregister_netdevice */
           NETREG_UNREGISTERED,    /* completed unregister todo */
           NETREG_RELEASED,        /* called free_netdev */
           NETREG_DUMMY,        /* dummy device for NAPI poll */
    } reg_state:8;

    /* 设备要被释放标记 */
    bool dismantle;

    enum {
        RTNL_LINK_INITIALIZED,
        RTNL_LINK_INITIALIZING,
    } rtnl_link_state:16;

    bool needs_free_netdev;
    void (*priv_destructor)(struct net_device *dev);

#ifdef CONFIG_NETPOLL
    struct netpoll_info __rcu    *npinfo;
#endif

    possible_net_t            nd_net;

    /* mid-layer private */
    union {
        void                    *ml_priv;
        struct pcpu_lstats __percpu        *lstats;
        struct pcpu_sw_netstats __percpu    *tstats;
        struct pcpu_dstats __percpu        *dstats;
        struct pcpu_vstats __percpu        *vstats;
    };

#if IS_ENABLED(CONFIG_GARP)
    struct garp_port __rcu    *garp_port;
#endif
#if IS_ENABLED(CONFIG_MRP)
    struct mrp_port __rcu    *mrp_port;
#endif

    struct device        dev;
    const struct attribute_group *sysfs_groups[4];
    const struct attribute_group *sysfs_rx_queue_group;

    const struct rtnl_link_ops *rtnl_link_ops;

    /* for setting kernel sock attribute on TCP connection setup */
#define GSO_MAX_SIZE        65536
    unsigned int        gso_max_size;
#define GSO_MAX_SEGS        65535
    u16            gso_max_segs;

#ifdef CONFIG_DCB
    const struct dcbnl_rtnl_ops *dcbnl_ops;
#endif
    u8            num_tc;
    struct netdev_tc_txq    tc_to_txq[TC_MAX_QUEUE];
    u8            prio_tc_map[TC_BITMASK + 1];

#if IS_ENABLED(CONFIG_FCOE)
    unsigned int        fcoe_ddp_xid;
#endif
#if IS_ENABLED(CONFIG_CGROUP_NET_PRIO)
    struct netprio_map __rcu *priomap;
#endif
    struct phy_device    *phydev;
    struct lock_class_key    *qdisc_tx_busylock;
    struct lock_class_key    *qdisc_running_key;
    bool            proto_down;
};

网络设备接收数据的主要方法是由中断引发设备的中断处理函数,中断处理函数判断中断类型,如果为接收中断,则读取接收到的数据,分配 sk_buffer 数据结构和数据缓冲区,将接收到的数据复制到数据缓冲区,并调用 netif_rx() 函数将 sk_buffer 传递给上层协议。

网络设备中 NAP,老式网络设备驱动程序是在中断驱动模式下工作,意味着每接收一个数据包,就需要中断一次事实证明此工作方式在负载很高情况下效率降低,为此解决这个问题,开发一种新的软件技术–>NAPI(New API)。采用 NAPler 技术时,如果负载很高,网络设备驱动程序将在轮询模式,而不是中断驱动模式下运行
【与数据包的中断接收方式不同的是,以轮询方式接收数据包时,当第一次中断发生后,中断处理程序要禁止设备的数据包接收中断并调度 NAPI】。

数据包的收发:网络设备驱动程序主要任务:接收目的地为当前主机的数据包,并将其传递给网络层,之后再将其传递给传输层。传输当前主机生成的外出数据包或转换当前主机收到数据包。对于每个数据包,无论它是接收到还是发送出去,都需要在路由子系统中执行一次查找操作。

每个 SKB(socket buffer) 都有一个dev成员(一个net_device结构实例)对于到来的数据包,这个成员表示接收它的网络设备,而对于外出的数据包,这个成员表示发送它的网络设备。

https://www.cnblogs.com/theseventhson/p/15858194.html

https://www.cnblogs.com/whiteBear/p/16380625.html

网络设备之net_device结构与操作

  • 14
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

飞大圣

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值