`

Linux内核中流量控制(15)

阅读更多
本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,严禁用于任何商业用途。
msn: yfydz_no1@hotmail.com
来源:http://yfydz.cublog.cn

5.15. Qdisc的netlink控制

各网卡的Qdisc的用户层操作控制是通过rtnetlink接口实现用户空间和内核之间的通信的: rtnetlink_link, rtnetlink是专门针对路由控制的netlink接口.
/* include/linux/rtnetlink.h */
struct rtnetlink_link
{
// 就两个成员函数, 操作和输出
 int (*doit)(struct sk_buff *, struct nlmsghdr*, void *attr);
 int (*dumpit)(struct sk_buff *, struct netlink_callback *cb);
};

// 全局数组, 具体在 net/core/rtnetlink.c中定义
extern struct rtnetlink_link * rtnetlink_links[NPROTO];

其中的两个成员定义如下:
/* net/core/rtnetlink.c */

void __init rtnetlink_init(void)
{
......
 rtnetlink_links[PF_UNSPEC] = link_rtnetlink_table;
 rtnetlink_links[PF_PACKET] = link_rtnetlink_table;
......
}

其中link_rtnetlink_table是一个数组, 定义为:
static struct rtnetlink_link link_rtnetlink_table[RTM_NR_MSGTYPES] =
{
// 数组基本元素, 处理路由, 邻居ARP等相关信息, 这些不是本文重点,
// 只是Qdisc的相关操作也是定义在这个数组中
 [RTM_GETLINK     - RTM_BASE] = { .doit   = rtnl_getlink,
      .dumpit = rtnl_dump_ifinfo  },
 [RTM_SETLINK     - RTM_BASE] = { .doit   = rtnl_setlink   },
 [RTM_GETADDR     - RTM_BASE] = { .dumpit = rtnl_dump_all  },
 [RTM_GETROUTE    - RTM_BASE] = { .dumpit = rtnl_dump_all  },
 [RTM_NEWNEIGH    - RTM_BASE] = { .doit   = neigh_add   },
 [RTM_DELNEIGH    - RTM_BASE] = { .doit   = neigh_delete   },
 [RTM_GETNEIGH    - RTM_BASE] = { .dumpit = neigh_dump_info  },
#ifdef CONFIG_FIB_RULES
 [RTM_NEWRULE     - RTM_BASE] = { .doit   = fib_nl_newrule  },
 [RTM_DELRULE     - RTM_BASE] = { .doit   = fib_nl_delrule  },
#endif
 [RTM_GETRULE     - RTM_BASE] = { .dumpit = rtnl_dump_all  },
 [RTM_GETNEIGHTBL - RTM_BASE] = { .dumpit = neightbl_dump_info  },
 [RTM_SETNEIGHTBL - RTM_BASE] = { .doit   = neightbl_set   },
};
 
5.15.1 初始化

初始化过程是定义对应tc的qdisc和class的操作命令的处理函数:
/* net/sched/sch_api.c */
static int __init pktsched_init(void)
{
 struct rtnetlink_link *link_p;
// 流控调度的时钟初始化
#ifdef CONFIG_NET_SCH_CLK_CPU
 if (psched_calibrate_clock() < 0)
  return -1;
#elif defined(CONFIG_NET_SCH_CLK_JIFFIES)
 psched_tick_per_us = HZ<<PSCHED_JSCALE;
 psched_us_per_tick = 1000000;
#endif
// 使用PF_UNSPEC(0)号rtnetlink_links元素用来作为QDISC操作的接口
 link_p = rtnetlink_links[PF_UNSPEC];
 /* Setup rtnetlink links. It is made here to avoid
    exporting large number of public symbols.
  */
// link_p将指向link_rtnetlink_table数组
 if (link_p) {
// 对数组中流控相关元素进行赋值
// Qdisc操作, 也就是对应tc qdisc add/modify等操作
  link_p[RTM_NEWQDISC-RTM_BASE].doit = tc_modify_qdisc;
// 删除/获取Qdisc操作
  link_p[RTM_DELQDISC-RTM_BASE].doit = tc_get_qdisc;
  link_p[RTM_GETQDISC-RTM_BASE].doit = tc_get_qdisc;
// 获取Qdisc信息, 也就是对应tc qdisc show
  link_p[RTM_GETQDISC-RTM_BASE].dumpit = tc_dump_qdisc;
// class操作, 也就是对应tc class add/delete/modify/get等操作, 在后续文章中分析
  link_p[RTM_NEWTCLASS-RTM_BASE].doit = tc_ctl_tclass;
  link_p[RTM_DELTCLASS-RTM_BASE].doit = tc_ctl_tclass;
  link_p[RTM_GETTCLASS-RTM_BASE].doit = tc_ctl_tclass;
  link_p[RTM_GETTCLASS-RTM_BASE].dumpit = tc_dump_tclass;
 }
// 登记FIFO流控处理, 这是网卡设备基本流控方法, 缺省必有的
 register_qdisc(&pfifo_qdisc_ops);
 register_qdisc(&bfifo_qdisc_ops);
 proc_net_fops_create("psched", 0, &psched_fops);
 return 0;
}

5.15.2 相关操作

以下函数中用到的Qdisc操作函数可见本系列第一篇, 第4节

5.15.2.1 创建/修改qdisc

/*
   Create/change qdisc.
 */
static int tc_modify_qdisc(struct sk_buff *skb, struct nlmsghdr *n, void *arg)
{
 struct tcmsg *tcm;
 struct rtattr **tca;
 struct net_device *dev;
 u32 clid;
 struct Qdisc *q, *p;
 int err;
replay:
 /* Reinit, just in case something touches this. */
// tc消息指针
 tcm = NLMSG_DATA(n);
 tca = arg;
// class id
 clid = tcm->tcm_parent;
 q = p = NULL;
// 该tc命令针对的网卡
 if ((dev = __dev_get_by_index(tcm->tcm_ifindex)) == NULL)
  return -ENODEV;
 if (clid) {
// 指定了类别ID的情况
  if (clid != TC_H_ROOT) {
// 如果不是根节点
   if (clid != TC_H_INGRESS) {
// 非ingress节点时, 根据类别ID的高16位查找Qdisc节点
    if ((p = qdisc_lookup(dev, TC_H_MAJ(clid))) == NULL)
     return -ENOENT;
// 获取p节点的叶子节点, 将调用p->ops->cl_ops->leaf()函数
    q = qdisc_leaf(p, clid);
   } else { /*ingress */
// 使用设备ingress流控
    q = dev->qdisc_ingress;
   }
  } else {
// 根节点情况下流控用的是设备的qdisc_sleeping
   q = dev->qdisc_sleeping;
  }
  /* It may be default qdisc, ignore it */
// 如果找到的Qdisc的句柄为0, 放弃q
  if (q && q->handle == 0)
   q = NULL;
  if (!q || !tcm->tcm_handle || q->handle != tcm->tcm_handle) {
// 没找到Qdisc节点, 或没在tc消息中指定句柄值, 或者找到的Qdisc句柄和tc消息中
// 的句柄不同
   if (tcm->tcm_handle) {
// TC指定了句柄
// 如果Qdisc存在但不是更新命令, 返回对象存在错误
    if (q && !(n->nlmsg_flags&NLM_F_REPLACE))
     return -EEXIST;
// TC句柄低16位不能位0
    if (TC_H_MIN(tcm->tcm_handle))
     return -EINVAL;
// 根据TC句柄查找该设备上的Qdisc, 找不到的话跳转到创建新节点操作
    if ((q = qdisc_lookup(dev, tcm->tcm_handle)) == NULL)
     goto create_n_graft;
// 找到但设置了NLM_F_EXCL排斥标志, 返回对象存在错误
    if (n->nlmsg_flags&NLM_F_EXCL)
     return -EEXIST;
// 比较TC命令中的算法名称和Qdisc名称算法相同
    if (tca[TCA_KIND-1] && rtattr_strcmp(tca[TCA_KIND-1], q->ops->id))
     return -EINVAL;
// 检查算法出现回环情况, p是用clid找到的Qdisc
    if (q == p ||
        (p && check_loop(q, p, 0)))
     return -ELOOP;
// 新找到的Qdisc有效, 转到嫁接操作
    atomic_inc(&q->refcnt);
    goto graft;
   } else {
// 没指定TC句柄, 如果没找到Qdisc, 跳转到创建新节点
    if (q == NULL)
     goto create_n_graft;
    /* This magic test requires explanation.
     *
     *   We know, that some child q is already
     *   attached to this parent and have choice:
     *   either to change it or to create/graft new one.
     *
     *   1. We are allowed to create/graft only
     *   if CREATE and REPLACE flags are set.
     *
     *   2. If EXCL is set, requestor wanted to say,
     *   that qdisc tcm_handle is not expected
     *   to exist, so that we choose create/graft too.
     *
     *   3. The last case is when no flags are set.
     *   Alas, it is sort of hole in API, we
     *   cannot decide what to do unambiguously.
     *   For now we select create/graft, if
     *   user gave KIND, which does not match existing.
     */
// 检查各种标志是否冲突, Qdisc名称是否正确
    if ((n->nlmsg_flags&NLM_F_CREATE) &&
        (n->nlmsg_flags&NLM_F_REPLACE) &&
        ((n->nlmsg_flags&NLM_F_EXCL) ||
         (tca[TCA_KIND-1] &&
          rtattr_strcmp(tca[TCA_KIND-1], q->ops->id))))
     goto create_n_graft;
   }
  }
 } else {
// 如果没指定类别ID, 从tc消息的句柄来查找Qdisc
  if (!tcm->tcm_handle)
   return -EINVAL;
  q = qdisc_lookup(dev, tcm->tcm_handle);
 }
// 到这里是属于Qdisc修改操作
 /* Change qdisc parameters */
// 没找到Qdisc节点, 返回错误
 if (q == NULL)
  return -ENOENT;
// 找到Qdisc节点, 但设置了NLM_F_EXCL(排斥)标志, 返回对象存在错误
 if (n->nlmsg_flags&NLM_F_EXCL)
  return -EEXIST;
// 检查找到的Qdisc节点的名称和tc中指定的是否匹配
 if (tca[TCA_KIND-1] && rtattr_strcmp(tca[TCA_KIND-1], q->ops->id))
  return -EINVAL;
// 修改Qdisc参数
 err = qdisc_change(q, tca);
 if (err == 0)
  qdisc_notify(skb, n, clid, NULL, q);
 return err;
create_n_graft:
// 创建新Qdisc节点
// 如果TC命令中没有创建标志, 返回错误
 if (!(n->nlmsg_flags&NLM_F_CREATE))
  return -ENOENT;
// 创建新Qdisc节点
 if (clid == TC_H_INGRESS)
  q = qdisc_create(dev, tcm->tcm_parent, tca, &err);
        else
  q = qdisc_create(dev, tcm->tcm_handle, tca, &err);
 if (q == NULL) {
// 创建失败, 如果不是EAGAIN(重来一次), 返回失败
  if (err == -EAGAIN)
   goto replay;
  return err;
 }
graft:
// 嫁接操作
 if (1) {
  struct Qdisc *old_q = NULL;
// 进行嫁接操作, 返回老节点
  err = qdisc_graft(dev, p, clid, q, &old_q);
  if (err) {
// 失败, 释放新建立的Qdisc
   if (q) {
    spin_lock_bh(&dev->queue_lock);
    qdisc_destroy(q);
    spin_unlock_bh(&dev->queue_lock);
   }
   return err;
  }
// Qdisc通告
  qdisc_notify(skb, n, clid, old_q, q);
  if (old_q) {
// 如果存在老Qdisc节点, 释放之
   spin_lock_bh(&dev->queue_lock);
   qdisc_destroy(old_q);
   spin_unlock_bh(&dev->queue_lock);
  }
 }
 return 0;
}

5.15.2.2 获取/删除qdisc
/*
 * Delete/get qdisc.
 */
static int tc_get_qdisc(struct sk_buff *skb, struct nlmsghdr *n, void *arg)
{
 struct tcmsg *tcm = NLMSG_DATA(n);
 struct rtattr **tca = arg;
 struct net_device *dev;
// class id
 u32 clid = tcm->tcm_parent;
 struct Qdisc *q = NULL;
 struct Qdisc *p = NULL;
 int err;
// 根据TC参数中的网卡索引号查找网卡设备
 if ((dev = __dev_get_by_index(tcm->tcm_ifindex)) == NULL)
  return -ENODEV;
// 根据类别ID或TC句柄查找Qdisc, 和上面函数类似
 if (clid) {
  if (clid != TC_H_ROOT) {
   if (TC_H_MAJ(clid) != TC_H_MAJ(TC_H_INGRESS)) {
    if ((p = qdisc_lookup(dev, TC_H_MAJ(clid))) == NULL)
     return -ENOENT;
    q = qdisc_leaf(p, clid);
   } else { /* ingress */
    q = dev->qdisc_ingress;
                        }
  } else {
   q = dev->qdisc_sleeping;
  }
  if (!q)
   return -ENOENT;
  if (tcm->tcm_handle && q->handle != tcm->tcm_handle)
   return -EINVAL;
 } else {
  if ((q = qdisc_lookup(dev, tcm->tcm_handle)) == NULL)
   return -ENOENT;
 }
// 检查找到的Qdisc名称和TC命令中指定的是否一致
 if (tca[TCA_KIND-1] && rtattr_strcmp(tca[TCA_KIND-1], q->ops->id))
  return -EINVAL;
// 删除Qdisc操作
 if (n->nlmsg_type == RTM_DELQDISC) {
// 必须指定类别ID
  if (!clid)
   return -EINVAL;
// 如果找到的Qdisc句柄为0, 返回错误
  if (q->handle == 0)
   return -ENOENT;
// 进行Qdisc嫁接操作, 新节点是NULL, 即将叶子节点替换为NULL, 即删除了原叶子节点
// 原叶子节点返回到q
  if ((err = qdisc_graft(dev, p, clid, NULL, &q)) != 0)
   return err;
  if (q) {
// 释放原叶子节点
   qdisc_notify(skb, n, clid, q, NULL);
   spin_lock_bh(&dev->queue_lock);
   qdisc_destroy(q);
   spin_unlock_bh(&dev->queue_lock);
  }
 } else {
// 非删除操作, 通告一下, q作为获得的Qdisc参数返回
  qdisc_notify(skb, n, clid, NULL, q);
 }
 return 0;
}

// 发送Qdisc通知信息, new是处理后新Qdisc节点信息, old是处理前老节点信息
static int qdisc_notify(struct sk_buff *oskb, struct nlmsghdr *n,
   u32 clid, struct Qdisc *old, struct Qdisc *new)
{
 struct sk_buff *skb;
 u32 pid = oskb ? NETLINK_CB(oskb).pid : 0;
// 分配netlink数据包
 skb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
 if (!skb)
  return -ENOBUFS;
 if (old && old->handle) {
// 填充老Qdisc的信息
  if (tc_fill_qdisc(skb, old, clid, pid, n->nlmsg_seq, 0, RTM_DELQDISC) < 0)
   goto err_out;
 }
 if (new) {
// 填充新Qdisc的信息
  if (tc_fill_qdisc(skb, new, clid, pid, n->nlmsg_seq, old ? NLM_F_REPLACE : 0, RTM_NEWQDISC) < 0)
   goto err_out;
 }
// 发送数据包
 if (skb->len)
  return rtnetlink_send(skb, pid, RTNLGRP_TC, n->nlmsg_flags&NLM_F_ECHO);
err_out:
// 错误处理, 释放数据包
 kfree_skb(skb);
 return -EINVAL;
}

5.15.2.3 输出网卡qdisc参数

static int tc_dump_qdisc(struct sk_buff *skb, struct netlink_callback *cb)
{
 int idx, q_idx;
 int s_idx, s_q_idx;
 struct net_device *dev;
 struct Qdisc *q;
// 起始网卡索引
 s_idx = cb->args[0];
// 起始Qdisc索引
 s_q_idx = q_idx = cb->args[1];
 read_lock(&dev_base_lock);
// 遍历所有网卡
 for (dev=dev_base, idx=0; dev; dev = dev->next, idx++) {
// 索引值小于所提供的起始索引值, 跳过
// 这个索引和网卡的索引号应该没啥关系
  if (idx < s_idx)
   continue;
// 索引值大于所提供的起始索引值, 将起始Qdisc索引清零
  if (idx > s_idx)
   s_q_idx = 0;
  read_lock(&qdisc_tree_lock);
// q_idx清零, 这样前面也用不着在初始化时赋值
  q_idx = 0;
// 遍历该网卡设备的所有Qdisc
  list_for_each_entry(q, &dev->qdisc_list, list) {
// 当前Qdisc索引小于起始Qdisc索引, 跳过
// 所以当idx > s_idx时, s_q_idx = 0, 只处理第一个Qdisc
// 当idx == s_idx时, 处理从s_q_idx开始的所有Qdisc
   if (q_idx < s_q_idx) {
    q_idx++;
    continue;
   }
// 填充Qdisc信息到数据包
   if (tc_fill_qdisc(skb, q, q->parent, NETLINK_CB(cb->skb).pid,
       cb->nlh->nlmsg_seq, NLM_F_MULTI, RTM_NEWQDISC) <= 0) {
    read_unlock(&qdisc_tree_lock);
    goto done;
   }
   q_idx++;
  }
  read_unlock(&qdisc_tree_lock);
 }
done:
 read_unlock(&dev_base_lock);
// 返回处理的所有网卡数和Qdisc数
 cb->args[0] = idx;
 cb->args[1] = q_idx;
 return skb->len;
}

// 填充Qdisc信息到skb数据包
static int tc_fill_qdisc(struct sk_buff *skb, struct Qdisc *q, u32 clid,
    u32 pid, u32 seq, u16 flags, int event)
{
 struct tcmsg *tcm;
 struct nlmsghdr  *nlh;
 unsigned char  *b = skb->tail;
 struct gnet_dump d;
// skb中的netlink数据头位置
 nlh = NLMSG_NEW(skb, pid, seq, event, sizeof(*tcm), flags);
// TC信息数据头位置
 tcm = NLMSG_DATA(nlh);
// 填充TC信息参数
 tcm->tcm_family = AF_UNSPEC;
 tcm->tcm__pad1 = 0;
 tcm->tcm__pad2 = 0;
 tcm->tcm_ifindex = q->dev->ifindex;
 tcm->tcm_parent = clid;
 tcm->tcm_handle = q->handle;
 tcm->tcm_info = atomic_read(&q->refcnt);
 RTA_PUT(skb, TCA_KIND, IFNAMSIZ, q->ops->id);
// Qdisc的输出函数
 if (q->ops->dump && q->ops->dump(q, skb) < 0)
  goto rtattr_failure;
 q->qstats.qlen = q->q.qlen;
// 准备开始拷贝统计信息
 if (gnet_stats_start_copy_compat(skb, TCA_STATS2, TCA_STATS,
   TCA_XSTATS, q->stats_lock, &d) < 0)
  goto rtattr_failure;
// 输出统计信息
 if (q->ops->dump_stats && q->ops->dump_stats(q, &d) < 0)
  goto rtattr_failure;
// 拷贝基本统计信息, 流控速率统计信息, 队列统计信息
 if (gnet_stats_copy_basic(&d, &q->bstats) < 0 ||
#ifdef CONFIG_NET_ESTIMATOR
     gnet_stats_copy_rate_est(&d, &q->rate_est) < 0 ||
#endif
     gnet_stats_copy_queue(&d, &q->qstats) < 0)
  goto rtattr_failure;
// 结束封口操作 
 if (gnet_stats_finish_copy(&d) < 0)
  goto rtattr_failure;
 
 nlh->nlmsg_len = skb->tail - b;
 return skb->len;
nlmsg_failure:
rtattr_failure:
 skb_trim(skb, b - skb->data);
 return -1;
}
 

5.16 Qdisc小结
 
关于流控(Qdisc)的分析就此告一段落, 后面将继续分析分类(class), 过滤(filter)和动作(action)的处理过程.

...... 待续 ......
分享到:
评论

相关推荐

    Linux网络体系结构 Linux内核中网络协议的设计与实现

    在Linux内核中,TCP和UDP模块处理连接建立、数据传输、流量控制和拥塞控制等问题。 5. **应用层**:这一层包含各种应用协议,如HTTP、FTP、SMTP等,它们直接与用户交互。Linux内核通过socket API为上层应用提供了与...

    基于Linux内核的BT流量控制的原理与实现.pdf

    【基于Linux内核的BT流量控制的原理与实现】 Linux操作系统以其开源、可定制的特点,在系统开发领域广泛应用,尤其在网络流量控制方面具有显著优势。针对BitTorrent(BT)这种大量占用带宽的P2P协议,Linux内核提供...

    Linux内核完全注释V3.0_linux内核_linux_

    4. **网络堆栈**:从硬件接口到应用层协议的整个网络传输流程,如TCP/IP协议族、套接字API、网络设备驱动程序以及流量控制策略。 5. **设备驱动**:内核如何与硬件交互,驱动程序的工作原理,包括字符设备、块设备...

    深入分析Linux内核源码

    通过分析源码,我们可以了解到数据包的接收与发送过程,理解TCP连接的建立与断开、拥塞控制、流量控制等机制,这对于网络编程和网络故障排查非常有帮助。 此外,Linux内核还涉及中断处理、设备驱动、I/O管理等多个...

    基于Linux内核扩展模块的P2P流量控制.pdf

    【基于Linux内核扩展模块的P2P流量控制】这篇文献主要探讨了如何在Linux操作系统中,通过内核扩展模块来实现对P2P流量的有效控制。P2P(Peer-to-Peer)技术的兴起改变了互联网的中心化结构,使得资源分享更为便捷,...

    基于Linux内核扩展模块的P2P流量控制

    基于Linux内核扩展模块的P2P流量控制

    基于Linux LQL流量控制系统的研究与实现.pdf

    基于LQL库的流量控制方法可以直接在Linux内核的框架下实现,而不需要使用传统方法中的TC命令解析、netlink传输和内核空间执行的三层结构。这可以提高流量控制的效率和可靠性,同时也可以减少流量控制的延迟和资源...

    Linux内核修炼之道精华版

    书中的内容涵盖了从内核基础到高级技术的方方面面,为那些希望提升Linux内核理解和开发能力的读者提供了宝贵的资源。在本文中,我们将探讨几个关键的知识点,包括Linux内核的基本结构、进程管理、内存管理和设备驱动...

    Linux内核源码(2.6.24)

    2.6.24版本在网络方面加强了IPv6的支持,并改进了网络流量控制算法。 6. **安全与权限管理**:Linux内核采用了用户和组的概念,通过权限系统(如chmod、chown等)来控制文件访问。此外,还有SELinux(Security-...

    深入理解linux内核word版本

    接着,作者深入剖析了网络设备数据结构net_device,它包含了设备的配置信息、统计信息、状态标志以及各种管理列表和流量控制字段,这些细节揭示了网络设备如何在内核中被抽象和管理。 通过以上内容,我们可以看到,...

    Linux内核情景分析(上下全集高清版)

    它处理网络数据的接收和发送,进行网络层的路由选择,以及传输层的拥塞控制和流量控制。 5. **设备驱动**:设备驱动程序是内核与硬件之间的桥梁,使得内核能够控制硬件设备。Linux内核支持大量设备驱动,包括块设备...

    linux高级路由和流量控制HOWTO中文版(牛老师译)

    在Linux操作系统中,高级路由和流量控制是网络管理员和系统管理员必须掌握的关键技能。这篇文档“Linux高级路由和流量控制HOWTO中文版”由牛老师翻译,为读者提供了深入理解这些概念的宝贵资源。以下是对其中核心...

    基于Linux的网络流量控制机制

    该模型内置于Linux内核中,并利用队列算法对不同服务质量(Quality of Service, QoS)需求的数据流进行分类,以提供灵活且差异化的服务。实验结果表明,采用该流量控制模型后,网络性能显著提高,并能很好地适应未来...

    《Linux内核源码剖析 TCP IP实现(上册) 樊东东 莫澜 pdf扫描版.

    同时,还会讨论TCP的流量控制和拥塞控制机制,如滑动窗口、慢启动、快速重传和快速恢复算法等,这些都是保证网络通信质量和效率的关键。 其次,关于IP协议,书里会涉及IP地址、子网掩码、路由选择等概念,以及IP分...

    TC(linux下流量控制工具)详细说明及应用实例借鉴.pdf

    TC 工具基于 Linux 内核的网络设备驱动程序,通过对网络设备的控制,来实现流量控制。TC 的工作原理可以分为以下三个阶段: 1. 流量控制方式:TC 提供了多种流量控制方式,包括 Token Bucket Filter(TBF)、...

    TC(linux下流量控制工具)详细说明及应用实例.pdf

    TC(Linux 下流量控制工具)详细说明及应用实例 TC 是 Linux 下的一种流量控制工具,用于控制和管理网络流量。它提供了一个灵活的方式来管理网络带宽、延迟和丢包率等网络性能参数,以满足不同应用场景的需求。 TC...

    linux内核协议栈源码解析(2.6.18内核)

    2. **TCP/IP协议**:在传输层,TCP(传输控制协议)提供可靠的数据传输服务,通过确认、重传和流量控制确保数据的完整性和顺序。IP(互联网协议)在网络层负责数据包的路由和分片,是互联网的基础协议。 3. **套接...

    linux内核中sock和socket数据结构

    Linux内核中的sock和socket数据结构是网络编程的核心组成部分,它们是实现网络通信的基础构件。在Linux操作系统中,网络通信的实现依赖于BSD套接字接口,而这一接口在内核中是通过sock和socket数据结构来实现的。 ...

Global site tag (gtag.js) - Google Analytics