Android 14 init进程解析

前言

当bootloader启动后,启动kernel,kernel启动完后,在用户空间启动init进程,再通过init进程,来读取init.rc中的相关配置,从而来启动其他相关进程以及其他操作。
init进程启动主要分为两个阶段:

第一个阶段负责:

  • 创建文件系统目录并挂载相关的文件系统
  • 初始化日志输出
  • 启用SELinux安全策略
  • 为第二阶段做准备

第二阶段负责:

  • 创建进程会话密钥、并初始化属性系统
  • 执行SELinux第二阶段、并恢复一些文件安全上下文
  • 新建epoll、并初始化子进程终止信号处理函数
  • 设置其他系统属性、并开启属性服务
  • 解析init.rc等文件,建立rc文件的action、service,启动其他进程
init进程如何被启动?

init进程是在Kernel启动后,启动的第一个用户空间进程,PID为1
kernel-5.10/init/main.c

static int __ref kernel_init(void *unused)
{int ret;kernel_init_freeable();//进行init进程的一些初始化操作/* need to finish all async __init code before freeing the memory */async_synchronize_full();//等待所有异步调用执行完成,在释放内存前,必须完成所有的异步 __init 代码ftrace_free_init_mem();jump_label_invalidate_initmem();free_initmem();//释放所有init.*中的内存mark_readonly();/** Kernel mappings are now finalized - update the userspace page-table* to finalize PTI.*/pti_finalize();system_state = SYSTEM_RUNNING;//设置系统状态为运行状态numa_default_policy();//设定NUMA系统的默认内存访问策略rcu_end_inkernel_boot();bootprof_log_boot("Kernel_init_done");if (ramdisk_execute_command) {//ramdisk_execute_command的值为“/init”ret = run_init_process(ramdisk_execute_command);//运行根目录下的init进程 *****if (!ret)return 0;pr_err("Failed to execute %s (error %d)\n",ramdisk_execute_command, ret);}/** We try each of these until one succeeds.** The Bourne shell can be used instead of init if we are* trying to recover a really broken machine.*/if (execute_command) {//execute_command的值如果有定义就去根目录下找对应的应用程序,然后启动ret = run_init_process(execute_command);if (!ret)return 0;panic("Requested init %s failed (error %d).",execute_command, ret);}if (!try_to_run_init_process("/sbin/init") ||!try_to_run_init_process("/etc/init") ||!try_to_run_init_process("/bin/init") ||!try_to_run_init_process("/bin/sh"))//如果ramdisk_execute_command和execute_command定义的应用程序都没有找到,//就到根目录下找 /sbin/init,/etc/init,/bin/init,/bin/sh 这四个应用程序进行启动return 0;panic("No working init found.  Try passing init= option to kernel. ""See Linux Documentation/admin-guide/init.rst for guidance.");
}

在/kernel/init/mian.c#kernel_init()方法调用了run_init_process()进行启动init进程

init进程入口

在Android Q(10.0)之前的init入口函数是init.cpp,从Android Q(10.0)开始init的入口函数是main.cpp,把各个阶段的操作分离开来,是代码更加简洁。
进入到main.cpp#main()
system/core/init/main.cpp

/** 1.第一个参数argc表示参数个数,第二个参数是参数列表,也就是具体的参数* 2.main函数有四个参数入口,*一是参数中有ueventd,进入ueventd_main*二是参数中有subcontext,进入InitLogging 和SubcontextMain*三是参数中有selinux_setup,进入SetupSelinux*四是参数中有second_stage,进入SecondStageMain* 3.main的执行顺序如下:*  (1)ueventd_main    init进程创建子进程ueventd,*      并将创建设备节点文件的工作托付给ueventd,ueventd通过两种方式创建设备节点文件*  (2)FirstStageMain  启动第一阶段*  (3)SetupSelinux     加载selinux规则,并设置selinux日志,完成SELinux相关工作*  (4)SecondStageMain  启动第二阶段*/int main(int argc, char** argv) {
#if __has_feature(address_sanitizer)__asan_set_error_report_callback(AsanReportCallback);
#endif// Boost prio which will be restored latersetpriority(PRIO_PROCESS, 0, -20);//当argv[0]的内容为ueventd时,strcmp的值为0,ueventd主要是负责设备节点的创建、权限设定等一些列工作if (!strcmp(basename(argv[0]), "ueventd")) {return ueventd_main(argc, argv);}//当传入的参数个数大于1时if (argc > 1) {//参数为subcontext,初始化日志系统if (!strcmp(argv[1], "subcontext")) {android::base::InitLogging(argv, &android::base::KernelLogger);const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap();return SubcontextMain(argc, argv, &function_map);}//参数为selinux_setup,启动Selinux安全策略if (!strcmp(argv[1], "selinux_setup")) {return SetupSelinux(argv);}//参数为“sencond_stage”,启动init进程第二阶段if (!strcmp(argv[1], "second_stage")) {return SecondStageMain(argc, argv);}}//默认启动init进程第一阶段return FirstStageMain(argc, argv);
}

ueventd_main()

Android根文件系统的镜像中不存在“/dev”目录,该目录是init进程启动后动态创建的。所以,建立Android中设备节点文件需要init进程完成,为此init进程创建子进程ueventd,并将创建设备节点文件的工作托付给ueventd。
ueventd通过两种方式创建设备节点文件:
第一种方式对应“冷插拔”(Cold Plug),即以预先定义的设备信息为基础,当ueventd启动后,同一创建设备节点文件。这一类设备节点文件也被称为静态节点文件。
第二种方式对应“热插拔”(Hot Plug),即在系统运行中,当有设备插入USB端口时,ueventd就会接收到这一事件,为插入的设备动态创建设备节点文件。这一类设备节点文件也被称为动态节点文件。
进入ueventd.cpp#ueventd_main()
system/core/init/ueventd.cpp

int ueventd_main(int argc, char** argv) {/** init sets the umask to 077 for forked processes. We need to* create files with exact permissions, without modification by* the umask.*///设置新建文件的默认值,这个与chmod相反,这里相当于新建文件后的权限为666umask(000);//初始化内核日志,位于节点/dev/kmsg,此时logd、logcat进程还没有起来//采用kernel的log系统,打开的设备节点/dev/kmsg,那么可通过cat /dev/kmsg来获取内核logandroid::base::InitLogging(argv, &android::base::KernelLogger);LOG(INFO) << "ueventd started!";//注册selinux相关的用于打印log的回调函数SelinuxSetupKernelLogging();SelabelInitialize();std::vector<std::unique_ptr<UeventHandler>> uevent_handlers;//解析xml,根据不同SOC厂商获取不同的hardware rc文件auto ueventd_configuration = GetConfiguration();uevent_handlers.emplace_back(std::make_unique<DeviceHandler>(std::move(ueventd_configuration.dev_permissions),std::move(ueventd_configuration.sysfs_permissions),std::move(ueventd_configuration.subsystems), android::fs_mgr::GetBootDevices(), true));uevent_handlers.emplace_back(std::make_unique<FirmwareHandler>(std::move(ueventd_configuration.firmware_directories),std::move(ueventd_configuration.external_firmware_handlers)));//冷启动if (ueventd_configuration.enable_modalias_handling) {std::vector<std::string> base_paths = {"/odm/lib/modules", "/vendor/lib/modules"};uevent_handlers.emplace_back(std::make_unique<ModaliasHandler>(base_paths));}UeventListener uevent_listener(ueventd_configuration.uevent_socket_rcvbuf_size);if (!android::base::GetBoolProperty(kColdBootDoneProp, false)) {ColdBoot cold_boot(uevent_listener, uevent_handlers,ueventd_configuration.enable_parallel_restorecon);cold_boot.Run();}for (auto& uevent_handler : uevent_handlers) {uevent_handler->ColdbootDone();}//忽略子进程终止信号signal(SIGCHLD, SIG_IGN);//在最后一次调用waitpid()和为上面的sigchld设置SIG_IGN之间退出的获取和挂起的子级while (waitpid(-1, nullptr, WNOHANG) > 0) {}// Restore prio before main loopsetpriority(PRIO_PROCESS, 0, 0);//监听来自驱动的uevent,进行“热插拔”处理uevent_listener.Poll([&uevent_handlers](const Uevent& uevent) {for (auto& uevent_handler : uevent_handlers) {uevent_handler->HandleUevent(uevent);}return ListenerAction::kContinue;});return 0;
}

init进程启动第一阶段first_stage_init.cpp

主要负责:

  • 创建文件系统目录并挂载相关的文件系统
  • 初始化日志输出
  • 启用SELinux安全策略
  • 为第二阶段做准备

system/core/init/first_stage_init.cpp

int FirstStageMain(int argc, char** argv) {//init crash时重启引导加载程序//这个函数主要作用将各种信号量,如SIGABRT,SIGBUS等的行为设置为SA_RESTART,一旦监听到这些信号即执行重启系统if (REBOOT_BOOTLOADER_ON_PANIC) {InstallRebootSignalHandlers();}boot_clock::time_point start_time = boot_clock::now();std::vector<std::pair<std::string, int>> errors;#define CHECKCALL(x) \if ((x) != 0) errors.emplace_back(#x " failed", errno);// Clear the umask.//清空文件权限umask(0);CHECKCALL(clearenv());CHECKCALL(setenv("PATH", _PATH_DEFPATH, 1));//在RAM内存上获取基本的文件系统,剩余的被rc文件所用CHECKCALL(mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755"));CHECKCALL(mkdir("/dev/pts", 0755));CHECKCALL(mkdir("/dev/socket", 0755));CHECKCALL(mkdir("/dev/dm-user", 0755));CHECKCALL(mount("devpts", "/dev/pts", "devpts", 0, NULL));#define MAKE_STR(x) __STRING(x)CHECKCALL(mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC)));#undef MAKE_STR//非特权应用不能使用Android cmdlineCHECKCALL(chmod("/proc/cmdline", 0440));std::string cmdline;android::base::ReadFileToString("/proc/cmdline", &cmdline);// Don't expose the raw bootconfig to unprivileged processes.chmod("/proc/bootconfig", 0440);std::string bootconfig;android::base::ReadFileToString("/proc/bootconfig", &bootconfig);gid_t groups[] = {AID_READPROC};CHECKCALL(setgroups(arraysize(groups), groups));CHECKCALL(mount("sysfs", "/sys", "sysfs", 0, NULL));CHECKCALL(mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL));CHECKCALL(mknod("/dev/kmsg", S_IFCHR | 0600, makedev(1, 11)));if constexpr (WORLD_WRITABLE_KMSG) {CHECKCALL(mknod("/dev/kmsg_debug", S_IFCHR | 0622, makedev(1, 11)));}CHECKCALL(mknod("/dev/random", S_IFCHR | 0666, makedev(1, 8)));CHECKCALL(mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9)));//这对于日志包装器是必需的,它在ueventd运行之前被调用CHECKCALL(mknod("/dev/ptmx", S_IFCHR | 0666, makedev(5, 2)));CHECKCALL(mknod("/dev/null", S_IFCHR | 0666, makedev(1, 3)));//在第一阶段挂在tmpfs、mnt/vendor、mount/product分区。其他的分区不需要在第一阶段加载,//只需要在第二阶段通过rc文件解析来加载CHECKCALL(mount("tmpfs", "/mnt", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,"mode=0755,uid=0,gid=1000"));//创建可供读写的vendor目录CHECKCALL(mkdir("/mnt/vendor", 0755));CHECKCALL(mkdir("/mnt/product", 0755));// 挂载APEX,这在Android 10.0中特殊引入,用来解决碎片化问题,类似一种组件方式,对Treble的增强,// 不写谷歌特殊更新不需要完整升级整个系统版本,只需要像升级APK一样,进行APEX组件升级CHECKCALL(mount("tmpfs", "/debug_ramdisk", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,"mode=0755,uid=0,gid=0"));// /second_stage_resources is used to preserve files from first to second// stage initCHECKCALL(mount("tmpfs", kSecondStageRes, "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,"mode=0755,uid=0,gid=0"))#undef CHECKCALL//把标准输入、标准输出和标准错误重定向到空设备文件“/dev/null”SetStdioToDevNull(argv);// Now that tmpfs is mounted on /dev and we have /dev/kmsg, we can actually
// talk to the outside world...
#ifdef MTK_LOG
#ifndef MTK_LOG_DISABLERATELIMIT
if (cmdline.find("init.mtklogdrl=1") != std::string::npos)
SetMTKLOGDISABLERATELIMIT();
#else
SetMTKLOGDISABLERATELIMIT();
#endif // MTK_LOG_DISABLERATELIMITif (GetMTKLOGDISABLERATELIMIT())
InitKernelLogging_split(argv);
else
InitKernelLogging(argv);
#else
//在/dev目录下挂载好tmpfs以及kmsg
//这样就可以初始化/kernel Log系统,供用户打印log
InitKernelLogging(argv);
#endif....../*
初始化一些必须的分区
主要作用是去解析/proc/device-tree/firmware/android/fstab
然后得到“/system”,“/vendor”,“/odm”三个目录的挂载信息
*/
if (!DoFirstStageMount(!created_devices)) {
LOG(FATAL) << "Failed to mount required partitions early ...";
}struct stat new_root_info;
if (stat("/", &new_root_info) != 0) {
PLOG(ERROR) << "Could not stat(\"/\"), not freeing ramdisk";
old_root_dir.reset();
}if (old_root_dir && old_root_info.st_dev != new_root_info.st_dev) {
FreeRamdisk(old_root_dir.get(), old_root_info.st_dev);
}SetInitAvbVersionInRecovery();setenv(kEnvFirstStageStartedAt, std::to_string(start_time.time_since_epoch().count()).c_str(),
1);//启动init进程,传入参数selinux_steup
//执行命令:/system/bin/init selinux_setup
const char* path = "/system/bin/init";
const char* args[] = {path, "selinux_setup", nullptr};
auto fd = open("/dev/kmsg", O_WRONLY | O_CLOEXEC);
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
close(fd);
execv(path, const_cast<char**>(args));// execv() only returns if an error happened, in which case we
// panic and never fall through this conditional.
PLOG(FATAL) << "execv(\"" << path << "\") failed";return 1;
}

加载SELinux规则

SELinux是「Security-Enhanced Linux」的简称,是美国国家安全局「NSA=The National Security Agency」
和SCC(Secure Computing Corporation)开发的 Linux的一个扩张强制访问控制安全模块。在这种访问控制体系的限制下,进程只能访问那些在他的任务中所需要文件。
SElinux有两种工作模式:

  1. permissive,所有的操作都被允许(即没有MAC),但是如果违法权限的话,会记录日志,一般eng模式用
  2. enforcing,所有操作都会进行权限检查,一般user和user-debug模式用

不管是security_setenforce还是security_getenforce都是去操作/sys/fs/selinux/enforce文件,0表示permissive 1表示enforcing
SetupSelinux:初始化selinux,加载SElinux规则,配置SWLinux相关log输出,并启动第二阶段
system/core/init/selinux.cpp

/*此函数初始化selinux,然后执行init以在init selinux中运行*/
int SetupSelinux(char** argv) {
#ifdef JOURNEY_FEATURE_ROOT_MODEinitJourneyRootMode();
#endifSetStdioToDevNull(argv);
#ifdef MTK_LOG
#ifndef MTK_LOG_DISABLERATELIMIT{std::string cmdline;android::base::ReadFileToString("/proc/cmdline", &cmdline);if (cmdline.find("init.mtklogdebuggable=1") != std::string::npos)SetMTKLOGDISABLERATELIMIT();}
#elseSetMTKLOGDISABLERATELIMIT();
#endif // MTK_LOG_DISABLERATELIMITif (GetMTKLOGDISABLERATELIMIT())InitKernelLogging_split(argv);elseInitKernelLogging(argv);
#else//初始化Kernel日志InitKernelLogging(argv);
#endif//Debug版本init crash时重启引导加载程序if (REBOOT_BOOTLOADER_ON_PANIC) {InstallRebootSignalHandlers();}boot_clock::time_point start_time = boot_clock::now();MountMissingSystemPartitions();#ifdef MTK_LOGif (GetMTKLOGDISABLERATELIMIT())SelinuxSetupKernelLogging_split();elseSelinuxSetupKernelLogging();
#else//注册回调,用来设置需要写入kmsg的selinux日志SelinuxSetupKernelLogging();
#endifLOG(INFO) << "Opening SELinux policy";// Read the policy before potentially killing snapuserd.std::string policy;ReadPolicy(&policy);auto snapuserd_helper = SnapuserdSelinuxHelper::CreateIfNeeded();if (snapuserd_helper) {// Kill the old snapused to avoid audit messages. After this we cannot// read from /system (or other dynamic partitions) until we call// FinishTransition().snapuserd_helper->StartTransition();}LoadSelinuxPolicy(policy);if (snapuserd_helper) {// Before enforcing, finish the pending snapuserd transition.snapuserd_helper->FinishTransition();snapuserd_helper = nullptr;}//加载SElinux规则SelinuxSetEnforcement();// We're in the kernel domain and want to transition to the init domain.  File systems that// store SELabels in their xattrs, such as ext4 do not need an explicit restorecon here,// but other file systems do.  In particular, this is needed for ramdisks such as the// recovery image for A/B devices.if (selinux_android_restorecon("/system/bin/init", 0) == -1) {PLOG(FATAL) << "restorecon failed of /system/bin/init failed";}setenv(kEnvSelinuxStartedAt, std::to_string(start_time.time_since_epoch().count()).c_str(), 1);//准备启动init进程,传入参数second_stage,进入到第二阶段const char* path = "/system/bin/init";const char* args[] = {path, "second_stage", nullptr};execv(path, const_cast<char**>(args));// execv() only returns if an error happened, in which case we// panic and never return from this function.PLOG(FATAL) << "execv(\"" << path << "\") failed";return 1;
}

SelinuxSetEnforcement():加载SeLinux规则
system/core/init/selinux.cpp

void SelinuxSetEnforcement() {//获取当前Kernel的工作模式bool kernel_enforcing = (security_getenforce() == 1);//获取工作模式的配置bool is_enforcing = IsEnforcing();//如果当前的工作模式与配置的不同,就将当前的工作模式改掉if (kernel_enforcing != is_enforcing) {if (security_setenforce(is_enforcing)) {PLOG(FATAL) << "security_setenforce(" << (is_enforcing ? "true" : "false")<< ") failed";}}if (auto result = WriteFile("/sys/fs/selinux/checkreqprot", "0"); !result.ok()) {LOG(FATAL) << "Unable to write to /sys/fs/selinux/checkreqprot: " << result.error();}
}

init进程启动第二阶段

主要负责:

  • 创建进程会话密钥,并初始化属性系统
  • 执行SELinux第二阶段,并恢复一些文件安全上下文
  • 新建epoll,并初始化子进程终止信号处理函数
  • 设置其他系统属性,并开启属性服务
  • 解析init.rc等文件,建立rc文件的action、service,启动其他进程

system/core/init/init.cpp

int SecondStageMain(int argc, char** argv) {#ifdef JOURNEY_FEATURE_ROOT_MODEinitJourneyRootMode();#endif/*
*init crash时重启引导加载程序
*这个函数主要作用将各种信号量,如SIGABRT,SIGBUS等的行为设置为SA_RESTART,一旦监听到这些信号即执行重启系统
*/if (REBOOT_BOOTLOADER_ON_PANIC) {InstallRebootSignalHandlers();}boot_clock::time_point start_time = boot_clock::now();trigger_shutdown = [](const std::string& command) { shutdown_state.TriggerShutdown(command); };//把标准输入、标准输出和标准错误重定向到空设备文件“/dev/null"SetStdioToDevNull(argv);#ifdef MTK_LOG#ifndef MTK_LOG_DISABLERATELIMIT{std::string cmdline;android::base::ReadFileToString("/proc/cmdline", &cmdline);if (cmdline.find("init.mtklogdrl=1") != std::string::npos)SetMTKLOGDISABLERATELIMIT();const char* force_debuggable_env = getenv("INIT_FORCE_DEBUGGABLE");if (force_debuggable_env && AvbHandle::IsDeviceUnlocked()) {SetMTKLOGDISABLERATELIMIT();}}#elseSetMTKLOGDISABLERATELIMIT();#endif // MTK_LOG_DISABLERATELIMITif (GetMTKLOGDISABLERATELIMIT())InitKernelLogging_split(argv);elseInitKernelLogging(argv);#else//在/dev目录下挂载好tmpfs以及kmsg//这样就可以初始化/kernel log系统,供用户打印logInitKernelLogging(argv);#endifLOG(INFO) << "init second stage started!";// Update $PATH in the case the second stage init is newer than first stage init, where it is// first set.if (setenv("PATH", _PATH_DEFPATH, 1) != 0) {PLOG(FATAL) << "Could not set $PATH to '" << _PATH_DEFPATH << "' in second stage";}// Init should not crash because of a dependence on any other process, therefore we ignore// SIGPIPE and handle EPIPE at the call site directly.  Note that setting a signal to SIG_IGN// is inherited across exec, but custom signal handlers are not.  Since we do not want to// ignore SIGPIPE for child processes, we set a no-op function for the signal handler instead.{struct sigaction action = {.sa_flags = SA_RESTART};action.sa_handler = [](int) {};sigaction(SIGPIPE, &action, nullptr);}// Set init and its forked children's oom_adj.if (auto result =WriteFile("/proc/1/oom_score_adj", StringPrintf("%d", DEFAULT_OOM_SCORE_ADJUST));!result.ok()) {LOG(ERROR) << "Unable to write " << DEFAULT_OOM_SCORE_ADJUST<< " to /proc/1/oom_score_adj: " << result.error();}// Set up a session keyring that all processes will have access to. It// will hold things like FBE encryption keys. No process should override// its session keyring.//01.创建进程会话密钥并初始化属性系统keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 1);// Indicate that booting is in progress to background fw loaders, etc.//创建/dev/.booting文件,就是个标记,表示booting进行中close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));// See if need to load debug props to allow adb root, when the device is unlocked.
const char* force_debuggable_env = getenv("INIT_FORCE_DEBUGGABLE");
bool load_debug_prop = false;
if (force_debuggable_env && AvbHandle::IsDeviceUnlocked()) {
load_debug_prop = "true"s == force_debuggable_env;
}
unsetenv("INIT_FORCE_DEBUGGABLE");// Umount the debug ramdisk so property service doesn't read .prop files from there, when it
// is not meant to.
if (!load_debug_prop) {
UmountDebugRamdisk();
}//初始化属性系统,并从指定文件读取属性
PropertyInit();// Umount second stage resources after property service has read the .prop files.
UmountSecondStageRes();// Umount the debug ramdisk after property service has read the .prop files when it means to.
if (load_debug_prop) {
UmountDebugRamdisk();
}// Mount extra filesystems required during second stage init
MountExtraFilesystems();// Now set up SELinux for second stage.
#ifdef MTK_LOG
if (GetMTKLOGDISABLERATELIMIT())
SelinuxSetupKernelLogging_split();
else
SelinuxSetupKernelLogging();
#else
SelinuxSetupKernelLogging();
#endif
SelabelInitialize();
/*
02.进行SELinux第二阶段并恢复一些文件安全上下文
恢复相关文件的安全上下文,因为这些文件是在SELinux安全机制初始化前创建的
所以需要重新恢复上下文
*/
SelinuxRestoreContext();/*
03.新建epoll并初始化子进程终止信号处理函数
创建epoll实例,并返回epoll的文件描述
*/
Epoll epoll;
if (auto result = epoll.Open(); !result.ok()) {
PLOG(FATAL) << result.error();
}#ifdef G1122717
// Watch properties with specific meanings to init.
LOG(INFO) << "Apply watching properties with specific meanings to init.";
ActionManager::GetInstance().StartWatchingProperty("sys.powerctl");
ActionManager::GetInstance().StartWatchingProperty("ro.persistent_properties.ready");
ActionManager::GetInstance().StartWatchingProperty(kColdBootDoneProp);
#endif
/*
主要是创建handler处理子进程终止信号,注册一个signal到epoll进行监听
进行子继承处理
*/
InstallSignalFdHandler(&epoll);
InstallInitNotifier(&epoll);//04.设置其他系统属性并开启系统属性服务
StartPropertyService(&property_fd);#if defined(MTK_LOG) && defined(MTK_COMMAND_WDOG)
ActionManager::GetInstance().StartCommandWDOG();
#endif// Make the time that init stages started available for bootstat to log.
RecordStageBoottimes(start_time);// Set libavb version for Framework-only OTA match in Treble build.
if (const char* avb_version = getenv("INIT_AVB_VERSION"); avb_version != nullptr) {
SetProperty("ro.boot.avb_version", avb_version);
}
unsetenv("INIT_AVB_VERSION");fs_mgr_vendor_overlay_mount_all();
export_oem_lock_status();
MountHandler mount_handler(&epoll);
SetUsbController();
#ifdef JOURNEY_FEATURE_SECURE
CheckJourneySecureMode();
#endifconst BuiltinFunctionMap& function_map = GetBuiltinFunctionMap();
Action::set_function_map(&function_map);if (!SetupMountNamespaces()) {
PLOG(FATAL) << "SetupMountNamespaces failed";
}//初始化文件上下文
InitializeSubcontext();ActionManager& am = ActionManager::GetInstance();
ServiceList& sm = ServiceList::GetInstance();
/*
05.解析init.rc等文件,建立rc文件的action、service,启动其他进程
*/
LoadBootScripts(am, sm);// Turning this on and letting the INFO logging be discarded adds 0.2s to
// Nexus 9 boot time, so it's disabled by default.
if (false) DumpState();// Make the GSI status available before scripts start running.
//当GSI脚本running时,确保GSI状态可用
auto is_running = android::gsi::IsGsiRunning() ? "1" : "0";
SetProperty(gsi::kGsiBootedProp, is_running);
auto is_installed = android::gsi::IsGsiInstalled() ? "1" : "0";
SetProperty(gsi::kGsiInstalledProp, is_installed);am.QueueBuiltinAction(SetupCgroupsAction, "SetupCgroups");
am.QueueBuiltinAction(SetKptrRestrictAction, "SetKptrRestrict");
am.QueueBuiltinAction(TestPerfEventSelinuxAction, "TestPerfEventSelinux");
//执行rc文件中触发器为 on early-init的语句
am.QueueEventTrigger("early-init");// Queue an action that waits for coldboot done so we know ueventd has set up all of /dev...
//等冷插拔设备初始化完成
am.QueueBuiltinAction(wait_for_coldboot_done_action, "wait_for_coldboot_done");
// ... so that we can start queuing up actions that require stuff from /dev.
am.QueueBuiltinAction(SetMmapRndBitsAction, "SetMmapRndBits");
//设备组合键的初始化操作
Keychords keychords;
am.QueueBuiltinAction(
[&epoll, &keychords](const BuiltinArguments& args) -> Result<void> {
for (const auto& svc : ServiceList::GetInstance()) {
keychords.Register(svc->keycodes());
}
keychords.Start(&epoll, HandleKeychord);
return {};
},
"KeychordInit");// Trigger all the boot actions to get us started.
//执行rc文件中触发器为on init的语句
am.QueueEventTrigger("init");// Don't mount filesystems or start core system services in charger mode.
/*
当设备处于充电模式时,不需要mount文件系统或者启动系统服务
充电模式下,将charger加入执行队列,否则把late-init加入执行队列
*/
std::string bootmode = GetProperty("ro.bootmode", "");
if (bootmode == "charger") {
am.QueueEventTrigger("charger");
} else {
am.QueueEventTrigger("late-init");
}// Run all property triggers based on current state of the properties.
//基于属性当前状态,运行所有的属性触发器
am.QueueBuiltinAction(queue_property_triggers_action, "queue_property_triggers");// Restore prio before main loop
setpriority(PRIO_PROCESS, 0, 0);
while (true) {
// By default, sleep until something happens.
auto epoll_timeout = std::optional<std::chrono::milliseconds>{};auto shutdown_command = shutdown_state.CheckShutdown();
if (shutdown_command) {
LOG(INFO) << "Got shutdown_command '" << *shutdown_command
<< "' Calling HandlePowerctlMessage()";
HandlePowerctlMessage(*shutdown_command);
shutdown_state.set_do_shutdown(false);
}//依次执行每个action中携带command对应的执行函数
if (!(prop_waiter_state.MightBeWaiting() || Service::is_exec_service_running())) {
am.ExecuteOneCommand();
}
if (!IsShuttingDown()) {
auto next_process_action_time = HandleProcessActions();// If there's a process that needs restarting, wake up in time for that.
if (next_process_action_time) {
epoll_timeout = std::chrono::ceil<std::chrono::milliseconds>(
*next_process_action_time - boot_clock::now());
if (*epoll_timeout < 0ms) epoll_timeout = 0ms;
}
}if (!(prop_waiter_state.MightBeWaiting() || Service::is_exec_service_running())) {
// If there's more work to do, wake up again immediately.
if (am.HasMoreCommands()) epoll_timeout = 0ms;
}#ifdef MTK_LOG
int log_ms = _LogReap();//PropSetLogReap();
if (log_ms > -1 && (!epoll_timeout || epoll_timeout->count() > log_ms))
epoll_timeout = std::chrono::milliseconds(log_ms);if (GetMTKLOGDISABLERATELIMIT()) {
if (!Getwhilepiggybacketed(1) && Getwhileepduration(1) > 1999)
LOG(INFO) << "Lastest epoll wait tooks " << Getwhileepduration(1) << "ms";
}android::base::Timer t;auto pending_functions = epoll.Wait(epoll_timeout);if (GetMTKLOGDISABLERATELIMIT()) {
uint64_t duration = t.duration().count();
uint64_t nowms = std::chrono::duration_cast<std::chrono::milliseconds>(boot_clock::now().time_since_epoch()).count();
Setwhiletime(1, duration, nowms);
}
#else
auto pending_functions = epoll.Wait(epoll_timeout);
#endif
if (!pending_functions.ok()) {
LOG(ERROR) << pending_functions.error();
} else if (!pending_functions->empty()) {
// We always reap children before responding to the other pending functions. This is to
// prevent a race where other daemons see that a service has exited and ask init to
// start it again via ctl.start before init has reaped it.
ReapAnyOutstandingChildren();
for (const auto& function : *pending_functions) {
(*function)();
}
}
if (!IsShuttingDown()) {
HandleControlMessages();
SetUsbController();
}
}return 0;
}

信号处理

init是一个守护进程,为了防止init的子进程成为僵尸进程(zombie process),需要init在子进程在结束时获取子进程的结束码,通过结束码将程序表中的子进程移除,防止成为僵尸进程的子进程占用程序表的空间(程序表的空间达到上限时,系统就不能再启动新的进程了,会引起严重的系统问题)。

信号处理主要工作:

  • 初始化信号signal句柄
  • 循环处理子进程
  • 注册epoll句柄
  • 处理子进程终止

注:EPOLL类似于POLL,是Linux中用来做事件触发的,跟EventBus功能差不多。linux很长的时间都在使用select来做事件触发,它是通过轮询来处理的,轮询的fd数目越多,自然耗时越多,对于大量的描述符处理,EPOLL更有优势

InstallSignalFdHandler

在linux当中,父进程是通过捕捉SIGCHLD信号来得知子进程运行结束的情况,SIGCHLD信号会在子进程终止的时候发出,了解这些背景后,我们来看看init进程如何处理这个信号。

  1. 新建一个sigaction结构体,sa_handler是信号处理函数,指向内核指定的函数指针SIG_DFL和Android 9.0及之前的版本不同,这里不再通过socket的读写句柄进行接收信号,改成了内核的信号处理函数SIG_DFL。
  2. sigaction(SIGCHLD, &act, nullptr) 这个是建立信号绑定关系,也就是说当监听到SIGCHLD信号时,由act这个sigaction结构体处理
  3. RegisterHandler 的作用就是signal_read_fd(之前的s[1])收到信号,触发handle_signal

终上所述,InstallSignalFdHandler函数的作用就是,接收到SIGCHLD信号时触发HandleSignalFd进行信号处理

信号处理示意图:

system/core/init/init.cpp

static void InstallSignalFdHandler(Epoll* epoll) {//SA_NOCLDSTOP使init进程只有在其进程终止时才会受到SIGCHLD信号const struct sigaction act { .sa_handler = SIG_DFL, .sa_flags = SA_NOCLDSTOP };sigaction(SIGCHLD, &act, nullptr);sigset_t mask;sigemptyset(&mask);sigaddset(&mask, SIGCHLD);if (!IsRebootCapable()) {//如果init不具备CAP_SYS_BOOT的能力,则它此时正值容器中运行//在这种场景下,接收SIGTERM将会导致系统关闭sigaddset(&mask, SIGTERM);}if (sigprocmask(SIG_BLOCK, &mask, nullptr) == -1) {PLOG(FATAL) << "failed to block signals";}//注册处理程序以解除对子进程中的信号的阻止const int result = pthread_atfork(nullptr, nullptr, &UnblockSignals);if (result != 0) {LOG(FATAL) << "Failed to register a fork handler: " << strerror(result);}//创建信号句柄signal_fd = signalfd(-1, &mask, SFD_CLOEXEC);if (signal_fd == -1) {PLOG(FATAL) << "failed to create signalfd";}//信号注册,当signal_fd收到信号时,触发HandlerSignalFdif (auto result = epoll->RegisterHandler(signal_fd, HandleSignalFd); !result.ok()) {LOG(FATAL) << result.error();}
}

RegisterHandler
说明:信号注册,把fd句柄加入到epoll_fd_的监听队列中
system/core/init/epoll.cpp

Result<void> Epoll::RegisterHandler(int fd, Handler handler, uint32_t events) {if (!events) {return Error() << "Must specify events";}auto sp = std::make_shared<decltype(handler)>(std::move(handler));auto [it, inserted] = epoll_handlers_.emplace(fd, std::move(sp));if (!inserted) {return Error() << "Cannot specify two epoll handlers for a given FD";}epoll_event ev;ev.events = events;// std::map's iterators do not get invalidated until erased, so we use the// pointer to the std::function in the map directly for epoll_ctl.ev.data.ptr = reinterpret_cast<void*>(&it->second);//将fd的可读事件加入到epoll_fd_的监听队列中if (epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, fd, &ev) == -1) {Result<void> result = ErrnoError() << "epoll_ctl failed to add fd";epoll_handlers_.erase(fd);return result;}return {};
}

HandlerSignalFd
说明:监控SIGCHLD信号,调用ReapAnyOutstadingChildren来终止出现问题的子进程
system/core/init/init.cpp

static void HandleSignalFd() {signalfd_siginfo siginfo;ssize_t bytes_read = TEMP_FAILURE_RETRY(read(signal_fd, &siginfo, sizeof(siginfo)));if (bytes_read != sizeof(siginfo)) {PLOG(ERROR) << "Failed to read siginfo from signal_fd";return;}//监控SIGCHLD信号switch (siginfo.ssi_signo) {case SIGCHLD:ReapAnyOutstandingChildren();break;case SIGTERM:HandleSigtermSignal(siginfo);break;default:PLOG(ERROR) << "signal_fd: received unexpected signal " << siginfo.ssi_signo;break;}
}

ReapAnyOutstandingChildren
system/core/init/sigchld_handler.cpp

void ReapAnyOutstandingChildren() {while (ReapOneProcess() != 0) {}
}

最终会调用到ReapOneProcess()方法
ReapOneProcess
说明:ReapOneProcess是最终的处理函数,这个函数先调用waitpid找出挂掉进程的pid,然后根据pid找到对应Service,最后调用Service的Reap方法清除资源,根据进程对应的类型,决定是否重启机器或重启进程
system/core/init/sigchld_handler.cpp

static pid_t ReapOneProcess() {siginfo_t siginfo = {};//用waitpid函数获取状态发生变化的子进程pid//waitpid的标记为WNOHANG,即非阻塞,返回为正值就说明有进程挂掉了if (TEMP_FAILURE_RETRY(waitid(P_ALL, 0, &siginfo, WEXITED | WNOHANG | WNOWAIT)) != 0) {PLOG(ERROR) << "waitid failed";return 0;}auto pid = siginfo.si_pid;if (pid == 0) return 0;//当我们知道当前有一个僵尸pid,我们使用scopeguard来清除该pidauto reaper = make_scope_guard([pid] { TEMP_FAILURE_RETRY(waitpid(pid, nullptr, WNOHANG)); });std::string name;std::string wait_string;Service* service = nullptr;if (SubcontextChildReap(pid)) {name = "Subcontext";} else {//通过该pid找到对应的serviceservice = ServiceList::GetInstance().FindService(pid, &Service::pid);if (service) {name = StringPrintf("Service '%s' (pid %d)", service->name().c_str(), pid);if (service->flags() & SVC_EXEC) {auto exec_duration = boot_clock::now() - service->time_started();auto exec_duration_ms =std::chrono::duration_cast<std::chrono::milliseconds>(exec_duration).count();wait_string = StringPrintf(" waiting took %f seconds", exec_duration_ms / 1000.0f);} else if (service->flags() & SVC_ONESHOT) {auto exec_duration = boot_clock::now() - service->time_started();auto exec_duration_ms =std::chrono::duration_cast<std::chrono::milliseconds>(exec_duration).count();wait_string = StringPrintf(" oneshot service took %f seconds in background",exec_duration_ms / 1000.0f);}} else {name = StringPrintf("Untracked pid %d", pid);}}if (siginfo.si_code == CLD_EXITED) {LOG(INFO) << name << " exited with status " << siginfo.si_status << wait_string;} else {LOG(INFO) << name << " received signal " << siginfo.si_status << wait_string;}//没有找到service,说明已经结束了,退出if (!service) return pid;//清除子进程相关的资源service->Reap(siginfo);if (service->flags() & SVC_TEMPORARY) {ServiceList::GetInstance().RemoveService(*service);//移除该service}return pid;
}

解析init.rc

当属性服务建立完成后,init的自身功能基本就告一段落,接下来需要来启动其他的进程。但是init进程如何其他其他进程呢?其他进程都是一个二进制文件,我们可以直接通过exec的命令方式来启动,例如 ./system/bin/init second_stage,来启动init进程的第二阶段。但是Android系统有那么多的Native进程,如果都通过传exec在代码中一个个的来执行进程,那无疑是一个灾难性的设计。
init.rc是一个配置文件,内部由Android初始化语言编写(Android Init Language)编写的脚本。类似通过读取配置文件的方式,来启动不同的进程。

LoadBootScripts

说明:如果没有特殊配置ro.boot.init_rc,则解析./init.rc
把/system/etc/init、/system_ext/etc/init、/odm/etc/init、/product/etc/init这几个路径加入init.rc之后解析的路径,在init.rc解析完成后,解析这些目录里面rc文件
注意:init.rc位于/system/core/rootdir下
system/core/init/init.cpp

static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list) {Parser parser = CreateParser(action_manager, service_list);std::string bootscript = GetProperty("ro.boot.init_rc", "");if (bootscript.empty()) {parser.ParseConfig("/system/etc/init/hw/init.rc");if (!parser.ParseConfig("/system/etc/init")) {late_import_paths.emplace_back("/system/etc/init");}// late_import is available only in Q and earlier release. As we don't// have system_ext in those versions, skip late_import for system_ext.parser.ParseConfig("/system_ext/etc/init");if (!parser.ParseConfig("/vendor/etc/init")) {late_import_paths.emplace_back("/vendor/etc/init");}if (!parser.ParseConfig("/odm/etc/init")) {late_import_paths.emplace_back("/odm/etc/init");}if (!parser.ParseConfig("/product/etc/init")) {late_import_paths.emplace_back("/product/etc/init");}} else {parser.ParseConfig(bootscript);}
}

Android7.0后,init.rc进行了拆分,每个服务都有自己的rc文件,他们基本上都被加载到/system/etc/init,/vendor/etc/init, /odm/etc/init等目录,等init.rc解析完成后,会来解析这些目录中的rc文件,用来执行相关的动作。

CreateParser
说明:创建Parser解析对象,例如service、on、import对象

Parser CreateParser(ActionManager& action_manager, ServiceList& service_list) {Parser parser;parser.AddSectionParser("service", std::make_unique<ServiceParser>(&service_list, GetSubcontext(), std::nullopt));parser.AddSectionParser("on", std::make_unique<ActionParser>(&action_manager, GetSubcontext()));parser.AddSectionParser("import", std::make_unique<ImportParser>(&parser));return parser;
}

执行Action动作
按顺序把相关Action加入触发器队列,按顺序为 early-init -> init -> late-init. 然后在循环中,执行所有触发器队列中Action带Command的执行函数。

am.QueueEventTrigger("early-init");
am.QueueEventTrigger("init");
am.QueueEventTrigger("late-init");
...
while (true) {
if (!(waiting_for_prop || Service::is_exec_service_running())) {am.ExecuteOneCommand();}
}

Zygote启动

从Android 5.0的版本开始,Android支持64位的编译,因此zygote本身也支持32位和64位。通过属性ro.zygote来控制不同版本的zygote进程启动。
在init.rc的import段我们看到如下代码:
system/core/rootdir/init.rc

import /system/etc/init/hw/init.${ro.zygote}.rc // 可以看出init.rc不再直接引入一个固定的文件,而是根据属性ro.zygote的内容来引入不同的文件

init.rc位于/system/core/rootdir下。在这个路径下还包括四个关于zygote的rc文件。
分别是init.zygote32.rc,init.zygote32_64.rc,init.zygote64.rc,init.zygote64_32.rc,由硬件决定调用哪个文件。
这里拿64位处理器为例,init.zygote64.rc的代码如下所示:
system/core/rootdir/init.zygote64.rc

service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server
class mainpriority -20user rootgroup root readproc reserved_disksocket zygote stream 660 root systemsocket usap_pool_primary stream 660 root systemonrestart exec_background - system system -- /system/bin/vdc volume abort_fuseonrestart write /sys/power/state ononrestart restart audioserveronrestart restart cameraserveronrestart restart mediaonrestart restart netdonrestart restart wificondwritepid /dev/cpuset/foreground/taskscritical window=${zygote.critical_window.minute:-off} target=zygote-fatal

service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server 解析:
service zygote :init.zygote64.rc 中定义了一个zygote服务。 init进程就是通过这个service名称来创建zygote进程
/system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server解析:
zygote这个服务,通过执行进行/system/bin/app_process64 并传入4个参数进行运行:

名称说明
参数1-Xzygote 该参数将作为虚拟机启动时所需的参数
参数2/system/bin 代表虚拟机程序所在目录
参数3–zygote 指明以ZygoteInit.java类中的main函数作为虚拟机执行入口
参数4–start-system-server 告诉Zygote进程启动systemServer进程

init总结

init进程主要分为两个阶段,
第一个阶段主要完成了:

  • 创建文件系统目录,并挂载了相关文件系统
  • 初始化了日志输出系统
  • 加载了SELinux(访问控制安全模块)安全策略
  • 进入第二阶段

第二阶段主要完成了

  • 初始化了属性系统
  • 执行了SELinux第二阶段,并恢复了一些文件的安全上下文
  • 新建了epoll,并初始化了子进程终止信号处理函数
  • 设置了系统其他属性,并开启了属性系统
  • 解析init.rc来启动其他进程

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/6938.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

jsPDF + html2canvas + Vue3 + ts项目内,分页导出当前页面为PDF、A 页面内导出 B 页面的内容为PDF,隐藏导出按钮等多余元素

jsPDF html2canvas Vue3 ts Arco Design项目&#xff0c;分页导出当前页面为PDF、A 页面内导出 B 页面的内容为PDF&#xff0c;隐藏导出按钮等多余元素… 1.下载所需依赖 pnpm install --save html2canvaspnpm install --save jspdf引入依赖 <script setup lang"…

OpenCV 库来捕获和处理视频输入和相似度测量(73)

返回:OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 上一篇:OpenCV的周期性噪声去除滤波器(70) 下一篇 :OpenCV系列文章目录&#xff08;持续更新中......&#xff09; ​ 目标 如今&#xff0c;拥有数字视频录制系统供您使用是很常见的。因此&#xff0c;您…

FreeBSD RISCV 在QEME中实践-网络配置

在前一篇文章中&#xff0c;我们一起进行了FreeBSD RISCV 在QEME中实践 现在&#xff0c;让我们配置好网络吧&#xff01; 先上结论&#xff1a;用默认配置启动即可&#xff0c;网络就加载好了&#xff0c;只是不能ping罢了。因为不能ping&#xff0c;以为网络没通&#xff0…

opencv图片的平移-------c++

图片平移 cv::Mat opencvTool::translateImage(const cv::Mat& img, int dx, int dy) {// 获取图像尺寸int rows img.rows;int cols img.cols;// 定义仿射变换矩阵cv::Mat M (cv::Mat_<float>(2, 3) << 1, 0, dx, 0, 1, dy);// 进行仿射变换cv::Mat dst;cv…

[附源码+视频教程]暗黑纪元H5手游_架设搭建_畅玩三网全通西方3D世界_带GM

本教程仅限学习使用&#xff0c;禁止商用&#xff0c;一切后果与本人无关&#xff0c;此声明具有法律效应&#xff01;&#xff01;&#xff01;&#xff01; 教程是本人亲自搭建成功的&#xff0c;绝对是完整可运行的&#xff0c;踩过的坑都给你们填上了 一. 演示视频 暗黑纪…

目标检测——水下垃圾数据集DeepTrash

引言 亲爱的读者们&#xff0c;您是否在寻找某个特定的数据集&#xff0c;用于研究或项目实践&#xff1f;欢迎您在评论区留言&#xff0c;或者通过公众号私信告诉我&#xff0c;您想要的数据集的类型主题。小编会竭尽全力为您寻找&#xff0c;并在找到后第一时间与您分享。 …

[图解]不变式的构造和化简

1 00:00:02,420 --> 00:00:03,380 下面这个&#xff0c;我们来看 2 00:00:03,390 --> 00:00:09,940 X→select&#xff08;Y&#xff09;&#xff0c;用Y这个条件来筛选 3 00:00:09,950 --> 00:00:11,340 之后得到的集合 4 00:00:12,400 --> 00:00:14,390 forAl…

SD-WAN介绍,为何成为主推。

1、SD-WAN&#xff08;Software Defined Wide Area Network&#xff0c; 软件定义的广域网&#xff09; 将企业的分支、总部和多云之间互联起来&#xff0c;应用在不同混合链路&#xff08;MPLS&#xff0c;Internet&#xff0c;5G&#xff0c;LTE等&#xff09;之间选择最优的…

EmotionBench—— 基于 LLM 情绪的量化框架

介绍 大型语言模型&#xff08;LLM&#xff09;在近年来取得了显著的进展&#xff0c;这在计算机科学领域被视为一个重要的里程碑。像ChatGPT和Claude这样的综合性软件已经不再仅仅是用于句子校正、文本翻译和编程的工具&#xff0c;它们已经演进成为类似于人类的助手。因此&a…

Linux 操作系统IPC

目录 1、IPC简介 1.1、共享内存 1.1.1 创建/访问共享内存 1.1.2 映射 1.1.3 解除映射 1.1.4 删除/修改共享内存 1.2 信号量集 1.2.1 创建信号量集合 1.2.2 信号量的初始化 1.2.3 信号量的还原和消耗 1.3 消息队列 1.3.1 概念 1.3.3 添加消息队列 1.3.4 读取消息…

社区养老服务|基于Springboot+vue的社区养老服务平台设计与实现(源码+数据库+文档)

社区养老服务平台 目录 基于Java的社区养老服务平台设计与实现 一、前言 二、系统设计 三、系统功能设计 1用户信息管理 2 服务信息管理 3服务申请管理 4公告信息管理 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#…

【Linux系统化学习】网络套接字(编写简单的UDP服务端和客户端)

目录 理解源IP地址和目的IP地址 认识端口号 端口号和进程ID的区别 源端口号和目的端口号 认识TCP和UDP协议 TCP协议 UDP协议 网络字节序 socket编程接口 socket常见API sockaddr结构 简单的UDP网络程序 UDP服务端 创建套接字 填充本地网络信息 绑定 收取消息 …

Amine-PEG-Amine,956496-54-1在生物成像、生物传感器等领域具有广泛的应用

【试剂详情】 英文名称 Amine-PEG-Amine&#xff0c;NH2-PEG-NH2 中文名称 氨基-聚乙二醇-氨基&#xff0c;氨基PEG氨基&#xff0c; 双端氨基聚乙二醇 CAS号 956496-54-1 外观性状 由分子量决定&#xff0c;液体或者固体 分子量 0.4k&#xff0c;0.6k&#xff0c;1k&…

千古一帝秦始皇的一生

在中国历史上&#xff0c;秦始皇绝对是最有资格被称之为‘千古一帝’的皇帝。 这不光是因为&#xff0c;他是中国的第一个皇帝&#xff0c;更是因为他奠定了中国两千多年的政治格局&#xff0c;让中国从此有了大一统的意识。 1、赵国生涯 公元前259年&#xff0c;秦始皇嬴政…

利用PS中Lab颜色模式进行简单调色?

【原图】 详细步骤如下&#xff1a; Step 1 : 打开PS&#xff0c;打开素材&#xff0c;点菜单栏&#xff0c;【图像】-【模式】-【Lab颜色】&#xff0c;效果如下图 Step2&#xff1a;ctrl(或command)m打开曲线工具&#xff0c;选择a通道&#xff0c;效果如下图。 Step3: 把标…

SpringBoot---------Swagger

第一步&#xff1a;引入依赖 <!-- swagger--><dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-spring-boot-starter</artifactId></dependency> 第二步&#xff1a;配置文件 import com.sky.intercept…

【刷题】代码随想录算法训练营第二十九天|491、递增子序列,46、全排列,47、全排列II

目录 491、递增子序列46、全排列47、全排列II 491、递增子序列 讲解&#xff1a;https://programmercarl.com/0491.%E9%80%92%E5%A2%9E%E5%AD%90%E5%BA%8F%E5%88%97.html class Solution { private:vector<vector<int>> result;vector<int> path;void backt…

pandas读取文件导致jupyter内核崩溃如何解决

读取execl文件出现以下问题: str_name "D:\\cao_use\\2017_2021(new).xlsx" train_df pd.read_excel(str_name, usecols[0])崩溃的指示图如下所示: bug原因:读入的文件太大&#xff0c;所需时间过长&#xff0c;在读取的过程中&#xff0c;使用中断按钮暂停会直…

OpenAI神秘模型,再次被Sam Altman提及

5月6日&#xff0c;OpenAI首席执行官Sam Altman在社交平台分享了一条推文“我是一个优秀的GPT-2聊天机器人”。 而在4月30日&#xff0c;Altman就提起过该模型非常喜欢GPT-2。按道理说一个只有15亿参数在2019年发布的开源模型&#xff0c;被反复提及两次就很不寻常。 更意外的…