Linux内核中流量控制(1)

引自:http://cxw06023273.iteye.com/blog/867318


本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性, 

严禁用于任何商业用途。 
msn: yfydz_no1@hotmail.com 
来源:http://yfydz.cublog.cn 

Java代码   收藏代码
  1. 1. 前言  
  2.   
  3. linux内核中提供了流量控制的相关处理功能,相关代码在net/sched目录下;而应用层上的控制是通  
  4. 过iproute2软件包中的tc来实现,tc和sched的关系就好象iptables和netfilter的关系一样,一个是  
  5. 用户层接口,一个是具体实现,关于tc的使用方法可详将Linux Advanced Routing HOWTO,本文主要  
  6. 分析内核中的具体实现。  
  7.   
  8. 流控包括几个部分: 流控算法, 通常在net/sched/sch_*.c中实现, 缺省的是FIFO, 是比较典型的黑  
  9. 盒模式, 对外只看到入队和出对两个操作; 流控结构的操作处理; 和用户空间的控制接口, 是通过  
  10. rtnetlink实现的。  
  11.   
  12. 以下内核代码版本为2.6.19.2。  
  13.   
  14. 2. 控制入口  
  15.   
  16. 2.1 控制入口  
  17.   
  18. linux流控功能反映为网卡设备的属性,表明是网络最底层的处理部分, 已经和上层的网络协议栈无  
  19. 关了:  
  20.   
  21. /* include/linux/netdevice.h */  
  22. struct net_device  
  23. {  
  24. ......  
  25. /* 
  26.  * Cache line mostly used on queue transmit path (qdisc) 
  27.  */  
  28.  /* device queue lock */  
  29.  spinlock_t  queue_lock ____cacheline_aligned_in_smp;  
  30. // 这是发送数据时的队列处理  
  31.  struct Qdisc  *qdisc;  
  32. // 网卡停止时保存网卡活动时的队列处理方法  
  33.  struct Qdisc  *qdisc_sleeping;  
  34. // 网卡处理的数据队列链表  
  35.  struct list_head qdisc_list;  
  36. // 最大队列长度  
  37.  unsigned long  tx_queue_len; /* Max frames per queue allowed */  
  38.  /* Partially transmitted GSO packet. */  
  39.  struct sk_buff  *gso_skb;  
  40.  /* ingress path synchronizer */  
  41. // 输入流控锁  
  42.  spinlock_t  ingress_lock;  
  43. // 这是对于接收数据时的队列处理  
  44.  struct Qdisc  *qdisc_ingress;  
  45. ......  
  46.   
  47. 2.1.2 输出流控  
  48.   
  49. 数据发出流控处理时,上层的所有处理已经完成,数据包已经交到网卡设备进行发送,在数据发送时  
  50. 进行相关的流控处理网络数据的出口函数为dev_queue_xmit(); 如果是接收流控, 数据只是刚从网卡  
  51. 设备中收到, 还未交到网络上层处理, 不过网卡的输入流控不是必须的, 缺省情况下并不进行流控,  
  52. 输入流控入口函数为ing_filter()函数,该函数被skb_receive_skb()调用:  
  53.   
  54. /* net/core/dev.c */  
  55. int dev_queue_xmit(struct sk_buff *skb)  
  56. {  
  57.  struct net_device *dev = skb->dev;  
  58.  struct Qdisc *q;  
  59.  int rc = -ENOMEM;  
  60. ......  
  61.  /* Updates of qdisc are serialized by queue_lock. 
  62.   * The struct Qdisc which is pointed to by qdisc is now a 
  63.   * rcu structure - it may be accessed without acquiring 
  64.   * a lock (but the structure may be stale.) The freeing of the 
  65.   * qdisc will be deferred until it's known that there are no 
  66.   * more references to it. 
  67.   * 
  68.   * If the qdisc has an enqueue function, we still need to 
  69.   * hold the queue_lock before calling it, since queue_lock 
  70.   * also serializes access to the device queue. 
  71.   */  
  72. // 获取网卡的qdisc指针, 此出不需要锁, 是各个CPU的私有数据  
  73.  q = rcu_dereference(dev->qdisc);  
  74. #ifdef CONFIG_NET_CLS_ACT  
  75.  skb->tc_verd = SET_TC_AT(skb->tc_verd,AT_EGRESS);  
  76. #endif  
  77. // 如果队列输入非空, 将数据包入队  
  78. // 对于物理网卡设备, 缺省使用的是FIFO qdisc, 该成员函数非空, 只有逻辑网卡  
  79. // 才可能为空  
  80.  if (q->enqueue) {  
  81.   /* Grab device queue */  
  82. // 加锁  
  83.   spin_lock(&dev->queue_lock);  
  84. // 可以直接访问dev->qdisc了  
  85.   q = dev->qdisc;  
  86.   if (q->enqueue) {  
  87. // 入队处理  
  88.    rc = q->enqueue(skb, q);  
  89. // 运行流控, 出队列操作  
  90.    qdisc_run(dev);  
  91.    spin_unlock(&dev->queue_lock);  
  92.    rc = rc == NET_XMIT_BYPASS ? NET_XMIT_SUCCESS : rc;  
  93.    goto out;  
  94.   }  
  95.   spin_unlock(&dev->queue_lock);  
  96.  }  
  97. ......  
  98. }  
  99.    
  100.   
  101. // 出队操作  
  102. static inline void qdisc_run(struct net_device *dev)  
  103. {  
  104.  if (!netif_queue_stopped(dev) &&  
  105.      !test_and_set_bit(__LINK_STATE_QDISC_RUNNING, &dev->state))  
  106.   __qdisc_run(dev);  
  107. }  
  108.   
  109. /* net/sched/sch_generic.c */  
  110. void __qdisc_run(struct net_device *dev)  
  111. {  
  112. // 如果是noop_qdisc流控, 实际是丢包  
  113.  if (unlikely(dev->qdisc == &noop_qdisc))  
  114.   goto out;  
  115.  while (qdisc_restart(dev) < 0 && !netif_queue_stopped(dev))  
  116.   /* NOTHING */;  
  117. out:  
  118.  clear_bit(__LINK_STATE_QDISC_RUNNING, &dev->state);  
  119. }  
  120.   
  121. /* Kick device. 
  122.    Note, that this procedure can be called by a watchdog timer, so that 
  123.    we do not check dev->tbusy flag here. 
  124.    Returns:  0  - queue is empty. 
  125.             >0  - queue is not empty, but throttled. 
  126.      <0  - queue is not empty. Device is throttled, if dev->tbusy != 0. 
  127.    NOTE: Called under dev->queue_lock with locally disabled BH. 
  128. */  
  129. static inline int qdisc_restart(struct net_device *dev)  
  130. {  
  131.  struct Qdisc *q = dev->qdisc;  
  132.  struct sk_buff *skb;  
  133.  /* Dequeue packet */  
  134. // 数据包出队  
  135.  if (((skb = dev->gso_skb)) || ((skb = q->dequeue(q)))) {  
  136.   unsigned nolock = (dev->features & NETIF_F_LLTX);  
  137.   dev->gso_skb = NULL;  
  138. ......  
  139. }  
  140. 2.1.3 输入流控  
  141.   
  142. 输入流控好象不是必须的,目前内核需要配置CONFIG_NET_CLS_ACT选项才起作用:  
  143.   
  144. /* net/core/dev.c */  
  145. int netif_receive_skb(struct sk_buff *skb)  
  146. {  
  147. ......  
  148. #ifdef CONFIG_NET_CLS_ACT  
  149.  if (pt_prev) {  
  150.   ret = deliver_skb(skb, pt_prev, orig_dev);  
  151.   pt_prev = NULL; /* noone else should process this after*/  
  152.  } else {  
  153.   skb->tc_verd = SET_TC_OK2MUNGE(skb->tc_verd);  
  154.  }  
  155.  ret = ing_filter(skb);  
  156.  if (ret == TC_ACT_SHOT || (ret == TC_ACT_STOLEN)) {  
  157.   kfree_skb(skb);  
  158.   goto out;  
  159.  }  
  160.  skb->tc_verd = 0;  
  161. ncls:  
  162. #endif  
  163. ......  
  164. }  
  165.   
  166. static int ing_filter(struct sk_buff *skb)  
  167. {  
  168.  struct Qdisc *q;  
  169.  struct net_device *dev = skb->dev;  
  170.  int result = TC_ACT_OK;  
  171. // 如果网卡设备有输入流控处理  
  172.  if (dev->qdisc_ingress) {  
  173.   __u32 ttl = (__u32) G_TC_RTTL(skb->tc_verd);  
  174.   if (MAX_RED_LOOP < ttl++) {  
  175.    printk(KERN_WARNING "Redir loop detected Dropping packet (%s->%  
  176. s)\n",  
  177.     skb->input_dev->name, skb->dev->name);  
  178.    return TC_ACT_SHOT;  
  179.   }  
  180. // 设置数据包的TC参数  
  181.   skb->tc_verd = SET_TC_RTTL(skb->tc_verd,ttl);  
  182.   skb->tc_verd = SET_TC_AT(skb->tc_verd,AT_INGRESS);  
  183.   spin_lock(&dev->ingress_lock);  
  184.   if ((q = dev->qdisc_ingress) != NULL)  
  185. // 数据入队  
  186.    result = q->enqueue(skb, q);  
  187.   spin_unlock(&dev->ingress_lock);  
  188.  }  
  189.  return result;  
  190. }  
  191.   
  192. 2.2 初始化  
  193.   
  194. 本节先跟踪一下网卡设备的qdisc指针是如何被赋值的, 缺省赋值为何值.  
  195.   
  196. 在网卡设备的初始化函数register_netdevice()函数中调用dev_init_scheduler()函数对网卡设备的  
  197. 流控队列处理进行了初始化, 也就是说每个网络网卡设备的qdisc指针都不会是空的:  
  198. /* net/sched/sch_gentric.c */  
  199. void dev_init_scheduler(struct net_device *dev)  
  200. {  
  201.  qdisc_lock_tree(dev);  
  202. // 处理发出数据的qdisc是必须的,而处理输入数据的qdisc_ingress则不是必须的  
  203. // 缺省情况下的qdisc  
  204.  dev->qdisc = &noop_qdisc;  
  205.  dev->qdisc_sleeping = &noop_qdisc;  
  206.  INIT_LIST_HEAD(&dev->qdisc_list);  
  207.  qdisc_unlock_tree(dev);  
  208.  dev_watchdog_init(dev);  
  209. }  
  210.   
  211. 当然noop_qdisc中的调度是不可用的, 只进行丢包处理;网卡在激活(dev_open)时会调用  
  212. dev_activate()函数重新对qdisc指针赋值,但未对qdisc_ingress赋值:  
  213. /* net/sched/sch_generic.c */  
  214. void dev_activate(struct net_device *dev)  
  215. {  
  216.  /* No queueing discipline is attached to device; 
  217.     create default one i.e. pfifo_fast for devices, 
  218.     which need queueing and noqueue_qdisc for 
  219.     virtual interfaces 
  220.   */  
  221. // 如果当前的qdisc_sleeping是noop_qdisc,重新找一个流控操作指针  
  222.  if (dev->qdisc_sleeping == &noop_qdisc) {  
  223.   struct Qdisc *qdisc;  
  224. // 前提条件是发送队列长度非0, 这正常情况肯定满足的  
  225.   if (dev->tx_queue_len) {  
  226. // 对dev设备建立fifo处理, 只是缺省的网卡发送策略: FIFO, 先入先出  
  227.    qdisc = qdisc_create_dflt(dev, &pfifo_fast_ops);  
  228.    if (qdisc == NULL) {  
  229.     printk(KERN_INFO "%s: activation failed\n", dev->name);  
  230.     return;  
  231.    }  
  232.    write_lock(&qdisc_tree_lock);  
  233.    list_add_tail(&qdisc->list, &dev->qdisc_list);  
  234.    write_unlock(&qdisc_tree_lock);  
  235.   } else {  
  236.    qdisc =  &noqueue_qdisc;  
  237.   }  
  238.   write_lock(&qdisc_tree_lock);  
  239. // 对qdisc_sleeping赋值  
  240.   dev->qdisc_sleeping = qdisc;  
  241.   write_unlock(&qdisc_tree_lock);  
  242.  }  
  243. // 如果现在网线没插, 返回  
  244.  if (!netif_carrier_ok(dev))  
  245.   /* Delay activation until next carrier-on event */  
  246.   return;  
  247.  spin_lock_bh(&dev->queue_lock);  
  248. // 将网卡当前的qdisc赋值为qdisc_sleeping所指的qdisc  
  249.  rcu_assign_pointer(dev->qdisc, dev->qdisc_sleeping);  
  250.  if (dev->qdisc != &noqueue_qdisc) {  
  251. // 启动  
  252.   dev->trans_start = jiffies;  
  253.   dev_watchdog_up(dev);  
  254.  }  
  255.  spin_unlock_bh(&dev->queue_lock);  
  256. }  
  257.   
  258. qdisc_sleeping用于保存在网卡起效时使用的qdisc, 因为在网卡down或网线拔除不可用时, 网卡设  
  259. 备的qdisc指针会指向noqueue_qdisc, 该qdisc操作就只是丢包, 这就是为什么在没插网线情况下也  
  260. 可以调用发包函数处理的原因, 结果就是直接丢包。  
  261. /* net/sched/sch_generic.c */  
  262. void dev_deactivate(struct net_device *dev)  
  263. {  
  264.  struct Qdisc *qdisc;  
  265.  spin_lock_bh(&dev->queue_lock);  
  266.  qdisc = dev->qdisc;  
  267. // 将网卡当前qdisc设置为noop_qdisc  
  268.  dev->qdisc = &noop_qdisc;  
  269. // 释放原来的qdisc  
  270.  qdisc_reset(qdisc);  
  271.  spin_unlock_bh(&dev->queue_lock);  
  272.  dev_watchdog_down(dev);  
  273.  /* Wait for outstanding dev_queue_xmit calls. */  
  274.  synchronize_rcu();  
  275.  /* Wait for outstanding qdisc_run calls. */  
  276.  while (test_bit(__LINK_STATE_QDISC_RUNNING, &dev->state))  
  277.   yield();  
  278.  if (dev->gso_skb) {  
  279.   kfree_skb(dev->gso_skb);  
  280.   dev->gso_skb = NULL;  
  281.  }  
  282. }  
  283.    
  284. 3. 数据结构  
  285.   
  286. 流控处理对外表现是一个黑盒,外部只能看到数据入队和出队,但内部队列是如何操作和管理外面是  
  287. 不知道的;另外处理队列处理外,流控还有一个调度器,该调度器将数据进行分类,然后对不同类型  
  288. 的数据采取不同的流控处理,所分的类型可能是多级的,形成一个树型的分类树。  
  289.   
  290. 流控的基本数据结构是struct Qdisc(queueing discipline,直译是“排队纪律”,意译为“流控”  
  291. ),这是内核中为数不多的以大写字母开头结构名称之一:  
  292. /* include/net/sch_generic.h */  
  293. struct Qdisc  
  294. {  
  295. // 入队操作  
  296.  int    (*enqueue)(struct sk_buff *skb, struct Qdisc *dev);  
  297. // 出队操作  
  298.  struct sk_buff * (*dequeue)(struct Qdisc *dev);  
  299. // 标志  
  300.  unsigned  flags;  
  301. #define TCQ_F_BUILTIN 1  
  302. #define TCQ_F_THROTTLED 2  
  303. #define TCQ_F_INGRESS 4  
  304.  int   padded;  
  305. // Qdisc的基本操作结构  
  306.  struct Qdisc_ops *ops;  
  307. // 句柄  
  308.  u32   handle;  
  309.  u32   parent;  
  310.  atomic_t  refcnt;  
  311. // 数据包链表头  
  312.  struct sk_buff_head q;  
  313. // 网卡设备  
  314.  struct net_device *dev;  
  315.  struct list_head list;  
  316. // 统计信息  
  317.  struct gnet_stats_basic bstats;  
  318.  struct gnet_stats_queue qstats;  
  319. // 速率估计  
  320.  struct gnet_stats_rate_est rate_est;  
  321. // 流控锁  
  322.  spinlock_t  *stats_lock;  
  323.  struct rcu_head  q_rcu;  
  324.  int   (*reshape_fail)(struct sk_buff *skb,  
  325.      struct Qdisc *q);  
  326.  /* This field is deprecated, but it is still used by CBQ 
  327.   * and it will live until better solution will be invented. 
  328.   */  
  329. // 父节点, 但基本已经被淘汰了  
  330.  struct Qdisc  *__parent;  
  331. };  
  332.   
  333. 流控队列的基本操作结构:  
  334. struct Qdisc_ops  
  335. {  
  336. // 链表中的下一个  
  337.  struct Qdisc_ops *next;  
  338. // 类别操作结构  
  339.  struct Qdisc_class_ops *cl_ops;  
  340. // Qdisc的名称, 从数组大小看应该就是网卡名称  
  341.  char   id[IFNAMSIZ];  
  342. // 私有数据大小  
  343.  int   priv_size;  
  344. // 入队  
  345.  int    (*enqueue)(struct sk_buff *, struct Qdisc *);  
  346. // 出队  
  347.  struct sk_buff * (*dequeue)(struct Qdisc *);  
  348. // 将数据包重新排队  
  349.  int    (*requeue)(struct sk_buff *, struct Qdisc *);  
  350. // 丢弃  
  351.  unsigned int  (*drop)(struct Qdisc *);  
  352. // 初始化  
  353.  int   (*init)(struct Qdisc *, struct rtattr *arg);  
  354. // 复位为初始状态,释放缓冲,删除定时器,清空计数器  
  355.  void   (*reset)(struct Qdisc *);  
  356. // 释放  
  357.  void   (*destroy)(struct Qdisc *);  
  358. // 更改Qdisc参数  
  359.  int   (*change)(struct Qdisc *, struct rtattr *arg);  
  360. // 输出  
  361.  int   (*dump)(struct Qdisc *, struct sk_buff *);  
  362.  int   (*dump_stats)(struct Qdisc *, struct gnet_dump *);  
  363.  struct module  *owner;  
  364. };  
  365. 流控队列类别操作结构:  
  366. struct Qdisc_class_ops  
  367. {  
  368.  /* Child qdisc manipulation */  
  369. // 减子节点  
  370.  int   (*graft)(struct Qdisc *, unsigned long cl,  
  371.      struct Qdisc *, struct Qdisc **);  
  372. // 增加子节点  
  373.  struct Qdisc *  (*leaf)(struct Qdisc *, unsigned long cl);  
  374.  /* Class manipulation routines */  
  375. // 获取, 增加使用计数  
  376.  unsigned long  (*get)(struct Qdisc *, u32 classid);  
  377. // 释放, 减少使用计数  
  378.  void   (*put)(struct Qdisc *, unsigned long);  
  379. // 改变  
  380.  int   (*change)(struct Qdisc *, u32, u32,  
  381.      struct rtattr **, unsigned long *);  
  382. // 删除  
  383.  int   (*delete)(struct Qdisc *, unsigned long);  
  384. // 遍历  
  385.  void   (*walk)(struct Qdisc *, struct qdisc_walker * arg);  
  386.  /* Filter manipulation */  
  387.  struct tcf_proto ** (*tcf_chain)(struct Qdisc *, unsigned long);  
  388. // tc捆绑  
  389.  unsigned long  (*bind_tcf)(struct Qdisc *, unsigned long,  
  390.      u32 classid);  
  391. // tc解除  
  392.  void   (*unbind_tcf)(struct Qdisc *, unsigned long);  
  393.  /* rtnetlink specific */  
  394. // 输出  
  395.  int   (*dump)(struct Qdisc *, unsigned long,  
  396.      struct sk_buff *skb, struct tcmsg*);  
  397.  int   (*dump_stats)(struct Qdisc *, unsigned long,  
  398.      struct gnet_dump *);  
  399. };  
  400. // 流控速率控制表结构  
  401. struct qdisc_rate_table  
  402. {  
  403.  struct tc_ratespec rate;  
  404.  u32  data[256];  
  405.  struct qdisc_rate_table *next;  
  406.  int  refcnt;  
  407. };  
  408.   
  409. 4. 基本操作  
  410.   
  411. 各种流控算法是通过流控操作结构实现,然后这些操作结构登记到内核的流控链表,在使用时可为网  
  412. 卡构造新的流控结构,将该结构和某种流控操作结构挂钩,这样就实现网卡采用某种策略发送数据进  
  413. 行流控,所有操作在用户空间都可通过tc程序设置。  
  414.   
  415. 4.1 Qdisc的一些基本操作  
  416.   
  417. 4.1.1 分配新的流控结构  
  418. /* net/sched/sch_generic.c */  
  419. // 分配新的Qdisc结构, Qdisc的操作结构由函数参数指定  
  420. struct Qdisc *qdisc_alloc(struct net_device *dev, struct Qdisc_ops *ops)  
  421. {  
  422.  void *p;  
  423.  struct Qdisc *sch;  
  424.  unsigned int size;  
  425.  int err = -ENOBUFS;  
  426.  /* ensure that the Qdisc and the private data are 32-byte aligned */  
  427. // Qdisc空间按32字节对齐  
  428.  size = QDISC_ALIGN(sizeof(*sch));  
  429. // 增加私有数据空间  
  430.  size += ops->priv_size + (QDISC_ALIGNTO - 1);  
  431.  p = kzalloc(size, GFP_KERNEL);  
  432.  if (!p)  
  433.   goto errout;  
  434. // 确保从缓冲区中的sch到缓冲区结束点空间是32字节对齐的  
  435.  sch = (struct Qdisc *) QDISC_ALIGN((unsigned long) p);  
  436. // 填充字节的数量  
  437.  sch->padded = (char *) sch - (char *) p;  
  438. //  +------------------------------------+  
  439. //  |________|___________________________|  
  440. //  ^        ^                           ^  
  441. //  |  pad   | <------ 32*N ------------>|  
  442. //  p        sch                           
  443.   
  444. // 初始化链表, 将用于挂接到dev的Qdisc链表  
  445.  INIT_LIST_HEAD(&sch->list);  
  446. // 初始化数据包链表  
  447.  skb_queue_head_init(&sch->q);  
  448. // Qdisc结构参数  
  449.  sch->ops = ops;  
  450.  sch->enqueue = ops->enqueue;  
  451.  sch->dequeue = ops->dequeue;  
  452.  sch->dev = dev;  
  453. // 网卡使用计数增加  
  454.  dev_hold(dev);  
  455.  sch->stats_lock = &dev->queue_lock;  
  456.  atomic_set(&sch->refcnt, 1);  
  457.  return sch;  
  458. errout:  
  459.  return ERR_PTR(-err);  
  460. }  
  461.   
  462. struct Qdisc * qdisc_create_dflt(struct net_device *dev, struct Qdisc_ops *ops)  
  463. {  
  464.  struct Qdisc *sch;  
  465. // 分配Qdisc结构   
  466.  sch = qdisc_alloc(dev, ops);  
  467.  if (IS_ERR(sch))  
  468.   goto errout;  
  469. // 如果没有初始化函数或者初始化成功, 返回Qdisc结构  
  470.  if (!ops->init || ops->init(sch, NULL) == 0)  
  471.   return sch;  
  472. // 初始化失败, 释放Qdisc  
  473.  qdisc_destroy(sch);  
  474. errout:  
  475.  return NULL;  
  476. }  
  477. /* Under dev->queue_lock and BH! */  
  478. // 调用Qdisc操作函数中的reset函数  
  479. void qdisc_reset(struct Qdisc *qdisc)  
  480. {  
  481.  struct Qdisc_ops *ops = qdisc->ops;  
  482.  if (ops->reset)  
  483.   ops->reset(qdisc);  
  484. }  
  485. /* this is the rcu callback function to clean up a qdisc when there 
  486.  * are no further references to it */  
  487. // 真正释放Qdisc缓冲区  
  488. static void __qdisc_destroy(struct rcu_head *head)  
  489. {  
  490.  struct Qdisc *qdisc = container_of(head, struct Qdisc, q_rcu);  
  491. // qdisc-padded就是缓冲区头的位置  
  492.  kfree((char *) qdisc - qdisc->padded);  
  493. }  
  494. /* Under dev->queue_lock and BH! */  
  495. // 释放Qdisc  
  496. void qdisc_destroy(struct Qdisc *qdisc)  
  497. {  
  498.  struct Qdisc_ops  *ops = qdisc->ops;  
  499. // 检查Qdisc的使用计数  
  500.  if (qdisc->flags & TCQ_F_BUILTIN ||  
  501.      !atomic_dec_and_test(&qdisc->refcnt))  
  502.   return;  
  503. // 将Qdisc从网卡设备的Qdisc链表中断开  
  504.  list_del(&qdisc->list);  
  505. #ifdef CONFIG_NET_ESTIMATOR  
  506.  gen_kill_estimator(&qdisc->bstats, &qdisc->rate_est);  
  507. #endif  
  508. // 复位操作  
  509.  if (ops->reset)  
  510.   ops->reset(qdisc);  
  511. // 内部释放操作  
  512.  if (ops->destroy)  
  513.   ops->destroy(qdisc);  
  514. // 减少操作结构的模块计数  
  515.  module_put(ops->owner);  
  516. // 减少网卡使用计数  
  517.  dev_put(qdisc->dev);  
  518. // 对每个CPU的数据进行具体空间释放  
  519.  call_rcu(&qdisc->q_rcu, __qdisc_destroy);  
  520. }  
  521.   
  522. /* include/net/sch_generic.h */  
  523. // 将skb包添加到数据队列最后  
  524. static inline int __qdisc_enqueue_tail(struct sk_buff *skb, struct Qdisc *sch,  
  525.            struct sk_buff_head *list)  
  526. {  
  527. // 将数据包连接到数据包链表尾  
  528.  __skb_queue_tail(list, skb);  
  529. // 更新统计信息  
  530. // 当前队列中数据包的数据长度增加  
  531.  sch->qstats.backlog += skb->len;  
  532. // Qdisc处理的数据包数字节数增加  
  533.  sch->bstats.bytes += skb->len;  
  534.  sch->bstats.packets++;  
  535.  return NET_XMIT_SUCCESS;  
  536. }  
  537. static inline int qdisc_enqueue_tail(struct sk_buff *skb, struct Qdisc *sch)  
  538. {  
  539.  return __qdisc_enqueue_tail(skb, sch, &sch->q);  
  540. }  
  541. // 将队列头的数据包出队列  
  542. static inline struct sk_buff *__qdisc_dequeue_head(struct Qdisc *sch,  
  543.          struct sk_buff_head *list)  
  544. {  
  545. // 从skb链表中头skb包出队列  
  546.  struct sk_buff *skb = __skb_dequeue(list);  
  547. // 减少当前数据队列数据长度计数  
  548.  if (likely(skb != NULL))  
  549.   sch->qstats.backlog -= skb->len;  
  550.  return skb;  
  551. }  
  552. static inline struct sk_buff *qdisc_dequeue_head(struct Qdisc *sch)  
  553. {  
  554.  return __qdisc_dequeue_head(sch, &sch->q);  
  555. }  
  556. // 将队列尾的数据包出队列  
  557. static inline struct sk_buff *__qdisc_dequeue_tail(struct Qdisc *sch,  
  558.          struct sk_buff_head *list)  
  559. {  
  560. // 从链表为提出数据包  
  561.  struct sk_buff *skb = __skb_dequeue_tail(list);  
  562.  if (likely(skb != NULL))  
  563.   sch->qstats.backlog -= skb->len;  
  564.  return skb;  
  565. }  
  566. static inline struct sk_buff *qdisc_dequeue_tail(struct Qdisc *sch)  
  567. {  
  568.  return __qdisc_dequeue_tail(sch, &sch->q);  
  569. }  
  570. // 将数据包重新入队  
  571. static inline int __qdisc_requeue(struct sk_buff *skb, struct Qdisc *sch,  
  572.       struct sk_buff_head *list)  
  573. {  
  574. // 添加到队列头  
  575.  __skb_queue_head(list, skb);  
  576. // 增加队列数据长度计数, 但不增加Qdisc处理的数据包数字节数  
  577.  sch->qstats.backlog += skb->len;  
  578.  sch->qstats.requeues++;  
  579.  return NET_XMIT_SUCCESS;  
  580. }  
  581. static inline int qdisc_requeue(struct sk_buff *skb, struct Qdisc *sch)  
  582. {  
  583.  return __qdisc_requeue(skb, sch, &sch->q);  
  584. }  
  585. // 复位Qdisc队列  
  586. static inline void __qdisc_reset_queue(struct Qdisc *sch,  
  587.            struct sk_buff_head *list)  
  588. {  
  589.  /* 
  590.   * We do not know the backlog in bytes of this list, it 
  591.   * is up to the caller to correct it 
  592.   */  
  593. // 释放Qdisc当前数据包队列中的所有数据包  
  594.  skb_queue_purge(list);  
  595. }  
  596. static inline void qdisc_reset_queue(struct Qdisc *sch)  
  597. {  
  598.  __qdisc_reset_queue(sch, &sch->q);  
  599.  sch->qstats.backlog = 0;  
  600. }  
  601. // 丢弃Qdisc数据队列尾的数据包  
  602. static inline unsigned int __qdisc_queue_drop(struct Qdisc *sch,  
  603.            struct sk_buff_head *list)  
  604. {  
  605. // 取队列尾数据包  
  606.  struct sk_buff *skb = __qdisc_dequeue_tail(sch, list);  
  607.  if (likely(skb != NULL)) {  
  608. // 释放该数据包  
  609.   unsigned int len = skb->len;  
  610.   kfree_skb(skb);  
  611.   return len;  
  612.  }  
  613.  return 0;  
  614. }  
  615. static inline unsigned int qdisc_queue_drop(struct Qdisc *sch)  
  616. {  
  617.  return __qdisc_queue_drop(sch, &sch->q);  
  618. }  
  619. // 丢弃数据包  
  620. static inline int qdisc_drop(struct sk_buff *skb, struct Qdisc *sch)  
  621. {  
  622. // 释放数据包  
  623.  kfree_skb(skb);  
  624. // 丢包计数增加  
  625.  sch->qstats.drops++;  
  626.  return NET_XMIT_DROP;  
  627. }  
  628. // 整形失败丢包  
  629. static inline int qdisc_reshape_fail(struct sk_buff *skb, struct Qdisc *sch)  
  630. {  
  631.  sch->qstats.drops++;  
  632. #ifdef CONFIG_NET_CLS_POLICE  
  633.  if (sch->reshape_fail == NULL || sch->reshape_fail(skb, sch))  
  634.   goto drop;  
  635.  return NET_XMIT_SUCCESS;  
  636. drop:  
  637. #endif  
  638.  kfree_skb(skb);  
  639.  return NET_XMIT_DROP;  
  640. }  
  641.   
  642. /* net/sched/sch_api.c */  
  643. /* We know handle. Find qdisc among all qdisc's attached to device 
  644.    (root qdisc, all its children, children of children etc.) 
  645.  */  
  646. // 根据句柄查找Qdisc, 句柄是个32位整数用于标识Qdisc的  
  647. struct Qdisc *qdisc_lookup(struct net_device *dev, u32 handle)  
  648. {  
  649.  struct Qdisc *q;  
  650.  read_lock(&qdisc_tree_lock);  
  651. // 遍历dev设备Qdisc链表  
  652.  list_for_each_entry(q, &dev->qdisc_list, list) {  
  653. // 句柄相同,返回Qdisc  
  654.   if (q->handle == handle) {  
  655.    read_unlock(&qdisc_tree_lock);  
  656.    return q;  
  657.   }  
  658.  }  
  659.  read_unlock(&qdisc_tree_lock);  
  660.  return NULL;  
  661. }  
  662. // 返回指定类别的Qdisc叶节点  
  663. static struct Qdisc *qdisc_leaf(struct Qdisc *p, u32 classid)  
  664. {  
  665.  unsigned long cl;  
  666.  struct Qdisc *leaf;  
  667. // Qdisc类别操作  
  668.  struct Qdisc_class_ops *cops = p->ops->cl_ops;  
  669.  if (cops == NULL)  
  670.   return NULL;  
  671. // 获取指定classid类型的类别句柄  
  672.  cl = cops->get(p, classid);  
  673.  if (cl == 0)  
  674.   return NULL;  
  675. // 调用类别操作结构的left成员函数获取叶Qdisc节点  
  676.  leaf = cops->leaf(p, cl);  
  677.  cops->put(p, cl);  
  678.  return leaf;  
  679. }  
  680.    
  681. /* Graft qdisc "new" to class "classid" of qdisc "parent" or 
  682.    to device "dev". 
  683.    Old qdisc is not destroyed but returned in *old. 
  684.  */  
  685. // "嫁接"Qdisc, 将新的Qdisc节点添加到父节点作为叶节点  
  686. static int qdisc_graft(struct net_device *dev, struct Qdisc *parent,  
  687.          u32 classid,  
  688.          struct Qdisc *new, struct Qdisc **old)  
  689. {  
  690.  int err = 0;  
  691.  struct Qdisc *q = *old;  
  692.   
  693.  if (parent == NULL) {  
  694. // 父qdisc节点为空, 将新节点作为dev的基本qdisc, 返回dev原来的老的qdisc  
  695.   if (q && q->flags&TCQ_F_INGRESS) {  
  696.    *old = dev_graft_qdisc(dev, q);  
  697.   } else {  
  698.    *old = dev_graft_qdisc(dev, new);  
  699.   }  
  700.  } else {  
  701. // 父qdisc非空情况  
  702. // 将使用Qdisc类操作结构中的相关成员函数来完成操作  
  703.   struct Qdisc_class_ops *cops = parent->ops->cl_ops;  
  704.   err = -EINVAL;  
  705.   if (cops) {  
  706. // 获取类别句柄值  
  707.    unsigned long cl = cops->get(parent, classid);  
  708.    if (cl) {  
  709. // 类别有效, 调用graft成员函数将新节点插入qdisc树中  
  710.     err = cops->graft(parent, cl, new, old);  
  711.     if (new)  
  712.      new->parent = classid;  
  713.     cops->put(parent, cl);  
  714.    }  
  715.   }  
  716.  }  
  717.  return err;  
  718. }  
  719.   
  720. /* Attach toplevel qdisc to device dev */  
  721. // 将qdisc作为顶层Qdisc节点附着于dev设备  
  722. static struct Qdisc *  
  723. dev_graft_qdisc(struct net_device *dev, struct Qdisc *qdisc)  
  724. {  
  725.  struct Qdisc *oqdisc;  
  726. // 如果网卡设备是启动的, 先停掉  
  727.  if (dev->flags & IFF_UP)  
  728.   dev_deactivate(dev);  
  729. // 加树锁  
  730.  qdisc_lock_tree(dev);  
  731. // 是处理输入的流控节点  
  732.  if (qdisc && qdisc->flags&TCQ_F_INGRESS) {  
  733.   oqdisc = dev->qdisc_ingress;  
  734.   /* Prune old scheduler */  
  735.   if (oqdisc && atomic_read(&oqdisc->refcnt) <= 1) {  
  736.    /* delete */  
  737.    qdisc_reset(oqdisc);  
  738.    dev->qdisc_ingress = NULL;  
  739.   } else {  /* new */  
  740.    dev->qdisc_ingress = qdisc;  
  741.   }  
  742.  } else {  
  743. // 是处理输入的流控节点  
  744. // 备份一下原来的流控Qdisc指针  
  745.   oqdisc = dev->qdisc_sleeping;  
  746.   /* Prune old scheduler */  
  747. // 如果老的Qdisc引用数不超过一个, 也就是最多只被当前设备dev所使用, 复位该Qdisc  
  748.   if (oqdisc && atomic_read(&oqdisc->refcnt) <= 1)  
  749.    qdisc_reset(oqdisc);  
  750.   /* ... and graft new one */  
  751. // 如果新qdisc是空的,用noop_qdisc  
  752.   if (qdisc == NULL)  
  753.    qdisc = &noop_qdisc;  
  754. // 将睡眠qdisc(dev启动时将使用的qdisc)赋值为新的qdisc  
  755.   dev->qdisc_sleeping = qdisc;  
  756.   dev->qdisc = &noop_qdisc;  
  757.  }  
  758.  qdisc_unlock_tree(dev);  
  759. // 激活网卡  
  760.  if (dev->flags & IFF_UP)  
  761.   dev_activate(dev);  
  762. // 返回dev设备的老的qdisc  
  763.  return oqdisc;  
  764. }  
  765.    
  766.   
  767. /* 
  768.    Allocate and initialize new qdisc. 
  769.    Parameters are passed via opt. 
  770.  */  
  771. // 在指定的网卡设备上创建新的Qdisc结构  
  772. static struct Qdisc *  
  773. qdisc_create(struct net_device *dev, u32 handle, struct rtattr **tca, int *errp)  
  774. {  
  775.  int err;  
  776.  struct rtattr *kind = tca[TCA_KIND-1];  
  777.  struct Qdisc *sch;  
  778.  struct Qdisc_ops *ops;  
  779. // 查找相关的Qdisc操作结构  
  780.  ops = qdisc_lookup_ops(kind);  
  781. #ifdef CONFIG_KMOD  
  782. // 如果没在当前内核中找到,加载相关名称的Qdisc操作内核模块  
  783.  if (ops == NULL && kind != NULL) {  
  784.   char name[IFNAMSIZ];  
  785.   if (rtattr_strlcpy(name, kind, IFNAMSIZ) < IFNAMSIZ) {  
  786.    /* We dropped the RTNL semaphore in order to 
  787.     * perform the module load.  So, even if we 
  788.     * succeeded in loading the module we have to 
  789.     * tell the caller to replay the request.  We 
  790.     * indicate this using -EAGAIN. 
  791.     * We replay the request because the device may 
  792.     * go away in the mean time. 
  793.     */  
  794.    rtnl_unlock();  
  795. // 加载模块  
  796.    request_module("sch_%s", name);  
  797.    rtnl_lock();  
  798. // 重新查找  
  799.    ops = qdisc_lookup_ops(kind);  
  800. // 如果找到还是会返回失败,不过错误号是EAGAIN,要求重新执行相关操作  
  801.    if (ops != NULL) {  
  802.     /* We will try again qdisc_lookup_ops, 
  803.      * so don't keep a reference. 
  804.      */  
  805.     module_put(ops->owner);  
  806.     err = -EAGAIN;  
  807.     goto err_out;  
  808.    }  
  809.   }  
  810.  }  
  811. #endif  
  812.  err = -ENOENT;  
  813.  if (ops == NULL)  
  814.   goto err_out;  
  815. // 分配新的Qdisc结构  
  816.  sch = qdisc_alloc(dev, ops);  
  817.  if (IS_ERR(sch)) {  
  818.   err = PTR_ERR(sch);  
  819.   goto err_out2;  
  820.  }  
  821.  if (handle == TC_H_INGRESS) {  
  822. // 是针对输入进行流控  
  823.   sch->flags |= TCQ_F_INGRESS;  
  824.   handle = TC_H_MAKE(TC_H_INGRESS, 0);  
  825.  } else if (handle == 0) {  
  826. // 分配个新的句柄  
  827.   handle = qdisc_alloc_handle(dev);  
  828.   err = -ENOMEM;  
  829.   if (handle == 0)  
  830.    goto err_out3;  
  831.  }  
  832.  sch->handle = handle;  
  833. // 调用Qdisc操作结构的初始化函数进行初始化  
  834.  if (!ops->init || (err = ops->init(sch, tca[TCA_OPTIONS-1])) == 0) {  
  835. #ifdef CONFIG_NET_ESTIMATOR  
  836.   if (tca[TCA_RATE-1]) {  
  837. // 创建预估器  
  838.    err = gen_new_estimator(&sch->bstats, &sch->rate_est,  
  839.       sch->stats_lock,  
  840.       tca[TCA_RATE-1]);  
  841.    if (err) {  
  842.     /* 
  843.      * Any broken qdiscs that would require 
  844.      * a ops->reset() here? The qdisc was never 
  845.      * in action so it shouldn't be necessary. 
  846.      */  
  847.     if (ops->destroy)  
  848.      ops->destroy(sch);  
  849.     goto err_out3;  
  850.    }  
  851.   }  
  852. #endif  
  853.   qdisc_lock_tree(dev);  
  854. // 将Qdisc结构添加到网卡设备的流控链表  
  855.   list_add_tail(&sch->list, &dev->qdisc_list);  
  856.   qdisc_unlock_tree(dev);  
  857.   return sch;  
  858.  }  
  859. err_out3:  
  860.  dev_put(dev);  
  861.  kfree((char *) sch - sch->padded);  
  862. err_out2:  
  863.  module_put(ops->owner);  
  864. err_out:  
  865.  *errp = err;  
  866.  return NULL;  
  867. }  
  868. /* Allocate an unique handle from space managed by kernel */  
  869. // 分配新句柄  
  870. static u32 qdisc_alloc_handle(struct net_device *dev)  
  871. {  
  872. // 最多循环65536次查找  
  873.  int i = 0x10000;  
  874. // 注意这是个静态量  
  875.  static u32 autohandle = TC_H_MAKE(0x80000000U, 0);  
  876.  do {  
  877. // 每次为句柄值增加一个增量  
  878.   autohandle += TC_H_MAKE(0x10000U, 0);  
  879. // 溢出处理  
  880.   if (autohandle == TC_H_MAKE(TC_H_ROOT, 0))  
  881.    autohandle = TC_H_MAKE(0x80000000U, 0);  
  882. // qdisc_lookup是根据句柄查找Qdisc结构,成功返回Qdisc结构指针, 失败返回NULL  
  883.  } while (qdisc_lookup(dev, autohandle) && --i > 0);  
  884.  return i>0 ? autohandle : 0;  
  885. }  
  886.   
  887. // 修改Qdisc参数  
  888. static int qdisc_change(struct Qdisc *sch, struct rtattr **tca)  
  889. {  
  890.  if (tca[TCA_OPTIONS-1]) {  
  891.   int err;  
  892.   if (sch->ops->change == NULL)  
  893.    return -EINVAL;  
  894. // 调用Qdisc操作结构的change函数完成修改参数工作  
  895.   err = sch->ops->change(sch, tca[TCA_OPTIONS-1]);  
  896.   if (err)  
  897.    return err;  
  898.  }  
  899. #ifdef CONFIG_NET_ESTIMATOR  
  900.  if (tca[TCA_RATE-1])  
  901.   gen_replace_estimator(&sch->bstats, &sch->rate_est,  
  902.    sch->stats_lock, tca[TCA_RATE-1]);  
  903. #endif  
  904.  return 0;  
  905. }  
  906.   
  907. 4.2 Qdisc操作结构的一些基本操作  
  908.   
  909. /* net/sched/sch_api.c */  
  910. // 登记Qdisc操作结构, 每种排队算法都是通过Qdisc操作结构实现的  
  911. int register_qdisc(struct Qdisc_ops *qops)  
  912. {  
  913.  struct Qdisc_ops *q, **qp;  
  914.  int rc = -EEXIST;  
  915.  write_lock(&qdisc_mod_lock);  
  916. // qdisc_base是全局变量, 系统的Qdisc操作结构链表头  
  917. // 遍历Qdisc操作链表  
  918.  for (qp = &qdisc_base; (q = *qp) != NULL; qp = &q->next)  
  919. // 如果ID相同, 返回已经存在错误  
  920.   if (!strcmp(qops->id, q->id))  
  921.    goto out;  
  922. // 如果操作结构中没有定义入队,出队和重入队操作的话, 用系统缺省的  
  923.  if (qops->enqueue == NULL)  
  924.   qops->enqueue = noop_qdisc_ops.enqueue;  
  925.  if (qops->requeue == NULL)  
  926.   qops->requeue = noop_qdisc_ops.requeue;  
  927.  if (qops->dequeue == NULL)  
  928.   qops->dequeue = noop_qdisc_ops.dequeue;  
  929. // 将结构节点添加到链表, 注意这里没使用内核里最常见的list链表操作  
  930. // 这是个单向链表  
  931.  qops->next = NULL;  
  932.  *qp = qops;  
  933.  rc = 0;  
  934. out:  
  935.  write_unlock(&qdisc_mod_lock);  
  936.  return rc;  
  937. }  
  938.   
  939. // 拆除Qdisc操作结构  
  940. int unregister_qdisc(struct Qdisc_ops *qops)  
  941. {  
  942.  struct Qdisc_ops *q, **qp;  
  943.  int err = -ENOENT;  
  944.  write_lock(&qdisc_mod_lock);  
  945. // 由于没有用list, 必须遍历链表找到节点在链表中的位置  
  946.  for (qp = &qdisc_base; (q=*qp)!=NULL; qp = &q->next)  
  947.   if (q == qops)  
  948.    break;  
  949.  if (q) {  
  950. // 将结构节点从链表中断开  
  951.   *qp = q->next;  
  952.   q->next = NULL;  
  953.   err = 0;  
  954.  }  
  955.  write_unlock(&qdisc_mod_lock);  
  956.  return err;  
  957. }  
  958. /* Find queueing discipline by name */  
  959. // 根据名称查找;流控操作结构  
  960. static struct Qdisc_ops *qdisc_lookup_ops(struct rtattr *kind)  
  961. {  
  962.  struct Qdisc_ops *q = NULL;  
  963.  if (kind) {  
  964.   read_lock(&qdisc_mod_lock);  
  965. // 遍历Qdisc操作链表  
  966.   for (q = qdisc_base; q; q = q->next) {  
  967. // 比较ID  
  968.    if (rtattr_strcmp(kind, q->id) == 0) {  
  969. // 增加Qdisc操作模块的计数  
  970.     if (!try_module_get(q->owner))  
  971.      q = NULL;  
  972.     break;  
  973.    }  
  974.   }  
  975.   read_unlock(&qdisc_mod_lock);  
  976.  }  
  977.  return q;  
  978. }  
  979.   
  980. ...... 待续 ......  
  981.   
  982. 发表于: 2007-07-21,修改于: 2007-07-28 21:28,已浏览6589次,有评论23条 推荐 投诉  
  983.     网友: 本站网友    时间:2007-08-16 11:02:10 IP地址:222.68.182.★  
  984.       
  985.   
  986. qdisc_sleeping用于保存在网卡起效时使用的qdisc, 因为在网卡down或网线拔除不可用时, 网卡设备的qdisc指针会指向noqueue_qdisc, 该qdisc操作就只是丢包, 这就是为什么在没插网线情况下也可以调用发包函数处理的原因, 结果就是直接丢包。  
  987.   
  988.   
  989.   
  990. noqueue_qdisc应该是noop_qdisc吧?我的msn:rtsovip@yahoo.com.cn  
  991.   
  992.   
  993.   
  994.   
  995.     网友: 本站网友    时间:2007-08-16 11:02:47 IP地址:222.68.182.★  
  996.       
  997.   
  998. 我的msn:rtosvip@yahoo.com.cn  
  999.   
  1000.   
  1001.     网友: yfydz   时间:2007-08-16 15:23:47 IP地址:218.247.216.★  
  1002.       
  1003.   
  1004. noqueue_qdisc和noop_qdisc不完全相同, noqueue_qdisc没有定义enqueue成员函数  
  1005.   
  1006.   
  1007.     网友: 本站网友    时间:2007-11-21 10:36:44 IP地址:202.198.16.★  
  1008.       
  1009.   
  1010. "输入流控好象不是必须的,目前内核需要配置CONFIG_NET_CLS_ACT选项才起作用"  
  1011.   
  1012.   
  1013.   
  1014. 想请教一下,想加载新的输入流控算法,比如说“RED”算法。我该在dev.c中的哪个函数里加载呢?不是很精通,请多多批评。  
  1015.   
  1016.   
  1017.     网友: yfydz   时间:2007-11-21 12:43:07 IP地址:218.247.216.★  
  1018.       
  1019.   
  1020. 不加在dev.c的,登记到sched系统就行,就可通过tc命令设置网卡所用的流控算法,如果名称是非标准的,可能需要改一下tc命令  
  1021.   
  1022.   
  1023.     网友: 本站网友    时间:2007-11-25 00:22:45 IP地址:59.72.63.★  
  1024.       
  1025.   
  1026. 可不可以详细介绍一下如何登记sched系统,及用tc命令设置网卡所用的流控算法RED  
  1027.   
  1028.   
  1029.     网友: yfydz   时间:2007-11-25 18:43:07 IP地址:123.118.7.★  
  1030.       
  1031.   
  1032. 整个系列里都有算法介绍,自己依葫芦画一下就行,至于tc,就自己看看吧,那个更简单  
  1033.   
  1034.   
  1035.     网友: 本站网友    时间:2007-11-28 11:28:35 IP地址:202.198.16.★  
  1036.       
  1037.   
  1038. 斑竹,你好.我想在linux\net\sched中添加一个新的QOS策略(非LINUX自带的),我该怎么做,大致步骤是怎样的?是不是需要重新编译内核,本人初识linux,理解能力较差,望斑竹能够耐心的写一下整个操作的详细过程.  
  1039.   
  1040.   
  1041.     网友: yfydz   时间:2007-11-29 08:46:56 IP地址:218.247.216.★  
  1042.       
  1043.   
  1044. 写个qdisc_ops,编译成模块挂到内核,修改tc...  
  1045.   
  1046. 不过既然是初识,别指望一口吃成个胖子,先看过APUE,LDD,ULK...以后再说  
  1047.   
  1048.   
  1049.     网友: 本站网友    时间:2008-03-22 12:08:34 IP地址:202.198.16.★  
  1050.       
  1051.   
  1052. 斑竹你好,想把red算法的平均队列,瞬时队列,以及时间作为日志输出到一文件中,却不知怎么下手,请指点....  
  1053.   
  1054.   
  1055.     网友: yfydz   时间:2008-03-25 14:48:00 IP地址:218.247.216.★  
  1056.       
  1057.   
  1058. 带个关键字printk,从日志文件中搜索该关键字  
  1059.   
  1060.   
  1061.     网友: 本站网友    时间:2008-04-12 19:53:14 IP地址:202.198.16.★  
  1062.       
  1063.   
  1064. 谢谢斑竹的回答,我现在已按照斑竹的意思添加了printk函数,但是在messages和dmesg中没有找到我所需要的平均队列,瞬时队列的值,请斑竹提醒下我该在red.c文件的什么位置分别添加什么命令才能分别输出瞬时队列,平均队列及时间(精确到微妙)到日志文件中,现在急切需要帮助,不胜感激。。。  
  1065.   
  1066.   
  1067.     网友: 本站网友    时间:2008-04-12 19:53:14 IP地址:202.198.16.★  
  1068.       
  1069.   
  1070. 谢谢斑竹的回答,我现在已按照斑竹的意思添加了printk函数,但是在messages和dmesg中没有找到我所需要的平均队列,瞬时队列的值,请斑竹提醒下我该在red.c文件的什么位置分别添加什么命令才能分别输出瞬时队列,平均队列及时间(精确到微妙)到日志文件中,现在急切需要帮助,不胜感激。。。  
  1071.   
  1072.   
  1073.     网友: yfydz   时间:2008-04-13 16:44:46 IP地址:58.31.243.★  
  1074.       
  1075.   
  1076. 每隔一行有效代码加一个printk...  
  1077.   
  1078.   
  1079.     网友: 本站网友    时间:2008-04-14 00:58:51 IP地址:202.98.13.★  
  1080.       
  1081.   
  1082. 我在linux源代码linux/net/sched/sch_red.c中有关瞬时队列,平均队列部分添加了printk也不知道位置正不正确(KERN_EMERG "MYLOG:%lu");(参数不知道该怎样添加,好象是个指针,不怎么懂,所以暂时没有添加参数)编译也成功了, 但是我没有在var/log/messages 下找到含有mlog(MYLOG)的信息呀?dmesg也没找到,有没有可能输出到其它地方去呢啊,是不是我的printk函数有语法错误啊,或则缺少头文件啊 ,还望斑竹指点,多谢  
  1083.   
  1084.   
  1085.     网友: 本站网友    时间:2008-04-14 00:58:57 IP地址:202.98.13.★  
  1086.       
  1087.   
  1088. 我在linux源代码linux/net/sched/sch_red.c中有关瞬时队列,平均队列部分添加了printk也不知道位置正不正确(KERN_EMERG "MYLOG:%lu");(参数不知道该怎样添加,好象是个指针,不怎么懂,所以暂时没有添加参数)编译也成功了, 但是我没有在var/log/messages 下找到含有mlog(MYLOG)的信息呀?dmesg也没找到,有没有可能输出到其它地方去呢啊,是不是我的printk函数有语法错误啊,或则缺少头文件啊 ,还望斑竹指点,多谢  
  1089.   
  1090.   
  1091.     网友: yfydz   时间:2008-04-21 21:09:02 IP地址:58.31.248.★  
  1092.       
  1093.   
  1094. 如果执行了就应该有的,没有就是没执行到那  
  1095.   
  1096.   
  1097.     网友: 本站网友    时间:2008-04-23 21:31:08 IP地址:202.198.16.★  
  1098.       
  1099.   
  1100. 谢谢斑竹指教  
  1101.   
  1102. 我 在red.c里面添加了printk("hello world!\n")已经输出出来拉,但是不知道在red.c程序什么位置以什么样的格式添加printk语句才能输出平均队列,瞬时队列和时间等参数的数值。。  
  1103.   
  1104. 还望斑竹指点  
  1105.   
  1106.   
  1107.     网友: 本站网友    时间:2008-04-23 21:31:10 IP地址:202.198.16.★  
  1108.       
  1109.   
  1110. 谢谢斑竹指教  
  1111.   
  1112. 我 在red.c里面添加了printk("hello world!\n")已经输出出来拉,但是不知道在red.c程序什么位置以什么样的格式添加printk语句才能输出平均队列,瞬时队列和时间等参数的数值。。  
  1113.   
  1114. 还望斑竹指点  
  1115.   
  1116.   
  1117.     网友: 本站网友    时间:2008-07-15 13:10:16 IP地址:219.239.50.★  
  1118.       
  1119.   
  1120. 请问斑竹, TC支持带宽保证么。 如果支持有相关文档推荐一下。谢谢了。   
  1121.   
  1122.   
  1123.     网友: yfydz   时间:2008-07-16 18:43:56 IP地址:58.31.248.★  
  1124.       
  1125.   
  1126. google  
  1127.   
  1128.   
  1129.     网友: 本站网友    时间:2009-02-07 16:56:37 IP地址:58.20.78.★  
  1130.       
  1131.   
  1132. 你好:  
  1133.   
  1134.      我现在向做一个全局限速的控制,不知道怎么反方向!请指点一下!谢谢  
  1135.   
  1136.   
  1137.     网友: 本站网友    时间:2010-01-26 16:55:36 IP地址:221.214.7.★  
  1138.       
  1139.   
  1140. 端木朋友:  
  1141.   
  1142.     你好,博客上拜读过你的一些文章,感觉很有收获。现在我想做一个用户态下的程序来实现一个简单的流控功能,不知道如何实现。我看过你的在内核中实现流控的文章。能给指点下思路吗?  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值