Linux USB 驱动开发实例 (三)—— 基于USB总线的无线网卡浅析

回顾一下USB的相关知识

       USB(Universal Serial Bus)总线又叫通用串行外部总线,它是20世纪90年代发展起来的。USB接口现在得到了广泛的应用和普及,现在的PC机中都带有大量的USB接口。它最大的特点就是方便通用、支持热插拔并且可以在一个接口上插上多个设备。当设备用电量小的时候,它还可以充当电源。它的众多优点使得它得到了广泛的应用。

       在PC机器内部有个USB中央控制器,这个中央控制器负责管理插到USB接口上的设备。当主机要向设备发送或接受数据时,都是向USB中央控制器发出命令USB设备不具备主动与主机通信的能力。编写USB设备驱动不用考虑申请设备地址空间,因为USB中央控制器会给设备分配一个设备号,这个设备号就代表这个设备。  

       USB设备和USB中央控制器之间的通信是通过端点来完成的。端点的职能有点类似一栋大楼的传达室。例如每个楼层都有一个传达室,当要访问5楼的10号房间时,那就是向5号端点发起对话,并提供偏移量,也就10号房间。USB接口的端点按传输信息的类型分为以下4种:  

a -- 控制端点

     主要用来传输控制信息的,例如配置设备时发出的控制信息。控制端点一般都是双向,既可以输入又可以输出。其他端点的输出方向一般是单向的,要么是输入,要么是输出的。这里是站在主机的角度来谈论输入输出的。  

b -- 中断端点

     主要用来传输中断信息的,由于USB设备是受USB中央控制器管理的,因此USB设备没有向主机发出中断的能力,并且USB设备不能主动向主机发出请求,只有主机可以向USB设备发出命令请求,因此所谓的中断是指主机周期性的查询USB设备。  

c -- 批量端点

     主要用来传输批量信息的,批量信息就意味着大量的信息。U盘一般主要使用的就是批量端点。本文研究的USB无线网卡也是使用批量断点来传输数据的。发送和接收函数都是使用批量端点和USB设备传输数据的。  

b -- 等时端点

    主要用来传输等时信息的,主要用于传输实时性要求较高的信息,例如实时的音频、视频等信息。有代表性的USB设备是USB摄像头等。  

    在一个具体的USB设备中不要求一定都存在这4种类型的端点,例如U盘一般就只有批量端点和控制端点。在Linux内核中用来描述USB设备端点信息的数据结构如下:

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. struct usb_endpoint_descriptor {            
  2.     __u8  bLength;            
  3.     __u8  bDescriptorType;            
  4.     __u8  bEndpointAddress;            
  5.     __u8  bmAttributes;            
  6.     __le16 wMaxPacketSize;            
  7.     __u8  bInterval;                
  8.     __u8  bRefresh;            
  9.     _u8  bSynchAddress;    
  10. } __attribute__ ((packed));   

      成员bLength描述本数据结构共有多少字节,因为后两个成员是针对音频设备的,如果不是音频设备则可以没有后两个成员。成员bDescriptorType是描述本数据结构要描述的类型,这里是描述端点的,在内核中0x05就代表端点

      成员bEndpointAddress包含端点号和输出方向,bits0-bits3表示的是端点号,从这里可以看出一个USB设备最多只能有不超过16个端点,bits8是代表传输方向的,如果该位是1就代表输入,也就是读设备;如果该位为0就代表输出,也就是写设备。

     成员bmAttributes 表示该端点的类型,如上述的4种类型。

     成员wMaxPacketSize表示该端点一次可以传输的最多字节数。如果要传输的数据大于这个数字,那就要分多次传输。成员bInterval代表的是该端点希望主机轮询自己的时间间隔,这只是一种希望,具体还要看主机怎么做。

     该数据结构最后的__attribute__((packed))代表在分配该数据结构时数据成员之间不要为了内存对齐而留下空隙,例如有这样一个数据结构的相邻两个成员类型是u8 和u16,在一般情况下u8后面要空一个字节,然后才是u16成员,如果有上面attribute的要求后,在u8后面就不要留空间,紧接着就是u16成员。在内核中有很多需要访问设备的数据结构都有这样的要求,因为在一个设备中一般没有内存对齐的要求。


 

一、USB设备驱动程序的构成

1、设备的探测

       用于检查传递给探测函数的设备信息,确认驱动程序是否适合该设备。

2、数据的发送和接收

       负责主机到设备的发送和设备到主机的数据接收。

3、设备断开

      当设备断开时候,模块负责清除和该设备关联的所有资源。

4、模块的加载和卸载

      用于加载和卸载usb接口的无线网卡驱动程序。


二、USB无线网卡的构成

        USB无线网卡主要由USB接口、MAC控制器、基带处理、调制解调器、功率放大器收发器及天线等组成。

        MAC控制器是核心部件,它负责从主机读取数据并发送出去,或者接收数据并发送给主机等。它负责通道选择、速率选择、加密解密等等的控制。

        固件存储区是用来存储MAC控制器要运行的微码。固件是一种经过编译的可执行代码,一般是由设备的芯片来执行的。

       帧缓存就是用来存储数据的暂时场所。

       EEPROM是否有没有要看具体的设备,有的设备是没有的,EEPROM一般都存放一些本设备的一些参数,例如本设备的MAC地址,本设备在家族产品中的型号等等。

       基带处理和ADC、DAC是数模拟转换的功能部分。要发送的数据或者接收的模拟信号在这个地方进行转换。

       收发器的功能类似调制解调器,收发器内部有个功率放大器,把弱信号增强到一定的强信号,收发器还负责滤波等工作。

      天线系统就是负责把数据通过天线发送或接收。天线的作用是使传输距离更远。

USB接口无线网卡的硬件逻辑:



USB无线网卡的通道和速率是多个的,在发送和接收时通道和速率是可以变换的。在Linux中通道用如下数据结构表示:

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. struct ieee80211_channel {  
  2.     enum ieee80211_band band;    
  3.     u16 center_freq;    
  4.     u16 hw_value;   
  5.     u32 flags;     
  6.     int max_antenna_gain;    
  7.     int max_power;    
  8.     bool beacon_found;    
  9.     u32 orig_flags;     
  10.     int orig_mag, orig_mpwr;    
  11. };  


三、模块的加载

       在编写USB无线网卡驱动函数之前,首先先了解一下设备在插入到USB接口到设备成功找到它自己的驱动这一过程。

1、获取设备一些信息,发生在USB核心

      当把USB设备插到USB接口上后,USB主机控制器会检测到有设备插入USB接口了,Linux内核会给设备分配一个数据结构来代表这个设备。本文中涉及的硬件是USB设备,因此Linux会分配一个struct usb_device数据结构来代表该设备,该数据结构记录设备的一些属性及数据。并把该数据结构挂载到一个全局的USB设备链上。在这一期间主机通过0号端点(控制端点)得知了设备的一些信息,并知道了设备的厂家号和产品号。

2、找到匹配的驱动,发生在USB核心

      然后到一个全局的USB驱动链上查找,看看哪个驱动程序支持的设备列表中有该设备的厂家号和产品号。当找到后设备就和驱动匹配上了。 

    了解了上面的过程后,首先需要注册一个代表USB驱动的数据结构,并要明确表示本驱动要支持的设备。在模块初始化函数module_init中,通过usb_register_driver注册一个usb驱动程序。USB核心将调用通过usb_register_driver注册的探测回调函数,在Linux中代表USB驱动的数据结构部分成员如下:

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. struct usb_driver{  
  2.     .name="alld";  
  3.     .probe=ad_probe;  
  4.     .disconnect=ad_disconnect;  
  5.     .id_table=ad_usb_ids;  
  6. };  

      该数据结构中name成员是代表该驱动的名称,该名称在USB驱动中必须要独一无二的,不能和别的驱动的名字重复,在起名字的时候最好和模块名字相同。      

     成员 probe()函数指针就是本章要实现的探索函数,该函数在本驱动和设备的厂家号和产品号相匹配后调用,作用是探索该驱动是否支持该设备,如果支持该设备的接口,那么在probe函数中调用usb_set_intfdata(struct usb_interface *intf, void *data)函数,该函数中的第一个参数就是的驱动要支持的那个设备接口数据结构的指针,第二个参数是该驱动为了实现接口正常运行而分配的自己的数据结构。

      usb_set_intfdata()的作用就是把接口和它的驱动要用到的数据结构关联起来。成功后返回0;如果不支持该设备那么返回-ENODEV。

      函数probe()的参数usb_interface验证了前文所说的一个接口对应一个驱动,本文所涉及的设备都是单一接口的,因此没有太区分接口和设备的差别,probe()的第二个参数usb_device_id数据结构就包含了上文提及的厂家号和产品号。它是设备的厂家号和产品号,而usb_driver的id_table是本驱动支持的所有设备的厂家号和产品号的列表。

      成员disconnect函数指针指向的函数的作用是当设备已经移走或者模块被卸载时调用,主要就是处善后工作,例如已经注册的取消注册,已经分配的内存释放掉。


四、私有数据结构的设计

       上文中提到 probe()函数中要调用usb_set_intfdata()函数,该函数的第二个参数就是本文驱动程序要用到的私有数据结构。由于驱动程序是工作在ieee802.11协议层,ieee802.11为驱动程序提供了一个分配内存函数ieee80211_hw*ieee80211_alloc_hw(size_t priv_data_len,const struct ieee80211_ops *ops),该函数第一个参数是自己驱动程序中的私有数据结构的长度,第二个参数是上文提及的指向驱动程序各个函数的数据结构的指针,正是在这里把驱动程序的所有函数提供给ieee802.11协议层的。ieee80211_alloc_hw()函数是即分配了802.11协议层需要的内存结构,又顺便分配了驱动的私有数据结构,该函数分配的内存结构如下图所示。图中除了驱动程序自己的私有数据结构,其他几个数据结构都是802.11协议层使用的数据结构。需要设计自己的私有数据结构,把这个私有数据结构抽象成为设备,把和设备有关的参数都设计成为数据结构放到这个私有数据结构中,在编写驱动程序的各个函数时,只要传递了私有数据结构的指针,就能找到所有关于设备的参数,并且它是全局的。


        函数ieee80211_alloc_hw()成功后返回的是struct ieee80211_hw结构的指针,而该结构的priv指向了的私有数据结构。本文设计的私有数据结构如下:

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. struct priv_dev{    
  2.     unsigned long          flags;          
  3.     struct usb_device       *udev;     
  4.     struct usb_interface     *intf;     
  5.     struct ieee80211_hw     *hw;    
  6.     loff_t                  savep;        
  7.     char                    fw_name[64];   
  8.     char                    path[64];      
  9.     u8 *eeprom;   struct ieee80211_supported_band bands[IEEE80211_NUM_BANDS];    
  10.     enum ieee80211_band     curr_band;    
  11.     spinlock_t              list_lock;     
  12.     struct mutex     list_op,rw_lock;    
  13.     int                    timeout;    
  14.     struct list_head   cfmg_list[30];   
  15.     u8   bulk[BULKSIZE];     
  16.     struct config_msg *msg_fun[10];//record config_msg()  position    
  17.     unsigned char *skb_data,*skb_tail,*rx_skb_data,*rx_skb_tail;    
  18.     struct data_queue *rx,*tx,*beacon;    
  19.     struct  prob_desc probdesc;    
  20.     struct priv_rate *privrate;    
  21.     struct priv_channel *privchannel;    
  22.     struct privdev_rx_status    rxstatus;    
  23.     struct priv_intf   privintf;    
  24.     u32 parameter[PRIV_PARAMETER_SIZE];    
  25.     int sparameter[PRIV_PARAMETER_SIZE];    
  26.     struct pstack    ps;    
  27. };   
       其中成员udev、intf和hw成员都是指向上层的数据结构,有了这些成员后可以很 方便的寻找上层数据结构。成员savep是用于在读参数文件时记录参数文件的偏移量,path成员是参数文件所在路径及参数文件的名字。成员fw_name是用来存放设备固件程序的名字。成员eeprom只有在设备存在EEPROM的时候才有意义,如果设备有EEPROM,那么本文的做法是分配一个和设备EEPROM一样大小的内存来存放EEPROM中所有的数据,这样的好处是当要从EEPROM中读数据时,就从内存读取,这样提升了读取的速度。这样也防止错误代码把EEPROM中的数据冲掉了。成员bands和curr_band记录本设备所在的频带及通道和速率列表,bands数据结构中存在指向通道和速率的指针成员。成员list_lock、list_op和rw_lock都是锁[29],list_lock是自旋锁,它用于短时间的锁,它的特点是在获取锁失败后不睡眠,而是一直循环查询锁的状态。List_op和rw_lock是互斥锁,它可以用于长时间锁,它的特点是获取锁不成功就阻塞在锁的链表上。成员timeout是和设备通信的定时器时间,由于本驱动框架想要支持多个设备,那么它的值就从参数文件中读取。成员cfmg_list就是上文提及的参数链链头指针数组,struct list_head数据结构是Linux中的常用的双向链表结构,它的结构非常简单: 

struct list_head {          

           struct list_head *next, *prev;   

};  

成员next指向下一个list_head数据结构,prev指向上一个list_head数据结构。那么如何使用list_head呢?在使用时把list_head嵌入到宿主数据结构中,只要知道list_head的地址,就可以算出宿主数据结构的地址。内核中给提供了list_entry(ptr,type,member)这个宏来计算宿主数据结构的地址,ptr就是宿主数据结构中list_head成员的地址,type是宿主数据结构的类型,member是list_head数据结构在宿主数据结构中的成员名字,在本文中如果知道list_head的指针例如head,那么config_msg的地址就是list_entry(head,struct config_msg,list)。 



五、操作函数集

        当探索完成后,就要编写驱动程序的打开、发送等函数。这些函数都要填充到下面 struct ieee80211_ops数据结构中去:

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. struct ieee80211_ops{  
  2.     int (*tx)(struct ieee80211_hw *hw, struct sk_buff *skb);  
  3.     int (*start)(struct ieee80211_hw *hw);  
  4.     void (*stop)(struct ieee80211_hw *hw);  
  5.     int (*add_interface)(struct ieee80211_hw *hw,  
  6.                 struct ieee80211_if_init_conf *conf);  
  7.     void (*remove_interface)(struct ieee80211_hw *hw,  
  8.                  struct ieee80211_if_init_conf *conf);  
  9.     int (*config)(struct ieee80211_hw *hw, u32 changed);  
  10.     void (*bss_info_changed)(struct ieee80211_hw *hw,  
  11.                  struct ieee80211_vif *vif,  
  12.                  struct ieee80211_bss_conf *info,  
  13.                  u32 changed);  
  14. };  
   这里只列举了部分主要的函数,一个驱动程序不一定要把这个数据结构中的所有函数指针所指向的函数都实现了,这要根据具体设备的情况而定。其中tx函数指针是指向发送函数,start函数指针指向的是开始函数,config函数指针指向的是配置函数,stop函数是停止函数等等。当把这里必须要实现的函数指针实现后,驱动程序就算写完了。


六、USB接口无线网卡数据的接收

       与pci、pcmia等无线网卡不同,usb总线没有中断资源。因此usb无线网卡的数据接收不通过中断实现,而是在open函数通过主机主动查询是否有数据需要读取

       因此,在open函数中向usb core发送一个读请求的urb,使得网络数据到来时候,主机能够接收到。

open回调函数主要代码:

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. ......  
  2. usb_fill_bulk_urb(dev->rx_urb,//构造读请求的urb  
  3. dev->udev,  
  4. usb_rcvbulkpipe(dev->udev,6),//指定读得端点  
  5. dev->rx_skb->data,  
  6. 512,//count  
  7. rx_complete,//读请求的回调函数  
  8. dev  
  9. );  
  10.   
  11. if(result=usb_submit_urb(dev->urb,GFP_KERNEL))  
  12. {  
  13. 将发送给kernel的usb core  
  14. }  

     读请求完成时候,read_bulk_callback函数将被内核调用,它构造一个skb_bufff数据结构来描述数据包,并调用netif_rx把数据包传给网络子系统,从而完成一次数据的接收过程。


七、USB接口无线网卡数据的发送

       当网络子系统要发送一个数据时候,上层协议会构造一个sk_buff来描述一个数据包,并调用驱动程序注册和实现的hard_start_xmit来发送数据包,由于该函数被调用时候,网络子系统持有xmit_lock自旋锁,因此驱动程序不必考虑设备写操作的同步问题。hard_start_xmit根据数据包的长度,拆分成usb设备可以传输的长度,然后构造相应地写请求urb,发送至usb core即可。

hard_start_xmit回调函数的主要代码:

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. ......  
  2. usb_fill_bulk_urb(dev->tx_urb,//构造写请求的urb  
  3. dev->udev,  
  4. usb_sndbulkpipe(dev->udev,2),//指定写端点  
  5. skb->data,  
  6. 512,//count  
  7. write_bulk_callback,//写请求的回调函数  
  8. dev  
  9. );  
  10.   
  11. if(result=usb_submit_urb(dev->tx_urb,GFP_ATOMIC))  
  12. {  
  13. 将发送给usb core  
  14. }  

写请求完成时候,write_bulk_callback回调函数将被调用,根据发送情况更新统计数据


八、设备的断开

       我们已经分析了usb_driver结构的探测函数,与设备探测对应的是设备的断开。设备断开可以看做是设备探测的逆过程,主要工作是释放驱动程序已经分配的系统资源。

       设备断开调用了usb_driver结构的disconnect(struct usb_interface *)函数,函数首先通过调用usb_get_intfdata()获取相关资源,然后通过usb_set_intfdata(intf,NULL)将资源清零,并释放资源。


九、模块的卸载

      与模块加载对应的是模块的卸载,module_exit函数首先调用usb_rtusb_exit()卸载网卡驱动程序,接着调用usb_deregister(&rtusb_driver)实现设备的注销。


十、IOCTL函数

       Linux中要让网卡正常工作需要配置IP地址、SSID、工作频段、工作模式等,这些控制操作都是通过ifconfig和iwconfig调用驱动实现的IOCTL函数实现的。驱动程序通过IOCTL为应用程序提供了一些诸如IO内存地址读写访问、配置空间寄存器读写访问、数据成员读写访问等函数,通过这些函数,应用程序就可以对设备进行相应地操作,其各种函数都是通过IOCTL命令实现的。应用程序将IOCTL命令将有关信息传递到驱动程序的内核空间,驱动程序再处理相应地操作。

      例如该函数的原型:

      rtxxx_ioctl(struct net_device * net_dev,struct ifreq * ,int cmd)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/401923.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux 设备驱动开发思想 —— 驱动分层与驱动分离

前面我们学习I2C、USB、SD驱动时,有没有发现一个共性,就是在驱动开发时,每个驱动都分层三部分,由上到下分别是: 1、XXX 设备驱动 2、XXX 核心层 3、XXX 主机控制器驱动 而需要我们编写的主要是设备驱动部分&#xff0c…

CortexM0开发 —— UART时序分析

通用异步收发传输器(UniversalAsynchronousReceiver/Transmitter),通常称作UART,是一种异步收发传输器。将数据由串行通信与并行通信间作传输转换,作为并行输入成为串行输出的芯片UART是一种通用串行数据总线,用于异步通信。该总线…

CortexM0开发 —— LPC11C14的UART使用方法

LPC1100系列微控制器UART LPC1100系列Cortex-M0微控制器具有一个符合16C550工业标准的异步串行口(UART)。此口同时增加了调制解调器(Modem)接口,DSR、DCD和RI Modem信号是只用于LQFP48和PLCC44封装的管脚配置。 特性…

Linux SD卡驱动开发(一) —— SD 相关基础概念

一.SD/MMC卡基础概念 1.1.什么是MMC卡 MMC:MMC就是MultiMediaCard的缩写,即多媒体卡。它是一种非易失性存储器件,体积小巧(24mm*32mm*1.4mm),容量大,耗电量低,传输速度快,广泛应用于消费类电子产品中。 1.2.什么是SD卡…

Linux SD卡驱动开发(二) —— SD 卡驱动分析HOST篇

回顾一下前面的知识,MMC 子系统范围三个部分: HOST 部分是针对不同主机的驱动程序,这一部是驱动程序工程师需要根据自己的特点平台来完成的。 CORE 部分: 这是整个MMC 的核心存,这部分完成了不同协议和规范的实现,并为…

MVC应用程序显示RealPlayer(rm)视频

本篇博文是演示MVC应用程序显示RealPlayer视频。 客户端能观看到RealPlayer视频,前提条件是需要安装RealPlayer客户端,就是想看Falsh或理WMV视频一样,均要安装客户端或相关插件等。 Insus.NET实现方法,还是在控制器中Render RealP…

Linux从入门到精通系列之PPTP

Linux从入门到精通系列之PPTP今天我们来说下怎么在linux环境下如何搭建PPTP-,PPTP(Point to Point Tunneling Protocol),即点对点隧道协议。该协议是在PPP协议的基础上开发的一种新的增强型安全协议,支持多协议虚拟专用…

Linux SD卡驱动开发(三) —— SD 卡驱动分析CORE篇

废话不多说,直接切进主题: Linux在内核源码的drivers/mmc/core文件夹下为我们的提供了一系列SD卡的接口服务函数。可以查看Makefile如下 可见,core文件夹下有针对总线的服务bus.c,针对主控制器的服务host.c,针对SD卡的…

Python数值计算:一 使用Pylab绘图(1)

Pylab的使用 学习使用Python进行科学计算,然而很难找到简单实用,又循序渐进的例子。正好手边有一本《Matlab可视化大学物理学》,里面的例子非常清晰地解释了Matlab在物理学中的应用。重新使用Python实现这些例子,学习了Python&…

Linux SD卡驱动开发(四) —— SD 控制器之真正的硬件操作

前面对SD卡控制器有了一个基本的介绍。其实SD控制器层更过的意义是为core层提供一种操作SD卡硬件的一种方法,当然不同的控制器对硬件控制的方法不尽相同,但是他们最终都能像core层提交一个统一的封装有操作方法的数据结构,那便是即将闪亮登场…

Linux SD卡驱动开发(五) —— SD 卡驱动分析Core补充篇

Core层中有两个重要函数 mmc_alloc_host 用于构造host,前面已经学习过,这里不再阐述;另一个就是 mmc_add_host,用于注册host 前面探测函数s3cmci_probe,现在就来回顾一下这个函数的作用。先简要的概括一下这个函数的功能&#xff…

navicat连接oracle 报 ORA-12737 set CHS16GBK

2019独角兽企业重金招聘Python工程师标准>>> 1首 先,我们打开“工具”-->"选项"菜单,见到如下界面,依据OCI library(oci.dll) 路径,导航到 navicat oci 目录下,备份里面的文件(通过…

Linux SD卡驱动开发(六) —— SD卡启动过程总体分析

一、工作流程 mmc驱动主要文件包括 drivers/mmc/card/block.c drivers/mmc/card/queue.c drivers/mmc/core/core.c drivers/mmc/core/host.c drivers/mmc/core/ 内核启动时,首先执行core/core.c的mmc_init,注册mmc、sd总线,以及一个host clas…

svn怎么上传文件 — 百度经验无耻推广

2019独角兽企业重金招聘Python工程师标准>>> svn怎么上传文件 — 欢乐地点进去捧场 PS:觉得笔者太无耻,直接在下方评论抨击 转载于:https://my.oschina.net/cenqingbo/blog/212284

apache 重写和虚拟目录配置

要求:假如我请求一个地址:www.lxy.com/news-sport-id123.html转成:www.lxy.com/show.php?catenews&classsport&id123步骤:①首先我们需要在apache中启用rewrite模块打开apache的httpd.conf文件,找到#LoadModu…

JavaScript代码片段

简介:本文收集了我常用的JavaScript代码片段,欢迎提意见! 大灰狼边敲门边说:“小兔子乖乖,把门儿开开!” 小兔子听到后,连忙去开门:“来喽!” 兔妈妈对小兔子喊道&#x…

路由器开发(一)—— 路由器硬件结构及软件体系

一、路由器的硬件构成 路由器主要由以下几个部分组成:输入/输出接口部分、包转发或交换结构部分(switching fabric)、路由计算或处理部分。如图所示 图1 路由器的基本组成 输入端口是物理链路和输入包的进口处。端口通常由线卡提供&#…

路由器开发(二)—— 路由器工作原理

当信息需要在两个网络之间传输时,常用路由器这种互连设备来负责数据的传输。路由器的主要工作是:路径的决定和数据包的转发(从路由器一个接口输入,然后选择合适接口输出);维护路由表。 路由器工作的方式非常…

Android颜色渐变的分隔线(ListView)

2019独角兽企业重金招聘Python工程师标准>>> shape.xml xx <?xml version"1.0" encoding"utf-8"?><shape xmlns:android"http://schemas.android.com/apk/res/android" > <gradient android:startColor&qu…

项目实践中Linux集群的总结和思考

2019独角兽企业重金招聘Python工程师标准>>> 前言&#xff1a;作为一名Linux/unix系统工程师、项目实施工程师&#xff0c;这几年一直在涉及到对外项目&#xff0c;经手过许多小中型网站的架构&#xff0c;F5、LVS及Nginx接触的都比较多&#xff0c;我想一种比较通俗…