需求:需要測(cè)試wifi模塊的5g吞吐量铺坞。需要開啟5g wifiap
實(shí)現(xiàn)過程:
1.可行性
首先看下WifiManager開啟ap函數(shù)的說明:
/**
* Start AccessPoint mode with the specified
* configuration. If the radio is already running in
* AP mode, update the new configuration
* Note that starting in access point mode disables station
* mode operation
* @param wifiConfig SSID, security and channel details as
* part of WifiConfiguration
* @return {@code true} if the operation succeeds, {@code false} otherwise
*
* @hide Dont open up yet
*/
public boolean setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) {
try {
mService.setWifiApEnabled(wifiConfig, enabled);
return true;
} catch (RemoteException e) {
return false;
}
}
參數(shù)wifiConfig可指定 SSID, security 和 channel
然而蜡歹,WifiConfiguration這個(gè)類中不包含channel或者freq這樣的屬性踱卵,那是如何實(shí)現(xiàn)配置的呢?
2. 跟源碼
跟一下framework的實(shí)現(xiàn):
目前環(huán)境是Rockchip3229 android 5.1 虑啤,代碼路徑與aosp有些差別作媚,不過總體區(qū)別不大拂募。
mService.setWifiApEnabled(wifiConfig, enabled);
這里通過Binder直接調(diào)用到WifiService的同名函數(shù)
2.1. frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiServiceImpl.java
/**
* see {@link android.net.wifi.WifiManager#setWifiApEnabled(WifiConfiguration, boolean)}
* @param wifiConfig SSID, security and channel details as
* part of WifiConfiguration
* @param enabled true to enable and false to disable
*/
public void setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) {
...
// null wifiConfig is a meaningful input for CMD_SET_AP
if (wifiConfig == null || wifiConfig.isValid()) {
mWifiController.obtainMessage(CMD_SET_AP, enabled ? 1 : 0, 0, wifiConfig).sendToTarget();
} else {
Slog.e(TAG, "Invalid WifiConfiguration");
}
}
這里可以看到庭猩,使用wifiController發(fā)送msg去下發(fā)指令
2.2. frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiController.java
WifiController繼承了StateMachine類窟她,上面的obtainMessage函數(shù)是StateMachine類的函數(shù),參數(shù)列表是(msgWhat, arg1, arg2, obj)
不清楚有哪些State或各個(gè)State下對(duì)命令的策略是什么的情況下蔼水,直接搜索case CMD_SET_AP震糖,
發(fā)現(xiàn)在ApStaDisabledState下的對(duì)該命令的處理為
class ApStaDisabledState extends State {
...
if (msg.arg1 == 1) {
mWifiStateMachine.setHostApRunning((WifiConfiguration) msg.obj,
true);
transitionTo(mApEnabledState);
}
可以看到他會(huì)執(zhí)行開啟softap,并轉(zhuǎn)到ApEnabledState趴腋,檢查一下這個(gè)State的操作
class ApEnabledState extends State {
@Override
public boolean processMessage(Message msg) {
switch (msg.what) {
...
case CMD_SET_AP:
if (msg.arg1 == 0) {
mWifiStateMachine.setHostApRunning(null, false);
transitionTo(mApStaDisabledState);
}
break;
default:
return NOT_HANDLED;
}
return HANDLED;
}
}
可以看到吊说,這個(gè)State沒有復(fù)寫enter()函數(shù),只處理了關(guān)閉softAP的命令
2.3 frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiStateMachine.java
public void setHostApRunning(WifiConfiguration wifiConfig, boolean enable) {
if (enable) {
sendMessage(CMD_START_AP, wifiConfig);
} else {
sendMessage(CMD_STOP_AP);
}
}
WifiStateMachine同樣繼承StateMachine优炬,所以直接找處理CMD_START_AP指令的代碼颁井,搜索case CMD_START_AP:
class InitialState extends State {
...
@Override
public boolean processMessage(Message message) {
...
case CMD_START_AP:
if (mWifiNative.loadDriver()) {
setWifiApState(WIFI_AP_STATE_ENABLING);
transitionTo(mSoftApStartingState);
} else {
loge("Failed to load driver for softap");
}
...
}
...
}
這里的執(zhí)行了加載wifi驅(qū)動(dòng)并轉(zhuǎn)入softapStartingState,沒有其他操作蠢护,那具體配置softap的操作應(yīng)該是在該State的enter函數(shù)中處理的
我們來看SoftApStartingState這個(gè)類的enter函數(shù)
class SoftApStartingState extends State {
@Override
public void enter() {
final Message message = getCurrentMessage();
if (message.what == CMD_START_AP) {
final WifiConfiguration config = (WifiConfiguration) message.obj;
if (config == null) {
mWifiApConfigChannel.sendMessage(CMD_REQUEST_AP_CONFIG);
} else {
mWifiApConfigChannel.sendMessage(CMD_SET_AP_CONFIG, config);
startSoftApWithConfig(config);
}
} else {
throw new RuntimeException("Illegal transition to SoftApStartingState: " + message);
}
}
...
}
這里我們傳入的config是不為空的雅宾,那么核心邏輯應(yīng)該是在startSoftApWithConfig(config)這個(gè)函數(shù)中
private void startSoftApWithConfig(final WifiConfiguration config) {
// Start hostapd on a separate thread
new Thread(new Runnable() {
public void run() {
try {
mNwService.startAccessPoint(config, mInterfaceName);
} catch (Exception e) {
loge("Exception in softap start " + e);
try {
mNwService.stopAccessPoint(mInterfaceName);
mNwService.startAccessPoint(config, mInterfaceName);
} catch (Exception e1) {
loge("Exception in softap re-start " + e1);
sendMessage(CMD_START_AP_FAILURE);
return;
}
}
...
}
}).start();
}
這里調(diào)用mNwService.startAccessPoint(config, mInterfaceName)并進(jìn)行了一次出錯(cuò)的重試,這個(gè)mInterfaceName是初始化WifiStateMachine時(shí)賦值的葵硕,我們?cè)赪ifiServiceImpl類的構(gòu)造函數(shù)中可以看到:
public WifiServiceImpl(Context context) {
mContext = context;
mInterfaceName = SystemProperties.get("wifi.interface", "wlan0");
mTrafficPoller = new WifiTrafficPoller(mContext, mInterfaceName);
...
}
這里執(zhí)行g(shù)etprop眉抬,默認(rèn)值為wlan0,實(shí)際也就是wlan0
現(xiàn)在我們繼續(xù)跟到mNwService這個(gè)對(duì)象懈凹,這里也是通過Binder的叫做INetworkManagementService的接口調(diào)用的蜀变,在frameworks目錄find一下文件NetworkManagementService.java
前方高能!U号;杷铡!M!M葑ā0袈印!Fㄉ獭Q毯堋!@狻雾袱!
2.4 frameworks/base/services/core/java/com/android/server/NetworkManagementService.java
@Override
public void startAccessPoint(
WifiConfiguration wifiConfig, String wlanIface) {
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
try {
wifiFirmwareReload(wlanIface, "AP");
if (wifiConfig == null) {
mConnector.execute("softap", "set", wlanIface);
} else {
mConnector.execute("softap", "set", wlanIface, wifiConfig.SSID,
"broadcast", "6", getSecurityType(wifiConfig),
new SensitiveArg(wifiConfig.preSharedKey));
}
mConnector.execute("softap", "startap");
} catch (NativeDaemonConnectorException e) {
throw e.rethrowAsParcelableException();
}
}
這里可以看到,這個(gè)execute函數(shù)里面官还,終于用到了我們傳入的WifiConfiguration芹橡,這個(gè)參數(shù)經(jīng)歷了長(zhǎng)途跋涉,終于被解析了望伦!
但是林说,這里只解析了ssid和preSharedKey煎殷,也就是wifiap的用戶名密碼,沒有留地方給我們需要的channel或者freq腿箩。我們繼續(xù)往下看這個(gè)函數(shù)的形參豪直。
2.5 frameworks/base/services/core/java/com/android/server/NativeDaemonConnector.java
在這個(gè)類中搜到了三個(gè)execute()函數(shù),根據(jù)上面的類型珠移,只能是下面這個(gè)
/**
* Issue the given command to the native daemon and return a single expected
* response. Any arguments must be separated from base command so they can
* be properly escaped.
*/
public NativeDaemonEvent execute(String cmd, Object... args)
throws NativeDaemonConnectorException {
final NativeDaemonEvent[] events = executeForList(cmd, args);
if (events.length != 1) {
throw new NativeDaemonConnectorException(
"Expected exactly one response, but received " + events.length);
}
return events[0];
}
函數(shù)說明中說道弓乙,將指令傳給native daemon,參數(shù)和基礎(chǔ)命令必須分開钧惧,繼續(xù)跟調(diào)用
public NativeDaemonEvent[] executeForList(String cmd, Object... args)
throws NativeDaemonConnectorException {
return execute(DEFAULT_TIMEOUT, cmd, args);
}
最終跟到了boss唆貌,
public NativeDaemonEvent[] execute(int timeout, String cmd, Object... args)
throws NativeDaemonConnectorException {
...
final StringBuilder rawBuilder = new StringBuilder();
final StringBuilder logBuilder = new StringBuilder();
final int sequenceNumber = mSequenceNumber.incrementAndGet();
makeCommand(rawBuilder, logBuilder, sequenceNumber, cmd, args);
final String rawCmd = rawBuilder.toString();
final String logCmd = logBuilder.toString();
synchronized (mDaemonLock) {
if (mOutputStream == null) {
throw new NativeDaemonConnectorException("missing output stream");
} else {
try {
mOutputStream.write(rawCmd.getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
throw new NativeDaemonConnectorException("problem sending command", e);
}
}
}
...
}
核心部分是通過這個(gè)outputStream將指令寫出去,寫到哪里呢垢乙,我們看下這個(gè)流對(duì)象如何被賦值的:
private void listenToSocket() throws IOException {
LocalSocket socket = null;
try {
socket = new LocalSocket();
LocalSocketAddress address = determineSocketAddress();
socket.connect(address);
InputStream inputStream = socket.getInputStream();
synchronized (mDaemonLock) {
mOutputStream = socket.getOutputStream();
}
...
} catch (IOException ex) {
loge("Communications error: " + ex);
throw ex;
} finally {
...
}
}
很明顯這個(gè)是通過determineSocketAddress()這個(gè)函數(shù)建立的unixSocket锨咙,并以客戶端的形式連接上這個(gè)socket, 看下bind到了哪個(gè)地址上:
private LocalSocketAddress determineSocketAddress() {
if (mSocket.startsWith("__test__") && Build.IS_DEBUGGABLE) {
return new LocalSocketAddress(mSocket);
} else {
return new LocalSocketAddress(mSocket, LocalSocketAddress.Namespace.RESERVED);
}
}
這個(gè)mSocket又是在構(gòu)造中賦值的追逮,那追構(gòu)造到NativeDaemonConnector->NetworkManagementService
發(fā)現(xiàn)這個(gè)構(gòu)造是private的酪刀,直接本地搜索,發(fā)現(xiàn)在
static NetworkManagementService create(Context context,
String socket) throws InterruptedException {
final NetworkManagementService service = new NetworkManagementService(context, socket);
final CountDownLatch connectedSignal = service.mConnectedSignal;
if (DBG) Slog.d(TAG, "Creating NetworkManagementService");
service.mThread.start();
if (DBG) Slog.d(TAG, "Awaiting socket connection");
connectedSignal.await();
if (DBG) Slog.d(TAG, "Connected");
return service;
}
public static NetworkManagementService create(Context context) throws InterruptedException {
return create(context, NETD_SOCKET_NAME);
}
private static final String NETD_SOCKET_NAME = "netd";
可以看到NETD_SOCKET_NAME就是剛才unixSocket通信的文件名钮孵,值為"netd"骂倘,那connector這邊是socket的client端,另一端在哪里呢巴席。
我們可以看下剛才的代碼
return new LocalSocketAddress(mSocket, LocalSocketAddress.Namespace.RESERVED);
注意第二個(gè)參數(shù)namespace是RESERVED历涝,這說明這個(gè)socket必須是init進(jìn)程開啟的,netd是android的很重要的daemon進(jìn)程漾唉,我們?cè)趇nit.rc中可以找到netd的聲明
service netd /system/bin/netd
class main
socket netd stream 0660 root system
socket dnsproxyd stream 0660 root inet
socket mdns stream 0660 root system
socket fwmarkd stream 0660 root inet
那么在/system/core/init/init.c中的main函數(shù)中可以看到解析init.rc的操作
int main(int argc, char **argv)
{
...
restorecon("/dev");
restorecon("/dev/socket");
…
init_parse_config_file("/init.rc");
…
}
socket的創(chuàng)建過程是在啟動(dòng)netd service的時(shí)候荧库,在函數(shù)service_start中實(shí)現(xiàn)
void service_start(struct service *svc, const char *dynamic_args)
{
...
for (si = svc->sockets; si; si = si->next) {
int socket_type = (!strcmp(si->type, "stream") ? SOCK_STREAM : (!strcmp(si->type, "dgram") ? SOCK_DGRAM : SOCK_SEQPACKET));
int s = create_socket(si->name, socket_type,
si->perm, si->uid, si->gid);
if (s >= 0) {
publish_socket(si->name, s);
}
}
…
}
socket的建立是在create_socket函數(shù)中
int create_socket(const char *name, int type, mode_t perm, uid_t uid, gid_t gid)
{
struct sockaddr_un addr;
int fd, ret;
#ifdef HAVE_SELINUX
char *secon;
#endif
fd = socket(PF_UNIX, type, 0);
...
}
其實(shí)從init啟動(dòng)到socket建立還有很復(fù)雜的過程,如要詳細(xì)說明需要另起篇幅赵刑,這里就說到netd的socket建立分衫,然后繼續(xù)查看netd作為socket的server端,如何處理從NativeDaemonConnector寫過去的命令的般此。
在netd的main.cpp中蚪战,可以看到他如何處理命令
2.6 system/netd/server/main.cpp
int main() {
CommandListener *cl;
NetlinkManager *nm;
...
ALOGI("Netd 1.0 starting");
remove_pid_file();
blockSigpipe();
if (!(nm = NetlinkManager::Instance())) {
ALOGE("Unable to create NetlinkManager");
exit(1);
};
cl = new CommandListener();
nm->setBroadcaster((SocketListener *) cl);
if (nm->start()) {
ALOGE("Unable to start NetlinkManager (%s)", strerror(errno));
exit(1);
}
...
}
這里注冊(cè)了一個(gè)command監(jiān)聽器,來處理通過netd socket傳進(jìn)來的指令
2.7 /system/netd/server/CommandListener.cpp
CommandListener::CommandListener() :
FrameworkListener("netd", true) {
registerCmd(new InterfaceCmd());
registerCmd(new IpFwdCmd());
registerCmd(new TetherCmd());
registerCmd(new NatCmd());
registerCmd(new ListTtysCmd());
registerCmd(new PppdCmd());
registerCmd(new SoftapCmd());
registerCmd(new BandwidthControlCmd());
registerCmd(new IdletimerControlCmd());
registerCmd(new ResolverCmd());
registerCmd(new FirewallCmd());
registerCmd(new ClatdCmd());
registerCmd(new NetworkCommand());
if (!sNetCtrl)
sNetCtrl = new NetworkController();
if (!sTetherCtrl)
sTetherCtrl = new TetherController();
if (!sNatCtrl)
sNatCtrl = new NatController();
if (!sPppCtrl)
sPppCtrl = new PppController();
if (!sSoftapCtrl)
sSoftapCtrl = new SoftapController();
if (!sBandwidthCtrl)
sBandwidthCtrl = new BandwidthController();
if (!sIdletimerCtrl)
sIdletimerCtrl = new IdletimerController();
if (!sResolverCtrl)
sResolverCtrl = new ResolverController();
if (!sFirewallCtrl)
sFirewallCtrl = new FirewallController();
if (!sInterfaceCtrl)
sInterfaceCtrl = new InterfaceController();
if (!sClatdCtrl)
sClatdCtrl = new ClatdController(sNetCtrl);
...
}
這個(gè)commandListener的構(gòu)造里面铐懊,定義了很多針對(duì)特定指令的處理器邀桑,這里我們發(fā)送的是softap類型的command,指令是set和startap科乎,處理softap指令部分的代碼如下
int CommandListener::SoftapCmd::runCommand(SocketClient *cli,
int argc, char **argv) {
...
if (argc < 2) {
cli->sendMsg(ResponseCode::CommandSyntaxError,
"Missing argument in a SoftAP command", false);
return 0;
}
if (!strcmp(argv[1], "startap")) {
rc = sSoftapCtrl->startSoftap();
} else if (!strcmp(argv[1], "stopap")) {
rc = sSoftapCtrl->stopSoftap();
} else if (!strcmp(argv[1], "fwreload")) {
rc = sSoftapCtrl->fwReloadSoftap(argc, argv);
} else if (!strcmp(argv[1], "status")) {
asprintf(&retbuf, "Softap service %s running",
(sSoftapCtrl->isSoftapStarted() ? "is" : "is not"));
cli->sendMsg(rc, retbuf, false);
free(retbuf);
return 0;
} else if (!strcmp(argv[1], "set")) {
rc = sSoftapCtrl->setSoftap(argc, argv);
} else {
cli->sendMsg(ResponseCode::CommandSyntaxError, "Unrecognized SoftAP command", false);
return 0;
}
...
return 0;
}
看到調(diào)用的是softapController->setSoftap()和softapController->startSoftap()
2.7 system/netd/server/SoftapController.cpp
static const char HOSTAPD_CONF_FILE[] = "/data/misc/wifi/hostapd.conf";
int SoftapController::setSoftap(int argc, char *argv[]) {
...
if (argc > 7) {
if (!strcmp(argv[6], "wpa-psk")) {
generatePsk(argv[3], argv[7], psk_str);
asprintf(&fbuf, "%swpa=3\nwpa_pairwise=TKIP CCMP\nwpa_psk=%s\n", wbuf, psk_str);
} else if (!strcmp(argv[6], "wpa2-psk")) {
generatePsk(argv[3], argv[7], psk_str);
asprintf(&fbuf, "%swpa=2\nrsn_pairwise=CCMP\nwpa_psk=%s\n", wbuf, psk_str);
} else if (!strcmp(argv[6], "open")) {
asprintf(&fbuf, "%s", wbuf);
}
}
...
fd = open(HOSTAPD_CONF_FILE, O_CREAT | O_TRUNC | O_WRONLY | O_NOFOLLOW, 0660);
...
if (write(fd, fbuf, strlen(fbuf)) < 0) {
ALOGE("Cannot write to \"%s\": %s", HOSTAPD_CONF_FILE, strerror(errno));
ret = ResponseCode::OperationFailed;
}
...
return ret;
}
可以看到setSoftap()實(shí)際就是把參數(shù)存入了配置文件/data/misc/wifi/hostapd.conf
而startSoftap()函數(shù)如下:
static const char HOSTAPD_BIN_FILE[] = "/system/bin/hostapd";
int SoftapController::startSoftap() {
...
if ((pid = fork()) < 0) {
ALOGE("fork failed (%s)", strerror(errno));
return ResponseCode::ServiceStartFailed;
}
if (!pid) {
ensure_entropy_file_exists();
if (execl(HOSTAPD_BIN_FILE, HOSTAPD_BIN_FILE,
"-e", WIFI_ENTROPY_FILE,
HOSTAPD_CONF_FILE, (char *) NULL)) {
ALOGE("execl failed (%s)", strerror(errno));
}
ALOGE("SoftAP failed to start");
return ResponseCode::ServiceStartFailed;
} else {
mPid = pid;
ALOGD("SoftAP started successfully");
usleep(AP_BSS_START_DELAY);
}
return ResponseCode::SoftapStatusResult;
}
核心就是調(diào)用/system/bin/hostapd壁畸,然后使用/data/misc/wifi/hostapd.conf中存儲(chǔ)的參數(shù),開啟ap喜喂。接下來就是hostapd去調(diào)用驅(qū)動(dòng)的過程了瓤摧,平臺(tái)層的代碼就分析完了竿裂。接下還會(huì)出發(fā)TetherStateChange,然后通過TetherController去調(diào)用dnsmasq去開啟DHCP服務(wù)照弥,這里就不做詳細(xì)分析了腻异。