在内核网络子系统里面,我们看不到其通过某节点与实际的PHY关联,不像字符设备和块设备,通过某设备节点和具体的物理设备关联起来.虽然像字符设备和块设备中间有可能通过层层子系统到达物理设备的操作层,如输入子系统的流程如下:
用户空间-->事件驱动层-->输入设备驱动层-->物理设备操作集
但是我们还是可以看到其开始的起点.实际上,网络子系统是通过"路由信息"完成用户空间与具体的PHY实现交互的.比如:
ifconfig eth0 192.168.1.201
我们便向内核添加一条"路由信息".
比如:
tftp -r YourFile -g 192.168.1.131
也生成一条"路由信息".它们便是通过两者的路由信息"间接"完成了数据交互的.
这里重点关注用户空间与具体PHY的交互部分.主要包括:
1).三个操作集eth_header_ops、dm9000_netdev_ops和dm9000_ethtool_ops;
2).数据的收发;
3).网络连接状态.
1.三个操作集:
操作集eth_header_ops是在函数void ether_setup(struct net_device *dev)设置的,是与具体物理设备无关的,与以太网头有关;
dm9000_netdev_ops与具体的PHY相关;
dm9000_ethtool_ops对应用户空间ethtool工具对应,实现用户空间对系统网络管理的工具.
1-1.eth_header_ops:
eth_header_ops是一个全局操作集,因此是与平台无关的,只与以太网协议有关.主要是完成以太网头的操作.如下:
const struct header_ops eth_header_ops ____cacheline_aligned = {
.create = eth_header,
.parse = eth_header_parse,
.rebuild = eth_rebuild_header,
.cache = eth_header_cache,
.cache_update = eth_header_cache_update,
};
如函数eth_header()是创建一个以太网头:
/**
* eth_header - create the Ethernet header
* @skb: buffer to alter
* @dev: source device
* @type: Ethernet type field
* @daddr: destination address (NULL leave destination address)
* @saddr: source address (NULL use device source address)
* @len: packet length (<= skb->len)
*
*
* Set the protocol type. For a packet of type ETH_P_802_3 we put the length
* in here instead. It is up to the 802.2 layer to carry protocol information.
*/
int eth_header(struct sk_buff *skb, struct net_device *dev,
unsigned short type,
const void *daddr, const void *saddr, unsigned len)
{
struct ethhdr *eth = (struct ethhdr *)skb_push(skb, ETH_HLEN);
if (type != ETH_P_802_3)
eth->h_proto = htons(type);
else
eth->h_proto = htons(len);
/*
* Set the source hardware address.
*/
if (!saddr)
saddr = dev->dev_addr;
memcpy(eth->h_source, saddr, ETH_ALEN);
if (daddr) {
memcpy(eth->h_dest, daddr, ETH_ALEN);
return ETH_HLEN;
}
/*
* Anyway, the loopback-device should never use this function...
*/
if (dev->flags & (IFF_LOOPBACK | IFF_NOARP)) {
memset(eth->h_dest, 0, ETH_ALEN);
return ETH_HLEN;
}
return -ETH_HLEN;
}
1-2.dm9000_ethtool_ops:
ethtool是LINUX下用于查询和管理网卡的工具.摘要如下:
ethtool ethX //查询ethX网口基本设置
ethtool –h //显示ethtool的命令帮助(help)
ethtool –i ethX //查询ethX网口的相关信息
ethtool –d ethX //查询ethX网口注册性信息
ethtool –r ethX //重置ethX网口到自适应模式
ethtool –S ethX //查询ethX网口收发包统计
1-3.dm9000_netdev_ops:
dm9000_netdev_ops和具体的PHY有关,这里是绑定DM9000.例如用户空间执行下面的命令时:
ifconfig eth0 192.168.1.201 up
会导致dm9000_open(struct net_device *dev)函数的执行,完成DM9000的IO资源、DMA、中断等动作的执行.如下:
/*
* Open the interface.
* The interface is opened whenever "ifconfig" actives it.
*/
static int dm9000_open(struct net_device *dev)
{
board_info_t *db = netdev_priv(dev);
unsigned long irqflags = db->irq_res->flags & IRQF_TRIGGER_MASK;
if (netif_msg_ifup(db))
dev_dbg(db->dev, "enabling %s\n", dev->name);
/* If there is no IRQ type specified, default to something that
* may work, and tell the user that this is a problem */
if (irqflags == IRQF_TRIGGER_NONE)
dev_warn(db->dev, "WARNING: no IRQ resource flags set.\n");
irqflags |= IRQF_SHARED;
if (request_irq(dev->irq, &dm9000_interrupt, irqflags, dev->name, dev))
return -EAGAIN;
/* Initialize DM9000 board */
dm9000_reset(db);
dm9000_init_dm9000(dev);
/* Init driver variable */
db->dbug_cnt = 0;
mii_check_media(&db->mii, netif_msg_link(db), 1);
netif_start_queue(dev);
dm9000_schedule_poll(db);
return 0;
}
数据的发送将会调用函数static int dm9000_start_xmit(struct sk_buff *skb, struct net_device *dev):
/*
* Hardware start transmission.
* Send a packet to media from the upper layer.
*/
static int
dm9000_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
unsigned long flags;
board_info_t *db = netdev_priv(dev);
dm9000_dbg(db, 3, "%s:\n", __func__);
if (db->tx_pkt_cnt > 1)
return NETDEV_TX_BUSY;
spin_lock_irqsave(&db->lock, flags);
/* Move data to DM9000 TX RAM */
writeb(DM9000_MWCMD, db->io_addr);
(db->outblk)(db->io_data, skb->data, skb->len);
dev->stats.tx_bytes += skb->len;
db->tx_pkt_cnt++;
/* TX control: First packet immediately send, second packet queue */
if (db->tx_pkt_cnt == 1) {
dm9000_send_packet(dev, skb->ip_summed, skb->len);
} else {
/* Second packet */
db->queue_pkt_len = skb->len;
db->queue_ip_summed = skb->ip_summed;
netif_stop_queue(dev);
}
spin_unlock_irqrestore(&db->lock, flags);
/* free this SKB */
dev_kfree_skb(skb);
return NETDEV_TX_OK;
}
2.数据的收发:
网络接口最重要的任务就是数据的传输和接收.
2-1.数据包传输:
内核以队列作为发送数据的工具,以skb作为发送数据的载体.
2-2.数据包接收:
网络接口数据包的接收提供了两种方式:中断和轮询.大部分都采用了中断模式.如DM9000的驱动在dm9000_open()函数里面有如下代码:
if (request_irq(dev->irq, &dm9000_interrupt, irqflags, dev->name, dev))
return -EAGAIN;
当DM9000这个设备对应的中断被触发时,函数static irqreturn_t dm9000_interrupt(int irq, void *dev_id)将会被触发调用.流程如下:
static irqreturn_t dm9000_interrupt(int irq, void *dev_id)
->
if (int_status & ISR_PRS)
dm9000_rx(dev);
->
if (GoodPacke && ((skb = dev_alloc_skb(RxLen + 4)) != NULL))
可见,当DM9000这个外设对CPU发出中断时,将在中断里面动态分配一个skb,然后打包数据往上递.
NAPI的策略:
有时候网络接口每秒钟收到几千个数据包,采用中断的方式会使系统性能大大降低.NAPI是基于轮询的接口针对这种情况而实现的一种策略.当网络接口产生中断时,禁止中断并切换到轮询模式.将数据处理完之后再恢复中断.
3.网络连接状态:
网络子系统需要了解网络链路是否正常,其依据就是载波状态信息,载波的存在与否意味着硬件功能是否正常.例如当用户拔掉网线时,载波信号消失,链路就不正常工作.内核提供软件上对载波的控制可以通过下面两个API:
void netif_carrier_off(struct net_device *dev)
void netif_carrier_on(struct net_device *dev)
当检测出设备上不存在载波时,调用netif_carrier_off()函数告诉内核,内核会采取一些策略在这种情况下不进行数据交互;
当检测出设备上存在载波,调用netif_carrier_on()函数告诉内核,这时候有可能准备有数据的交互.
因此,有两点内核必须要做到的.首先第一点必须是轮询是否有载波的程序机制;其次是载波存在与否的物理设备依据.
轮询载波是否存在的程序机制,内核采用了工作队列来实现.在dm9000_probe()函数里面,见下面代码:
INIT_DELAYED_WORK(&db->phy_poll, dm9000_poll_work);
判断载波存在与否的物理设备依据是什么?这个必须是具体的PHY实时标志的.DM9000是用寄存器NSR来标志的.见数据手册,如下:
因此,在函数dm9000_poll_work()里面有对此寄存器的读取并作判断:
unsigned nsr = dm9000_read_locked(db, DM9000_NSR);
new_carrier = (nsr & NSR_LINKST) ? 1 : 0;
#define NSR_LINKST (1<<6)
根据NSR的位6来判断是不是网路有有载波而作出软件上的载波存在与否的判断依据.