一、tc核心架构
linux tc采用模块化分层设计,核心组件包括:
- qdisc(排队规则):流量调度的基本单元(如
pfifo_fast、htb) - class(分类):qdisc内部的子队列(仅存在于分类型qdisc中)
- filter(过滤器):将流量分类到特定class(如
u32、fwmark) - policer(策略器):执行速率限制(如
tbf) - action(动作):对数据包执行操作(如
mirred重定向)
二、核心数据结构
1.qdisc结构体(net/sched/sch_generic.c)
struct qdisc {
int (*enqueue)(struct sk_buff *skb, struct qdisc *sch); // 入队操作
struct sk_buff* (*dequeue)(struct qdisc *sch); // 出队操作
struct qdisc_ops *ops; // qdisc操作函数集
struct netdev_queue *dev_queue; // 关联的网络设备队列
};
2.qdisc操作集(include/net/sch_generic.h)
struct qdisc_ops {
struct qdisc_ops *next;
const struct qdisc_class_ops *cl_ops; // class操作函数集
int (*enqueue)(struct sk_buff *, struct qdisc *);
struct sk_buff * (*dequeue)(struct qdisc *);
// ... 其他钩子函数(init, destroy, reset等)
};
3.filter结构体(net/sched/cls_api.c)
struct tcf_proto {
__be16 protocol; // 匹配的协议(如eth_p_ip)
struct tcf_proto_ops *ops; // filter操作函数集
struct tcf_result result; // 分类结果(指向class)
};
三、关键处理流程
1.数据包入队流程
graph td
a[数据包到达] --> b{设备是否启用tc?}
b -->|是| c[调用dev_queue_xmit()]
c --> d[执行__dev_xmit_skb()]
d --> e[调用sch_direct_xmit() -> qdisc->enqueue()]
e --> f[qdisc特定入队逻辑]
f --> g[按调度算法缓存/丢弃]
2.数据包出队调度
无分类qdisc(如pfifo):
static struct sk_buff *pfifo_fast_dequeue(struct qdisc *sch) {
struct sk_buff *skb = __qdisc_dequeue_head(&sch->q);
return skb;
}
分类型qdisc(如htb):
struct sk_buff *htb_dequeue(struct qdisc *sch) {
while ((skb = htb_do_dequeue(sch, prio, band)) != null) {
// 按类别优先级和令牌桶算法出队
}
}
四、经典qdisc实现分析
1.htb(hierarchical token bucket)
核心机制:
- 令牌桶按层次分配带宽
- 子类可借用父类空闲带宽
关键数据结构:
struct htb_class {
struct qdisc_class_common common;
struct psched_ratecfg rate; // 速率配置
struct psched_ratecfg ceil; // 上限配置
s64 tokens, ctokens; // 令牌计数
struct htb_class *parent; // 父类指针
};
2.netem(网络模拟器)
实现延迟/丢包/乱序:
static int netem_enqueue(struct sk_buff *skb, struct qdisc *sch) {
if (loss_condition) { // 按概率丢包
kfree_skb(skb);
return net_xmit_success;
}
if (delay_calculated) { // 计算延迟时间
tfifo = netem_skb_cb(skb);
tfifo->time_to_send = now + delay;
}
__qdisc_enqueue_tail(skb, &sch->q); // 加入延迟队列
}
五、filter与classifier机制
1.u32过滤器示例
static int u32_classify(struct sk_buff *skb, const struct tcf_proto *tp, struct tcf_result *res) {
struct tc_u32_key *key = tp->data;
if (skb->len < key->off + 4) // 检查偏移量是否有效
return -1;
if (*(u32*)(skb->data + key->off) == key->val) // 匹配关键值
res->classid = key->classid; // 设置分类id
}
2.ebpf集成(cls_bpf)
允许加载ebpf程序进行高级分类:
static int cls_bpf_classify(struct sk_buff *skb, const struct tcf_proto *tp, struct tcf_result *res) {
struct cls_bpf_prog *prog = tp->data;
int ret = bpf_prog_run(prog->filter, skb); // 执行ebpf程序
if (ret == tc_act_shot) return -1; // 丢弃包
res->classid = ret; // 设置分类id
}
六、tc配置接口(netlink)
用户空间工具:iproute2的tc命令
内核处理路径:
// net/sched/sch_api.c
static int tc_ctl_tfilter(struct sk_buff *skb, struct nlmsghdr *n) {
struct net *net = sock_net(skb->sk);
struct tcmsg *tcm = nlmsg_data(n);
struct net_device *dev = __dev_get_by_index(net, tcm->tcm_ifindex);
// 解析并调用qdisc/class/filter操作函数
}
七、性能优化机制
多队列qdisc (mq):
- 每个cpu核心一个队列,减少锁竞争
fq_codel (fair queuing with controlled delay):
- 使用流哈希分离流量
- 基于延迟的ecn标记
static struct sk_buff *fq_codel_dequeue(struct qdisc *sch) {
struct fq_codel_flow *flow;
list_for_each_entry(flow, &q->new_flows, flowchain) {
skb = flow->head;
if (codel_time_after(skb->tstamp, now)) // 检查是否需延迟
continue;
// ... 出队逻辑
}
}
八、调试与监控
tc统计信息:
tc -s qdisc show dev eth0
内核tracepoint:
perf record -e 'net:net_dev_queue' -e 'net:net_dev_xmit'
九、代码目录结构
net/sched/ ├── sch_generic.c // qdisc基础框架 ├── sch_htb.c // htb实现 ├── sch_netem.c // netem实现 ├── cls_api.c // filter框架 ├── cls_u32.c // u32分类器 ├── act_api.c // action框架 └── act_mirred.c // 重定向action
十、总结与挑战
优势:
- 灵活的分层流量控制
- 可扩展的模块化设计
挑战:
- 复杂配置导致学习曲线陡峭
- 单核处理瓶颈(部分qdisc未充分并行化)
- 与xdp/bpf等新技术的整合
通过深入分析可见,linux tc通过抽象qdisc/class/filter三层模型,实现了从简单fifo到复杂分层调度的灵活控制,其代码设计充分体现了unix的"组合小工具"哲学。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论