我做网卡驱动,最主要的内容就是设计和改进速率调整算法,随着802.11协议簇的新标准越来越多,速率越来越高,调制编码方式也越来越多,一般来说,速率越高越可能丢包,速率越低越稳定,这是整体状况,但不是必然的规律,所以,只用固定的速率来发送显然是不合适的,这就需要速率调整算法来自己调节,信号比较好的时候,就用高速率来发送,信道状况不好了,就换用低速率来发,atheros驱动中提供了两种可选的速率调整算法,ath9k和minstrel,其中minstrel要好一些,后面我会分别根据源码解读minstrel和ath9k这两种算法,这一篇文章,只介绍一个重要的结构体,从而引出速率调整算法的任务。
接触过网络编程的朋友都不会对socket感到陌生,在数据链路层,有一个与之对应的结构体,叫做sk_buff,一般记作skb,skb中有一个域叫做control_buffer,也就是skb->cb,是一个48字节长度的内存区域,这个区域的设计,是为了协议栈中各个层存储一些私有的数据,数据包在不同层之间传递时,比如从网络层传输到链路层之后,这个域的数据就没有用了,可以放心地被链路层写入新的数据,链路层在把这个数据包交给物理层去发送的时候,需要指定一些参数,比如这个数据包要用20MHz还是40MHz去发送,用哪个速率去发送,如果发送失败了需要重传的话,最多重传多少次等等的信息,这些信息就存储在skb->cb中,存储的格式,是按照下面这个结构体来存的:
struct ieee80211_tx_info { /* common information */ u32 flags; u8 band; u8 antenna_sel_tx; u16 ack_frame_id; union { struct { union { /* rate control */ struct { struct ieee80211_tx_rate rates[ IEEE80211_TX_MAX_RATES]; s8 rts_cts_rate_idx; }; /* only needed before rate control */ unsigned long jiffies; }; /* NB: vif can be NULL for injected frames */ struct ieee80211_vif *vif; struct ieee80211_key_conf *hw_key; struct ieee80211_sta *sta; } control; struct { struct ieee80211_tx_rate rates[IEEE80211_TX_MAX_RATES]; u8 ampdu_ack_len; int ack_signal; u8 ampdu_len; /* 15 bytes free */ } status; struct { struct ieee80211_tx_rate driver_rates[ IEEE80211_TX_MAX_RATES]; void *rate_driver_data[ IEEE80211_TX_INFO_RATE_DRIVER_DATA_SIZE / sizeof(void *)]; }; void *driver_data[ IEEE80211_TX_INFO_DRIVER_DATA_SIZE / sizeof(void *)]; }; };
其中,很多字段的意义不需要操心,只需要关注其中union的一部分(标红的union),这段内存区域可以通过control和status等方式访问,这个control和status就是我们关注的重点!
他们的核心又是同样的一个字段:我标红的ieee80211_tx_rate:
struct ieee80211_tx_rate { s8 idx; u8 count; u8 flags; } __packed;
这个结构体代表一个速率,看字段名就很明显了:速率号、发送次数和一个标志位(标识带宽、SGI/LGI等),网卡可能会支持多速率重传,就是先用某个速率发送,如果几次都失败了就换第二个速率发,因此,底层在发包过程中,需要一个ieee80211_tx_rate的数组,速率调整算法的任务,就是把前面结构体中的这个字段填充好交给底层:
struct ieee80211_tx_rate rates[IEEE80211_TX_MAX_RATES];
回到前面那个结构体tx_info,它拥有一个union,主要包括control和status两个部分,control部分是在发送过程中被使用的,速率调整算法会:
struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb); struct ieee80211_tx_rate *rates = tx_info->control.rates; rates[0].idx=0; rates[0].count=2; rates[0].flags=zzz0; rates[1].idx=1; rates[1].count=4; rates[1].flags=zzz1; rates[2].idx=2; rates[2].count=8; rates[2].flags=zzz2;
这样,我就指定了,发送时数据包依次使用0、1、2这三个速率最多发送2、4、8次,直到发送成功为止。那么发送完成之后,我们需要知道发送到底成功了没有,最后是哪个速率发送成功的等信息。所以这个结构体又会被底层返回给我们,此时,我们可以通过status访问之前设置的这些数据,只是每一项的count域被底层重写了,之前是表示每个速率的最大发送次数是多少,现在变成了每个速率的实际发送次数是多少。
struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb); struct ieee80211_tx_rate *rates = tx_info->status.rates; // rates[0].count == 4 // rates[1].count == 1 // rates[2].count == 0
这就代表速率0发了4次,最终都失败了,速率1发了1次就成功了,当然速率2没有用到,发了0次。
所以速率调整算法的任务就是在发送过程中把tx_info->control.rates填充好,等发送结束后根据tx_info->status.rates来做速率的调整。