文中所有代码基于Android8.0
1.dumpsys在哪,如何实现的?
android开发经常使用 adb shell dumpsys xoxo,xoxo在SystemServer.java里有很多,像activity,window之类的,比如:
ServiceManager.addService(Context.WINDOW_SERVICE, wm);
ServiceManager.addService(Context.INPUT_SERVICE, inputManager);
native里也有,比如:
void MediaPlayerService::instantiate() {
defaultServiceManager()->addService(
String16("media.player"), new MediaPlayerService());
}
总之,是我们平时所见的service。
dumpsys能执行,无疑是一个可执行程序,在这里哟:
framework/native/cmds/dumpsys
从它的Android.bp文件中可以看到,会编出一个二进制文件:
cc_binary {
name: "dumpsys",
defaults: ["dumpsys_defaults"],
srcs: [
"main.cpp",
],
}
首先,看下它的main.cpp:
int main(int argc, char* const argv[]) {
signal(SIGPIPE, SIG_IGN);
sp sm = defaultServiceManager();
fflush(stdout);
if (sm == nullptr) {
ALOGE("Unable to get default service manager!");
aerr << "dumpsys: Unable to get default service manager!" << endl;
return 20;
}
Dumpsys dumpsys(sm.get());
return dumpsys.main(argc, argv);
}
用signal函数捕获了SIGPIPE信号并忽略,防止收到信号后程序退出。接着
拿到了ServiceManager后,用sm生成了一个Dumpsys的实例,然后执行了他的main方法。
接下来看Dumpsys的main方法(其实它也就这一个方法):
int Dumpsys::main(int argc, char* const argv[]) {
Vector services;
Vector args;
...
while (1) {
...balabala....
for (size_t i = 0; i < N; i++) {
String16 service_name = std::move(services[i]);
// 1. 获取service
sp service = sm_->checkService(service_name);
if (service != nullptr) {
int sfd[2];
//2.打开管道,用来读写数据
if (pipe(sfd) != 0) {
continue;
}
//3.生成fd用 unique_fd封装一下
unique_fd local_end(sfd[0]);
unique_fd remote_end(sfd[1]);
sfd[0] = sfd[1] = -1;
//4.开个线程,准备dump
std::thread dump_thread([=, remote_end { std::move(remote_end) }]() mutable {
//5.dump数据
int err = service->dump(remote_end.get(), args);
remote_end.reset();
});
auto timeout = std::chrono::seconds(timeoutArg);
auto start = std::chrono::steady_clock::now();
auto end = start + timeout;
struct pollfd pfd = {
.fd = local_end.get(),
.events = POLLIN
};
bool timed_out = false;
bool error = false;
while (true) {
auto time_left_ms = [end]() {
auto now = std::chrono::steady_clock::now();
auto diff = std::chrono::duration_cast(end - now);
return std::max(diff.count(), 0ll);
};
//6.等待有数据写入
int rc = TEMP_FAILURE_RETRY(poll(&pfd, 1, time_left_ms()));
...
char buf[4096];
//7.读出数据
rc = TEMP_FAILURE_RETRY(read(local_end.get(), buf, sizeof(buf)));
...
//8.写入到标准输出
if (!WriteFully(STDOUT_FILENO, buf, rc)) {
break;
}
}
} else {
aerr << "Can't find service: " << service_name << endl;
}
}
return 0;
}
代码挺长,精简掉一些balabala的其他代码,比如解析参数,错误判断,是否skipservice之类的,主要流程有以下8点,代码中已经标记:
1.1 从ServiceManager中按名字拿出服务,名字是我们所谓的各种xoxo。
1.2 打开一个管道,linux下的管道想必多少有些了解,就是一头写一头读,现在写入端传给service那边去写,dumpsys这边来读。模拟了一个类似场景,见第二小节。
1.3 刚才生成的读写端都是fd,用android的类封装了一下,好处是能用它的析构方法将fd关掉,避免资源泄漏,类似一些智能指针。同时还提供了一个移动构造函数,用于下面创建线程时用 std::move()移动到lambda表达式里面。
1.4 用一个lambda表达式创建线程。
1.5 调用service的dump方法,并把管道的写入端fd传给service,service里会将数据写入这个管道中。
1.6 用 poll 系统调用来监测刚才的管道 读 这一端有没有数据来,也就是service那边是不是写进去了。TEMP_FAILURE_RETRY这个宏在失败了会自动重试。
1.7 当service将数据写入管道后,poll系统调用会返回,接着就可以读取数据了。
1.8 将所有数据全部写到 STDOUT_FILENO ,也就是标准输出,即我们执行命令的terminal,这样就service的将数据显示出来了。
2.pip poll 的模拟demo
上面提到创建pipe,然后写入,然后从另一端读出,这两个方法的基本用法,参考下面的小demo。
编译运行(mac下需要加上-std=c++11,linux不需要):
$ g++ main.cpp -std=c++11 -lpthread
$ ./a.out
oh baby, data is coming ...
data received : Oh, yes !
#include
#include
#include
#include
#include
#include
#include
#include
constexpr int kTimeOut = 5;
int main(int argc, char** argv) {
//1.创建管道
int pids[2];
int ret = pipe(pids);
//2.新线程中延时写入
std::string src{"Oh, yes !"};
std::thread t([&]() {
std::this_thread::sleep_for(std::chrono::seconds(2));
write(pids[1], src.c_str(), src.length());
close(pids[1]);
});
t.detach();
nfds_t size = 1;
struct pollfd fds[size];
fds[0].fd = pids[0];
fds[0].events = POLLIN;
//3.等待数据写入
ret = poll(fds, size, kTimeOut * 1000);
if (ret == -1) {
std::cerr << "failed, baby ~" << std::endl;
return -1;
}
if (ret == 0) {
std::cout << "time out , baby ~" << std::endl;
return -1;
}
//4.读出数据
if (fds[0].revents & POLLIN) {
std::cout << "oh baby, data is coming ... " << std::endl;
char dst[src.length() + 1];
read(pids[0], dst, src.length());
dst[src.length()] = '\0';
close(pids[0]);
std::cout << "data received : " << dst << std::endl;
}
}
2.1 打开一个管道,结果保存在pids[2]这个数组中,其中pids[0]用于读取,pids[1]用于写入。
2.2 新线程等待两秒后将数据写入。
2.3 poll在这等待有数据可读(POLLIN)。
2.4 将数据读出来打印。
这样就可以理解dumpsys中这个用法。
3. service 来源 和 dump
从代码上看,service是从IServiceManager这个类里取出来的:
sp service = sm_->checkService(service_name);
这是一个c++的类,那我们ServiceManager.java里添加的不都是java的对象吗?
3.1 native层的ServiceManager
### IServiceManager.cpp
sp defaultServiceManager()
{
if (gDefaultServiceManager != NULL) return gDefaultServiceManager;
{
AutoMutex _l(gDefaultServiceManagerLock);
while (gDefaultServiceManager == NULL) {
gDefaultServiceManager = interface_cast(
ProcessState::self()->getContextObject(NULL));
if (gDefaultServiceManager == NULL)
sleep(1);
}
}
return gDefaultServiceManager;
}
可以看出,native层的servicemanager是这样拿到的:
gDefaultServiceManager = interface_cast(
ProcessState::self()->getContextObject(NULL));
先通过ProcessState::self()->getContextObject(NULL) 生成了一个BpBinder(0)的对象,然后又通过interface_cast这个宏生成了一个BpManagerService(BpBinder(0))的实例。
关于Binder,后面文章专门分解。
3.2 java层的ServiceManager
### ServiceManager.java
private static IServiceManager getIServiceManager() {
if (sServiceManager != null) {
return sServiceManager;
}
// Find the service manager
sServiceManager = ServiceManagerNative
.asInterface(Binder.allowBlocking(BinderInternal.getContextObject()));
return sServiceManager;
}
首先BinderInternal.getContextObject()拿到一个对象,contextObject跟上面有点像,继续往下看,一个native方法:
/**
* Return the global "context object" of the system. This is usually
* an implementation of IServiceManager, which you can use to find
* other services.
*/
public static final native IBinder getContextObject();
意思是,你能通过这个 context object 去find 其他service,这不就是servicemanager吗?
继续来到jni层:
static jobject android_os_BinderInternal_getContextObject(JNIEnv* env, jobject clazz)
{
sp b = ProcessState::self()->getContextObject(NULL);
return javaObjectForIBinder(env, b);
}
同样是ProcessState::self()->getContextObject(NULL); 然后转成一个java对象返回了。也就证明无论java还是native,用的都是一个servicemanager,所以service都在一个native对象里存着。
当然servicemanager从8.0开始不止一个,vndservicemanager,hwservicemanager也同时存在,现在我们先关心这一个。后面文章再分解。
3.3 dump方法
作为系统服务,肯定要与binder通信,下面看native和java 系统服务的实现,超精简,主要看dump方法。后面单独写如何实现native和java的系统服务。
3.3.1 java层一般用aidl生成接口,比如PMS:
public class PackageManagerService extends IPackageManager.Stub
implements PackageSender {
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return;
.....
}
}
IPackageManager.Stub 继承Binder类,实现dump方法, 第一个参数FileDescriptor 就是我们在dumpsys用管道创建的写入端。
第一句checkpermission一定要有,否则google cts过不了,自己写服务要注意。
3.3.2 native层的服务,比如MediaPlayerService:
//继承BnMediaPlayerService
class MediaPlayerService : public BnMediaPlayerService {
virtual status_t dump(int fd, const Vector& args) const;
}
//继承BnInterface
class BnMediaPlayerService: public BnInterface {}
//继承BBinder,BBinder继承IBinder,从而拥有dump方法
template
class BnInterface : public INTERFACE, public BBinder {}
这样两种service都有相同的接口,都存储在一个servicemanager中,所以dumpsys可以统一执行命令,dump不同的service。
欢迎一起讨论。