Android 13添加自定义native服务

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

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

native服务添加

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

  1. 首先需要写一个rc文件
// 文件路径根据自己需求放置
// vendor/zzh/native-service/bean-server/beanserver.rc
service 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 <hidl/HidlTransportSupport.h>
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

# beanserver
type 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.mk
BOARD_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.cpp
static 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 <hidl/HidlTransportSupport.h>
using namespace android;


void quickSort(int arr[], int left, int right) {
    
    
    int i = left, j = right;
    int tmp;
    int pivot = arr[(left + right) / 2];

    /* partition */
    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--;
        }
    };

    /* recursion */
    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.aidl
package com.zzh;

interface IBeanService {
    
    
    void sayHello();
}
  1. 加入Android.bp中进行编译
// vendor/zzh/native-service/bean-server/libbeanservice/Android.bp
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",
}

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 <com/zzh/BnBeanService.h>
#include <binder/BinderService.h>

namespace android {
    
    

class BeanService:
    public BinderService<BeanService>,
    public virtual ::com::zzh::BnBeanService,
    public virtual IBinder::DeathRecipient
{
    
    
public:
    // Implementation of BinderService<T>
    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 <iostream>

namespace android {
    
    

using binder::Status;

BeanService::BeanService() {
    
    

}

BeanService::~BeanService() {
    
    

}

/*virtual*/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.bp
cc_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 <hidl/HidlTransportSupport.h>
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, /*willjoin*/ 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

# beanserver
type 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.te
type beanserver_service,       service_manager_type;
// vendor/zzh/sepolicy/private/service_contexts
bean.like                             u:object_r:beanserver_service:s0
// device/generic/car/emulator/aosp_car_emulator.mk
SYSTEM_EXT_PUBLIC_SEPOLICY_DIRS += vendor/zzh/sepolicy/public
SYSTEM_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 <com/zzh/IBeanService.h>
#include <binder/IServiceManager.h>

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.bp
cc_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.mk
PRODUCT_PACKAGES += beanserver_client

也可以不加上面的编译选项,直接make beanserver_client,然后将生成的产物adb push到系统目录也可以。

启动模拟器,在终端执行beanserver_client,查看日志:

可以看出是不允许shell访问beanserver服务,这里在实际业务中要对client端配置selinux,由于我们这里是测试,就不赘述了。直接setenforce 0进行测试

su
setenforce  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_contexts

11 directories, 13 files

上述文件我已经整理好,大家想要的话可以关注我的微信公众号无限无羡,回复"nt" 即可获取,获取后只需修改部分名称,添加编译选项到自己项目的配置文件即可。

猜你喜欢

转载自blog.csdn.net/weixin_41678668/article/details/129828093
今日推荐