上一篇文章介绍了驱动中minstrel_ht速率调整算法,atheros中提供了可选的的两种速率调整算法,分别是ath9k和minstrel,这两个算法分别位于:
drivers\net\wireless\ath\ath9k\rc.c···················Ath9k
net\mac80211\minstrel_ht.c···························Minstrel
无论从理论分析还是实验结果上看,minstrel都要胜ath9k一筹,为了一个完整性,这里也把ath9k算法介绍一下,相比较于minstrel的随机探测,ath9k是按照特定顺序来探测的,不过这个顺序的排列却有问题。
1. 速率表
ath9k根据当前的标准是802.11a还是b/g/n,使用不同的速率表,这个速率表是硬编码的,首先来看存储这个速率表的结构体:
struct ath_rate_table {
int rate_cnt;
int mcs_start;
struct {
u16 rate_flags;
u8 phy;
u32 ratekbps;
u32 user_ratekbps;
u8 ratecode;
u8 dot11rate;
u8 ctrl_rate;
u8 cw40index;
u8 sgi_index;
u8 ht_index;
} info[RATE_TABLE_SIZE];
u32 probe_interval;
u8 initial_ratemax;
};
因为我的实验环境用的是802.11n,所以使用的速率表是11na的:
static const struct ath_rate_table ar5416_11na_ratetable = {
,
, /* MCS start */
{
[] = { RC_L_SDT, WLAN_RC_PHY_OFDM, ,
, , , , , , }, /* 6 Mb */
[] = { RC_L_SDT, WLAN_RC_PHY_OFDM, ,
, , , , , , }, /* 9 Mb */
[] = { RC_L_SDT, WLAN_RC_PHY_OFDM, ,
, , , , , , }, /* 12 Mb */
[] = { RC_L_SDT, WLAN_RC_PHY_OFDM, ,
, , , , , , }, /* 18 Mb */
[] = { RC_L_SDT, WLAN_RC_PHY_OFDM, ,
, , , , , , }, /* 24 Mb */
[] = { RC_L_SDT, WLAN_RC_PHY_OFDM, ,
, , , , , , }, /* 36 Mb */
[] = { RC_L_SDT, WLAN_RC_PHY_OFDM, ,
, , , , , , }, /* 48 Mb */
[] = { RC_L_SDT, WLAN_RC_PHY_OFDM, ,
, , , , , , }, /* 54 Mb */
[] = { RC_HT_SDT_2040, WLAN_RC_PHY_HT_20_SS, ,
, , , , , , }, /* 6.5 Mb */
……
[] = { RC_HT_S_20, WLAN_RC_PHY_HT_20_SS, ,
, , , , , , }, /* 65 Mb */
[] = { RC_HT_S_20, WLAN_RC_PHY_HT_20_SS_HGI, ,
, , , , , , }, /* 75 Mb */
……
[] = { RC_HT_SDT_40, WLAN_RC_PHY_HT_40_SS, ,
, , , , , , }, /* 13.5 Mb*/
……
[] = { RC_HT_S_40, WLAN_RC_PHY_HT_40_SS, ,
, , , , , , }, /* 135 Mb */
[] = { RC_HT_S_40, WLAN_RC_PHY_HT_40_SS_HGI, ,
, , , , , , }, /* 150 Mb */
……
[] = { RC_HT_T_40, WLAN_RC_PHY_HT_40_TS_HGI, ,
, , , , , , }, /* 450 Mb */
},
, /* probe interval */
WLAN_RC_HT_FLAG, /* Phy rates allowed initially */
};
这个变量的定义和前面的结构体一一对应,首先是rate_cnt,是68,即有68个速率,之后是mcs_start,也就是从第几个速率开始是MCS的速率,对速率比较熟悉的能够看出来,0-7分别是11b/g的8个传统速率,最大54Mbps,从第8个开始,就是MCS速率,以[8]为例,结合前面的结构体看各个域的含义:
rate_flags | phy | ratekbps | user_ratekbps | ratecode | dot11rate | ctrl_rate | cw40index | sgi_index | ht_index |
RC_HT_SDT_2040 | WLAN_RC_PHY_HT_20_SS | 6500 | 6400 | 0 | 0 | 0 | 38 | 8 | 38 |
这里面字段比较多,不过很多我都没有深究,一知半解,rate_flags的含义结合rc.h中的宏定义更清晰一些,RC_HT_S(单流)D(双流)T(三流)_2040(20/40M带宽),物理意义我不是很清楚,可能是说这个参数可以在哪些配置下用吧,比如说在配置的40MHz带宽下,是可以使用20MHz的MCS0的,但是配置的20MHz带宽就不能用40Mhz的MCS0发送。phy是当前速率所对应的物理层参数,这个就是20MHz的SS(单流),数据率是6.5Mbps,user_ratekbps我没有仔细看过,可能是对去掉MAC和PHY头部之后对速率的估算吧,ratecode和dot11rate说的基本是一个事,就是说这是哪个MCS,这里就是MCS0,ctrl_rate是说,在11b/g的速率里面,哪一个可以作为当前速率的保底速率,最后三个就是说,当前的速率,也就是20MHz的MCS0,在40MHz下对应速率表里的第几个速率、SGI的情况下对应于哪一个,40MHz+SGI的情况下又对应于第几个速率。
介绍完速率表,剩下的,就按照和minstrel同样的思路来分析,先来看注册rate_control_ops的结构体:
static struct rate_control_ops ath_rate_ops = {
.module = NULL,
.name = "ath9k_rate_control",
.tx_status = ath_tx_status,
.get_rate = ath_get_rate,
.rate_init = ath_rate_init,
.rate_update = ath_rate_update,
.alloc = ath_rate_alloc,
.free = ath_rate_free,
.alloc_sta = ath_rate_alloc_sta,
.free_sta = ath_rate_free_sta,
#ifdef CONFIG_ATH9K_DEBUGFS
.add_sta_debugfs = ath_rate_add_sta_debugfs,
#endif
};
下面依次来看ath_rate_init、ath_get_rate、ath_tx_status。
2. 算法的初始化
for (i = ; i < sband->n_bitrates; i++) {
if (sta->supp_rates[sband->band] & BIT(i)) {
ath_rc_priv->neg_rates.rs_rates[j]
= (sband->bitrates[i].bitrate * ) / ;
j++;
}
}
ath_rc_priv->neg_rates.rs_nrates = j; if (sta->ht_cap.ht_supported) {
for (i = , j = ; i < ; i++) {
if (sta->ht_cap.mcs.rx_mask[i/] & (<<(i%)))
ath_rc_priv->neg_ht_rates.rs_rates[j++] = i;
if (j == ATH_RATE_MAX)
break;
}
ath_rc_priv->neg_ht_rates.rs_nrates = j;
}
这两个循环的代码不用深究,n_bitrates一般是8,第一个循环就是看看11b/g的那8个速率哪一个被当前的驱动和硬件支持,第二个循环就是看看MCS的速率又有哪些被支持,把能支持的加入到ath_rc_priv->neg_rates和ath_rc_priv->neg_ht_rates中,neg表示Negotatied。
下面主要做了三件事:1.判断当前的配置是20MHz还是40MHz,2.当前配置是不是SGI,3.根据配置选择速率表,是前面介绍的ar5416_11na_ratetable,还是ar5416_11ng_ratetable、ar5416_11a_ratetable、ar5416_11g_ratetable。
最后调用了ath_rc_init函数,这个函数对速率调整各个速率的基本参数进行初始化:
for (i = ; i < ath_rc_priv->rate_table_size; i++) {
ath_rc_priv->per[i] = ;
}
有这么一个叫per的数组,per是packet error rate的缩写,数组的每一项对应于刚才的那个速率表,这里先对所有的速率的per初始化为0。之后初始化两个变量:
for (i = ; i < WLAN_RC_PHY_MAX; i++) {
for (j = ; j < MAX_TX_RATE_PHY; j++)
ath_rc_priv->valid_phy_rateidx[i][j] = ;
ath_rc_priv->valid_phy_ratecnt[i] = ;
}
打眼一看这两个变量不是很好理解,但是只要一看WLAN_RC_PHY_MAX是什么,就比较明朗了:
enum {
WLAN_RC_PHY_OFDM,
WLAN_RC_PHY_CCK,
WLAN_RC_PHY_HT_20_SS,
WLAN_RC_PHY_HT_20_DS,
WLAN_RC_PHY_HT_20_TS,
WLAN_RC_PHY_HT_40_SS,
WLAN_RC_PHY_HT_40_DS,
WLAN_RC_PHY_HT_40_TS,
WLAN_RC_PHY_HT_20_SS_HGI,
WLAN_RC_PHY_HT_20_DS_HGI,
WLAN_RC_PHY_HT_20_TS_HGI,
WLAN_RC_PHY_HT_40_SS_HGI,
WLAN_RC_PHY_HT_40_DS_HGI,
WLAN_RC_PHY_HT_40_TS_HGI,
WLAN_RC_PHY_MAX
};
前面的那个速率表如果看熟了,这些东西就会变得非常眼熟,正是每个速率的第二个域(也就是前面说明速率表结构体的那个表格的第二列),phy。换句话说,ath9k在这个时候也是给这些速率分了个组,分组的依据就是每个速率的phy域,valid_phy_ratecnt是存储这个组里有几个速率,valid_phy_rateidx就是存储各个组里都有哪些速率了,后面的一段代码就不粘帖了,就是把速率表里的速率遍历一下,把被支持的速率挑出来,给这两个数组赋上正确的值。这时候valid_phy_rateidx就是一个存储被支持的速率的二维数组了,这里要是不能理解也没有关系,因为这个函数之后基本就用不到这两个变量了,下一步,把这个二维数组压成一个一维数组,存储在valid_rate_index里面,只要明白valid_rate_index是什么意思就够了,结合一下代码看看把二维数组压成一维数组是什么意思(看代码应该比较清晰):
for (i = , k = ; i < WLAN_RC_PHY_MAX; i++) {
for (j = ; j < ath_rc_priv->valid_phy_ratecnt[i]; j++) {
ath_rc_priv->valid_rate_index[k++] =
ath_rc_priv->valid_phy_rateidx[i][j];
}
……
}
然后,按照速率值的大小,也就是那个多少Mbps,对这些速率排序(也就是对ath_rc_priv->valid_rate_index这个数组排序):
ath_rc_sort_validrates(rate_table, ath_rc_priv);
这个数组,就决定了以后的探测顺序,Ath9k的探测就是沿着这个方向,碰到投递率低的速率就停止探测,但是因为有带宽和空间流的影响,120Mbps的速率投递率是10%,150Mbps的投递率就一定很低吗?这是不一定的。
2. 获取发送速率
一开始,和minstrel一样,当目标站点不存在,或者本次发送不需要等ACK的时候,为了确保数据包尽可能被对方正确接收,那么会直接用传统速率来发送,不给它分配MCS速率:
if (rate_control_send_low(sta, priv_sta, txrc))
return;
接下来调用ath_rc_get_highest_rix函数,计算当前哪个速率是最好的速率:
maxindex = ath_rc_priv->max_valid_rate-;
minindex = ;
best_rate = minindex; for (index = maxindex; index >= minindex ; index--) {
u8 per_thres; rate = ath_rc_priv->valid_rate_index[index];
……
per_thres = ath_rc_priv->per[rate];
if (per_thres < )
per_thres = ; this_thruput = rate_table->info[rate].user_ratekbps * (100 - per_thres); if (best_thruput <= this_thruput) {
best_thruput = this_thruput;
best_rate = rate;
}
}
从maxindex开始往minindex遍历,是在信道状况好的情况下,可以减少内存计算开销,当速率的per小于12%的时候,就按12%来对待,这个在源码里有解释:For TCP the average collision rate is around 11%, so we ignore PERs less than this. This is to prevent the rate we are currently using (whose PER might be in the 10-15 range because of TCP collisions) looking worse than the next lower rate whose PER has decayed close to 0. If we used to next lower rate, its PER would grow to 10-15 and we would be worse off then staying at the current rate.
调用ath_rc_get_highest_rix找到最好的速率之后存到变量rix中,rix是前面讲过的11na速率表中的速率索引。还有一个比较关键的函数是ath_rc_get_lower_rix,这个函数是要获取比rix稍差的速率,获取思路很简单,还记得前面的valid_rate_index吗,这个数组里存储着按比特率排序的各个速率,次佳速率就是这个数组中某速率的前一个(这个取法当然是不怎么科学的)。算法维护了一个当前最大速率的序号,如果新获得的这个highest_rix比这个数大,并且离上次探测时间超过一定阈值,就会启动探测,这个最后会讲。
if (is_probe) {
ath_rc_rate_set_series(rate_table, &rates[i++], txrc, 1, rix, );
ath_rc_get_lower_rix(rate_table, ath_rc_priv, rix, &rix);
ath_rc_rate_set_series(rate_table, &rates[i++], txrc, try_per_rate, rix, ); tx_info->flags |= IEEE80211_TX_CTL_RATE_CTRL_PROBE;
} else {
ath_rc_rate_set_series(rate_table, &rates[i++], txrc, try_per_rate, rix, );
} for ( ; i < ; i++) {
ath_rc_get_lower_rix(rate_table, ath_rc_priv, rix, &rix);
ath_rc_rate_set_series(rate_table, &rates[i], txrc, try_per_rate, rix, );
} try_per_rate = ; if ((rates[].flags & IEEE80211_TX_RC_MCS) &&
(!(tx_info->flags & IEEE80211_TX_CTL_AMPDU) ||
(ath_rc_priv->per[high_rix] > )))
rix = ath_rc_get_highest_rix(sc, ath_rc_priv, rate_table, &is_probe, true);
else
ath_rc_get_lower_rix(rate_table, ath_rc_priv, rix, &rix); ath_rc_rate_set_series(rate_table, &rates[i], txrc, try_per_rate, rix, );
和minstrel类似,关键函数长得也很像,标红的三个参数,分别是底层发包需要的速率结构体、最大发送次数和速率编号,ath9k是用4个速率来发送,对应的参数要赋到rates[0]、rates[1]、rates[2]和rates[3]这四个变量上,try_per_rate初始为4,这段代码的含义就是:如果需要探测,则rates[0]用探测速率发1次,如果失败,则换rates[1]用探测速率的次佳速率发4次,再失败就换rates[2]用rates[1]的次佳速率再发4次。如果不是探测,就按当前最佳速率、次佳速率、次次佳速率的顺序各尝试4次。最后还剩一个rates[3],如果rates[0]的per超过45%,重新获取最高速率作为rates[3]的发送速率,如果rates[0]的per不到45%,就用rates[2]的次佳速率发送,最大尝试8次。在2.4GHz环境下或者数据包分片的情况下还会再微调,此处不再分析。
3. 发送完成后更新速率状态
和minstrel一样,聚合帧发送完之后,每一个子帧都会调用速率调整算法的tx_status函数,但是每个聚合帧里只有一个帧是携带了有用信息的,其他帧直接返回不予处理:
if ((tx_info->flags & IEEE80211_TX_CTL_AMPDU) &&
!(tx_info->flags & IEEE80211_TX_STAT_AMPDU))
return;
对于携带了发送状况信息的帧,驱动用了一个叫做xretries的变量区分这4个速率的状态,现在考虑两个例子:
例1:rates[0]发送4次都失败,rates[1]发送4次也都失败,rates[2]发送第3次成功,那么使用了的速率就是3个,rates[3]没有用到;
例2:rates[0]-rates[3]这总共20次发送全部失败。
xretries | 含义 |
0 | 数据帧最终被成功发送,并且是使用的本速率,对上面的例子来说,例1的rates[2]的xretries就是0 |
1 | 数据帧最终没有成功发送,丢了,例2的4个速率的xretries都是1 |
2 | 数据帧最终被成功发送,但不是使用的本速率,例1的rates[0]和rates[1]的xretries是2 |
针对每个速率,首先调用ath_rc_update_per来更新per:
if (xretries == ) {
ath_rc_priv->per[tx_rate] += ;
if (ath_rc_priv->per[tx_rate] > )
ath_rc_priv->per[tx_rate] = ;
}
如果是xretries=1这种情况,per直接加30%。
else {
/* xretries == 2 *//* new_PER = 7/8*old_PER + 1/8*(currentPER) */
ath_rc_priv->per[tx_rate] =
(u8)(last_per - (last_per >> ) + ( >> ));
}
新的per是旧per的7/8加上本次per的1/8,因为xretries=2对应的是发送全都失败的速率,所以本次per就是100%。
最后也是最复杂的就是最后成功的那个速率,这里需要先介绍几个基本参数,从头来看一下这个函数的参数和一个写死的lookup数组:
static void ath_rc_update_per(struct ath_softc *sc, const struct ath_rate_table *rate_table,
struct ath_rate_priv *ath_rc_priv, struct ieee80211_tx_info *tx_info,
int tx_rate, int xretries, int retries, u32 now_msec)
{
int count, n_bad_frames;
u8 last_per;
static const u32 nretry_to_per_lookup[] = {
* / ,
* / ,
* / ,
* / ,
* / ,
* / ,
* / ,
* / ,
* / ,
* /
};
tx_rate不用说就能猜到,表示当前速率,xretries前面已经介绍,现在介绍的这种情况xretries=0。retries是说这个速率重传了多少次,比如rates[0]发了4次都失败了,就是重传了3次,rates[2]第3次成功,就是retries=2。
下面看逻辑:
if (n_bad_frames) {
if (tx_info->status.ampdu_len > ) {
int n_frames, n_bad_tries;
u8 cur_per, new_per; n_bad_tries = retries * tx_info->status.ampdu_len + n_bad_frames;
n_frames = tx_info->status.ampdu_len * (retries + );
cur_per = ( * n_bad_tries / n_frames) >> ;
new_per = (u8)(last_per - (last_per >> ) + cur_per);
ath_rc_priv->per[tx_rate] = new_per;
}
} else {
ath_rc_priv->per[tx_rate] =
(u8)(last_per - (last_per >> ) + (nretry_to_per_lookup[retries] >> ));
}
这里又分了两种情况,前面说的成功发送其实是指聚合帧被成功发送,成功与否的标志是收到了块确认,也就是说有可能有子帧因为CRC等错误还是没有被正确接受,这样出错的帧的数量就是n_bad_frames,更新PER的算法都是一样的:new_per=old_per*7/8+cur_per/8,但是cur_per的算法不固定,如果n_bad_frames是0,那不按照正统方法来算,还是假设第3次发送成功,也就是重传了两次,那么this_per不是常规认为的66%,而是查表得来的50%。如果n_bad_frames不是0,那么就是正规方法了,前面retries次一共丢了多少加上最后一次丢了几个,除以总的发送子帧数就是cur_per。
最后,如果成功发送的速率是探测速率,如果它的PER小于50%且大于30%的话,置成20%,并且,因为这次探测的结果还不错,所以下次探测的时间就会在半个探测间隔(rate_table->probe_interval / 2)之后就开始:
if (ath_rc_priv->probe_rate && ath_rc_priv->probe_rate == tx_rate) {
if (retries > || * n_bad_frames > tx_info->status.ampdu_len) {
……
} else {
……
if (ath_rc_priv->per[probe_rate] > )
ath_rc_priv->per[probe_rate] = ;
……
ath_rc_priv->probe_time = now_msec - rate_table->probe_interval / ;
}
}
后面还有一些小判断,如果当前速率的PER已经超过55%,而且这是一个比当前最高速率更小的速率,按照ath9k算法的理念,它按比特率排序的结果就是越靠前的速率越稳定,如果当前速率都已经不行了,那么比它更靠后的最大速率肯定更不行了,这时候就降低rate_max_phy的值:
if (ath_rc_priv->per[tx_rate] >= && tx_rate > &&
rate_table->info[tx_rate].ratekbps <=
rate_table->info[ath_rc_priv->rate_max_phy].ratekbps) {
ath_rc_get_lower_rix(rate_table, ath_rc_priv, (u8)tx_rate, &ath_rc_priv->rate_max_phy); /* Don't probe for a little while. */
ath_rc_priv->probe_time = now_msec;
}
ath9k是以排序越靠前的速率越稳定为前提的,那么如果这个单调性不存在了怎么办?ath9k的做法是强制让它单调,如果前面的速率的PER比后面的大,就赋值成和后面一样的:
if (ath_rc_priv->per[tx_rate] < last_per) {
for (rate = tx_rate - ; rate >= ; rate--) {
if (ath_rc_priv->per[rate] > ath_rc_priv->per[rate+]) {
ath_rc_priv->per[rate] =
ath_rc_priv->per[rate+];
}
}
}
同样,从当前速率越往后就需要越不稳定,也需要强制规范一下,这个代码就不贴了。
最后,为了不让PER升的太高,每隔一段时间就会降为原来的7/8:
if (now_msec - ath_rc_priv->per_down_time >= rate_table->probe_interval) {
for (rate = ; rate < size; rate++) {
ath_rc_priv->per[rate] = * ath_rc_priv->per[rate] / ;
}
ath_rc_priv->per_down_time = now_msec;
}
4. 探测频率
最后一个问题,ath9k什么时候开始探测,本文开头介绍的rate_table里有一个域是probe_interval,11na的速率表设定的值是50,再加上ath_rc_priv->probe_time存储上次探测的时间,根据这两个变量,在时间上控制探测的间隔。除此之外还有一个非常重要的变量,沿着这个变量的赋值搜索下去,就能弄清楚探测的原理,这个变量就是ath_rc_priv->rate_max_phy,前面反复提到,ath9k对所有的速率排了个序,它认为,这些速率的表现是单调变化的,如果排序之后的速率n已经不行了,那么n+1、n+2肯定都已经不行了,这个rate_max_phy就是当前性能不错的最大速率的序号,下面就沿着这个这个变量分析:
在ath_rc_init函数中完成对所有速率的排序之后,将排序后的倒数第三个速率设置为rate_max_phy:
ath_rc_priv->rate_max_phy = ath_rc_priv->valid_rate_index[k-];
此时k=valid_rate_index.length,为什么是取倒数第三个作为rate_max_phy,不了解。
前面介绍,在发送数据包时,会寻找当前吞吐率最高的速率,找到之后会进行下面的判断:
if (rate >= ath_rc_priv->rate_max_phy) {
rate = ath_rc_priv->rate_max_phy; /* Probe the next allowed phy state */
if (ath_rc_get_nextvalid_txrate(rate_table, ath_rc_priv, rate, &next_rate) &&
(now_msec - ath_rc_priv->probe_time > rate_table->probe_interval) &&
(ath_rc_priv->hw_maxretry_pktcnt >= )) {
rate = next_rate;
ath_rc_priv->probe_rate = rate;
ath_rc_priv->probe_time = now_msec;
ath_rc_priv->hw_maxretry_pktcnt = ;
*is_probing = ;
}
}
rate就是找到的最大速率,仔细看代码会发现,这个rate其实一定会是一个小于等于rate_max_phy的数,因为算法自动忽略了大于rate_max_phy的速率,不过没有关系,对这段代码的解释可以是这样:当找到的最佳速率不比之前设定的最大速率小的时候,说明这个时候可以往高速率上探测一下了,于是呢get_nextvalid_txrate获取rate之后更高的速率,并且判断一下是不是已经到了该探测的时间了,如果是,则使用这个更高速率进行探测。最后,这个rate_max_phy是怎么改变的呢,当探测帧的投递率大于50%的时候,会把刚刚探测的速率作为新的rate_max_phy:
if (ath_rc_priv->probe_rate && ath_rc_priv->probe_rate == tx_rate) {
if (retries > || * n_bad_frames > tx_info->status.ampdu_len) {
ath_rc_priv->probe_rate = ;
} else {
u8 probe_rate = ;
ath_rc_priv->rate_max_phy = ath_rc_priv->probe_rate;
probe_rate = ath_rc_priv->probe_rate;
……
ath_rc_priv->probe_rate = ; /*
* Since this probe succeeded, we allow the next probe twice as soon.
* This allows the maxRate to move up faster if the probes are successful.
*/
ath_rc_priv->probe_time = now_msec - rate_table->probe_interval / ;
}
}
至此,ath9k速率调整算法的基本流程就差不多了。