文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Android 13添加自定义native服务

2023-08-19 14:47

关注

欢迎加入我的知识星球Android系统开发指南

欢迎关注微信公众号 无限无羡
欢迎关注知乎账号 无限无羡

native服务添加

native服务就是用c++写的系统服务,通过init进程启动,可以实现binder接口供client调用。
下面我们以实现一个beanserver的后台服务为例:

  1. 首先需要写一个rc文件
// 文件路径根据自己需求放置// vendor/zzh/native-service/bean-server/beanserver.rcservice beanserver /system/bin/beanserver    class main
  1. 写服务的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;}

这个服务我们启动后只是打印了一行日志就退出了,具体可以根据自己的需求加入自己开机处理的业务。

  1. 编写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);    }}
  1. 加到编译镜像环境
// device/generic/car/emulator/aosp_car_emulator.mk// 找到自己项目的mk文件加入PRODUCT_PACKAGES += beanserver

这样才会将beanserver编译到镜像中。

  1. 编译镜像验证
    编译完启动时看到有如下报错日志:
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权限配置

  1. 编写自定义服务的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)
  1. 编写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
  1. 编译后启动模拟器
    这个时候能看到服务启动了,但是发现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接口,这样才算是一个完整的系统服务。

  1. 定义aidl接口
// vendor/zzh/native-service/bean-server/libbeanservice/aidl/com/zzh/IBeanService.aidlpackage com.zzh;interface IBeanService {    void sayHello();}
  1. 加入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

  1. 实现服务,继承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",}
  1. 注册服务
// 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; }   ...}
  1. 编译后启动模拟器,看日志:

接下来根据提示配置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中。

  1. 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

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯