三、sbin/recovery/服务流程-recovery.cpp main方法

Android recovery分区表 写在前面 这篇文章以imx6q的android5.1为例,介绍recovery分区表的配置和加载 分区表加载 //bootable/recovery/roots.cpp void load_volume_table() { int i; int ret; fstab = fs_mgr_read_fstab("/etc/recovery.fstab"); 阅读详情

三、sbin/recovery/服务流程-recovery.cpp main方法

    本次将分析recovery.cpp main方法,其中会介绍一部分C++ 基础,话不多说,直接开始,我们都知道从bootloder引导分区后会判断misc的参数,如果是boot-recovery ,会驱动进入recovery模式,这部分后面会进行补充,暂时对主要流程进行二次分析

1、加载分区表

2、get_args

3、加载selinux

4、关于路径转换解析

5、执行传入参数命令

6、finish_recovery

 

那么首先其实跟system的启动相同,会执行内核的init.rc

etc/init.rc:

service recovery /sbin/recovery

   seclabel u:r:recovery:s0

直接去启动recovery服务,这个文件是编译在recovey/sbin下的可执行文件,我们开启main方法之旅

//argc和argv参数在用命令行编译程序时有用。main( int argc, char* argv[], char **env ) 中 
//第一个参数,int型的argc,为整型,用来统计程序运行时发送给main函数的命令行参数的个数
//第二个参数,char*型的argv[],为字符串数组,用来存放指向的字符串参数的指针数组
//首先我们看到mian,需要知道这两个参数传进来的是什么
int main(int argc, char **argv) {
  //所以一开始在这里加了打印出参数的内容,但是试了几次都没有看到last_log的输出,没有参数时不可能的,
  //所以应该这里应该是我们的print提前了,这里还没有定义log和log定向输出
  //int i;
  //for (i = 0; i < argc; i++){
  //printf("argc && argv we need know how much the argc argv\n");
  //printf("argv %d is %s\n", i, argv[i]);
  //}
  //从这里开始,定义log部分,从注释中也能够看出来
  // We don't have logcat yet under recovery; so we'll print error on screen and
  // log to stdout (which is redirected to recovery.log) as we used to do.
  android::base::InitLogging(argv, &UiLogger);

  // Take last pmsg contents and rewrite it to the current pmsg session.
  static const char filter[] = "recovery/";
  // Do we need to rotate?
  bool doRotate = false;

  __android_log_pmsg_file_read(LOG_ID_SYSTEM, ANDROID_LOG_INFO, filter, logbasename, &doRotate);
  // Take action to refresh pmsg contents
  __android_log_pmsg_file_read(LOG_ID_SYSTEM, ANDROID_LOG_INFO, filter, logrotate, &doRotate);

  //启动一个adbd,为了使用adb sideload命令,也就是如果我们用这个命令启动recovery sdcard升级,
  //那么其实是启动了sbin/adbd服务,如果我们直接在线升级的话 ,是用不到adbd服务的,所以参数里没有adbd
  if (argc == 2 && strcmp(argv[1], "--adbd") == 0) {
    minadbd_main();
    return 0;
  }
  // redirect_stdio should be called only in non-sideload mode. Otherwise
  // we may have two logger instances with different timestamps.
  // log的重定向 
  redirect_stdio(TEMPORARY_LOG_FILE);
  int i;
  //打印结果 argv 0 is /sbin/recovery, 目前暂时猜想传进来的只有recovery服务/sbin/recovery
  for (i = 0; i < argc; i++){
    printf("argc && argv we need know how much the argc argv\n");
    printf("argv %d is %s\n", i, argv[i]);
  }
  //打印出当前升级的时间
  printf("Starting recovery (pid %d) on %s", getpid(), ctime(&start));
  //加载分区表 TODO 暂时知道输出了什么,具体的流程比较复杂,需要慢慢分析
  load_volume_table();
  //判断是不是可以加载cache 和 nvdata
  has_cache = volume_for_mount_point(CACHE_ROOT) != nullptr;
  has_nvdata = volume_for_mount_point(NVDATA_ROOT) != nullptr;
  //初始化分区类型,不过这个方法在之前load_volume_table()方法中已经执行过了,所以这里只是再次确认
  //如果已经初始化,则跳过
  mt_init_partition_type();

 

1、加载分区表 load_volume_table();

补充说明:fstab类型  system/core/fs_mgr/include_fstab/fstab/fstab.h

//定义了一个链表,包含了三个元素,包含个数,每条信息所包含的内容recs,fstab表的名称
struct fstab {
    int num_entries;
    struct fstab_rec* recs;
    char* fstab_filename;
};

struct fstab_rec {
    char* blk_device;
    char* mount_point;
    char* fs_type;
    unsigned long flags;
    char* fs_options;
    int fs_mgr_flags;
    char* key_loc;
    char* key_dir;
    char* verity_loc;
    long long length;
    char* label;
    int partnum;
    int swap_prio;
    int max_comp_streams;
    unsigned int zram_size;
    uint64_t reserved_size;
    unsigned int file_contents_mode;
    unsigned int file_names_mode;
    unsigned int erase_blk_size;
    unsigned int logical_blk_size;
    char* sysfs_path;
};

 

bootable/recovery/roots/cpp load_volume_table方法

//注意下这个时全局静态的fstab,因为别的方法也会调用到
static struct fstab* fstab = nullptr;
//在变量定义前加extern关键字表示声明一个变量但不定义它 。它表示"这只是一个声明,它的定义在别的地方",关键字extern,它表示变量是在文件以外定义的,或在文件后面部分才定义.
extern struct selabel_handle* sehandle;

void load_volume_table() {
  //根据基础的etc/recovery.fstab生成fstab
  fstab = fs_mgr_read_fstab_default();
  if (!fstab) {
    LOG(ERROR) << "Failed to read default fstab";
    return;
  }
  //追加条目/tmp ramdisk ramdisk 0
  int ret = fs_mgr_add_entry(fstab, "/tmp", "ramdisk", "ramdisk");
  if (ret == -1) {
    LOG(ERROR) << "Failed to add /tmp entry to fstab";
    fs_mgr_free_fstab(fstab);
    fstab = nullptr;
    return;
  }
  //追加条目sdcard 
  ///sdcard vfat /dev/block/mmcblk1p1 0 
  ///sdcard_dev2 vfat /dev/block/platform/bootdevice/by-name/intsd 0
  ret = mt_load_volume_table(fstab);
  if (ret < 0 ) {
    LOG(ERROR) << "mt_load_volume_table fail to add entry to fstab";
    fs_mgr_free_fstab(fstab);
    fstab = NULL;
    return;
  }

  mt_ensure_dev_ready("/misc");
  mt_ensure_dev_ready("/cache");
  mt_fstab_translation_NAND(fstab);
  printf("recovery filesystem table\n");
  printf("=========================\n");
  //遍历出fstab并打印,看log结果中 length为0 为什么都为0?
  //这里会遍历fstab,打印出 编号 挂载点 文件系统类型 块设备 长度
  for (int i = 0; i < fstab->num_entries; ++i) {
    const Volume* v = &fstab->recs[i];
    printf("  %d %s %s %s %lld\n", i, v->mount_point, v->fs_type, v->blk_device, v->length);
  }
  printf("\n");
}

 

system/core/fs_mgr/fs_mgr_fstab.cpp  fs_mgr_read_fstab_default()方法

/*
 * loads the fstab file and combines with fstab entries passed in from device tree.
 */
struct fstab *fs_mgr_read_fstab_default()
{
    std::string default_fstab;

    // Use different fstab paths for normal boot and recovery boot, respectively
    //access()会检查是否可以读/写某一已存在的文件 F_OK则是用来判断该文件是否存在,返回值 若所有欲查核的权限都通过了检查则返回0值,表示成功,只要有一权限被禁止则返回-1
    //如果有recovery这个可执行文件,说明启动到了recovery,使用etc/recovery.fstab
    if (access("/sbin/recovery", F_OK) == 0) {
        default_fstab = "/etc/recovery.fstab";
    } else {  // normal boot
        default_fstab = get_fstab_path();
    }
    //struct fstab {
    //int num_entries;
    //struct fstab_rec* recs;
    //char* fstab_filename;
    //};
    struct fstab* fstab = nullptr;
    if (!default_fstab.empty()) {
        //读取/etc/recovery.fstab,返回fstab结构体
        fstab = fs_mgr_read_fstab(default_fstab.c_str());
    } else {
        LINFO << __FUNCTION__ << "(): failed to find device default fstab";
    }
    //读取/proc/device-tree/firmware/android/fstb文件 返回fstab结构体
    struct fstab* fstab_dt = fs_mgr_read_fstab_dt();

    // combines fstab entries passed in from device tree with
    // the ones found from default_fstab file
    //合并fsta 和 fstab_dt
    return in_place_merge(fstab_dt, fstab);
}

继续分析后续流程

2、get_args(argc, argv)

recovery.cpp
int main(int argc, char **argv) {
  //std::是个名称空间标示符,C++标准库中的函数或者对象都是在命名空间std中定义的,所以我们要使用标准函数库中的函数或对象都要使用std来限定。
  //对象count是标准函数库所提供的对象,而标准库在名字空间中被指定为std,所以在使用cout的时候要加上std::。这样编译器就会明白我们调用的cout是名字空间std中的cout。
  //get_args获取misc分区中的command信息
  //C++ vector类为内置数组提供了一种替代表示
  std::vector<std::string> args = get_args(argc, argv);
  std::vector<char*> args_to_parse(args.size());
  //不知道干了啥就加打印把,trnasform有两种用法,一种时修改大小写,一种时数据转移
  //在这里我个人理解是数据转移的意思,因为获得的args容器是string类型的,后续的方法中我们需要的时char类型
  std::transform(args.cbegin(), args.cend(), args_to_parse.begin(),
                 [](const std::string& arg) { return const_cast<char*>(arg.c_str()); });
  printf("=======================\n");
  printf("std::transform\n");
  for (const auto& arg : args_to_parse) {
    printf(" \"%s\"", arg);  //"/sbin/recovery" "--update_package=@/cache/recovery/block.map" "--locale=en-US"
  }

补充说明 bootable/recovery/bootloader_message/include/bootloader_message/bootloader_message.h

 

//bootloader_message是一个链表,包含了command,status,recovery,stage,reserved元素,为char类型数组
struct bootloader_message {
    char command[32];
    char status[32];
    char recovery[768];

    // The 'recovery' field used to be 1024 bytes.  It has only ever
    // been used to store the recovery command line, so 768 bytes
    // should be plenty.  We carve off the last 256 bytes to store the
    // stage string (for multistage packages) and possible future
    // expansion.
    char stage[32];

    // The 'reserved' field used to be 224 bytes when it was initially
    // carved off from the 1024-byte recovery field. Bump it up to
    // 1184-byte so that the entire bootloader_message struct rounds up
    // to 2048-byte.
    char reserved[1184];
};

回到recovery.cpp中的get_args具体方法 从misc或者cache/recovery/command中读取出具体的升级指令

 

static std::vector<std::string> get_args(const int argc, char** const argv) {
  CHECK_GT(argc, 0);
  //声明bootloader_message结构体
  bootloader_message boot = {};
  std::string err;
  //读取read_bootloader_message中的信息,如果没有读到,输出error的值,设置bootloder_message为空
  if (!read_bootloader_message(&boot, &err)) {
    LOG(ERROR) << err;
    // If fails, leave a zeroed bootloader_message.
    boot = {};
  }
  stage = std::string(boot.stage);
  //打印出获取的bootloader_message结构体中的参数内容
  printf("\nget_args==================\n");
  //Boot command: boot-recovery
  std::string boot_command = std::string(boot.command, sizeof(boot.command));
  LOG(INFO) << "Boot command: " << boot_command;
  //Boot status:
  std::string boot_status = std::string(boot.status, sizeof(boot.status));
  LOG(INFO) << "Boot status: " << boot_status;
  //boot_stage: 
  std::string boot_stage = std::string(boot.stage, sizeof(boot.stage));
  LOG(INFO) << "boot_stage: " << boot_status;
  //boot_recovery: recovery --update_package=@/cache/recovery/block.map --locale=en-US
  std::string boot_recovery = std::string(boot.recovery, sizeof(boot.recovery));
  LOG(INFO) << "boot_recovery: " << boot_recovery;
  //boot_reserved: 
  std::string boot_reserved = std::string(boot.reserved, sizeof(boot.reserved));
  LOG(INFO) << "boot_reserved: " << boot_reserved;
  printf("get_args==================\n");
  if (boot.command[0] != 0) {
    std::string boot_command = std::string(boot.command, sizeof(boot.command));
    LOG(INFO) << "Boot command: " << boot_command;
  }

  if (boot.status[0] != 0) {
    std::string boot_status = std::string(boot.status, sizeof(boot.status));
    LOG(INFO) << "Boot status: " << boot_status;
  }
  //输出时什么?size为1  这个容器中其实存储的还是字符串格式的"/sbin/recovery"
  std::vector<std::string> args(argv, argv + argc);
  printf("vector getargs args the farst data\n");
  for (const auto& arg : args) {
    printf(" \"%s\"", arg.c_str()); //"/sbin/recovery"
  }
  printf("\n");
  // --- if arguments weren't supplied, look in the bootloader control block
  //如果args的size为1,结合我们的打印,其实就是1
  if (args.size() == 1) {
    //保证数字最后的位置上为null ,"\0"是c++上的null的边上方式
    boot.recovery[sizeof(boot.recovery) - 1] = '\0';  // Ensure termination
    //转换char为string字符串
    std::string boot_recovery(boot.recovery);
    //声明一个新的容器,把boot_recovery中的信息按空格进行分割,分割后为三个字符串,
    //recovery --update_package=@/cache/recovery/block.map --locale=en-US
    std::vector<std::string> tokens = android::base::Split(boot_recovery, "\n");
    if (!tokens.empty() && tokens[0] == "recovery") {
      //auto可以在声明变量的时候根据变量初始值的类型自动为此变量选择匹配的类型
      //for (auto i = vs.begin(); i != vs.end(); i++)代替for (std::vector<std::string>::iterator i = vs.begin(); i != vs.end(); i++)
      for (auto it = tokens.begin() + 1; it != tokens.end(); it++) {
        // Skip empty and '\0'-filled tokens.
        //std::move是将对象的状态或者所有权从一个对象转移到另一个对象,只是转移,没有内存的搬迁或者内存拷贝。在这里其实就是迭代出的内容放到args容器中
        if (!it->empty() && (*it)[0] != '\0') args.push_back(std::move(*it));
      }
      //根据我们的打印,last_log中的Got 3 arguments from boot message是从misc分区读取到的
      printf("==============if (args.size() == 1) {==================\n");
      LOG(INFO) << "Got " << args.size() << " arguments from boot message";
    } else if (boot.recovery[0] != 0) {
      LOG(ERROR) << "Bad boot message: \"" << boot_recovery << "\"";
    }
  }
  
  //跟上面的方法相同其实,只不过取数据的位置不同,如果从misc没取到内容,那么从command中取出
  // --- if that doesn't work, try the command file (if we have /cache).
  if (args.size() == 1 && has_cache) {
      ......
      ......
      LOG(INFO) << "Got " << args.size() << " arguments from " << COMMAND_FILE;
    }
  } 
  //将得到的参数回写到misc分区中,这里就是为什么如果没有升级完,还是进入recovery的原因,也是断电后继续升级的关键步骤
  //TODO 这个问题暂时不看,我们已经知道了后续取出来的参数是什么,后续再继续研究分线流程
  std::vector<std::string> options(args.cbegin() + 1, args.cend());
  if (!update_bootloader_message(options, &err)) {
    LOG(ERROR) << "Failed to set BCB message: " << err;
  }
  return args;
}

以下main方法中的代码暂时做简单说明,主要为处理UI部分

recovery.cpp
int main(int argc, char **argv) {
  .....
  .....
  int arg;
  int option_index;
  //getopt_long获取参数,固定用法,将--update_package=@/cache/recovery/block.map 
  //得到参数-u --locale=en-US 得到参数l
  while ((arg = getopt_long(args_to_parse.size(), args_to_parse.data(), "", OPTIONS,
                            &option_index)) != -1) {
    switch (arg) {
      case 'n':
        android::base::ParseInt(optarg, &retry_count, 0);
        break;
      case 'u':
        //传入升级包路径的参数
        update_package = optarg; //@/cache/recovery/block.map 
        perform_fota = 1; 
        break;
      .....
      .....
      case 'l':
        //得到locale 
        locale = optarg; //en-US
        break;
      case '?':
        LOG(ERROR) << "Invalid command argument";
        continue;
    }
  }
  //如果locale为空,则判断有没有cache分区,如果在cache中没有读到,则使用默认值
  if (locale.empty()) {
    if (has_cache) {
      locale = load_locale_from_cache();
    }

    if (locale.empty()) {
      locale = DEFAULT_LOCALE;
    }
  }
  //初始化UI界面
  Device* device = make_device();
  if (android::base::GetBoolProperty("ro.boot.quiescent", false)) {
    printf("Quiescent recovery mode.\n");
    ui = new StubRecoveryUI();
  } else {
    ui = device->GetUI();

    if (!ui->Init(locale)) {
      printf("Failed to initialize UI, use stub UI instead.\n");
      ui = new StubRecoveryUI();
    }
  }

  // Set background string to "installing security update" for security update,
  // otherwise set it to "installing system update".
  //界面上显示的内容installing security update 或者 installing system update
  ui->SetSystemUpdateText(security_update);

  int st_cur, st_max;
  if (!stage.empty() && sscanf(stage.c_str(), "%d/%d", &st_cur, &st_max) == 2) {
    ui->SetStage(st_cur, st_max);
  }
  //显示recovery背景
  ui->SetBackground(RecoveryUI::NONE);
  if (show_text) ui->ShowText(true);

 

  //加载selinux TODO 这部分没有查到太多的资料,后续搞清楚了再往上添加
  sehandle = selinux_android_file_context_handle();
  selinux_android_set_sehandle(sehandle);
  if (!sehandle) {
    ui->Print("Warning: No file_contexts\n");
  }
  //打印出了command信息,这个我们根据之前的代码已经知道了里面的内容
  //Command: "/sbin/recovery"
  //"--update_package=/storage/032B-13F5/Android/data/com.adups.fota/files/adupsfota/update.zip" 
  //"--locale=en-US"
  printf("Command:");
  for (const auto& arg : args) {
    printf(" \"%s\"", arg.c_str());
  }
  printf("\n\n");
    
    // 路径转换
    //下面的代码中拼接和拷贝我都加了注释,大致意思,分配内存空间,拷贝字符串放入sdcard/
    //拼接字符串将sdcard/.../update.zip拼接
    //modified_path这个指针处理好后,会将内存空间给到update_package这个指针做后续处理
    #ifdef ADUPS_FOTA_SUPPORT
    if (perform_fota == 1) {
    printf("xxxxxxxxxxxxxxxxx sleep 6 seconds\n");
    sleep(6);
    find_update_package_new(update_package);
        //strncmp是一个比较字符串的方法,表示update_package 的前9位是否相等,如果相等,返回值为0
        if (strncmp(update_package, "/storage/", 9) == 0) {
            //strlen(char*)函数求的是字符串的实际长度,它求得方法是从开始到遇到第一个'\0',如果你只定义没有给它赋初值,这个结果是不定的,它会从aa首地址一直找下去,直到遇到'\0'停止
            int len = strlen(update_package) + 12;
            //分配内存供操作路径使用  在C语言中“char*”是声明一个字符类型的指针
            char* modified_path = (char*)malloc(len);
            printf("\n====char* modified_path====\n");
            printf(" \"%s\"", modified_path);// ""
            //这里面用到了两个方法,一个是strlcpy 拷贝字符串 一个是strlcat 拼接字符串,具体的没有查到太多信息,只知道是更新版解决了内存溢出,还是继续打log把
            strlcpy(modified_path, SDCARD_ROOT, len);
            printf("\n====strlcpy(modified_path, SDCARD_ROOT, len)====\n");
            printf(" \"%s\"", modified_path);//"/sdcard"
            //strstr(str1,str2) 函数用于判断字符串str2是否是str1的子串。如果是,则该函数返回str2在str1中首次出现的地址
            if(strstr(update_package, "/Android/data/com.adups.fota/files/adupsfota/update.zip") != NULL){
                char const *SDCARD_PATH = "/Android/data/com.adups.fota/files/adupsfota/update.zip";
                strlcat(modified_path, SDCARD_PATH, len);
                printf("\n====strlcat(modified_path, SDCARD_PATH, len)====\n");
                printf(" \"%s\"\n", modified_path);//"/sdcard/Android/data/com.adups.fota/files/adupsfota/update.zip"
                printf("\n\n");
      }
      else if(strstr(update_package, "/Android/data/com.adups.fota/files/LocalSdUpdate.zip") != NULL){
                char const *SDCARD_PATH = "/Android/data/com.adups.fota/files/LocalSdUpdate.zip";
                strlcat(modified_path, SDCARD_PATH, len);
      }
            printf("(replacing path \"%s\" with \"%s\")\n",
                   update_package, modified_path);
            update_package = modified_path;
        }
  }
    #endif
  //打印出当前版本的build.prop
  property_list(print_property, nullptr);
  printf("\n");

继续填坑,分析剩余的进入install_package之前的判断条件和finish_recovery方法,我们先对其中主要流程做下说明,再分析其中的一些细节方法

 

recovery.cpp
init main
 ......
 ......
//读取的 RECOVERY_API_VERSION 在Android.mk中有定义数值
ui->Print("Supported API: %d\n", kRecoveryApiVersion);
  //初始化status
  int status = INSTALL_SUCCESS;
  //判断update_package 是否有值
  if (update_package != nullptr) {
    modified_flash = true;
    //如果判断电量不满足条件,打印log,并跳出升级,目前的最低升级电量为20%,TODO后续有时间再研究时lib里面的方法
    if (!is_battery_ok()) {
      ui->Print("battery capacity is not enough for installing package, needed is %d%%\n",
                BATTERY_OK_PERCENTAGE);
      //otautil/include/otautil/error_code.h:22:  kLowBattery = 20
      //把错误的信息输入到last_install中
      log_failure_code(kLowBattery, update_package);
      status = INSTALL_SKIPPED;
    } else if (bootreason_in_blacklist()) {
      //如果bootreason 是kernel_panic 或者similar 则跳出流程,输入到last_install中
      ui->Print("bootreason is in the blacklist; skip OTA installation\n");
      log_failure_code(kBootreasonInBlacklist, update_package);
      status = INSTALL_SKIPPED;
    } else {
      //首次升级args参数中时没有retry_count,初始定义也为0
      if (retry_count == 0) {
        set_retry_bootloader_message(retry_count + 1, args);
      }
      //真正的升级的流程install_package
      status = install_package(update_package, &should_wipe_cache, TEMPORARY_INSTALL_FILE, true,
                               retry_count);
      if (status == INSTALL_SUCCESS && should_wipe_cache) {
        wipe_cache(false, device);
      }
      if (status != INSTALL_SUCCESS) {
        ui->Print("Installation aborted.\n");
        //这里暂时不明白时什么场景下的,但是这里相当于出现异常,又重新走一遍升级的意思
        if (status == INSTALL_RETRY && retry_count < RETRY_LIMIT) {
          copy_logs();
          retry_count += 1;
          set_retry_bootloader_message(retry_count, args);
          // Print retry count on screen.
          ui->Print("Retry attempt %d\n", retry_count);

          // Reboot and retry the update
          if (!reboot("reboot,recovery")) {
            ui->Print("Reboot failed\n");
          } else {
            while (true) {
              pause();
            }
          }
        }
        //如果是userdbug或者eng版本,直接显示报错,如果时user版本,需要按键组合显示
        if (is_ro_debuggable()) {
          ui->ShowText(true);
        }
      }
    }

log_failure_code方法  其实就是把状态信息写入到/tmp/recovery.log 和 /tmp/last_install中

static void log_failure_code(ErrorCode code, const char *update_package) {
    //code 为 20,创建一个字符串容器
    std::vector<std::string> log_buffer = {
        update_package,
        "0",  // install result
        "error: " + std::to_string(code),
    };
    //在每个元素中间加上换行拼接为字符串,放入到tmp/last_install文件中
    std::string log_content = android::base::Join(log_buffer, "\n");
    if (!android::base::WriteStringToFile(log_content, TEMPORARY_INSTALL_FILE)) {
        PLOG(ERROR) << "failed to write " << TEMPORARY_INSTALL_FILE;
    }
    // Also write the info into last_log.
    LOG(INFO) << log_content;
}

set_retry_bootloader_message(retry_count + 1, args) 方法

 

recovery.cpp

static void set_retry_bootloader_message(int retry_count, const std::vector<std::string>& args) {
  std::vector<std::string> options;
  //遍历出args中的每个元素
  for (const auto& arg : args) {
    if (!android::base::StartsWith(arg, "--retry_count")) {
      //把取出的元素添加到option容器中,
      options.push_back(arg);
    }
  }
  //把--retry_count放入option的尾部 push_back(elem)// 在尾部加入一个数据。
  options.push_back(android::base::StringPrintf("--retry_count=%d", retry_count));
  std::string err;
  //更新bcb的信息,这个方法其实在get_args中已经出现过,回写BCB,所以我们现在需要填之前的坑
  if (!update_bootloader_message(options, &err)) {
    LOG(ERROR) << err;
  }
}

bootable/recovery/bootloader_message/bootloader_message.cpp

bool update_bootloader_message(const std::vector<std::string>& options, std::string* err) {
  bootloader_message boot;
  if (!read_bootloader_message(&boot, err)) {
    return false;
  }
  //更新retry_count参数到链表boot中
  update_bootloader_message_in_struct(&boot, options);
  //把新的boot写入到misc分区
  return write_bootloader_message(boot, err);
}

bool update_bootloader_message_in_struct(bootloader_message* boot,
                                         const std::vector<std::string>& options) {
  if (!boot) return false;
  // Replace the command & recovery fields.
  //memset是一个赋值函数,在这里是用来清空了boot.command和boot.recovery的内容
  memset(boot->command, 0, sizeof(boot->command));
  memset(boot->recovery, 0, sizeof(boot->recovery));
  //这个我们在看路径转换的时候已经知道了这两个方法,strlcpy拷贝strlcat拼接
  strlcpy(boot->command, "boot-recovery", sizeof(boot->command));
  strlcpy(boot->recovery, "recovery\n", sizeof(boot->recovery));
  for (const auto& s : options) {
    strlcat(boot->recovery, s.c_str(), sizeof(boot->recovery));
    //s.back() 传回最后一个数据,不检查这个数据是否存在。
    if (s.back() != '\n') {
      strlcat(boot->recovery, "\n", sizeof(boot->recovery));
    }
  }
  //输出内容[    6.905132] update_bootloader_message_in_struct==================
[    6.905167] boot_command boot-recovery
[    6.905226] boot_recovery recovery
[    6.905235] /sbin/recovery
[    6.905242] --update_package=/storage/C520-1F1D/Android/data/com.adups.fota/files/adupsfota/update.zip
[    6.905248] --locale=zh-Hans-CN
[    6.905254] --retry_count=1
[    6.905260]
[    6.905289]
[    6.905317] update_bootloader_message_in_struct==================
  printf("\nupdate_bootloader_message_in_struct==================\n");
  std::string boot_command = std::string(boot->command, sizeof(boot->command));
  printf("boot_command %s\n", boot_command.c_str());
  std::string boot_recovery = std::string(boot->recovery, sizeof(boot->recovery));
  printf("boot_recovery %s\n", boot_recovery.c_str());
  printf("\nupdate_bootloader_message_in_struct==================\n");
  return true;
}

这里要插入一个前面提到过的内容

  //getopt_long获取参数,固定用法,将--update_package=@/cache/recovery/block.map 得到参数-u --locale=en-US 得到参数l
  while ((arg = getopt_long(args_to_parse.size(), args_to_parse.data(), "", OPTIONS,
                            &option_index)) != -1) {
    switch (arg) {
   //这里的n就是 读取到的“retry_count”,如果升级中断,会将optarg = 1 赋值给retry_count
   //相当于一个标志位,判断是不是首次升级
       case 'n':
        android::base::ParseInt(optarg, &retry_count, 0);
        break;

finish_recovery详情

recovery.cpp

static void finish_recovery() {
  //如果locale不为空且cache可以挂载到cache分区
  if (!locale.empty() && has_cache) {
    //在recovery.log上打印出信息
    LOG(INFO) << "Saving locale \"" << locale << "\"";
    //如果不能挂载到/cache/recovery/last_locale路径,则打印错误
    if (ensure_path_mounted(LOCALE_FILE) != 0) {
      LOG(ERROR) << "Failed to mount " << LOCALE_FILE;
    //如果可以挂载,将locale信息写入到last_locate中
    } else if (!android::base::WriteStringToFile(locale, LOCALE_FILE)) {
      PLOG(ERROR) << "Failed to save locale to " << LOCALE_FILE;
    }
  }
  //拷贝log从tmp到cache/recovery/last_*
  copy_logs();
  //清除了bcb中的信息,保证下次开机直接进入正常启动
  std::string err;
  if (!clear_bootloader_message(&err)) {
    LOG(ERROR) << "Failed to clear BCB message: " << err;
  }
  //清除了cache分区中command中的信息,保证下次开机直接进入正常启动
  //unlink()会删除参数pathname指定的文件,文件夹处理不了。成功返回0,否则返回1  
  //ENOENT  (C++11) //无此文件或目录
  if (has_cache) {
    if (ensure_path_mounted(COMMAND_FILE) != 0 || (unlink(COMMAND_FILE) && errno != ENOENT)) {
      LOG(WARNING) << "Can't unlink " << COMMAND_FILE;
    }
    ensure_path_unmounted(CACHE_ROOT);
  }
  sync();  // For good measure.
}

copy_logs方法

recovery.cpp

static void copy_logs() {
    //modified初始设定为false,进入update_package=nullptr后置为ture,
    if (!modified_flash) {
        return;
    }
    //拷贝log到pmsg,TODO 暂时不知道是作什么的
    copy_log_file_to_pmsg(TEMPORARY_LOG_FILE, LAST_LOG_FILE);
    copy_log_file_to_pmsg(TEMPORARY_INSTALL_FILE, LAST_INSTALL_FILE);
    //判断cache分区是否可以挂载
    if (!has_cache) {
        return;
    }
    //确保log路径可以挂载成功,
    ensure_path_mounted(LAST_LOG_FILE);
    ensure_path_mounted(LAST_KMSG_FILE);
    //修改log名称,让last_log,last_kmsg为最新的一次升级,
    //的方法在bootable/recovery/rotate_logs中,其实就是修改log名称,把log文件名last_log> last_log.1
    rotate_logs(LAST_LOG_FILE, LAST_KMSG_FILE);
    //拷贝log,并给到文件相应的权限和文件组
    copy_log_file(TEMPORARY_LOG_FILE, LOG_FILE, true);
    //拷贝recovery.log到last.log
    copy_log_file(TEMPORARY_LOG_FILE, LAST_LOG_FILE, false);
    ///拷贝tmp/last_install到cache/recovery/last_install
    copy_log_file(TEMPORARY_INSTALL_FILE, LAST_INSTALL_FILE, false);
    //保存kernel的log到last_kmsg
    save_kernel_log(LAST_KMSG_FILE);
    //给文件设定权限
    chmod(LOG_FILE, 0600);
    //给文件设定所在组
    chown(LOG_FILE, AID_SYSTEM, AID_SYSTEM);
    chmod(LAST_KMSG_FILE, 0600);
    chown(LAST_KMSG_FILE, AID_SYSTEM, AID_SYSTEM);
    chmod(LAST_LOG_FILE, 0640);
    chmod(LAST_INSTALL_FILE, 0644);
    sync();
}

 

Recovery系统的框架结构说明及常用的客制化修改流程 一、Recovery系统简介 Recovery模式指的是一种可以对安卓机内部的数据或系统进行修改的模式(类似于windows PE或DOS)。在这个模式下我们可以刷入新的Android系统,或者对已有的系统进行备份或升级,也可以在此模式下恢复出厂设置。系统进入recovery模式后会装载recovery分区,该分区包含recovery.img(与boot.img类似,也包含了标准的内核和根文件系统).进入该模式后主要就是运行了recovery服务(/sbin/recovery)。 二、Recovery系统的 阅读详情

相关推荐

Buildroot+Qt嵌入式开发实战:从配置到部署的七层校验

嵌入式Linux构建系统是定制化操作系统的基础工具,其核心在于对内核、工具链、根文件系统及应用运行时的全栈可控。Buildroot作为轻量级元框架,通过Kconfig驱动的模块化配置,实现从SoC适配(如ARMv7-A架构的aic8800d40)到Qt/QML交叉编译的精准控制;而Qt嵌入式部署则依赖ABI一致性、OpenGL ES后端集成、QML模块路径与运行时环境协同。技术价值体现在小体积、高确定性、强可复现性,广泛应用于工业HMI、车载终端、边缘网关等资源受限场景。本文聚焦Buildroot配置决策链

weixin_34279184的博客 292

Recovery启动流程(3)--recovery.cpp分析

转载请注明来源:cuixiaolei的技术博客 这篇文章主要通过分析高通recovery目录下的recovery.cpp源码,对recovery启动流程有一个宏观的了解。MTK和高通的recovery几乎一样,只是使用自己家的mt_xxx文件。 为什么要分析recovery.cpp这个文件? 我们知道,当我们通过按键或者应用进入recovery模式,实质是kernel后加载recovery.img,kernel起来后执行的第一个进程就 是init,此进程会读入init.rc启动相应的服务。在r

蓝精灵的专栏 1964

Android真实系统架构:从Linux内核到Framework的三层绞杀结构

Android并非教科书定义的标准化操作系统,而是一个以Linux内核为基底、经深度裁剪与定制的移动工程生态。其核心原理在于三层紧耦合架构:底层是被阉割又强扩展的Linux内核(含LMK内存管理、wakelock电源机制与Binder IPC);中层是芯片厂商主导的HAL与Vendor Interface,充满私有扩展与兼容性暗坑;上层则是Java Framework与Native Runtime持续博弈的‘拔河现场’。这种架构赋予Android极强硬件适配能力与商业灵活性,但也导致功耗差异、OTA升级复杂

diaoqi6581的博客 423

2020.03.23-Android Q recovery 程序走读分析

文章目录Android Q recovery 程序 Android Q recovery 程序 程序入口:为bootable/recovery/recovery_main.cpp 下的main函数; 313 int main(int argc, char** argv) { 314 // We don't have logcat yet under recovery; so we'll p...

mengyays的博客 806

Ubuntu 16.04 下 Icinga2 轻量监控部署实战

监控系统是保障服务可用性的基础设施,其核心在于稳定性、低资源占用与强兼容性。在老旧环境(如内核4.4、glibc 2.23、Python 2.7)中,传统方案常因依赖冲突、内存溢出或API失效而失能。Icinga2 凭借静态链接二进制、POSIX兼容插件、分层配置与控制面/数据面分离架构,成为 Ubuntu 16.04 等受限系统的高确定性选择。它支持 ICMP/TCP/HTTP 等基础探活,可无缝集成企业微信告警、渗透测试工作流(monitoring oscp),并规避 systemd/新Python等升

weixin_30664051的博客 392

Debian 9 手动部署 LAMP 栈:Apache+MariaDB+PHP 实战指南

LAMP 是 Linux、Apache、MySQL(或 MariaDB)、PHP 四大开源组件构成的经典 Web 服务架构,其核心在于操作系统级服务协同与进程权限控制。理解 Apache 的 MPM 模块机制、MariaDB 的 socket 连接路径、PHP 扩展加载顺序及 systemd 服务依赖链,是保障 Web 应用稳定运行的基础能力。该架构广泛应用于老旧工控系统、嵌入式网关及等保合规环境,尤其在 Debian 9 这类长期稳定发行版中,需精准匹配内核、glibc 与软件包版本。本文聚焦真实运维场景

weixin_34174105的博客 434

Jetson Orin NX + Boson载板从裸模块到本地LLM部署全栈指南

嵌入式AI系统启动涉及硬件初始化、操作系统适配与边缘模型部署三大基础环节。其核心原理在于:模块级硬件需通过BSP固件建立可靠引导链,再由JetPack提供深度绑定的CUDA/TensorRT等AI加速栈,最终在内存与带宽受限的边缘端完成模型量化、推理引擎选型与安全服务封装。这一技术路径广泛应用于工业AIoT、边缘智能终端及高校嵌入式AI教学场景。本文聚焦Jetson Orin NX与国产Boson载板组合,详解BSP刷写、JetPack离线安装及GGUF格式本地LLM部署等关键实践,覆盖从BootROM唤醒

weixin_33976072的博客 247

Android 10.0 进入recovery模式(等待用户选择recovery模式界面)实现自动恢复出厂设置

在10.0 11.0的产品由于没有音量键 所以用音量键和电源键来选择recovery模式就无法实现了 所以当进入 recovery选择模式界面 就一直停在那里 根据需要 要修改成进入等待用户选择recovery模式时,直接 恢复出厂设置就好 先看recovery_main.cpp代码如下 路径:bootable\recovery\recovery_main.cpp int main(int argc, char** argv) { // We don't have logcat yet under rec

安卓兼职framework和app工程师的博客 2427

Android 13 FirstStageMain深度解析:系统启动的裸机级重构

FirstStageMain是Android系统启动流程中位于内核与用户空间交界处的关键阶段,属于init进程的初始执行入口,而非普通函数调用。其核心原理在于绕过libc和bionic,在无堆栈、无动态链接、无文件系统挂载的极简环境下,完成SELinux策略加载、APEX镜像预解压、设备树解析及init自身内存重映射四大硬性任务。这一阶段直接决定开机时长、安全启动可靠性与系统原子性,是车机、IoT等对启动性能与可信度有严苛要求场景的技术基石。本文聚焦Android13中FirstStageMain的真实行为

weixin_34357436的博客 445

最新扣子(coze)案例:在coze上用DeepSeek+私有数据库、知识库,创建你的专属智能体

Gartner预测,到2026年,70%的企业将通过Al知识库实现「组织记忆外化」。数字化时代,数据已成为企业发展的核心资产。我们通过人工智能将知识库从“静态存储"升级为“智能服务”。让"沉睡宝藏"焕发新生,助力企业突破数据困局。

weixin_72689845的博客 2164

2025年有道翻译网页版JS逆向实战:从抓包到AES解密,手把手教你搞定sign签名

本文详细解析2025年有道翻译网页版的JS逆向实战,从抓包分析到AES解密全流程。通过实战演示签名生成机制和AES-CBC解密技术,帮助开发者破解有道翻译的反爬系统,实现高效数据获取。重点涵盖动态密钥处理、错误重试机制及反反爬策略,为开发者提供完整的技术解决方案。

weixin_30379531的博客 505

Android与Linux启动机制本质差异解析

Linux启动是内核加载根文件系统并执行首个用户进程(如/sbin/init)的基础过程,核心在于硬件初始化、设备树解析与可预测的最小化启动;Android启动则是在Linux内核之上构建的定制化应用级协议,依赖Binder、属性服务、SELinux、Zygote预加载等专有机制,强调3秒内完成UI就绪。其技术价值体现在安全启动链(BootROM→Kernel→Zygote→system_server)与服务依赖图的双重保障,广泛应用于智能手机、车载IVI、智能终端等场景。本文深入对比二者在init行为、日

weixin_34245082的博客 367

STM32 A/B分区OTA升级实战:从原理到避坑指南

本文详细解析STM32 A/B分区OTA升级的核心原理与实战实现,涵盖双分区设计、Bootloader开发、固件传输验证及常见问题解决方案。重点介绍通过A/B分区实现无缝升级与安全回滚的机制,帮助开发者规避典型陷阱,提升嵌入式系统远程更新的可靠性与效率。

qqq44的博客 1108

y9000p后封机如何验证(by quqi99)

14, 打联想客服(400990888-2-1-6-5), 但客服说默认保修时间就是从生产日期算的, 保修是二选一, 不需要修改这个时间也可通过发票日期找他们修改(保修是这样用的, 平时不用带发票, 但系统显示过保带发票就能修). 另外他说联想官方没有渠道可查询是否被激活(激活是windows的事). 客户说windows在机器生产日期之前或之后安装都有可能, 为什么两个时间相差那么长她也不清楚建议问经销商, 她同时说她们没办法知道是不是后封机.

技术并艺术着 1807

技术美术个人笔记(十六)——Houdini到UE的大地形

文章目录关于地形白模制作关于PDG流程 关于地形白模制作 用heightfield noise和笔刷刷出合适的mask做山形叠加: 用remap可快捷反转mask ,用来做平原地区的小型丘陵: 接着用clip切平最低处,用distort制造侵蚀等,对部分地区用笔刷进行微调拓宽峡谷: 用经过sweep打平的曲线以及裁切的各种多面体用heightfield_project投影制作河道和水池,用blur mask并remap的方法拓宽河道 用distort、erode制造合适的弯曲和侵蚀,同理可挖出月牙形

小谁的博客 3180

VMware 虚拟机里连不上网的五种解决方案

在VMware虚拟机里的虚拟机系统连接不上网络 首先,注意查看适配器选项里的网络连接这两个网络连接是否存在,如果不存在可以重新装一下VM 如果存在,连不上网 解决办法一: 虚拟机设置里,找到“网络适配器”,右边的网络连接选择“NAT 模式”,如果不行的话再linux系统。还是不行的话接着看第二种解决方案。 解决方法二: 可能原因是VMware NET Service...

墨轩 18万+

Ubuntu 16.04服务器初始配置实战指南:SSH、sudo、UFW与开发适配

Linux服务器初始配置是系统稳定与安全的根基,其核心在于操作系统启动后对用户权限(sudo)、远程访问(SSH)、网络防护(UFW)及基础工具链的精准校准。Ubuntu 16.04虽已进入ESM维护阶段,但因其长期部署于嵌入式、科研等不可迁移场景,其内核4.4、OpenSSH 7.2和systemd 229等组件组合,决定了SSH协议协商、sudo权限模型与防火墙规则行为的特殊性。掌握sshd_config深度调优、sudoers语法安全加固、UFW规则顺序控制及apt源迁移至old-releases,不

weixin_33802505的博客 458

直线电机驱动技术原理与应用解析

直线电机作为直接电能-直线运动转换装置,其核心原理基于电磁感应定律和洛伦兹力公式。通过三相交流电在定子绕组中产生行波磁场,与动子永磁体相互作用实现精准推力控制。相比传统旋转电机传动系统,直线电机具有零传动间隙、高动态响应(加速度可达10g)和微米级定位精度等技术优势,这使其成为半导体光刻机、精密数控机床等高端装备的关键驱动部件。现代直线电机驱动单元通常采用三环控制架构(电流环、速度环、位置环),配合SiC功率器件和高分辨率光栅反馈,实现纳米级运动控制。随着模块化设计和直接冷却技术的发展,直线电机正朝着更高功

weixin_26802933的博客 259

Audacity au3→au4 翻译迁移完整指南:5 个脚本把 .po 灌进 .ts 的实操手册

Audacity au4 用 Qt 的 `.ts`/`.qm` 取代了 au3 的 gettext 翻译栈。`tools/translations/au3-au4-migration/` 下的 5 个一次性脚本负责这次 au3→au4 翻译迁移:改写源码宏、灌入旧 PO 译文、回收失配残量。复现迁移或维护语言包的译者按本文顺序执行即可。 [![Audacity 版本4 新功能发布视频封面](ht

gitblog_00455的博客 215

甲基化生物信息学分析及批次效应校正全流程

本文系统阐述了DNA甲基化数据分析中的批次效应问题及解决方案。甲基化数据(β值和M值)对实验批次差异高度敏感,可能导致假阳性结果。文章详细介绍了批次效应检测方法(PCA可视化、BEclear定量评分)和校正流程,包括数据预处理、参数化(ComBat-met)与非参数化(BEclear)校正方法,并提供了完整的R代码实现。重点解决了β值有界分布与统计模型假设冲突的问题,为甲基化研究的可靠性提供技术保障。

huanghm88的专栏 109

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

RACECAR ROS 2底层平台安装:JetPack固件、实时内核与CycloneDDS深度配置

ROS 2是机器人中间件的核心框架,其运行稳定性高度依赖底层操作系统、硬件驱动与通信机制的协同优化。在RACECAR等嵌入式移动机器人平台上,单纯安装ROS 2发行版远不足以支撑实时控制需求;必须深入固件层(如JetPack版本与Orin SoC Revision精确匹配)、内核层(PREEMPT_RT补丁的精准裁剪)和中间件层(CycloneDDS低延迟QoS策略),才能满足舵机响应≤8ms、传感器时间同步、CAN总线确定性传输等硬实时约束。本文聚焦RACECAR-Jetson AGX Orin+ROS

wentao365的专栏 472

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

Android与Linux启动过程深度解析:从BootROM到SystemUI全链路

启动过程是操作系统最底层的因果链,本质是硬件复位后控制权在BootROM、Bootloader、Kernel、init四层间的精确移交。理解这一机制,才能诊断黑屏、SSH无法连接、zygote崩溃等系统级故障。本文聚焦启动时序、关键断点(如AVB验证失败、initramfs挂载异常、system_server初始化超时)及跨层依赖(如content://URI可用性取决于zygote→PMS→init.rc配置),覆盖Android 12/13与主流Linux发行版(Ubuntu/CentOS)的启动差异,

weixin_34099526的博客 442

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

RK3588 U-Boot烧录失败的五大根源与工程化规避方案

U-Boot是嵌入式Linux系统启动链的核心引导程序,其烧录可靠性直接决定SoC能否正常启动。RK3588作为ARM64架构的高性能SoC,启动流程复杂,涉及BootROM、TPL、SPL、ATF及U-Boot多级协同,对镜像签名、DDR初始化、USB协议时序和烧录路径等均有严苛要求。常见失败并非工具缺陷,而是SPL镜像不匹配、Secure Boot配置缺失、RKDevTool USB电源管理干扰、loader.bin内存参数错误及烧录分区遗漏等底层工程问题。本文聚焦RK3588平台,结合启动原理与产线实

weixin_34414650的博客 388

Android 10 白名单安装管控:PMS级硬隔离实战指南

Android 安装权限控制已从用户层 Settings 开关演进为系统级 PackageManagerService(PMS)内核机制。在 Android 10 及以上版本中,INSTALL_PACKAGES 权限可绕过‘未知来源’设置,导致静默安装风险激增;WhiteListAppFilter.properties 作为 AOSP 原生支持的白名单机制,通过 mmap 内存映射+SHA-256 签名哈希校验,在 PackageParser.parsePackage 后、installStage 前毫秒级

weixin_33766168的博客 290

【轻松领养】Microduck-零基础部署调试实战:从拆封到跑通机器人控制软件

本文详述将 Radxa ZERO 3W(RK3566,2GB LPDDR4)从零部署为 Microduck 机器人主控的全流程:涵盖选镜像、烧录 microSD(推荐使用 Radxa OS + balenaEtcher 避免分区丢失)、首次上电、SSH 免密登录、板级 bring-up 及 Rust 守护进程 robotd 部署。重点解决电源不足、Windows 烧录工具破坏分区、设备树缺失等常见坑点,提供可复现的命令与诊断方法,附常见问题速查表与调试指南,适用于嵌入式开发与边缘计算项目。

qq_41584795的博客 533
上一篇: 一、ota流程之framwork installpackage流程
下一篇: 四、install_package详细流程
李标标
博客等级 码龄11年 124粉丝 43原创
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值