当前位置: 首页 > news >正文

Android系统启动全流程分析

当我们买了一个手机或者平板,按下电源键的那一刻,到进入Launcher,选择我们想要使用的某个App进入,这个过程中,系统到底在做了什么事,伙伴们有仔细的研究过吗?可能对于Framework这块晦涩难懂的专题比较枯燥,那么从这篇文章开始,将会对Framework相关的知识进行全面的剖析,先从系统启动流程开始。

1 系统启动流程分析

当我们打开电源键的时候,硬件执行的第一段代码就是BootLoader,会做一些初始化的操作,例如初始化CPU速度、内存等。然后会启动第一个进程idle进程(pid = 0),这个进程是在内核空间初始化的。

idle进程作为系统启动的第一个进程,它会创建两个进程,系统创建进程都是通过fork的形式完成,其中在Kernel空间会创建kthreadd进程,还有一个就是在用户空间创建init进程(pid = 1),这个进程想必我们都非常熟悉了。

像我们启动app,或者系统应用,都需要zygote进程来孵化进程,那么zygote进程也是通过init进程来创建完成的,像系统服务的创建和启动,是通过system_server进程来管理,而system_server进程则是由zygote进程fork创建。

所以通过下面这个图,我们就能大致了解,从电源按下的那一刻到应用启动的流程。

如需完整版Android 学习资料 请点击此处免费获取

在这里插入图片描述
接下来我们分析每个进程启动流程。

2 C/C++ Framework Native层

2.1 init进程启动分析

通过上面的流程图,我们知道init进程是通过内核空间启动的,所以我们看一下内核层的代码。

kernel_common/init/main.c

在内核层的main.c文件中,有一个静态方法kernel_init,这个方法会首先执行。

//
static int kernel_init(void *);
static int __ref kernel_init(void *unused)
{int ret;kernel_init_freeable();/* need to finish all async __init code before freeing the memory */async_synchronize_full();kprobe_free_init_mem();ftrace_free_init_mem();free_initmem();mark_readonly();/** Kernel mappings are now finalized - update the userspace page-table* to finalize PTI.*/pti_finalize();system_state = SYSTEM_RUNNING;numa_default_policy();rcu_end_inkernel_boot();//初始化文件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"))return 0;panic("No working init found.  Try passing init= option to kernel. ""See Linux Documentation/admin-guide/init.rst for guidance.");
}

在kernel_init方法中,我们看到会调用try_to_run_init_process函数去加载一些文件,例如我们需要关心的/bin/init,这个文件就是在设备system/bin/init下的。
在这里插入图片描述
init可以看做是一个模块,它与install、gzip等系统能力属于平级,都是通过系统源码编译过来的一种二进制文件,那么在这个文件加载的时候,具体执行的是哪些代码呢,这个需要我们看这个模块具体是怎么编译出来的,需要看下Android.bp文件。

cc_binary {name: "init_second_stage",recovery_available: true,stem: "init",defaults: ["init_defaults"],static_libs: ["libinit"],srcs: ["main.cpp"],symlinks: ["ueventd"],target: {platform: {required: ["init.rc","ueventd.rc","e2fsdroid","extra_free_kbytes","make_f2fs","mke2fs","sload_f2fs",],},recovery: {cflags: ["-DRECOVERY"],exclude_static_libs: ["libxml2",],exclude_shared_libs: ["libbinder","libutils",],required: ["init_recovery.rc","ueventd.rc.recovery","e2fsdroid.recovery","make_f2fs.recovery","mke2fs.recovery","sload_f2fs.recovery",],},},visibility: ["//packages/modules/Virtualization/microdroid"],
}

当系统编译init模块的时候,对应的srcs源码为main.cpp,也就是说系统system/bin/下的init模块入口函数为main.cpp,当kernel内核执行kernel_init函数的时候,其实就会执行init模块的main.cpp。

system/core/init/main.cpp

int main(int argc, char** argv) {
#if __has_feature(address_sanitizer)__asan_set_error_report_callback(AsanReportCallback);
#endifif (!strcmp(basename(argv[0]), "ueventd")) {return ueventd_main(argc, argv);}if (argc > 1) {if (!strcmp(argv[1], "subcontext")) {android::base::InitLogging(argv, &android::base::KernelLogger);const BuiltinFunctionMap function_map;return SubcontextMain(argc, argv, &function_map);}if (!strcmp(argv[1], "selinux_setup")) {return SetupSelinux(argv);}if (!strcmp(argv[1], "second_stage")) {return SecondStageMain(argc, argv);}}return FirstStageMain(argc, argv);
}

所有函数的入口都是main函数,所以看下main函数中做了什么事。首先我们看到当第一次进来时,会执行FirstStageMain这个函数,如果再次进入,此时就会走SecondStageMain。那么我们首先进入第一阶段,看系统做了什么事。

system/core/init/ first_stage_init.cpp

这个类中,我们找一些关键的代码来看一下,

int FirstStageMain(int argc, char** argv) {if (REBOOT_BOOTLOADER_ON_PANIC) {//核心代码1//init如果挂掉,就会重启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);//核心代码2CHECKCALL(clearenv());CHECKCALL(setenv("PATH", _PATH_DEFPATH, 1));// Get the basic filesystem setup we need put together in the initramdisk// on / and then we'll let the rc file figure out the rest.CHECKCALL(mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755"));CHECKCALL(mkdir("/dev/pts", 0755));CHECKCALL(mkdir("/dev/socket", 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// Don't expose the raw commandline to unprivileged processes.CHECKCALL(chmod("/proc/cmdline", 0440));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)));// This is needed for log wrapper, which gets called before ueventd runs.CHECKCALL(mknod("/dev/ptmx", S_IFCHR | 0666, makedev(5, 2)));CHECKCALL(mknod("/dev/null", S_IFCHR | 0666, makedev(1, 3)));// These below mounts are done in first stage init so that first stage mount can mount// subdirectories of /mnt/{vendor,product}/.  Other mounts, not required by first stage mount,// should be done in rc files.// Mount staging areas for devices managed by vold// See storage config details at http://source.android.com/devices/storage/CHECKCALL(mount("tmpfs", "/mnt", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,"mode=0755,uid=0,gid=1000"));// /mnt/vendor is used to mount vendor-specific partitions that can not be// part of the vendor partition, e.g. because they are mounted read-write.CHECKCALL(mkdir("/mnt/vendor", 0755));// /mnt/product is used to mount product-specific partitions that can not be// part of the product partition, e.g. because they are mounted read-write.CHECKCALL(mkdir("/mnt/product", 0755));// /apex is used to mount APEXesCHECKCALL(mount("tmpfs", "/apex", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,"mode=0755,uid=0,gid=0"));// /debug_ramdisk is used to preserve additional files from the debug ramdiskCHECKCALL(mount("tmpfs", "/debug_ramdisk", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,"mode=0755,uid=0,gid=0"));
#undef CHECKCALLSetStdioToDevNull(argv);// Now that tmpfs is mounted on /dev and we have /dev/kmsg, we can actually// talk to the outside world...//初始化日志模块InitKernelLogging(argv);//......const char* path = "/system/bin/init";const char* args[] = {path, "selinux_setup", nullptr};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;
}

2.2 init进程启动总结

到此init进程的主要任务就完成了,我们总结一下init进程主要干了什么事:

(1)init进程是由内核进程idle进程fork出来的,因此init进程初始化,也是由kernel启动的,即调用了kernel_int方法,此时会从系统的system/bin文件夹下查找init二进制文件;

(2)init二进制文件,是通过Android.bp脚本编译,从bp文件中可以看到,init关联的srcs为main.cpp,也就是system/core/init/main.cpp文件,其入口为main函数;

(3)当进入到main函数中时,首先会执行FirstStageMain函数,在这个函数中主要是:注册signal,挂载文件或者创建文件,进行一些初始化操作,然后再次进入到main函数中;

(4)此时进入main函数会执行SetupSeLinux,这里主要做linux的一些安全策略,然后会再次执行init的main函数;

(5)此时会执行SecondStageMain函数,在这个函数中,首先会初始化属性域,注册到enpoll中;然后解析init.rc文件,随后进入while循环,继续执行init.rc中的command指令。

3 Java Framework层

过了C/C++源码,真正到.java文件结尾的源码,就是Zygote进程,是由init进程fork出来的,也就是说Zygote才是Java进程的鼻祖。

3.1 init.rc文件

前面我们提到了,在SecondStageMain函数中,会进行init.rc文件的解析,那么init.rc到底是什么呢?你可以理解为就是一个脚本文件,只不过在脚本文件中,需要系统执行指令。

system/core/rootdir/init.rc

import /init.${ro.zygote}.rc# Mount filesystems and start core system services. 
on late-init//......# Now we can start zygote for devices with file based encryption trigger zygote-starton zygote-start && property:ro.crypto.state=unencrypted# A/B update verifier that marks a successful boot.exec_start update_verifier_nonencryptedstart netdstart zygotestart zygote_secondary

从init.rc文件中我们可以看到,当在SecondStageMain中解析init.rc文件的时候,就会启动Zygote进程,所以这个时候,才会真正进入到了Java的进程。

从脚本中看,start zygote最终会执行import进来的init.zygote.rc文件。

3.2 Zygote启动流程

在这里插入图片描述
所以当启动Zygote进程的时候,如果是32位的操作系统,那么就会解析init.zygote32.rc文件

service zygote /system/bin/app_process -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 write /sys/android_power/request_state wakeonrestart write /sys/power/state ononrestart restart audioserveronrestart restart cameraserveronrestart restart mediaonrestart restart netdonrestart restart wificondwritepid /dev/cpuset/foreground/tasks

对于.rc文件的语法,这里简单介绍一下,对于service命令,具体格式为:

service <name> <pathname> [args......]
name:服务的名称;
pathname:可执行的二进制文件路径,service的文件路径
args:要启动service所要带的参数

这里我们会看到启动Zygote服务进程,会执行/system/bin/app_process二进制文件,对于二进制文件是通过Android.bp来编译生成的,我们看下对应的文件。

cc_binary {name: "app_process",srcs: ["app_main.cpp"],multilib: {lib32: {suffix: "32",},lib64: {suffix: "64",},},
}

我们可以看到,对于app_process可执行文件,其函数入口为app_main.cpp文件,也就是在启动Zygote进程之后,就会进入到app_main.cpp。

3.3 native启动Zygote进程总结

至此,在native层的Zygote进程就已经启动完成了,我们来简单总结一下,当解析init.rc文件的时候,init进程就会fork出zygote进程。

此时系统执行init.rc中的脚本:执行start zygote时,会执行import进来的init.zygote.rc脚本,此时会根据系统版本,决定执行32位的脚本或者64位的脚本。当执行service zygote命令时,会执行系统system/bin下的二进制执行文件app_process,会进入到app_main.cpp中的main函数。

此时会调用AndroidRuntime的start方法执行ZygoteInit.java.main方法,在此之前会在native层创建VM虚拟机,并注册JNI函数保证C++和Java层之前的双向通信调用。

3.4 Java层的Zygote启动

通过前面我们知道,native层启动Zygote时,会调用Java层的ZygoteInit.java.main方法,我们看下这个类。

public static void main(String[] argv) {ZygoteServer zygoteServer = null;//......Runnable caller;try {// ......boolean startSystemServer = false;String zygoteSocketName = "zygote";String abiList = null;boolean enableLazyPreload = false;// 与nativ层一致,也是在根据传入的属性给一些状态位赋值for (int i = 1; i < argv.length; i++) {if ("start-system-server".equals(argv[i])) {startSystemServer = true;} else if ("--enable-lazy-preload".equals(argv[i])) {enableLazyPreload = true;} else if (argv[i].startsWith(ABI_LIST_ARG)) {abiList = argv[i].substring(ABI_LIST_ARG.length());} else if (argv[i].startsWith(SOCKET_NAME_ARG)) {zygoteSocketName = argv[i].substring(SOCKET_NAME_ARG.length());} else {throw new RuntimeException("Unknown command line argument: " + argv[i]);}}// .....// In some configurations, we avoid preloading resources and classes eagerly.// In such cases, we will preload things prior to our first fork.//核心代码 1if (!enableLazyPreload) {bootTimingsTraceLog.traceBegin("ZygotePreload");EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START,SystemClock.uptimeMillis());preload(bootTimingsTraceLog);EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END,SystemClock.uptimeMillis());bootTimingsTraceLog.traceEnd(); // ZygotePreload}// Do an initial gc to clean up after startupbootTimingsTraceLog.traceBegin("PostZygoteInitGC");gcAndFinalize();bootTimingsTraceLog.traceEnd(); // PostZygoteInitGCbootTimingsTraceLog.traceEnd(); // ZygoteInitZygote.initNativeState(isPrimaryZygote);ZygoteHooks.stopZygoteNoThreadCreation();//创建 Socket对象zygoteServer = new ZygoteServer(isPrimaryZygote);//核心代码 2if (startSystemServer) {Runnable r = forkSystemServer(abiList, zygoteSocketName, zygoteServer);// {@code r == null} in the parent (zygote) process, and {@code r != null} in the// child (system_server) process.if (r != null) {r.run();return;}}Log.i(TAG, "Accepting command socket connections");// The select loop returns early in the child process after a fork and// loops forever in the zygote.caller = zygoteServer.runSelectLoop(abiList);} catch (Throwable ex) {Log.e(TAG, "System zygote died with fatal exception", ex);throw ex;} finally {if (zygoteServer != null) {zygoteServer.closeServerSocket();}}// We're in the child process and have exited the select loop. Proceed to execute the// command.if (caller != null) {caller.run();}
}

在方法的开始,有一个ZygoteServer对象,它其实是一个Socket,用于与各个进程间通信;既然使用到进程间通信了,为什么不使用Binder呢?

不知有没有伙伴会考虑这个问题,为什么要使用Socket呢?例如AMS想要创建一个进程,那么就会通知Zygote来孵化出一个进程,此时创建进程就需要通过fork这种形式,其实相当于是做了一次进程copy,那么当前进程所有线程、对象都会被copy到新的进程,那么此时线程就不再拥有线程的特性而是一个对象,此时在子进程中如果调用线程的方法,那么是无效的;还有就是如果在父进程中,某个线程持有一把锁,那么在子进程中想要竞争这把锁对象,但是这把锁可能永远无法被释放,导致死锁的情况发生。

所以在Zygote进程中,如果使用Binder,因其内部是多线程组成的线程池,会有发生死锁的可能性,通过Socket进行进程间通信,也是为了避免这种情况的发生。

3.5 Java层启动Zygote进程总结

当fork出system_server进程之后,Java层的Zygote进程将会进入死循环,接收消息并执行,简单总结一下:

(1)当在native层创建JVM,并注册JNI函数之后,就会执行Zygote.java.main方法,进入到Java代码中;

(2)在main方法中,首先会解析传入的参数,给一些标志位赋值;然后会根据标志位进行判断是否支持预加载,预加载包括但不限于classes、resources,目的为了快速启动进程;

(3)在预加载完成之后(如有需要),那么就会创建Socket连接;然后调用forkSystemServer方法,fork system_server进程,最终调用的还是C++层的函数,调用系统的fork函数;

(4)随后会调用ZygoteServer(scoket)的runSelectLoop方法,开启死循环,socket服务端会接收客户端发送的消息进行处理,例如AMS想要创建一个进程。

相关文章:

Android系统启动全流程分析

当我们买了一个手机或者平板&#xff0c;按下电源键的那一刻&#xff0c;到进入Launcher&#xff0c;选择我们想要使用的某个App进入&#xff0c;这个过程中&#xff0c;系统到底在做了什么事&#xff0c;伙伴们有仔细的研究过吗&#xff1f;可能对于Framework这块晦涩难懂的专…...

RabbitMQ --- 惰性队列、MQ集群

一、惰性队列 1.1、消息堆积问题 当生产者发送消息的速度超过了消费者处理消息的速度&#xff0c;就会导致队列中的消息堆积&#xff0c;直到队列存储消息达到上限。之后发送的消息就会成为死信&#xff0c;可能会被丢弃&#xff0c;这就是消息堆积问题。 解决消息堆积有三种…...

1.Buffer_Overflow-1.Basic_Jump

github上面的练习题 git clone https://github.com/Adamkadaban/LearnPwn 然后开始做 先进行 readelf 然后进行执行看看 是怎么回事 ./buf1发现就是一个输入和输出 我们checksec看看 发现stack 保护关闭 开启了NX保护 我们进入ida64看看反汇编 我习惯先看看字符串 SHITF…...

MySQL入门语法第三课:表结构的创建

数据表结构 定点数类型decimal(m,d) m表示数字总位数 d表示小数位数 ★创建数据表先要选择数据库 1 . CREATE TABLE 表名称 创建数据表 (字段名1 数据类型1 [,字段名2 数据名2] [, .....] ); 一个字段写一行 修改表名 alter table 旧表名 rename 新表名…...

SpringSecurity框架学习与使用

SpringSecurity框架学习与使用 SpringSecurity学习SpringSecurity入门SpringSecurity深入认证授权自定义授权失败页面权限注解SecuredPreAuthorizePostAuthorizePostFilterPreFilter 参考 SpringSecurity学习 SpringSecurity入门 引入相关的依赖&#xff0c;SpringBoot的版本…...

DHCP+链路聚合+NAT+ACL小型实验

实验要求: 1.按照拓扑图上标识规划网络。 2.使用0SPF协议进程100实现ISP互通。 3.私网内PC属于VLAN1O, FTP Server属于VLAN2O,网关分 别为所连接的接入交换机&#xff0c;其中PC要求通过DHCP动态获取 4:私网内部所有交换机都为三层交换机&#xff0c;请合理规划VLAN&#…...

西瓜书读书笔记整理(三)—— 第二章 模型评估与选择

第二章 模型评估与选择 第 2 章 模型评估与选择2.1 经验误差与过拟合1. 错误率 / 精度 / 误差2. 训练误差 / 经验误差 / 泛化误差3. 过拟合 / 欠拟合4. 学习能力5. 模型选择 2.2 评估方法1. 评估方法概述2. 留出法3. 交叉验证法4. 自助法5. 调参 / 最终模型 2.3 性能度量1. 回归…...

AcWing算法提高课-1.3.6货币系统

宣传一下算法提高课整理 <— CSDN个人主页&#xff1a;更好的阅读体验 <— 本题链接&#xff08;AcWing&#xff09; 点这里 题目描述 给你一个n种面值的货币系统&#xff0c;求组成面值为m的货币有多少种方案。 输入格式 第一行&#xff0c;包含两个整数n和m。 接…...

vue3回到上一个路由页面

学习链接 Vue Router获取当前页面由哪个路由跳转 在Vue3的setup中如何使用this beforeRouteEnter 在这个路由方法中不能访问到组件实例this&#xff0c;但是可以使用next里面的vm访问到组件实例&#xff0c;并通过vm.$data获取组件实例上的data数据getCurrentInstance 是vue3提…...

Linux三种网络模式 | 仅主机、桥接、NAT

&#x1f497;wei_shuo的个人主页 &#x1f4ab;wei_shuo的学习社区 &#x1f310;Hello World &#xff01; Linux三种网络模式 仅主机模式&#xff1a;虚拟机只能访问物理机&#xff0c;不能上网 桥接模式&#xff1a;虚拟机和物理机连接同一网络&#xff0c;虚拟机和物理机…...

数据库设计与前端框架

数据库设计与前端框架 学习目标&#xff1a; 理解多租户的数据库设计方案 熟练使用PowerDesigner构建数据库模型理解前端工程的基本架构和执行流程 完成前端工程企业模块开发 多租户SaaS平台的数据库方案 多租户是什么 多租户技术&#xff08;Multi-TenancyTechnology&a…...

技术探秘:揭秘Bean Factory与FactoryBean的区别!

大家好&#xff0c;我是小米&#xff0c;一个热衷于技术分享的29岁小编。今天&#xff0c;我们来聊一聊在Spring框架中常用的两个概念&#xff1a;beanFactory和FactoryBean。它们虽然看似相似&#xff0c;但实际上有着不同的用途和作用。让我们一起来揭开它们的神秘面纱吧&…...

MD-MTSP:遗传算法GA求解多仓库多旅行商问题(提供MATLAB代码,可以修改旅行商个数及起点)

一、多仓库多旅行商问题 多旅行商问题&#xff08;Multiple Traveling Salesman Problem, MTSP&#xff09;是著名的旅行商问题&#xff08;Traveling Salesman Problem, TSP&#xff09;的延伸&#xff0c;多旅行商问题定义为&#xff1a;给定一个&#x1d45b;座城市的城市集…...

技术面试的终极指南:助你取得成功的关键步骤

背景 技术面试是许多求职者最关键的一环&#xff0c;因为它评估了你在特定领域的知识和技能。无论你是刚毕业的大学应届生&#xff0c;还是有多年工作经验的职场老兵&#xff0c;准备充分是成功面试的关键。 这篇文章将提供一系列关键步骤&#xff0c;帮助你充分准备和展现自己…...

Nautilus Chain 测试网第二阶段,推出忠诚度计划及广泛空投

随着更多的公链底层面向市场&#xff0c;通过参与早期测试在主网上线后获得激励成为了行业的一个热点话题&#xff0c;在 Apots、Arbitrum One、Optimism等陆续发放了测试空投后&#xff0c;以 Layer3为主要特性的 Nautilus Chain 也在前不久明确表示将会有空投&#xff0c;引发…...

Python爬虫(三):BeautifulSoup库

BeautifulSoup 是一个可以从 HTML 或 XML 文件中提取数据的 Python 库&#xff0c;它能够将 HTML 或 XML 转化为可定位的树形结构&#xff0c;并提供了导航、查找、修改功能&#xff0c;它会自动将输入文档转换为 Unicode 编码&#xff0c;输出文档转换为 UTF-8 编码。 Beauti…...

Python使用CV2库捕获、播放和保存摄像头视频

Python使用CV2库捕获、播放和保存摄像头视频 特别提示&#xff1a;CV2指的是OpenCV2&#xff08;Open Source Computer Vision Library&#xff09;&#xff0c;安装的时候是 opencv_python&#xff0c;但在导入的时候采用 import cv2。 若想使用cv2库必须先安装&#xff0c;P…...

[数据结构 -- C语言] 栈(Stack)

目录 1、栈 1.1 栈的概念及结构 2、栈的实现 2.1 接口 3、接口的实现 3.1 初始化 3.2 入栈/压栈 3.3 出栈 3.4 获取栈顶元素 3.5 获取栈中有效元素个数 3.6.1 bool 类型接口 3.6.2 int 类型接口 3.7 销毁栈 4、完整代码 5、功能测试 1、栈 1.1 栈的概念及结构 …...

【我的C++入门之旅】(上)

前言 C的发展史 1979年&#xff0c;贝尔实验室的Bjarne等人试图分析unix内核的时候&#xff0c;试图将内核模块化&#xff0c;但是发现C语言有很多的不足之处&#xff0c;于是在C语言的基础上进行扩展&#xff0c;增加了类的机制&#xff0c;完成了一个可以运行的预处理程序&…...

dcdc降压电路原理及仿真

在之前的文章 DCDC 降压芯片基本原理及选型主要参数介绍 中已经大致讲解了dcdc降压电路的工作原理&#xff0c;今天再结合仿真将buck电路工作过程讲一讲。 基本拓扑 上图为buck电路的基本拓扑结构&#xff0c;开关打到1&#xff0c;电感充电&#xff1b;开关打到0&#xff0c;…...

搭建Redis主从集群+哨兵+代理predixy

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、Redis是什么&#xff1f;二、搭建Redis集群步骤1.环境和版本2.Redis 安装部署3.主从同步配置4.哨兵模式配置5.代理predixy配置 总结 前言 提示&#xff1a…...

Syncthing文件同步 - 免费搭建开源的文件自动同步服务器并公网远程访问【私人云盘】

文章目录 1. 前言2. Syncthing网站搭建2.1 Syncthing下载和安装2.2 Syncthing网页测试2.3 注册安装cpolar内网穿透 3. 本地网页发布3.1 Cpolar云端设置3.2 Cpolar本地设置 4. 公网访问测试5. 结语 1. 前言 在数据爆炸的当下&#xff0c;每天都会产生海量的数据&#xff0c;这些…...

SQL——索引

&#x1f4a1; 索引 在关系型数据库中&#xff0c;索引是一种单独的、物理上的对数据库表中的一列或多列的值进行排序的一种存储结构&#xff0c;他是某个表中的一列或着若干列值的集合和相应的指向表中物理标识这些值的数据页的逻辑指针清单&#xff08;类似于图书目录&#x…...

Java代码组成部分

一、构造函数与默认构造函数 构造函数&#xff0c;是一种特殊方法。主要用来在创建对象时初始化对象&#xff0c;即为对象成员变量赋初始值&#xff0c;总与new运算符一起使用在创建对象的语句中。 /** * 矩形 */ class Rectangle {/*** 构造函数*/public Rectangle(int leng…...

vue2和vue3有啥区别,vue3的优点有哪些?

Vue.js 是一种流行的 JavaScript 框架&#xff0c;用于开发现代 Web 应用程序。Vue.js 具有简单易用、高效和灵活等特点&#xff0c;能够极大地提高开发效率并改进用户体验。Vue.js 一直在不断更新和改进&#xff0c;它的最新版本是 Vue 3。 在本文中&#xff0c;我们将探讨 V…...

就业内推 | 上市公司招网工,最高25k*14薪,六险一金

01 锐捷网络 招聘岗位&#xff1a;网络工程师 职责描述&#xff1a; 1、承接本产品线&#xff08;无线或数通&#xff09;所有咨询、故障、网络变更等业务&#xff0c;响应内外部客户的业务响应需求&#xff0c;需要值班。 2、同时作为产品线技术力的核心&#xff0c;需要负责…...

低代码让开发变得不再复杂

文章目录 前言低代码 VS 传统开发为什么选择IVX&#xff1f;平台比对总结 前言 在数字化的时代背景下&#xff0c;企业都面临巨大的数字化转型的挑战。为了应对这样的挑战&#xff0c;企业软件开发工具和平台也在不断革新和发展。低代码开发平台随之应运而生&#xff0c;成为了…...

【前端客栈】使用CSS实现畅销书排行榜页面

&#x1f4ec;&#x1f4eb;hello&#xff0c;各位小伙伴们&#xff0c;我是小浪。大家都知道&#xff0c;我最近是在更新各大厂的软件测试开发的面试真题&#xff0c;也是得到了很大的反馈和好评&#xff0c;几位小伙伴也是成功找到了测开的实习&#xff0c;非常不错。如果能前…...

【周末闲谈】超越ChatGPT?科大讯飞星火认知大模型

个人主页&#xff1a;【&#x1f60a;个人主页】 系列专栏&#xff1a;【❤️周末闲谈】 ✨第一周 二进制VS三进制 ✨第二周 文心一言&#xff0c;模仿还是超越&#xff1f; ✨第二周 畅想AR 文章目录 前言星火名字的由来科大讯飞星火落地应用演示赶超ChatGPT的底气在哪里?“硬…...

第N2周:中文文本分类-Pytorch实现

目录 一、前言二、准备工作三、数据预处理1.加载数据2.构建词典3.生成数据批次和迭代器 三、模型构建1. 搭建模型2. 初始化模型3. 定义训练与评估函数 四、训练模型1. 拆分数据集并运行模型 一、前言 &#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客 …...