欢迎加入我的知识星球Android系统开发指南
native服务添加
native服务就是用c++写的系统服务,通过init进程启动,可以实现binder接口供client调用。
下面我们以实现一个beanserver的后台服务为例:
- 首先需要写一个rc文件
// 文件路径根据自己需求放置// vendor/zzh/native-service/bean-server/beanserver.rcservice beanserver /system/bin/beanserver class main
- 写服务的main函数
// vendor/zzh/native-service/bean-server/main_beanserver.cpp#define LOG_TAG "beanserver"//#define LOG_NDEBUG 0#include using namespace android;int main(int argc __unused, char** argv __unused){ ALOGD(" beamserver start......"); return 0;}
这个服务我们启动后只是打印了一行日志就退出了,具体可以根据自己的需求加入自己开机处理的业务。
- 编写Android.bp
cc_binary {// 最终会生成到/system/bin/beanserver name: "beanserver", srcs: ["main_beanserver.cpp"], header_libs: [ ], shared_libs: [ "liblog", "libutils", "libui", "libgui", "libbinder", "libhidlbase", ], compile_multilib: "first", cflags: [ "-Wall", "-Wextra", "-Werror", "-Wno-unused-parameter", ],// 最终会生成到/system/etc/init/beanserver.rc// init进程启动时会解析/system/etc/init/目录下的rc文件 init_rc: ["beanserver.rc"], vintf_fragments: [ ],}
beanserver.rc在经过编译后会生成到/system/etc/init/beanserver.rc,然后init进程启动的时候就会解析该文件启动进程。
init进程解析/system/etc/init/的代码
// system/core/init/init.cpp// 可以看到init会解析多个目录下的rc文件static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list) { Parser parser = CreateParser(action_manager, service_list); std::string bootscript = GetProperty("ro.boot.init_rc", ""); if (bootscript.empty()) { parser.ParseConfig("/system/etc/init/hw/init.rc"); if (!parser.ParseConfig("/system/etc/init")) { late_import_paths.emplace_back("/system/etc/init"); } // late_import is available only in Q and earlier release. As we don't // have system_ext in those versions, skip late_import for system_ext. parser.ParseConfig("/system_ext/etc/init"); if (!parser.ParseConfig("/vendor/etc/init")) { late_import_paths.emplace_back("/vendor/etc/init"); } if (!parser.ParseConfig("/odm/etc/init")) { late_import_paths.emplace_back("/odm/etc/init"); } if (!parser.ParseConfig("/product/etc/init")) { late_import_paths.emplace_back("/product/etc/init"); } } else { parser.ParseConfig(bootscript); }}
- 加到编译镜像环境
// device/generic/car/emulator/aosp_car_emulator.mk// 找到自己项目的mk文件加入PRODUCT_PACKAGES += beanserver
这样才会将beanserver编译到镜像中。
- 编译镜像验证
编译完启动时看到有如下报错日志:
03-29 07:13:38.364 0 0 I init : Parsing file /system/etc/init/beanserver.rc...03-29 07:13:41.706 0 0 E init : Could not start service 'beanserver' as part of class 'core': File /system/bin/beanserver(labeled "u:object_r:system_file:s0") has incorrect label or no domain transition from u:r:init:s0 to another SELinux domain defined. Have you configured your service correctly? https://source.android.com/security/selinux/device-policy#label_new_services_and_address_denials. Note: this error shows up even in permissive mode in order to make auditing denials possible.
提示缺少selinux权限配置,我们可以在Google官网看到label_new_services_and_address_denials
下面我们配置一下selinux权限
selinux权限配置
- 编写自定义服务的te文件
// 我这里将新加的selinux配置放在了自己创建目录中// 注意文件的首尾保留一行空行// vendor/zzh/sepolicy/vendor/beanserver.te# beanservertype beanserver, domain, coredomain;type beanserver_exec, exec_type, file_type, system_file_type;init_daemon_domain(beanserver)
- 编写file_contexts文件
// vendor/zzh/sepolicy/vendor/file_contexts// 注意文件的首尾保留一行空行/system/bin/beanserver u:object_r:beanserver_exec:s0
添加到sepolicy编译规则
// device/generic/car/emulator/aosp_car_emulator.mkBOARD_VENDOR_SEPOLICY_DIRS += vendor/zzh/sepolicy/vendor
- 编译后启动模拟器
这个时候能看到服务启动了,但是发现beanserver一直在重启,日志如下:
经过排查发生这个现象的原因有两个:
(1)我们的进程启动后只是打印了一行日志就return 0退出了,通过查看init的代码发现,init进程会通过signal 9去kill掉退出的进程,这里总感觉有点矛盾,可能时init担心进程自己退出的不彻底,所以来个signal 9? init 里的代码如下:
// system/core/init/sigchld_handler.cppstatic pid_t ReapOneProcess() { siginfo_t siginfo = {}; // This returns a zombie pid or informs us that there are no zombies left to be reaped. // It does NOT reap the pid; that is done below. if (TEMP_FAILURE_RETRY(waitid(P_ALL, 0, &siginfo, WEXITED | WNOHANG | WNOWAIT)) != 0) { PLOG(ERROR) << "waitid failed"; return 0; } auto pid = siginfo.si_pid; if (pid == 0) return 0; // At this point we know we have a zombie pid, so we use this scopeguard to reap the pid // whenever the function returns from this point forward. // We do NOT want to reap the zombie earlier as in Service::Reap(), we kill(-pid, ...) and we // want the pid to remain valid throughout that (and potentially future) usages. auto reaper = make_scope_guard([pid] { TEMP_FAILURE_RETRY(waitpid(pid, nullptr, WNOHANG)); }); std::string name; std::string wait_string; Service* service = nullptr; if (SubcontextChildReap(pid)) { name = "Subcontext"; } else { service = ServiceList::GetInstance().FindService(pid, &Service::pid); if (service) { name = StringPrintf("Service '%s' (pid %d)", service->name().c_str(), pid); if (service->flags() & SVC_EXEC) { auto exec_duration = boot_clock::now() - service->time_started(); auto exec_duration_ms = std::chrono::duration_cast<std::chrono::milliseconds>(exec_duration).count(); wait_string = StringPrintf(" waiting took %f seconds", exec_duration_ms / 1000.0f); } else if (service->flags() & SVC_ONESHOT) { auto exec_duration = boot_clock::now() - service->time_started(); auto exec_duration_ms = std::chrono::duration_cast<std::chrono::milliseconds>(exec_duration) .count(); wait_string = StringPrintf(" oneshot service took %f seconds in background", exec_duration_ms / 1000.0f); } } else { name = StringPrintf("Untracked pid %d", pid); } }// beanserver退出后,si_code为CLD_EXITED if (siginfo.si_code == CLD_EXITED) { LOG(INFO) << name << " exited with status " << siginfo.si_status << wait_string; } else { LOG(INFO) << name << " received signal " << siginfo.si_status << wait_string; } if (!service) { LOG(INFO) << name << " did not have an associated service entry and will not be reaped"; return pid; }// 这里将siginfo传入Reap函数 service->Reap(siginfo); if (service->flags() & SVC_TEMPORARY) { ServiceList::GetInstance().RemoveService(*service); } return pid;}
void Service::Reap(const siginfo_t& siginfo) {// 如果没有oneshot属性或者设置了restart的属性,则会调用SIGKILL杀掉进程// 这里的oneshot和restart指的是rc文件里面的配置,我们的rc文件只声明了class main// 如果没有声明oneshot属性,则服务被杀后会重新启动,如果配置了oneshot则只会启动一次 if (!(flags_ & SVC_ONESHOT) || (flags_ & SVC_RESTART)) { KillProcessGroup(SIGKILL, false); } else { // Legacy behavior from ~2007 until Android R: this else branch did not exist and we did not // kill the process group in this case. if (SelinuxGetVendorAndroidVersion() >= __ANDROID_API_R__) { // The new behavior in Android R is to kill these process groups in all cases. The // 'true' parameter instructions KillProcessGroup() to report a warning message where it // detects a difference in behavior has occurred. KillProcessGroup(SIGKILL, true); } } ...}
为了保证服务不退出,我们先在main函数里加个死循环看下效果
int main(int argc __unused, char** argv __unused){ ALOGD(" beamserver start......"); while (true) { sleep(3); ALOGD("beanserver_thread..........."); } return 0;}
编译后启动模拟器看下启动日志:
可以看到目前服务已经正常了,这个服务将一直在后台运行。
如果我们只想开机后执行一些操作后退出,那也可以,我们修改下看看效果。
修改beanserver.rc文件:
service beanserver /system/bin/beanserver class main oneshot //只启动一次
修改main_beanserver.cpp文件:
#define LOG_TAG "beanserver"//#define LOG_NDEBUG 0#include using namespace android;void quickSort(int arr[], int left, int right) { int i = left, j = right; int tmp; int pivot = arr[(left + right) / 2]; while (i <= j) { while (arr[i] < pivot) i++; while (arr[j] > pivot) j--; if (i <= j) { tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; i++; j--; } }; if (left < j) quickSort(arr, left, j); if (i < right) quickSort(arr, i, right);}int main(int argc __unused, char** argv __unused){ ALOGD(" beamserver start..."); int data[] = {9, 21, 3, 66, 100, 8, 36}; quickSort(data, 0, 7); for (int i = 0; i < 7; i++) { ALOGD("data[%d]=%d", i, data[i]); } return 0;}
将一组数据用快速排序进行排序后输出,然后服务退出,通过日志可以看到,服务退出后init发送了signal 9,到此服务进程退出。
通过binder访问服务
先看下文件目录:
如果想要被别的进程调用,就需要实现binder接口,这样才算是一个完整的系统服务。
- 定义aidl接口
// vendor/zzh/native-service/bean-server/libbeanservice/aidl/com/zzh/IBeanService.aidlpackage com.zzh;interface IBeanService { void sayHello();}
- 加入Android.bp中进行编译
// vendor/zzh/native-service/bean-server/libbeanservice/Android.bpcc_library_shared { name: "libbeanservice_aidl", aidl: { export_aidl_headers: true, local_include_dirs: ["aidl"], include_dirs: [ ], }, srcs: [ ":beanservice_aidl", ], shared_libs: [ "libbase", "libcutils", "libutils", "liblog", "libbinder", "libgui", ], cflags: [ "-Werror", "-Wall", "-Wextra", ],}filegroup { name: "beanservice_aidl", srcs: [ "aidl/com/zzh/IBeanService.aidl", ], path: "aidl",}
make libbeanservice_aidl 后可以看到out下生成的文件:
IBeanService.cpp
IBeanService.h
BpBeanService.h
BnBeanService.h
- 实现服务,继承BnBeanService
BeanService.h
// vendor/zzh/native-service/bean-server/libbeanservice/BeanService.h#ifndef BEANSERVICE_H#define BEANSERVICE_H#include #include namespace android {class BeanService: public BinderService<BeanService>, public virtual ::com::zzh::BnBeanService, public virtual IBinder::DeathRecipient{public: // Implementation of BinderService static char const* getServiceName() { return "bean.like"; } // IBinder::DeathRecipient implementation virtual void binderDied(const wp<IBinder> &who); BeanService(); ~BeanService(); virtual binder::Status sayHello();};}#endif
这里BeanService继承了BinderService,必须重写getServiceName函数用来返回服务的名称,后续注册服务时会用到这个名字。
BeanService.cpp
// vendor/zzh/native-service/bean-server/libbeanservice/BeanService.cpp#define LOG_TAG "BeanService"//#define LOG_NDEBUG 0#include "BeanService.h"#include namespace android {using binder::Status;BeanService::BeanService() {}BeanService::~BeanService() {}void BeanService::binderDied(const wp<IBinder> &who) { ALOGE("%s: Java client's binder died, removing it from the list of active clients, who=%p", __FUNCTION__, &who);}Status BeanService::sayHello() { ALOGD(" BeanService::sayHello "); return Status::ok();}}
Android.bp
// vendor/zzh/native-service/bean-server/libbeanservice/Android.bpcc_library_shared { name: "libbeanservice", srcs: [ "BeanService.cpp", ], header_libs: [ ], shared_libs: [ "libbeanservice_aidl", "libbase", "libui", "liblog", "libutils", "libbinder", "libcutils", ], static_libs: [ ], include_dirs: [ ], export_shared_lib_headers: [ "libbinder", "libbeanservice_aidl", ], export_include_dirs: ["."], cflags: [ "-Wall", "-Wextra", "-Werror", "-Wno-ignored-qualifiers", ],}cc_library_shared { name: "libbeanservice_aidl", aidl: { export_aidl_headers: true, local_include_dirs: ["aidl"], include_dirs: [ ], }, srcs: [ ":beanservice_aidl", ], shared_libs: [ "libbase", "libcutils", "libutils", "liblog", "libbinder", "libgui", ], cflags: [ "-Werror", "-Wall", "-Wextra", ],}filegroup { name: "beanservice_aidl", srcs: [ "aidl/com/zzh/IBeanService.aidl", ], path: "aidl",}
- 注册服务
// vendor/zzh/native-service/bean-server/main_beanserver.cpp#define LOG_TAG "beanserver"//#define LOG_NDEBUG 0#include "BeanService.h"#include using namespace android;int main(int argc __unused, char** argv __unused){ ALOGD(" beamserver start..."); signal(SIGPIPE, SIG_IGN); // Set 5 threads for HIDL calls. Now cameraserver will serve HIDL calls in // addition to consuming them from the Camera HAL as well. hardware::configureRpcThreadpool(5, false); sp<ProcessState> proc(ProcessState::self()); sp<IServiceManager> sm = defaultServiceManager(); ALOGI("ServiceManager: %p", sm.get()); BeanService::instantiate(); ALOGI("ServiceManager: %p done instantiate", sm.get()); ProcessState::self()->startThreadPool(); IPCThreadState::self()->joinThreadPool();}
BeanService::instantiate()这个函数会调用父类BinderService的方法,最终会调用publish方法进行服务注册。
template<typename SERVICE>class BinderService{public:// 进行服务注册的方法,被instantiate()调用 static status_t publish(bool allowIsolated = false,int dumpFlags = IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT) { sp<IServiceManager> sm(defaultServiceManager()); // 这里进行服务注册,SERVICE::getServiceName()这个就是BeanService重写的方法,返回"bean.like" return sm->addService(String16(SERVICE::getServiceName()), new SERVICE(), allowIsolated, dumpFlags); } static void publishAndJoinThreadPool( bool allowIsolated = false, int dumpFlags = IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT) { publish(allowIsolated, dumpFlags); joinThreadPool(); }// 调用publish()方法 static void instantiate() { publish(); } static status_t shutdown() { return NO_ERROR; } ...}
- 编译后启动模拟器,看日志:
接下来根据提示配置selinux权限
adb pull /sys/fs/selinux/policy
adb logcat -b events -d | audit2allow -p policy
输出信息如下:
#============= beanserver ==============allow beanserver servicemanager:binder call;#============= hal_audio_caremu ==============allow hal_audio_caremu audioserver:fifo_file write;allow hal_audio_caremu default_prop:file read;#============= init ==============allow init vendor_toolbox_exec:file execute_no_trans;
我们将规则加入beanserver.te即可:
selinux一般情况下并不能一次加完,每次都是添加了log中所报的权限后,再次运行时又会有新的权限错误,我们按照上面的方法逐一添加即可,最终的te文件如下:
// vendor/zzh/sepolicy/vendor/beanserver.te# beanservertype beanserver, domain, coredomain;type beanserver_exec, exec_type, file_type, system_file_type;init_daemon_domain(beanserver)allow beanserver servicemanager:binder { call transfer };allow beanserver beanserver_service:service_manager add;
除了上述修改外,还需要做如下修改:
// vendor/zzh/sepolicy/public/service.tetype beanserver_service, service_manager_type;
// vendor/zzh/sepolicy/private/service_contextsbean.like u:object_r:beanserver_service:s0
// device/generic/car/emulator/aosp_car_emulator.mkSYSTEM_EXT_PUBLIC_SEPOLICY_DIRS += vendor/zzh/sepolicy/publicSYSTEM_EXT_PRIVATE_SEPOLICY_DIRS += vendor/zzh/sepolicy/private
简单解释一下,我们需要为定义的服务定义一个标签,ServiceManager执行addService操作时会进行检查,如果不定义标签的话会使用默认的default_android_service,但Selinux是不允许以这个标签add service的。
// system/sepolicy/public/domain.te# Do not allow service_manager add for default service labels.# Instead domains should use a more specific type such as# system_app_service rather than the generic type.# New service_types are defined in {,hw,vnd}service.te and new mappings# from service name to service_type are defined in {,hw,vnd}service_contexts.neverallow * default_android_service:service_manager *;neverallow * default_android_vndservice:service_manager *;neverallow * default_android_hwservice:hwservice_manager *;
再次编译启动模拟器:
到此,我们的服务已经成功的添加到ServiceManager中。
- Client通过binder访问服务
我们写一个demo进行访问服务
测试文件:
// vendor/zzh/native-service/bean-server/client/BeanServer_client_test.cpp#define LOG_TAG "beanclient"//#define LOG_NDEBUG 0#include #include using namespace android;using com::zzh::IBeanService;int main(int argc __unused, char** argv __unused) { ALOGD(" beanclient start..."); // 先获取IServiceManager sp<IServiceManager> sm = defaultServiceManager(); sp<IBinder> binder; do { // 通过服务名称拿到代理对象 binder = sm->getService(String16("bean.like")); if (binder != 0) { break; } usleep(500000); // 0.5s } while (true);// 转换成IBeanService sp<IBeanService> beanService = interface_cast<IBeanService>(binder); // 通过代理调用服务端函数 beanService->sayHello(); return 0;}
Android.bp
// vendor/zzh/native-service/bean-server/client/Android.bpcc_binary { name: "beanserver_client", srcs: ["BeanServer_client_test.cpp"], cflags: [ "-Wall", "-Wextra", "-Werror", "-Wno-unused-parameter", ], compile_multilib: "first", shared_libs: [ "libbeanservice_aidl", "libbase", "libcutils", "libutils", "liblog", "libbinder", "libgui", ], include_dirs: ["."],}
编译到系统目录
// device/generic/car/emulator/aosp_car_emulator.mkPRODUCT_PACKAGES += beanserver_client
也可以不加上面的编译选项,直接make beanserver_client,然后将生成的产物adb push到系统目录也可以。
启动模拟器,在终端执行beanserver_client,查看日志:
可以看出是不允许shell访问beanserver服务,这里在实际业务中要对client端配置selinux,由于我们这里是测试,就不赘述了。直接setenforce 0进行测试
susetenforce 0
然后再重新执行beanservere_client
调用成功!
为了方便大家清晰的理解代码结果,下面列出了本例中的代码目录:
zzh@ubuntu:~/work/android/aosp/android-13.0.0_r35/vendor/zzh$ tree.├── native-service│ └── bean-server│ ├── Android.bp│ ├── beanserver.rc│ ├── client│ │ ├── Android.bp│ │ └── BeanServer_client_test.cpp│ ├── libbeanservice│ │ ├── aidl│ │ │ └── com│ │ │ └── zzh│ │ │ └── IBeanService.aidl│ │ ├── Android.bp│ │ ├── BeanService.cpp│ │ └── BeanService.h│ └── main_beanserver.cpp└── sepolicy ├── private │ └── service_contexts ├── public │ └── service.te └── vendor ├── beanserver.te └── file_contexts11 directories, 13 files
上述文件我已经整理好,大家想要的话可以关注我的微信公众号无限无羡,回复"nt" 即可获取,获取后只需修改部分名称,添加编译选项到自己项目的配置文件即可。
来源地址:https://blog.csdn.net/weixin_41678668/article/details/129828093