閱讀573 返回首頁    go 阿裏雲 go 技術社區[雲棲]


網絡子係統5_設備隊列規則

//	1.設備描述符與隊列規則相關的字段:
//		1.1 dev->tx_queue_len 指示驅動程序規則隊列的隊列長度,在注冊設備時使用,通知核心是否為設備提供隊列規則機製.
//			1.1.1 不適用隊列規則=0
//			1.1.2 使用隊列規則>0
//		1.2 dev->qdisc,執行設備傳輸時,qdisc_run,dev_queue_xmit始終通過該字段獲取設備當前使用的隊列規則。
//		1.3 dev->qdisc_sleep, 保存設備具備傳輸能力時,使用的設備隊列。

//	2.設備隊列規則設置的時機:
//		2.1 注冊設備(register_netdevice)時,設置為noop_qdisc
//		2.2 開啟設備(dev_open)時,創建新的隊列規則。
//		2.3 關閉設備(dev_close)時,設置dev->qdisc為noop_qdisc,表示在設備關閉的過程中,任何的傳輸都會被丟棄


//	初始化設備的隊列規則
//	調用路徑:register_netdevice->dev_init_scheduler
1.1 void dev_init_scheduler(struct net_device *dev)
{
	//獲取qdisc_tree_lock,dev->queue_lock,並關軟中斷
	qdisc_lock_tree(dev);
	//設置dev的隊列規則
	dev->qdisc = &noop_qdisc;
	dev->qdisc_sleeping = &noop_qdisc;
	INIT_LIST_HEAD(&dev->qdisc_list);
	//開鎖
	qdisc_unlock_tree(dev);
	//隊列看門狗
	dev_watchdog_init(dev);
}

//初始化看門狗
1.2 static void dev_watchdog_init(struct net_device *dev)
{
	//初始化定時器
	init_timer(&dev->watchdog_timer);
	dev->watchdog_timer.data = (unsigned long)dev;
	//定時器函數
	dev->watchdog_timer.function = dev_watchdog;
}

//	隊列規則使用的看門狗定時器
//	看門狗函數執行的條件:
//		1.設備在係統中
//		2.設備處於IFF_UP狀態
//		3.設備有載波
//		4.傳輸沒有被關閉
//		5,上一次傳輸距離現在已經超過了到期時間
1.3 static void dev_watchdog(unsigned long arg)
{
	struct net_device *dev = (struct net_device *)arg;

	//持有hard_start_xmit的保護鎖
	spin_lock(&dev->xmit_lock);
	//判斷如果設備的隊列規則不是noop_qdisc
	if (dev->qdisc != &noop_qdisc) {
		//檢查設備是否存在,_PRESENT標誌
		if (netif_device_present(dev) && netif_running(dev) && netif_carrier_ok(dev)) {
			if (netif_queue_stopped(dev) && (jiffies - dev->trans_start) > dev->watchdog_timeo) {
				printk(KERN_INFO "NETDEV WATCHDOG: %s: transmit timed out\n", dev->name);
				//執行注冊設備時提供的time_out函數
				dev->tx_timeout(dev);
			}
			//修改定時器的下一次到期時間
			if (!mod_timer(&dev->watchdog_timer, jiffies + dev->watchdog_timeo))
				//對非活躍的定時器修改到期時間,則同時增加對dev的引用計數
				dev_hold(dev);
		}
	}
	spin_unlock(&dev->xmit_lock);
	//釋放對dev的引用計數
	dev_put(dev);
}

//	noop隊列規則
//	所有操作,均將skb釋放掉
2.1 struct Qdisc noop_qdisc = {
	.enqueue	=	noop_enqueue,//skb入隊操作
	.dequeue	=	noop_dequeue,//skb出隊操作
	.flags		=	TCQ_F_BUILTIN,//表示內建的隊列規則
	.ops		=	&noop_qdisc_ops,//規則操作
	.list		=	LIST_HEAD_INIT(noop_qdisc.list),//鏈表頭
};



//	分配隊列規則:
//		當設備第一次被開啟式時,為設備創建隊列規則
//	參數:
//		ops為pfifo_fast_ops

//	調用路徑:dev_open->dev_activate->qdisc_create_dflt
3.1 struct Qdisc * qdisc_create_dflt(struct net_device *dev, struct Qdisc_ops *ops)
{
	void *p;
	struct Qdisc *sch;
	int size;
	//隊列規則對齊到32字節
	size = ((sizeof(*sch) + QDISC_ALIGN_CONST) & ~QDISC_ALIGN_CONST);
	size += ops->priv_size + QDISC_ALIGN_CONST;
	//分配內存
	p = kmalloc(size, GFP_KERNEL);
	if (!p)
		return NULL;
	memset(p, 0, size);
	//對齊到32字節
	sch = (struct Qdisc *)(((unsigned long)p + QDISC_ALIGN_CONST) 
			       & ~QDISC_ALIGN_CONST);
	//保存為對齊而浪費的字節
	sch->padded = (char *)sch - (char *)p;

	//初始化規則隊列鏈表頭,用於鏈接到dev->qdisc_list
	INIT_LIST_HEAD(&sch->list);
	//初始化設備的傳輸隊列
	skb_queue_head_init(&sch->q);
	//隊列操作
	sch->ops = ops;
	sch->enqueue = ops->enqueue;
	sch->dequeue = ops->dequeue;
	//此隊列關聯的設備
	sch->dev = dev;
	//增加設備引用計數
	dev_hold(dev);
	//隊列規則的統計變量鎖為設備的傳輸鎖
	//說明隊列的統計數據更新依賴於傳輸隊列
	sch->stats_lock = &dev->queue_lock;
	//隊列引用規則為1
	atomic_set(&sch->refcnt, 1);
	if (!ops->init || ops->init(sch, NULL) == 0)
		return sch;
	//
	dev_put(dev);
	kfree(p);
	return NULL;
}



//	關閉隊列規則
//	dev_close->dev_deactivate
3.2 void dev_deactivate(struct net_device *dev)
{
	struct Qdisc *qdisc;
	//關中斷,獲取隊列鎖
	spin_lock_bh(&dev->queue_lock);
	qdisc = dev->qdisc;
	dev->qdisc = &noop_qdisc;//將隊列規則修改為noop_qdisc,丟棄所有的傳輸
	//調用qdisc->reset操作,將隊列規則中所有未傳輸的skb丟棄掉
	qdisc_reset(qdisc);

	spin_unlock_bh(&dev->queue_lock);
	//關閉看門狗
	dev_watchdog_down(dev);
	//檢查設備是否被調度
	while (test_bit(__LINK_STATE_SCHED, &dev->state))
		yield();//將當前進程放到就緒隊列,切換到其他進程執行

	//等待dev->xmit_lock沒有被任何cpu占用,從而保證在dev_deactivate返回後,沒有任何cpu在dev上執行傳輸
	spin_unlock_wait(&dev->xmit_lock);
}



//	fifo隊列規則
4.1 static struct Qdisc_ops pfifo_fast_ops = {
	.next		=	NULL,
	.cl_ops		=	NULL,
	.id		=	"pfifo_fast",//規則id
	.priv_size	=	3 * sizeof(struct sk_buff_head),//三個skb鏈表頭
	.enqueue	=	pfifo_fast_enqueue,//入隊
	.dequeue	=	pfifo_fast_dequeue,//出隊
	.requeue	=	pfifo_fast_requeue,//重新入隊
	.init		=	pfifo_fast_init,//初始化
	.reset		=	pfifo_fast_reset,//重置
	.dump		=	pfifo_fast_dump,//dump
	.owner		=	THIS_MODULE,
};

//	fifo的入隊操作:
4.2 static int pfifo_fast_enqueue(struct sk_buff *skb, struct Qdisc* qdisc)
{
	//規則隊列中的隊列頭
	struct sk_buff_head *list = qdisc_priv(qdisc);
	//根據skb的優先級,計算skb應該進入的list頭
	list += prio2band[skb->priority&TC_PRIO_MAX];
	//如果當前列表頭中鏈接的skb個數<設備傳輸隊列的長度
	if (list->qlen < qdisc->dev->tx_queue_len) {
		//將skb掛在到此list最後
		__skb_queue_tail(list, skb);
		//增加隊列長度、隊列中數據byte數、輸出封包個數
		qdisc->q.qlen++;
		qdisc->bstats.bytes += skb->len;
		qdisc->bstats.packets++;
		return 0;
	}
	//該skb應該進入的隊列已滿,丟掉skb
	qdisc->qstats.drops++;
	kfree_skb(skb);
	return NET_XMIT_DROP;
}

// fifo出隊操作:
4.3 static struct sk_buff *pfifo_fast_dequeue(struct Qdisc* qdisc)
{
	int prio;
	struct sk_buff_head *list = qdisc_priv(qdisc);
	struct sk_buff *skb;

	//3個sk_buff_head,0-3優先級遞減
	for (prio = 0; prio < 3; prio++, list++) {
		//從最高優先級的sk_buff_head出隊一個skb
		skb = __skb_dequeue(list);
		if (skb) {
			//遞減規則隊列的包個數
			qdisc->q.qlen--;
			return skb;
		}
	}
	return NULL;
}

//	fifo重新入隊操作:
//	調用時機:
//		出隊skb後,發現hard_start_xmit的鎖被獲取,則重新入隊skb
4.4 static int pfifo_fast_requeue(struct sk_buff *skb, struct Qdisc* qdisc)
{
	struct sk_buff_head *list = qdisc_priv(qdisc);
	//計算skb優先級對應的head
	list += prio2band[skb->priority&TC_PRIO_MAX];
	//入隊
	__skb_queue_head(list, skb);
	//更新長度與統計變量
	qdisc->q.qlen++;
	qdisc->qstats.requeues++;
	return 0;
}

//	fifo 複位隊列
//	清空隊列中的所有skb
4.5 static void pfifo_fast_reset(struct Qdisc* qdisc)
{
	int prio;
	struct sk_buff_head *list = qdisc_priv(qdisc);
	//0-3優先級依次清空隊列頭
	for (prio=0; prio < 3; prio++)
		skb_queue_purge(list+prio);
	qdisc->q.qlen = 0;
}

//	清空鏈表
//	pfifo_fast_reset->skb_queue_purge
4.6 void skb_queue_purge(struct sk_buff_head *list)
{
	struct sk_buff *skb;
	//出鏈表後釋放
	while ((skb = skb_dequeue(list)) != NULL)
		kfree_skb(skb);
}


//規則隊列初始化
4.7 static int pfifo_fast_init(struct Qdisc *qdisc, struct rtattr *opt)
{
	int i;
	struct sk_buff_head *list = qdisc_priv(qdisc);
	//初始化3個sk_buff_head
	for (i=0; i<3; i++)
		skb_queue_head_init(list+i);

	return 0;
}

//skb->priority -> sk_buff_head的映射
4.8 static const u8 prio2band[TC_PRIO_MAX+1] =
	{ 1, 2, 2, 2, 1, 2, 0, 0 , 1, 1, 1, 1, 1, 1, 1, 1 };

//刪除隊列規則
//調用路徑unregister_netdevice->dev_shutdown->qdisc_destroy
void qdisc_destroy(struct Qdisc *qdisc)
{
	struct list_head cql = LIST_HEAD_INIT(cql);
	struct Qdisc *cq, *q, *n;

	if (qdisc->flags & TCQ_F_BUILTIN ||//noop_qdisc 設置此標識
		!atomic_dec_and_test(&qdisc->refcnt))//pfifo_fast_ops 在初始化時,設置引用計數為1
				return;

	if (!list_empty(&qdisc->list)) {//由於將qdisc鏈接到dev->qdisc_list,因此此判斷失敗
		if (qdisc->ops->cl_ops == NULL)//pfifo_fast_ops 的此字段為null
			list_del(&qdisc->list);
		else
			list_move(&qdisc->list, &cql);
	}

	list_for_each_entry(cq, &cql, list)//由於pfifo_fast_ops->cl_ops為null,所以cql為空,此循環不執行
		list_for_each_entry_safe(q, n, &qdisc->dev->qdisc_list, list)
			if (TC_H_MAJ(q->parent) == TC_H_MAJ(cq->handle)) {
				if (q->ops->cl_ops == NULL)
					list_del_init(&q->list);
				else
					list_move_tail(&q->list, &cql);
			}
	list_for_each_entry_safe(cq, n, &cql, list)//不執行
		list_del_init(&cq->list);

	call_rcu(&qdisc->q_rcu, __qdisc_destroy);//直接調用__qdisc_destroy,kfree(qdisc)
}

最後更新:2017-04-03 15:21:55

  上一篇:go linux下自動同步internet時間
  下一篇:go 使用WinDbg內核調試