Android 12 S 系统开机流程分析 - SetupSelinux(二)
Android 12 S 系统开机流程分析-FirstStageMain(一)
本文接着上文开始讲解,上文中最后一步执行后会执行init启动过程中的第二步SetupSelinux(Selinux配置阶段),这样又会走到main.cpp中的main方法。
目录
1. SetupSelinux
1.1 初始化kernel log系统
1.2 ReadPolicy
1.3 加载策略
1.4 使能/关闭Selinux并写入节点
1.5 selinux_android_restorecon
1.6 重新执行init进程并携带参数second_stage
1. SetupSelinux
由于上一篇中最后一步在重新执行init的时候携带了参数selinux_setup,所以此处会走入SetupSelinux方法,加载selinux的策略。
SetupSelinux主要功能:
1)open sepolicy
2)load sepolicy
3)使能/关闭selinux
int main(int argc, char** argv) {
...if (!strcmp(argv[1], "selinux_setup")) {
//执行此处return SetupSelinux(argv);}if (!strcmp(argv[1], "second_stage")) {return SecondStageMain(argc, argv);}}return FirstStageMain(argc, argv);
}
1.1 初始化kernel log系统
1)初始化kernel log
2)若系统发生了panic,则走InstallRebootSignalHandlers
InstallRebootSignalHandlers中主要做了以下几件事:
1. 初始化了一个自定义信号集,将其所有信号都填充满,即将信号集中的所有的标志位都置为1,使得这个集合包含所有可接受的信号,也就是阻塞所有信号。这个函数可以用于快速创建一个包含所有信号的信号集,然后可以根据需要删除其中的某些信号。
2. init创建出来的子进程不做处理,直接exit;如果不是子进程,则代表是init进程,则执行InitFatalReboot
3. 通过syscall向内核发送重启命令
4. 捕获一些信号
int SetupSelinux(char** argv) {SetStdioToDevNull(argv);InitKernelLogging(argv);if (REBOOT_BOOTLOADER_ON_PANIC) {InstallRebootSignalHandlers();}boot_clock::time_point start_time = boot_clock::now();
//这是R上为了R system.img/system_ext.img作为system_ext.img工作在old vendor.img上。
//我们在init第二阶段挂载system_ext,因为在system-only OTA场景中boot.img的init第一阶段是不会被更新的。MountMissingSystemPartitions();SelinuxSetupKernelLogging();LOG(INFO) << "Opening SELinux policy";
SelinuxGetVendorAndroidVersion主要作用是读/vendor/etc/selinux/plat_sepolicy_vers.txt中的第一行,获取Selinux vendor version。
int SelinuxGetVendorAndroidVersion() {static int vendor_android_version = [] {if (!IsSplitPolicyDevice()) {
//如果这个设备不拆分sepolicy文件,它就不是一个Treble设备return __ANDROID_API_FUTURE__;}std::string version;
//获取vendor Selinux version
//读/vendor/etc/selinux/plat_sepolicy_vers.txt中的第一行
//可以adb看下vendor版本:
//adb shell cat /vendor/etc/selinux/plat_sepolicy_vers.txt
//32.0if (!GetVendorMappingVersion(&version)) {LOG(FATAL) << "Could not read vendor SELinux version";}
//解析version值,version一般是带小数点的,比如32.0。int major_version;std::string major_version_str(version, 0, version.find('.'));if (!ParseInt(major_version_str, &major_version)) {PLOG(FATAL) << "Failed to parse the vendor sepolicy major version "<< major_version_str;}
//返回version中整数return major_version;}();return vendor_android_version;
}
1.2 ReadPolicy
ReadPolicy主要做了两件事
1)open split policy
2)读sepolicy
这个是SetupSelinux的主要功能之一。
// 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();}
ReadPolicy的主要功能是:
如果设备中存在/system/etc/selinux/plat_sepolicy.cil文件,并可以访问,则IsSplitPolicyDevice为true,会走OpenSplitPolicy流程,这个设备中默认都有这个文件的。
void ReadPolicy(std::string* policy) {PolicyFile policy_file;
//constexpr const char plat_policy_cil_file[] = "/system/etc/selinux/plat_sepolicy.cil";//bool IsSplitPolicyDevice() {
// return access(plat_policy_cil_file, R_OK) != -1;
//}
//IsSplitPolicyDevice如上,如果能访问设备中的/system/etc/selinux/plat_sepolicy.cil文件,
//则IsSplitPolicyDevice为true,会走OpenSplitPolicybool ok = IsSplitPolicyDevice() ? OpenSplitPolicy(&policy_file): OpenMonolithicPolicy(&policy_file);if (!ok) {LOG(FATAL) << "Unable to open SELinux policy";}if (!android::base::ReadFdToString(policy_file.fd, policy)) {PLOG(FATAL) << "Failed to read policy file: " << policy_file.path;}
}
OpenSplitPolicy的主要功能是:
1)如果设备是userdebug版本+设备unlock+存在/debug_ramdisk/adb_debug.prop并且可以访问,则加载userdebug system sepolicy
2)从代码看是access system,vendor,system_ext和vendor下etc/selinux/mapping/下的.cil文件
3)再查看对应目录下的.cil文件内容是否为空,不为空则将对应目录下的.cil文件内容push进compile_args
4)执行compile_args并等待,决定include哪个mapping下的.cil文件5)将临时文件/dev/sepolicy.XXXXXX的fd和path保存进policy_file->fd和 policy_file->path。
bool OpenSplitPolicy(PolicyFile* policy_file) {
//若存在/force_debuggable,则在init启动第一阶段,force_debuggable_env 环境变量已经设置好了
//setenv("INIT_FORCE_DEBUGGABLE", "true", 1);const char* force_debuggable_env = getenv("INIT_FORCE_DEBUGGABLE");
//是userdebug版本+设备unlock+存在/debug_ramdisk/adb_debug.prop并且可以访问,
//则加载userdebug system sepolicybool use_userdebug_policy =((force_debuggable_env && "true"s == force_debuggable_env) &&AvbHandle::IsDeviceUnlocked() && access(kDebugRamdiskSEPolicy, F_OK) == 0);if (use_userdebug_policy) {LOG(WARNING) << "Using userdebug system sepolicy";}if (!use_userdebug_policy) {if (auto res = FindPrecompiledSplitPolicy(); res.ok()) {unique_fd fd(open(res->c_str(), O_RDONLY | O_CLOEXEC | O_BINARY));if (fd != -1) {policy_file->fd = std::move(fd);policy_file->path = std::move(*res);return true;}} else {LOG(INFO) << res.error();}}// No suitable precompiled policy could be loaded
//没有合适的策略可以加载LOG(INFO) << "Compiling SELinux policy";// We store the output of the compilation on /dev because this is the most convenient tmpfs// storage mount available this early in the boot sequence.char compiled_sepolicy[] = "/dev/sepolicy.XXXXXX";
//mkostemp:创建临时文件
//创建临时文件/dev/sepolicy.XXXXXX,其fd为compiled_sepolicy_fdunique_fd compiled_sepolicy_fd(mkostemp(compiled_sepolicy, O_CLOEXEC));if (compiled_sepolicy_fd < 0) {PLOG(ERROR) << "Failed to create temporary file " << compiled_sepolicy;return false;}
...
//从代码看是access system,vendor,system_ext和vendor下etc/selinux/mapping/下的.cil文件
//再查看对应目录下的.cil文件内容是否为空,不为空则将对应目录下的.cil文件内容push进compile_args
//执行compile_args并等待
//将临时文件/dev/sepolicy.XXXXXX的fd和path保存进policy_file->fd和 policy_file->path。
//决定include哪个mapping下的.cil文件std::string vend_plat_vers;if (!GetVendorMappingVersion(&vend_plat_vers)) {return false;}std::string plat_mapping_file("/system/etc/selinux/mapping/" + vend_plat_vers + ".cil");std::string plat_compat_cil_file("/system/etc/selinux/mapping/" + vend_plat_vers +".compat.cil");if (access(plat_compat_cil_file.c_str(), F_OK) == -1) {plat_compat_cil_file.clear();}std::string system_ext_policy_cil_file("/system_ext/etc/selinux/system_ext_sepolicy.cil");if (access(system_ext_policy_cil_file.c_str(), F_OK) == -1) {system_ext_policy_cil_file.clear();}std::string system_ext_mapping_file("/system_ext/etc/selinux/mapping/" + vend_plat_vers +".cil");if (access(system_ext_mapping_file.c_str(), F_OK) == -1) {system_ext_mapping_file.clear();}std::string system_ext_compat_cil_file("/system_ext/etc/selinux/mapping/" + vend_plat_vers +".compat.cil");if (access(system_ext_compat_cil_file.c_str(), F_OK) == -1) {system_ext_compat_cil_file.clear();}std::string product_policy_cil_file("/product/etc/selinux/product_sepolicy.cil");if (access(product_policy_cil_file.c_str(), F_OK) == -1) {product_policy_cil_file.clear();}std::string product_mapping_file("/product/etc/selinux/mapping/" + vend_plat_vers + ".cil");if (access(product_mapping_file.c_str(), F_OK) == -1) {product_mapping_file.clear();}// vendor_sepolicy.cil and plat_pub_versioned.cil are the new design to replace// nonplat_sepolicy.cil.std::string plat_pub_versioned_cil_file("/vendor/etc/selinux/plat_pub_versioned.cil");std::string vendor_policy_cil_file("/vendor/etc/selinux/vendor_sepolicy.cil");if (access(vendor_policy_cil_file.c_str(), F_OK) == -1) {// For backward compatibility.// TODO: remove this after no device is using nonplat_sepolicy.cil.vendor_policy_cil_file = "/vendor/etc/selinux/nonplat_sepolicy.cil";plat_pub_versioned_cil_file.clear();} else if (access(plat_pub_versioned_cil_file.c_str(), F_OK) == -1) {LOG(ERROR) << "Missing " << plat_pub_versioned_cil_file;return false;}// odm_sepolicy.cil is default but optional.std::string odm_policy_cil_file("/odm/etc/selinux/odm_sepolicy.cil");if (access(odm_policy_cil_file.c_str(), F_OK) == -1) {odm_policy_cil_file.clear();}const std::string version_as_string = std::to_string(SEPOLICY_VERSION);//有一些代码,我们并不想让clang-format调整格式,这时可以使用注释临时禁用clang-format// clang-format offstd::vector<const char*> compile_args {"/system/bin/secilc",use_userdebug_policy ? kDebugRamdiskSEPolicy: plat_policy_cil_file,"-m", "-M", "true", "-G", "-N","-c", version_as_string.c_str(),plat_mapping_file.c_str(),"-o", compiled_sepolicy,// We don't care about file_contexts output by the compiler"-f", "/sys/fs/selinux/null", // /dev/null is not yet available};// clang-format on//将对应目录下的.cil文件内容push进compile_argsif (!plat_compat_cil_file.empty()) {compile_args.push_back(plat_compat_cil_file.c_str());}if (!system_ext_policy_cil_file.empty()) {compile_args.push_back(system_ext_policy_cil_file.c_str());}if (!system_ext_mapping_file.empty()) {compile_args.push_back(system_ext_mapping_file.c_str());}if (!system_ext_compat_cil_file.empty()) {compile_args.push_back(system_ext_compat_cil_file.c_str());}if (!product_policy_cil_file.empty()) {compile_args.push_back(product_policy_cil_file.c_str());}if (!product_mapping_file.empty()) {compile_args.push_back(product_mapping_file.c_str());}if (!plat_pub_versioned_cil_file.empty()) {compile_args.push_back(plat_pub_versioned_cil_file.c_str());}if (!vendor_policy_cil_file.empty()) {compile_args.push_back(vendor_policy_cil_file.c_str());}if (!odm_policy_cil_file.empty()) {compile_args.push_back(odm_policy_cil_file.c_str());}compile_args.push_back(nullptr);
//执行compile_args并等待if (!ForkExecveAndWaitForCompletion(compile_args[0], (char**)compile_args.data())) {unlink(compiled_sepolicy);return false;}unlink(compiled_sepolicy);
//将临时文件/dev/sepolicy.XXXXXX的fd和path保存进policy_file->fd和 policy_file->path。policy_file->fd = std::move(compiled_sepolicy_fd);policy_file->path = compiled_sepolicy;return true;
}
1.3 加载策略
这也是SetupSelinux中最重要的功能之一。
LoadSelinuxPolicy(policy);
上一节中对policy进行了赋值。
static void LoadSelinuxPolicy(std::string& policy) {LOG(INFO) << "Loading SELinux policy";set_selinuxmnt("/sys/fs/selinux");if (security_load_policy(policy.data(), policy.size()) < 0) {PLOG(FATAL) << "SELinux: Could not load policy";}
}
security_load_policy主要功能是:
1)open $selinux_mnt/load,获取其对应fd
2)根据fd将/dev/sepolicy.XXXXXX中的内容写入$selinux_mnt/load中。selinux_mnt如下#define SELINUXMNT "/sys/fs/selinux"
external/selinux/libselinux/src/load_policy.c
int security_load_policy(void *data, size_t len)
{char path[PATH_MAX];int fd, ret;if (!selinux_mnt) {errno = ENOENT;return -1;}snprintf(path, sizeof path, "%s/load", selinux_mnt);fd = open(path, O_RDWR | O_CLOEXEC);if (fd < 0)return -1;ret = write(fd, data, len);close(fd);if (ret < 0)return -1;return 0;
}
1.4 使能/关闭Selinux并写入节点
获取selinux状态是否是enforce,然后将selinux状态is_enforcing写入 $selinux_mnt/enforce中。selinux_mnt如下
#define SELINUXMNT "/sys/fs/selinux"
if (snapuserd_helper) {// Before enforcing, finish the pending snapuserd transition.snapuserd_helper->FinishTransition();snapuserd_helper = nullptr;}SelinuxSetEnforcement();
void SelinuxSetEnforcement() {bool kernel_enforcing = (security_getenforce() == 1);
//check selinux是否是enforce状态(开启selinux)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();}
}
bool IsEnforcing() {
//如果关闭系统的selinux,即将selinux置为permissive,此处直接返回false即可
//return false;if (ALLOW_PERMISSIVE_SELINUX) {return StatusFromProperty() == SELINUX_ENFORCING;}return true;
}
/external/selinux/libselinux/src/setenforce.c
int security_setenforce(int value)
{int fd, ret;char path[PATH_MAX];char buf[20];if (!selinux_mnt) {errno = ENOENT;return -1;}snprintf(path, sizeof path, "%s/enforce", selinux_mnt);fd = open(path, O_RDWR | O_CLOEXEC);if (fd < 0)return -1;snprintf(buf, sizeof buf, "%d", value);ret = write(fd, buf, strlen(buf));close(fd);if (ret < 0)return -1;return 0;
}
1.5 selinux_android_restorecon
从kernel domain域过渡到init domain域
文件系统在xattrs中存储了SELabels,比如ext4不需要restorecon,但其他文件系统需要
//从kernel domain域过渡到init domain域
//文件系统在xattrs中存储了SELabels,比如ext4不需要restorecon,但其他文件系统需要if (selinux_android_restorecon("/system/bin/init", 0) == -1) {PLOG(FATAL) << "restorecon failed of /system/bin/init failed";}
1.6 重新执行init进程并携带参数second_stage
setenv(kEnvSelinuxStartedAt, std::to_string(start_time.time_since_epoch().count()).c_str(), 1);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;
}
到了SetupSelinux的最后一步,这一步还是重新执行/system/bin/init,这样就会再次走init bin程序的main函数,由代码可以看到,此次携带了参数second_stage。
后面接着讲启动流程
相关文章:
Android 12 S 系统开机流程分析 - SetupSelinux(二)
Android 12 S 系统开机流程分析-FirstStageMain(一) 本文接着上文开始讲解,上文中最后一步执行后会执行init启动过程中的第二步SetupSelinux(Selinux配置阶段),这样又会走到main.cpp中的main方法。 目录 1. SetupSelinux 1.1 …...
高速信号PCB布局怎么布?(电子硬件)
对于高速信号,pcb的设计要求会更多,因为高速信号很容易收到其他外在因素的干扰,导致实际设计出来的东西和原本预期的效果相差很多。 所以在高速信号pcb设计中,需要提前考虑好整体的布局布线,良好的布局可以很好的决定布…...
vue 子页面通过暴露属性,实现主页面的某事件的触发
目录 1.前言2.代码2-1 子页面2-2 主页面 1.前言 需求:当我在子页面定义了一个定时器,点击获取验证码,计时器开始倒计时,在这个定时器没有走完,退出关闭子页面,再次进入子页面,定时器此时会被刷…...
计算机丢失mfc140.dll是什么意思?附送修复教程
mfc140.dll是Microsoft Foundation Classes(MFC)库的一部分,是一种动态链接库(DLL)文件。MFC库是Microsoft提供的一种C编程框架,它为开发者提供了许多方便的工具和类,以简化Windows应用程序的开…...
R语言将向量横向转换为单行数据框,随后整合数量不确定的数据框
vector1 c(1, “karthik”, “IT”) names(vector1) c(“id”, “name”, “branch”) df data.frame(as.list(vector1)) print(df) 先给向量的元素命名,然后转换为列表,最后转换为数据框。 我的需求大概是这个样子:数量不确定的仅有单行…...
怎么测试websocket接口
在部分业务中,我们需要使用长连接,我们可以使用http长连接或者websocket,开发结束后难免会遇到测试问题,这里推荐2个,一个是postman,一个是网站 postman 测试网站 测这边推荐测试网站,支持ws/w…...
21 移动网络的前世今生
1、移动网络的发展历程 发展过程就是:2G,3G,4G,5G的过程,用2G看txt,用3G看jpg,用4G看avi。 2、2G网络 手机本来是用来打电话的,不是用来上网的,所以原来在2G时代,上网使用的不是IP网络&#…...
里氏替换原则
定义:子类对象能够替换程序中父类对象出现的任何地方,并且*保证原来程序的逻辑行为不变及正确性不被破坏*。 public class Transporter {private HttpClient httpClient;public Transporter(HttpClient httpClient) {this.httpClient httpClient;}public Response…...
【JS】Chapter11-正则阶段案例
站在巨人的肩膀上 黑马程序员前端JavaScript入门到精通全套视频教程,javascript核心进阶ES6语法、API、js高级等基础知识和实战教程 (十一)正则&阶段案例 1. 正则表达式 1.1 介绍 正则表达式(Regular Expression࿰…...
跨时钟域(Clock Domain Crossing,CDC)
本文参考:http://t.csdnimg.cn/VHga2 【数字IC基础】跨时钟域(CDC,Clock Domain Crossing)_ReRrain的博客-CSDN博客 同步设计:所有设计使用同一时钟源,频率相位可预知。 异步设计:设计中有两…...
PTA古风排版
中国的古人写文字,是从右向左竖向排版的。本题就请你编写程序,把一段文字按古风排版。 输入格式: 输入在第一行给出一个正整数N(<100),是每一列的字符数。第二行给出一个长度不超过1000的非空字符串&a…...
SQL 注入漏洞详解
SQL 注入漏洞详解 漏洞描述 sql注入漏洞是指恶意用户在应用与数据库交互的地方利用非法的操作获取数据库内容从以下两点分析: 没有对用户输入的数据进行充分的过滤和验证,导致一些用户利用此漏洞向数据库插入恶意sql语句非法请求数据库从而获得一些敏感数据在与数…...
关于阿里云 ACK ingress部分补充
强调: 本文只是作为记录,过一段时间会删除 跟唐老师学习网络 一 Nginx Ingress管理 ① 流量走向 需求: 应用绑定LoadBalance,会自动创建或使用SLBeip:port --> nodeport_ip:port --> service_ip:port --> pod_ip:port 支持的注解 通过…...
轻量封装WebGPU渲染系统示例<22>- 渲染到纹理(RTT)(源码)
当前示例源码github地址: https://github.com/vilyLei/voxwebgpu/blob/feature/rendering/src/voxgpu/sample/RTTTest.ts 当前示例运行效果: 此示例基于此渲染系统实现,当前示例TypeScript源码如下: export class RTTTest {private mRscene new RendererScene()…...
官方Redis视图化工具Redisinsight
一、下载最新版本的 docker pull redislabs/redisinsight mkdir /data/redisinsight docker run -d -u root -p 8001:8001 -v /etc/localtime:/etc/localtime -v /data/redisinsight:/db --restartunless-stopped redislabs/redisinsight:latest 二、浏览器打开 http://192…...
Vue+Django REST framework 打造生鲜电商项目课程下载树大根深
VueDjango REST framework 打造生鲜电商项目 链接:https://pan.baidu.com/s/1kEDxPsoTYSVWPYB2H0jbBw?pwd6666 提取码:6666Django是高水准的Python编程语言驱动的一个开源模型.视图,控制器风格的Web应用程序框架,它…...
react中遇到的分页问题
问题: 1.使用useState时不能够进行当前页码的改变,数据不会随着页码变化 2.删除当前页的最后一条数据时,页码返回上一页但是数据为空 解决: 1.由于useState和useRef的区别那我们就不考虑使用useState 2.再删除的逻辑当中添加判断条…...
变电站自动化系统中的安全措施分析及应用-安科瑞
安科瑞电气股份有限公司 上海嘉定 201801 摘要:阐述变电运行中的问题,电气自动化系统与安全运行措施,包括自动控制设备的投入,电气自动 化与计算机技术相、设备数据的采集与处理、自动化系统的升级、人工智能技术的应用。 关键…...
【MongoDB】索引 – 文本索引
一、准备工作 这里准备一些数据 db.books.insertMany([{_id: 1, name: "Java", description: "java 入门图书", translation: [{ language: "english", description: "java basic book" }]},{_id: 2, name: "C", descript…...
【广州华锐互动】影视制作VR在线学习:身临其境,提高学习效率
随着科技的不断发展,影视后期制作技术也在日新月异。然而,传统的教学方式往往难以满足学员的学习需求,无法充分展现影视后期制作的魅力和潜力。近年来,虚拟现实(VR)技术的崛起为教学领域带来了新的机遇。通过VR教学课件࿰…...
Unity3D中Gfx.WaitForPresent优化方案
前言 在Unity中,Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染(即CPU被阻塞),这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案: 对惹,这里有一个游戏开发交流小组&…...
k8s从入门到放弃之Ingress七层负载
k8s从入门到放弃之Ingress七层负载 在Kubernetes(简称K8s)中,Ingress是一个API对象,它允许你定义如何从集群外部访问集群内部的服务。Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机等功能。通过Ingress,你可…...
循环冗余码校验CRC码 算法步骤+详细实例计算
通信过程:(白话解释) 我们将原始待发送的消息称为 M M M,依据发送接收消息双方约定的生成多项式 G ( x ) G(x) G(x)(意思就是 G ( x ) G(x) G(x) 是已知的)࿰…...
理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...
《通信之道——从微积分到 5G》读书总结
第1章 绪 论 1.1 这是一本什么样的书 通信技术,说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号(调制) 把信息从信号中抽取出来&am…...
vue3 定时器-定义全局方法 vue+ts
1.创建ts文件 路径:src/utils/timer.ts 完整代码: import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...
Rust 异步编程
Rust 异步编程 引言 Rust 是一种系统编程语言,以其高性能、安全性以及零成本抽象而著称。在多核处理器成为主流的今天,异步编程成为了一种提高应用性能、优化资源利用的有效手段。本文将深入探讨 Rust 异步编程的核心概念、常用库以及最佳实践。 异步编程基础 什么是异步…...
Python如何给视频添加音频和字幕
在Python中,给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加,包括必要的代码示例和详细解释。 环境准备 在开始之前,需要安装以下Python库:…...
CRMEB 框架中 PHP 上传扩展开发:涵盖本地上传及阿里云 OSS、腾讯云 COS、七牛云
目前已有本地上传、阿里云OSS上传、腾讯云COS上传、七牛云上传扩展 扩展入口文件 文件目录 crmeb\services\upload\Upload.php namespace crmeb\services\upload;use crmeb\basic\BaseManager; use think\facade\Config;/*** Class Upload* package crmeb\services\upload* …...
Unit 1 深度强化学习简介
Deep RL Course ——Unit 1 Introduction 从理论和实践层面深入学习深度强化学习。学会使用知名的深度强化学习库,例如 Stable Baselines3、RL Baselines3 Zoo、Sample Factory 和 CleanRL。在独特的环境中训练智能体,比如 SnowballFight、Huggy the Do…...
