?? 網卡驅動.txt
字號:
}
/*
* skb_reserver用來增加skb的date和tail,因為以太網頭部為14字節長,再補上兩個字節就剛好16字節邊界
* 對齊,所以大多數以太網設備都會在數據包之前保留2個字節。
*/
skb_reserve(skb, 2); /* align IP on 16B boundary */
memcpy(skb_put(skb, pkt->datalen), pkt->data, pkt->datalen);
skb->dev = dev; /*skb與接收設備就關聯起來了,它在網絡棧中會被廣泛使用,沒道理不知道數據是誰接收來的吧*/
skb->protocol = eth_type_trans(skb, dev); /*獲取上層協議類型,這樣,上層處理函數才知道如何進一步處理*/
skb->ip_summed = CHECKSUM_UNNECESSARY; /* 設置較驗標志:不進行任何校驗,作者的驅動的收發都在內存中進行,是沒有必要進行校驗*/
/*累加計數器*/
priv->stats.rx_packets++;
priv->stats.rx_bytes += pkt->datalen;
/*
* 把數據包交給上層。netif_rx會逐步調用netif_rx_schedule -->__netif_rx_schedule,
* __netif_rx_schedule函數會調用__raise_softirq_irqoff(NET_RX_SOFTIRQ);觸發網絡接收數據包的軟中斷函數net_rx_action。
* 軟中斷是Linux內核完成中斷推后處理工作的一種機制,請參考《Linux內核設計與實現》第二版。
* 唯一需要提及的是,這個軟中斷函數net_rx_action是在網絡系統初始化的時候(linux/net/core/dev.c):注冊的
* open_softirq(NET_RX_SOFTIRQ, net_rx_action, NULL);
*/
netif_rx(skb);
out:
return;
}
/*
* NAPI 的poll輪詢函數.
*/
static int snull_poll(struct net_device *dev, int *budget)
{
/*
* dev->quota是當前CPU能夠從所有接口中接收數據包的最大數目,budget是在
* 初始化階段分配給接口的weight值,輪詢函數必須接受二者之間的最小值。表示
* 輪詢函數本次要處理的數據包個數。
*/
int npackets = 0, quota = min(dev->quota, *budget);
struct sk_buff *skb;
struct snull_priv *priv = netdev_priv(dev);
struct snull_packet *pkt;
/*這個循環次數由要處理的數據包個數,并且,以處理完接收隊列為上限*/
while (npackets < quota && priv->rx_queue) {
/*從隊列中取出數據包*/
pkt = snull_dequeue_buf(dev);
/*接下來的處理,和傳統中斷事實上是一樣的*/
skb = dev_alloc_skb(pkt->datalen + 2);
if (! skb) {
if (printk_ratelimit())
printk(KERN_NOTICE "snull: packet dropped\n");
priv->stats.rx_dropped++;
snull_release_buffer(pkt);
continue;
}
skb_reserve(skb, 2); /* align IP on 16B boundary */
memcpy(skb_put(skb, pkt->datalen), pkt->data, pkt->datalen);
skb->dev = dev;
skb->protocol = eth_type_trans(skb, dev);
skb->ip_summed = CHECKSUM_UNNECESSARY; /* don't check it */
/*需要調用netif_receive_skb而不是net_rx將包交給上層協議棧*/
netif_receive_skb(skb);
/*累加計數器 */
npackets++;
priv->stats.rx_packets++;
priv->stats.rx_bytes += pkt->datalen;
snull_release_buffer(pkt);
}
/* If we processed all packets, we're done; tell the kernel and reenable ints */
*budget -= npackets;
dev->quota -= npackets;
//
if (! priv->rx_queue) {
netif_rx_complete(dev);
snull_rx_ints(dev, 1);
return 0;
}
/* We couldn't process everything. */
return 1;
}
/*
* 設備的中斷函數,當需要發/收數據,出現錯誤,連接狀態變化等,它會被觸發
* 對于典型的網絡設備,一般會在open函數中注冊中斷函數,這樣,當網絡設備產生中斷時,如接收到數據包時,
* 中斷函數將會被調用。不過在這個例子中,因為沒有真正的物理設備,所以,不存在注冊中斷,也就不存在觸
* 發,對于接收和發送,它都是在自己設計的函數的特定位置被調用。
* 這個中斷函數設計得很簡單,就是取得設備的狀態,判斷是“接收”還是“發送”的中斷,以調用相應的處理函數。
* 而對于,“是哪個設備產生的中斷”這個問題,則由調用它的函數通過第二個參數的賦值來決定。
*/
static void snull_regular_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
int statusword;
struct snull_priv *priv;
struct snull_packet *pkt = NULL;
/*
* 通常,需要檢查 "device" 指針以確保這個中斷是發送給自己的。
* 然后為 "struct device *dev" 賦
*/
struct net_device *dev = (struct net_device *)dev_id;
/* paranoid */
if (!dev)
return;
/* 鎖住設備 */
priv = netdev_priv(dev);
spin_lock(&priv->lock);
/* 取得設備狀態指字,對于真實設備,使用I/O指令,比如:int txsr = inb(TX_STATUS); */
statusword = priv->status;
priv->status = 0;
if (statusword & SNULL_RX_INTR) { /*如果是接收數據包的中斷*/
/* send it to snull_rx for handling */
pkt = priv->rx_queue;
if (pkt) {
priv->rx_queue = pkt->next;
snull_rx(dev, pkt);
}
}
if (statusword & SNULL_TX_INTR) { /*如果是發送數據包的中斷*/
/* a transmission is over: free the skb */
priv->stats.tx_packets++;
priv->stats.tx_bytes += priv->tx_packetlen;
dev_kfree_skb(priv->skb);
}
/* 釋放鎖 */
spin_unlock(&priv->lock);
/*釋放緩沖區*/
if (pkt) snull_release_buffer(pkt); /* Do this outside the lock! */
return;
}
/*
* A NAPI interrupt handler.
* 在設備初始化的時候,poll指向指向了snull_poll函數,所以,NAPI中斷處理函數很簡單,
* 當“接收中斷”到達的時候,它就屏蔽此中斷,然后netif_rx_schedule函數接收,接收函數
* 會在未來某一時刻調用注冊的snull_poll函數實現輪詢,當然,對于“傳輸中斷”,處理方法
* 同傳統中斷處理并無二致。
*/
static void snull_napi_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
int statusword;
struct snull_priv *priv;
/*
* As usual, check the "device" pointer for shared handlers.
* Then assign "struct device *dev"
*/
struct net_device *dev = (struct net_device *)dev_id;
/* ... and check with hw if it's really ours */
/* paranoid */
if (!dev)
return;
/* Lock the device */
priv = netdev_priv(dev);
spin_lock(&priv->lock);
/* retrieve statusword: real netdevices use I/O instructions */
statusword = priv->status;
priv->status = 0;
/*
* 唯一的區別就在這里,它先屏蔽掉接收中斷,然后調用netif_rx_schedule,而不是netif_rx
* 重點還是在于poll函數的設計。
*/
if (statusword & SNULL_RX_INTR) {
snull_rx_ints(dev, 0); /* Disable further interrupts */
netif_rx_schedule(dev);
}
if (statusword & SNULL_TX_INTR) {
/* a transmission is over: free the skb */
priv->stats.tx_packets++;
priv->stats.tx_bytes += priv->tx_packetlen;
dev_kfree_skb(priv->skb);
}
/* Unlock the device and we are done */
spin_unlock(&priv->lock);
return;
}
/*
* Transmit a packet (low level interface)
*/
static void snull_hw_tx(char *buf, int len, struct net_device *dev)
{
/*
* This function deals with hw details. This interface loops
* back the packet to the other snull interface (if any).
* In other words, this function implements the snull behaviour,
* while all other procedures are rather device-independent
*/
struct iphdr *ih;
struct net_device *dest;
struct snull_priv *priv;
u32 *saddr, *daddr;
struct snull_packet *tx_buffer;
/* I am paranoid. Ain't I? */
if (len < sizeof(struct ethhdr) + sizeof(struct iphdr)) {
printk("snull: Hmm... packet too short (%i octets)\n",
len);
return;
}
if (0) { /* enable this conditional to look at the data */
int i;
PDEBUG("len is %i\n" KERN_DEBUG "data:",len);
for (i=14 ; i<len; i++)
printk(" %02x",buf[i]&0xff);
printk("\n");
}
/*
* 取得來源IP和目的IP地址
*/
ih = (struct iphdr *)(buf+sizeof(struct ethhdr));
saddr = &ih->saddr;
daddr = &ih->daddr;
/*
* 這里做了三個調換,以實現欺騙:來源地址第三octet 0<->1,目的地址第三octet 0<->1,設備snX編輯0<->1,這樣做的理由是:
* sn0(發):192.168.0.88 --> 192.168.0.99 做了調換后,就變成:
* sn1(收):192.168.1.88 --> 192.168.1.99 因為sn1的地址就是192.168.1.99,所以,它收到這個包后,會回應:
* sn1(發):192.168.1.99 --> 192.168.1.88 ,同樣地,做了這樣的調換后,就變成:
* sn0(收):192.168.0.99 --> 192.168.0.88 這樣,sn0就會收到這個包,實現了ping的請求與應答,^o^
*/
((u8 *)saddr)[2] ^= 1; /* change the third octet (class C) */
((u8 *)daddr)[2] ^= 1;
/*重新計算較驗和*/
ih->check = 0; /* and rebuild the checksum (ip needs it) */
ih->check = ip_fast_csum((unsigned char *)ih,ih->ihl);
/*輸出調試信息*/
if (dev == snull_devs[0])
PDEBUGG("%08x:%05i --> %08x:%05i\n",
ntohl(ih->saddr),ntohs(((struct tcphdr *)(ih+1))->source),
ntohl(ih->daddr),ntohs(((struct tcphdr *)(ih+1))->dest));
else
PDEBUGG("%08x:%05i <-- %08x:%05i\n",
ntohl(ih->daddr),ntohs(((struct tcphdr *)(ih+1))->dest),
ntohl(ih->saddr),ntohs(((struct tcphdr *)(ih+1))->source));
/*調換設備編號,即dest指向接收設備,原因如前所述*/
dest = snull_devs[dev == snull_devs[0] ? 1 : 0];
/*將發送的數據添加到接收設備的接收隊列中*/
priv = netdev_priv(dest);
tx_buffer = snull_get_tx_buffer(dev);
tx_buffer->datalen = len;
memcpy(tx_buffer->data, buf, len);
snull_enqueue_buf(dest, tx_buffer);
/*
* 如果設備接收標志打開,就調用中斷函數把數據包發送給目標設備——即觸發目的設備的接收中斷,這樣
* 中斷程序就會自接收設備的接收隊列中接收數據包,并交給上層網絡棧處理
*/
if (priv->rx_int_enabled) {
priv->status |= SNULL_RX_INTR;
snull_interrupt(0, dest, NULL);
}
/*發送完成后,觸發“發送完成”中斷*/
priv = netdev_priv(dev);
priv->tx_packetlen = len;
priv->tx_packetdata = buf;
priv->status |= SNULL_TX_INTR;
/*
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -