快捷搜索:   服务器  安全  linux 安全  MYSQL  dedecms

Linux串口上网的简单实现(2)

    下面介绍一下网络设备的主要操作例程ednet_open()、ednet_release()、ednet_tx ()、ednet_stats ()、ednet_change_mtu()、ednet_header()。网络设备文件操作结构struct net_device(<linux/netdevice.h>中有定义)中定义了指向以上函数的函数指针的原形:

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)的作用是打开伪网络接口设备,获得其需要的I/O端口、IRQ等,但是本网络接口不需要和实际硬件打交道,所以不需要自动获得或者赋予I/O端口值,也不需要IRQ中断号,唯一需要程序指定的是其伪硬件地址(这个硬件地址是"0ED000",ifconfig可以看到其硬件地址是 00:45:44:30:30:30,struct net_device中的dev_addr域存放网络接口的物理地址。操作ednet_open()必须调用netif_start_queue()内核 API开启网络接口接收和发送数据队列。

    当接口关闭的时候,int ednet_release(struct net_device *dev)例程被系统调用,在ednet_release()中调用netif_stop_queque()将停止接收和发送队列的工作。

    伪网络设备驱动的传送例程int ednet_tx(struct sk_buff *skb, struct net_device *dev)将把要发送的网络数据包写入字符设备ed[ED_TX_DEVICE]。在发送完毕数据包的时候,dev_kfree_skb() Kernel API释放由上层协议栈分配的sk_buff数据块。伪网络接口在进行硬件传输的时候,需要为网络数据包打上时间戳。如果传送数据包的时候超时,将调用超时处理例程ednet_tx_timeout()超时处理例程。例程ednet_tx()调用真正的"硬件"传送例程ednet_hw_tx()在实际的网卡驱动程序中,就是真正向特定的网络硬件设备写数据的程序。我们看到,我们的"硬件"就是本文前面描述的字符设备,字符设备的操作例程. kernel_write()在ednet_hw_tx()将被调用。

     如果我们希望使用ifconfig看到伪网络接口的统计信息,那么系统就调用 struct net_device_stats *ednet_stats(struct net_device *dev)。我们看到,网络接口的统计信息被放到设备的私有数据指针指向的内存。网络数据信息的统计结构被放在内核结构struct net_device_stats中。

    在TCP会话中,也许要协商MTU的大小,int ednet_change_mtu(struct net_device *dev, int new_mtu)可以随时改变MTU的大小。比如在使用FTP协议的时候,在传送数据库的时候,MTU可能被协商为最大,以提高网络传送吞吐量。由于改变了MTU,存放网络数据的字符设备初始化分配的缓存区就要重新被分配,并把已经存放数据的旧的缓存区的内容拷贝到新的缓存区中,所以,当MTU改变大小的时候,那么就要使用kmalloc(new_mtu ,GFP_KERNEL)重新分配缓存区。读者可以根据自己的需要定义新的缓存区大小。kfree()是内核API,负责释放内核空间的内存,它的使用方法和用户空间的free()系统调用一致,这里就不列举ed_realloc()函数的源程序了。

    IP数据包在被网络接口发送前,需要构建其以太网头信息int ednet_header(struct sk_buff *skb,struct net_device *dev,unsigned short type,void *daddr,void *saddr,unsigned int len)例程完成此功能,我们看到网络数据包的以太源、目的地址,都是从发送这个数据包的网络接口设备数据结构struct net_device中得到的。源地址和目的地址信息是从网络设备结构得到的。在编译本程序的时候,如果发现htons()这个函数没有定义,可以这样定义htons()为:#define htons(x) ((x>>8) | (x<<8)) 。

    因为伪网络接口没有使用ARP获得硬件地址,所以我们可以把我们自己定义的伪硬件地址复制到数据包的以太网包头。Linux2.4.x使用设备方法hard_header()代替设备

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

编写用户空间串口通信程序

    控制串口的server应用程序完成非常简单的打包和拆包的工作,它没有差错控制,没有重发机制,在实际应用中,需要加上适当的控制协议。server创建的子进程负责从串口读取数据并把数据传送到receiving device /dev/ed_rec;父进程则负责从sending device /dev/ed_tx 读取需要发送的网络数据包,然后从串口发送出去。子进程和父进程都是用轮询方式读取和写入设备。Server的程序流图如图所示。


图 7

    传送的frame按照SLIP定义的格式:数据的两头都是END字符(0300),如图8所示。


图 8

特殊控制字符的定义如下:

#define END 0300 #define ESC 0333 #define ESC_END 0334 #define ESC_ESC 0335

    如果打包前的数据中有END这个字符,那么使用ESC_END代替,如果发现有ESC这个字符,那么使用ESC_ESC字符替换。在Linux环境下,串口名从ttyS0开始依次是 ttyS1、ttyS2等。在本程序中,使用ttyS0作为通信串口。在打开ttyS0的时候,选项O_NOCTTY 表示不能把本串口当成控制终端,否则用户的键盘输入信息将影响程序的执行; O_NDELAY表示打开串口的时候,程序并不关心另一端的串口是否在使用中。在Linux中,打开串口设备和打开普通文件一样,使用的是open()系统调用。比如我么打开串口设备1也就是COM1,只需要:

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

    打开的串口设备有很多设置选项。本文中使用int setup_com(int fd)设置。在系统头文件<termios.h>中定义了终端控制结构struct termios,tcgetattr()和tcsetattr()两个系统函数获得和设置这些属性。结构struct termios中的域描述的主要属性包括:

c_cflag : 控制选项 c_lflag : 线选项 c_iflag : 输入选项 c_oflag :输出选项 c_cc :控制字符 c_ispeed :输入数据波特率 c_ospeed :输出数据波特率

    如果要设置某个选项,那么就使用"|="运算,如果关闭某个选项就使用"&="和"~"运算。本文使用的各个选项的意义定义如下:

c_cflag: CLOCAL 本地模式,不改变端口的所有者 CREAD 表示使能数据接收器 PARENB 表示偶校验 PARODD 表示奇校验 CSTOPB 使用两个停止位 CSIZE 对数据的bit使用掩码 CS8 数据宽度是8bit c_lflag: ICANON 使能规范输入,否则使用原始数据(本文使用) ECHO 回送(echo)输入数据 ECHOE 回送擦除字符 ISIG 使能SIGINTR,SIGSUSP, SIGDSUSP和 SIGQUIT 信号 c_iflag: IXON 使能输出软件控制 IXOFF 使能输入软件控制 IXANY 允许任何字符再次开启数据流 INLCR 把字符NL(0A)映射到CR(0D) IGNCR 忽略字符CR(0D) ICRNL 把CR(0D)映射成字符NR(0A) c_oflag: OPOST 输出后处理,如果不设置表示原始数据(本文使用原始数据) c_cc[VMIN]: 最少可读数据 c_cc[VTIME]: 等待数据时间(10秒的倍数)

    根据以上设置的定义,串口端口设置函数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; }

    两个打包和拆包函数和SLIP协议定义的一样,拆包函数和打包相反,这里不列举了。

小结

    本文描述的是一个非常简单的串口上网程序,如果需要可靠的通信,增加吞吐量,可在用户空间添加适当的网络控制协议,也可增加数据压缩算法。

顶(0)
踩(0)

您可能还会对下面的文章感兴趣:

最新评论