android存储1--device解锁前的流程

news2024/12/23 10:20:30

android版本:android-11.0.0_r21
http://aospxref.com/android-11.0.0_r21/

一、主用户primary user的创建

开机后kernel启动第一个用户态进程init,init进程fork出zygote进程。zygote又fork出system server进程。
http://aospxref.com/android-11.0.0_r21/xref/frameworks/base/core/java/com/android/internal/os/ZygoteInit.java#920

832      public static void main(String argv[]) {
833          ZygoteServer zygoteServer = null;
834  
             ……
916  
917              zygoteServer = new ZygoteServer(isPrimaryZygote);
918  
919              if (startSystemServer) {
920                  Runnable r = forkSystemServer(abiList, zygoteSocketName, zygoteServer);
921  
922                  // {@code r == null} in the parent (zygote) process, and {@code r != null} in the
923                  // child (system_server) process.
924                  if (r != null) {
925                      r.run();
926                      return;
927                  }
928              }

920行,fork system server进程,925行运行子进程system server。system server入口代码如下:

http://aospxref.com/android-11.0.0_r21/xref/frameworks/base/services/java/com/android/server/SystemServer.java#main

410      /**
411       * The main entry point from zygote.
412       */
413      public static void main(String[] args) {
414          new SystemServer().run();
415      }

SystemServer().run()会启动各种service:
http://aospxref.com/android-11.0.0_r21/xref/frameworks/base/services/java/com/android/server/SystemServer.java#run


436      private void run() {
             ……
592  
593          // Start services.
594          try {
595              t.traceBegin("StartServices");
596              startBootstrapServices(t);
597              startCoreServices(t);
598              startOtherServices(t);
599          } catch (Throwable ex) {
600              Slog.e("System", "******************************************");
601              Slog.e("System", "************ Failure starting system services", ex);
602              throw ex;
603          } finally {
604              t.traceEnd(); // StartServices
605          }

596行启动一些列的service:
http://aospxref.com/android-11.0.0_r21/xref/frameworks/base/services/java/com/android/server/SystemServer.java#startBootstrapServices


710      private void startBootstrapServices(@NonNull TimingsTraceAndSlog t) {
             ……
760  
761          // Activity manager runs the show.
762          t.traceBegin("StartActivityManager");
763          // TODO: Might need to move after migration to WM.
764          ActivityTaskManagerService atm = mSystemServiceManager.startService(
765                  ActivityTaskManagerService.Lifecycle.class).getService();
766          mActivityManagerService = ActivityManagerService.Lifecycle.startService(
767                  mSystemServiceManager, atm);
768          mActivityManagerService.setSystemServiceManager(mSystemServiceManager);
769          mActivityManagerService.setInstaller(installer);

在启动 ActivityManagerService 时,SystemServer 会等待 ActivityManagerService 初始化完成并绑定到系统服务中心(System Service Manager)。然后,SystemServer 会调用 ActivityManagerService 的 systemReady() 方法:
http://aospxref.com/android-11.0.0_r21/xref/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java#systemReady

9515      public void systemReady(final Runnable goingCallback, @NonNull TimingsTraceAndSlog t) {
               ……
9640          // headless-user start logic to UserManager-land
9641          final boolean bootingSystemUser = currentUserId == UserHandle.USER_SYSTEM;
9642  
9643          if (bootingSystemUser) {
9644              mSystemServiceManager.startUser(t, currentUserId);
9645          }

9644行执行SystemServiceManager::startUser
http://aospxref.com/android-11.0.0_r21/xref/frameworks/base/services/core/java/com/android/server/SystemServiceManager.java#startUser

254      /**
255       * Starts the given user.
256       */
257      public void startUser(final @NonNull TimingsTraceAndSlog t, final @UserIdInt int userHandle) {
258          onUser(t, START, userHandle);
259      }

258行,会发送START消息,系统中的多个模块会接收并处理该消息,包括Zygote、PackageManagerService和ActivityManagerService本身等。其中,Zygote进程会根据用户ID创建新的应用程序进程;PackageManagerService会根据用户信息和应用程序信息初始化和加载应用程序;而ActivityManagerService则会负责协调和管理用户进程的生命周期。

当primary user和各种service启动后,SystemServiceManager发送H_BOOT_COMPLETED消息,这个消息的处理见第二部分。

二、处理H_BOOT_COMPLETED消息

开机后,StorageManagerService收到开机广播消息H_BOOT_COMPLETED,按时间顺序完成以下4件事情:

  • init用户目录的加密状态
  • reset external storage service
  • reset vold service
  • 添加用户

代码整体逻辑如下:

1,init用户目录的加密状态

代码路径:StorageManagerServiceHandler::handleMessage --> handleBootCompleted --> initIfBootedAndConnected

分两种情况:
1)用户目录采用硬件加解密(native encryption),此阶段什么事也不做,函数直接返回
2)用户目录采用软件加解密(enmulated encryption),执行lock  encryption key(用户目录已加密的情况)或者unlock encryption key(用户目录未加密的情况)

从安全性、性能角度看,硬件加解密更好。

现在的手机基本都是硬件加解密方式,通过adb shell getprop ro.crypto.state可查询手机用的是哪种加解密方式。

adb shell getprop ro.crypto.state

返回字符串

加解密方式

"encrypted"

native encryption(硬件加解密)

"unencrypted" 或 "unsupported"

enmulated encryption(软件加解密)

"default"

手机还没有加密过

handleMessage处理H_BOOT_COMPLETED:
StorageManagerService.java - OpenGrok cross reference for /frameworks/base/services/core/java/com/android/server/StorageManagerService.java

707      class StorageManagerServiceHandler extends Handler {
713          public void handleMessage(Message msg) {
714              switch (msg.what) {
719                  case H_BOOT_COMPLETED: {
720                      handleBootCompleted();
721                      break;
722                  }

 收到H_BOOT_COMPLETED广播消息后, 执行719行分支,handleBootCompleted函数如下:

2076      private void handleBootCompleted() {
2077          initIfBootedAndConnected();
2078          resetIfBootedAndConnected();
2079      }

initIfBootedAndConnected函数处理enmulated encryption场景,如果用户目录是加密状态,那么lock encryption key,否则就unlock encryption key。
StorageManagerService.java - OpenGrok cross reference for /frameworks/base/services/core/java/com/android/server/StorageManagerService.java

1061      private void initIfBootedAndConnected() {

1064          if (mBootCompleted && mDaemonConnected
1065                  && !StorageManager.isFileEncryptedNativeOnly()) {

1069              final boolean initLocked = StorageManager.isFileEncryptedEmulatedOnly();

1071              final List<UserInfo> users = mContext.getSystemService(UserManager.class).getUsers();
1072              for (UserInfo user : users) {
1073                  try {
1074                      if (initLocked) {
1075                          mVold.lockUserKey(user.id);
1076                      } else {
1077                          mVold.unlockUserKey(user.id, user.serialNumber, encodeBytes(null),
            
        ……

1065行StorageManager.isFileEncryptedNativeOnly(),判断设备是不是硬件加解密。如果是硬件加解密,则什么也不做,退出函数。设备用软件加解密时,进入1066行分支处理,根据StorageManager.isFileEncryptedEmulatedOnly()获取到的用户目录加密状态,执行lock 或者unlock encryption key操作。

2,reset external service

代码路径:StorageManagerServiceHandler::handleMessage --> handleBootCompleted --> resetIfBootedAndConnected --> StorageSessionController::onReset

external storage service是一个由名为com.android.providers.media.module的package提供的服务,用于管理外部存储设备(sd卡、u盘等),服务名称为com.android.providers.media.fuse.ExternalStorageServiceImpl。external storage service负责挂载/卸载外部存储设备、管理存储设备上的文件系统、处理app访问外部存储的请求。可通过adb shell dumpsys activity services com.android.providers.media.module/com.android.providers.media.fuse.ExternalStorageServiceImpl命令查看service详细信息。

app访问外部存储时,调用framework提供的接口(比如函数getExternalFilesDir、getExternalStorageDirectory、Environment.getExternalStoragePublicDirectory),然后这些函数再通过external storage service去访问外部存储。出于安全考虑,只有用户connect到external storage service后,也即该用户是被认可的,用户名下的app才允许访问外部存储。

reset external service主要完成2件事:
1)umount外部存储
2)关闭连接至external storage service的所有connection

一般情况下,外部存储还没有挂载成功,所以不需要卸载任何存储,也不需要关闭任何connection。不过下面还是过一下代码:

StorageManagerService.java - OpenGrok cross reference for /frameworks/base/services/core/java/com/android/server/StorageManagerService.java

1087      private void resetIfBootedAndConnected() {

1090          if (mBootCompleted && mDaemonConnected) {
1091              final UserManager userManager = mContext.getSystemService(UserManager.class);
1092              final List<UserInfo> users = userManager.getUsers();
1093  
1094              if (mIsFuseEnabled) {
1095                  mStorageSessionController.onReset(mVold, () -> {
1096                      mHandler.removeCallbacksAndMessages(null);
1097                  });
1098              } else {
1099                  killMediaProvider(users);
1100              }
            
            ……

1095行umount外部存储并关闭connection,mStorageSessionController.onReset函数如下:
StorageSessionController.java - OpenGrok cross reference for /frameworks/base/services/core/java/com/android/server/storage/StorageSessionController.java

240      public void onReset(IVold vold, Runnable resetHandlerRunnable) {
245          SparseArray<StorageUserConnection> connections = new SparseArray();
246          synchronized (mLock) {
247              mIsResetting = true;
248              Slog.i(TAG, "Started resetting external storage service...");
249              for (int i = 0; i < mConnections.size(); i++) {
250                  connections.put(mConnections.keyAt(i), mConnections.valueAt(i));
251              }
252          }
253  
254          for (int i = 0; i < connections.size(); i++) {
255              StorageUserConnection connection = connections.valueAt(i);
256              for (String sessionId : connection.getAllSessionIds()) {
259                 vold.unmount(sessionId);
                     ……
268                  connection.removeSessionAndWait(sessionId);
279                  connection.close();

259行vold.unmount(即VoldNativeService::mount函数,参考StorageManagerService.java中的mVold.mount_geshifei的博客-CSDN博客)卸载外部存储设备,不过,在开机流程不会执行到这个函数。

279行关闭connection。

3,reset vold service

代码路径:StorageManagerServiceHandler::handleMessage --> handleBootCompleted --> resetIfBootedAndConnected --> mVold.reset

这个阶段主要完成3件事:

  • 销毁volume
  • 重置disk对象
  • 清空用户

StorageManagerService.java - OpenGrok cross reference for /frameworks/base/services/core/java/com/android/server/StorageManagerService.java
 

1087      private void resetIfBootedAndConnected() {

                     /* reset external service,
                      * umount外部存储并关闭external storage service所有connection
                      */
1095                  mStorageSessionController.onReset(mVold, () -> {
1096                      mHandler.removeCallbacksAndMessages(null);
1097                  });         
1101  
1115                  // TODO(b/135341433): Remove paranoid logging when FUSE is stable
1116                  Slog.i(TAG, "Resetting vold...");
1117                  mVold.reset();
1118                  Slog.i(TAG, "Reset vold");
1112              }


1117行,mVold.reset()执行VoldNativeService::reset。
VoldNativeService.cpp - OpenGrok cross reference for /system/vold/VoldNativeService.cpp
 

164  binder::Status VoldNativeService::reset() {
165      ENFORCE_SYSTEM_OR_ROOT;
166      ACQUIRE_LOCK;
167  
168      return translate(VolumeManager::Instance()->reset());
169  }

168行,执行VolumeManager::reset函数。
VolumeManager.cpp - OpenGrok cross reference for /system/vold/VolumeManager.cpp

912  int VolumeManager::reset() {
913      // Tear down all existing disks/volumes and start from a blank slate so
914      // newly connected framework hears all events.
915      for (const auto& vol : mInternalEmulatedVolumes) {
916          vol->destroy();
917      }
918      mInternalEmulatedVolumes.clear();
919  
920      for (const auto& disk : mDisks) {
921          disk->destroy();
922          disk->create();
923      }
924      updateVirtualDisk();
925      mAddedUsers.clear();
926      mStartedUsers.clear();
927      return 0;
928  }

916行销毁volume(vol->destroy --> VolumeBase::destroy --> doDestroy)。

920行如果创建了disk对象(此时一般没有创建任何disk),就重置disk(先disk->destroy --> Disk:destory销毁disk对象,再disk->create --> Disk::create创建disk对象)。

925行,清空mAddedUsers中保存的所有用户ID,即删除所有已添加的用户。

926行,清空mStartedUsers中保存的所有用户ID,即删除所有已启动的用户。

4,在mAddedUsers中记录用户信息

代码路径:StorageManagerServiceHandler::handleMessage --> handleBootCompleted --> resetIfBootedAndConnected --> mVold.onUserAdded

mAddedUsers 是 VolumeManager 用于跟踪已添加的用户信息的数据结构,当系统启动时,它会将记录在其中的用户信息发送给 vold,以确保 vold 了解系统中所有用户的存在。这样可以确保 vold 能够正确地管理卷和磁盘。android支持多用户,用户外部存储挂载点为/storage/emulated/用户id,如果某个用户在连接设备后注销了系统,VolumeManager将记录在mAddedUsers中该用户信息发送给vold,vold就可以正确地卸个载设备上的卷或文件系统。

StorageManagerService.java - OpenGrok cross reference for /frameworks/base/services/core/java/com/android/server/StorageManagerService.java

1087      private void resetIfBootedAndConnected() {

                      /* reset external service,
                       * umount外部存储并关闭external storage service所有connection
                       */
1095                  mStorageSessionController.onReset(mVold, () -> {
1096                      mHandler.removeCallbacksAndMessages(null);
1097                  });

                      /* reset vold service,
                       * 销毁volume,重置disk,清空用户
1117                  mVold.reset();

                      /* 在mAddedUsers中 记录用户信息 */
1121                  for (UserInfo user : users) {
1122                      mVold.onUserAdded(user.id, user.serialNumber);
1123                  }
1124                  for (int userId : systemUnlockedUsers) {
1125                      mVold.onUserStarted(userId);
1126                      mStoraged.onUserStarted(userId);
1127                  }
1128                  if (mIsAutomotive) {
1129                      restoreAllUnlockedUsers(userManager, users, systemUnlockedUsers);
1130                  }
1131                  mVold.onSecureKeyguardStateChanged(mSecureKeyguardShowing);
1132                  mStorageManagerInternal.onReset(mVold);
1133             

1122行mVold.onUserAdded,调用VoldNativeService::onUserAdded,代码VoldNativeService.cpp - OpenGrok cross reference for /system/vold/VoldNativeService.cpp

185  binder::Status VoldNativeService::onUserAdded(int32_t userId, int32_t userSerial) {
186      ENFORCE_SYSTEM_OR_ROOT;
187      ACQUIRE_LOCK;
188  
189      return translate(VolumeManager::Instance()->onUserAdded(userId, userSerial));
190  }

189行执行的是VolumeManager::onUserAdded,代码VolumeManager.cpp - OpenGrok cross reference for /system/vold/VolumeManager.cpp

455  int VolumeManager::onUserAdded(userid_t userId, int userSerialNumber) {
456      LOG(INFO) << "onUserAdded: " << userId;
457  
458      mAddedUsers[userId] = userSerialNumber;
459      return 0;
460  }

458行将user信息添加到mAddedUsers中。

回到resetIfBootedAndConnected函数,看一下1124行分支:

1087      private void resetIfBootedAndConnected() {

                      /* reset external service,
                       * umount外部存储并关闭external storage service所有connection
                       */
1095                  mStorageSessionController.onReset(mVold, () -> {
1096                      mHandler.removeCallbacksAndMessages(null);
1097                  });

                      /* reset vold service,
                       * 销毁volume,重置disk,清空用户
1117                  mVold.reset();

                      /* 在mAddedUsers中 记录用户信息 */
1121                  for (UserInfo user : users) {
1122                      mVold.onUserAdded(user.id, user.serialNumber);
1123                  }
1124                  for (int userId : systemUnlockedUsers) {
1125                      mVold.onUserStarted(userId);
1126                      mStoraged.onUserStarted(userId);
1127                  }
1128                  if (mIsAutomotive) {
1129                      restoreAllUnlockedUsers(userManager, users, systemUnlockedUsers);
1130                  }
1131                  mVold.onSecureKeyguardStateChanged(mSecureKeyguardShowing);
1132                  mStorageManagerInternal.onReset(mVold);
1133             

systemUnlockedUsers"数组包含已经解锁的用户ID。当设备启动时,通常只有一个用户(User 0)被解锁,而其他用户(如果有的话)会在后续解锁后被添加到数组中。所以,对于开机流程,mVold.onUserStarted(userId)不会执行。
在某些情况下,例如设备支持多用户功能并且用户在设置中添加了一个或多个额外的用户,或者设备已经解锁并且用户在后续的操作中解锁了其他用户,则systemUnlockedUsers数组中就会包含多个用户ID。

那么,什么时间触发mVold.onUserStarted启动用户,为用户创建、挂载相关目录呢?看下一篇文章分析。

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

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

相关文章

垃圾收集器面试总结(一)

垃圾收集器 Serial 收集器&#xff08;GC日志标识&#xff1a;DefNew&#xff09; Serial&#xff08;串行&#xff09;收集器是最基本、历史最悠久的垃圾收集器了。大家看名字就知道这个收集器是一个单线程收集器了。 它的 “单线程” 的意义不仅仅意味着它只会使用一条垃圾…

[比赛简介]BirdCLEF-2023

比赛链接&#xff1a;BirdCLEF 2023 | Kaggle 比赛简介 鸟类是生物多样性变化的极好指标&#xff0c;因为它们具有高度流动性并且具有不同的栖息地要求。因此&#xff0c;物种组合和鸟类数量的变化可以表明恢复项目的成败。然而&#xff0c;经常在大面积地区进行传统的基于观…

你的车有通风座椅吗?新款奔驰S400升级原厂主副驾座椅通风

大家好&#xff0c;我是奔之升小志&#xff08;bzs878&#xff09;&#xff0c;专注名车原厂升级&#xff0c;欢迎戳戳右上角“”号关注一下&#xff0c;持续为您带来精彩改装案例。 座椅通风有什么用&#xff1f;能改善身体与座椅接触面空气流通&#xff0c;达到不出汗的效果…

Linux网络服务----SSH

文章目录 一 、SSH服务1.1 什么是SSH服务器&#xff1f;1.2 常用的SSH软件的介绍 二 、ssh的运用2.1 存放ssh服务端的配置文件2.2 ssh在Linux中的密码登录2.3 利用ssh协议传输文件和获取文件2.4 sftp远程访问操作 三 、 ssh密钥登录操作四 、TCP_wapper的原理和运用4.1 TCP_wap…

IP-GUARD能否实现打印指定文件时需经过管理员审批后才能打印?

支持。先设置禁止打印文档的策略,然后设置相关审批流程,再给到客户端相应的申请权限: 1、在控制台-高级-打印控制策略中,给需要进行打印管控的客户端设置以下策略: 动作:禁止 2、在控制台-申请管理-桌面申请管理-审批流程管理中,添加申请类型为打印的审批流程,指定审批人…

通过ADB实现移动端h5项目无线真机调试(超级简单!)

前言 做移动端h5项目的时候&#xff0c;电脑浏览器调试样式和效果&#xff0c;可能和真机展示出来的效果有差距&#xff0c;比如有的手机开启了home键&#xff0c;比如文字大小等样式有偏差。虽然可以通过手机扫描网页二维码在手机上看样式&#xff0c;但是和真机还是有区别。…

每天一道大厂SQL题【Day23】华泰证券真题实战(五)

每天一道大厂SQL题【Day23】华泰证券真题实战(五) 大家好&#xff0c;我是Maynor。相信大家和我一样&#xff0c;都有一个大厂梦&#xff0c;作为一名资深大数据选手&#xff0c;深知SQL重要性&#xff0c;接下来我准备用100天时间&#xff0c;基于大数据岗面试中的经典SQL题&…

Veritas 与星辰天合的官方一体化方案来了

11&#xff1e;2&#xff0c;XSKY星辰天合联手 Veritas 贡献企业数据管理最佳实践。 近日&#xff0c;XSKY星辰天合以“科技联盟伙伴”身份亮相 2023 Veritas Solution Day&#xff0c;并宣布与 Veritas 推出联合解决方案。双方将携手为大型企业客户带来业界领先的数据存储与保…

CDH中的MySQL升级(RPM包方式)

CDH中的MySQL升级&#xff08;RPM包方式&#xff09; 1.下载官网的5.7中最新的版本&#xff0c;地址&#xff1a;MySQL 5.7.41 rpm下载地址 2.解压下载的tar包&#xff1a;tar -xvf mysql-5.7.41-1.el7.x86_64.rpm-bundle.tar 3.备份数据库 3.1 先停止MySQL服务&#xff1a;sy…

【测试开发】第一节.测开入门(附常考面试题)

文章目录 前言 一、什么是测试开发 1.1 常考面试题 二、软件测试的基础概念 2.1 需求 2.2 测试用例 3、BUG 三、生命周期 3.1 软件的生命周期 3.2 软件测试的生命周期 四、软件工程中的几种常见的开发模型 4.1 瀑布模型 4.2 螺旋模型 4.3 增量模型和迭代模型 4.4 敏捷…

【Windows10】〖问题〗Win10默认应用Web浏览器设置里出现两个Microsoft Edge图标,如何删掉空白图标?

〖问题〗Win10默认应用Web浏览器设置里出现两个Microsoft Edge图标&#xff0c;如何删掉空白图标&#xff1f; 问题 出现原因&#xff1a; 空白那个应该是旧版edge&#xff0c;可能是因为你曾经升级最新版Chromium的edge时&#xff0c;旧版本的edge并没有被系统清除干净所…

spring security (史上最全)

认证与授权&#xff08;Authentication and Authorization&#xff09; 一般意义来说的应用访问安全性&#xff0c;都是围绕认证&#xff08;Authentication&#xff09;和授权&#xff08;Authorization&#xff09;这两个核心概念来展开的。 即&#xff1a; 首先需要确定用…

计算机组成原理——第七章输入输出系统(下)

还君明珠双泪目&#xff0c;恨不相逢未嫁时 文章目录 前言7.3.2 中断的作用和原理7.3.3 多重中断7.3.4 程序中断方式7.3.5 DMA 方式 前言 本节除了对时间的计算考察比较多之外&#xff0c;其他的方面也有考察&#xff0c;同时中断的考点在操作系统中也有考察&#xff0c;机组里…

〖Python网络爬虫实战⑯〗- 网页解析利器parsel

订阅&#xff1a;新手可以订阅我的其他专栏。免费阶段订阅量1000 python项目实战 Python编程基础教程系列&#xff08;零基础小白搬砖逆袭) 说明&#xff1a;本专栏持续更新中&#xff0c;目前专栏免费订阅&#xff0c;在转为付费专栏前订阅本专栏的&#xff0c;可以免费订阅付…

【hello Linux】进程控制

目录 1. 进程创建 2. 进程终止 3. 进程常见的退出方法 4. 进程等待 5. 进程等待的方法 6. 获取子进程status Linux&#x1f337; 1. 进程创建 fork 函数初识 在 linux 中 fork 函数是非常重要的函数&#xff0c;它可以从已存在进程中创建一个新进程。 新进程便是我们所说的子进…

从0到1搭建react 工程化前端项目

一、npm init 初始化包管理 1.在使用该命令之前&#xff0c;创建一个文件夹&#xff0c;例如&#xff1a;reactDemo2.使用在电脑终端命令行工具中&#xff0c;找到1创建的文件夹&#xff0c;并转到改文件夹指定目录&#xff1b;3.执行 npm init4.如图所示&#xff1a; 5.执行命…

云看消博会:政策、技术、玩家造就的数字化革命

配图来自Canva可画 会展作为展示地域经济、文化、技术等软硬实力的最佳舞台&#xff0c;在塑造城市品牌形象、加速地域经济发展中发挥着重要的促进作用。近几年&#xff0c;在数字经济浪潮的推动下&#xff0c;会展产业走上了网联化、数字化、智能化的道路&#xff0c;催生了不…

上货避坑指南 私域上货选品工具 无货源选品上货 采集商品详情数据API分享 详情图 sku信息

电商开店之后&#xff0c;第一件事就是上货了&#xff0c;上货其实也是有技巧的。 上传商品时我们一定要注意细节&#xff0c;不可忽略一些重要细节&#xff0c;所以商家们在上传商品前&#xff0c;不可忽略是否预售、标题、主图、详情页、保证金、上架时间这几个细节。 详情…

PHP实现输入数值计算幂次,输入工资,判断个人所得税的金额这两个程序的代码

目录 前言 一、输入数值计算幂次 1.1运行流程&#xff08;思想&#xff09; 1.2代码段 1.3运行截图 二、输入工资&#xff0c;判断个人所得税的金额 2.1运行流程&#xff08;思想&#xff09; 2.2代码段 2.3运行截图 前言 1.因多重原因&#xff0c;本博文有两个代码程…

【动手学深度学习】使用块的网络(VGG)

使用块的网络&#xff08;VGG&#xff09; 本文为李沐老师《动手学深度学习》一书的学习笔记&#xff0c;原书地址为&#xff1a;Dive into Deep Learning。 另&#xff0c;给自己练习时没有gpu资源的小伙伴推荐下kaggle数据科学网站&#xff0c;每周免费训练时长30h。 1 网络结…