init进程启动过程
首语
init进程是Android系统中用户空间的第一个进程,进程号为1,是Android系统启动的一个关键步骤,作为第一个进程,它的主要工作是创建Zygote和启动属性服务等。init进程是由多个源文件共同组成的,源码目录在system/core/init中。
源码分析
main(入口函数)
Linux内核加载完成后,在系统文件中寻找init.rc文件,并启动init进程。init进程的入口函数main。
源码路径:system/core/init/main.cpp
main函数根据参数分别执行:
ueventd_main
。init进程创建子进程ueventd,并将创建设备节点文件的工作交给veventd。veventd通过两种方式创建设备节点文件(冷启动和热启动)。FirstStageMain
。启动第一阶段。SubcontextMain
。初始化日志系统。SetupSelinux
。加载Selinux规则,并设置Selinux日志,完成Selinux相关工作。SecondStageMain
。启动第二阶段。
int main(int argc, char** argv) {
#if __has_feature(address_sanitizer)__asan_set_error_report_callback(AsanReportCallback);
#elif __has_feature(hwaddress_sanitizer)__hwasan_set_error_report_callback(AsanReportCallback);
#endif// Boost prio which will be restored latersetpriority(PRIO_PROCESS, 0, -20);//当arg[0]的内容是"ueventd",strcmp的值为0,!strcmp的值为1,执行ueventd_main。veventd主要进行设备节点的创建、权限设置工作if (!strcmp(basename(argv[0]), "ueventd")) {return ueventd_main(argc, argv);}//当传入的参数个数大于1if (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);}//参数为"second_stage",启动init进程第二阶段if (!strcmp(argv[1], "second_stage")) {return SecondStageMain(argc, argv);}}//默认启动init进程第一阶段return FirstStageMain(argc, argv);
}
ueventd(节点创建)
ueventd_main
负责节点创建。
源码路径:system/core/init/ueventd.cpp
ueventd进程通过两种方式创建设备节点文件:
- 冷启动。统一创建好的文件节点如cpu频率等。
- 热启动。动态创建的节点如usb插拔等。
int ueventd_main(int argc, char** argv) {//创建的文件没有权限限制umask(000);android::base::InitLogging(argv, &android::base::KernelLogger);LOG(INFO) << "ueventd started!";SelinuxSetupKernelLogging();SelabelInitialize();std::vector<std::unique_ptr<UeventHandler>> uevent_handlers;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)));//enable_modalias_handling=true,创建一个新的ModaliasHandler对象并添加到uevent_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);//kColdBootDoneProp=false,冷启动if (!android::base::GetBoolProperty(kColdBootDoneProp, false)) {ColdBoot cold_boot(uevent_listener, uevent_handlers,ueventd_configuration.enable_parallel_restorecon,ueventd_configuration.parallel_restorecon_dirs);cold_boot.Run();}for (auto& uevent_handler : uevent_handlers) {uevent_handler->ColdbootDone();}// We use waitpid() in ColdBoot, so we can't ignore SIGCHLD until now.signal(SIGCHLD, SIG_IGN);// Reap and pending children that exited between the last call to waitpid() and setting SIG_IGN// for SIGCHLD above.while (waitpid(-1, nullptr, WNOHANG) > 0) {}// Restore prio before main loop//监听驱动的热插拔处理setpriority(PRIO_PROCESS, 0, 0);uevent_listener.Poll([&uevent_handlers](const Uevent& uevent) {for (auto& uevent_handler : uevent_handlers) {uevent_handler->HandleUevent(uevent);}return ListenerAction::kContinue;});return 0;
}} // namespace init
} // namespace android
FirstStageMain(启动第一阶段)
init进程第一阶段主要进行挂载分区、创建设备节点和一些关键目录、初始化日志输出系统、启用Selinux安全策略。
源码路径:system/core/init/first_stage_init.cpp
挂载了tmpsfs、devpts、proc、sysfs和selinuxfs五种文件系统,这些都是系统运行时目录,系统停止时会消失。
挂载mnt和tmpfs,分别创建vendor和product目录。
CHECKCALL(mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755"));CHECKCALL(mount("devpts", "/dev/pts", "devpts", 0, NULL));CHECKCALL(mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC)));CHECKCALL(mount("sysfs", "/sys", "sysfs", 0, NULL));CHECKCALL(mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL));
启动init进程,根据main函数,传入参数selinux_setup,执行SetupSelinux函数。
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;
SetupSelinux(加载Selinux)
SetupSelinux主要做了初始化Selinux、加载Selinux并启动。
源码路径:system/core/init/selinux.cpp
SelinuxSetupKernelLogging();LOG(INFO) << "Opening SELinux policy";PrepareApexSepolicy();// Read the policy before potentially killing snapuserd.std::string policy;ReadPolicy(&policy);CleanupApexSepolicy();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);
传入参数second_stage,执行main函数,匹配参数执行SecondStageMain函数。
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;
SecondStageMain(启动第二阶段)
启动第二阶段主要做了初始化属性系统、解析Selinux的匹配路径、处理子进程终止信号、启动系统属性服务、解析init.rc文件等。
代码路径:system/core/init/init.cpp
int SecondStageMain(int argc, char** argv) { //初始化属性服务配置PropertyInit();...//子进程信号处理函数,如果子进程异常退出,init进程会调用该函数设定的信号处理函数进行处理InstallSignalFdHandler(&epoll);//启动属性服务StartPropertyService(&property_fd);...ActionManager& am = ActionManager::GetInstance();ServiceList& sm = ServiceList::GetInstance();//解析各个目录下的init.rcLoadBootScripts(am, sm);...//重启终止进程auto next_process_action_time = HandleProcessActions();
}static void InstallSignalFdHandler(Epoll* epoll) {...auto handler = std::bind(HandleSignalFd, false);...
}
static void HandleSignalFd(bool one_off) {...switch (siginfo.ssi_signo) {case SIGCHLD://找到终止进程服务并移除。system/core/init/sigchld_handler.cppReapAnyOutstandingChildren();break;...}
}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()) {//解析init.rc配置文件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);}
}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;
}
属性服务
Windows有一个注册表管理器,内容采用键值对来记录用户、软件使用信息。即使软件或系统重启,还能根据之前注册表的记录,进行相应的初始化工作,Android提供一个类似的机制,称为属性服务。
init进程启动属性服务并分配内存,存储这些属性,需要直接读取。
源码路径:system/core/init/property_service.cpp
void PropertyInit() {...CreateSerializedPropertyInfo();//__system_property_area_init() 初始化属性内存区域if (__system_property_area_init()) {LOG(FATAL) << "Failed to initialize property area";}if (!property_info_area.LoadDefaultPath()) {LOG(FATAL) << "Failed to load serialized property info file";}...
}void StartPropertyService(int* epoll_socket) {InitPropertySet("ro.property_service.version", "2");int sockets[2];if (socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0, sockets) != 0) {PLOG(FATAL) << "Failed to socketpair() between property_service and init";}*epoll_socket = from_init_socket = sockets[0];init_socket = sockets[1];StartSendingMessages();//创建非阻塞的socketif (auto result = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,false, 0666, 0, 0, {});result.ok()) {property_set_fd = *result;} else {LOG(FATAL) << "start_property_service socket creation failed: " << result.error();}//监听property_set_fd,socket变为server,成为属性服务。属性服务最多同时为8个设置属性的用户提供服务。listen(property_set_fd, 8);auto new_thread = std::thread{PropertyServiceThread};property_service_thread.swap(new_thread);
}static void PropertyServiceThread() {Epoll epoll;if (auto result = epoll.Open(); !result.ok()) {LOG(FATAL) << result.error();}//Epoll是Linux内核为处理大批量文件描述符而作的改进。它是多路复用IO接口select/poll的增强版本,能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。保存数据类型是红黑树,查找速度快。//epoll监听property_set_fd,当property_set_fd有数据时,调用handle_property_set_fd函数处理if (auto result = epoll.RegisterHandler(property_set_fd, handle_property_set_fd);!result.ok()) {LOG(FATAL) << result.error();}...
}static void handle_property_set_fd() {...switch (cmd) {case PROP_MSG_SETPROP: {...//封装处理uint32_t result =HandlePropertySet(prop_name, prop_value, source_context, cr, nullptr, &error);...break;}}
uint32_t HandlePropertySet(const std::string& name, const std::string& value,const std::string& source_context, const ucred& cr,SocketConnection* socket, std::string* error) {...//属性名称以"ctl."开头,为控制属性if (StartsWith(name, "ctl.")) {//设置控制属性return SendControlMessage(name.c_str() + 4, value, cr.pid, socket, error);}//特殊属性sys.powerctl,可使设备重新启动。// sys.powerctl is a special property that is used to make the device reboot. We want to log// any process that sets this property to be able to accurately blame the cause of a shutdown.if (name == "sys.powerctl") {...}...//普通属性return PropertySet(name, value, error);
}static uint32_t PropertySet(const std::string& name, const std::string& value, std::string* error) {...//判断属性是否合法,实现规则在sytem/core/init/util.cppif (!IsLegalPropertyName(name)) {*error = "Illegal property name";return PROP_ERROR_INVALID_NAME;}if (auto result = IsLegalPropertyValue(name, value); !result.ok()) {*error = result.error().message();return PROP_ERROR_INVALID_VALUE;}//从属性存储空间查找该属性prop_info* pi = (prop_info*) __system_property_find(name.c_str());//如果属性存在if (pi != nullptr) {//属性名称以"ro."开头,则表示只读,不能修改,直接返回// ro.* properties are actually "write-once".if (StartsWith(name, "ro.")) {*error = "Read-only property was already set";return PROP_ERROR_READ_ONLY_PROPERTY;}//属性存在,更新属性值__system_property_update(pi, value.c_str(), valuelen);} else {int rc = __system_property_add(name.c_str(), name.size(), value.c_str(), valuelen);//属性不存在,添加属性if (rc < 0) {*error = "__system_property_add failed";return PROP_ERROR_SET_FAILED;}}//属性名称以"persist."开头,可写。// Don't write properties to disk until after we have read all default// properties to prevent them from being overwritten by default values.if (persistent_properties_loaded && StartsWith(name, "persist.")) {WritePersistentProperty(name, value);}// If init hasn't started its main loop, then it won't be handling property changed messages// anyway, so there's no need to try to send them.auto lock = std::lock_guard{accept_messages_lock};if (accept_messages) {PropertyChanged(name, value);}return PROP_SUCCESS;
}
Android系统属性分为普通属性和控制属性两种。
普通属性是用于描述设备或系统的某些特定信息,例如手机厂商、型号等。这些属性通常以特定的字符串作为前缀,例如"ro"、“persist” 等。
控制属性是用于执行某些命令的属性,例如启动或关闭某个服务。这些属性通常以"ctl.“作为前缀,例如"ctl.start”、"ctl.stop"等。
通过adb setprop/getprop命令,可以在Android系统中查看和设置系统属性。
adb shell setprop [key] [value]
adb shell getprop [prop_name [default]]
Android系统属性配置文件主要是***/default.prop和/system/build.prop***两个文件。
/default.prop文件位于/default.prop,包含了一些默认的系统属性,如ro.sf.cpu_name、ro.product.model等。
/system/build.prop文件位于/system/build.prop,包含了系统的一些详细配置信息,如ro.product.name、ro.product.cpu_abi等。
子进程信号处理
InstallSignalFdHandler函数用于设置子进程信号处理函数。主要防止init进程的子进程成为僵尸进程,为了防止僵尸进程出现,系统会在子进程暂停和终止的时候发出SIGCHLD 信号,InstallSignalFdHandler函数就是用来接收SIGCHLD 信号的(内部只处理进程终止的SIGCHLD 信号)。
假设init进程的某个子进程终止了,InstallSignalFdHandler函数调用HandleSignalFd函数,层层调用处理,找到终止的子进程服务并移除它。再重启子进程服务的启动脚本中带有onrestart的服务。
僵尸进程
在Unix/Linux中,父进程fork创建子进程,在子进程终止后,如果父进程不知道子进程已经终止了,这时子进程虽然退出了,但是系统进程表还保留它的信息,这个子进程就被称为僵尸进程。系统进程表是有限资源,如果系统进程表被僵尸进程耗尽的话,就不能创建新的进程了。
这里着重说明下fork。fork用于创建一个子进程(复制调用fork进程的堆栈等信息),它与原进程(调用fork进程)同时运行,原进程称为父进程。fork不需要参数并返回一个返回值。
- 负值:创建子进程失败
- 零:返回到新创建的子进程
- 正值:返回父进程,该值包含创建子进程的进程ID
源码路径:system/core/init/sigchld_handler.cpp
void ReapAnyOutstandingChildren() {while (ReapOneProcess() != 0) {}
}static pid_t ReapOneProcess() {...//找到服务service = ServiceList::GetInstance().FindService(pid, &Service::pid);...//移除服务ServiceList::GetInstance().RemoveService(*service);...
}
解析init.rc
init.rc是Android初始化语言(Android Init Language)编写的脚本,这种语言主要包含五种类型语句:Action、Command、Service、Option和Import。
以system/core/rootdir/init.rc文件为例。
on initsysclktz 0
...
service ueventd /sbin/ueventdclass core
...
on init 是Action类型的语句,格式如下:
on <trigger> [&& <trigger>]* //设置触发器<command><command> //动作出发要执行的命令
service ueventd /sbin/ueventd是Service类型的语句,格式如下:
service <name> <pathname> [ <argument> ]* //<service name><执行程序路径><传递参数><option> //option是service的修饰词,影响什么时候,如何启动service<option>
每个服务对应一个rc文件,基本都被加载到/system/etc/init、/vendor/etc/init、/odm/etc/init、/product/etc/init等目录。
解析Service 类型语句
init.rc中的Action类型语句、Import类型语句和Service类型语句都有相应的文件来解析,如CreateParser函数中的ActionParser、ImportParser、ServiceParser。对应解析文件目录均在sytem/core/init下。
源码路径:system/core/init/service_parser.cpp
Result<void> ServiceParser::ParseSection(std::vector<std::string>&& args,const std::string& filename, int line) {//判断service是否有name和可执行程序if (args.size() < 3) {return Error() << "services must have a name and a program";}const std::string& name = args[1];//检查service的name是否有效if (!IsValidName(name)) {return Error() << "invalid service name '" << name << "'";}...//构造service对象,解析完所有数据后,调用EndSection函数service_ = std::make_unique<Service>(name, restart_action_subcontext, str_args, from_apex_);return {};
}Result<void> ServiceParser::EndSection() {...//构造Service list对象service_list_->AddService(std::move(service_));...
}
源码路径:system/core/init/service_list.cpp
void ServiceList::AddService(std::unique_ptr<Service> service) {//将services对象添加到Service链表中services_.emplace_back(std::move(service));
}
init启动Zygote
init进程会启动Zygote进程。我们分析下Zygote的启动脚本,以64位处理器为例。
脚本路径:system/core/rootdir/init.zygote64.rc
service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-serverclass 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 media.tuneronrestart restart netdonrestart restart wificondtask_profiles ProcessCapacityHighcritical window=${zygote.critical_window.minute:-off} target=zygote-fatal
上述脚本大致含义是创建名为zygote的进程,这个进程的执行程序路径为/system/bin/app_process64,后面的代码是传给app_process64的参数。class main
表示zygote的classname是main。
在解析init.rc时,有以下一段Action类型语句,脚本路径:system/core/rootdir/init.rc
on nonencryptedclass_start mainclass_start late_start
class_start是一个command,对应的函数为do_class_start, class_start main
表示启动classname为main的Service,刚才分析zygote的classname为main,因此它会启动zygote进程。
do_class_start函数在builtins.cpp中。源码路径:system/core/init/builtins.cpp
static Result<void> do_class_start(const BuiltinArguments& args) {// Do not start a class if it has a property persist.dont_start_class.CLASS set to 1.if (android::base::GetBoolProperty("persist.init.dont_start_class." + args[1], false))return {};// Starting a class does not start services which are explicitly disabled.// They must be started individually.for (const auto& service : ServiceList::GetInstance()) {if (service->classnames().count(args[1])) {if (auto result = service->StartIfNotDisabled(); !result.ok()) {LOG(ERROR) << "Could not start service '" << service->name()<< "' as part of class '" << args[1] << "': " << result.error();}}}return {};
}
遍历ServiceList,执行StartIfNotDisabled函数。
如果没有在对应的rc文件中设置disable选项,zygote对应的rc文件没有设置disable选项,则会调用Start函数。
子进程启动,并进入该Service的的main函数中,对于zygote来说,执行程序路径是/system/bin/app_process64,对应的文件为app_main.cpp,进入app_main.cpp的main函数中,也是zygote的main函数。
源码路径:system/core/init/service.cpp
Result<void> Service::StartIfNotDisabled() {if (!(flags_ & SVC_DISABLED)) {return Start();} else {flags_ |= SVC_DISABLED_START;}return {};
}Result<void> Service::Start() {...//service 运行,则不启动// Running processes require no additional work --- if they're in the// process of exiting, we've ensured that they will immediately restart// on exit, unless they are ONESHOT. For ONESHOT service, if it's in// stopping status, we just set SVC_RESTART flag so it will get restarted// in Reap().if (flags_ & SVC_RUNNING) {if ((flags_ & SVC_ONESHOT) && disabled) {flags_ |= SVC_RESTART;}LOG(INFO) << "service '" << name_<< "' requested start, but it is already running (flags: " << flags_ << ")";// It is not an error to try to start a service that is already running.reboot_on_failure.Disable();return {};}...//判断service对应的执行文件是否存在,不存在则不启动该servicestruct stat sb;if (stat(args_[0].c_str(), &sb) == -1) {flags_ |= SVC_DISABLED;return ErrnoError() << "Cannot find '" << args_[0] << "'";}...pid_t pid = -1;if (namespaces_.flags) {pid = clone(nullptr, nullptr, namespaces_.flags | SIGCHLD, nullptr);} else {//如果子进程没有启动,则调用fork函数创建子进程pid = fork();}//当前代码逻辑在子进程运行if (pid == 0) {umask(077);//子进程启动RunService(override_mount_namespace, descriptors, std::move(pipefd));_exit(127);}if (pid < 0) {pid_ = 0;return ErrnoError() << "Failed to fork";}...
}
zygote的main函数源码如下,源码路径:frameworks\base\cmds\app_process\app_main.cpp
int main(int argc, char* const argv[])
{...if (zygote) {//启动zygote,细节会在Zygote进程启动过程中分析runtime.start("com.android.internal.os.ZygoteInit", args, zygote);} else if (className) {runtime.start("com.android.internal.os.RuntimeInit", args, zygote);} else {fprintf(stderr, "Error: no class name or --zygote supplied.\n");app_usage();LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");}
}
init启动总结
init进程启动做了很多工作,总的来说有以下三点:
- 挂载分区、创建设备结点和一些关键目录、初始化日志输出系统、启用Selinux安全策略。
- 初始化属性系统、解析Selinux的匹配规则、启动属性服务。
- 解析init.rc配置文件并启动Zygote进程。
相关文章:

init进程启动过程
首语 init进程是Android系统中用户空间的第一个进程,进程号为1,是Android系统启动的一个关键步骤,作为第一个进程,它的主要工作是创建Zygote和启动属性服务等。init进程是由多个源文件共同组成的,源码目录在system/co…...

全网最详细的【shell脚本的入门】
🏅我是默,一个在CSDN分享笔记的博主。📚📚 🌟在这里,我要推荐给大家我的专栏《Linux》。🎯🎯 🚀无论你是编程小白,还是有一定基础的程序员,这…...

CH10_简化条件逻辑
分解条件表达式(Decompose Conditional) if (!aDate.isBefore(plan.summerStart) && !aDate.isAfter(plan.summerEnd))charge quantity * plan.summerRate; elsecharge quantity * plan.regularRate plan.regularServiceCharge;if (summer())…...

nn.LayerNorm解释
这个是层归一化。我们输入一个参数,这个参数就必须与最后一个维度对应。但是我们也可以输入多个维度,但是必须从后向前对应。 import torch import torch.nn as nna torch.rand((100,5)) c nn.LayerNorm([5]) print(c(a).shape)a torch.rand((100,5,…...

Springboot搭建微服务案例之Eureka注册中心
一、父工程依赖管理 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org…...

【MySQL】用户管理权限控制
文章目录 前言一. 用户管理1. 创建用户2. 删除用户3. 修改用户密码 二. 权限控制1. 用户授权2. 查看权限3. 回收权限 结束语 前言 MySQL的数据其实也以文件形式保存,而登录信息同样保存在文件中 MySQL的数据在Linux下默认路径是/var/lib/mysql 登录MySQL同样也可以…...

若依框架前后端分离版服务器部署,前端nginx的配置
server {listen 80;server_name 120.46.177.184;index index.php index.html index.htm default.php default.htm default.html;root /www/wwwroot/qilaike-vue/dist;#SSL-START SSL相关配置,请勿删除或修改下一行带注释的404规则#error_page 404/404.html;#SSL-END…...

基于单片机的滚筒洗衣机智能控制系统设计
收藏和点赞,您的关注是我创作的动力 文章目录 概要 一、系统整体设计方案2.1控制系统的功能2.2设计的主要内容 二、硬件设计3.1 控制系统整体框图3.2 电源电路 三 软件设计主程序设计仿真设计 四、 结论 概要 因此我们需要一个完善的智能系统来设计一个全自动滚筒洗…...

简述多模态学习中,对齐、融合和表示
在多模态学习中,对齐、融合和表示是三个核心概念,它们相互关联,共同支持多模态数据的处理和分析。 对齐(Alignment) 对齐是多模态学习中的一个关键步骤,它涉及到如何在不同的数据模态之间发现和建立对应关…...

Kotlin 进阶函数式编程技巧
Kotlin 进阶函数式编程技巧 Kotlin 简介 软件开发环境不断变化,要求开发人员不仅适应,更要进化。Kotlin 以其简洁的语法和强大的功能迅速成为许多人进化过程中的信赖伙伴。虽然 Kotlin 的初始吸引力可能是它的简洁语法和与 Java 的互操作性,…...

操作系统——内存映射文件(王道视频p57)
1.总体概述: 2.传统文件访问方式: 我认为,这种方式最大的劣势在于,如果要对整个文件的不同部分进行多次操作的话,这样确实开销可能会大一些,而且程序员还要指定对应的“分块”载入到内存中 3.内存映射文件…...

王道p18 07.将两个有序顺序表合并为一个新的有序顺序表,并由函数返回结果顺序表。(c语言代码实现)
视频讲解在这:👇 p18 第7题 c语言代码实现王道数据结构课后代码题_哔哩哔哩_bilibili 本题代码如下 int merge(struct sqlist* A, struct sqlist* B, struct sqlist* C) {if (A->length B->length > C->length)//大于顺序表的最大长度r…...

2024最新mac电脑清理垃圾的软件有哪些?
mac电脑是许多人喜爱的电子产品,它拥有优美的设计、流畅的操作系统和强大的性能。但是,随着使用时间的增长,mac电脑也会积累一些不必要的垃圾文件,这些文件会占用宝贵的存储空间,影响电脑的运行速度和稳定性。因此&…...

2023年【山东省安全员C证】考试技巧及山东省安全员C证模拟试题
题库来源:安全生产模拟考试一点通公众号小程序 山东省安全员C证考试技巧考前必练!安全生产模拟考试一点通每个月更新山东省安全员C证模拟试题题目及答案!多做几遍,其实通过山东省安全员C证模拟考试题很简单。 1、【多选题】《环境…...

2024最新免费的mac电脑清理垃圾的软件有哪些?
mac电脑是许多人喜爱的电子产品,它拥有优美的设计、流畅的操作系统和强大的性能。但是,随着使用时间的增长,mac电脑也会积累一些不必要的垃圾文件,这些文件会占用宝贵的存储空间,影响电脑的运行速度和稳定性。因此&…...

linux下sqlplus登录oracle显示问号处理办法
问题描述 昨天紧急通过rpm按安装方式给客户装了一台linux的19c数据库,操作系统是CentOs Stream release 9,过程不再回忆了… 今天应用发现sqlplus登入后部分显示问号?,需要处理下 原因分析: 很明显,这就是…...

Git 删除本地和远程分支
目录 删除本地和远程分支分支删除验证验证本地分支验证远程分支 开源项目微服务商城项目前后端分离项目 删除本地和远程分支 删除 youlai-mall 的 dev 本地和远程分支 # 删除本地 dev 分支(注:一定要切换到dev之外的分支才能删除,否则报错&…...

Selenium元素定位之页面检测技巧
在进行web自动化测试的时候进行XPath或者CSS定位,需要检测页面元素定位是否正确,如果用脚本去检测,那么效率是极低的。 一般网上推选装额外的插件来实现页面元素定位检测 如:firebug。 其实F12开发者工具就能直接在页面上检测元…...

C# 文件 文件夹 解除占用
文件/文件夹 解除占用或直接删除。 编程语言:C# 这个就不用过多功能描述了。 注册windows 文件/文件夹 右键菜单。 文件夹解除占用:遍历文件夹所有文件,判断是否被占用,先解除文件占用,后解除文件夹占用࿰…...

数据库 存储引擎
存储引擎概念 在mysql当中数据库用不同的技术存储在文件中,每一种技术都是使用不同的存储引擎机制,索引技巧,锁定水平,以及最终提供的不同的功能和能力,这些就是我们说的存储引擎 主要功能 1mysql将数据存储在文件系…...

操作系统复习(2)进程管理
一、概述 1.1程序的顺序执行 一个具有独立功能的程序独占CPU运行,直至得到最终结果的过程称为程序的顺序执行。 程序的并发执行所表现出的特性说明两个问题 ⑴ 程序和计算机执行程序的活动不再一一对应 ⑵ 并发程序间存在相互制约关系(要求共享信息&…...

通过51单片机控制28byj48步进电机按角度正反转旋转
一、前言 本项目基于STC89C52单片机,通过控制28BYJ-48步进电机实现按角度正反转旋转的功能。28BYJ-48步进电机是一种常用的电机,精准定位和高扭矩输出,适用于许多小型的自动化系统和机械装置。 在这个项目中,使用STC89C52单片机…...

二十三种设计模式全面解析-装饰器模式的高级应用:打造灵活可扩展的通知系统
在现代软件开发中,通知系统是一个广泛应用的功能,用于实时向用户发送各种类型的通知,如短信、微信、邮件以及系统通知。然而,通知系统的需求通常是多变且动态的,因此需要一种灵活可扩展的设计模式来满足不同类型的通知…...

使用脚本整合指定文件/文件夹,执行定制化 ESLint 命令
背景 最近面对一个庞大的项目,但是只需要修改某个模块,每次都手搓命令太麻烦了,于是就想着能不能写个脚本来辅助处理这些事情。 解决方案 定制化一键 ESLint,执行文件下载地址: https://github.com/mazeyqian/go-g…...

C++ static与类
C static与类 1. 不和对象直接相关的数据,声明为static2. static成员函数没有this指针3.在类的外部定义static成员变量4.static与类的一些小应用 1. 不和对象直接相关的数据,声明为static 想象有一个银行账户的类,每个人都可以开银行账户。存…...

数据结构之堆的实现(图解➕源代码)
一、堆的定义 首先明确堆是一种特殊的完全二叉树,分为大根堆和小根堆,接下来我们就分别介绍一下这两种不同的堆。 1.1 大根堆(简称:大堆) 在大堆里面:父节点的值 ≥ 孩子节点的值 我们的兄弟节点没有限制&…...

持续集成部署-k8s-配置与存储-配置管理:ConfigMap
持续集成部署-k8s-配置与存储-配置管理:ConfigMap 1. ConfigMap 简介2. 创建 ConfigMap3. ConfigMap 环境变量与配置文件加载3.1 环境变量的使用3.2 配置文件加载1. ConfigMap 简介 在Kubernetes (K8s) 中,ConfigMap是一种用于存储配置数据的API对象。它用于将应用程序的配置…...

【漏洞复现】Apache_HTTP_2.4.50_路径穿越漏洞(CVE-2021-42013)
感谢互联网提供分享知识与智慧,在法治的社会里,请遵守有关法律法规 文章目录 1.1、漏洞描述1.2、漏洞等级1.3、影响版本1.4、漏洞复现1、基础环境2、漏洞扫描3、漏洞验证方式一 curl方式二 bp抓捕 1.5、修复建议 说明内容漏洞编号CVE-2021-42013漏洞名称…...

【KVM】软件虚拟化和硬件虚拟化类型
前言 大家好,我是秋意零。 今天介绍的内容是虚拟化技术以及软件虚拟化和硬件虚拟化。 👿 简介 🏠 个人主页: 秋意零🔥 账号:全平台同名, 秋意零 账号创作者、 云社区 创建者🧑 个…...

ES-初识ES
文章目录 介绍ElasticSearchElasticSearch的主要功能ElasticSearch的主要特性ElasticSearch的家族成员LogStashKibanaBeats ELK(ElasticSearch LogStash Kibana)的应用场景与数据库集成指标采集/日志分析 安装和配置ElasticSearch一、安装1、下载ES安装…...