نگهبان ماشین

از نگهبان خودرو برای کمک به رفع اشکال VHAL استفاده کنید. ناظر خودرو بر سلامت فرآیندهای ناسالم نظارت می کند - و آنها را می کشد. برای اینکه فرآیندی توسط سازمان دیده بان خودرو نظارت شود، فرآیند باید در سازمان دیده بان خودرو ثبت شود. هنگامی که ناظر خودرو فرآیندهای ناسالم را از بین می‌برد، سازمان دیده‌بان خودرو وضعیت فرآیندها را مانند سایر برنامه‌های کاربردی بدون پاسخ (ANR) روی data/anr می‌نویسد. انجام این کار فرآیند اشکال زدایی را تسهیل می کند.

این مقاله توضیح می‌دهد که چگونه HAL‌ها و خدمات فروشنده می‌توانند فرآیندی را در ناظر خودرو ثبت کنند.

فروشنده HAL

به طور معمول، فروشنده HAL از یک thread pool برای hwbinder استفاده می کند. با این حال، مشتری نگهبان خودرو با دیمون نگهبان خودرو از طریق binder ارتباط برقرار می کند که با hwbinder متفاوت است. بنابراین، استخر نخ دیگری برای binder در حال استفاده است.

کمک نگهبان خودرو را در makefile مشخص کنید

  1. شامل carwatchdog_aidl_interface-ndk_platform در 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",
        ],
    }

یک سیاست SELinux اضافه کنید

  1. به system_server اجازه دهید HAL شما را بکشد. اگر system_server.te ندارید، یکی ایجاد کنید. اکیداً توصیه می شود که یک خط مشی SELinux را به هر دستگاه اضافه کنید.
  2. به فروشنده HAL اجازه دهید از binder ( binder_use macro) استفاده کند و فروشنده HAL را به دامنه مشتری carwatchdog ( carwatchdog_client_domain macro) اضافه کند. کد زیر را برای systemserver.te و vehicle_default.te ببینید:

    system_server.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)

با به ارث بردن BnCarWatchdogClient یک کلاس کلاینت را پیاده سازی کنید

  1. در checkIfAlive ، بررسی سلامت را انجام دهید. به عنوان مثال، به کنترل کننده حلقه موضوع ارسال کنید. اگر سالم هستید، با ICarWatchdog::tellClientAlive تماس بگیرید. کد زیر را برای WatchogClient.h و 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();
    }

رشته کلاسور را راه اندازی کنید و مشتری را ثبت کنید

  1. یک مخزن نخ برای ارتباط بایندر ایجاد کنید. اگر فروشنده HAL از hwbinder برای هدف خود استفاده می کند، باید یک Thread Pool دیگر برای ارتباط بایندر نگهبان خودرو ایجاد کنید.
  2. دیمون را با نام جستجو کنید و با ICarWatchdog::registerClient تماس بگیرید. نام واسط شبح نگهبان خودرو android.automotive.watchdog.ICarWatchdog/default است.
  3. بر اساس پاسخگویی سرویس، یکی از سه نوع مهلت زمانی زیر را که توسط ناظر خودرو پشتیبانی می شود انتخاب کنید و سپس در تماس با ICarWatchdog::registerClient ، مهلت زمانی را بگذرانید:
    • بحرانی (3s)
    • متوسط ​​(5 ثانیه)
    • عادی (10 ثانیه)
    کد زیر را برای VehicleService.cpp و 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;
    }

خدمات فروشنده (بومی)

فایل makefile car watchdog idl را مشخص کنید

  1. شامل carwatchdog_aidl_interface-ndk_platform در shared_libs .

    Android.bp

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

یک خط مشی SELinux اضافه کنید

  1. برای افزودن یک خط‌مشی SELinux، به دامنه سرویس فروشنده اجازه دهید از binder (ماکرو binder_use ) استفاده کند و دامنه سرویس فروشنده را به دامنه مشتری carwatchdog (macro carwatchdog_client_domain ) اضافه کنید. کد زیر را برای sample_client.te و 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)

    file_contexts

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

با به ارث بردن BnCarWatchdogClient یک کلاس کلاینت را پیاده سازی کنید

  1. در checkIfAlive ، یک بررسی سلامتی انجام دهید. یکی از گزینه ها ارسال به کنترل کننده حلقه موضوع است. اگر سالم هستید، با ICarWatchdog::tellClientAlive تماس بگیرید. کد زیر را برای SampleNativeClient.h و 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);
    }

یک thread بایندر راه اندازی کنید و مشتری را ثبت کنید

نام واسط شبح نگهبان خودرو android.automotive.watchdog.ICarWatchdog/default است.

  1. دیمون را با نام جستجو کنید و با ICarWatchdog::registerClient تماس بگیرید. کد زیر را برای main.cpp و 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);
    }

خدمات فروشنده (اندروید)

با به ارث بردن CarWatchdogClientCallback یک مشتری را پیاده سازی کنید

  1. فایل جدید را به صورت زیر ویرایش کنید:
    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() {}
    };

مشتری را ثبت کنید

  1. با 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);
    }

مشتری را لغو ثبت کنید

  1. پس از اتمام سرویس، با CarWatchdogManager.unregisterClient() تماس بگیرید:
    private void finishClient() {
        CarWatchdogManager manager =
            (CarWatchdogManager) car.getCarManager(
            Car.CAR_WATCHDOG_SERVICE);
        manager.unregisterClient(mClientCallback);
    }

فرآیندهای پایان یافته توسط ناظر خودرو را شناسایی کنید

نگهبان خودرو فرآیندهایی (HAL فروشنده، سرویس‌های بومی فروشنده، سرویس‌های Android فروشنده) را که در ناظر خودرو ثبت می‌شوند، زمانی که گیر می‌کنند و پاسخ نمی‌دهند، از بین می‌برد. چنین تخلیه ای با بررسی logcat ها تشخیص داده می شود. هنگامی که یک فرآیند مشکل ساز از بین می رود یا کشته می شود، سازمان دیده بان خودرو یک log carwatchdog killed process_name (pid:process_id) خروجی می دهد. بنابراین:

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

سیاهههای مربوطه گرفته شده است. به عنوان مثال، اگر برنامه KitchenSink (یک مشتری نگهبان خودرو) گیر کند، خطی مانند زیر در گزارش نوشته می شود:

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

برای تعیین اینکه چرا یا کجا برنامه KitchenSink گیر کرده است، از فرآیند تخلیه ذخیره شده در /data/anr استفاده کنید، همانطور که از موارد Activity ANR استفاده می کنید.

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

خروجی نمونه زیر مختص برنامه 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 -----

فایل dump را پیدا کنید (به عنوان مثال /data/anr/anr_2020-05-01-09-50-18-290 در مثال بالا) و تجزیه و تحلیل خود را شروع کنید.