Watchdog dell'auto

Usa il watchdog dell'auto per eseguire il debug del VHAL. Il watchdog di Car monitora l'integrità e termina i processi non integri. Per monitorare un processo dal watchdog dell'auto, il processo deve essere registrato con il watchdog dell'auto. Quando il watchdog dell'auto termina i processi non integro, il watchdog dell'auto scrive lo stato i processi per data/anr come per le altre richieste di non risposta (ANR). In questo modo viene gestito il processo di debug.

Questo articolo descrive come i servizi e gli HAL del fornitore possono registrare un processo con il watchdog dell'auto.

HAL fornitore

In genere, l'HAL del fornitore utilizza un pool di thread per hwbinder. Tuttavia, il client watchdog dell'auto comunica con il daemon watchdog dell'auto tramite binder, che è diverso da hwbinder. Pertanto, è in uso un altro pool di thread per binder.

Specifica l'aidl del watchdog dell'auto nel file makefile

  1. Includi carwatchdog_aidl_interface-ndk_platform in shared_libs:

    Android.bp:

    cc_defaults {
        name: "vhal_v2_0_defaults",
        shared_libs: [
            "libbinder_ndk",
            "libhidlbase",
            "liblog",
            "libutils",
            "android.hardware.automotive.vehicle@2.0",
            "carwatchdog_aidl_interface-ndk_platform",
        ],
        cflags: [
            "-Wall",
            "-Wextra",
            "-Werror",
        ],
    }

Aggiungi un criterio SELinux

  1. Consenti a system_server di terminare il tuo HAL. Se non hai system_server.te, creane uno. È fortemente ti consigliamo di aggiungere un criterio SELinux a ciascun dispositivo.
  2. Consenti all'HAL del fornitore di utilizzare binder (macro binder_use) e aggiungi l'HAL del fornitore al dominio client carwatchdog (macro carwatchdog_client_domain). Visualizza il codice seguente per systemserver.te e vehicle_default.te:

    server_di_sistema.te

    # Allow system_server to kill vehicle HAL
    allow system_server hal_vehicle_server:process sigkill;

    hal_vehicle_default.te

    # Configuration for register VHAL to car watchdog
    carwatchdog_client_domain(hal_vehicle_default)
    binder_use(hal_vehicle_default)

Implementa una classe client ereditando BnCarWatchdogClient

  1. In checkIfAlive, esegui il controllo di integrità. Ad esempio, pubblicare nel gestore del loop del thread. Se è integro, chiama ICarWatchdog::tellClientAlive. Consulta il codice seguente per WatchogClient.h e WatchogClient.cpp:

    WatchogClient.h

    class WatchdogClient : public aidl::android::automotive::watchdog::BnCarWatchdogClient {
      public:
        explicit WatchdogClient(const ::android::sp<::android::Looper>& handlerLooper, VehicleHalManager* vhalManager);
    
    ndk::ScopedAStatus checkIfAlive(int32_t sessionId, aidl::android::automotive::watchdog::TimeoutLength timeout) override; ndk::ScopedAStatus prepareProcessTermination() override; };

    WatchogClient.cpp

    ndk::ScopedAStatus WatchdogClient::checkIfAlive(int32_t sessionId, TimeoutLength /*timeout*/) {
        // Implement or call your health check logic here
        return ndk::ScopedAStatus::ok();
    }

Avvia il thread del binder e registra il client

  1. Crea un pool di thread per la comunicazione del binder. Se il fornitore HAL utilizza hwbinder per i suoi del tuo scopo, devi creare un altro pool di thread per le comunicazioni con il binder del watchdog dell'auto.
  2. Cerca il daemon con il nome e chiama ICarWatchdog::registerClient. Il nome dell'interfaccia del daemon watchdog dell'auto è android.automotive.watchdog.ICarWatchdog/default.
  3. In base alla reattività del servizio, seleziona uno dei tre seguenti tipi di timeout supportato dal watchdog dell'auto, per poi passare il timeout nella chiamata ICarWatchdog::registerClient:
    • critical(3s)
    • moderate(5s)
    • normal(10s)
    Visualizza il codice seguente per VehicleService.cpp e WatchogClient.cpp:

    VehicleService.cpp

    int main(int /* argc */, char* /* argv */ []) {
        // Set up thread pool for hwbinder
        configureRpcThreadpool(4, false /* callerWillJoin */);
    
        ALOGI("Registering as service...");
        status_t status = service->registerAsService();
    
        if (status != OK) {
            ALOGE("Unable to register vehicle service (%d)", status);
            return 1;
        }
    
        // Setup a binder thread pool to be a car watchdog client.
        ABinderProcess_setThreadPoolMaxThreadCount(1);
        ABinderProcess_startThreadPool();
        sp<Looper> looper(Looper::prepare(0 /* opts */));
        std::shared_ptr<WatchdogClient> watchdogClient =
                ndk::SharedRefBase::make<WatchdogClient>(looper, service.get());
        // The current health check is done in the main thread, so it falls short of capturing the real
        // situation. Checking through HAL binder thread should be considered.
        if (!watchdogClient->initialize()) {
            ALOGE("Failed to initialize car watchdog client");
            return 1;
        }
        ALOGI("Ready");
        while (true) {
            looper->pollAll(-1 /* timeoutMillis */);
        }
    
        return 1;
    }

    WatchogClient.cpp

    bool WatchdogClient::initialize() {
        ndk::SpAIBinder binder(AServiceManager_getService("android.automotive.watchdog.ICarWatchdog/default"));
        if (binder.get() == nullptr) {
            ALOGE("Failed to get carwatchdog daemon");
            return false;
        }
        std::shared_ptr<ICarWatchdog> server = ICarWatchdog::fromBinder(binder);
        if (server == nullptr) {
            ALOGE("Failed to connect to carwatchdog daemon");
            return false;
        }
        mWatchdogServer = server;
    
        binder = this->asBinder();
        if (binder.get() == nullptr) {
            ALOGE("Failed to get car watchdog client binder object");
            return false;
        }
        std::shared_ptr<ICarWatchdogClient> client = ICarWatchdogClient::fromBinder(binder);
        if (client == nullptr) {
            ALOGE("Failed to get ICarWatchdogClient from binder");
            return false;
        }
        mTestClient = client;
        mWatchdogServer->registerClient(client, TimeoutLength::TIMEOUT_NORMAL);
        ALOGI("Successfully registered the client to car watchdog server");
        return true;
    }

Servizi del fornitore (nativo)

Specifica il file make aidl del watchdog dell'auto

  1. Includi carwatchdog_aidl_interface-ndk_platform in shared_libs.

    Android.bp

    cc_binary {
        name: "sample_native_client",
        srcs: [
            "src/*.cpp"
        ],
        shared_libs: [
            "carwatchdog_aidl_interface-ndk_platform",
            "libbinder_ndk",
        ],
        vendor: true,
    }

Aggiungere un criterio SELinux

  1. Per aggiungere un criterio SELinux, consenti al dominio di servizio del fornitore di utilizzare il binder (macro binder_use) e aggiungi il dominio di servizio del fornitore al dominio client carwatchdog (macro carwatchdog_client_domain). Consulta il codice seguente per sample_client.te e file_contexts:

    sample_client.te

    type sample_client, domain;
    type sample_client_exec, exec_type, file_type, vendor_file_type;
    
    carwatchdog_client_domain(sample_client)
    
    init_daemon_domain(sample_client)
    binder_use(sample_client)

    contesti_file

    /vendor/bin/sample_native_client  u:object_r:sample_client_exec:s0

Implementa una classe client ereditando BnCarWatchdogClient

  1. Esegui un controllo di integrità in checkIfAlive. Un'opzione è pubblicare su il gestore di loop dei thread. Se è integro, chiama ICarWatchdog::tellClientAlive. Consulta il codice seguente per SampleNativeClient.h e SampleNativeClient.cpp:

    SampleNativeClient.h

    class SampleNativeClient : public BnCarWatchdogClient {
    public:
        ndk::ScopedAStatus checkIfAlive(int32_t sessionId, TimeoutLength
            timeout) override;
        ndk::ScopedAStatus prepareProcessTermination() override;
        void initialize();
    
    private:
        void respondToDaemon();
    private:
        ::android::sp<::android::Looper> mHandlerLooper;
        std::shared_ptr<ICarWatchdog> mWatchdogServer;
        std::shared_ptr<ICarWatchdogClient> mClient;
        int32_t mSessionId;
    };

    SampleNativeClient.cpp

    ndk::ScopedAStatus WatchdogClient::checkIfAlive(int32_t sessionId, TimeoutLength timeout) {
        mHandlerLooper->removeMessages(mMessageHandler,
            WHAT_CHECK_ALIVE);
        mSessionId = sessionId;
        mHandlerLooper->sendMessage(mMessageHandler,
            Message(WHAT_CHECK_ALIVE));
        return ndk::ScopedAStatus::ok();
    }
    // WHAT_CHECK_ALIVE triggers respondToDaemon from thread handler
    void WatchdogClient::respondToDaemon() {
      // your health checking method here
      ndk::ScopedAStatus status = mWatchdogServer->tellClientAlive(mClient,
            mSessionId);
    }

Avvia un thread del binder e registra il client

Il nome dell'interfaccia del daemon watchdog dell'auto è android.automotive.watchdog.ICarWatchdog/default.

  1. Cerca il daemon con il nome e chiama ICarWatchdog::registerClient. Consulta il codice seguente per main.cpp e SampleNativeClient.cpp:

    main.cpp

    int main(int argc, char** argv) {
        sp<Looper> looper(Looper::prepare(/*opts=*/0));
    
        ABinderProcess_setThreadPoolMaxThreadCount(1);
        ABinderProcess_startThreadPool();
        std::shared_ptr<SampleNativeClient> client =
            ndk::SharedRefBase::make<SampleNatvieClient>(looper);
    
        // The client is registered in initialize()
        client->initialize();
        ...
    }

    SampleNativeClient.cpp

    void SampleNativeClient::initialize() {
        ndk::SpAIBinder binder(AServiceManager_getService(
            "android.automotive.watchdog.ICarWatchdog/default"));
        std::shared_ptr<ICarWatchdog> server =
            ICarWatchdog::fromBinder(binder);
        mWatchdogServer = server;
        ndk::SpAIBinder binder = this->asBinder();
        std::shared_ptr<ICarWatchdogClient> client =
            ICarWatchdogClient::fromBinder(binder)
        mClient = client;
        server->registerClient(client, TimeoutLength::TIMEOUT_NORMAL);
    }

Servizi del fornitore (Android)

Implementa un client ereditando CarWatchdogClientCallback

  1. Modifica il nuovo file come segue:
    private final CarWatchdogClientCallback mClientCallback = new CarWatchdogClientCallback() {
        @Override
        public boolean onCheckHealthStatus(int sessionId, int timeout) {
            // Your health check logic here
            // Returning true implies the client is healthy
            // If false is returned, the client should call
            // CarWatchdogManager.tellClientAlive after health check is
            // completed
        }
    
        @Override
        public void onPrepareProcessTermination() {}
    };

Registra il client

  1. Chiama CarWatchdogManager.registerClient():
    private void startClient() {
        CarWatchdogManager manager =
            (CarWatchdogManager) car.getCarManager(
            Car.CAR_WATCHDOG_SERVICE);
        // Choose a proper executor according to your health check method
        ExecutorService executor = Executors.newFixedThreadPool(1);
        manager.registerClient(executor, mClientCallback,
            CarWatchdogManager.TIMEOUT_NORMAL);
    }

Annulla la registrazione del client

  1. Chiama CarWatchdogManager.unregisterClient() al termine del servizio:
    private void finishClient() {
        CarWatchdogManager manager =
            (CarWatchdogManager) car.getCarManager(
            Car.CAR_WATCHDOG_SERVICE);
        manager.unregisterClient(mClientCallback);
    }

Rileva i processi terminati dal watchdog dell'auto

Il watchdog dell'auto esegue il dump/l'interruzione dei processi (HAL del fornitore, servizi nativi del fornitore, servizi Android del fornitore) registrati al watchdog dell'auto quando sono bloccati e non rispondono. Questo dumping viene rilevato controllando i logcat. Il watchdog della vettura genera un log carwatchdog killed process_name (pid:process_id) quando un processo problematico viene dumpato o terminato. Quindi:

$ adb logcat -s CarServiceHelper | fgrep "carwatchdog killed"

Vengono acquisiti i log pertinenti. Ad esempio, se l'app KitchenSink (un watchdog di auto client) rimane bloccato, una riga come quella riportata di seguito viene scritta nel log:

05-01 09:50:19.683   578  5777 W CarServiceHelper: carwatchdog killed com.google.android.car.kitchensink (pid: 5574)

Per determinare perché o dove l'app KitchenSink si è bloccata, utilizza il dump del processo archiviati all'indirizzo /data/anr proprio come faresti per le richieste di ANR delle attività.

$ adb root
$ adb shell grep -Hn "pid process_pid" /data/anr/*

Il seguente output di esempio è specifico per l'app KitchenSink:

$ adb shell su root grep -Hn "pid 5574" /data/anr/*.
/data/anr/anr_2020-05-01-09-50-18-290:3:----- pid 5574 at 2020-05-01 09:50:18 -----
/data/anr/anr_2020-05-01-09-50-18-290:285:----- Waiting Channels: pid 5574 at 2020-05-01 09:50:18 -----

Individua il file di dump (ad esempio, /data/anr/anr_2020-05-01-09-50-18-290) nell'esempio precedente) e iniziare l'analisi.