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

【安卓源码】安卓Watchdog 机制

在Android系统中,也设计了一个软件层面Watchdog,用于保护一些重要的系统服务,比如:AMS、WMS、PMS等,由于以上核心服务运行在system_server进程里面,所以当以上服务出现异常时,通常会将system_server进程kill掉,即让Android系统重启。

WatchDog功能主要是分析系统核心服务的重要线程、和锁 是否处于Blocked状态,即以下两个功能:

  • 监控 system_server 中几个关键的锁,原理是在 android_fg 线程中尝试加锁
  • 监控几个常用线程的执行时间,原理是在这几个线程中执行任务

WatchDog 的启动是在系统进程初始化后

/frameworks/base/services/java/com/android/server/SystemServer.java

991      private void startBootstrapServices(@NonNull TimingsTraceAndSlog t) {
992          t.traceBegin("startBootstrapServices");
993  
994          // Start the watchdog as early as possible so we can crash the system server
995          // if we deadlock during early boot
996          t.traceBegin("StartWatchdog");// 调用 Watchdog 的构造方法初始化
997          final Watchdog watchdog = Watchdog.getInstance();// 调用start 方法,启动线程
998          watchdog.start();
999          t.traceEnd();
1000  
1001          Slog.i(TAG, "Reading configuration...");
1002          final String TAG_SYSTEM_CONFIG = "ReadingSystemConfig";

1. WatchDog 的初始化

调用 Watchdog 的构造方法初始化

/frameworks/base/services/core/java/com/android/server/Watchdog.java

    public static Watchdog getInstance() {if (sWatchdog == null) {
// 单例模式,创建 Watchdog 对象sWatchdog = new Watchdog();}return sWatchdog;}

Watchdog 构造方法

    private Watchdog() {
// 创建线程,名字为 watchdog,线程调用run方法 mThread = new Thread(this::run, "watchdog");// 监听锁机制和 前台线程 FgThread 的handler处理 ,其也是单例模式,
// 提供给其他对象使用,如 PermissionManagerServicemMonitorChecker = new HandlerChecker(FgThread.getHandler(),"foreground thread", DEFAULT_TIMEOUT);// 将 mMonitorChecker 也保存到 mHandlerCheckers 中,去监听handler 是否超时,默认超时时间为 DEFAULT_TIMEOUT 30秒mHandlerCheckers.add(mMonitorChecker);// 监听系统进程的主线程mHandlerCheckers.add(new HandlerChecker(new Handler(Looper.getMainLooper()),"main thread", DEFAULT_TIMEOUT));//  监听ui线程 UI thread.mHandlerCheckers.add(new HandlerChecker(UiThread.getHandler(),"ui thread", DEFAULT_TIMEOUT));
//  监听Io线程 mHandlerCheckers.add(new HandlerChecker(IoThread.getHandler(),"i/o thread", DEFAULT_TIMEOUT));//  监听DisplayThread线程 mHandlerCheckers.add(new HandlerChecker(DisplayThread.getHandler(),"display thread", DEFAULT_TIMEOUT));//  监听动画 AnimationThread线程 mHandlerCheckers.add(new HandlerChecker(AnimationThread.getHandler(),"animation thread", DEFAULT_TIMEOUT));//  监听surface animation thread.也是关于动画的mHandlerCheckers.add(new HandlerChecker(SurfaceAnimationThread.getHandler(),"surface animation thread", DEFAULT_TIMEOUT));// 监听是否有可用的binder 线程addMonitor(new BinderThreadMonitor());// 将系统进程增加到感兴趣的进程队列中mInterestingJavaPids.add(Process.myPid());// See the notes on DEFAULT_TIMEOUT.assert DB ||DEFAULT_TIMEOUT > ZygoteConnectionConstants.WRAPPED_PID_TIMEOUT_MILLIS;mTraceErrorLogger = new TraceErrorLogger();}

在 SystemServer 启动过程中初始化 Watchdog。Watchdog 除了在初始化时创建一个线程mThread ,还会构建很多 HandlerChecker,大致可以分为两类:

  1. Monitor Checker,用于检查 Monitor 对象可能发生的死锁,AMS,IMS,WMS PMS 等核心的系统服务都是 Monitor 对象
  2. Looper Checker,用于检查线程的消息队列是否长时间处于工作状态。Watchdog 自身的消息队列,ui,io, Display 这些全局的消息队列都是被检查的对象。此外,一些重要的线程的消息队列,也会加入到 Looper Checker中,譬如 AMS,WMS 这些是在对应的对象初始化时加入的

new HandlerChecker 的构造函数初始化

// HandlerChecker 实现了 Runnable接口,会回调run 方法public final class HandlerChecker implements Runnable {private final Handler mHandler;private final String mName;private final long mWaitMax;private final ArrayList<Monitor> mMonitors = new ArrayList<Monitor>();private final ArrayList<Monitor> mMonitorQueue = new ArrayList<Monitor>();private boolean mCompleted;private Monitor mCurrentMonitor;private long mStartTime;private int mPauseCount;HandlerChecker(Handler handler, String name, long waitMaxMillis) {mHandler = handler;mName = name;
// 等待的最长时间设置给 mWaitMaxmWaitMax = waitMaxMillis;
// 初始化 mCompleted 为 truemCompleted = true;}// 增加监听锁的方法,调用的接口是 addMonitorvoid addMonitorLocked(Monitor monitor) {// We don't want to update mMonitors when the Handler is in the middle of checking// all monitors. We will update mMonitors on the next schedule if it is safemMonitorQueue.add(monitor);}public void addMonitor(Monitor monitor) {synchronized (mLock) {mMonitorChecker.addMonitorLocked(monitor);}}

系统进程的service 给watchdog 监听

/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

由前面分析,监听是否持锁时间长,在 前台线程去监听 

// ams 实现了 Watchdog.Monitor 接口
431  public class ActivityManagerService extends IActivityManager.Stub
432          implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback, ActivityManagerGlobalLock {2226      public ActivityManagerService(Context systemContext, ActivityTaskManagerService atm) {
2227          LockGuard.installLock(this, LockGuard.INDEX_ACTIVITY);
2228          mInjector = new Injector(systemContext);
2229          mContext = systemContext;。。。。
// 监听是否持锁时间长
2328          Watchdog.getInstance().addMonitor(this);
// 监听ams 的handler 是否超时
2329          Watchdog.getInstance().addThread(mHandler);// ams 实现了 Watchdog.Monitor 接口,会回调 monitor 方法
15024      public void monitor() {
15025          synchronized (this) { }
15026      }

同样wms 也监听了锁是否超时

/frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java

330  public class WindowManagerService extends IWindowManager.Stub
331          implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs {1417      public void onInitReady() {
1418          initPolicy();
1419  
1420          // Add ourself to the Watchdog monitors.
1421          Watchdog.getInstance().addMonitor(this);=========
6658      @Override
6659      public void monitor() {// 监听mGlobalLock 锁
6660          synchronized (mGlobalLock) { }
6661      }

2. 调用start 方法,启动WatchDog 线程监听

    public void start() {
// 即调用this::run 方法mThread.start();}

调用this::run 方法

/frameworks/base/services/core/java/com/android/server/Watchdog.java

    private void run() {boolean waitedHalf = false;while (true) {List<HandlerChecker> blockedCheckers = Collections.emptyList();String subject = "";boolean allowRestart = true;int debuggerWasConnected = 0;boolean doWaitedHalfDump = false;final ArrayList<Integer> pids;synchronized (mLock) {// timeout 为 30 秒long timeout = CHECK_INTERVAL;// Make sure we (re)spin the checkers that have become idle within// this wait-and-check interval// 2-1)遍历所有的 HandlerCheckers,去post消息看是否超时for (int i=0; i<mHandlerCheckers.size(); i++) {HandlerChecker hc = mHandlerCheckers.get(i);hc.scheduleCheckLocked();}if (debuggerWasConnected > 0) {debuggerWasConnected--;}// 记录开始的时间long start = SystemClock.uptimeMillis();while (timeout > 0) {if (Debug.isDebuggerConnected()) {debuggerWasConnected = 2;}try {
// 等待 30 秒mLock.wait(timeout);// Note: mHandlerCheckers and mMonitorChecker may have changed after waiting} catch (InterruptedException e) {Log.wtf(TAG, e);}if (Debug.isDebuggerConnected()) {debuggerWasConnected = 2;}
// 保证执行等待30秒,然后跳出循环timeout = CHECK_INTERVAL - (SystemClock.uptimeMillis() - start);}// 2-2)去计算等待的状态 evaluateCheckerCompletionLockedfinal int waitState = evaluateCheckerCompletionLocked();if (waitState == COMPLETED) {waitedHalf = false;continue;} else if (waitState == WAITING) {continue;
// 2-3)处理时间大于 30秒但是小于60秒流程} else if (waitState == WAITED_HALF) {if (!waitedHalf) {Slog.i(TAG, "WAITED_HALF");
// 则设置 waitedHalf = truewaitedHalf = true;// We've waited half, but we'd need to do the stack trace dump w/o the lock.pids = new ArrayList<>(mInterestingJavaPids);
// 设置 doWaitedHalfDump = truedoWaitedHalfDump = true;} else {continue;}} else {
// 2-4)执行超时的流程blockedCheckers = getBlockedCheckersLocked();subject = describeCheckersLocked(blockedCheckers);allowRestart = mAllowRestart;pids = new ArrayList<>(mInterestingJavaPids);}} // END synchronized (mLock)if (doWaitedHalfDump) {
// 超时 30秒后,会ams先dump 消息ActivityManagerService.dumpStackTraces(pids, null, null,getInterestingNativePids(), null, subject);continue;}// 超时会打印下列logEventLog.writeEvent(EventLogTags.WATCHDOG, subject);

2-1)遍历所有的 HandlerCheckers,去post消息看是否超时

        public void scheduleCheckLocked() {if (mCompleted) {// Safe to update monitors in queue, Handler is not in the middle of workmMonitors.addAll(mMonitorQueue);mMonitorQueue.clear();}// 当不是前台线程 FgThread,并且在轮询状态;或者调用了 pauseLocked 则直接return 出去if ((mMonitors.size() == 0 && mHandler.getLooper().getQueue().isPolling())|| (mPauseCount > 0)) {// Don't schedule until after resume OR// If the target looper has recently been polling, then// there is no reason to enqueue our checker on it since that// is as good as it not being deadlocked.  This avoid having// to do a context switch to check the thread. Note that we// only do this if we have no monitors since those would need to// be executed at this point.mCompleted = true;return;}
// mCompleted 为false 则表示在查询if (!mCompleted) {// we already have a check in flight, so no needreturn;}mCompleted = false;mCurrentMonitor = null;
// 设置开始调用的时间mStartTime = SystemClock.uptimeMillis();
// 将this 是runable,插入到消息队列的对头mHandler.postAtFrontOfQueue(this);}

如果消息处理的话,则会执行run 方法:

        @Overridepublic void run() {final int size = mMonitors.size();
// 如果是 FgThread 则有监听持锁是否久,则会回调 monitor 方法for (int i = 0 ; i < size ; i++) {synchronized (mLock) {mCurrentMonitor = mMonitors.get(i);}
// 可能会卡住,则不会设置 mCompleted 为 truemCurrentMonitor.monitor();}synchronized (mLock) {
// 执行完则会mCompleted设置为 truemCompleted = true;mCurrentMonitor = null;}}

2-2)去计算等待的状态 evaluateCheckerCompletionLocked

  • COMPLETED = 0:等待完成;
  • WAITING = 1:等待时间小于DEFAULT_TIMEOUT的一半,即30s;
  • WAITED_HALF = 2:等待时间处于30s~60s之间;
  • OVERDUE = 3:等待时间大于或等于60s。
// 有下列 4 种状态private static final int COMPLETED = 0;private static final int WAITING = 1;private static final int WAITED_HALF = 2;private static final int OVERDUE = 3;private int evaluateCheckerCompletionLocked() {int state = COMPLETED;for (int i=0; i<mHandlerCheckers.size(); i++) {HandlerChecker hc = mHandlerCheckers.get(i);
// 同样也是遍历所有的 HandlerCheckerstate = Math.max(state, hc.getCompletionStateLocked());}return state;}========
// 调用 getCompletionStateLocked 方法public int getCompletionStateLocked() {// 如果1.对应handler 有处理队头消息;2.Fgthread 处理了队头消息并且监听的锁没有超时;则 mCompleted 为 trueif (mCompleted) {return COMPLETED;} else {long latency = SystemClock.uptimeMillis() - mStartTime;
// 如果是处理的时间小于 30 秒,则设置状态为等待 WAITINGif (latency < mWaitMax/2) {return WAITING;
// 如果处理的时间大于 30 秒,但是小于 60s,则设置状态为 WAITED_HALF} else if (latency < mWaitMax) {return WAITED_HALF;}}return OVERDUE;}

2-3)处理时间大于 30秒但是小于60秒流程 WAITED_HALF

// 2-3)处理时间大于 30秒但是小于60秒流程} else if (waitState == WAITED_HALF) {if (!waitedHalf) {Slog.i(TAG, "WAITED_HALF");
// 则设置 waitedHalf = truewaitedHalf = true;// We've waited half, but we'd need to do the stack trace dump w/o the lock.pids = new ArrayList<>(mInterestingJavaPids);
// 设置 doWaitedHalfDump = truedoWaitedHalfDump = true;} else {continue;}} else {
// 执行超时的流程blockedCheckers = getBlockedCheckersLocked();subject = describeCheckersLocked(blockedCheckers);allowRestart = mAllowRestart;pids = new ArrayList<>(mInterestingJavaPids);}} // END synchronized (mLock)if (doWaitedHalfDump) {
// 超时 30秒后,会ams先dump 消息,然后 continue 不往下执行
// dump 出 NATIVE_STACKS_OF_INTEREST 数组中的进程信息ActivityManagerService.dumpStackTraces(pids, null, null,getInterestingNativePids(), null, subject);continue;}

2-4)执行超时的流程

} else if (waitState == WAITED_HALF) {if (!waitedHalf) {Slog.i(TAG, "WAITED_HALF");} else {
// 执行超时的流程
// 首先getBlockedCheckersLocked获取到执行超时的 HandlerChecker 对应的线程blockedCheckers = getBlockedCheckersLocked();// 获取超时的信息 describeCheckersLockedsubject = describeCheckersLocked(blockedCheckers);
// 默认是允许重启的allowRestart = mAllowRestart;pids = new ArrayList<>(mInterestingJavaPids);}} // END synchronized (mLock)。。。。
// 会打印下列logEventLog.writeEvent(EventLogTags.WATCHDOG, subject);final UUID errorId;if (mTraceErrorLogger.isAddErrorIdEnabled()) {errorId = mTraceErrorLogger.generateErrorId();mTraceErrorLogger.addErrorIdToTrace("system_server", errorId);} else {errorId = null;}FrameworkStatsLog.write(FrameworkStatsLog.SYSTEM_SERVER_WATCHDOG_OCCURRED, subject);long anrTime = SystemClock.uptimeMillis();StringBuilder report = new StringBuilder();report.append(MemoryPressureUtil.currentPsiState());ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(false);StringWriter tracesFileException = new StringWriter();final File stack = ActivityManagerService.dumpStackTraces(pids, processCpuTracker, new SparseArray<>(), getInterestingNativePids(),tracesFileException, subject);// Give some extra time to make sure the stack traces get written.// The system's been hanging for a minute, another second or two won't hurt much.SystemClock.sleep(5000);processCpuTracker.update();report.append(processCpuTracker.printCurrentState(anrTime));report.append(tracesFileException.getBuffer());// Trigger the kernel to dump all blocked threads, and backtraces on all CPUs to the kernel logdoSysRq('w');doSysRq('l');// Try to add the error to the dropbox, but assuming that the ActivityManager// itself may be deadlocked.  (which has happened, causing this statement to// deadlock and the watchdog as a whole to be ineffective)Thread dropboxThread = new Thread("watchdogWriteToDropbox") {public void run() {// If a watched thread hangs before init() is called, we don't have a// valid mActivity. So we can't log the error to dropbox.if (mActivity != null) {mActivity.addErrorToDropBox("watchdog", null, "system_server", null, null, null,null, report.toString(), stack, null, null, null,errorId);}}};dropboxThread.start();try {dropboxThread.join(2000);  // wait up to 2 seconds for it to return.} catch (InterruptedException ignored) {}IActivityController controller;synchronized (mLock) {controller = mController;}if (controller != null) {Slog.i(TAG, "Reporting stuck state to activity controller");try {Binder.setDumpDisabled("Service dumps disabled due to hung system process.");// 1 = keep waiting, -1 = kill systemint res = controller.systemNotResponding(subject);if (res >= 0) {Slog.i(TAG, "Activity controller requested to coninue to wait");waitedHalf = false;continue;}} catch (RemoteException e) {}}// Only kill the process if the debugger is not attached.if (Debug.isDebuggerConnected()) {debuggerWasConnected = 2;}if (debuggerWasConnected >= 2) {Slog.w(TAG, "Debugger connected: Watchdog is *not* killing the system process");} else if (debuggerWasConnected > 0) {Slog.w(TAG, "Debugger was connected: Watchdog is *not* killing the system process");} else if (!allowRestart) {Slog.w(TAG, "Restart not allowed: Watchdog is *not* killing the system process");} else {Slog.w(TAG, "*** WATCHDOG KILLING SYSTEM PROCESS: " + subject);WatchdogDiagnostics.diagnoseCheckers(blockedCheckers);Slog.w(TAG, "*** GOODBYE!");if (!Build.IS_USER && isCrashLoopFound()&& !WatchdogProperties.should_ignore_fatal_count().orElse(false)) {breakCrashLoop();}// 杀掉系统进程Process.killProcess(Process.myPid());System.exit(10);}waitedHalf = false;}}

// 首先getBlockedCheckersLocked获取到执行超时的 HandlerChecker 对应的线程

    private ArrayList<HandlerChecker> getBlockedCheckersLocked() {ArrayList<HandlerChecker> checkers = new ArrayList<HandlerChecker>();
// 同样也是遍历所有的 mHandlerCheckersfor (int i=0; i<mHandlerCheckers.size(); i++) {HandlerChecker hc = mHandlerCheckers.get(i);// 通过下列方法isOverdueLocked看是否是超时的if (hc.isOverdueLocked()) {checkers.add(hc);}}return checkers;}==========
// 通过下列方法看是否是超时的boolean isOverdueLocked() {return (!mCompleted) && (SystemClock.uptimeMillis() > mStartTime + mWaitMax);}

// 获取超时的信息 describeCheckersLocked

    private String describeCheckersLocked(List<HandlerChecker> checkers) {StringBuilder builder = new StringBuilder(128);for (int i=0; i<checkers.size(); i++) {if (builder.length() > 0) {builder.append(", ");}builder.append(checkers.get(i).describeBlockedStateLocked());}return builder.toString();}=======String describeBlockedStateLocked() {
// 如果 mCurrentMonitor 为空,则表示不是锁超时出现问题,而是handler,则打印handler 信息if (mCurrentMonitor == null) {return "Blocked in handler on " + mName + " (" + getThread().getName() + ")";} else {// 不为空,则是锁出现问题,只能在 FgThread 中return "Blocked in monitor " + mCurrentMonitor.getClass().getName()+ " on " + mName + " (" + getThread().getName() + ")";}}

Watchdog 检测到异常的信息收集

  • AMS.dumpStackTraces:输出Java和Native进程的栈信息
  • doSysRq
  • dropBox

收集完信息后便会杀死 system_server 进程。此处 allowRestart 默认值为 true,当执行 am hang 操作则设置不允许重启 (allowRestart =false), 则不会杀死 system_server 进程。
 

相关文章:

【安卓源码】安卓Watchdog 机制

在Android系统中&#xff0c;也设计了一个软件层面Watchdog&#xff0c;用于保护一些重要的系统服务&#xff0c;比如&#xff1a;AMS、WMS、PMS等&#xff0c;由于以上核心服务运行在system_server进程里面&#xff0c;所以当以上服务出现异常时&#xff0c;通常会将system_se…...

inscode连接不上gpu,持续8小时,为了数据不丢失续费了6小时,我只想知道什么时候可以连接

并且给我相应的补偿...

QT位置相关函数

Qt&#xff08;Qt Framework&#xff09;是一个流行的C应用程序开发框架&#xff0c;提供了丰富的位置相关函数和类&#xff0c;用于处理窗口、窗口小部件和图形的位置和几何操作。以下是一些常用的Qt位置相关函数和类&#xff1a; QPoint&#xff1a;QPoint类表示一个二维点的…...

vulnhub靶场 Kioptrix-level-1

简介&#xff1a; vulnhub是一个提供靶场环境的平台。而Kioptrix-level-1就是一个对新手比较友好的靶场。初学渗透的同学可以做做试试看&#xff0c;项目地址如下。 项目地址&#xff1a;Kioptrix: Level 1 (#1) ~ VulnHub 信息收集 查看本机IP&#xff0c;靶机跟kali都是使用…...

全网最细,真实企业性能测试落地实施,一文带你快速打通...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、什么是性能测试…...

三十一、【进阶】B+树的演变过程

1、B树简单介绍 &#xff08;1&#xff09;介绍&#xff1a;B树也属于B树&#xff0c;是B树的变种 &#xff08;2&#xff09;特点&#xff1a;所有的数据都位于叶子节点上&#xff0c;叶子节点上的所有元素形成了一个单项链表 &#xff08;3&#xff09;图示&#xff1a; 2…...

算法通过村第十三关-术数|白银笔记|术数高频问题

文章目录 前言数组实现加法专题数组实现整数加法字符串加法二进制加法 幂运算专题求2的次幂求3的次幂求4的次幂 总结 前言 提示&#xff1a;人心本易趋死寂&#xff0c;苦难之后&#xff0c;焕然重建&#xff0c;激荡一阵&#xff0c;又趋麻木。 --苏枕书《有鹿来》 我们继续看…...

Java 线程的生命周期

&#x1f648;作者简介&#xff1a;练习时长两年半的Java up主 &#x1f649;个人主页&#xff1a;程序员老茶 &#x1f64a; ps:点赞&#x1f44d;是免费的&#xff0c;却可以让写博客的作者开兴好久好久&#x1f60e; &#x1f4da;系列专栏&#xff1a;Java全栈&#xff0c;…...

Vue页面监听键盘按键的多种方法

在Vue页面中&#xff0c;可以使用多种方法来监听键盘按键。以下是至少五种常用的方法&#xff1a; 使用keydown或keyup指令来绑定键盘按键事件。 <template><div><input type"text" keydown.enter"handleEnterKey" /></div> <…...

解析硬件连通性测试的重要性及测试方法

在现代科技世界中&#xff0c;硬件设备的复杂性和多样性已经达到了前所未有的水平。无论是计算机、智能手机、物联网设备还是嵌入式系统&#xff0c;各种硬件组件的协同工作对于设备的正常运行至关重要。硬件连通性测试是确保这些组件相互配合无误的重要步骤。 一、硬件连通性测…...

Hive窗口函数回顾

1.语法 1.1 基于行的窗口函数 Hive的窗口函数分为两种类型&#xff0c;一种是基于行的窗口函数&#xff0c;即将某个字段的多行限定为一个范围&#xff0c;对范围内的字段值进行计算&#xff0c;最后将形成的字段拼接在该表上。 注意&#xff1a;在进行窗口函数计算之前&#…...

flink自定义窗口分配器

背景 我们知道处理常用的滑动窗口分配器&#xff0c;滚动窗口分配器&#xff0c;全局窗口分配器&#xff0c;会话窗口分配器外&#xff0c;我们可以实现自己的自定义窗口分配器&#xff0c;以实现我们的自己的窗口逻辑 自定义窗口分配器的实现 package wikiedits.assigner;i…...

iOS CGRect CGPoint NSRange等结构体的NSLog打印输出

iOS的UIKit里提供了UIGeometry.h内有各结构体转换成NSString的方法&#xff0c;可用于打印输出&#xff1b; UIKIT_EXTERN NSString *NSStringFromCGPoint(CGPoint point); UIKIT_EXTERN NSString *NSStringFromCGVector(CGVector vector); UIKIT_EXTERN NSString *NSStringFr…...

Viper FTP Mac/ftp管理工具

Viper FTP 是一个用于文件传输和管理的 Mac 应用程序。它允许用户上传、下载和管理远程服务器上的文件&#xff0c;以及在不同本地文件夹之间传输文件。 Viper FTP 支持广泛的文件传输协议&#xff0c;包括 FTP、SFTP、WebDav、Amazon S3、Google Drive 等。它还包括文件同步、…...

web漏洞-xml外部实体注入(XXE)

web漏洞-xml外部实体注入&#xff08;XXE&#xff09; 目录 web漏洞-xml外部实体注入&#xff08;XXE&#xff09;概念危害检测方法利用方法漏洞利用xxe-lab有回显情况无回显情况 pikachu靶场有回显内容无回显 修复方案 概念 xml可拓展标记语言&#xff1a; xml是一种可拓展的标…...

Impeller-Flutter的新渲染引擎

Impeller是什么&#xff1f;它本质上是怎样运行的&#xff1f; Impeller是Flutter的新的渲染引擎&#xff0c;直到现在Flutter正在用一个叫做Skia的渲染引擎。 问题是Skia不是为了Flutter量身定做的。它有为范围广阔的设备构建的一大堆的渲染特性&#xff0c;这意味着它并不总…...

python 面试算法题

1.第一题 题目描述:给定两个字符串, s 和 goal。如果在若干次旋转操作之后&#xff0c;s 能变成 goal &#xff0c;那么返回 true 。 s 的 旋转操作 就是将 s 最左边的字符移动到最右边。 例如, 若 s abcde&#xff0c;在旋转一次之后结果就是bcdea 。 示例一: 输入: s &quo…...

Python中的yield关键字

基本概念 yield 是 Python 中的一个关键字&#xff0c;主要在定义生成器函数时使用。使用 yield 的函数在调用时返回一个特殊的迭代器&#xff0c;称为生成器。不同于常规的函数返回一个单一的值&#xff08;如数字、字符串或其他对象&#xff09;&#xff0c;带有 yield 的函…...

怎么压缩pdf文件?分享缩小pdf文件的简单方法

在我们的日常生活和工作中&#xff0c;往往需要处理大量的PDF文件&#xff0c;而很多时候这些文件的大小会成为传输和存储的难题。为了解决这个问题&#xff0c;下面我们将介绍三种方法来压缩PDF文件&#xff0c;一起来看看吧~ 一、嗨格式压缩大师 首先&#xff0c;最简单也是…...

51单片机可调幅度频率波形信号发生器( proteus仿真+程序+原理图+报告+讲解视频)

51单片机可调幅度频率信号发生器( proteus仿真程序原理图报告讲解视频&#xff09; 讲解视频1.主要功能&#xff1a;2.仿真3. 程序代码4. 原理图4. 设计报告5. 设计资料内容清单&&下载链接***[资料下载链接](https://docs.qq.com/doc/DS1daV1BKRXZMeE9u)*** 51单片机可…...

Vuex的介绍

介绍 :::warning 注意 在阅读此文章之前请确保你已经掌握了组件中的选项 data、计算属性 computed、methods 方法等相关知识。 ::: 什么是 Vuex&#xff1f; Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态&#xff0c;并以…...

mysql基础语法速成版

mysql基础语法速成版 一、前言二、基础语法2.1 数据库操作2.2 MySQL数据类型2.3 表操作2.3.1 表的创建、删除&#xff0c;及表结构的改变2.3.2表数据的增删改查2.3.4 like模糊查询2.3.5 UNION 操作符2.3.6 order by排序2.3.7 group by分组2.3.8 join连接2.3.9 null处理2.3.10 m…...

Docker镜像 配置ssh

安装 1.安装ssh 2.设置root密码 RUN echo root:123456 | chpasswd 3.设置sshd config RUN echo Port 22 >> /etc/ssh/sshd_config RUN echo PermitRootLogin yes >> /etc/ssh/sshd_config4.设置开机启动 RUN mkdir /var/run/sshd #没有这个目录&#xff0c;s…...

12.2 实现键盘模拟按键

本节将向读者介绍如何使用键盘鼠标操控模拟技术&#xff0c;键盘鼠标操控模拟技术是一种非常实用的技术&#xff0c;可以自动化执行一些重复性的任务&#xff0c;提高工作效率&#xff0c;在Windows系统下&#xff0c;通过使用各种键盘鼠标控制函数实现动态捕捉和模拟特定功能的…...

《DevOps 精要:业务视角》- 读书笔记(七)

DevOps 精要:业务视角&#xff08;七&#xff09; DevOps历程什么是企业体系的DevOps&#xff1f;DevOps的目标是什么&#xff1f; DevOps的知识体系规范敏捷持续交付IT服务管理以TPS理念为基础 DevOps团队角色流程主管&#xff08;Process Master&#xff09;服务主管&#xf…...

【随想】每日两题Day.12(实则一题)

题目&#xff1a;15. 三数之和 给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k &#xff0c;同时还满足 nums[i] nums[j] nums[k] 0 。请 你返回所有和为 0 且不重复的三元组。 注意&#xff1a;答案中不…...

基于复旦微JFM7K325T FPGA的高性能PCIe总线数据预处理载板(100%国产化)

PCIE711是一款基于PCIE总线架构的高性能数据预处理FMC载板&#xff0c;板卡采用复旦微的JFM7K325T FPGA作为实时处理器&#xff0c;实现各个接口之间的互联。该板卡可以实现100%国产化。 板卡具有1个FMC&#xff08;HPC&#xff09;接口&#xff0c;1路PCIe x8主机接口&#x…...

什么是原型链(prototype chain)?如何实现继承?

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…...

RabbitMQ 5种工作模式介绍和Springboot具体实现

RabbitMQ有5中工作模式&#xff1a;简单模式、工作队列模式、发布/订阅模式、路由模式和主题模式 简单模式&#xff08;Simple Mode&#xff09; 简单模式是最基本的工作模式&#xff0c;也是最简单的模式。在简单模式中&#xff0c;生产者将消息发送到一个队列中&#xff0c;…...

C++ - 可变模版参数 - emplace相关接口函数 - 移动构造函数 和 移动赋值运算符重载 的 默认成员函数

可变模版参数 我们先来了解一下&#xff0c;可变参数。可变参数就是在定义函数的时候&#xff0c;某一个参数位置使用 "..." 的方式来写的&#xff0c;在库当中有一个经典的函数系列就是用的 可变参数&#xff1a;printf&#xff08;&#xff09;系列就是用的可变参…...