Linux串口上網(wǎng)的程序?qū)崿F(xiàn)方法
圖 6
由圖6我們看到,ednet_rx()并不是網(wǎng)絡(luò)設(shè)備的一個(gè)操作,而是模塊中的一個(gè)函數(shù)。在實(shí)際的網(wǎng)卡驅(qū)動(dòng)程序中,當(dāng)網(wǎng)卡確實(shí)接收到數(shù)據(jù)的時(shí)候,由網(wǎng)絡(luò)中斷喚醒等待接收數(shù)據(jù)的用戶進(jìn)程,也就是說,ednet_rx()應(yīng)該由那個(gè)網(wǎng)絡(luò)中斷處理例程調(diào)用。我們這里并沒有中斷,所以字符設(shè)備的device_write()可以看成是一個(gè)中斷例程,也就是說,用戶空間往字符寫操作的時(shí)候,也就調(diào)用了網(wǎng)絡(luò)設(shè)備的數(shù)據(jù)接收內(nèi)核例程ednet_rx()了。然后ednet_rx()會(huì)把原始的數(shù)據(jù)包發(fā)送到TCP/IP上層進(jìn)行處理,這一切均依賴于內(nèi)核API 函數(shù)netif_rx()。ednet_rx()就需要sk_buff數(shù)據(jù)結(jié)構(gòu)(linux/skbuff.h>中定義),用來存放從網(wǎng)絡(luò)接口接收到的原始網(wǎng)絡(luò)數(shù)據(jù),分配后的sk_buff結(jié)構(gòu)將在TCP/IP協(xié)議棧上被釋放掉。
下面介紹一下網(wǎng)絡(luò)設(shè)備的主要操作例程ednet_open()、ednet_release()、ednet_tx()、ednet_stats ()、ednet_change_mtu()、ednet_header()。網(wǎng)絡(luò)設(shè)備文件操作結(jié)構(gòu)struct net_device(linux/netdevice.h>中有定義)中定義了指向以上函數(shù)的函數(shù)指針的原形:
ednet_open: int (*open)(struct net_device *dev);
ednet_release: int (*stop)(struct net_device *dev);
ednet_tx: int (*hard_start_xmit) (struct sk_buff *skb,struct net_device *dev);
ednet_stats: struct net_device_stats* (*get_stats)(struct net_device *dev);
ednet_change_mtu:int(*change_mtu)(struct net_device *dev, int new_mtu);
ednet_header: int (*hard_header) (struct sk_buff *skb,
struct net_device *dev,
unsigned short type,
void *daddr,
void *saddr,
unsigned len);
操作int ednet_open(struct net_device *dev)的作用是打開偽網(wǎng)絡(luò)接口設(shè)備,獲得其需要的I/O端口、IRQ等,但是本網(wǎng)絡(luò)接口不需要和實(shí)際硬件打交道,所以不需要自動(dòng)獲得或者賦予I/O端口值,也不需要IRQ中斷號(hào),唯一需要程序指定的是其偽硬件地址(這個(gè)硬件地址是0ED000,ifconfig可以看到其硬件地址是 00:45:44:30:30:30,struct net_device中的dev_addr域存放網(wǎng)絡(luò)接口的物理地址。操作ednet_open()必須調(diào)用netif_start_queue()內(nèi)核API開啟網(wǎng)絡(luò)接口接收和發(fā)送數(shù)據(jù)隊(duì)列。
當(dāng)接口關(guān)閉的時(shí)候,int ednet_release(struct net_device *dev)例程被系統(tǒng)調(diào)用,在ednet_release()中調(diào)用netif_stop_queque()將停止接收和發(fā)送隊(duì)列的工作。
偽網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)的傳送例程int ednet_tx(struct sk_buff *skb, struct net_device *dev)將把要發(fā)送的網(wǎng)絡(luò)數(shù)據(jù)包寫入字符設(shè)備ed[ED_TX_DEVICE]。在發(fā)送完畢數(shù)據(jù)包的時(shí)候,dev_kfree_skb() Kernel API釋放由上層協(xié)議棧分配的sk_buff數(shù)據(jù)塊。偽網(wǎng)絡(luò)接口在進(jìn)行硬件傳輸?shù)臅r(shí)候,需要為網(wǎng)絡(luò)數(shù)據(jù)包打上時(shí)間戳。如果傳送數(shù)據(jù)包的時(shí)候超時(shí),將調(diào)用超時(shí)處理例程ednet_tx_timeout()超時(shí)處理例程。例程ednet_tx()調(diào)用真正的硬件傳送例程ednet_hw_tx()在實(shí)際的網(wǎng)卡驅(qū)動(dòng)程序中,就是真正向特定的網(wǎng)絡(luò)硬件設(shè)備寫數(shù)據(jù)的程序。我們看到,我們的硬件就是本文前面描述的字符設(shè)備,字符設(shè)備的操作例程.kernel_write()在ednet_hw_tx()將被調(diào)用。
如果我們希望使用ifconfig看到偽網(wǎng)絡(luò)接口的統(tǒng)計(jì)信息,那么系統(tǒng)就調(diào)用 struct net_device_stats *ednet_stats(struct net_device *dev)。我們看到,網(wǎng)絡(luò)接口的統(tǒng)計(jì)信息被放到設(shè)備的私有數(shù)據(jù)指針指向的內(nèi)存。網(wǎng)絡(luò)數(shù)據(jù)信息的統(tǒng)計(jì)結(jié)構(gòu)被放在內(nèi)核結(jié)構(gòu)struct net_device_stats中。
在TCP會(huì)話中,也許要協(xié)商MTU的大小,int ednet_change_mtu(struct net_device *dev, int new_mtu)可以隨時(shí)改變MTU的大小。比如在使用FTP協(xié)議的時(shí)候,在傳送數(shù)據(jù)庫(kù)的時(shí)候,MTU可能被協(xié)商為最大,以提高網(wǎng)絡(luò)傳送吞吐量。由于改變了MTU,存放網(wǎng)絡(luò)數(shù)據(jù)的字符設(shè)備初始化分配的緩存區(qū)就要重新被分配,并把已經(jīng)存放數(shù)據(jù)的舊的緩存區(qū)的內(nèi)容拷貝到新的緩存區(qū)中,所以,當(dāng)MTU改變大小的時(shí)候,那么就要使用kmalloc(new_mtu ,GFP_KERNEL)重新分配緩存區(qū)。讀者可以根據(jù)自己的需要定義新的緩存區(qū)大小。kfree()是內(nèi)核API,負(fù)責(zé)釋放內(nèi)核空間的內(nèi)存,它的使用方法和用戶空間的free()系統(tǒng)調(diào)用一致,這里就不列舉ed_realloc()函數(shù)的源程序了。
IP數(shù)據(jù)包在被網(wǎng)絡(luò)接口發(fā)送前,需要構(gòu)建其以太網(wǎng)頭信息int ednet_header(struct sk_buff *skb,struct net_device *dev,unsigned short type,void *daddr,void *saddr,unsigned int len)例程完成此功能,我們看到網(wǎng)絡(luò)數(shù)據(jù)包的以太源、目的地址,都是從發(fā)送這個(gè)數(shù)據(jù)包的網(wǎng)絡(luò)接口設(shè)備數(shù)據(jù)結(jié)構(gòu)struct net_device中得到的。源地址和目的地址信息是從網(wǎng)絡(luò)設(shè)備結(jié)構(gòu)得到的。在編譯本程序的時(shí)候,如果發(fā)現(xiàn)htons()這個(gè)函數(shù)沒有定義,可以這樣定義htons()為:#define htons(x) ((x>>8) | (x8)) 。
因?yàn)閭尉W(wǎng)絡(luò)接口沒有使用ARP獲得硬件地址,所以我們可以把我們自己定義的偽硬件地址復(fù)制到數(shù)據(jù)包的以太網(wǎng)包頭。Linux2.4.x使用設(shè)備方法hard_header()代替設(shè)備方法rebuild_header()。Linux2.x使用的rebuild_header()例程在本文的附加源程序中,這里不再說明。
編寫用戶空間串口通信程序
控制串口的server應(yīng)用程序完成非常簡(jiǎn)單的打包和拆包的工作,它沒有差錯(cuò)控制,沒有重發(fā)機(jī)制,在實(shí)際應(yīng)用中,需要加上適當(dāng)?shù)目刂茀f(xié)議。server創(chuàng)建的子進(jìn)程負(fù)責(zé)從串口讀取數(shù)據(jù)并把數(shù)據(jù)傳送到receiving device /dev/ed_rec;父進(jìn)程則負(fù)責(zé)從sending device /dev/ed_tx 讀取需要發(fā)送的網(wǎng)絡(luò)數(shù)據(jù)包,然后從串口發(fā)送出去。子進(jìn)程和父進(jìn)程都是用輪詢方式讀取和寫入設(shè)備。Server的程序流圖如圖所示。
圖 7
傳送的frame按照SLIP定義的格式:數(shù)據(jù)的兩頭都是END字符(0300),如圖8所示。
圖 8
特殊控制字符的定義如下:
#define END 0300
#define ESC 0333
#define ESC_END 0334
#define ESC_ESC 0335
linux操作系統(tǒng)文章專題:linux操作系統(tǒng)詳解(linux不再難懂)linux相關(guān)文章:linux教程
評(píng)論