Linux网络子系统学习笔记

简单阐述了Linux网络子系统的流程,包含基本数据结构,数据发送与接收等。

LINUX网络子系统学习笔记

重要数据结构

struct softnet_data

/*
 * Incoming packets are placed on per-cpu queues
 */
struct softnet_data {
        struct Qdisc            *output_queue;
        struct Qdisc            **output_queue_tailp;
        struct list_head        poll_list; /*有需要处理报文的NAPI设备*/
        struct sk_buff          *completion_queue;
        struct sk_buff_head     process_queue;

        /* stats */
        unsigned int            processed;
        unsigned int            time_squeeze;
        unsigned int            cpu_collision;
        unsigned int            received_rps;

#ifdef CONFIG_RPS
        struct softnet_data     *rps_ipi_list;

        /* Elements below can be accessed between CPUs for RPS */
        struct call_single_data csd ____cacheline_aligned_in_smp;
        struct softnet_data     *rps_ipi_next;
        unsigned int            cpu;
        unsigned int            input_queue_head;  /*旧接口的输入队列*/
        unsigned int            input_queue_tail;
#endif
        unsigned int            dropped;
        //在incoming frame被Driver处理之前,存储的位置
        struct sk_buff_head     input_pkt_queue;
        struct napi_struct      backlog; /*虚拟的NAPI设备 backlog*/

#ifdef CONFIG_NET_FLOW_LIMIT
        struct sd_flow_limit __rcu *flow_limit;
#endif
};

/*
 * Structure for NAPI scheduling similar to tasklet but with weighting
 */
struct napi_struct {
        /* The poll_list must only be managed by the entity which
         * changes the state of the NAPI_STATE_SCHED bit.  This means
         * whoever atomically sets that bit can add this napi_struct
         * to the per-cpu poll_list, and whoever clears that bit
         * can remove from the list right before clearing the bit.
         */
        //This is a bidirectional list of devices with input frames waiting to be processed.
        struct list_head        poll_list;

        unsigned long           state;
        int                     weight;
        unsigned int            gro_count;
        int                     (*poll)(struct napi_struct *, int);
#ifdef CONFIG_NETPOLL
        spinlock_t              poll_lock;
        int                     poll_owner;
#endif
        //represents a device that has scheduled net_rx_action for execution on the associated CPU.
        struct net_device       *dev;
        struct sk_buff          *gro_list;
        struct sk_buff          *skb;
        struct list_head        dev_list;
        struct hlist_node       napi_hash_node;
        unsigned int            napi_id;
};

定义一个 per_cpu 变量 softnet_data

DECLARE_PER_CPU(struct softnet_data,softnet_data);

在函数 net_dev_init 对其进行了初始化。

struct sk_buff

2016071101.png

struct net_device

struct netdev_queue

encapsulates all of the information about a single transmit queue. 结构定义如下:

struct netdev_queue {
/*
 * read mostly part
 */
        struct net_device       *dev;
        struct Qdisc __rcu      *qdisc;
        struct Qdisc            *qdisc_sleeping;
#ifdef CONFIG_SYSFS
        struct kobject          kobj;
#endif
#if defined(CONFIG_XPS) && defined(CONFIG_NUMA)
        int                     numa_node;
#endif
/*
 * write mostly part
 */
        spinlock_t              _xmit_lock ____cacheline_aligned_in_smp;
        int                     xmit_lock_owner;
        /*
         * please use this field instead of dev->trans_start
         */
        unsigned long           trans_start;

        /*
         * Number of TX timeouts for this queue
         * (/sys/class/net/DEV/Q/trans_timeout)
         */
        unsigned long           trans_timeout;

  /*
   * __QUEUE_STATE_DRV_XOFF is used by drivers to stop the transmit queue.
   * netif_tx_* functions below are used to manipulate this flag.
   * __QUEUE_STATE_STACK_XOFF flag is used by the stack to stop the transmit
   * queue independently.  The netif_xmit_*stopped functions below are called
   * o check if the queue has been stopped by the driver or stack
   */
        unsigned long           state;

#ifdef CONFIG_BQL
        struct dql              dql;
#endif
} ____cacheline_aligned_in_smp;

引入这个结构体是为了支持多队列发送,即发送数据被放到不同优先级的队 列中。

2016110701.png

为此,引入了新的接口:

struct net_device *alloc_etherdev_mq(int sizeof_priv, unsigned int queue_count);

其中, 参数 queue_count 表示设备支持的最大传输队列数量。实际使 用的传输队列的数量保存在 net_devicereal_num_tx_queues 中。 为了判断将要发送的数据包应该发送到哪个队列中,调用如下函数:

u16 skb_get_queue_mapping(struct sk_buff *skb);

返回的值范围为 [0, realnumtxqueue)。 针对某个发送队列上的接口如下:

struct netdev_queue *netdev_get_tx_queue(struct net_device *dev,
                                         u16 index);

void netif_tx_start_queue(struct netdev_queue *dev_queue);
void netif_tx_wake_queue(struct netdev_queue *dev_queue);
void netif_tx_stop_queue(struct netdev_queue *dev_queue);

针对所有队列的接口:

void netif_tx_start_all_queues(struct net_device *dev);
void netif_tx_wake_all_queues(struct net_device *dev);
void netif_tx_stop_all_queues(struct net_device *dev);

接收数据

概述

L2层数据包的处理是通过中断来触发的。当中断发生后,内核会调用Driver相应的 中断处理函数。中断处理函数会立即响应,并将主要工作放到下半部去执行。

  1. 拷贝数据包到一个 struct sk_buff 的数据结构里面。 如果使用DMA, 则只需要初始化一个指针。
  2. 初始化一些 sk_buff 所需要的一些参数,供后续上层网络层使用。用 于标识上层协议处理函数。
  3. 更新其他的一些设备相关的参数。
  4. 通过调度 NET_RX_SOFTIRQ 软为断来告知内核有新的数据帧到达。

开启和禁用设备

net_device->state 这个值可以显示网络设备当前的状态, 当为 _ _LINK_STATE_START 代表设备已经使能。 _LINK_STATE_XOFF 用于显示地 开启或关闭数据发送。 接收则没有相关的状态标记来开启或关闭。 netif_running 用于检查当前网络设备的运行状态。

队列

有两个队列,发送队列和接收队列。 每个队列都有一个指向相关设备的指针 以及存储数据包的 sk_buff 数据结构。

通知一个数据包的到来

通过调用 netif_rx 来通知内核

一般的逻辑如下:

skb = dev_alloc_skb(pkt_len + 5);
... ... ...
if (skb != NULL) {
skb->dev = dev;
skb_reserve(skb, 2);
/* Align IP on 16 byte boundaries */
... ... ...
/* copy the DATA into the sk_buff structure */
... ... ...
skb->protocol = eth_type_trans(skb, dev);
netif_rx(skb);
dev->last_rx = jiffies;
... ... ...
}

利用NAPI新机制

NAPI与旧机制的不同点主要有两个地方:

  1. 驱动必须提供poll方法。
  2. 调用帧的接口
    //直接调用此接口通知内核收到数据帧
    napi_schedule(...); // obsolete: netif_rx_schedule(...);
    
    //或通过分为如下两个调用
    netif_rx_schedule_prep(...)
    __netif_rx_schedule(...)
    

当收到数据后,Driver都会将接收帧的设备加入到 poll_list 队列中,并 调度 NET_RX_SOFTIRQ 软中断执行。 最终会被 =netrxaction=。 两者 之间的差异如下图所示: 2016032301.png

netif_rx 函数流程图

netif_rx_ni 是非中断环境下运行的版本。 2016032302.png

下半部处理

处理 NET_RX_SOFTIRQ 软中断消息函数 net_rx_action 。 对于NAPI驱动来说,它会调用driver注册的poll函数。 2016032303.pngnet_dev_init 初始化阶段, 会注册poll函数为 process_backlog

netif_receive_skb

此函数是处理帧的具体函数,它的执行逻辑大概如下图所示: 2016032401.png

图中提到的Diverter可以修改数据包的目的地址。 执行到这个函数后,接收到的数据包将会根据需要传递到L3层去处理。 L3层 会注册相关的协议处理函数。

发送数据

概述

发送数据相关的软中断为: NET_TX_SOFTIRQ ,对应的处理函数为 net_tx_actionsoftnet_data 也有一个对应的列表 output_queue ,它代表有数据有发送的设备列表。只有 __LINK_STATE_XOFF 标记被清掉 的情况下,该设备才会被调度去发送数据。一旦设备被调度去发送数据,则 会置上这个标记: __LINK_STATE_SCHEDdev_queue_xmit 的作用类 似 netif_rx

禁用和开启数据传输

__LINK_STATE_XOFF 这个标记代表当前是否允许数据发送。 主要检查 net_device->state 的值。 __LINK_STATE_SCHED 这个标记代码当前是 否已经调度了数据发送。

netif_start_queue

开启当前设备上的数据传输。 当设备停止后,也可以再次调用该 函数重新开启数据传输。

netif_stop_queue

停止在当前设备上的数据传输。

netif_queue_stopped

检测发送队列的状态:禁用或者开启

static inline int netif_queue_stopped(const struct net_device *dev)
{
  return test_bit(_ _LINK_STATE_XOFF, &dev->state);
}

netif_wake_queue

当传输数据的Buffer不够时,就会暂时关闭数据传输,等到当前这些数据传 输完成或者有足够的空间传输至少一帧数据时,重新开启数据传输,就需要 调用些函数。 netif_wake_queuenetif_start_queue 相比,除了 开启数据传输外,还会检查发送队列中是否有数据待传。

停止当前网卡上的数据传输

netif_carrier_off(...)
netif_tx_stop_all_queues(...)

调度数据传输

内核提供了函数 dev_queue_xmit 来传输一个数据帧,该函数从设备的发 送队列中拿出一帧数据,并将其传递给 hard_start_xmit 方法。 dev_queue_xmit 并不总是能够传输数据,比如设备的发送队列被禁用,或 者无法获得当前发送队列的锁,对于后面这种情况,内核提供了另一个函数 __netif_schedule 。 一般不直接调用这个函数,而是通过使用如下两个 函数:

netif_schedule

static inline void netif_schedule(struct net_device *dev)
{
  if (!test_bit(_ _LINK_STATE_XOFF, &dev->state))
    _ _netif_schedule(dev);
}

一般在驱动代码之外被调用,如 net_tx_action 或传输控制。

netif_wake_queue

开启设备的数据发送,如果之前传输已经被调度,再次调用这个函数将是一 个空操作。

static inline void netif_wake_queue(struct net_device *dev)
{
  ...
  f (test_and_clear_bit(_ _LINK_STATE_XOFF, &dev->state))
    _ _netif_schedule(dev);
}

一般在设备驱动中调用。

Queuing Discipline Interface

内核使用的一种算法,用于以最有效率的顺序安排数据的发送。每种Traffic Control的入队规则都提供了不同的函数指针以供上层调用以完成不同的任务。 比较重要的函数有:

enqueue

入队

dequeue

出队

requeue

重新入队

每当驱动准备调度数据发送时,通过函数 qdisc_run 来选择下一帧需要传 输的数据, 这个函数会间接地调用与入队规则相关的 dequeue 虚函数。实 际的工作实际上在函数 qdisc_restart 里面做的。

static inline void qdisc_run(struct net_device *dev)
{
  while (!netif_queue_stopped(dev) && qdisc_restart(dev) < 0)
    /* NOTHING */;
}

qdisc_restart

根据各种入队规则的 dequeue 方法的返回值,来采取不同的动作。

int qdisc_restart(struct net_device *dev)
{
  struct Qdisc *q = dev->qdisc;
  struct sk_buff *skb;
  if ((skb = q->dequeue(q)) != NULL) {

    //传输数据需要两个Lock
    //dev->queue_lock 保护quue
    //dev->xmit_lock  由hard_start_xmit管理

    if (!spin_trylock(&dev->xmit_lock)) {
      //其他CPU已经在通过该设备传送数据了
    collision:
      ...
      goto requeue;
    }
    ...
 requeue:
    q->ops->requeue(skb, q);
    netif_schedule(dev);


    if (!netif_queue_stopped(dev)) {
      int ret;
      if (netdev_nit)
        dev_queue_xmit_nit(skb, dev);
      ret = dev->hard_start_xmit(skb, dev);
      if (ret == NETDEV_TX_OK) {
        if (!nolock) {
          dev->xmit_lock_owner = -1;
          spin_unlock(&dev->xmit_lock);
        }
        spin_lock(&dev->queue_lock);
        return -1;
      }
      if (ret == NETDEV_TX_LOCKED && nolock) {
        spin_lock(&dev->queue_lock);
        goto collision;
      }
    }
  }
}

hard_start_xmit 是每个WiFi驱动必须提供的一个回调函数。一般通过 qdisc_run ,如果不使用Traffic Control机制,则可以直接调用该函数接 口。 返回可能有:

NETDEV_TX_OK

传输正常

NETDEV_TX_BUSY

NIC没有足够的空间来发送当前数据帧。 这种情况下,通常会调用 netif_stop_queue

NETDEV_TX_LOCKED

驱动已经被锁住。

总的来说,如下三种情况下,会导致当前发送的数据帧被重新放入到发送队 列中:

The queue is disabled ( netif_queue_stopped(dev) is true).

Another CPU is holding the lock on the driver.

The driver failed ( hard_start_xmit did not return NEtdEV_TX_OK).

net_tx_action

与软中断 NET_RX_SOFTIRQ 关联的处理函数。通过函数调用 raise_softirq_irqoff(NET_TX_SOFTIRQ) 触发。主要在如下两种情况下调 用:

  1. 当启用数据传输时,调用 netif_wake_queue 触发。
  2. 当数据传输完成时,调用 dev_kfree_skb_irq 来释放内存。

协议处理

网络模型

网络模型通常有OSI 7层参考模型和TCP/IP 4层参考模型,如图所示:

2016042510.png

被传输的数据包在不同的协议层,称呼不一样。 链路层称为frame,网络层 称为packet, 传输层称为segment,应用层称之为message。

协议家族

主要的协议家族有:

  1. PF_INET
  2. PF_PACKET It's the Linux way to capture frames at the link layer and inject frames into the link layer, directly bypassing all the intermediate protocol layers.
  3. PF_NETLINK Used as the preferred interface for network configuration.
  4. PF_KEY Used as a key management interface for network security services. IPsec is one of these services.
  5. PF_LLC Logical Link Control (LLC)

驱动如何调用L3协议处理器

当设备驱动接收到一帧数据,它会保存到 sk_buffer 中,并初始化它的 protocol域:

struct sk_buff
{
  ... ... ...
  unsigned short
  ... ... ...
};

netif_receive_skb 函数会根据protocol的值,来决定调用哪个协议处理 函数。

协议处理函数注册

每个协议由 packet_type 结构体描述。内核中调用 dev_add_pack 注册 一种协议处理函数。

struct packet_type
{
  unsigned short type; //协议代码
  struct net_device *dev;//协议使能的设备,为NULL时,代表所有设备
  int (*func) (struct sk_buff *, struct net_device *,
           struct packet_type *);
  void *af_packet_priv;
  struct list_head *list;
};

dev_remove_pack 注册协议处理函数。如下是IPv4的初始化代码:

static struct packet_type ip_packet_type =
  {
    .type = _ _constant_htons(ETH_P_IP),
    .func = ip_rcv,
  }
  ...
  void _ _init ip_init(void)
  {
    dev_add_pack(&ip_packet_type);
    ...
  }

eth_type_trans performs two main tasks: setting the packet type setting the protocol.

static void tr_rx(struct device *dev)
{
  ...
  skb->protocol=tr_type_trans(skb, dev);
  ...
  netif_rx(skb);
  ...
}