5.3.3 網路裝置模組資料幀
本節結合5.3.2節鍊錶和佇列的實現,定義diy tcp/ip網路裝置模組資料幀的資料結構,以便用於佇列操作。在pcap_callback返回接收到的資料幀後,將pcap_pkthdr封裝成本節定義的資料幀,為實現網路裝置模組的接收佇列做準備。
diy tcp/ip網路裝置模組rx和tx資料幀的資料結構定義在device.h標頭檔案中。
typedef struct _dev_rx_pkt __attribute__ ((packed)) dev_rxpkt_t;
typedef struct _dev_tx_pkt __attribute__ ((packed)) dev_txpkt_t;
struct _dev_rx_pkt封裝了queue_t資料結構, seq和len是接收到的資料幀的序號和有效載荷的長度,payload指向鏈路層資料幀有效載荷的第乙個位元組。0長度陣列是gnu c的用法,表示可變長陣列,其本身只是乙個佔位符,sizeof計算dev_rxpkt_t資料結構的大小時,0長度陣列佔0個位元組。
根據接收到的鏈路層資料幀的長度,為網路裝置模組資料幀分配記憶體空間的大小為:sizeof(dev_rxpkt_t)加上有效載荷的長度。使用0長度陣列,將有效載荷放在dev_rxpkt_t資料結構結束後的第乙個位元組,便於釋放分配的記憶體空間。
struct _dev_tx_pkt同樣封裝了queue_t節點,seq為網路裝置模組傳送資料幀的序號,void *pdbuf,指向diy tcp/ip上層模組傳送的資料幀。上層模組傳送的資料幀到達網路裝置模組時已經具備了傳輸層tcp/udp,網路層ip和乙太網802.3的頭部,這些頭部在diy tcp/ip的各模組實現時加入。網路裝置模組只需根據802.3頭部的目標位址將資料幀通過pf_packet型別的socket傳送到linux kernel中對應的網路裝置的驅動即可。
5.3.2節實現了佇列的資料結構和操作函式,將queue_t資料結構封裝在struct _dev_rx_pkt和struct _dev_tx_pkt中,利用node成員,基於佇列的操作函式,即可實現網路裝置模組的資料幀入隊和出隊操作。通過container_of又可以得到封裝node成員的struct _dev_rx_pkt或struct _dev_tx_pkt的資料結構的指標。
5.3.4 接收佇列
介紹了接收執行緒,鍊錶,佇列和網路裝置模組資料幀的資料結構的實現之後,本節在這些基礎上完成網路裝置模組接收佇列的實現。首先是修改網路裝置結構體net_device_t資料結構,向其中加入接收佇列相關的成員。
device.h
typedef struct _net_device net_device_t;
line 7: 在net_device_t結構體中新增rxpkt_q成員,做為接收佇列的隊頭節點。修改網路裝置接收邏輯的初始化函式dev_rx_init,新增接收佇列的初始化如下。
接收佇列初始化
static int dev_rx_init(net_device_t *ndev)
line 2-4略去的**與5.3.1節一致。dev_rx_init函式中,在pthread_create建立接收執行緒之後,呼叫queue_init初始化網路裝置的接收佇列rxpkt_q。
修改pcap_callback函式,在收到鏈路層資料幀後,將其封裝成dev_rxpkt_t資料型別,再放入網路裝置的接收佇列,最後喚醒接收執行緒處理接收佇列中的資料幀。
static void pcap_callback(unsigned char *arg,
const struct pcap_pkthdr *pkthdr, const unsigned char *packet)
memset(rxpkt, 0, copy_len);
rxpkt->len = pkthdr->caplen;
memcpy(rxpkt->payload, packet, pkthdr->caplen);
pthread_mutex_lock(&ndev->rxq_mutex);
enqueue(&ndev->rxpkt_q, &rxpkt->node);
pthread_mutex_unlock(&ndev->rxq_mutex);
pthread_cond_signal(&ndev->rxq_cond);
}
將解析鏈路層資料幀乙太網頭部的操作從pcap_callback函式中移除,它只需盡快將接收到的資料幀入隊,即可返回。pcap_callback越快返回,pcap_loop庫函式就可以越快的處理下乙個鏈路層資料幀。入隊後資料幀的解析和後續處理,在接收執行緒中並行完成。
line 14-23: 根據鏈路層資料幀的長度,為dev_rxpkt_t資料結構申請記憶體。申請記憶體空間的大小為sizeof(dev_rxpkt_t)加上有效載荷的長度pkthdr->caplen。rxpkt->len儲存有效載荷的長度,再將有效載荷packet複製到dev_rxpkt_t的payload指向的記憶體空間中。
line 24-27: 獲取保護接收佇列的互斥量,將封裝好的rxpkt加入網路裝置的接收佇列中,釋放互斥量,最後呼叫pthread_cond_signal喚醒接收執行緒處理接收佇列中的資料幀。
修改接收執行緒的主迴圈體dev_rx_routine
static void *dev_rx_routine(void *args)
pthread_mutex_unlock(&ndev->rxq_mutex);
}exit:
printf("dev rx routine exited\n");
pthread_exit(0);
}
line 16-26: 接收執行緒阻塞在等待條件rxq_cond的睡眠佇列上,被喚醒之後,先獲取互斥量,判斷接收佇列不空時,從接收佇列中取出佇列節點。通過container_of獲取封裝佇列節點的dev_rxpkt_t資料結構的指標,然後呼叫dev_process_rxpkt解析資料幀的payload。
static void dev_process_rxpkt(net_device_t *ndev, dev_rxpkt_t *rxpkt)
dev_process_rxpkt是device.c檔案中新增的靜態函式,解析dev_rxpkt_t的操作只是將rxpkt->payload強制轉換為乙太網頭部資料型別,然後列印乙太網頭部的目的位址,源位址和頭部型別。後續章節會不斷的擴充套件dev_process_rxpkt函式,該函式根據乙太網頭部型別,將剝去乙太網頭部後的資料幀,交給diy tcp/ip上層模組的接收函式處理。
diy tcp/ip銷毀時,需要清理接收佇列,防止記憶體洩漏。新增網路裝置模組接收佇列的清理函式dev_flush_rxpktq。
static void dev_flush_rxpktq(net_device_t *ndev)
printf("dev flushed %d packets\n", flush_count);
}
該函式在銷毀網路裝置的接收邏輯的函式dev_rx_deinit中被呼叫,釋放為dev_rxpkt_t資料幀申請的記憶體空間。接收執行緒退出後呼叫dev_flush_rxpktq,所以該函式操作網路裝置的接收佇列時不需要獲取保護接收佇列的互斥量。
while迴圈遍歷網路裝置的接收佇列,通過containter_of得到dev_rxpkt_t的指標。此時接收執行緒已經退出,沒有必要再處理接收佇列中的資料幀,直接釋放分配的記憶體空間即可。
修改dev_rx_deinit函式
static void dev_rx_deinit(net_device_t *ndev)
line 11-12: pthread_join返回後,表明接收執行緒已經退出。呼叫dev_flush_rxpktq清空接收佇列。至此網路裝置模組的接收佇列已經實現完成,編譯執行結果如下:
gannicus@ubuntu:~/guojia/tasks/diy_user_space_tcpip/ch2/2$ sudo ./tcp_ip_stack
[sudo] password for gannicus:
network device init
filter: ether proto 0x0800 or ether proto 0x0806
network device rx init
1533030301.608931: capture length: 78, pkt length: 78
1533030301.858040: capture length: 143, pkt length: 143
1533030301.858057: capture length: 171, pkt length: 171
dev rx, ethernet type: 0800, 60:eb:69:c3:51:70 --> 00:19:2f:91:cd:ff
dev rx, ethernet type: 0800, 00:19:2f:91:cd:ff --> 60:eb:69:c3:51:70
dev rx, ethernet type: 0800, 60:eb:69:c3:51:70 --> 00:19:2f:91:cd:ff
1533030302.070609: capture length: 139, pkt length: 139
1533030302.088789: capture length: 60, pkt length: 60
1533030302.398916: capture length: 215, pkt length: 215
…^cpcap_loop ended
network device deinit
network device rx deinit
dev rx routine exited
dev flushed 0 packets
DIY TCP IP 網路裝置模組1
5.網路裝置模組的實現 本章介紹diy tcp ip的網路裝置模組的實現。網路裝置層的邏輯包括,接收邏輯和傳送邏輯。接收邏輯 diy tcp ip通過pf packet域的socket接收鏈路層資料幀,模擬硬體網絡卡的接收。接收佇列非同步處理鏈路層接收的資料幀,解析乙太網頭部,根據乙太網頭部型別,將...
VOIP網路裝置
voip 系統的基本元件 終端 閘道器 關守 網管伺服器 記帳伺服器等,下面介紹一下各個元件的功能 1 終端 terminal voip的終端可以有多種型別,其中包括傳統的語音 isdn終端 pc,也可以是集語音 資料和圖象於一體的多 業務終端。由於不同種類的終端產生的資料來源結構是不同的,要在同乙...
連線網路裝置
雙機網際網路絡 集線器網際網路絡 交換機網際網路絡 路由器網際網路絡 雙機網際網路絡 計算機與計算機連線 如果只有兩台計算機進行互連,也可以使用雙絞線將兩台計算機的網絡卡連線在一起,但是必須使用交叉線方法製作的網線,才能正常連線。雙絞線的連線標準 eia tia對雙絞線的連線制定了標準,使用資料通訊...