需求:需要测试wifi模块的5g吞吐量。需要开启5g wifiap
实现过程:
1.可行性
首先看下WifiManager开启ap函数的说明:
/**
* 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;
}
}
参数wifiConfig可指定 SSID, security 和 channel
然而,WifiConfiguration这个类中不包含channel或者freq这样的属性,那是如何实现配置的呢?
2. 跟源码
跟一下framework的实现:
目前环境是Rockchip3229 android 5.1 ,代码路径与aosp有些差别,不过总体区别不大。
mService.setWifiApEnabled(wifiConfig, enabled);
这里通过Binder直接调用到WifiService的同名函数
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发送msg去下发指令
2.2. frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiController.java
WifiController继承了StateMachine类,上面的obtainMessage函数是StateMachine类的函数,参数列表是(msgWhat, arg1, arg2, obj)
不清楚有哪些State或各个State下对命令的策略是什么的情况下,直接搜索case CMD_SET_AP,
发现在ApStaDisabledState下的对该命令的处理为
class ApStaDisabledState extends State {
...
if (msg.arg1 == 1) {
mWifiStateMachine.setHostApRunning((WifiConfiguration) msg.obj,
true);
transitionTo(mApEnabledState);
}
可以看到他会执行开启softap,并转到ApEnabledState,检查一下这个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;
}
}
可以看到,这个State没有复写enter()函数,只处理了关闭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");
}
...
}
...
}
这里的执行了加载wifi驱动并转入softapStartingState,没有其他操作,那具体配置softap的操作应该是在该State的enter函数中处理的
我们来看SoftApStartingState这个类的enter函数
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是不为空的,那么核心逻辑应该是在startSoftApWithConfig(config)这个函数中
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();
}
这里调用mNwService.startAccessPoint(config, mInterfaceName)并进行了一次出错的重试,这个mInterfaceName是初始化WifiStateMachine时赋值的,我们在WifiServiceImpl类的构造函数中可以看到:
public WifiServiceImpl(Context context) {
mContext = context;
mInterfaceName = SystemProperties.get("wifi.interface", "wlan0");
mTrafficPoller = new WifiTrafficPoller(mContext, mInterfaceName);
...
}
这里执行getprop,默认值为wlan0,实际也就是wlan0
现在我们继续跟到mNwService这个对象,这里也是通过Binder的叫做INetworkManagementService的接口调用的,在frameworks目录find一下文件NetworkManagementService.java
前方高能!!!!!!!!!!!!!!
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();
}
}
这里可以看到,这个execute函数里面,终于用到了我们传入的WifiConfiguration,这个参数经历了长途跋涉,终于被解析了!
但是,这里只解析了ssid和preSharedKey,也就是wifiap的用户名密码,没有留地方给我们需要的channel或者freq。我们继续往下看这个函数的形参。
2.5 frameworks/base/services/core/java/com/android/server/NativeDaemonConnector.java
在这个类中搜到了三个execute()函数,根据上面的类型,只能是下面这个
/**
* 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];
}
函数说明中说道,将指令传给native daemon,参数和基础命令必须分开,继续跟调用
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);
}
}
}
...
}
核心部分是通过这个outputStream将指令写出去,写到哪里呢,我们看下这个流对象如何被赋值的:
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 {
...
}
}
很明显这个是通过determineSocketAddress()这个函数建立的unixSocket,并以客户端的形式连接上这个socket, 看下bind到了哪个地址上:
private LocalSocketAddress determineSocketAddress() {
if (mSocket.startsWith("__test__") && Build.IS_DEBUGGABLE) {
return new LocalSocketAddress(mSocket);
} else {
return new LocalSocketAddress(mSocket, LocalSocketAddress.Namespace.RESERVED);
}
}
这个mSocket又是在构造中赋值的,那追构造到NativeDaemonConnector->NetworkManagementService
发现这个构造是private的,直接本地搜索,发现在
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);
注意第二个参数namespace是RESERVED,这说明这个socket必须是init进程开启的,netd是android的很重要的daemon进程,我们在init.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函数中可以看到解析init.rc的操作
int main(int argc, char **argv)
{
...
restorecon("/dev");
restorecon("/dev/socket");
…
init_parse_config_file("/init.rc");
…
}
socket的创建过程是在启动netd service的时候,在函数service_start中实现
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函数中
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);
...
}
其实从init启动到socket建立还有很复杂的过程,如要详细说明需要另起篇幅,这里就说到netd的socket建立,然后继续查看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);
}
...
}
这里注册了一个command监听器,来处理通过netd socket传进来的指令
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);
...
}
这个commandListener的构造里面,定义了很多针对特定指令的处理器,这里我们发送的是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;
}
看到调用的是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()实际就是把参数存入了配置文件/data/misc/wifi/hostapd.conf
而startSoftap()函数如下:
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;
}
核心就是调用/system/bin/hostapd,然后使用/data/misc/wifi/hostapd.conf中存储的参数,开启ap。接下来就是hostapd去调用驱动的过程了,平台层的代码就分析完了。接下还会出发TetherStateChange,然后通过TetherController去调用dnsmasq去开启DHCP服务,这里就不做详细分析了。