當(dāng)前位置:首頁(yè) > 嵌入式 > 嵌入式軟件
[導(dǎo)讀]Linux串口上網(wǎng)的簡(jiǎn)單實(shí)現(xiàn)

Linux為串口上網(wǎng)提供了豐富的支持,比如PPP(Peer-to-Peer Protocol, 端對(duì)端協(xié)議)和SLIP(Serial Line Interface Protocol, 非常老的串行線路接口協(xié)議),這里所說(shuō)的"上網(wǎng)"是指把串口當(dāng)成一個(gè)網(wǎng)絡(luò)接口,通過(guò)封裝網(wǎng)絡(luò)數(shù)據(jù)包(如IP包)以達(dá)到無(wú)網(wǎng)卡的終端可以通過(guò)串口進(jìn)行網(wǎng)絡(luò)通信。但是使用這兩種協(xié)議必須得到內(nèi)核的支持。例如,如果在沒(méi)有配置PPP的Linux環(huán)境中使用PPP,除了安裝PPP應(yīng)用層軟件外,還必須重新編譯內(nèi)核。SLIP是一個(gè)比較老的簡(jiǎn)單的協(xié)議,現(xiàn)在的Linux內(nèi)核缺省配置都支持,不需要重新編譯內(nèi)核,盡管如此,其源代碼看上去有點(diǎn)"古怪而復(fù)雜"。在嵌入式Linux系統(tǒng)使用過(guò)程中,如果內(nèi)核已經(jīng)被燒入Flash中,而為了節(jié)省空間內(nèi)核又沒(méi)有提供諸如PPP或者SLIP的支持,當(dāng)然就沒(méi)有辦法在不重新燒寫(xiě)Flash的情況下直接使用PPP或者SLIP了,事實(shí)上用戶(hù)必須動(dòng)態(tài)加載PPP和SLIP的內(nèi)核實(shí)現(xiàn)模塊。對(duì)某些嵌入式應(yīng)用來(lái)說(shuō)移植或者修改PPP源代碼變成了乏味和繁鎖的工作。這里介紹一種非常經(jīng)濟(jì)而且實(shí)用的實(shí)現(xiàn)串口上網(wǎng)的簡(jiǎn)單方法。

Linux簡(jiǎn)單串口上網(wǎng)原理

簡(jiǎn)單串口上網(wǎng)的實(shí)現(xiàn)原理如圖1所示。


圖 1

Linux Box A 和 Linux Box B 是兩個(gè)安裝有Linux操作系統(tǒng)的終端(可以是PC,也可以是嵌入式設(shè)備),它們通過(guò)一條串口通信線(null modem cable line)連接。控制串口通信的服務(wù)進(jìn)程server讀和寫(xiě)兩個(gè)字符設(shè)備:發(fā)送字符設(shè)備sending device和接收字符設(shè)備receiving device。在內(nèi)核空間,偽網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)程序pseudo network driver可以直接讀寫(xiě)發(fā)送字符設(shè)備和接收字符設(shè)備,事實(shí)上在內(nèi)核空間它們之間的通信只是對(duì)共享緩存區(qū)的讀寫(xiě)而已。偽網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)程序具有大部分普通網(wǎng)卡驅(qū)動(dòng)程序提供服務(wù)功能,只是沒(méi)有硬件部分代碼的實(shí)現(xiàn)而已。當(dāng)用戶(hù)空間的進(jìn)程要發(fā)送數(shù)據(jù)的時(shí)候,其首先讓數(shù)據(jù)經(jīng)過(guò)Linux操作系統(tǒng)的TCP/IP處理層進(jìn)行數(shù)據(jù)打包,然后把打包后的數(shù)據(jù)直接寫(xiě)入sending device,等待server進(jìn)程讀取,最后通過(guò)串口發(fā)送到另一個(gè)Linux Box的server進(jìn)程;而當(dāng)server進(jìn)程發(fā)現(xiàn)有數(shù)據(jù)從串口傳送過(guò)來(lái)時(shí)就把數(shù)據(jù)寫(xiě)入receiving device,偽網(wǎng)絡(luò)驅(qū)動(dòng)程序發(fā)現(xiàn)receiving device設(shè)備有新數(shù)據(jù)的時(shí)候,就又把數(shù)據(jù)傳遞到TCP/IP層處理,最終網(wǎng)絡(luò)應(yīng)用程序收到對(duì)方發(fā)來(lái)的數(shù)據(jù)。本文設(shè)計(jì)的源程序主要有三個(gè),ed_device.c、ed_device.h、server.c。其中在ed_device.c是串口上網(wǎng)的內(nèi)核部分的主程序,包含字符設(shè)備和偽網(wǎng)絡(luò)接口設(shè)備程序,server.c負(fù)責(zé)串口通信。主文件ed_device.c中包括的頭文件在源程序中,這里就不一一列舉了。

Linux串口上網(wǎng)設(shè)備加載和注銷(xiāo)形式

Linux串口上網(wǎng)程序的整個(gè)內(nèi)核部分是以LKM(Loadable Kernel Module)形式實(shí)現(xiàn)的。LKM加載的時(shí)候完成偽網(wǎng)絡(luò)設(shè)備、發(fā)送字符設(shè)備、接收字符設(shè)備的初始化和注冊(cè)。注冊(cè)的目的是讓操作系統(tǒng)可以識(shí)別用戶(hù)進(jìn)程所要操作的設(shè)備,并完成在其上的操作(比如read,write等系統(tǒng)調(diào)用)。Linux加載模塊,實(shí)際上就是模塊鏈表的插入;刪除模塊象是模塊鏈表成員的刪除。

初始化內(nèi)核模塊入口函數(shù)init_module()中包括對(duì)字符設(shè)備的初始化入口 函數(shù)eddev_module_init()和偽網(wǎng)絡(luò)設(shè)備初始化入口函數(shù)ednet_module_init()。

在內(nèi)核需要卸載的時(shí)候,必須進(jìn)行資源釋放以及設(shè)備注銷(xiāo), cleanup_module()完成這個(gè)任務(wù)。函數(shù)cleanup_module()中用eddev_module_cleanup()來(lái)釋放字符設(shè)備占用的資源(比如分配的緩存區(qū)等);有ednet_module_cleanup()來(lái)釋放偽網(wǎng)絡(luò)設(shè)備占用的資源。本文的內(nèi)核部分模塊程序編譯后就是ed_device.o,加載后使用lsmod命令查看,模塊名就是ed_device。模塊ed_device的加載和注銷(xiāo)函數(shù)如圖2所示。


圖 2

當(dāng)我們需要加載模塊的時(shí)候,我們只需要使用insmod命令,如果需要卸載模塊,我們使用rmmod命令。比如加載ed_device模塊,并且配置偽網(wǎng)絡(luò)接口IP地址為192.168.5.1

[root@localhost test]insmod ed_device.o,
[root@localhost test]ifconfig ed0 192.168.5.1 up

這時(shí)可以在/proc/net/dev 文件中看到有ed0偽網(wǎng)絡(luò)設(shè)備了。如果需要卸載ed_device模塊,應(yīng)先停止其網(wǎng)絡(luò)數(shù)據(jù)發(fā)送和接收工作,然后卸載模塊:

[root@localhost test]ifconfig ed0 down
[root@localhost test]rmmod ed_device

如果我們?cè)O(shè)置另一臺(tái)Linux box的偽網(wǎng)接口地址是192.168.5.2那么,我們可以用串口線直接連接兩臺(tái)終端并使用網(wǎng)絡(luò)應(yīng)用程序了,在兩臺(tái)終端上運(yùn)行server守護(hù)程序,然后執(zhí)行telnet:

[root@localhost test]# telnet 192.168.5.2
Trying 192.168.5.2...
Connected to 192.168.5.2 (192.168.5.2).
Escape character is '^]'.
Red Hat Linux release 9 (Shrike)
Kernel 2.4.20-8 on an i686
login:

編寫(xiě)字符設(shè)備驅(qū)動(dòng)程序

用戶(hù)空間的進(jìn)程主要通過(guò)兩種方式和內(nèi)核空間模塊打交道,一種是使用proc文件系統(tǒng),另一種是使用字符設(shè)備。本文所描述的兩個(gè)字符設(shè)備sending device 和receiving device事實(shí)上是內(nèi)核空間和用戶(hù)空間交換數(shù)據(jù)的緩存區(qū),編寫(xiě)字符設(shè)備驅(qū)動(dòng)實(shí)際上就是編寫(xiě)用戶(hù)空間讀寫(xiě)字符設(shè)備所需要的內(nèi)核設(shè)備操作函數(shù)。

在頭文件中,我們定義ED_REC_DEVICE為receiving device,名字是ed_rec;定義ED_TX_DEVICE為sending device,名字是ed_tx。

#define MAJOR_NUM_REC 200
#define MAJOR_NUM_TX  201
#define IOCTL_SET_BUSY _IOWR(MAJOR_NUM_TX,1,int)

200和201分別代表receiving device 和 sending device的主設(shè)備號(hào)。在內(nèi)核空間,驅(qū)動(dòng)程序是根據(jù)主、次設(shè)備號(hào)識(shí)別設(shè)備的,而不是設(shè)備名;本文的字符設(shè)備的次設(shè)備號(hào)都是0,主設(shè)備號(hào)是用戶(hù)定義的且不能和系統(tǒng)已有的設(shè)備的主設(shè)備有沖突。IOCTL_SET_BUSY _IOWR(MAJOR_NUM_TX,1,int)是ioctl的操作函數(shù)定義(從用戶(hù)空間發(fā)送命令到內(nèi)核空間),主要作用是使得每次在同一時(shí)間,同一字符設(shè)備上,只可進(jìn)行一次操作。我們可以使用mknod來(lái)建立這兩個(gè)字符設(shè)備:

[root@localhost]#mknod c 200 0 /dev/ed_rec
[root@localhost]#mknod c 201 0 /dev/ed_tx

設(shè)備建立后,編譯好的模塊就可以動(dòng)態(tài)加載了:

[root@localhost]#insmod ed_device.o

為了方便對(duì)設(shè)備編程,我們還需要一個(gè)字符設(shè)備管理的數(shù)據(jù)結(jié)構(gòu):

struct ed_device{
	int magic;
	char name[8]; 	
	int busy;
	unsigned char *buffer;
    #ifdef LINUX_24
wait_queue_head_t rwait;
#endif
	int mtu;
	spinlock_t lock;
	int data_len;
    int buffer_size;
	struct file *file;
    ssize_t (*kernel_write)(const char *buffer,size_t length,int buffer_size);
};

這個(gè)數(shù)據(jù)結(jié)構(gòu)是用來(lái)保存字符設(shè)備的一些基本狀態(tài)信息。ssize_t (*kernel_write)(const char *buffer,size_t length,int buffer_size) 是一個(gè)指向函數(shù)的指針,它的作用是為偽網(wǎng)絡(luò)驅(qū)動(dòng)程序提供寫(xiě)字符設(shè)備數(shù)據(jù)的系統(tǒng)調(diào)用接口。magic字段主要是標(biāo)志設(shè)備類(lèi)型號(hào)的,這里沒(méi)有別的特殊意義;busy字段用來(lái)說(shuō)明字符設(shè)備是否是處于忙狀態(tài),buffer指向內(nèi)核緩存區(qū),用來(lái)存放讀寫(xiě)數(shù)據(jù);mtu保存當(dāng)前可發(fā)送的網(wǎng)絡(luò)數(shù)據(jù)包最大傳輸單位,以字節(jié)為單位;lock的類(lèi)型是自旋鎖類(lèi)型spinlock_t,它實(shí)際以一個(gè)整數(shù)域作為鎖,在同一時(shí)刻對(duì)同一字符設(shè)備,只能有一個(gè)操作,所以使用內(nèi)核鎖機(jī)制保護(hù)防止數(shù)據(jù)污染;data_len是當(dāng)前緩存區(qū)內(nèi)保存的數(shù)據(jù)實(shí)際大小,以字節(jié)為單位;file是指向設(shè)備文件結(jié)構(gòu)struct file的一個(gè)指針,其作用主要是定位設(shè)備的私有數(shù)據(jù) file-> private_data。定義字符設(shè)備struct ed_device ed[2],其中ed[ED_REC_DEVICE]就是receving device,ed[ED_TX_DEVICE]就是sending device。如果sending device ED_TX_DEVICE沒(méi)有數(shù)據(jù),用戶(hù)空間的read調(diào)用將被阻塞,并把進(jìn)程信息放于rwait隊(duì)列中。當(dāng)有數(shù)據(jù)的時(shí)候,kernel_write()中的wake_up_interruptible()將喚醒等待進(jìn)程。kernel_write()函數(shù)定義如下:

ssize_t kernel_write(const char *buffer,size_t length,int buffer_size)
{
    if(length > buffer_size )
        length = buffer_size;
    memset(ed[ED_TX_DEVICE].buffer,0,buffer_size);
    memcpy(ed[ED_TX_DEVICE].buffer,buffer,buffer_size);
    ed[ED_TX_DEVICE].tx_len = length;
    #ifdef LINUX_24
    wake_up_interruptible(&ed[ED_TX_DEVICE].rwait);	
    #endif   
    return length;
}

字符設(shè)備的操作及其相關(guān)函數(shù)調(diào)用過(guò)程如圖3 所示。


圖 3

當(dāng)ed_device模塊被加載的時(shí)候,eddev_module_init()調(diào)用register_chrdev()內(nèi)核API注冊(cè)ed_tx和ed_rec兩個(gè)字符設(shè)備。這個(gè)函數(shù)定義在<linux/fs.h>:

int register_chdev(unsigned int major, const char *, struct fle_operations *fops)

字符設(shè)備被注冊(cè)成功后,內(nèi)核把這兩個(gè)字符設(shè)備加入到內(nèi)核字符設(shè)備驅(qū)動(dòng)表中。內(nèi)核字符設(shè)備驅(qū)動(dòng)表保留指向struct file_operations的一個(gè)數(shù)據(jù)指針。用戶(hù)進(jìn)程調(diào)用設(shè)備讀寫(xiě)操作時(shí),通過(guò)這個(gè)指針訪問(wèn)設(shè)備的操作函數(shù), struct file_operations中的域大部分是指向函數(shù)的函數(shù)指針,指向用戶(hù)自己編寫(xiě)的設(shè)備操作函數(shù)。

struct file_operations ed_ops ={
#ifdef LINUX_24
    NULL,
#endif
    NULL,
    device_read,
    device_write,
    NULL,
    NULL,
    device_ioctl,
    NULL,
    device_open,
    NULL,
    device_release,    	
};

注意到Linux2.4.x和Linux2.2.x內(nèi)核中定義的struct file_operations是不一樣的。device_read()、device_write()、device_ioctl()、device_open()、device_release()就是需要用戶(hù)自己定義的函數(shù)操作了,這幾個(gè)函數(shù)是最基本的操作,如果需要設(shè)備驅(qū)動(dòng)程序完成更復(fù)雜的任務(wù),還必須編寫(xiě)其他struct file_operations中定義的操作。eddev_module_init()除了注冊(cè)設(shè)備及其操作外,它還有初始化字符設(shè)備結(jié)構(gòu)struct ed_device,分配內(nèi)核緩存區(qū)所需要的空間的作用。在內(nèi)核空間,分配內(nèi)存空間的API函數(shù)是kmalloc()。

下面介紹一下字符設(shè)備的主要操作例程device_open()、device_release()、device_read()、devie_write()。字符設(shè)備文件操作結(jié)構(gòu)ed_ops中定義的指向以上函數(shù)的函數(shù)指針的原形:

	 device_open:  int(*open)(struct inode *,struct file *)     
     device_release: int (*release) (struct inode *, struct file *);
     device_read:  ssize_t (*read) (struct file *, char *, size_t, loff_t *);
     device_write: ssize_t (*write) (struct file *, const char *, size_t, loff_t *);

操作int device_open(struct inode *inode,struct file *file)是設(shè)備節(jié)點(diǎn)上的第一個(gè)操作,如果多個(gè)設(shè)備共享這一個(gè)操作函數(shù),必須區(qū)分設(shè)備的設(shè)備號(hào)。我們使用inode->i_rdev >> 8 語(yǔ)句獲得設(shè)備的主設(shè)備號(hào),本文中的接收設(shè)備主設(shè)備號(hào)是200,發(fā)送設(shè)備號(hào)是201。每個(gè)字符設(shè)備的file>private_data指向打開(kāi)設(shè)備時(shí)候使用的file結(jié)構(gòu),private_data實(shí)際上可以指向用戶(hù)定義的任何結(jié)構(gòu),這里只指向我們自己定義的struct ed_device,用來(lái)保存字符設(shè)備的一些基本信息,比如設(shè)備名、內(nèi)核緩存區(qū)等。

操作ssize_t device_read(struct file *file,char *buffer,size_t length, loff_t *offset)是讀取設(shè)備數(shù)據(jù)的操作。device_read()結(jié)構(gòu)如圖4所示。


圖4

從設(shè)備中讀取數(shù)據(jù)(用戶(hù)空間調(diào)用read()系統(tǒng)調(diào)用)的時(shí)候,需要從內(nèi)核空間把數(shù)據(jù)拷貝到用戶(hù)空間,copy_to_user()可完成此功能,它和memcpy()此類(lèi)函數(shù)有本質(zhì)的區(qū)別,memcpy()不能完成不同用戶(hù)空間數(shù)據(jù)的交換。如果需要數(shù)據(jù)臨界區(qū)的保護(hù),使用spin_lock()內(nèi)核API負(fù)責(zé)加鎖,spin_unlock()負(fù)責(zé)解鎖,防止數(shù)據(jù)污染。由于串口守候進(jìn)程server需要不斷輪詢(xún)?cè)O(shè)備,以查詢(xún)是否有數(shù)據(jù)可讀,如果用戶(hù)進(jìn)程不處于休眠狀態(tài),在用戶(hù)空間查看進(jìn)程使用資源情況,發(fā)現(xiàn)server占用了很多CPU資源。所以我們改進(jìn)device_read(),使之在內(nèi)核中輪詢(xún),當(dāng)發(fā)現(xiàn)當(dāng)前設(shè)備沒(méi)有數(shù)據(jù)可讀取,那么就阻塞用戶(hù)進(jìn)程,使用內(nèi)核API add_wait_queue()可完成此功能,這時(shí)候用戶(hù)進(jìn)程并沒(méi)有占用很多CPU資源,而是處于休眠狀態(tài)。當(dāng)內(nèi)核發(fā)現(xiàn)有數(shù)據(jù)可讀的時(shí)候,調(diào)用remove_wait_queue()即可喚醒等待進(jìn)程,這段

代碼如下:

    DECLARE_WAITQUEUE(wait,current);
    add_wait_queue(&edp->rwait,&wait);
    for(;;){        
        set_current_state(TASK_INTERRUPTIBLE);
        if ( file->f_flags & O_NONBLOCK)
            break;
        /*其他代碼 */
        if ( signal_pending(current))
            break;
        schedule();
    }
    set_current_state(TASK_RUNNING);
remove_wait_queue(&edp->rwait,&wait);

操作ssize_t device_write(struct file *file,const char *buffer, size_t length,loff_t *offset)向設(shè)備寫(xiě)入數(shù)據(jù)。拷貝數(shù)據(jù)的copy_from_user()和copy_to_user()的功能恰恰相反,它是從用戶(hù)空間拷貝數(shù)據(jù)到內(nèi)核空間,如圖5所示。


圖 5

編寫(xiě)偽網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)程序

偽網(wǎng)絡(luò)驅(qū)動(dòng)程序和字符設(shè)備驅(qū)動(dòng)程序一樣,也必須初始化和注冊(cè)。網(wǎng)絡(luò)驅(qū)動(dòng)需記錄其發(fā)送和接收數(shù)據(jù)量的統(tǒng)計(jì)信息,所以我們定義一個(gè)記錄這些信息的數(shù)據(jù)結(jié)構(gòu)。

struct ednet_priv {
#ifdef LINUX_24
    struct net_device_stats stats;
#else
    struct enet_statistics stats;
#endif
    struct sk_buff *skb;
    spinlock_t lock;
};

struct ednet_priv只有3個(gè)數(shù)據(jù)成員。Linux2.4.x 使用的網(wǎng)絡(luò)數(shù)據(jù)狀態(tài)統(tǒng)計(jì)結(jié)構(gòu)是struct net_device_stats,而Linux 2.2.x則使用的是struct enet_statistics。同樣,對(duì)控制網(wǎng)絡(luò)接口設(shè)備的設(shè)備結(jié)構(gòu)也有不同的定義:Linux2.4.x使用的是struct net_device,而Linux2.2.x卻是struct device。

#ifdef LINUX_24
struct net_device ednet_dev;
#else
struct device ednet_dev;
#endif

偽網(wǎng)絡(luò)驅(qū)動(dòng)程序的也需要初始化和注冊(cè)。和字符設(shè)備的注冊(cè)不同之處是,它使用的是register_netdev(net_device *) kernel API。

int ednet_module_init(void)
{
    int err;
    strcpy(ednet_dev.name, "ed0");
    ednet_dev.init = ednet_init;
    if ( (err = register_netdev(&ednet_dev)) )
            printk("ednet: error %i registering pseudo network device "%s"
",
                   err, ednet_dev.name);
        
    return err;
}

ednet_dev的name域是接口名,ednet_module_init()中賦予網(wǎng)絡(luò)接口的名字為ed0,如果本網(wǎng)絡(luò)設(shè)備被加載,使用ifconfig命令可以看到ed0。

[root@localhost pku]# /sbin/ifconfig
ed0       Link encap:Ethernet  HWaddr 00:45:44:30:30:30
          inet addr:192.168.3.9  Bcast:192.168.3.255  Mask:255.255.255.0
          UP BROADCAST RUNNING NOARP MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:100
          RX bytes:0 (0.0 b)  TX bytes:0 (0.0 b)

我們看到我們的偽網(wǎng)絡(luò)接口沒(méi)有Interrupt和Base address,這是因?yàn)檫@個(gè)偽網(wǎng)絡(luò)接口不和硬件打交道,也沒(méi)有分配中斷號(hào)和IO基址。否則,如果你看一個(gè)實(shí)實(shí)在在的網(wǎng)絡(luò)接口(如下面的eth1),可以看到它的Interrupt號(hào)是11和IO Base address是0xa000。

eth1      Link encap:Ethernet  HWaddr 50:78:4C:43:1D:01
          inet addr:192.168.21.202  Bcast:192.168.21.255  Mask:255.255.255.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:356523 errors:0 dropped:0 overruns:0 frame:0
          TX packets:266 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:100
          RX bytes:21542043 (20.5 Mb)  TX bytes:19510 (19.0 Kb)
          Interrupt:11 Base address:0xa000

ednet_dev的init域是一個(gè)函數(shù)指針,指向用戶(hù)定義的ednet_init()例程。ednet_init()添充net_device結(jié)構(gòu),只有ednet_init()初始化成功后,系統(tǒng)才被加入到設(shè)備鏈表中。ednet_dev的初始化例程ednet_init()如下:

#ifdef LINUX_24
int ednet_init(struct net_device *dev)
#else
int ednet_init(struct device *dev)
#endif
{  
    ether_setup(dev); 
    dev->open            = ednet_open;
    dev->stop            = ednet_release;
    dev->hard_start_xmit   = ednet_tx;
    dev->get_stats         = ednet_stats;
    dev->change_mtu      = ednet_change_mtu;  
#ifdef LINUX_24
    dev->hard_header      = ednet_header;
#endif
    dev->rebuild_header    = ednet_rebuild_header;
#ifdef LINUX_24
    dev->tx_timeout        = ednet_tx_timeout;
    dev->watchdog_timeo   = timeout;
#endif
    /* We do not need the ARP protocol. */
    dev->flags           |= IFF_NOARP;
#ifndef LINUX_20                        
    dev->hard_header_cache = NULL;      
#endif 
#ifdef LINUX_24                                 
    SET_MODULE_OWNER(dev);
#endif
    dev->priv = kmalloc(sizeof(struct ednet_priv), GFP_KERNEL);
    if (dev->priv == NULL)
        return -ENOMEM;
    memset(dev->priv, 0, sizeof(struct ednet_priv));
    spin_lock_init(& ((struct ednet_priv *) dev->priv)->lock);
    return 0;
}

ether_setup()填充一些以太網(wǎng)的缺省設(shè)置。dev->hard_header_cache=NULL表示不緩存向本網(wǎng)絡(luò)接口回復(fù)的ARP網(wǎng)絡(luò)數(shù)據(jù)包。IFF_NOARP的標(biāo)志設(shè)置表明本網(wǎng)絡(luò)接口不使用ARP。ARP的主要功能是獲得通信對(duì)方的網(wǎng)絡(luò)接口的硬件地址,本文的偽網(wǎng)絡(luò)接口的物理地址是程序中設(shè)定的偽物理地址,所以我們不需要ARP協(xié)議。SET_MODULE_OWNER(dev)這個(gè)宏是設(shè)置dev結(jié)構(gòu)中owner域(定義為struct module *owner;),使得它指向本模塊本身。與字符設(shè)備一樣,本網(wǎng)絡(luò)設(shè)備也需要定義在其上的操作例程。下面就對(duì)ednet_init()中用戶(hù)定義的設(shè)備操作函數(shù)做進(jìn)一步說(shuō)明。整個(gè)偽網(wǎng)絡(luò)設(shè)備操作調(diào)用結(jié)構(gòu)如圖6所示。


圖 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ù)的用戶(hù)進(jìn)程,也就是說(shuō),ednet_rx()應(yīng)該由那個(gè)網(wǎng)絡(luò)中斷處理例程調(diào)用。我們這里并沒(méi)有中斷,所以字符設(shè)備的device_write()可以看成是一個(gè)"中斷例程",也就是說(shuō),用戶(hù)空間往字符寫(xiě)操作的時(shí)候,也就調(diào)用了網(wǎng)絡(luò)設(shè)備的數(shù)據(jù)接收內(nèi)核例程ednet_rx()了。然后ednet_rx()會(huì)把原始的數(shù)據(jù)包發(fā)送到TCP/IP上層進(jìn)行處理,這一切均依賴(lài)于內(nèi)核API 函數(shù)netif_rx()。ednet_rx()就需要sk_buff數(shù)據(jù)結(jié)構(gòu)(<linux/skbuff.h>中定義),用來(lái)存放從網(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)的作用是打開(kāi)偽網(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開(kāi)啟網(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ù)包寫(xiě)入字符設(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è)備寫(xiě)數(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ì)話(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)存,它的使用方法和用戶(hù)空間的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ù)沒(méi)有定義,可以這樣定義htons()為:#define htons(x) ((x>>8) | (x<<8)) 。

因?yàn)閭尉W(wǎng)絡(luò)接口沒(méi)有使用ARP獲得硬件地址,所以我們可以把我們自己定義的偽硬件地址復(fù)制到數(shù)據(jù)包的以太網(wǎng)包頭。Linux2.4.x使用設(shè)備方法hard_header()代替設(shè)備

方法rebuild_header()。Linux2.x使用的rebuild_header()例程在本文的附加源程序中,這里不再說(shuō)明。

編寫(xiě)用戶(hù)空間串口通信程序

控制串口的server應(yīng)用程序完成非常簡(jiǎn)單的打包和拆包的工作,它沒(méi)有差錯(cuò)控制,沒(méi)有重發(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)程都是用輪詢(xún)方式讀取和寫(xiě)入設(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 

如果打包前的數(shù)據(jù)中有END這個(gè)字符,那么使用ESC_END代替,如果發(fā)現(xiàn)有ESC這個(gè)字符,那么使用ESC_ESC字符替換。在Linux環(huán)境下,串口名從ttyS0開(kāi)始依次是ttyS1、ttyS2等。在本程序中,使用ttyS0作為通信串口。在打開(kāi)ttyS0的時(shí)候,選項(xiàng)O_NOCTTY 表示不能把本串口當(dāng)成控制終端,否則用戶(hù)的鍵盤(pán)輸入信息將影響程序的執(zhí)行; O_NDELAY表示打開(kāi)串口的時(shí)候,程序并不關(guān)心另一端的串口是否在使用中。在Linux中,打開(kāi)串口設(shè)備和打開(kāi)普通文件一樣,使用的是open()系統(tǒng)調(diào)用。比如我么打開(kāi)串口設(shè)備1也就是COM1,只需要:

fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY | O_NDELAY );

打開(kāi)的串口設(shè)備有很多設(shè)置選項(xiàng)。本文中使用int setup_com(int fd)設(shè)置。在系統(tǒng)頭文件<termios.h>中定義了終端控制結(jié)構(gòu)struct termios,tcgetattr()和tcsetattr()兩個(gè)系統(tǒng)函數(shù)獲得和設(shè)置這些屬性。結(jié)構(gòu)struct termios中的域描述的主要屬性包括:

c_cflag  : 控制選項(xiàng)
c_lflag  : 線選項(xiàng)
c_iflag  : 輸入選項(xiàng)
c_oflag  :輸出選項(xiàng)
c_cc    :控制字符
c_ispeed :輸入數(shù)據(jù)波特率
c_ospeed :輸出數(shù)據(jù)波特率

如果要設(shè)置某個(gè)選項(xiàng),那么就使用"|="運(yùn)算,如果關(guān)閉某個(gè)選項(xiàng)就使用"&="和"~"運(yùn)算。本文使用的各個(gè)選項(xiàng)的意義定義如下:

c_cflag: CLOCAL 本地模式,不改變端口的所有者
         CREAD  表示使能數(shù)據(jù)接收器
         PARENB  表示偶校驗(yàn)
         PARODD 表示奇校驗(yàn)
CSTOPB  使用兩個(gè)停止位
CSIZE    對(duì)數(shù)據(jù)的bit使用掩碼
CS8      數(shù)據(jù)寬度是8bit
c_lflag:  ICANON 使能規(guī)范輸入,否則使用原始數(shù)據(jù)(本文使用)
ECHO    回送(echo)輸入數(shù)據(jù)
ECHOE   回送擦除字符
ISIG      使能SIGINTR,SIGSUSP, SIGDSUSP和 SIGQUIT 信號(hào)
c_iflag:  IXON     使能輸出軟件控制
         IXOFF    使能輸入軟件控制
         IXANY    允許任何字符再次開(kāi)啟數(shù)據(jù)流
         INLCR    把字符NL(0A)映射到CR(0D)
         IGNCR    忽略字符CR(0D)
       ICRNL    把CR(0D)映射成字符NR(0A)
    c_oflag: OPOST  輸出后處理,如果不設(shè)置表示原始數(shù)據(jù)(本文使用原始數(shù)據(jù)) 
c_cc[VMIN]:  最少可讀數(shù)據(jù)
c_cc[VTIME]: 等待數(shù)據(jù)時(shí)間(10秒的倍數(shù))

根據(jù)以上設(shè)置的定義,串口端口設(shè)置函數(shù)setup_com()定義如下:

int setup_com(int fd){
    struct termios options; 
    tcgetattr(fd, &options);
    /* Set the baud rates to 38400...*/
    cfsetispeed(&options, B38400);
    cfsetospeed(&options, B38400);
    /* Enable the receiver and set local mode...*/
    options.c_cflag |= (CLOCAL | CREAD);
    /* Set c_cflag options.*/
    options.c_cflag |= PARENB;
    options.c_cflag &= ~PARODD;
    options.c_cflag &= ~CSTOPB;
    options.c_cflag &= ~CSIZE;
    options.c_cflag |= CS8;    
    /* Set c_iflag input options */
    options.c_iflag &=~(IXON | IXOFF | IXANY);
    options.c_iflag &=~(INLCR | IGNCR | ICRNL);
    options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
    /* Set c_oflag output options */
    options.c_oflag &= ~OPOST;   
    /* Set the timeout options */
    options.c_cc[VMIN]  = 0;
    options.c_cc[VTIME] = 10;
    tcsetattr(fd, TCSANOW, &options);
    return 1;
}

兩個(gè)打包和拆包函數(shù)和SLIP協(xié)議定義的一樣,拆包函數(shù)和打包相反,這里不列舉了。

小結(jié)

本文描述的是一個(gè)非常簡(jiǎn)單的串口上網(wǎng)程序,如果需要可靠的通信,增加吞吐量,可在用戶(hù)空間添加適當(dāng)?shù)木W(wǎng)絡(luò)控制協(xié)議,也可增加數(shù)據(jù)壓縮算法。


 

本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請(qǐng)聯(lián)系該專(zhuān)欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請(qǐng)及時(shí)聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車(chē)的華為或?qū)⒋呱龈蟮莫?dú)角獸公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

倫敦2024年8月29日 /美通社/ -- 英國(guó)汽車(chē)技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車(chē)工程師從創(chuàng)意到認(rèn)證的所有需求的工具,可用于創(chuàng)建軟件定義汽車(chē)。 SODA V工具的開(kāi)發(fā)耗時(shí)1.5...

關(guān)鍵字: 汽車(chē) 人工智能 智能驅(qū)動(dòng) BSP

北京2024年8月28日 /美通社/ -- 越來(lái)越多用戶(hù)希望企業(yè)業(yè)務(wù)能7×24不間斷運(yùn)行,同時(shí)企業(yè)卻面臨越來(lái)越多業(yè)務(wù)中斷的風(fēng)險(xiǎn),如企業(yè)系統(tǒng)復(fù)雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務(wù)連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報(bào)道,騰訊和網(wǎng)易近期正在縮減他們對(duì)日本游戲市場(chǎng)的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國(guó)國(guó)際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)開(kāi)幕式在貴陽(yáng)舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導(dǎo)體

8月28日消息,在2024中國(guó)國(guó)際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)上,華為常務(wù)董事、華為云CEO張平安發(fā)表演講稱(chēng),數(shù)字世界的話(huà)語(yǔ)權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機(jī) 衛(wèi)星通信

要點(diǎn): 有效應(yīng)對(duì)環(huán)境變化,經(jīng)營(yíng)業(yè)績(jī)穩(wěn)中有升 落實(shí)提質(zhì)增效舉措,毛利潤(rùn)率延續(xù)升勢(shì) 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務(wù)引領(lǐng)增長(zhǎng) 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競(jìng)爭(zhēng)力 堅(jiān)持高質(zhì)量發(fā)展策略,塑強(qiáng)核心競(jìng)爭(zhēng)優(yōu)勢(shì)...

關(guān)鍵字: 通信 BSP 電信運(yùn)營(yíng)商 數(shù)字經(jīng)濟(jì)

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺(tái)與中國(guó)電影電視技術(shù)學(xué)會(huì)聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會(huì)上宣布正式成立。 活動(dòng)現(xiàn)場(chǎng) NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長(zhǎng)三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會(huì)上,軟通動(dòng)力信息技術(shù)(集團(tuán))股份有限公司(以下簡(jiǎn)稱(chēng)"軟通動(dòng)力")與長(zhǎng)三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉