【surfaceflinger源码分析】surfaceflinger进程的消息驱动模型
概述
对于surfaceflinger大多数人都知道它的功能是做图形合成的,用英语表示就是指composite。其大致框图如下:
- 各个Android app将自己的图形画面通过surface为载体通过AIDL接口(Binder IPC)传递到surfaceflinger进程
- surfaceflinger进程中的composition engine与HWC协商,哪些图层HWC可以直接显示,哪些图层需要自己合成为一个图层后再送给HWC显示。
- surfaceflinger与HWC把合成策略协商完后,再将合成后的图层和独立显示的图层分别传递给HWC,HWC再操作硬件显示在屏幕上。
本系列文章会深入分析surfaceflinger的各个方面,力争弄懂下列问题:
- surfaceflinger做合成的动作是如何触发的?进程的消息模型是什么样的? 如何驱动循环做合成动作的?
- surface是什么? surface与图形数据buffer有什么关系?
- surface对应在surfaceflinger进程内用什么东西表示?各个app的图形数据buffer怎么传递过来的?
- 图形数据在surfaceflinger进程内部流转过程是什么样的?
- 既然涉及到跨进程传递,图形buffer的生产和消费是如何同步的?Fence是什么玩意?
- Vsync是个什么意思?有什么用?
本篇文章先来阅读源码分析下第一个问题。
surfaceflinger进程的main函数
int main(int, char**) {signal(SIGPIPE, SIG_IGN);hardware::configureRpcThreadpool(1 /* maxThreads */,false /* callerWillJoin */);startGraphicsAllocatorService();// When SF is launched in its own process, limit the number of// binder threads to 4.ProcessState::self()->setThreadPoolMaxThreadCount(4);// start the thread poolsp<ProcessState> ps(ProcessState::self());ps->startThreadPool();// instantiate surfaceflingersp<SurfaceFlinger> flinger = surfaceflinger::createSurfaceFlinger();setpriority(PRIO_PROCESS, 0, PRIORITY_URGENT_DISPLAY);set_sched_policy(0, SP_FOREGROUND);// Put most SurfaceFlinger threads in the system-background cpuset// Keeps us from unnecessarily using big cores// Do this after the binder thread pool initif (cpusets_enabled()) set_cpuset_policy(0, SP_SYSTEM);// initialize before clients can connectflinger->init();// publish surface flingersp<IServiceManager> sm(defaultServiceManager());sm->addService(String16(SurfaceFlinger::getServiceName()), flinger, false,IServiceManager::DUMP_FLAG_PRIORITY_CRITICAL | IServiceManager::DUMP_FLAG_PROTO);startDisplayService(); // dependency on SF getting registered aboveif (SurfaceFlinger::setSchedFifo(true) != NO_ERROR) {ALOGW("Couldn't set to SCHED_FIFO: %s", strerror(errno));}// run surface flinger in this threadflinger->run();return 0;
}
上面的代码大致可以缩减为三句话:
int main(int, char**) {..............................;sp<SurfaceFlinger> flinger = surfaceflinger::createSurfaceFlinger();..............................;flinger->init();..............................;flinger->run();return 0;
}
下面再分别看下这三个函数分别做了什么?
surfaceflinger::createSurfaceFlinger
sp<SurfaceFlinger> createSurfaceFlinger() {static DefaultFactory factory;return new SurfaceFlinger(factory);
}
surfaceflinger::init
主要是对子模块mCompositionEngine,Displays, RenderEngine等模块的初始化,貌似和消息传递关系不大,先放着不深入分析。
void SurfaceFlinger::init() {ALOGI( "SurfaceFlinger's main thread ready to run. ""Initializing graphics H/W...");Mutex::Autolock _l(mStateLock);mCompositionEngine->setRenderEngine(renderengine::RenderEngine::create(renderengine::RenderEngineCreationArgs::Builder().setPixelFormat(static_cast<int32_t>(defaultCompositionPixelFormat)).setImageCacheSize(maxFrameBufferAcquiredBuffers).setUseColorManagerment(useColorManagement).setEnableProtectedContext(enable_protected_contents(false)).setPrecacheToneMapperShaderOnly(false).setSupportsBackgroundBlur(mSupportsBlur).setContextPriority(useContextPriority? renderengine::RenderEngine::ContextPriority::HIGH: renderengine::RenderEngine::ContextPriority::MEDIUM).build()));mCompositionEngine->setTimeStats(mTimeStats);LOG_ALWAYS_FATAL_IF(mVrFlingerRequestsDisplay,"Starting with vr flinger active is not currently supported.");mCompositionEngine->setHwComposer(getFactory().createHWComposer(getBE().mHwcServiceName));mCompositionEngine->getHwComposer().setConfiguration(this, getBE().mComposerSequenceId);// Process any initial hotplug and resulting display changes.processDisplayHotplugEventsLocked();const auto display = getDefaultDisplayDeviceLocked();LOG_ALWAYS_FATAL_IF(!display, "Missing internal display after registering composer callback.");LOG_ALWAYS_FATAL_IF(!getHwComposer().isConnected(*display->getId()),"Internal display is disconnected.");................................................................;// initialize our drawing statemDrawingState = mCurrentState;// set initial conditions (e.g. unblank default device)initializeDisplays();char primeShaderCache[PROPERTY_VALUE_MAX];property_get("service.sf.prime_shader_cache", primeShaderCache, "1");if (atoi(primeShaderCache)) {getRenderEngine().primeCache();}// Inform native graphics APIs whether the present timestamp is supported:const bool presentFenceReliable =!getHwComposer().hasCapability(hal::Capability::PRESENT_FENCE_IS_NOT_RELIABLE);mStartPropertySetThread = getFactory().createStartPropertySetThread(presentFenceReliable);if (mStartPropertySetThread->Start() != NO_ERROR) {ALOGE("Run StartPropertySetThread failed!");}ALOGV("Done initializing");
}
surfaceflinger::run
看到死循环了,哈哈,这应该就是本篇文章要找的死循环。看来要重点看下这个mEventQueue在wait什么Message了,哪里来的Message。
void SurfaceFlinger::run() {while (true) {mEventQueue->waitMessage();}
}
std::unique_ptr mEventQueue创建
SurfaceFlinger::SurfaceFlinger(Factory& factory, SkipInitializationTag): mFactory(factory),mInterceptor(mFactory.createSurfaceInterceptor(this)),mTimeStats(std::make_shared<impl::TimeStats>()),mFrameTracer(std::make_unique<FrameTracer>()),mEventQueue(mFactory.createMessageQueue()),mCompositionEngine(mFactory.createCompositionEngine()),mInternalDisplayDensity(getDensityFromProperty("ro.sf.lcd_density", true)),mEmulatedDisplayDensity(getDensityFromProperty("qemu.sf.lcd_density", false)) {}std::unique_ptr<MessageQueue> DefaultFactory::createMessageQueue() {return std::make_unique<android::impl::MessageQueue>();
}
MessageQueue 的定义
可以看到MessageQueue类中还定义了一个Handler类。现在还不知道这些类中的成员变量和函数的作用。那么就从waitMessage()函数入手,一探究竟。
class MessageQueue final : public android::MessageQueue {class Handler : public MessageHandler {enum { eventMaskInvalidate = 0x1, eventMaskRefresh = 0x2, eventMaskTransaction = 0x4 };MessageQueue& mQueue;int32_t mEventMask;std::atomic<nsecs_t> mExpectedVSyncTime;public:explicit Handler(MessageQueue& queue) : mQueue(queue), mEventMask(0) {}virtual void handleMessage(const Message& message);void dispatchRefresh();void dispatchInvalidate(nsecs_t expectedVSyncTimestamp);};friend class Handler;sp<SurfaceFlinger> mFlinger;sp<Looper> mLooper;sp<EventThreadConnection> mEvents;gui::BitTube mEventTube;sp<Handler> mHandler;static int cb_eventReceiver(int fd, int events, void* data);int eventReceiver(int fd, int events);public:~MessageQueue() override = default;void init(const sp<SurfaceFlinger>& flinger) override;void setEventConnection(const sp<EventThreadConnection>& connection) override;void waitMessage() override;void postMessage(sp<MessageHandler>&&) override;// sends INVALIDATE message at next VSYNCvoid invalidate() override;// sends REFRESH message at next VSYNCvoid refresh() override;
};
MessageQueue
waitMessage源码分析
void MessageQueue::waitMessage() {do {IPCThreadState::self()->flushCommands();int32_t ret = mLooper->pollOnce(-1);switch (ret) {case Looper::POLL_WAKE:case Looper::POLL_CALLBACK:continue;case Looper::POLL_ERROR:ALOGE("Looper::POLL_ERROR");continue;case Looper::POLL_TIMEOUT:// timeout (should not happen)continue;default:// should not happenALOGE("Looper::pollOnce() returned unknown status %d", ret);continue;}} while (true);
}
看来要重点看下IPCThreadState和这个mLooper了
IPCThreadState这玩意是和Binder相关啊,看着这意思是要把Binder中的消息刷出来,看看各个app进程有没有通过Binder发消息过来。先放在着,后面回过头再来分析。
void IPCThreadState::flushCommands()
{if (mProcess->mDriverFD < 0)return;talkWithDriver(false);if (mOut.dataSize() > 0) {talkWithDriver(false);}if (mOut.dataSize() > 0) {ALOGW("mOut.dataSize() > 0 after flushCommands()");}
}
再来看看这个mLooper的实现。
class Looper : public RefBase {
protected:virtual ~Looper();
public:Looper(bool allowNonCallbacks);bool getAllowNonCallbacks() const;int pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData);inline int pollOnce(int timeoutMillis) {return pollOnce(timeoutMillis, nullptr, nullptr, nullptr);}int pollAll(int timeoutMillis, int* outFd, int* outEvents, void** outData);inline int pollAll(int timeoutMillis) {return pollAll(timeoutMillis, nullptr, nullptr, nullptr);}void wake();int addFd(int fd, int ident, int events, Looper_callbackFunc callback, void* data);int addFd(int fd, int ident, int events, const sp<LooperCallback>& callback, void* data);int removeFd(int fd);void sendMessage(const sp<MessageHandler>& handler, const Message& message);void sendMessageDelayed(nsecs_t uptimeDelay, const sp<MessageHandler>& handler,const Message& message);void sendMessageAtTime(nsecs_t uptime, const sp<MessageHandler>& handler,const Message& message);void removeMessages(const sp<MessageHandler>& handler);void removeMessages(const sp<MessageHandler>& handler, int what);bool isPolling() const;static sp<Looper> prepare(int opts);static void setForThread(const sp<Looper>& looper);static sp<Looper> getForThread();
private:struct Request {int fd;int ident;int events;int seq;sp<LooperCallback> callback;void* data;void initEventItem(struct epoll_event* eventItem) const;};struct Response {int events;Request request;};struct MessageEnvelope {MessageEnvelope() : uptime(0) { }MessageEnvelope(nsecs_t u, const sp<MessageHandler> h,const Message& m) : uptime(u), handler(h), message(m) {}nsecs_t uptime;sp<MessageHandler> handler;Message message;};const bool mAllowNonCallbacks; // immutableandroid::base::unique_fd mWakeEventFd; // immutableMutex mLock;Vector<MessageEnvelope> mMessageEnvelopes; // guarded by mLockbool mSendingMessage; // guarded by mLock// Whether we are currently waiting for work. Not protected by a lock,// any use of it is racy anyway.volatile bool mPolling;android::base::unique_fd mEpollFd; // guarded by mLock but only modified on the looper threadbool mEpollRebuildRequired; // guarded by mLock// Locked list of file descriptor monitoring requests.KeyedVector<int, Request> mRequests; // guarded by mLockint mNextRequestSeq;// This state is only used privately by pollOnce and does not require a lock since// it runs on a single thread.Vector<Response> mResponses;size_t mResponseIndex;nsecs_t mNextMessageUptime; // set to LLONG_MAX when noneint pollInner(int timeoutMillis);int removeFd(int fd, int seq);void awoken();void pushResponse(int events, const Request& request);void rebuildEpollLocked();void scheduleEpollRebuildLocked();static void initTLSKey();static void threadDestructor(void *st);static void initEpollEvent(struct epoll_event* eventItem);
};
先来看看这个pollOnce(-1)函数是在干啥 ?
int pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData);inline int pollOnce(int timeoutMillis) {return pollOnce(timeoutMillis, nullptr, nullptr, nullptr);}
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {int result = 0;for (;;) {//因为参数outFd, outEvents, outData都为null,所以此while循环不用太关注,//如果mResponses中有事件,就将mResponseIndex加一返回,没有事件就执行下面的pollInner//下面重点关注mResponses里面的item在哪里push进去的while (mResponseIndex < mResponses.size()) {const Response& response = mResponses.itemAt(mResponseIndex++);int ident = response.request.ident;if (ident >= 0) {int fd = response.request.fd;int events = response.events;void* data = response.request.data;if (outFd != nullptr) *outFd = fd;if (outEvents != nullptr) *outEvents = events;if (outData != nullptr) *outData = data;return ident;}}if (result != 0) {if (outFd != nullptr) *outFd = 0;if (outEvents != nullptr) *outEvents = 0;if (outData != nullptr) *outData = nullptr;return result;}//重点关注下这个实现result = pollInner(timeoutMillis);}
}
pollInner干了什么 ?
int Looper::pollInner(int timeoutMillis) {//计算出timeoutMillis作为epoll_wait的timeout时长if (timeoutMillis != 0 && mNextMessageUptime != LLONG_MAX) {nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);int messageTimeoutMillis = toMillisecondTimeoutDelay(now, mNextMessageUptime);if (messageTimeoutMillis >= 0&& (timeoutMillis < 0 || messageTimeoutMillis < timeoutMillis)) {timeoutMillis = messageTimeoutMillis;}}// Poll.int result = POLL_WAKE;mResponses.clear(); //清空mResponses,我擦,看来mResponses就是在这里push和clear的mResponseIndex = 0;mPolling = true;struct epoll_event eventItems[EPOLL_MAX_EVENTS];//epoll_wait 等待事件的到来,后面再具体看下这个epoll中都有哪些被监控的文件句柄?int eventCount = epoll_wait(mEpollFd.get(), eventItems, EPOLL_MAX_EVENTS, timeoutMillis);mPolling = false;mLock.lock();...........................;for (int i = 0; i < eventCount; i++) {int fd = eventItems[i].data.fd;uint32_t epollEvents = eventItems[i].events;if (fd == mWakeEventFd.get()) {............................;} else {ssize_t requestIndex = mRequests.indexOfKey(fd);if (requestIndex >= 0) {int events = 0;if (epollEvents & EPOLLIN) events |= EVENT_INPUT;if (epollEvents & EPOLLOUT) events |= EVENT_OUTPUT;if (epollEvents & EPOLLERR) events |= EVENT_ERROR;if (epollEvents & EPOLLHUP) events |= EVENT_HANGUP;//将epoll监控到的event push到mResponses中pushResponse(events, mRequests.valueAt(requestIndex));} else {ALOGW("Ignoring unexpected epoll events 0x%x on fd %d that is ""no longer registered.", epollEvents, fd);}}}
Done: mNextMessageUptime = LLONG_MAX;//处理mMessageEnvelopes中具体message,这里面的message哪里来的,现在还不知道while (mMessageEnvelopes.size() != 0) {nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);const MessageEnvelope& messageEnvelope = mMessageEnvelopes.itemAt(0);if (messageEnvelope.uptime <= now) {{ // obtain handlersp<MessageHandler> handler = messageEnvelope.handler;Message message = messageEnvelope.message;mMessageEnvelopes.removeAt(0);mSendingMessage = true;mLock.unlock();handler->handleMessage(message); //需要重点分析的函数} // release handlermLock.lock();mSendingMessage = false;result = POLL_CALLBACK;} else {// The last message left at the head of the queue determines the next wakeup time.mNextMessageUptime = messageEnvelope.uptime;break;}}mLock.unlock();//处理epoll监控到的事件,通过提前注册的callback来处理此event,这些callback是什么时候注册的,现在还不知道for (size_t i = 0; i < mResponses.size(); i++) {Response& response = mResponses.editItemAt(i);if (response.request.ident == POLL_CALLBACK) {int fd = response.request.fd;int events = response.events;void* data = response.request.data;int callbackResult = response.request.callback->handleEvent(fd, events, data);if (callbackResult == 0) {removeFd(fd, response.request.seq);}response.request.callback.clear();result = POLL_CALLBACK;}}return result;
}
通过上面的代码分析pollInner主要逻辑如下:
- 通过epoll_wait监控获取event,将获取到的event封装成response结构push到mResponses中。
- 处理mMessageEnvelopes中的message:
sp handler = messageEnvelope.handler;
Message message = messageEnvelope.message;
handler->handleMessage(message); - 处理mResponses中每一个response:
response.request.callback->handleEvent(fd, events, data)
现在新疑问来了,
- mMessageEnvelopes中message哪里来的?
- epoll中被监控的fd是什么后添加到epoll中的 ?
void MessageQueue::setEventConnection(const sp<EventThreadConnection>& connection) {if (mEventTube.getFd() >= 0) {mLooper->removeFd(mEventTube.getFd());}mEvents = connection;mEvents->stealReceiveChannel(&mEventTube);//往epoll中添加mEventTube的fd,其中MessageQueue::cb_eventReceiver是epoll监控到事件后执行的callbackmLooper->addFd(mEventTube.getFd(), 0, Looper::EVENT_INPUT, MessageQueue::cb_eventReceiver,this);
}
//找到答案了: "epoll中被监控的fd是什么后添加到epoll中的"
int Looper::addFd(int fd, int ident, int events, const sp<LooperCallback>& callback, void* data) {....................;{ // acquire lockAutoMutex _l(mLock);Request request;request.fd = fd;request.ident = ident;request.events = events;request.seq = mNextRequestSeq++;request.callback = callback;request.data = data;struct epoll_event eventItem;request.initEventItem(&eventItem);ssize_t requestIndex = mRequests.indexOfKey(fd);if (requestIndex < 0) {int epollResult = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, fd, &eventItem);mRequests.add(fd, request);} else {........................;}} // release lockreturn 1;
}int MessageQueue::cb_eventReceiver(int fd, int events, void* data) {MessageQueue* queue = reinterpret_cast<MessageQueue*>(data);//执行MessageQueue中的eventReceiver函数return queue->eventReceiver(fd, events);
}int MessageQueue::eventReceiver(int /*fd*/, int /*events*/) {ssize_t n;DisplayEventReceiver::Event buffer[8];//监控到mEventTube中有事件后,从mEventTube中读取事件,处理事件while ((n = DisplayEventReceiver::getEvents(&mEventTube, buffer, 8)) > 0) {for (int i = 0; i < n; i++) {if (buffer[i].header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC) {mHandler->dispatchInvalidate(buffer[i].vsync.expectedVSyncTimestamp);break;}}}return 1;
}void MessageQueue::Handler::dispatchInvalidate(nsecs_t expectedVSyncTimestamp) {if ((android_atomic_or(eventMaskInvalidate, &mEventMask) & eventMaskInvalidate) == 0) {mExpectedVSyncTime = expectedVSyncTimestamp;//从mEventTube读取VSYNC事件后,先Looper中发一个MessageQueue::INVALIDATE类型的消息mQueue.mLooper->sendMessage(this, Message(MessageQueue::INVALIDATE));}
}void Looper::sendMessage(const sp<MessageHandler>& handler, const Message& message) {nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);sendMessageAtTime(now, handler, message);
}void Looper::sendMessageDelayed(nsecs_t uptimeDelay, const sp<MessageHandler>& handler,const Message& message) {nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);sendMessageAtTime(now + uptimeDelay, handler, message);
}void Looper::sendMessageAtTime(nsecs_t uptime, const sp<MessageHandler>& handler,const Message& message) {{ // acquire lockAutoMutex _l(mLock);size_t messageCount = mMessageEnvelopes.size();while (i < messageCount && uptime >= mMessageEnvelopes.itemAt(i).uptime) {i += 1;}MessageEnvelope messageEnvelope(uptime, handler, message);//找到答案了: "mMessageEnvelopes中message哪里来的?"mMessageEnvelopes.insertAt(messageEnvelope, i, 1);if (mSendingMessage) {return;}} // release lockif (i == 0) {wake();}
}
上面的问题解答后,又来两个新疑问:
- MessageQueue::setEventConnection(…) 什么时候有谁调用的 ?
- mEventTube是个什么玩意?
下一篇文章继续深入分析,然后画图…
相关文章:

【surfaceflinger源码分析】surfaceflinger进程的消息驱动模型
概述 对于surfaceflinger大多数人都知道它的功能是做图形合成的,用英语表示就是指composite。其大致框图如下: 各个Android app将自己的图形画面通过surface为载体通过AIDL接口(Binder IPC)传递到surfaceflinger进程surfaceflinger进程中的composition engine与HW…...
「架构师」001计算机组成与体系结构
文章目录 前言一、计算机结构1.1 计算机组成结构1.2 CPU组成1.3 冯诺依曼结构与哈佛结构二、存储结构2.1 层次化存储结构2.2 Cache三、数据传输控制方式四、总线五、CISC与RISC六、流水线七、校验码八、嵌入式前言 本文主要介绍计算机组成与体系结构。 一、计算机结构 1.1 计…...

既然有HTTP协议,为什么还要有RPC
既然有HTTP协议,为什么还要有RPC? 从TCP聊起 作为一个程序员,假设我们需要在A电脑的进程发一段数据到B电脑的进程,我们一般会在代码里使用socket进行编程。 这时候,我们可选项一般也就TCP和UDP二选一。TCP可靠&…...
【新2023】华为OD机试 - 选座位(Python)
华为 OD 清单查看地址:blog.csdn.net/hihell/category_12199275.html 选座位 题目 疫情期间需要大家保证一定的社交距离 公司组织开交流会议,座位有一排共N个座位 编号分别为[0...n-1] 要求员工一个接着一个进入会议室 并且还可以在任何时候离开会议室 每当一个员工进入时…...

数据分析与SAS学习笔记4
INPUT语句:格式修饰符: “:” 修饰符。表示从下一个非空格列读入数据,直到:1 遇到再下一个空格列; 2 读到预先定义的变量长度; 3 数据行结束。哪个先出现就在哪儿结束。 “&” 修饰符。表示从下一个非空格列读入…...

Xepor:一款针对逆向工程和安全分析的Web路由框架
关于Xepor Xepor是一款专为逆向分析工程师和安全研究专家设计的Web路由框架,该工具可以为研究人员提供类似Flask API的功能,支持以人类友好的方式拦截和修改HTTP请求或HTTP响应信息。 该项目需要与mitmproxy一起结合使用,用户可以使用Xepor…...

Hadoop核心组成和生态系统简介
一、Hadoop的概念 Hadoop是一个由Apache基金会所开发的分布式系统基础架构。用户可以在不了解分布式底层细节的情况下,开发分布式程序。充分利用集群的威力进行高速运算和存储。Hadoop实现了一个分布式文件系统( Distributed File System)&am…...

Flutter-Charts_painter大数据量绘制性能优化-数据收敛
Flutter-Charts_painter大数据量绘制性能优化-数据收敛 1、背景介绍 HRV测量仪器上传的数据,每秒有250个数据,业务上需要测量180秒,预计有3w-5w个数据点需要绘制到折线图上去。Charts_painter绘制这么大的数据是时候会有些卡顿,…...

使用 GeForce Experience 更新 NVIDIA GPU 显卡驱动
使用 GeForce Experience 更新 NVIDIA GPU 显卡驱动1. NVIDIA GeForce Experience 2. 驱动程序 -> 检查更新文件 3. 下载 如果有可用的新版驱动的话,点击后方的 [下载] 按钮即可。 4. 安装 [快速安装] 按照默认设置安装驱动,[自定义安装] 可以自行…...
Java泛型的<? super T>,<? extend T>的区别
? extends T ? extends T 描述了通配符上界, 即具体的泛型参数需要满足条件: 泛型参数必须是 T 类型或它的子类, 例如: List<? extends Number> numberArray new ArrayList<Number>(); // Number 是 Number 类型的 List<? extends Number>…...

如何做出好看的Excel可视化图表?
可视化死磕excel是不行的,作为数据分析行业的偷懒大户,分享一些我在可视化工具上的使用心得,总结了三大类:快速出图类、专业图表类、高端大屏类。个人经验,大家按需采纳: 一、快速出图类 如果你只是因为偶…...
智能吸吹一体式方案设计特点
一、家用吸吹一体吸尘器方案研发设计要素: 1.小巧的机身设计,一手掌握,无论是床底、沙发下还是家具缝隙之中都能够使用。 2.无线,插电两用,在家方便可插电使用。内置可充电锂电池,充满电也可无线使用。 3.采…...

CSDN 编辑器 Marddown 语法备忘
原文链接:https://blog.csdn.net/blogdevteam/article/details/103478461 本文对其二次加工,增加渲染样式、补充例程、添加未收录的常用语法。 CSDN Markdown 编辑器遵循 CommonMark spec 语法规范。 快捷键 撤销:Ctrl/Command Z 重做&…...

回归预测 | MATLAB实现NGO-BiLSTM北方苍鹰算法优化双向长短期记忆网络多输入单输出回归预测
回归预测 | MATLAB实现NGO-BiLSTM北方苍鹰算法优化双向长短期记忆网络多输入单输出回归预测 目录回归预测 | MATLAB实现NGO-BiLSTM北方苍鹰算法优化双向长短期记忆网络多输入单输出回归预测预测效果基本介绍程序设计参考资料预测效果 基本介绍 Matlab实现NGO-BiLSTM北方苍鹰算法…...

Linux——操作系统安装
个人简介:云计算网络运维专业人员,了解运维知识,掌握TCP/IP协议,每天分享网络运维知识与技能。个人爱好: 编程,打篮球,计算机知识个人名言:海不辞水,故能成其大;山不辞石…...

AFLNET lightftp项目报错解决方法
在学习AFLNET的时候,本人尝试对示例项目中的lightftp进行fuzz,而后出现如下报错: AFLNet - the states hashtable should always contain an entry of the initial state 在github项目issue里看到了有人的问题和我一摸一样,Stack Overflow里…...
av 146 003
121. 团队章程的目标是什么? A. 使团队正规化,以便能够清楚地了解资源分配和参与情况 B. 创造一个团队可以自我管理和自我指导的环境 C. 创造一个环境,使团队成员能够尽其所能地工作 D. 创造一种团队归属感,促进包容性和协作性的行为 12…...

干了1年“点点点”,自己辞职了,下一步是继续干测试还是转开发?
最后后台有个粉丝向我吐槽,不知道怎么选择了....下面就他的情况说说怎么选择? 目前已经提桶跑路,在大工厂里混了半年初级低级功能测试经验,并没有什么用。测试培训班来的。从破山村贫困户贫困专项出去的,学校上海的。…...

国产技术迎来突破,14nm芯片横空出世,低代码也有好消息
芯片,被称为工业时代的“粮食”,小到手机手环,大到飞机轮船,几乎各个行业都不离开芯片的支持,其重要性不言而喻。而我国在这一领域一直较为薄弱。 一、“芯片之路坎坷” 由于国内半导体芯片市场底子薄弱、没有主动权…...
使用clickhouse-backup工具备份clickhouse数据库
工具官网:https://github.com/AlexAkulov/clickhouse-backup/dockerhub工具官网:https://hub.docker.com/r/alexakulov/clickhouse-backup注意:这个工具只支持MergeTree 系列表引擎一、clickhouse在容器外的备份和恢复若clickhouse装在容器外…...
web vue 项目 Docker化部署
Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage):…...
《Playwright:微软的自动化测试工具详解》
Playwright 简介:声明内容来自网络,将内容拼接整理出来的文档 Playwright 是微软开发的自动化测试工具,支持 Chrome、Firefox、Safari 等主流浏览器,提供多语言 API(Python、JavaScript、Java、.NET)。它的特点包括&a…...
Qt Widget类解析与代码注释
#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码,写上注释 当然可以!这段代码是 Qt …...

如何理解 IP 数据报中的 TTL?
目录 前言理解 前言 面试灵魂一问:说说对 IP 数据报中 TTL 的理解?我们都知道,IP 数据报由首部和数据两部分组成,首部又分为两部分:固定部分和可变部分,共占 20 字节,而即将讨论的 TTL 就位于首…...

脑机新手指南(七):OpenBCI_GUI:从环境搭建到数据可视化(上)
一、OpenBCI_GUI 项目概述 (一)项目背景与目标 OpenBCI 是一个开源的脑电信号采集硬件平台,其配套的 OpenBCI_GUI 则是专为该硬件设计的图形化界面工具。对于研究人员、开发者和学生而言,首次接触 OpenBCI 设备时,往…...

Proxmox Mail Gateway安装指南:从零开始配置高效邮件过滤系统
💝💝💝欢迎莅临我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:「storms…...
CppCon 2015 学习:Time Programming Fundamentals
Civil Time 公历时间 特点: 共 6 个字段: Year(年)Month(月)Day(日)Hour(小时)Minute(分钟)Second(秒) 表示…...

Win系统权限提升篇UAC绕过DLL劫持未引号路径可控服务全检项目
应用场景: 1、常规某个机器被钓鱼后门攻击后,我们需要做更高权限操作或权限维持等。 2、内网域中某个机器被钓鱼后门攻击后,我们需要对后续内网域做安全测试。 #Win10&11-BypassUAC自动提权-MSF&UACME 为了远程执行目标的exe或者b…...
Spring事务传播机制有哪些?
导语: Spring事务传播机制是后端面试中的必考知识点,特别容易出现在“项目细节挖掘”阶段。面试官通过它来判断你是否真正理解事务控制的本质与异常传播机制。本文将从实战与源码角度出发,全面剖析Spring事务传播机制,帮助你答得有…...
Docker环境下安装 Elasticsearch + IK 分词器 + Pinyin插件 + Kibana(适配7.10.1)
做RAG自己打算使用esmilvus自己开发一个,安装时好像网上没有比较新的安装方法,然后找了个旧的方法对应试试: 🚀 本文将手把手教你在 Docker 环境中部署 Elasticsearch 7.10.1 IK分词器 拼音插件 Kibana,适配中文搜索…...