【安卓源码】安卓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,大致可以分为两类:
- Monitor Checker,用于检查 Monitor 对象可能发生的死锁,AMS,IMS,WMS PMS 等核心的系统服务都是 Monitor 对象
- 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系统中,也设计了一个软件层面Watchdog,用于保护一些重要的系统服务,比如:AMS、WMS、PMS等,由于以上核心服务运行在system_server进程里面,所以当以上服务出现异常时,通常会将system_se…...

inscode连接不上gpu,持续8小时,为了数据不丢失续费了6小时,我只想知道什么时候可以连接
并且给我相应的补偿...

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

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

全网最细,真实企业性能测试落地实施,一文带你快速打通...
目录:导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜) 前言 1、什么是性能测试…...

三十一、【进阶】B+树的演变过程
1、B树简单介绍 (1)介绍:B树也属于B树,是B树的变种 (2)特点:所有的数据都位于叶子节点上,叶子节点上的所有元素形成了一个单项链表 (3)图示: 2…...

算法通过村第十三关-术数|白银笔记|术数高频问题
文章目录 前言数组实现加法专题数组实现整数加法字符串加法二进制加法 幂运算专题求2的次幂求3的次幂求4的次幂 总结 前言 提示:人心本易趋死寂,苦难之后,焕然重建,激荡一阵,又趋麻木。 --苏枕书《有鹿来》 我们继续看…...

Java 线程的生命周期
🙈作者简介:练习时长两年半的Java up主 🙉个人主页:程序员老茶 🙊 ps:点赞👍是免费的,却可以让写博客的作者开兴好久好久😎 📚系列专栏:Java全栈,…...

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

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

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

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

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

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

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

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

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

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

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

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

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

mysql基础语法速成版
mysql基础语法速成版 一、前言二、基础语法2.1 数据库操作2.2 MySQL数据类型2.3 表操作2.3.1 表的创建、删除,及表结构的改变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 #没有这个目录,s…...

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

《DevOps 精要:业务视角》- 读书笔记(七)
DevOps 精要:业务视角(七) DevOps历程什么是企业体系的DevOps?DevOps的目标是什么? DevOps的知识体系规范敏捷持续交付IT服务管理以TPS理念为基础 DevOps团队角色流程主管(Process Master)服务主管…...

【随想】每日两题Day.12(实则一题)
题目:15. 三数之和 给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k ,同时还满足 nums[i] nums[j] nums[k] 0 。请 你返回所有和为 0 且不重复的三元组。 注意:答案中不…...

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

什么是原型链(prototype chain)?如何实现继承?
聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅:探索Web开发的奇妙世界 欢迎来到前端入门之旅!感兴趣的可以订阅本专栏哦!这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…...

RabbitMQ 5种工作模式介绍和Springboot具体实现
RabbitMQ有5中工作模式:简单模式、工作队列模式、发布/订阅模式、路由模式和主题模式 简单模式(Simple Mode) 简单模式是最基本的工作模式,也是最简单的模式。在简单模式中,生产者将消息发送到一个队列中,…...

C++ - 可变模版参数 - emplace相关接口函数 - 移动构造函数 和 移动赋值运算符重载 的 默认成员函数
可变模版参数 我们先来了解一下,可变参数。可变参数就是在定义函数的时候,某一个参数位置使用 "..." 的方式来写的,在库当中有一个经典的函数系列就是用的 可变参数:printf()系列就是用的可变参…...