转自:http://xiyong8260.blog.163.com/blog/static/66514621200892465922669/

在我的Doctor课题研究中,基于ARF协议设计了一个改进型的AMARF协议,该文发表在milcom06和电子科学学刊英文版上。最近,我们将PC机上,使用linux操作系统,基于madwifi开源代码实现了AMARF协议,已经在室内固定以及移动环境测试表明,AMARF协议明显优越于现有的协议。

2008-9-20

现在使用了madwifi程序实现AMARF协议,下面分析其代码。

使用iwconfig命令可以设置速率:

1、iwconfig源代码阅读

首先下载iwconfig.c代码,源代码包为\wireless_tools.29目录

先看执行

iwconfig eth0

的命令的执行过程:

调用main函数,因为是两个参数:

if(argc == 2)

print_info(skfd, argv[1], NULL, 0);

print_info调用

get_info(int                skfd,

char *               ifname,

struct wireless_info *     info)

将网卡的各种信息打印出来。

下面与打印发送速率为例,说明调用过程,get_info函数里面:

/* Get bit rate */

if(iw_get_ext(skfd, ifname, SIOCGIWRATE, &wrq) >= 0)

{

info->has_bitrate = 1;

memcpy(&(info->bitrate), &(wrq.u.bitrate), sizeof(iwparam));

}

对信息的获取都是通过iw_get_ext函数来实现的,通过参数SIOCGIWRATE来识别不同的内容

iwlib.h文件定义了iw_get_ext:

iw_get_ext(int              skfd,      /* Socket to the kernel */

const char *       ifname,          /* Device name */

int                request,   /* WE ID */

struct iwreq *     pwrq)      /* Fixed part of the request */

{

/* Set device name */

strncpy(pwrq->ifr_name, ifname, IFNAMSIZ);

/* Do the request */

return(ioctl(skfd, request, pwrq));

}

因此,真正对网卡其他参数的实现是通过ioctl函数实现的,ioctl是驱动程序的基本功能,因此,如果自己想编写一个对网卡参数设置的程序,也应该使用ioctl函数。

下面在看使用

iwconfig eth0 rate auto

命令执行情况

main函数,当输入参数大于2的时候,调用:

goterr = set_info(skfd, argv + 2, argc - 2, argv[1]);

set_info函数调用iwcmd = find_command(args[0]);

用来查找命令,所有的命令都存放在一个表中,只要查找这个表即可:

static const struct iwconfig_entry iwconfig_cmds[] = {

……….

{ "bit",         set_bitrate_info,     1,    SIOCSIWRATE,

"Set Bit Rate",             "{N[k|M|G]|auto|fixed}" },

{ "rate",           set_bitrate_info,     1,    SIOCSIWRATE,

"Set Bit Rate",             "{N[k|M|G]|auto|fixed}" },

当第三个参数为rate的时候,就会自动调用set_bitrate_info函数,该函数的定义为:

set_bitrate_info

if(!strcasecmp(args[0], "auto"))

{

wrq.u.bitrate.value = -1;

wrq.u.bitrate.fixed = 0; //如果输入是auto,那么设置为bitrate.value = -1;

}

else

{

if(!strcasecmp(args[0], "fixed"))

{

/* Get old bitrate */

if(iw_get_ext(skfd, ifname, SIOCGIWRATE, &wrq) < 0)

return(IWERR_GET_EXT);

wrq.u.bitrate.fixed = 1;

}

……………

if(iw_set_ext(skfd, ifname, SIOCSIWRATE, &wrq) < 0)

return(IWERR_SET_EXT);

也就是说,如果选择auto,那么设置变量bitrate.fixed=0,最后调用iw_set_ext(skfd, ifname, SIOCSIWRATE, &wrq)函数,这个函数也是直接与网卡相关的。

与iw_get_ext函数一样,iw_set_ext也是通过SIOCGIWRATE来识别的:

static inline int

iw_set_ext(int              skfd,      /* Socket to the kernel */

const char *       ifname,          /* Device name */

int                request,   /* WE ID */

struct iwreq *     pwrq)      /* Fixed part of the request */

{

/* Set device name */

strncpy(pwrq->ifr_name, ifname, IFNAMSIZ);

/* Do the request */

return(ioctl(skfd, request, pwrq));

}

2、madiwifi驱动和硬件说明

该说明来源于“IEEE 802.11 Rate Adaptation: A Practical Approach”

该文主要提出了AMRR算法。

本文首先谈到是否能够基于包级的速率控制需要看硬件是否支持,有些芯片在硬件里面包含了CPU,

Texas Instruments around an ARM core ,WaveLAN 802.11b,因此具有低的延时。

然而,Atheros 802.11 chipsets芯片不包含CPU,需要主机CPU来完成很多的MAC功能,而主机CPU很难实现实时的控制,基于包的速率控制是不可行的,认为是高延时的一类。

ARF算法和AARF算法、以及AMARF都是基于低延时的控制算法,

Atheros的linux驱动,称为Multiband Atheros Driver for WiFi (MadWiFi),可以在SourceForge上找到源代码,使用了HAL(硬件抽象层的概念),与硬件有关的部分是二进制的代码,不提供源代码。

Atheros硬件允许用户创建9个发送FIFIO描述符,对发送进行调度;每个发送描述符包含了发送状况,数据的指针和长度,并且包含了一个4对的“速率/重传次数”对(r0/c0, r1/ c1,r2/c2,r3/c3)。

当无线信道可以发送的时候,硬件将引发处于FIFO头的数据的发送,首先以速率r0发送,如果发送失败,继续以速率r0发送c0-1次,然而再次以速率r1发送,如果发送失败,继续以速率r1发送c1-1次……如果发送失败了c0+c1+c2+c3次,那么放弃这次发送。

当发送完毕,或者发送放弃,硬件会在发送描述符中报告本次发送丢失的ACK的数目,因此,通过获知丢失的ACK的数目可以间接的得到本次的发送速率。

MadWiFi 的这个机制称为Multi Rate Retry mechanism。

3、onoe自适应速率说明

该说明来源于“Bit-rate Selection in Wireless Networks“,John C. Bicket ,MIT 硕士论文 P29

该文主要提出了SampleRate算法,也对其他的算法进行了说明。

onoe对每个包的失败与否并不敏感,试图找到丢帧率小于50%的最高速率;

对于每个目的连接,onoe保存了一个当前链路的比特速率、以及该比特率的信用度;算法跟踪每个链路当前比特速率的信用度,如果丢包率较小,那么增加该信用度;如果该信用度达到一定的阈值,那么尝试增加信用度;如果发生了一些错误,那么该信用度复位,速率下降。

当第一次发送到目的地址的时候,将初始的速率设置为24M(802.11a/g),11M(802.11b);onoe周期性的执行算法(缺省值为1秒)

1)如果没有数据发送成功,降低到下一个速率;

2)如果发送了10个以上的包,并且每个包的平均重传次数大于1,降低到下一个速率;

3)如果超过10%的包需要重传,将信用值减小(保持大于0),

4)如果少于10%的包需要重传,增加信用值;

5)如果信用值大于10,增加速率;

相关代码为:

//每次发送完数据后,都会调用该函数,来判断发送的结果,成功还是失败,以及重传的次数

void

ath_rate_tx_complete(struct ath_softc *sc,

struct ath_node *an, const struct ath_desc *ds)

{

struct onoe_node *on = ATH_NODE_ONOE(an);

if (ds->ds_txstat.ts_status == 0) //如果ts_status为0,表明发送成功

on->on_tx_ok++; //统计发送成功和发送失败的次数,

else

on->on_tx_err++;

on->on_tx_retr += ds->ds_txstat.ts_shortretry  //统计重传的次数,长重传加上短重传,适用于不同的长度,一般来说应该是一个为0,而另外一个不为0,

+ ds->ds_txstat.ts_longretry;

if (jiffies >= on->on_nextcheck) {  //判断现在的时间,每大约1秒钟执行一次速率控制

ath_rate_ctl(sc, &an->an_node);

/* XXX halve rate for station mode */

on->on_nextcheck = jiffies + (ath_rateinterval * HZ) / 1000; //ath_rateinterval定义为1000,

}

}

//这个是速率控制函数

static void

ath_rate_ctl(void *arg, struct ieee80211_node *ni)

{

struct ath_softc *sc = arg;

struct onoe_node *on = ATH_NODE_ONOE(ATH_NODE(ni));

struct ieee80211_rateset *rs = &ni->ni_rates;

int dir = 0, nrate, enough;

sc->sc_stats.ast_rate_calls++;

/*

* Rate control

* XXX: very primitive version.

*/

enough = (on->on_tx_ok + on->on_tx_err >= 10); //这两参数是从ath_rate_tx_complete中获得的,onoe算法要统计10次以上的发送结果才又有动作

/* no packet reached -> down */

if (on->on_tx_err > 0 && on->on_tx_ok == 0)  //如果压根就没有任何一次发送成功,速率控制的方向是递减,也就是算法中的第一中情况

dir = -1;

/* all packets needs retry in average -> down */

if (enough && on->on_tx_ok < on->on_tx_retr) //就是算法中的第二种情况,基本上所有的包都需要重传,降低速率

dir = -1;

//ath_rate_raise初始值定义为10,判断用于10%的一个变量

//如果没有发生传输失败,并且少于10%的包需要重传,增加速率,也就是第四钟情况

/* no error and less than rate_raise% of packets need retry -> up */

if (enough && on->on_tx_err == 0 &&

on->on_tx_retr < (on->on_tx_ok * ath_rate_raise) / 100)

dir = 1;

DPRINTF(sc, "%s: ok %d err %d retr %d upper %d dir %d\n",

ether_sprintf(ni->ni_macaddr),

on->on_tx_ok, on->on_tx_err, on->on_tx_retr,

on->on_tx_upper, dir);

nrate = ni->ni_txrate; //目前使用的速率,实际上是一个大于等于0的编号

switch (dir) {

case 0:  //不增加也不减少

if (enough && on->on_tx_upper > 0)

on->on_tx_upper--;

// on_tx_upper定义为信用值

//如果发送超过了10次,并且超过10%的包需要重传,信用值on_tx_upper减小

break;

case -1: //减少速率,

if (nrate > 0) {

nrate--; //如果大于0,减小速率

sc->sc_stats.ast_rate_drop++;

}

on->on_tx_upper = 0; //将信用值复位置0

break;

case 1:

/* raise rate if we hit rate_raise_threshold */

if (++on->on_tx_upper < ath_rate_raise_threshold)

break; //信用值加1,如果信用值没有达到阈值,那么直接退出,

on->on_tx_upper = 0; //否则,增加速率,将信用值复位置0

if (nrate + 1 < rs->rs_nrates) {

nrate++;

sc->sc_stats.ast_rate_raise++;

}

break;

}

if (nrate != ni->ni_txrate) {  //如果速率发生了改变,更新速率

DPRINTF(sc, "%s: %dM -> %dM (%d ok, %d err, %d retr)\n",

__func__,

(rs->rs_rates[ni->ni_txrate] & IEEE80211_RATE_VAL) / 2,

(rs->rs_rates[nrate] & IEEE80211_RATE_VAL) / 2,

on->on_tx_ok, on->on_tx_err, on->on_tx_retr);

ath_rate_update(sc, ni, nrate);

} else if (enough) //如果速率没有发生改变,发送次数已经大约10次了,重新复位

on->on_tx_ok = on->on_tx_err = on->on_tx_retr = 0; //复位

}

//速率更新的代码

static void

ath_rate_update(struct ath_softc *sc, struct ieee80211_node *ni, int rate)

{

// ath_rate_ctl函数仅仅是确定了合适使用的速率,然而,atheor网卡需要在发送描述符中写入r0/c0,r1/c1, r2/c2, r3/c3四个速率和retry对,如果填充这四个发送对呢?

r0速率,就是判断的最佳发送速率,r1就是最佳速率的下一个速率(如果r0已经是最低速率,那么r1为0);

r2是r1的下一个速率,r3是r2的下一个速率,对于retry次数,分别设置为4,2,2,2

on->on_tx_rix0 = sc->sc_rixmap[ni->ni_rates.rs_rates[rate] & IEEE80211_RATE_VAL];

on->on_tx_rate0 = rt->info[on->on_tx_rix0].rateCode;

on->on_tx_try0 = 1 + 3;          /* 4 tries at rate 0 */  //速率r0的设置,就是最佳速率rate

if (--rate >= 0) { //速率r1的设置,就是最佳速率rate的下一个速率

rix = sc->sc_rixmap[ni->ni_rates.rs_rates[rate]&IEEE80211_RATE_VAL];

on->on_tx_rate1 = rt->info[rix].rateCode;

on->on_tx_rate1sp = on->on_tx_rate1 |

rt->info[rix].shortPreamble;

} else

on->on_tx_rate1 = on->on_tx_rate1sp = 0;

……….

速率r1/c1, r2/c2,  r3/c3是通过ath_rate_setupxtxdesc函数写入硬件的,这是与硬件相关的函数,

ath_rate_setupxtxdesc(struct ath_softc *sc, struct ath_node *an,

struct ath_desc *ds, int shortPreamble, size_t frame_size, u_int8_t rix)

{

struct onoe_node *on = ATH_NODE_ONOE(an);

ath_hal_setupxtxdesc(sc->sc_ah, ds

, on->on_tx_rate1sp, 2 /* series 1 */

, on->on_tx_rate2sp, 2 /* series 2 */

, on->on_tx_rate3sp, 2 /* series 3 */

);

}

速率r0/c0是通过ath_hal_setuptxdesc函数写入硬件的,

ath_hal_setuptxdesc(ah, ds

, pktlen          /* packet length */

, hdrlen          /* header length */

, atype           /* Atheros packet type */

, MIN(ni->ni_txpower, 60)/* txpower */

, txrate, try0    /* series 0 rate/tries */

, keyix           /* key cache index */

该函数在if_ath.c 中每次ath_tx_start函数进行发送的时候调用

通过上面的分析,我们认为实际上使用的发送速率就是r0,那么如何获取呢?

速率控制的驱动中有一个函数:

void

ath_rate_findrate(struct ath_softc *sc, struct ath_node *an,

int shortPreamble, size_t frameLen,

u_int8_t *rix, int *try0, u_int8_t *txrate)

{

struct onoe_node *on = ATH_NODE_ONOE(an);

*rix = on->on_tx_rix0;

*try0 = on->on_tx_try0;

if (shortPreamble)

*txrate = on->on_tx_rate0sp;

else

*txrate = on->on_tx_rate0;

}

返回的txrate就是实际上使用的on->on_tx_rate0,r0

该函数也在if_ath.c 中每次ath_tx_start函数进行发送的时候调用,这个函数虽然参数很多,但是查看代码,只有struct ath_node *an的输入代码是有效的,shortPreamble是否为短符号,可以不考虑,设置为0.

因此,需要修改ioctl部分的函数,将auto部分返回-1的代码,修改为调用ath_rate_findrat函数,调用该函数的关键就是如何获取struct ath_node *an参数。

4、AMRR代码阅读

AMRR是onoe的改进,使用了二进制避退(BEB)的概念,来适应改变rn/cn的周期,为了适应快速变化的无线信道,设置c0=c1=c2=c3=1,速率r3设置为最小可用速率(6Mbps 802.11a),而r1和r2由r0决定,也就是r1为r0的下一个低速率,r2为r1的下一个低速率;因此,核心是改变r0。

AMRR使用了一个定时器来周期性的进行速率控制,时间设置为1秒。

static void

ath_ratectl(unsigned long data)

{

……..

asc->timer.expires = jiffies + ((HZ * interval) / 1000);

add_timer(&asc->timer);

}

首先看ath_rate_tx_complete函数,每次发送后是如何处理的,

void

ath_rate_tx_complete(struct ath_softc *sc,

struct ath_node *an, const struct ath_desc *ds)

{

struct amrr_node *amn = ATH_NODE_AMRR(an);

int sr = ds->ds_txstat.ts_shortretry;

int lr = ds->ds_txstat.ts_longretry;

int retry_count = sr + lr;

//这个变量表明重传的次数,需要注意c0=c1=c2=c3=1,因此,重传次数最多为4次,

amn->amn_tx_try0_cnt++;

//amn->amn_tx_try0_cnt变量为使用速率r0发送的次数,因为c0=1,每次发送,都会并且只会使//用r0发送一次,因此,这个变量还可以同时表示发送的包的个数(包括成功和失败的)

if (retry_count == 1) {

//如果重传次数是一次,由于c0=c1=c2=c3=1,必然可以得出使用速率r0发送了一次,失败之后,又使用速率r1发送了一次

amn->amn_tx_try1_cnt++;

} else if (retry_count == 2) {

//同理可以得出如果重传次数是2次,由于c0=c1=c2=c3=1,必然可以得出使用速率r0发送了一次,失败之后,又使用速率r1发送了一次,再使用r2发送了一次

amn->amn_tx_try1_cnt++;

amn->amn_tx_try2_cnt++;

} else if (retry_count == 3) {

amn->amn_tx_try1_cnt++;

amn->amn_tx_try2_cnt++;

amn->amn_tx_try3_cnt++;

} else if (retry_count > 3) {

//如果次数为4次,说明各个速率都尝试过,并且最后都发送都失败了。

amn->amn_tx_try1_cnt++;

amn->amn_tx_try2_cnt++;

amn->amn_tx_try3_cnt++;

amn->amn_tx_failure_cnt++;

}

}

static void

ath_rate_ctl(void *arg, struct ieee80211_node *ni)

{

struct ath_softc *sc = arg;

struct amrr_node *amn = ATH_NODE_AMRR(ATH_NODE (ni));

int old_rate;

#define is_success(amn) (amn->amn_tx_try1_cnt  < (amn->amn_tx_try0_cnt / 10))

如果发送包的中小于10%的包出现了重传,那么认为上次速率设置发送是成功的,

#define is_enough(amn)  (amn->amn_tx_try0_cnt > 10)

如果发送包的个数大于10,认为是足够进行判断了

#define is_failure(amn) (amn->amn_tx_try1_cnt > (amn->amn_tx_try0_cnt / 3))

如果发送包中出现大约33%的包需要重传,那么认为上次速率设置是失败的

#define is_max_rate(ni) ((ni->ni_txrate + 1) >= ni->ni_rates.rs_nrates)

#define is_min_rate(ni) (ni->ni_txrate == 0)

old_rate = ni->ni_txrate;

if (is_success(amn) && is_enough(amn)) {

//如果发送成功,将成功的计数器加1

amn->amn_success++;

if (amn->amn_success == amn->amn_success_threshold &&

!is_max_rate(ni)) {

如果计数器达到阈值,那么将速率增加,并且设置一个变量,amn_recovery=1,表明处于尝试速率增加阶段

amn->amn_recovery = 1;

amn->amn_success = 0;

ni->ni_txrate++;

DPRINTF(sc, "increase rate to %d\n", ni->ni_txrate);

} else

amn->amn_recovery = 0;

} else if (is_failure(amn)) {

如果发送失败,成功的计数器清零。

amn->amn_success = 0;

if (!is_min_rate(ni)) {

if (amn->amn_recovery) {

/* recovery failure. */

如果处于是处于尝试速率增加阶段,那么将阈值翻倍,

amn->amn_success_threshold *= 2;

amn->amn_success_threshold = min(amn->amn_success_threshold,

(u_int)ath_rate_max_success_threshold);

DPRINTF(sc, "decrease rate recovery thr: %d\n",

amn->amn_success_threshold);

} else {

/* simple failure. */

否则,如果处于正常情况下的失败,将阈值设置为最小

amn->amn_success_threshold = ath_rate_min_success_threshold;

DPRINTF(sc, "decrease rate normal thr: %d\n",

amn->amn_success_threshold);

}

amn->amn_recovery = 0;

ni->ni_txrate--;

} else

amn->amn_recovery = 0;

}

}

可以看出来,AMRR协议类似于AARF,每次尝试速率增加的时候,如果尝试失败,将阈值翻倍。

5、SampleRate自适应速率说明

该说明来源于“Bit-rate Selection in Wireless Networks“,John C. Bicket ,MIT 硕士论文 P29

该文主要提出了SampleRate算法,也对其他的算法进行了说明。

该算法周期性的发送其他速率的包进行探询,看其他速率发送是否合适。

本文认为速率选择算法需要考虑一下几个方面:

1)不能认为因为如果某低速率性能性能差,那么比它高一级的速率会性能更差(3.5章节的说明)

2)最高吞吐量的算法可能丢包率也多,丢包率小的速率可能吞吐量未必最高(3.2节)

3)链路状态是会发生变化的,不能对链路状态进行反映,会导致低的吞吐量;

4)速率选择算法必须要高效,不能尝试所有的速率;

6、AMARF协议的实现

AMARF协议是ARF协议的改进,核心是给每个速率设置一个不同的阈值,阈值的大小可以自适应的变化,因此,AMARF适用于信道快速变化的场合。

使用madwifi可能存在的问题:

1)madwifi属于高延时的系统,是否能够进行每个包的控制,另外如何控制FIFIO?

2)madwifi驱动本身提供了多速率多重传的机制,如何利用?

因此,我们提出两种方法:

纯粹的P-AMARF,结合madwifi的M-AMARF

首先分析P-AMARF:

还是从ath_rate_tx_complete函数看其,每次发送后是如何处理的。

上面的几个算法都是周期性的对速率进行控制,引入了定时器,每次发送之后仅仅是对速率进行统计,我们的算法需要进行快速的调整速率,每次发送完之后都进行控制!!

void ath_rate_tx_complete(struct ath_softc *sc,struct ath_node *an, const struct ath_desc *ds)

{

struct amarf_node *arn = ATH_NODE_AMARF(an);

arn->amarf_txok=!(ds->ds_txstat.ts_status); //判断是否发送成功

//printk("txok is %d\n",arn->amarf_txok);

ath_rate_ctl(sc,&an->an_node);

}

static void ath_rate_ctl(void *arg,struct ieee80211_node *ni)

{

struct ath_softc *sc=arg;

struct amarf_node *arn=ATH_NODE_AMARF(ATH_NODE(ni));

int old_rate;

sc->sc_stats.ast_rate_calls++;

#define is_max_rate(ni)    ((ni->ni_txrate + 1) >= ni->ni_rates.rs_nrates)

#define is_min_rate(ni)    (ni->ni_txrate == 0) /* ni_txrate : index to ni_rates[] */

old_rate = ni->ni_txrate;

if(arn->amarf_txok)

{

if(arn->amarf_counter_success>=amarf_th[ni->ni_txrate])

{ arn->amarf_counter_success=0;

amarf_th[ni->ni_txrate]+=amarf_c;

DEBUG2("amarf_c is %f\n",amarf_c);

if(!is_max_rate(ni))

{ ni->ni_txrate++;

DEBUG2("increase to %d\n",ni->ni_txrate);

arn->amarf_isprobe=1;

}

}

else

arn->amarf_counter_success++;

arn->amarf_isprobe=0;

//  DEBUG2("consecutive counter_success is %d\n",arn->amarf_counter_success);

}

else

{

arn->amarf_counter_success=0;

if(arn->amarf_isprobe==1)

amarf_th[ni->ni_txrate]-=amarf_a;

DEBUG2("amarf_a is %f\n",amarf_a);

else

amarf_th[ni->ni_txrate]-=amarf_b;

DEBUG2("amarf_b is %f\n",amarf_b);

if(!is_min_rate(ni))

{

ni->ni_txrate--;

DEBUG2("decrease to %d\n",ni->ni_txrate);

}

arn->amarf_isprobe=0;

}

if(ni->ni_txrate!=old_rate)

ath_rate_update(sc,ni,ni->ni_txrate);

}

7、madwifi驱动阅读

该程序有好几个目录

ath: 物理网卡的控制函数,例如发送等

ath_rate: 速率控制的相关代码

hal: 硬件抽象层的相关库,不公开源代码

net80211: 802.11相关的代码,与物理层独立,完成诸如scan、wap、VAP等功能。

Ieee80211_wireless.c

static const iw_handler ieee80211_handlers[] = {

(iw_handler) ieee80211_ioctl_siwrate,        /* SIOCSIWRATE */

(iw_handler) ieee80211_ioctl_giwrate,        /* SIOCGIWRATE */

模块的入口函数:if_ath_pci.c /module_init(init_ath_pci);

static int __init

init_ath_pci(void)

{

printk(KERN_INFO "%s: %s\n", dev_info, version);

if (pci_register_driver(&ath_pci_drv_id) < 0) {

printk("ath_pci: No devices found, driver not installed.\n");

return (-ENODEV);

}

#ifdef CONFIG_SYSCTL

ath_sysctl_register();

#endif

return (0);

}

module_init(init_ath_pci);

初始化会调用 ath_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)函数,

进一步加载ath无线网卡:

if (ath_attach(vdevice, dev) != 0)

goto bad4;

If_ath.c (ath)文件实现ath_attach函数,该函数的实现非常复杂,关键的代码如下:

ath_attach(u_int16_t devid, struct net_device *dev)

{……………

ah = _ath_hal_attach(devid, sc, NULL, (void *) dev->mem_start, &status);

可能是探测与硬件相关的东西,不能显示更进一步的代码:

………………..

dev->hard_start_xmit = ath_hardstart;

dev->do_ioctl = ath_ioctl;

ieee80211_ifattach(ic); 该函数在Ieee80211.c文件中实现

ic->ic_vap_create = ath_vap_create;

ic->ic_vap_delete = ath_vap_delete;

重点需要掌握ath与80211目录的关联,ath_vap_create是做什么的。

static struct ieee80211vap *

ath_vap_create(struct ieee80211com *ic, const char *name, int unit,

int opmode, int flags, struct net_device *mdev)

{

……………………

ieee80211_vap_setup(ic, dev, name, unit, opmode, flags);

………….

(void) ieee80211_vap_attach(vap,

ieee80211_media_change, ieee80211_media_status);

Ieee80211_wireless.c

ieee80211_vap_setup(struct ieee80211com *ic, struct net_device *dev,

const char *name, int unit, int opmode, int flags)->

调用下面的函数,实现ioctl函数,

ieee80211_ioctl_vattach(struct ieee80211vap *vap)

{

struct net_device *dev = vap->iv_dev;

dev->do_ioctl = ieee80211_ioctl;

这里就存在两次对dev->do_ioctl赋值的情况,一次是在ath_attach函数中,一次在ieee80211_ioctl_vattach函数中,怀疑第二次是不是就将第一次的覆盖了。

ieee80211_ioctl函数就有很多的子函数用以实现不同的功能,以设置和获取速率为例:

ieee80211_ioctl_siwrate(struct net_device *dev, struct iw_request_info *info,

struct iw_param *rrq, char *extra)

获取速率的ioctl函数的实现

ieee80211_ioctl_giwrate(struct net_device *dev,   struct iw_request_info *info,

struct iw_param *rrq, char *extra)

{

struct ieee80211vap *vap = dev->priv;

struct ifmediareq imr;

int rate;

memset(&imr, 0, sizeof(imr));

vap->iv_media.ifm_status(vap->iv_dev, &imr);

rrq->fixed = IFM_SUBTYPE(vap->iv_media.ifm_media) != IFM_AUTO;

/* media status will have the current xmit rate if available */

rate = ieee80211_media2rate(imr.ifm_active);  //这个函数的意思是将速率编号转换为实际的速率

if (rate == -1)       /* IFM_AUTO */

rate = 0;   //如果是auto,那么就返回0

rrq->value = 1000000 * (rate / 2);

return 0;

}

有关于速率的东西都在ifm_active变量中,这个变量包含了很多的属性,包括速率、模式等等,在if_media.h文件中

#define    IFM_IEEE80211_DS1     5     /* Direct Sequence 1Mbps */

#define    IFM_IEEE80211_DS2     6     /* Direct Sequence 2Mbps */

#define    IFM_IEEE80211_DS5     7     /* Direct Sequence 5.5Mbps */

#define    IFM_IEEE80211_DS11    8     /* Direct Sequence 11Mbps */

函数ieee80211com_media_status实现获得ifm_active变量

ieee80211com_media_status(struct net_device *dev, struct ifmediareq *imr)

{

struct ieee80211com *ic = dev->priv;   /*XXX*/

imr->ifm_status = IFM_AVALID;

if (!TAILQ_EMPTY(&ic->ic_vaps))

imr->ifm_status |= IFM_ACTIVE;

imr->ifm_active = media_status(ic->ic_opmode, ic->ic_curchan);

}

通过上述分析,发现ifm_active变量的获得实际上是ieee80211com *ic结构体获得的。

数据发送过程分析:

static int

ath_hardstart(struct sk_buff *skb, struct net_device *dev)->

ath_tx_start(struct net_device *dev, struct ieee80211_node *ni, struct ath_buf *bf, struct sk_buff *skb, int nextfraglen)

{

……….

ath_rate_findrate(sc, an, shortPreamble, skb->len,

&rix, &try0, &txrate);  //有关速率控制的东西

………

ath_hal_setuptxdesc(ah, ds

, pktlen          /* packet length */

, hdrlen          /* header length */

, atype           /* Atheros packet type */

, MIN(ni->ni_txpower, 60)/* txpower */

, txrate, try0    /* series 0 rate/tries */

, keyix           /* key cache index */

, antenna         /* antenna mode */

, flags           /* flags */

, ctsrate         /* rts/cts rate */

, ctsduration     /* rts/cts duration */

, icvlen          /* comp icv len */

, ivlen           /* comp iv len */

, comp       /* comp scheme */

);  //与硬件相关的函数

if (try0 != ATH_TXMAXTRY)

ath_rate_setupxtxdesc(sc, an, ds, shortPreamble, skb->len, rix);  //速率控制的描述符

}

8、有关速率获取的问题

通过上面的代码分析,我们认为在madwifi情况下,r0就是实际的速率,因此,需要在应用层能够获得r0的指,经过跟踪,发现当调用iwconfig命令,来获取速率的值的时候,会调用

ieee80211_ioctl_giwrate(struct net_device *dev,   struct iw_request_info *info,

struct iw_param *rrq, char *extra)

{

struct ieee80211vap *vap = dev->priv;

现在当使用auto的自适应速率时候,自动返回-1,因此,获得速率值为0.

因此,需要修改这个函数,调用ARMARF自适应速率的ath_rate_findrate函数

通过上面的分析,我们认为实际上使用的发送速率就是r0,那么如何获取呢?

速率控制的驱动中有一个函数:

void

ath_rate_findrate(struct ath_softc *sc, struct ath_node *an,

int shortPreamble, size_t frameLen,

u_int8_t *rix, int *try0, u_int8_t *txrate)

{

struct onoe_node *on = ATH_NODE_ONOE(an);

*rix = on->on_tx_rix0;

*try0 = on->on_tx_try0;

if (shortPreamble)

*txrate = on->on_tx_rate0sp;

else

*txrate = on->on_tx_rate0;

}

返回的txrate就是实际上使用的on->on_tx_rate0,r0

该函数为实际上只有struct ath_node *an参数是需要传递的,

ieee80211_ioctl_giwrate输入的参数为net_device *dev,因此,需要将该输入参数转换得到struct ath_node *an参数:

因此,我们需要关注三个结构体:

struct net_device *dev结构体包含了ieee80211vap *vap结构体:

ieee80211vap *vap = dev->priv;

an = ATH_NODE(ni);

struct ath_softc {

struct ieee80211com sc_ic;        /* NB: must be first */

struct net_device *sc_dev;

struct semaphore sc_lock;         /* dev-level lock */

struct ath_node {

struct ieee80211_node an_node;         /* base class */

u_int16_t an_decomp_index;        /* decompression mask index */

u_int32_t an_avgrssi;             /* average rssi over all rx frames */

u_int8_t  an_prevdatarix;         /* rate ix of last data frame */

u_int16_t an_minffrate;                /* mimum rate in kbps for ff to aggragate */

HAL_NODE_STATS an_halstats;       /* rssi statistics used by hal */

struct ath_buf *an_tx_ffbuf[WME_NUM_AC]; /* ff staging area */

ath_bufhead an_uapsd_q;                /* U-APSD delivery queue */

int an_uapsd_qdepth;              /* U-APSD delivery queue depth */

ath_bufhead an_uapsd_overflowq;   /* U-APSD overflow queue (for > MaxSp frames) */

int an_uapsd_overflowqdepth;           /* U-APSD overflow queue depth */

spinlock_t an_uapsd_lock;         /* U-APSD deleivery queue lock */

/* variable-length rate control state follows */

};

struct ieee80211_node {

struct ieee80211vap *ni_vap;

struct ieee80211com *ni_ic;

struct ieee80211_node_table *ni_table;

…………..

struct ieee80211vap {

struct net_device *iv_dev;        /* associated device */

struct net_device_stats     iv_devstats;     /* interface statistics */

struct ifmedia iv_media;               /* interface media config */

#ifdef CONFIG_NET_WIRELESS

struct iw_statistics iv_iwstats;       /* wireless statistics block */

#endif

…………

struct ieee80211com *iv_ic;           /* back ptr to common state */

……………

下面三个结构体的包含关系为:

ath_node-> ieee80211_node-> ieee80211vap

并且由于首地址是一样的,因此,只要获得任意一个结构体,可以使用指针强制转换为另外两个结构体,例如已知ieee80211_node结构体,可以获得ath_node,在if_ath.c 的ath_tx_start函数中,使用

an = ATH_NODE(ni)命令获得,

#define    ATH_NODE(_n)                ((struct ath_node *)(_n))

struct ath_softc *sc = dev->priv;

struct ieee80211com *an = dev->priv;

ath_rate_findrate(sc, an, shortPreamble, skb->len, &rix, &try0, &txrate);

调试中发现,始终不能正确的传递参数,经过调试,发现ieee80211.c和if_ath.c代码的struct net_device变量是不一样的,两者的参数名字分别为ath0和wifi0,也就是两个网络设备的名字,因此,没有办法传递参数。

下面还是分析iwconfig中的参数是如何传递的,以ieee80211_ioctl_siwpower函数为例,设置发送功率。

ieee80211_ioctl_siwpower(struct net_device *dev, struct iw_request_info *info,

struct iw_param *wrq, char *extra)

{

struct ieee80211vap *vap = dev->priv;

struct ieee80211com *ic = vap->iv_ic;

……………..

return IS_UP(ic->ic_dev) ? ic->ic_reset(ic->ic_dev) : 0;  如果设备已经UP,那么以新的参数对设备设置。

这里面调用了一个函数指针,ic->ic_reset,经过检查,只有 if_ath.c文件调用了,

if_ath.c(692):      ic->ic_reset = ath_reset;

ath_attach(u_int16_t devid, struct net_device *dev)

{

struct ath_softc *sc = dev->priv;

struct ieee80211com *ic = &sc->sc_ic;

struct ath_hal *ah;

………….

ic->ic_reset = ath_reset;

ath_reset(struct net_device *dev)

{

struct ath_softc *sc = dev->priv;

struct ieee80211com *ic = &sc->sc_ic;

struct ath_hal *ah = sc->sc_ah;

struct ieee80211_channel *c;

HAL_STATUS status;

iwconfig命令会最终调用ieee80211_wireless.c中的各种参数设置和控制命令,查看代码,发现ieee80211_wireless中本身定义了很多的参数变量,如果需要设置,再调用底层的函数将参数写入网卡芯片,如果读参数,就直接读自己已经保存好的参数,而并不是读真正的物理层的参数!

ieee80211_ioctl_siwpower代码最终会调用ath_reset,这两个函数的输入参数都是struct net_device *dev,他是如何进行参数转换的呢?将ath0变为wifi0?

Iwconfig中,首先由输入参数ath0(struct net_device *dev)获得vap

struct ieee80211vap *vap = dev->priv;

该结构体定义为:

struct ieee80211vap {

struct net_device *iv_dev;        /* associated device */

struct ieee80211com *iv_ic;      /* back ptr to common state */

……………

然后由vap获得ic,

struct ieee80211com *ic = vap->iv_ic;

而该结构体定义为:

struct ieee80211com {

struct net_device *ic_dev;        /* associated device */

又关联了一个net_device设备,这个设备正是wifi0,因此,调用ic_reset的时候,并不是直接将

ieee80211_ioctl_siwpower的输出参数dev放进去,而是转了两个弯,

ic->ic_reset(ic->ic_dev)。

再次回到获取速率的函数,调用下面的函数获得速率,

ath_rate_findrate(sc, an, shortPreamble, skb->len, &rix, &try0, &txrate);

这里,sc以前直接定义为:ieee80211_ioctl_siwpower输入参数的dev,

struct ath_softc *sc = dev->priv;

需要将其定义为wifi0,因此,需要定义为:

struct ieee80211vap *vap = dev->priv; 由ath0 device获得vap

struct ieee80211com *ic = vap->iv_ic;

struct net_device *wifi= ic->ic_dev;  再次获得wifi device

struct ath_softc *sc = wifi ->priv; 获得sc

如果调用findrate函数,第二个参数ath_node需要传递过来,发现这比较困难,因此,暂时不使用这个方法,而是自己在ieee80211com结构体中定义一个变量,利用该变量传递参数。

ath_rate_findrate(struct ath_softc *sc, struct ath_node *an,

int shortPreamble, size_t frameLen,

u_int8_t *rix, int *try0, u_int8_t *txrate)

9、madwifi程序阅读(二)

前面的阅读已经发现,madwifi驱动里面有两套net_device设备,一套是if_ath­_xx.c程序使用的,名字为“wifi0”,另一套是ieee802.11_xx程序使用的,为ath0。

ath_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)

\if_ath_pci.c(179):   dev = alloc_netdev(sizeof(struct ath_pci_softc), "wifi%d", ether_setup);

int ath_attach(u_int16_t devid, struct net_device *dev)

\if_ath.c(897): error = ieee80211_create_vap(ic, "ath%d", dev,

autocreatemode, IEEE80211_CLONE_BSSID);

相应的,两个设备都定义了自己的发送函数

这是ath0的发送函数,

int ieee80211_vap_setup(struct ieee80211com *ic, struct net_device *dev,

const char *name, int unit, int opmode, int flags)

\ieee80211.c(395):    dev->hard_start_xmit = ieee80211_hardstart;

这是wifi的发送函数,

int ath_attach(u_int16_t devid, struct net_device *dev)

if_ath.c(670):   dev->hard_start_xmit = ath_hardstart;

ieee80211_hardstart函数调用ieee80211_parent_queue_xmit,ieee80211_parent_queue_xmit 继续调用(void) dev_queue_xmit(skb),dev_queue_xmit函数是在linux内核中定义的

\net\core\dev.c(994):int dev_queue_xmit(struct sk_buff *skb),该函数会调用 dev->hard_start_xmit,这个函数估计就是ath_hardstart。

ath_hardstart调用ath_tx_start函数,ath_tx_start继续调用ath_hal_filltxdesc等与硬件相关的函数,完成最终的发送任务。

这样就出来一个问题了,由谁来调用ieee80211_hardstart?

经过查找,两个hard_start_xmit函数都没有在madfiwi程序中调用,看来都是在linux内核中调用的。

也就说,madwifi同时注册了两个网络设备,一个是ath0,一个是wifi0,应用程序发送数据的流程是这样的:应用程序->内核->ath0设备->内核->wifi0设备。

要搞清楚为什么会这样,就必须了解madwifi中的VAP(虚拟AP的概念),可以在一个实际的无线设备上创建多个逻辑设备,要使用设备,必须使用下面的命令创建ath设备

wlanconfig athX create wlandev wifiX wlanmode

例如:

wlanconfig ath0 create wlandev wifi0 wlanmode ap

wlanconfig ath1 create wlandev wifi0 wlanmode ap

wlanconfig ath2 create wlandev wifi0 wlanmode ap

iwconfig ath0 essid "lmn"

iwconfig ath1 essid "XYZ"

iwconfig ath2 essid "abc"

因此,可以认为ath是逻辑设备,wifi是物理设备。

(上面部分参考了madwifi.pdf文档,一个ppt,上面对madwifi进行了粗略的介绍,包括存在的问题)

The OpenHAL ported to MADWiFi at http://pdos.csail.mit.edu/~jbicket/openhal/

因此,我们在发送数据的时候,已经指明了使用ath0虚拟网卡,自然就会调用ieee80211_hardstart,问题就变成了两次调用内核操作是如何实现的?

还是先看看几个结构体是如何关联的,

ath_attach(u_int16_t devid, struct net_device *dev)

{

struct ath_softc *sc = dev->priv;  由wifi设备获得sc

struct ieee80211com *ic = &sc->sc_ic; 由wifi设备获得com设备ic,

……….

ic->ic_dev = dev;          ic的设备指向wifi

ic->ic_mgtstart = ath_mgtstart;

ic->ic_init = ath_init;

ic->ic_reset = ath_reset;

ic->ic_newassoc = ath_newassoc;

ic->ic_updateslot = ath_updateslot;

………

dev->open = ath_init;

dev->stop = ath_stop;

dev->hard_start_xmit = ath_hardstart;

dev->tx_timeout = ath_tx_timeout;

ieee80211_ifattach(ic); 加载ieee802设备

创建VAP,得到ath0设备。

error = ieee80211_create_vap(ic, "ath%d", dev,

autocreatemode, IEEE80211_CLONE_BSSID);

[转]madwifi无线网卡源代码阅读的更多相关文章

  1. Mongodb源代码阅读笔记:Journal机制

    Mongodb源代码阅读笔记:Journal机制 Mongodb源代码阅读笔记:Journal机制 涉及的文件 一些说明 PREPLOGBUFFER WRITETOJOURNAL WRITETODAT ...

  2. 【转】Tomcat总体结构(Tomcat源代码阅读系列之二)

    本文是Tomcat源代码阅读系列的第二篇文章,我们在本系列的第一篇文章:在IntelliJ IDEA 和 Eclipse运行tomcat 7源代码一文中介绍了如何在intelliJ IDEA 和 Ec ...

  3. 利用doxygen提高源代码阅读效率

    阅读开源项目的源代码是提高自己编程能力的好方法,而有一个好的源代码阅读工具无疑能够让你在阅读源代码时事半功倍.之前找过不少源代码阅读工具,像SourceInsight.sourcenav.scitoo ...

  4. CI框架源代码阅读笔记5 基准測试 BenchMark.php

    上一篇博客(CI框架源代码阅读笔记4 引导文件CodeIgniter.php)中.我们已经看到:CI中核心流程的核心功能都是由不同的组件来完毕的.这些组件类似于一个一个单独的模块,不同的模块完毕不同的 ...

  5. 淘宝数据库OceanBase SQL编译器部分 源代码阅读--Schema模式

    淘宝数据库OceanBase SQL编译器部分 源代码阅读--Schema模式 什么是Database,什么是Schema,什么是Table,什么是列,什么是行,什么是User?我们能够能够把Data ...

  6. CI框架源代码阅读笔记3 全局函数Common.php

    从本篇開始.将深入CI框架的内部.一步步去探索这个框架的实现.结构和设计. Common.php文件定义了一系列的全局函数(一般来说.全局函数具有最高的载入优先权.因此大多数的框架中BootStrap ...

  7. [C++ 2011 STL (VS2012 Update4) 源代码阅读系列(2)]熟悉一些宏定义和模版偏特化或叫模版专门化

    [C++ 2011 STL (VS2012 Update4) 源代码阅读系列(2)]熟悉一些宏定义和模版偏特化或叫模版专门化 // point_test.cpp : 知识点练习和测试,用于单步调试,跟 ...

  8. 【转】Tomcat源代码阅读系列

    在IntelliJ IDEA 和 Eclipse运行tomcat 7源代码(Tomcat源代码阅读系列之一) Tomcat总体结构(Tomcat源代码阅读系列之二) Tomcat启动过程(Tomcat ...

  9. Java Jdk1.8 HashMap源代码阅读笔记二

    三.源代码阅读 3.元素包括containsKey(Object key) /** * Returns <tt>true</tt> if this map contains a ...

随机推荐

  1. string类(三、string.format格式字符串)

    参考连接: http://www.cnblogs.com/luluping/archive/2009/04/30/1446665.html http://blog.csdn.net/samsone/a ...

  2. oracle decode处理被除数为0 的情况

    ,,a) per from aa; 例如 我的b为(N30+N31+N32+N33+N34+N35+N36+N37+N38) ,,(N33)||'%' WHERE ssrq=''||sssq||'';

  3. Qt slot中获取sender

    调用sender();函数 例如获取一个QRadioButton QRadioButton *rb = qobject_cast<QRadioButton *>(sender());

  4. 盘点:七大.NET著名开源项目

    尽管过了相当长的时间,花费了不少资源,微软和.NET社区还是在最近几年加入到了开源运动的阵营中来了,这令人相当惊讶,因为两大阵营一直都是经常对立的.然而,事实是依靠开源,微软社区中的开源开发工具日益发 ...

  5. .net Session延长过期时间

    一.全局网站(即服务器)级 IIS-网站-属性-Asp.net-编辑配置-状态管理-会话超时(分钟)-设置为120,即为2小时,即120分钟后如果当前用户没有操作,那么Session就会自动过期. 二 ...

  6. 【集中工作薄】 当前文件夹中所有Excel文件中 多个工作簿的第一个工作表 复制到工作簿中

    功能:当前文件夹中所有Excel文件中 多个工作簿的第一个工作表 复制到工作簿中 Sub Books2Sheets() '定义对话框变量 Dim fd As FileDialog Set fd = A ...

  7. [Chrome] 如何下载老版本的 Chrome

    Google 官方只提供了最新版的 Chrome, 在旧版本的系统(如:Ubuntu 12.04 LTS)安装不上 这里提供了 Chrome 的历史版本下载 备注:Ubuntu 12.04 LTS 可 ...

  8. Servlet------>jsp jstl核心标签库

    这里不需要刻意记,在jar里,c.tld文件都有,可以自己找源码看

  9. webpack4学习笔记(三)

    webpack打包资源文件 1,打包css文件,先安装css-loader和style-loader npm install --save-dev css-loader style-loader we ...

  10. LMAX Disruptor 原理

    http://mechanitis.blogspot.com/search/label/disruptor http://ifeve.com/disruptor/, 并发框架Disruptor译文 h ...