Android 11(R)启动流程 初版
启动流程
bootloader会去启动android第一个进程Idle,pid为0,会对进程 内存管理等进行初始化。Idle还被称作swapper。Idle会去创建两个进程,一个是init,另外一个是kthread。 kthread会去启动内核,用户是由init进行启动。
init进程的启动
在内核common/init/main.c 的kernel_init函数中
if (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"))return 0;
可以看到这里启动了init进程。并且这个bin是在根目录下的。
static int run_init_process(const char *init_filename)
{argv_init[0] = init_filename;pr_info("Run %s as init process\n", init_filename);return do_execve(getname_kernel(init_filename),(const char __user *const __user *)argv_init,(const char __user *const __user *)envp_init);
}
do_execve
是一个内核函数,用于执行用户空间的程序,最后会调用到__do_execve_file函数
/* 参数解释
int fd:文件描述符,用于指定要执行的文件。如果不使用文件描述符,可以传入 AT_FDCWD。
struct filename *filename:文件名结构体,包含要执行的可执行文件的路径。
struct user_arg_ptr argv:参数指针结构体,指向传递给新进程的命令行参数。
struct user_arg_ptr envp:环境变量指针结构体,指向传递给新进程的环境变量。
int flags:标志位,用于控制执行行为。
struct file *file:文件结构体指针,指向要执行的文件。如果不通过文件描述符打开文件,可以传入 NULL。*/static int __do_execve_file(int fd, struct filename *filename,struct user_arg_ptr argv,struct user_arg_ptr envp,int flags, struct file *file)
{char *pathbuf = NULL;struct linux_binprm *bprm;struct files_struct *displaced;int retval;//检查 filename 是否为错误指针,如果是,则返回相应的错误码。if (IS_ERR(filename))return PTR_ERR(filename);/** We move the actual failure in case of RLIMIT_NPROC excess from* set*uid() to execve() because too many poorly written programs* don't check setuid() return code. Here we additionally recheck* whether NPROC limit is still exceeded.*///检查当前用户的进程数是否超过了限制,如果超过了限制,则返回 -EAGAIN 错误码。//清除进程标志 PF_NPROC_EXCEEDED。if ((current->flags & PF_NPROC_EXCEEDED) &&atomic_read(¤t_user()->processes) > rlimit(RLIMIT_NPROC)) {retval = -EAGAIN;goto out_ret;}/* We're below the limit (still or again), so we don't want to make* further execve() calls fail. */current->flags &= ~PF_NPROC_EXCEEDED;//调用 unshare_files 函数分离文件结构,以确保进程在执行期间独占文件描述符表retval = unshare_files(&displaced);if (retval)goto out_ret;retval = -ENOMEM;//分配并初始化 linux_binprm 结构体,该结构体用于存储进程执行所需的各种信息bprm = kzalloc(sizeof(*bprm), GFP_KERNEL);if (!bprm)goto out_files;retval = prepare_bprm_creds(bprm);if (retval)goto out_free;//调用 check_unsafe_exec 函数进行安全检查check_unsafe_exec(bprm);current->in_execve = 1;//如果没有提供文件指针,则使用 do_open_execat 函数根据文件描述符和文件名打开文件if (!file)file = do_open_execat(fd, filename, flags);retval = PTR_ERR(file);if (IS_ERR(file))goto out_unmark;/*准备执行环境:调用 sched_exec 函数设置调度相关信息。初始化 bprm 结构体中的文件和文件名信息。调用 bprm_mm_init 函数初始化进程的内存管理。调用 prepare_arg_pages 函数准备参数和环境变量的内存页。调用 prepare_binprm 函数准备可执行文件的二进制参数。*/sched_exec();bprm->file = file;if (!filename) {bprm->filename = "none";} else if (fd == AT_FDCWD || filename->name[0] == '/') {bprm->filename = filename->name;} else {if (filename->name[0] == '\0')pathbuf = kasprintf(GFP_KERNEL, "/dev/fd/%d", fd);elsepathbuf = kasprintf(GFP_KERNEL, "/dev/fd/%d/%s",fd, filename->name);if (!pathbuf) {retval = -ENOMEM;goto out_unmark;}/** Record that a name derived from an O_CLOEXEC fd will be* inaccessible after exec. Relies on having exclusive access to* current->files (due to unshare_files above).*/if (close_on_exec(fd, rcu_dereference_raw(current->files->fdt)))bprm->interp_flags |= BINPRM_FLAGS_PATH_INACCESSIBLE;bprm->filename = pathbuf;}bprm->interp = bprm->filename;retval = bprm_mm_init(bprm);if (retval)goto out_unmark;retval = prepare_arg_pages(bprm, argv, envp);if (retval < 0)goto out;retval = prepare_binprm(bprm);if (retval < 0)goto out;//复制参数和环境变量://调用 copy_strings_kernel 函数复制文件名到用户栈。//调用 copy_strings 函数依次复制环境变量和命令行参数到用户栈retval = copy_strings_kernel(1, &bprm->filename, bprm);if (retval < 0)goto out;bprm->exec = bprm->p;retval = copy_strings(bprm->envc, envp, bprm);if (retval < 0)goto out;retval = copy_strings(bprm->argc, argv, bprm);if (retval < 0)goto out;/** When argv is empty, add an empty string ("") as argv[0] to* ensure confused userspace programs that start processing* from argv[1] won't end up walking envp. See also* bprm_stack_limits().*/if (bprm->argc == 0) {const char *argv[] = { "", NULL };retval = copy_strings_kernel(1, argv, bprm);if (retval < 0)goto out;bprm->argc = 1;}//调用 exec_binprm 函数执行可执行文件retval = exec_binprm(bprm);if (retval < 0)goto out;//如果执行成功,清理各种临时数据结构,释放资源,并返回成功状态。//如果执行失败,进行错误处理,清理资源,并返回相应的错误码。/* execve succeeded */current->fs->in_exec = 0;current->in_execve = 0;rseq_execve(current);acct_update_integrals(current);task_numa_free(current, false);free_bprm(bprm);kfree(pathbuf);if (filename)putname(filename);if (displaced)put_files_struct(displaced);return retval;out:if (bprm->mm) {acct_arg_size(bprm, 0);mmput(bprm->mm);}out_unmark:current->fs->in_exec = 0;current->in_execve = 0;out_free:free_bprm(bprm);kfree(pathbuf);out_files:if (displaced)reset_files_struct(displaced);
out_ret:if (filename)putname(filename);return retval;
}
接下来就正式进入init的启动流程,init代码路径:system/core/init/main.cpp main函数代码较短,直接贴上来。
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 = GetBuiltinFunctionMap();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);
}
FirstStageMain
在FirstStageMain中,主要是挂载文件目录,创建一些文件,做一些初始化等的阶段。
还包括初始化日志系统,将输入输出重定向。
最后,会去启动init 并且带一个selinux_setup
SelinuxInitialize
在SelinuxInitialize中会初始化一些安全策略
selinux_setup之后会去second_stage
SecondStageMain
PropertyInit(); 初始化属性域selinux初始化
// Now set up SELinux for second stage.
SelinuxSetupKernelLogging();
SelabelInitialize();
SelinuxRestoreContext();//恢复安全上下文//处理子进程的终止信号,判断子进程有没有挂掉 (僵尸进程) 回收资源。
InstallSignalFdHandler(&epoll);
InstallInitNotifier(&epoll);
StartPropertyService(&property_fd);//匹配命令和函数之间的关系,ls 等命令和函数的关系
const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap();
/********************************/
//解析init.rc
LoadBootScripts(am, sm);
/********************************/
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("/product/etc/init")) {late_import_paths.emplace_back("/product/etc/init");}if (!parser.ParseConfig("/odm/etc/init")) {late_import_paths.emplace_back("/odm/etc/init");}if (!parser.ParseConfig("/vendor/etc/init")) {late_import_paths.emplace_back("/vendor/etc/init");}} else {parser.ParseConfig(bootscript);}
}
在LoadBootScripts中会去创建三种对应解释器
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;
}
init处理的重要事情
1.挂载文件
2.设置selinux
3.开启属性服务
4.解析init.rc
5.循环处理脚本 -》 启动zygote
6.循环等待
init.rc -》zygote
# Now we can start zygote for devices with file based encryption
trigger zygote-start
这段 init.rc
脚本配置了在不同加密状态下启动 zygote
及其相关服务的逻辑。根据设备的加密状态(未加密、不支持加密或已加密)
init.rc解析时会导入import /system/etc/init/hw/init.${ro.zygote}.rc
这里大括号{}中的ro.zygote表示它会用 ro.zygote
系统属性的值来替换 ${ro.zygote}
。
所以这里是 import /system/etc/init/hw/init.zygote32.rc
init.zygote32.rc类似的文件有四个,对应32位系统,64位系统,还有主32次64或者主64的。这种会在32执行失败之后再执行64位的。
启动一个服务 zygote 后面是路径, 在后面是参数
zygote的作用之一是要进入java层,为Android启动运行时环境。
AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv)); -》class AppRuntime : public AndroidRuntime
app_main.cpp中 runtime.start("com.android.internal.os.ZygoteInit", args, zygote);去启动android运行时环境。
app_main.cpp runtime.start -》
AndroidRuntime.cpp AndroidRuntime::start -》
startVm 启动虚拟机-》
startReg 注册jni-》
env->CallStaticVoidMethod(startClass, startMeth, strArray);
启动com.android.internal.os.ZygoteInit -》ZygoteInit.java . main
AndroidRuntime 中包括对虚拟机一系列的初始化,这里包括heapsize的初始化为16M,
进程和虚拟机是什么关系
虚拟机实现了进程中内存管理的功能
注册jni
startReg-》
register_jni_procs-》
register_com_android_internal_os_RuntimeInit (env)
register_com_android_internal_os_ZygoteInit_nativeZygoteInit(env)
register_com_android_internal_os_......
Zygote的java启动
runtime.start("com.android.internal.os.ZygoteInit", args, zygote); -》
env->CallStaticVoidMethod(startClass, startMeth, strArray);-》
ZygoteInit.java main-》
preload(bootTimingsTraceLog);//加快进程的启动-》
zygoteServer = new ZygoteServer(isPrimaryZygote);-》
Runnable r = forkSystemServer(abiList, zygoteSocketName, zygoteServer);// 启动systemserver进程-》
caller = zygoteServer.runSelectLoop(abiList);进入loop死循环,接收AMS传过来的消息
preload加载的时间
Zygote总结:
native:
1.初始化运行环境,创建jvm
2.注册jni
3.调用zygoteinit.main
java
1.预加载 -- 加快进程启动
2.socket 服务器
3.循环等待
Zygote fork SystemServer进程
SystemServer的主要工作是管理服务,AMS WMS都和SystemServer属于同一进程,这些服务都是在SystemServer运行起来的。
ZygoteInit.java
-》
Runnable r = forkSystemServer(abiList, socketName, zygoteServer);
-》
Zygote.forkSystemServer-》
Zygote.java nativeForkSystemServer
-》
com_android_internal_os_Zygote.cpp com_android_internal_os_Zygote_nativeForkSystemServer
-》
ForkAndSpecializeCommon-》
fork(); -》
pid == 0 handleSystemServerProcess-》
ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs, cl);
-》
RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader);
-》
RuntimeInit.commonInit();//初始化运行环境
-》
ZygoteInit.nativeZygoteInit //启动binder,方法在androidRuntime.cpp中注册
-》
RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader);//ActivityThread.main();
-》
findStaticMain //app启动流程也是走这里,因此这里不仅返回AMS通过socket传过来的ActivityThread、还有systemserver
-》cl.getMethod("main", new Class[] { String[].class });//通过反射拿到对应类的main方法的Method对象
-》MethodAndArgsCaller implements Runnable 封装成Runnable对象
-》
mMethod.invoke(null, new Object[] { mArgs }); 执行run时通过invoke函数执行类所对应的main函数
-》r.run();
SystemServer基本流程
main-》new SystemServer().run();-》
startBootstrapServices
startCoreServices
startOtherServices -》
createSystemContext //创建系统上下文
SystemServer如何管理服务
systemserver基本都通过mSystemServiceManager.startService 来启动服务,可以看出SystemServer是通过SystemServiceManager来管理service的,服务都必须封装systemservice类,
mActivityManagerService = mSystemServiceManager.startService(ActivityManagerService.Lifecycle.class).getService();
mActivityManagerService = mSystemServiceManager.startService(ActivityManagerService.Lifecycle.class).getService();
public class ActivityManagerService extends IActivityManager.Stub
implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {
..............} 从这里看出ActivityManagerService 是一个binder。但是因为只能有一个父类,为了能够实现继承自systemservice的效果,ActivityManagerService 实现了一个静态内部类Lifecycle拓展自SystemService。
public static final class Lifecycle extends SystemService {.......}
public Lifecycle(Context context) {super(context);mService = new ActivityManagerService(context);
}
并且mSystemServiceManager.startService传递的是类名,可以通过反射创建实例,通过实例调用service.onStart();可以调用到继承自SystemService 的Lifecycle里面的onStart函数。
public <T extends SystemService> T startService(Class<T> serviceClass) {}
在通过
ServiceManager.addService(Context.ACTIVITY_SERVICE, this, /* allowIsolated= */ true, DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL | DUMP_FLAG_PROTO);
或者
publishBinderService(Context.USER_SERVICE, mUms); 将服务注册到servicemanager里面去,尽管publishBinderService最后也是调用ServiceManager.addService函数。
相关文章:

Android 11(R)启动流程 初版
启动流程 bootloader会去启动android第一个进程Idle,pid为0,会对进程 内存管理等进行初始化。Idle还被称作swapper。Idle会去创建两个进程,一个是init,另外一个是kthread。 kthread会去启动内核,用户是由init进行启动。…...

从零安装pytorch
背景介绍 目前主流使用的工具有Facebook搞的pythorch和谷歌开发的tensorflow两种,二者在实现理念上有一定区别,pytorch和人的思维模式与变成习惯更像,而tensorflow则是先构建整体结构,然后整体运行,开发调试过程较为繁…...

2024.07.28 校招 实习 内推 面经
绿*泡*泡VX: neituijunsir 交流*裙 ,内推/实习/校招汇总表格 1、自动驾驶一周资讯 - 特斯拉FSD年底入华?理想成立“端到端”实体组织;小马智行或最快于今年9月赴美IPO 自动驾驶一周资讯 - 特斯拉FSD年底入华?理想…...

python实现小游戏——植物大战僵尸(魔改版本)
制作一款DIY的‘植物大战僵尸’游戏引起了很多人的兴趣。在这里,我将分享一个使用Python语言在PyCharm环境中开发的初始状态版本。这个版本主要应用了pygame库来完成,是一个充满创意和趣味的魔改版本。 文章目录 前言一、开发环境准备二、代码1.main方法…...

基于K210智能人脸识别+车牌识别系统(完整工程资料源码)
运行效果: 基于K210的智能人脸与车牌识别系统工程 目录: 运行效果: 目录: 前言: 一、国内外研究现状与发展趋势 二、相关技术基础 2.1 人脸识别技术 2.2 车牌识别技术 三、智能小区门禁系统设计 3.1 系统设计方案 3.2 …...

8.怎么配嵌套子路由,以及它的作用
作用 配嵌套子路由,就是可以通过同一个页面,让不同的位置发生变化,其他的位置不会发生变化,而做到一个局部刷新 例子 红线框住的部分,头部和导航栏是不会发生变化的,变化的只有中间的内容 子路由的操作步骤 将这个页面的头部和导航栏部分的样式和风格,移到主路由上(<tem…...

【海贼王航海日志:前端技术探索】HTML你学会了吗?(二)
目录 1 -> HTML常见标签 1.1 -> 表格标签 1.1.1 -> 基本使用 1.1.2 -> 合并单元格 1.2 -> 列表标签 1.3 -> 表单标签 1.3.1 -> form标签 1.3.2 -> input标签 1.4 -> label标签 1.5 -> select标签 1.6 -> textarea标签 1.7 -> …...

体系结构论文导读(三十一)(下):Soft errors in DNN accelerators: A comprehensive review
第五部分:DNN加速器中的软错误 本部分回顾和分析了有关人工神经网络(ANN)可靠性的研究。特别是关注通过DNN加速器解决DNN可靠性的研究,从软错误的角度进行探讨。许多前期工作声称ANN本身对故障具有固有的容错能力。然而ÿ…...

Python在指定文件夹下创建虚拟环境
基于不同python版本和第三方包版本开发的项目,为了方便学习和管理python环境,可以在指定的文件夹里创建项目所需的虚拟环境。具体流程如下: (1) 以管理员身份打开Ananconda Prompt,查看当前虚拟环境,输入命令如下&…...

【SpringBoot】 定时任务之任务执行和调度及使用指南
【SpringBoot】 定时任务之任务执行和调度及使用指南 Spring框架分别通过TaskExecutor和TaskScheduler接口为任务的异步执行和调度提供了抽象。Spring还提供了支持应用程序服务器环境中的线程池或CommonJ委托的那些接口的实现。最终,在公共接口后面使用这些实现&…...

理解 Objective-C 中 +load 方法的执行顺序
在 Objective-C 中,load 方法是在类或分类(category)被加载到内存时调用的。它的执行顺序非常严格,并且在应用启动过程中可能会导致一些令人困惑的行为。理解 load 方法的执行顺序对调试和控制应用的初始化过程非常重要。 load 方…...

切面条问题算法的详解
切面条问题是一个经典的动态规划问题,也称为切钢条问题。问题描述为:给定一根长度为n的钢条和一个价格表P[i],表示长度为i的钢条的价格。求解如何切割钢条使得收益最大。 解决这个问题的关键是找到一个最优子结构和递推关系。 首先…...

JNDI注入
🎼个人主页:金灰 😎作者简介:一名简单的大一学生;易编橙终身成长社群的嘉宾.✨ 专注网络空间安全服务,期待与您的交流分享~ 感谢您的点赞、关注、评论、收藏、是对我最大的认可和支持!❤️ 🍊易编橙终身成长社群&#…...

SQL Server数据库文件过大而无法直接导出解决方案
目录 1. 使用分割备份 (Split Backup) 2. 使用文件和文件组备份 (File and Filegroup Backup) 3. 使用压缩备份 (Compressed Backup) 4. 逻辑备份 (BCP工具) 5. 使用导出工具 (SQL Server Management Studio) 6. 部分备份 (Partial Backup) 7. 使用第三方工具 1. 使用分割…...

学习日志8.4--DHCP攻击防范
目录 DHCP饿死攻击 DHCP Sever仿冒攻击 DHCP攻击防范 DHCP动态主机配置协议,是给主机提供自动获取IP地址等配置信息的服务。在主机对DHCP服务器发送DHCP Discover请求之后,服务器回复offer,主机再回复request,最后服务器回复AC…...

解决多个Jenkins Master实例共享Jenkins_home目录的问题(加锁解锁机制)
在Jenkins的持续集成和持续部署(CI/CD)环境中,JENKINS_HOME目录扮演着至关重要的角色。它存储了Jenkins的配置、插件、作业历史记录等核心数据。然而,在某些场景下,我们可能面临多个Jenkins master实例需要共享同一个J…...

postgresql array 反向截取
postgresql array 反向截取 array_to_string((string_to_array(REPLACE(delcell.小区网管名称,‘‘,’-‘),’-‘))[:array_length(string_to_array(REPLACE(delcell.小区网管名称,’’,‘-’),‘-’),1)-1],‘-’) as 基站名称 在PostgreSQL中,如果你想要对数组进…...

最新口型同步技术EchoMimic部署
EchoMimic是由蚂蚁集团推出的一个 AI 驱动的口型同步技术项目,能够通过人像面部特征和音频来帮助人物“对口型”,生成逼真的动态肖像视频。 EchoMimic的技术亮点在于其创新的动画生成方法,它不仅能够通过音频和面部关键点单独驱动图像动画&a…...

程序设计基础(c语言)_补充_1
1、编程应用双层循环输出九九乘法表 #include <stdio.h> #include <stdlib.h> int main() {int i,j;for(i1;i<9;i){for(j1;j<i;j)if(ji)printf("%d*%d%d",j,i,j*i);elseprintf("%d*%d%-2d ",j,i,j*i);printf("\n");}return 0…...

8.4 day bug
bug1 忘记给css变量加var 复制代码到通义千问,解决 bug2 这不是我的bug,是freecodecamp的bug 题目中“ 将 --building-color2 变量的颜色更改为 #000” “ 应改为” 将 #000 变量的颜色更改为 --building-color2 “ bug3 又忘记加var(–xxx) 还去问…...

【Material-UI】Autocomplete中的禁用选项:Disabled options
文章目录 一、简介二、基本用法三、进阶用法1. 动态禁用2. 提示禁用原因3. 复杂的禁用条件 四、最佳实践1. 一致性2. 提供反馈3. 优化性能 五、总结 Material-UI的Autocomplete组件提供了丰富的功能,包括禁用特定选项的能力。这一特性对于限制用户选择、提供更好的用…...

Pytest测试报告生成专题
在 pytest 中,你可以使用多个选项生成不同格式的测试报告。以下是几种常用的生成测试报告的方法: 1. 生成简单的测试结果文件 你可以使用 pytest 的 --junitxml 选项生成一个 XML 格式的测试报告,这个报告可以与 CI/CD 工具集成。 pytest --junitxml=report.xml这将在当前…...

QT 笔记
HTTPS SSL配置 下载配置 子父对象 QTimer *timer new QTimer; // QTimer inherits QObject timer->inherits("QTimer"); // returns true timer->inherits("QObject"); // returns true timer->inherits("QAbst…...

【redis 第七篇章】动态字符串
一、概述 string 类型底层实现的简单动态字符串 sds,是可以修改的字符串。它采用预分配冗余空间的方式来减少内存的频繁分配。 二、SDS动态字符串 动态字符串 是以 \0 为分隔符。最大容量 是 redis 主动分配的一块内存空间,实际存储内容 是具体的存的数…...

rk3588 部署yolov8.rknn
本文从步骤来记录在rk3588芯片上部署yolov8模型 主机:windows10 VMware Workstation 16 Pro 硬件:RK3588 EVB板 模型: RK3588.rknn 软件开发环境: c cmake step1: 主机上执行: 将rknn_model_zoo 工程文件下载…...

【正点原子i.MX93开发板试用连载体验】中文提示词的训练
本文首发于电子发烧友论坛:【正点原子i.MX93开发板试用连载体验】基于深度学习的语音本地控制 - 正点原子学习小组 - 电子技术论坛 - 广受欢迎的专业电子论坛! 好久没有更新了,今天再来更新一下。 我们用前面提到的录音工具录制了自己的中文语音&#…...

WordPress资源下载类主题 CeoMax-Pro_v7.6绕授权开心版
CeoMax-Pro强大的功能 在不久的将来Ta能实现你一切幻想!我们也在为此而不断努力。适用于资源站、下载站、交易站、素材站、源码站、课程站、cms等等等等,Ta 为追求极致的你而生。多风格多样式多类型多行业多功能 源码下载:ceomax-pro7.6.zip…...

使用GCC编译Notepad++的插件
Notepad的本体1是支持使用MSVC和GCC编译的2,但是Notepad插件的官方文档3里却只给出了MSVC的编译指南4。 网上也没有找到相关的讨论,所以我尝试在 Windows 上使用 MinGW,基于 GCC-8.1.0 的 posix-sjlj 线程版本5,研究一下怎么编译…...

技术周总结 2024.07.29 ~ 08.04周日(MyBatis, 极限编程)
文章目录 一、08.01 周四1.1)mybatis的 xml文件中的 ${var} 和 #{var}的区别? 二、08.03 周六2.1)极限编程核心价值观核心实践实施极限编程的好处极限编程的挑战适用场景 三、08.04 周日3.1)《计算机信息系统安全保护等级划分准则…...

C语言调试宏全面总结(六大板块)
C语言调试宏进阶篇:实用指南与案例解析C语言调试宏高级技巧与最佳实践C语言调试宏的深度探索与性能考量C语言调试宏在嵌入式系统中的应用与挑战C语言调试宏在多线程环境中的应用与策略C语言调试宏在并发编程中的高级应用 C语言调试宏进阶篇:实用指南与案…...