1. 程式人生 > >跟蹤一次網絡發送

跟蹤一次網絡發送

進入 block sched 根據 nec offset time lse 狀態

一、報文的分層轉發

當我們上層通過write來發送消息的時候,會走到socket文件系統的發送接口。

首先,socket是整個系統中所有網絡設備在用戶態的一個抽象,也就是一個socket可以為appletalk,bluetooth一樣,代表不同的協議類型。所以此時首先經過一次協議類型的轉發,整個最為上層的就是我們通常最為常見的IPV6協議,調用的接口為

const struct proto_ops inet_stream_ops = {
.family = PF_INET,
.owner = THIS_MODULE,
.release = inet_release,

.bind = inet_bind,
.connect = inet_stream_connect,
.socketpair = sock_no_socketpair,
.accept = inet_accept,
.getname = inet_getname,
.poll = tcp_poll,
.ioctl = inet_ioctl,
.listen = inet_listen,
.shutdown = inet_shutdown,
.setsockopt = sock_common_setsockopt,
.getsockopt = sock_common_getsockopt,
.sendmsg = inet_sendmsg,
.recvmsg = inet_recvmsg,
.mmap = sock_no_mmap,
.sendpage = inet_sendpage,
.splice_read = tcp_splice_read,
#ifdef CONFIG_COMPAT
.compat_setsockopt = compat_sock_common_setsockopt,
.compat_getsockopt = compat_sock_common_getsockopt,
#endif
};

當消息確定為IPV4協議之後,就可以繼續確定是以太網上的哪個協議,此時就可以有TCP/UDP/Raw之類的再次轉發,此時經過的就是TCP的send

struct proto tcp_prot = {
.name = "TCP",
.owner = THIS_MODULE,
.close = tcp_close,
.connect = tcp_v4_connect,
.disconnect = tcp_disconnect,
.accept = inet_csk_accept,
.ioctl = tcp_ioctl,
.init = tcp_v4_init_sock,
.destroy = tcp_v4_destroy_sock,
.shutdown = tcp_shutdown,
.setsockopt = tcp_setsockopt,
.getsockopt = tcp_getsockopt,
.recvmsg = tcp_recvmsg,
.sendmsg = tcp_sendmsg,
.sendpage = tcp_sendpage,
.backlog_rcv = tcp_v4_do_rcv,
.hash = inet_hash,
.unhash = inet_unhash,
.get_port = inet_csk_get_port,
.enter_memory_pressure = tcp_enter_memory_pressure,

註意:這個函數是IPV4和IPV6公用的一個函數。

在該函數中,完成的操作為

tcp_push--->>__tcp_push_pending_frames--->>tcp_write_xmit---->>tcp_transmit_skb--->>ip_queue_xmit(註意,這個函數使用了我們另一個比較關系的概念,就是路由表查詢的概念)-->>ip_local_out-->>ip_output--->>ip_finish_output-->>dev_queue_xmit-->>dev_queue_xmit--->>>dev_hard_start_xmit,然後以我們最為熟悉的realtek為例說明:

static const struct net_device_ops ne2k_netdev_ops = {
.ndo_open = ne2k_pci_open,
.ndo_stop = ne2k_pci_close,
.ndo_start_xmit = ei_start_xmit,
.ndo_tx_timeout = ei_tx_timeout,

二、網絡設備的中斷申請

根據Linux當前的設備分類方法,網絡設備已經和block char 設備一樣,成為系統中一個單獨的、和前兩者主流設備並列的設備。

通過內核的調試,可以知道系統中剛啟動的時候是通過ioctrl的IOUP來設置一個設備開始進入工作狀態,然後對應的可以通過down或者close來關閉一個設備,這些也就是我們經常來進行一個設備啟動和關閉時進行的操作。

同樣對於realtek設備的初始化路徑

devinet_ioctl-->>>dev_change_flags---->>>dev_change_flags--->>>__dev_open-->>>ne2k_pci_open

其中對於設備的打開操作,其中執行了一個比較華麗的操作:

if ((old_flags ^ flags) & IFF_UP) { /* Bit is different ? */
ret = ((old_flags & IFF_UP) ? __dev_close : __dev_open)(dev);這是對函數指針直接的操作,看來沒有做不到,只有想不到

在這個設備的打開過程中,執行了中斷向量的申請操作。

int ret = request_irq(dev->irq, ei_interrupt, IRQF_SHARED, dev->name, dev);
三、中斷函數的處理__ei_interrupt

if (interrupts & ENISR_OVER)
ei_rx_overrun(dev);
else if (interrupts & (ENISR_RX+ENISR_RX_ERR))
{
/* Got a good (?) packet. */
ei_receive(dev);
}
/* Push the next to-transmit packet through. */
if (interrupts & ENISR_TX)
ei_tx_intr(dev);
else if (interrupts & ENISR_TX_ERR)
ei_tx_err(dev);

者三個處理函數和我們用戶進程看到的東西類似,包含了標準輸出、標準輸入和標準錯誤三個接口,分別處理網卡的接受、輸入和錯誤

現在看一下接收到的數據時如何進行的

skb = dev_alloc_skb(pkt_len+2);
skb_reserve(skb,2); /* IP headers on 16 byte boundaries */
skb_put(skb, pkt_len); /* Make room */
ei_block_input(dev, pkt_len, skb, current_offset + sizeof(rx_frame));
skb->protocol=eth_type_trans(skb,dev);
netif_rx(skb);
dev->stats.rx_packets++;
dev->stats.rx_bytes += pkt_len; 一些統計信息的增加
if (pkt_stat & ENRSR_PHY)
dev->stats.multicast++;

接下來的操作為:
enqueue_to_backlog--->>>____napi_schedule

__raise_softirq_irqoff(NET_RX_SOFTIRQ);
可以看到,對於網卡數據的接受是通過軟中斷來實現的,而且也可以看到,對於網卡接收到的數據是最早掛在一個設備對應的全局結構中的

通過代碼可以知道,其中的全局隊列就是

__skb_queue_tail(&sd->input_pkt_queue, skb);
中使用的input_pkt_queue,所有的網卡接受的信息都應該放在這個設備上,從而等待進一步的路由。

四、網卡軟中斷的處理

linux-2.6.37.1\net\core\dev.c

static int __init net_dev_init(void)

open_softirq(NET_TX_SOFTIRQ, net_tx_action);
open_softirq(NET_RX_SOFTIRQ, net_rx_action);

接下來是軟中斷中的處理:

sd->backlog.poll = process_backlog;
其中在軟中斷中的處理

static void net_rx_action(struct softirq_action *h)
奇怪的是其中使用的是這個polllist

while (!list_empty(&sd->poll_list)) {
這個結構的設置位於enqueue_to_backlog函數

/* Schedule NAPI for backlog device
* We can use non atomic operation since we own the queue lock
*/
if (!__test_and_set_bit(NAPI_STATE_SCHED, &sd->backlog.state)) {
if (!rps_ipi_queued(sd))
____napi_schedule(sd, &sd->backlog);

向上繼續操作

process_backlog-->>>__netif_receive_skb--->>deliver_skb

以ARP報文為例說明一下

static struct packet_type arp_packet_type __read_mostly = {
.type = cpu_to_be16(ETH_P_ARP),
.func = arp_rcv,
};

arp_rcv--->>>arp_process--->>>arp_send-->>arp_xmit--->>>dev_queue_xmit

這樣我們就完成了一個閉環的操作

跟蹤一次網絡發送