`
simohayha
  • 浏览: 1401084 次
  • 性别: Icon_minigender_1
  • 来自: 火星
社区版块
存档分类
最新评论

内核网络输出帧的处理

阅读更多
首先来看如何打开和关闭一个输出队列。

帧的输出状态是通过device->state设为__LINK_STATE_XOFF来表示的。而打开和关闭队列也就是通过这个状态位来处理。

static inline void netif_start_queue(struct net_device *dev)
{
	netif_tx_start_queue(netdev_get_tx_queue(dev, 0));
}

static inline void netif_tx_start_queue(struct netdev_queue *dev_queue)
{
	clear_bit(__QUEUE_STATE_XOFF, &dev_queue->state);
}

static inline void netif_stop_queue(struct net_device *dev)
{
	netif_tx_stop_queue(netdev_get_tx_queue(dev, 0));
}

static inline void netif_tx_stop_queue(struct netdev_queue *dev_queue)
{
	set_bit(__QUEUE_STATE_XOFF, &dev_queue->state);
}


通过上面的函数我们可以看到开启和关闭队列都是通过设置state的状态位进行实现的。

为什么要在设备运行的时候开启或关闭队列呢,举个例子,当设备使用完了它的内存,也就是无法再处理多余的帧,此时我们就需要停止掉队列,而当设备的可以接受新的帧时,我们又需要唤醒队列。

可以看下3c59x.c中的代码片段:

if (ioread16(ioaddr + TxFree) > 1536) {
			netif_start_queue (dev);	/* AKPM: redundant? */
		} else {
			/* Interrupt us when the FIFO has room for max-sized packet. */
///没有足够的空间容纳mtu,因此停掉队列。
			netif_stop_queue(dev);
			iowrite16(SetTxThreshold + (1536>>2), ioaddr + EL3_CMD);
		}


当有可用的空间时,设备会执行一个中断来通知内核驱动,有足够的内存,此时我们就需要通过netif_wake_queue来唤醒队列。

继续来看代码片段,在3c598x.c的中断处理函数中:

if (status & TxAvailable) {
			if (vortex_debug > 5)
				printk(KERN_DEBUG "	TX room bit was handled.\n");
			/* There's room in the FIFO for a full-sized packet. */
			iowrite16(AckIntr | TxAvailable, ioaddr + EL3_CMD);
///唤醒队列。
			netif_wake_queue (dev);
		}


为什么要用netif_wake_queue,而不是netif_start_queue呢,我们先来看它的实现:

static inline void netif_wake_queue(struct net_device *dev)
{
	netif_tx_wake_queue(netdev_get_tx_queue(dev, 0));
}

static inline void netif_tx_wake_queue(struct netdev_queue *dev_queue)
{
#ifdef CONFIG_NETPOLL_TRAP
	if (netpoll_trap()) {
		clear_bit(__QUEUE_STATE_XOFF, &dev_queue->state);
		return;
	}
#endif
	if (test_and_clear_bit(__QUEUE_STATE_XOFF, &dev_queue->state))

///相比于netif_start_queue多了着一个函数调用。
		__netif_schedule(dev_queue->qdisc);
}


我们这里要先明确一下,这里的关闭和打开队列,是相对于上层来说的,当我们关闭掉队列之后,我们不仅需要打开队列,而且还要将设备重新加入到output_queue,并触发软中断。执行新的包的传输。因此这里会调用
__netif_schedule.

static inline void __netif_reschedule(struct Qdisc *q)
{
	struct softnet_data *sd;
	unsigned long flags;

	local_irq_save(flags);
	sd = &__get_cpu_var(softnet_data);
	q->next_sched = sd->output_queue;
	sd->output_queue = q;
	raise_softirq_irqoff(NET_TX_SOFTIRQ);
	local_irq_restore(flags);
}


帧的输出和输入最大的一个差别就是帧的输出包含一个流量控制模块,相当于一个过滤器,它提供了一些虚的操作方法,不同的排队策略实现自己的虚方法。而每个帧在送出去之前都会调用qdisc_run方法,这个方法最终会调用qdisc_restart方法。

先来看下排队策略的主要的数据结构:

struct Qdisc_ops
{
	struct Qdisc_ops	*next;
///这里将数据分为不同的类别,每个类别都有自己的处理方法
	const struct Qdisc_class_ops	*cl_ops;
	char			id[IFNAMSIZ];
	int			priv_size;
///入队列虚函数
	int 			(*enqueue)(struct sk_buff *, struct Qdisc *);
///出队列虚函数
	struct sk_buff *	(*dequeue)(struct Qdisc *);
///把数据包放回队列,比如这次传输失败,则需要这样做
	int 			(*requeue)(struct sk_buff *, struct Qdisc *);
///丢包虚函数
	unsigned int		(*drop)(struct Qdisc *);

	int			(*init)(struct Qdisc *, struct nlattr *arg);
	void			(*reset)(struct Qdisc *);
	void			(*destroy)(struct Qdisc *);
	int			(*change)(struct Qdisc *, struct nlattr *arg);

	int			(*dump)(struct Qdisc *, struct sk_buff *);
	int			(*dump_stats)(struct Qdisc *, struct gnet_dump *);

	struct module		*owner;
};



来看qdisc_restart函数,这个函数会通过拍对策略的出队列函数来取得数据包,然后会调用每个驱动都会实现的帧传输的虚函数,最终判断返回值做相应的处理:


static inline int qdisc_restart(struct Qdisc *q)
{
	struct netdev_queue *txq;
	int ret = NETDEV_TX_BUSY;
	struct net_device *dev;
	spinlock_t *root_lock;
	struct sk_buff *skb;
///调用相应的设备的排队策略的出队列方法,取出数据包
	/* Dequeue packet */
	if (unlikely((skb = dequeue_skb(q)) == NULL))
		return 0;

///取得数据包的锁。
	root_lock = qdisc_lock(q);
///释放锁
	/* And release qdisc */
	spin_unlock(root_lock);

///得到设备
	dev = qdisc_dev(q);
	txq = netdev_get_tx_queue(dev, skb_get_queue_mapping(skb));

///请求设备队列的xmit_lock锁
	HARD_TX_LOCK(dev, txq, smp_processor_id());
	if (!netif_tx_queue_stopped(txq) &&
	    !netif_tx_queue_frozen(txq))
///执行每一个驱动所需实现的帧的传递的虚函数。
		ret = dev_hard_start_xmit(skb, dev, txq);
	HARD_TX_UNLOCK(dev, txq);

///给数据包加锁。
	spin_lock(root_lock);
///开始判断返回值
	switch (ret) {
	case NETDEV_TX_OK:
		/* Driver sent out skb successfully */
		ret = qdisc_qlen(q);
		break;

	case NETDEV_TX_LOCKED:
		/* Driver try lock failed */
		ret = handle_dev_cpu_collision(skb, txq, q);
		break;

	default:
		/* Driver returned NETDEV_TX_BUSY - requeue skb */
		if (unlikely (ret != NETDEV_TX_BUSY && net_ratelimit()))
			printk(KERN_WARNING "BUG %s code %d qlen %d\n",
			       dev->name, ret, q->q.qlen);

		ret = dev_requeue_skb(skb, q);
		break;
	}

	if (ret && (netif_tx_queue_stopped(txq) ||
		    netif_tx_queue_frozen(txq)))
		ret = 0;

	return ret;
}


dev_queue_xmit这个函数和帧的输入中的netif_rx很类似。它不管怎么样都会调用hard_start_xmit也就是每个驱动自己实现的帧输出虚函数,可是它有两种途径,一种就是流量控制模块,也就是调用qdisc_run,第二种是直接调用hard_start_xmit(主要是虚拟设备,或者回环。).


先来看他的调用图:




int dev_queue_xmit(struct sk_buff *skb)
{
	.............................

	/* GSO will handle the following emulations directly. */
///有关gso的文章可以看这里:http://lwn.net/Articles/189970/

	if (netif_needs_gso(dev, skb))
		goto gso;
..................................
......................................

	/* If packet is not checksummed and device does not support
	 * checksumming for this protocol, complete checksumming here.
	 */
	if (skb->ip_summed == CHECKSUM_PARTIAL) {
		skb_set_transport_header(skb, skb->csum_start -
					      skb_headroom(skb));
///进行包的校验。
		if (!dev_can_checksum(dev, skb) && skb_checksum_help(skb))
			goto out_kfree_skb;
	}

gso:
............................................
///如果有定义enqueue,其实也就是判断是否有流量控制了。
	if (q->enqueue) {
		spinlock_t *root_lock = qdisc_lock(q);

		spin_lock(root_lock);
///测试状态。如果是__QDISC_STATE_DEACTIVATED,则丢掉此包。
		if (unlikely(test_bit(__QDISC_STATE_DEACTIVATED, &q->state))) {
			kfree_skb(skb);
			rc = NET_XMIT_DROP;
		} else {
///否则调用qdisc_run来处理
			rc = qdisc_enqueue_root(skb, q);
			qdisc_run(q);
		}
		spin_unlock(root_lock);
///返回
		goto out;
	}

[color=red]	/* The device has no queue. Common case for software devices:
	   loopback, all the sorts of tunnels...

	   Really, it is unlikely that netif_tx_lock protection is necessary
	   here.  (f.e. loopback and IP tunnels are clean ignoring statistics
	   counters.)
	   However, it is possible, that they rely on protection
	   made by us here.

	   Check this and shot the lock. It is not prone from deadlocks.
	   Either shot noqueue qdisc, it is even simpler 8)
	 */[/color]
///下面省略的代码就是处理一些虚拟设备。
......................................

	rc = -ENETDOWN;
	rcu_read_unlock_bh();

out_kfree_skb:
	kfree_skb(skb);
	return rc;
out:
	rcu_read_unlock_bh();
	return rc;
}


最后我们来看输出队列的软中断处理函数net_rx_action,它主要会在两种情况下被触发:

1 当传输被设备打开,此时设备调用netif_wake_queue.
2 当传输已经完毕,此时设备会调用dev_kfree_skb_irq.


static void net_tx_action(struct softirq_action *h)
{
///取得cpu的softnet_data变量

	struct softnet_data *sd = &__get_cpu_var(softnet_data);

	if (sd->completion_queue) {
		struct sk_buff *clist;
///关闭中断
		local_irq_disable();
///清除sd的完成队列列表
		clist = sd->completion_queue;
		sd->completion_queue = NULL;
		local_irq_enable();

		while (clist) {
			struct sk_buff *skb = clist;
			clist = clist->next;

			WARN_ON(atomic_read(&skb->users));
///循环释放数据包
			__kfree_skb(skb);
		}
	}
///开始释放排队策略的相关资源。
	if (sd->output_queue) {
		struct Qdisc *head;

		local_irq_disable();
		head = sd->output_queue;
		sd->output_queue = NULL;
		local_irq_enable();

		while (head) {
			struct Qdisc *q = head;
			spinlock_t *root_lock;

			head = head->next_sched;

................................
		}
	}
}


只有一个buffer的引用计数为0时,才能释放它,这里就是skb->users为0.
void dev_kfree_skb_irq(struct sk_buff *skb)
{
	if (atomic_dec_and_test(&skb->users)) {
		struct softnet_data *sd;
		unsigned long flags;

		local_irq_save(flags);
		sd = &__get_cpu_var(softnet_data);
///将所有需要dealloc的数据包加到completime_queue中。
		skb->next = sd->completion_queue;
		sd->completion_queue = skb;
		raise_softirq_irqoff(NET_TX_SOFTIRQ);
		local_irq_restore(flags);
	}
}


看段代码片段,3c59x.c的中断处理函数中的:



///dma传输已经完成,也就代表此次数据包传输已经完成。
if (status & DMADone) {
			if (ioread16(ioaddr + Wn7_MasterStatus) & 0x1000) {
				iowrite16(0x1000, ioaddr + Wn7_MasterStatus); /* Ack the event. */
				pci_unmap_single(VORTEX_PCI(vp), vp->tx_skb_dma, (vp->tx_skb->len + 3) & ~3, PCI_DMA_TODEVICE);
///触发软中断,释放资源。
				dev_kfree_skb_irq(vp->tx_skb); /* Release the transferred buffer */
				if (ioread16(ioaddr + TxFree) > 1536) {
					/*
					 * AKPM: FIXME: I don't think we need this.  If the queue was stopped due to
					 * insufficient FIFO room, the TxAvailable test will succeed and call
					 * netif_wake_queue()
					 */
					netif_wake_queue(dev);
				} else { /* Interrupt when FIFO has room for max-sized packet. */
					iowrite16(SetTxThreshold + (1536>>2), ioaddr + EL3_CMD);
					netif_stop_queue(dev);
				}
			}
		}

  • 大小: 38 KB
分享到:
评论

相关推荐

    LINUX内核编程---相当经典

    理解中断处理程序的编写,以及异常帧的处理,能帮助开发更高效的系统响应代码。 8. **内核模块**:内核模块允许动态加载和卸载代码,这使得对内核的修改变得更加灵活。学习如何编写和使用`module_init()`和`module_...

    linux内核升级的详细步骤

    2. **设备驱动**:根据系统中的硬件类型,选择适当的驱动支持,如Loopback设备、RAM磁盘、RAID/LVM、帧缓冲设备、USB存储、网络适配器等。 3. **文件系统**:确保支持的文件系统符合需求,如ext2、ext3等。 ### 第...

    最新嵌入式系统的以太网接口设计及linux内核网络设备驱动.doc

    ### 最新嵌入式系统的以太网接口设计及Linux内核网络设备驱动 #### 一、以太网概述 以太网(Ethernet)是目前应用最为广泛的局域网(LAN)通信协议标准之一。它主要采用了带冲突检测的载波侦听多路访问(Carrier ...

    深入分析linux tcp/ip协议栈

    此外,阅读源码可以帮助我们更好地理解Linux内核如何处理网络通信。例如,`net/core/skbuff.c`包含网络数据包的结构体sk_buff,它是内核处理数据包的基础;`net/ipv4/tcp.c`包含了TCP协议的具体实现;`...

    linux-网络协议栈流程.pdf

    Linux内核中的网络驱动负责与物理网络设备交互,接收网络帧并将其传递给上层的网络协议栈。这一过程涉及中断处理和DMA(Direct Memory Access)等技术,以确保数据能高效地从网络设备传输到内存中。 接着,数据包在...

    kernel内核目录

    "Kernel抓屏"可能指的是在内核级别捕获和记录系统的屏幕输出,这对于调试和分析系统行为非常有用。在Linux中,可以使用`fbgrab`工具或者通过编程方式访问帧缓冲(framebuffer)来实现这一功能。对于嵌入式设备,...

    Java音频视频播放器VLC内核

    5. 音频输出:处理音频流,确保音频能在系统上正确播放。 6. 元数据获取:获取媒体文件的元信息,如标题、艺术家、时长等。 7. 错误处理:捕获并处理可能出现的错误,如文件不存在、网络连接问题等。 使用VLC内核的...

    电信设备-信息处理装置以及信息处理程序.zip

    信息处理程序的设计原理,如编译器、解释器和操作系统内核的工作机制;信息处理流程的详细步骤,包括数据的接收、解码、处理和转发;以及电信设备中常见的信息处理技术,如错误检测与纠正、流量控制和拥塞避免策略等...

    Linux网络设备驱动程序分析与设计.pdf

    Linux网络设备驱动程序是操作系统内核的重要组成部分,它们负责处理网络设备的输入输出,使得操作系统能够与硬件进行通信,实现数据的发送和接收。本文将深入探讨Linux网络设备驱动程序的结构组成、工作原理以及设计...

    OBDII ISO 15765-4 CAN协议解析并输出

    2. **帧格式**:包括标准帧和扩展帧,标准帧的标识符为11位,扩展帧为29位,扩展帧可以提供更大的地址空间,适合大型网络。 3. **数据长度**:每个数据帧可以携带最多8个数据字节。在OBDII应用中,通常使用单个或多...

    Video_Network_Server.rar_DM642_dm642 网络_network video server

    而在视频处理中,运动补偿是用于处理连续帧间的运动物体,以减少视频压缩中的块效应和运动模糊,提高视频质量。 视频网络服务器的实现通常包括以下几个关键组件: 1. **视频输入/输出**:这涉及到捕获视频源,如...

    LINUX-1.2.13 network_stack_kernel_source_analysis

    《Linux 1.2.13 内核网络栈实现源代码分析》 在Linux操作系统中,网络栈是系统核心的重要组成部分,它负责处理网络通信的输入和输出,包括数据包的接收、处理和发送。本文将深入探讨Linux 1.2.13内核中的网络栈实现...

    PPP协议C语言源程序.rar

    1. 帧的编码和解码:处理输入和输出数据,按照PPP帧的结构进行封装和解析。 2. LCP交互:实现LCP协议的协商过程,包括发送和接收配置请求、响应、 Nak(否定确认)和Rej(拒绝)报文,以及处理链路质量检测和认证...

    单片机驱动enc28j60网络控制模块

    4. **错误检测与恢复**:要处理可能出现的错误,例如CRC校验失败、帧格式错误等,并采取适当的恢复措施。 5. **软件设计**:在单片机端,需要编写相应的驱动程序,封装SPI通信、状态管理、网络协议栈等功能,使其...

    入手新唐M0-M4内核

    - **新唐CAN IP特点**:针对新唐微控制器的CAN模块特性进行了详细介绍,例如支持的帧格式、错误处理机制等。 - **寄存器配置**:对CAN模块中的关键寄存器进行了详细解释,帮助开发者更好地配置CAN接口。 #### 7. ...

    FreeBSD_kernel_networking

    总结来说,FreeBSD的内核网络架构是高度模块化和可扩展的,通过精心设计的层次结构和数据结构,有效地处理各种网络任务,从硬件驱动到高层协议栈,都得到了细致的考虑和实现。这不仅确保了系统的稳定性和性能,也为...

    国嵌最新版网络摄像头(已加入v4l2)

    v4l2是Linux系统下用于处理多媒体数据,特别是视频输入和输出的接口,它极大地增强了对硬件设备的支持和功能。 【描述详解】 描述中提到,“这个是国嵌最新版的webcam代码”,这暗示我们这里有一个用于开发或控制...

    Hi3511/Hi3512 媒体处理软件开发指南

    VO模块专注于视频信号的输出,支持将处理后的视频信号输出到不同的显示设备上。文档不仅介绍了VO模块的基本概念,还提供了如何配置和启动VO输出的指导,以确保视频输出的质量和稳定性。 #### 3.3 VENC模块 VENC...

Global site tag (gtag.js) - Google Analytics