OTA升级失败,端口占用bind: Address already in use
目前程序的OTA逻辑是判断MD5SUM,如果不匹配,则下载新程序,杀掉旧程序启动新程序。
今天突然发现ota升级不管用了,查看后台进程发现老进程没有完全退出。导致端口被占用。
源代码:
// 重命名运行的程序, 下载的新程序名字覆盖
static int download_exec(const string& download_address, string cloud_md5)
{rename(PROGRAM_NAME, PROGRAM_NAME_HIS);string wget_cmd = "wget " + download_address + " -O " + PROGRAM_NAME +" -q -o wgetlog";FILE *f = popen(wget_cmd.c_str(), "r");char res[512];if(fread(res, 1, sizeof(res), f) < 0){hloge("Download Failed");pclose(f);return -1;} // 阻塞主程序,等待下载完成string download_md5 = local_md5_get();hlogi("download md5:%s", download_md5);//cout<<"download_md5"<<download_md5<<endl;hlogi("cloud md5:%d", cloud_md5);//cout<<"cloud_md5"<<cloud_md5<<endl;if(cloud_md5.compare(download_md5) != 0){ // 下载失败/MD5校验不匹配rename(PROGRAM_NAME_HIS, PROGRAM_NAME);pclose(f);return -1;}hlogi("file download success");// fstream tmpfile("./.update", ios::out | ios::app); // 下载成功时,创建临时文件// tmpfile.close();hlogi("Ready to execute new program");string exe_path = "./" + (string)PROGRAM_NAME;update_chmod(exe_path);pid_t pid;pid = fork(); //这里不能正常退出sleep(10);if(pid < 0){hloge("failed to fork child porcess\n");}else if(pid > 0){hlogi("Old process end");_exit(0);}else{if(execl(exe_path.c_str(), "Laserbird", NULL) == -1){ // 执行下载的程序hloge("FATAL ERROR: Failed to execute new version serivce");return -1;}}return 0;
}
研究发现fork 后父进程直接 exit 可能导致后台僵尸进程
解决方案很简单,把exit(0)改成
_exit(0)
区别:
exit()
会执行标准库清理(比如 flush
输出缓冲区、调用 atexit
注册的函数等)。
_exit()
不做清理,直接从内核退出进程,更适合在 fork()
后的父进程中使用,避免留下僵尸进程。
exit()
和 _exit()
的差异
函数 | 来自 | 会清理 stdio 缓冲区 | 会执行 atexit 函数 | 直接退出到内核 |
---|---|---|---|---|
exit() | C 标准库 | ✅ 是 | ✅ 是 | ❌ 否 |
_exit() | 系统调用 | ❌ 否 | ❌ 否 | ✅ 是 |
// 父进程调用 exit()
pid_t pid = fork();
if (pid > 0) {exit(0); // 可能留下僵尸
}// 父进程调用 _exit()
pid_t pid = fork();
if (pid > 0) {_exit(0); // 不会留下僵尸
}
这样正常OTA就没问题。