Create Android daemon (underlying service)

Create Android daemon (underlying service)

foreword

The Android underlying service, that is, the process running under linux, is the basis for the operation of the Android system, and completes the most basic functions of Android or a computer. Such as connection services (including WIFI, BT, etc.); such as Android's adb function; such as storage monitoring and so on. Without these underlying services, the upper layer will have no corresponding functions.

Android underlying services are often resident in memory and run all the time to complete tasks. The underlying service process often has more permissions, may communicate with the driver, may communicate with the linux kernel, may require operating system core running files and nodes, and so on. Therefore, the underlying services can help you complete more basic computer functions.

The AOSP used in this article is based on Android 8.1. Reading the text requires a basic understanding of Android's architecture, build system, AOSP project and SeAndroid.

Create a daemon

Create a directory to write code

Create a directory

We create our daemon process in the Android system general daemon process directory, of course, you can also place your daemon process in other directories.

/system/core/

In the above directory, create the folder nativeservice of the daemon process, then, our daemon process exists in the following directory, hereinafter referred to as the directory for short, represents the following directory .

/system/core/nativeservice/

Write code

Create the main code file native_main.cpp in the directory. In addition, we need to compile, then we need the mk file, create an Android.mk file. At this point, the directory structure looks like this

write picture description here

Write Android.mk

I tried my best to annotate the function of important statements in the code. If readers don't understand Android AOSP compilation, they can refer to more information on mk syntax.

# Copyright 2013 The Android Open Source Project
# 当前路径
LOCAL_PATH := $(call my-dir)
#清除历史变量
include $(CLEAR_VARS)

### nativeservice ###

#待编译的源码文件
LOCAL_SRC_FILES := \
    native_main.cpp \

common_c_includes := \
    bionic \
    system/core/include/sysutils \
#引用一些函数库
common_shared_libraries := \
    libsysutils \
    libcutils \
    liblog \
    libutils \
    libbinder \
    libbase

LOCAL_C_INCLUDES := \
    $(common_c_includes)

#守护进程的名字
LOCAL_MODULE := nativeservice
LOCAL_CFLAGS := -Wall -Wno-unused-parameter -Werror
LOCAL_SHARED_LIBRARIES := \
    $(common_shared_libraries)
LOCAL_MODULE_TAGS := optional

#编译守护进程,也就是可执行文件
#编译后,在/system/bin/ 下,变多了 nativeservice 可执行文件。
include $(BUILD_EXECUTABLE)
Write native_main.cpp

In Linux, a service that is started at boot will automatically exit after execution, and we are a daemon process, so we need to run it all the time. There are many ways to keep a program running. There are three ways posted in native_main.cpp, they are epoll, named pipe (FIFO) and loop.

The epoll method is a common method in the Android system. The system's battery status changes, USB interface status changes and other daemon processes are identified and read in real time through the epoll method.

Well-known pipe, relatively simple and convenient in IPC communication, suitable for light-weight tasks.

Loop, this is the most old-fashioned way.

The three methods are posted in native_main.cpp. This article focuses on the method of using the famous pipeline (FIFO). Since the space is too long, the other methods will be covered in one go. If readers are more interested in epoll, they can refer to more information to learn by themselves. .

The following is the code of native_main.cpp, please read the comments carefully.

//
// Created familyyuan user on 18-4-20.
//

#include <errno.h>
#include <string.h>
#include <unistd.h>

#include <cutils/log.h>

#include <fcntl.h>
#include <android-base/logging.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/epoll.h>
#include <cutils/uevent.h>

#include <sys/ioctl.h>

#define MAX_EPOLL_EVENTS 40
//epoll方式的 epoll fd
static int epollfd;
//FIFO 方式的 fd
static int fifo_fd;
//epoll方式的 uevent fd
static int uevent_fd;

#define BUFFER_SIZE PIPE_BUF

int main(int argc, char *argv[]) {
    SLOGD("native_service start");
    //
    // 1、epoll 的方式,
    // 监听一个 socket,如果 socket 被连接,便激活程序读取数据。
    // Android 驱动和用户态程序较多使用这种方式交互。
    //
/*
    int eventct = 5;
    struct epoll_event events[eventct];
    struct epoll_event ev;
    uevent_fd = uevent_open_socket(64*1024, true);

    //创建 epoll 通道,监听 socket fd
    epollfd = epoll_create(MAX_EPOLL_EVENTS);
    if (epollfd == -1) {
        SLOGD("native_service epoll_create failed");
    } else {
        SLOGD("native_service epoll_create success");
    }

    //
    fcntl(uevent_fd, F_SETFL, O_NONBLOCK);
    ev.events = EPOLLIN;
    ev.data.fd=uevent_fd;
    //注册 epoll fd
    if (epoll_ctl(epollfd, EPOLL_CTL_ADD, uevent_fd, &ev) == -1) {
            SLOGD("native_service epoll_ctl failed");
    } else {
        SLOGD("native_service epoll_ctl success");
    }

    while(1){
        SLOGD("native_service epoll running");
        int nevents = 0;
        // 监听 socket 端口
        nevents = epoll_wait(epollfd, events, eventct, 100000);
        if (nevents == -1 || nevents == 0) {
            SLOGD("native_service epoll_wait failed");
        } else {
            SLOGD("native_service epoll_wait success");
        }
        epoll_ctl(epollfd, EPOLL_CTL_DEL, uevent_fd, &ev);
    }
    close(uevent_fd);
*/

    //
    // 2、 FIFO 的方式,
    // 在/mnt/下创建一个名为 nativeservice 的管道,
    // 监听管道的数据变化,如果有数据写入管道,便读取数据。
    // 
    int res;
    int bytes = 0;
    char buffer[BUFFER_SIZE + 1];
    // 创建 FIFO
    res = mkfifo("/mnt/nativeservice", 0777);
    if (res != 0){
        SLOGD("native_service create fifo exist or failed");
    } else{
        SLOGD("native_service create fifo success");
    }
    // 以阻塞的方式打开 FIFO,知道管道有数据写入,激活程序,往下执行
    fifo_fd = TEMP_FAILURE_RETRY(open("/mnt/nativeservice",O_RDONLY));
    if (fifo_fd < 0) {
        SLOGD("native_service open failed");
    } else {
        SLOGD("native_service open success");
    }
    if (fifo_fd != -1){
        while(1){
            //读取管道数据,如果没有数据,阻塞等待数据被写入,激活
            res = read(fifo_fd, buffer, BUFFER_SIZE);
            bytes += res;
            SLOGD("native_service result=%s", buffer);
        }
    } else {
        SLOGD("native_service open failed");
    }
    //关闭管道资源。
    close(fifo_fd);

    //
    // 3、循环的方式
    // 这种方式代码最简单,但是耗资源,没有实时性。
    // 一个死循环,每隔 5 秒运行一次
    //
/*    while(1){
        SLOGD("native_service runnig");
        sleep(5);
        SLOGD("native_service wake");
    }
*/
    SLOGD("native_service die");
    return 0;
}

advance build system

After writing Android.mk and native_main.cpp, we can compile our daemon through the unilateral command "mmm system/core/nativeservice". But at this time, when we compile the whole AOSP with make, our nativeservice will not be compiled. Therefore, you need to tell the compilation system to compile the nativeservice at the same time when compiling the project. Modify as follows

write picture description here

Add nativeservice in the /build/make/target/product/core.mk file, of course, there is no restriction on adding it in this file. Many manufacturers' projects will also add their own PRODUCT_PACKAGES configuration mk files.

configure boot

So far, after compiling the entire project, the daemon process can also be compiled. At this time, can it be run after swiping the phone? No, we also need to make the daemon run when the phone is turned on, and if the running process dies, we also need to restart the daemon. Methods as below

Add the following code to the system/core/rootdir/init.rc file

service healthd /system/bin/healthd
    class core
    critical
    group root system wakelock
#我们的代码开始
service nativeservice /system/bin/nativeservice
    class main #main类,属于main的服务会开机被运行,且死掉会重启
    group system #属于 system 组
    #user system #以system用户启动,不设置以root用户启动
    seclabel u:r:nativeservice:s0 #SeAndroid SContext,domain是nativeservice
    restorecon nativeservice
#我们的代码结束
service console /system/bin/sh

Readers can check the system/core/init/README.md file in AOSP to understand the syntax and configuration method of init.rc. For the difference between different categories such as class core, readers can read the relevant elaboration of " Full Disk Encryption of Android Encryption ".

Configure SeAndroid

At this point, the entire project has been compiled, the daemon can also be compiled, and self-starting at boot is also configured. At this time, can the daemon process run after swiping to the phone? No, we know that Android continues to use the SeLinux security mechanism, and at the same time develops the SeAndroid mechanism. All files and processes need to be configured with SeAndroid to have permissions. Therefore, if no permissions are given to the daemon and the directories and files that the daemon needs to operate, they will be filtered or prohibited by SeAndroid.

Due to the difference between QCOM and Mediatek, the placement paths of related files will be different, but the methods are the same. For different platforms, you can find the files in the corresponding paths. This article takes the MTK platform as an example.

1. Add the following code to device/mediatek/sepolicy/basic/non_plat/file_contexts

/system/bin/nativeservice                  u:object_r:nativeservice_exec:s0

2. Add the nativeservice.te file in device/mediatek/sepolicy/basic/non_plat/, the content of the file is as follows

#守护进程 domain 为 nativeservice
type nativeservice, domain;
typeattribute nativeservice coredomain;

type nativeservice_exec, exec_type, file_type;

init_daemon_domain(nativeservice)

#allow nativeservice self:netlink_kobject_uevent_socket create_socket_perms_no_ioctl;
#allow nativeservice tmpfs:file { getattr open read write ioctl create };
#允许 nativeservice 在mnt目录读写管道文件
allow nativeservice tmpfs:fifo_file rw_file_perms;
#允许 nativeservice 在mnt目录创建管道文件
allow nativeservice tmpfs:fifo_file create_file_perms;
#允许 nativeservice 在mnt目录读写
allow nativeservice tmpfs:dir rw_dir_perms;
#允许 nativeservice 在mnt目录创建目录
allow nativeservice tmpfs:dir create_dir_perms;

Flash verification

At this point, the entire AOSP project needs to be compiled. Of course, if it has been compiled, only incremental compilation is required, and the compilation can be completed soon.

1. After flashing, you can see the daemon process in the /system/bin/nativeservie directory of the phone;

write picture description here

2. Take a look at SeAndroid's SContext

write picture description here

3. Take a look at the FIFO pipeline file

write picture description here

4. The p in front of prwx represents a pipeline file

write picture description here

5. The tcontext of the pipeline file SeAndroid

write picture description here

6. The daemon starts, opens the pipe after starting, and waits for the pipe data to be written. Since the daemon process starts earlier than the tool that captures the log, the previous log cannot be captured at boot time. The following log is the log printed by the daemon process that was manually killed.

write picture description here

7. Write data to the pipe through the terminal

write picture description here

8. The daemon process is activated and data is read

write picture description here

Summarize

The Android daemon can do many functions that the upper layers cannot. However, for security, SeAndroid should be used well, and security permissions should be configured with the principle of minimum capability. To create a daemon process, you need to write the corresponding code, configure the rc file, and configure SeAndroid.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324663251&siteId=291194637