在update_engine-整体结构(二)中分析到了Action,那么我们接着继续分析.

首先来看一下BuildUpdateActons(...)这个方法。

src/system/update_engine/update_attempter_android.cc

  void UpdateAttempterAndroid::BuildUpdateActions(const string& url) {
CHECK(!processor_->IsRunning());
processor_->set_delegate(this); // Actions:
shared_ptr<InstallPlanAction> install_plan_action(
new InstallPlanAction(install_plan_)); HttpFetcher* download_fetcher = nullptr;
if (FileFetcher::SupportedUrl(url)) {
DLOG(INFO) << "Using FileFetcher for file URL.";
download_fetcher = new FileFetcher();
} else {
#ifdef _UE_SIDELOAD
LOG(FATAL) << "Unsupported sideload URI: " << url;
#else
LibcurlHttpFetcher* libcurl_fetcher =
new LibcurlHttpFetcher(&proxy_resolver_, hardware_);
libcurl_fetcher->set_server_to_check(ServerToCheck::kDownload);
download_fetcher = libcurl_fetcher;
#endif // _UE_SIDELOAD
}
shared_ptr<DownloadAction> download_action(
new DownloadAction(prefs_,
boot_control_,
hardware_,
nullptr, // system_state, not used.
download_fetcher)); // passes ownership
shared_ptr<FilesystemVerifierAction> filesystem_verifier_action(
new FilesystemVerifierAction()); shared_ptr<PostinstallRunnerAction> postinstall_runner_action(
new PostinstallRunnerAction(boot_control_, hardware_)); download_action->set_delegate(this);
download_action->set_base_offset(base_offset_);
download_action_ = download_action;
postinstall_runner_action->set_delegate(this); actions_.push_back(shared_ptr<AbstractAction>(install_plan_action));
actions_.push_back(shared_ptr<AbstractAction>(download_action));
actions_.push_back(shared_ptr<AbstractAction>(filesystem_verifier_action));
actions_.push_back(shared_ptr<AbstractAction>(postinstall_runner_action)); // Bond them together. We have to use the leaf-types when calling
// BondActions().
BondActions(install_plan_action.get(), download_action.get());
BondActions(download_action.get(), filesystem_verifier_action.get());
BondActions(filesystem_verifier_action.get(),
postinstall_runner_action.get()); // Enqueue the actions.
for (const shared_ptr<AbstractAction>& action : actions_)
processor_->EnqueueAction(action.get());
}

我们会发现processor_,InstallPlanAction,DownloadAction,FilesystemVerifierAction,PostinstallRunnerAction。首先分析processor_,它是在UpdateAttempterAndroid的构造方法中被赋值

  UpdateAttempterAndroid::UpdateAttempterAndroid(
DaemonStateInterface* daemon_state,
PrefsInterface* prefs,
BootControlInterface* boot_control,
HardwareInterface* hardware)
: daemon_state_(daemon_state),
prefs_(prefs),
boot_control_(boot_control),
hardware_(hardware),
processor_(new ActionProcessor()) {
network_selector_ = network::CreateNetworkSelector();
}

ActionProcessor的数据结构为:

   // An ActionProcessor keeps a queue of Actions and processes them in order.

   namespace chromeos_update_engine {

   class AbstractAction;
class ActionProcessorDelegate; class ActionProcessor {
public:
ActionProcessor() = default; virtual ~ActionProcessor(); // Starts processing the first Action in the queue. If there's a delegate,
// when all processing is complete, ProcessingDone() will be called on the
// delegate.
virtual void StartProcessing(); // Aborts processing. If an Action is running, it will have
// TerminateProcessing() called on it. The Action that was running and all the
// remaining actions will be lost and must be re-enqueued if this Processor is
// to use it.
void StopProcessing(); // Suspend the processing. If an Action is running, it will have the
// SuspendProcessing() called on it, and it should suspend operations until
// ResumeProcessing() is called on this class to continue. While suspended,
// no new actions will be started. Calling SuspendProcessing while the
// processing is suspended or not running this method performs no action.
void SuspendProcessing(); // Resume the suspended processing. If the ActionProcessor is not suspended
// or not running in the first place this method performs no action.
void ResumeProcessing(); // Returns true iff the processing was started but not yet completed nor
// stopped.
bool IsRunning() const { return current_action_ != nullptr || suspended_; } // Adds another Action to the end of the queue.
virtual void EnqueueAction(AbstractAction* action); // Sets/gets the current delegate. Set to null to remove a delegate.
ActionProcessorDelegate* delegate() const { return delegate_; }
void set_delegate(ActionProcessorDelegate *delegate) {
delegate_ = delegate;
} // Returns a pointer to the current Action that's processing.
AbstractAction* current_action() const {
return current_action_;
} // Called by an action to notify processor that it's done. Caller passes self.
void ActionComplete(AbstractAction* actionptr, ErrorCode code); private:
// Continue processing actions (if any) after the last action terminated with
// the passed error code. If there are no more actions to process, the
// processing will terminate.
void StartNextActionOrFinish(ErrorCode code); // Actions that have not yet begun processing, in the order in which
// they'll be processed.
std::deque<AbstractAction*> actions_; // A pointer to the currently processing Action, if any.
AbstractAction* current_action_{nullptr}; // The ErrorCode reported by an action that was suspended but finished while
// being suspended. This error code is stored here to be reported back to the
// delegate once the processor is resumed.
ErrorCode suspended_error_code_{ErrorCode::kSuccess}; // Whether the action processor is or should be suspended.
bool suspended_{false}; // A pointer to the delegate, or null if none.
ActionProcessorDelegate* delegate_{nullptr}; DISALLOW_COPY_AND_ASSIGN(ActionProcessor);
}; // A delegate object can be used to be notified of events that happen
// in an ActionProcessor. An instance of this class can be passed to an
// ActionProcessor to register itself.
class ActionProcessorDelegate {
public:
virtual ~ActionProcessorDelegate() = default; // Called when all processing in an ActionProcessor has completed. A pointer
// to the ActionProcessor is passed. |code| is set to the exit code of the
// last completed action.
virtual void ProcessingDone(const ActionProcessor* processor,
ErrorCode code) {} // Called when processing has stopped. Does not mean that all Actions have
// completed. If/when all Actions complete, ProcessingDone() will be called.
virtual void ProcessingStopped(const ActionProcessor* processor) {} // Called whenever an action has finished processing, either successfully
// or otherwise.
virtual void ActionCompleted(ActionProcessor* processor,
AbstractAction* action,
ErrorCode code) {}
}; }

从中可以看到ActionProcessor其实就是用来管理Action的,它的方法都比较简单,根据注释我们大体就能够明白每个方法的意思,在遇到的时候某一个方法再具体分析。接下来再看Action它所存在的继承关系如下

Aciton继承关系

FilesystemVerifierAction,PostinstallRunnerAction,DownloadAction都继承了InstallPlanAction,根据继承关系可以看出他们都会有PerformAction,ActionCompleted等方法。PerformAction()是在Action开始执行前进行调用,而ActionCompleted是在执行完成后进行调用。先来看看InstallPlanAction中的内容

 src/system/update_engine/payload_consumer/install_plan.h

  class InstallPlanAction : public Action<InstallPlanAction> {
public:
InstallPlanAction() {}
explicit InstallPlanAction(const InstallPlan& install_plan):
install_plan_(install_plan) {} void PerformAction() override {
if (HasOutputPipe()) {
SetOutputObject(install_plan_);
}
processor_->ActionComplete(this, ErrorCode::kSuccess);
} InstallPlan* install_plan() { return &install_plan_; } static std::string StaticType() { return "InstallPlanAction"; }
std::string Type() const override { return StaticType(); } typedef ActionTraits<InstallPlanAction>::InputObjectType InputObjectType;
typedef ActionTraits<InstallPlanAction>::OutputObjectType OutputObjectType; private:
InstallPlan install_plan_; DISALLOW_COPY_AND_ASSIGN(InstallPlanAction);
};

可以看到InstallAction比较简单,仅仅是将install_plan_设置为了输出对象,传递给了下一个Action,这是Action之间的一个通信方式,这个方式可以称之为pipe方式,下面来分析一下这种通信方式。先来看在Action这个类里面提到的ActionPipe

src/system/update_engine/common/action_pipe.h

  namespace chromeos_update_engine {

  // Used by Actions an InputObjectType or OutputObjectType to specify that
// for that type, no object is taken/given.
class NoneType {}; template<typename T>
class Action; template<typename ObjectType>
class ActionPipe {
public:
virtual ~ActionPipe() {} // This should be called by an Action on its input pipe.
// Returns a reference to the stored object.
const ObjectType& contents() const { return contents_; } //获取管道中的内容 // This should be called by an Action on its output pipe.
// Stores a copy of the passed object in this pipe.
void set_contents(const ObjectType& contents) { contents_ = contents; } //设置管道中的内容 // Bonds two Actions together with a new ActionPipe. The ActionPipe is
// jointly owned by the two Actions and will be automatically destroyed
// when the last Action is destroyed.
template<typename FromAction, typename ToAction>
static void Bond(FromAction* from, ToAction* to) { //将两个Action连接通过pipe连接在一起
std::shared_ptr<ActionPipe<ObjectType>> pipe(new ActionPipe<ObjectType>);
from->set_out_pipe(pipe); to->set_in_pipe(pipe); // If you get an error on this line, then
// it most likely means that the From object's OutputObjectType is
// different from the To object's InputObjectType.
} private:
ObjectType contents_; // The ctor is private. This is because this class should construct itself
// via the static Bond() method.
ActionPipe() {}
DISALLOW_COPY_AND_ASSIGN(ActionPipe);
}; // Utility function
template<typename FromAction, typename ToAction>
void BondActions(FromAction* from, ToAction* to) {
static_assert(
std::is_same<typename FromAction::OutputObjectType,
typename ToAction::InputObjectType>::value,
"FromAction::OutputObjectType doesn't match ToAction::InputObjectType");
ActionPipe<typename FromAction::OutputObjectType>::Bond(from, to);
} }

可以看到ActionPipe主要就是将两个Action连接在一起。为什么说就会连接在一起呢?再来看Action中相关的方法

src/system/update_engine/common/action.h

  template<typename SubClass>
class Action : public AbstractAction {
public:
~Action() override {} // Attaches an input pipe to this Action. This is optional; an Action
// doesn't need to have an input pipe. The input pipe must be of the type
// of object that this class expects.
// This is generally called by ActionPipe::Bond()
void set_in_pipe( //设置输入管道
// this type is a fancy way of saying: a shared_ptr to an
// ActionPipe<InputObjectType>.
const std::shared_ptr<ActionPipe<
typename ActionTraits<SubClass>::InputObjectType>>& in_pipe) {
in_pipe_ = in_pipe;
} // Attaches an output pipe to this Action. This is optional; an Action
// doesn't need to have an output pipe. The output pipe must be of the type
// of object that this class expects.
// This is generally called by ActionPipe::Bond()
void set_out_pipe( //设置输出管道
// this type is a fancy way of saying: a shared_ptr to an
// ActionPipe<OutputObjectType>.
const std::shared_ptr<ActionPipe<
typename ActionTraits<SubClass>::OutputObjectType>>& out_pipe) {
out_pipe_ = out_pipe;
} // Returns true iff there is an associated input pipe. If there's an input
// pipe, there's an input object, but it may have been constructed with the
// default ctor if the previous action didn't call SetOutputObject().
bool HasInputObject() const { return in_pipe_.get(); } //是否有输入管道 // returns a const reference to the object in the input pipe.
const typename ActionTraits<SubClass>::InputObjectType& GetInputObject() //获取输入的内容
const {
CHECK(HasInputObject());
return in_pipe_->contents();
} // Returns true iff there's an output pipe.
bool HasOutputPipe() const { //是否有输出管道
return out_pipe_.get();
} // Copies the object passed into the output pipe. It will be accessible to
// the next Action via that action's input pipe (which is the same as this
// Action's output pipe).
void SetOutputObject( //设置输出的内容
const typename ActionTraits<SubClass>::OutputObjectType& out_obj) {
CHECK(HasOutputPipe());
out_pipe_->set_contents(out_obj);
} // Returns a reference to the object sitting in the output pipe.
const typename ActionTraits<SubClass>::OutputObjectType& GetOutputObject() { //获取输出的内容
CHECK(HasOutputPipe());
return out_pipe_->contents();
} protected:
// We use a shared_ptr to the pipe. shared_ptr objects destroy what they
// point to when the last such shared_ptr object dies. We consider the
// Actions on either end of a pipe to "own" the pipe. When the last Action
// of the two dies, the ActionPipe will die, too.
std::shared_ptr<ActionPipe<typename ActionTraits<SubClass>::InputObjectType>>
in_pipe_;
std::shared_ptr<ActionPipe<typename ActionTraits<SubClass>::OutputObjectType>>
out_pipe_;
};

从这里我们就能够看出每个Action其实有两个ActionPipe,一个是输入ActionPipe,一个是输出ActionPipe,输入ActionPipe和前一个Action的输出ActionPipe其实是一个ActionPipe,输出Actionpipe和下一个Action的输出ActionPipe是一个ActionPipe.

ActionTraits在这个类里仅仅是为InstallPlan这个类型定义了一个新的类型

src/system/update_engine/payload_consumer/install_plan.h

 template<>
class ActionTraits<InstallPlanAction> {
public:
// Takes the install plan as input
typedef InstallPlan InputObjectType;
// Passes the install plan as output
typedef InstallPlan OutputObjectType;
};

到这里Action机制也分析的差不多了,我们可以回到BuildUpdateActions中继续进行分析了。

  void UpdateAttempterAndroid::BuildUpdateActions(const string& url) {
CHECK(!processor_->IsRunning());
processor_->set_delegate(this); // Actions:
shared_ptr<InstallPlanAction> install_plan_action(
new InstallPlanAction(install_plan_)); HttpFetcher* download_fetcher = nullptr;
if (FileFetcher::SupportedUrl(url)) {
DLOG(INFO) << "Using FileFetcher for file URL.";
download_fetcher = new FileFetcher();
} else {
#ifdef _UE_SIDELOAD
LOG(FATAL) << "Unsupported sideload URI: " << url;
#else
LibcurlHttpFetcher* libcurl_fetcher =
new LibcurlHttpFetcher(&proxy_resolver_, hardware_);
libcurl_fetcher->set_server_to_check(ServerToCheck::kDownload);
download_fetcher = libcurl_fetcher;
#endif // _UE_SIDELOAD
}
shared_ptr<DownloadAction> download_action(
new DownloadAction(prefs_,
boot_control_,
hardware_,
nullptr, // system_state, not used.
download_fetcher)); // passes ownership
shared_ptr<FilesystemVerifierAction> filesystem_verifier_action(
new FilesystemVerifierAction()); shared_ptr<PostinstallRunnerAction> postinstall_runner_action(
new PostinstallRunnerAction(boot_control_, hardware_)); download_action->set_delegate(this);
download_action->set_base_offset(base_offset_);
download_action_ = download_action;
postinstall_runner_action->set_delegate(this); actions_.push_back(shared_ptr<AbstractAction>(install_plan_action));
actions_.push_back(shared_ptr<AbstractAction>(download_action));
actions_.push_back(shared_ptr<AbstractAction>(filesystem_verifier_action));
actions_.push_back(shared_ptr<AbstractAction>(postinstall_runner_action)); // Bond them together. We have to use the leaf-types when calling
// BondActions().
BondActions(install_plan_action.get(), download_action.get());
BondActions(download_action.get(), filesystem_verifier_action.get());
BondActions(filesystem_verifier_action.get(),
postinstall_runner_action.get()); // Enqueue the actions.
for (const shared_ptr<AbstractAction>& action : actions_)
processor_->EnqueueAction(action.get());
}

在这个方法里主要做了:

1.为processor_设置delegate,其实也就是注册了回调方法,UpdateAttempterAndroid实现了ActionProcessDelegate中的方法.

2. 创建了InstallPlanAction

3.创建了download_fetcher,我们这里假定用的是本地的文件既使用file:///协议,所以download_fetcher即为FileFetcher,从这一部分的代码可以看HtppFetcher,FileFetcher,LibcurlHttpFetcher之间具有继承或实现的关系。

4.创建DownloadAction,注意在创建的时候传入了download_fetcher为FileFetcher类型

5.创建FilesystemVerifierAction,PostinstallRunnerAction.从这里可以看出升级流程的精华应该就是这三个Action了

6.为download_action设置delegate,设置开始下载的offfset等,因为代码中设置delegate的操作比较多,如果不注意很有可能记混乱了。

7.为postinstall_runner_action设置delegate

8.将Action加入到Action的集合中

9.使用BondActions方法为Action之间建立管道。

10.将action遍历放入到processor_的队列中,并且设置action的管理者为processor_。

在分析完这个方法所干的事情之后,再分析一下HtppFetcher,FileFetcher,LibcurlHttpFetcher这三者之间的关系

 HtppFetcher,FileFetcher,LibcurlHttpFetcher这三者之间的关系

现在继续分析ApplyPayload中的最后一个方法UpdateBootFlags()

  void UpdateAttempterAndroid::UpdateBootFlags() {
if (updated_boot_flags_) {
LOG(INFO) << "Already updated boot flags. Skipping.";
CompleteUpdateBootFlags(true);
return;
}
// This is purely best effort.
LOG(INFO) << "Marking booted slot as good.";
if (!boot_control_->MarkBootSuccessfulAsync(
Bind(&UpdateAttempterAndroid::CompleteUpdateBootFlags,
base::Unretained(this)))) {
LOG(ERROR) << "Failed to mark current boot as successful.";
CompleteUpdateBootFlags(false);
}
}

首先检查当前运行的slot是否已经被标记为successful状态,如果是则调用CompleteUpdateBootFlags方法,否则的就调用MarkBootSuccessfulAsync将当前的slot标记为successful。标记完成后调用CompleteUpdateBootFlags方法

 void UpdateAttempterAndroid::CompleteUpdateBootFlags(bool successful) {
updated_boot_flags_ = true;
ScheduleProcessingStart();
}

从这里看出即使标记失败了仍然调用 ScheduleProcessingStart(),这个方法主要就是开始执行Action

 void UpdateAttempterAndroid::ScheduleProcessingStart() {
LOG(INFO) << "Scheduling an action processor start.";
brillo::MessageLoop::current()->PostTask(
FROM_HERE,
Bind([](ActionProcessor* processor) { processor->StartProcessing(); },
base::Unretained(processor_.get())));
}

在来看看StartProcessing()方法的实现,首先是获取对列中的第一个action,打印action的类型,之后将action移出队列,并且调用PerformAction。

src/system/update_engine/common/action_processor.cc

 void ActionProcessor::StartProcessing() {
CHECK(!IsRunning());
if (!actions_.empty()) {
current_action_ = actions_.front();
LOG(INFO) << "ActionProcessor: starting " << current_action_->Type();
actions_.pop_front();
current_action_->PerformAction();
}
}

分析到了这里就对整体的update_engine有了一定的了解,接下来只需要对各个Action逐个击破就好了。在之前已经看过了InstallPlanAction,它的内容很简单,仅仅是在输出管道中设置了install_plan_,接下来就调用了processor_->ActionComplete(this, ErrorCode::kSuccess),看一下ActionComplete的内容,它是如何让下一个action开始执行的。

  void ActionProcessor::ActionComplete(AbstractAction* actionptr,
ErrorCode code) {
CHECK_EQ(actionptr, current_action_);
if (delegate_)
delegate_->ActionCompleted(this, actionptr, code);
string old_type = current_action_->Type();
current_action_->ActionCompleted(code);
current_action_->SetProcessor(nullptr);
current_action_ = nullptr;
LOG(INFO) << "ActionProcessor: finished "
<< (actions_.empty() ? "last action " : "") << old_type
<< (suspended_ ? " while suspended" : "")
<< " with code " << utils::ErrorCodeToString(code);
if (!actions_.empty() && code != ErrorCode::kSuccess) {
LOG(INFO) << "ActionProcessor: Aborting processing due to failure.";
actions_.clear();
}
if (suspended_) {
// If an action finished while suspended we don't start the next action (or
// terminate the processing) until the processor is resumed. This condition
// will be flagged by a nullptr current_action_ while suspended_ is true.
suspended_error_code_ = code;
return;
}
StartNextActionOrFinish(code);
}

其实这个方法中也就进行了善后和开始下一个Action的工作。包括:

1.判断是否注册了回调方法。这里的delegate_的类型为UpdateAttempterAndroid。如果注册了就回调ActionCompleted方法,在UpdateAttempterAndroid中它的内容为

  void UpdateAttempterAndroid::ActionCompleted(ActionProcessor* processor,
AbstractAction* action,
ErrorCode code) {
// Reset download progress regardless of whether or not the download
// action succeeded.
const string type = action->Type();
if (type == DownloadAction::StaticType()) {
download_progress_ = ;
}
if (code != ErrorCode::kSuccess) {
// If an action failed, the ActionProcessor will cancel the whole thing.
return;
}
if (type == DownloadAction::StaticType()) {
SetStatusAndNotify(UpdateStatus::FINALIZING);
}
}

可以看到这个方法主要就是为重置下载的进度。

2.调用当前的Action的ActionCompleted,将Processor和当前Action置空等。

3.如果执行到某人Action的时候出了错,则停止执行其他的Action

4. processor如果被挂起,则暂停执行下一个Action

5.执行下一个Action或者是否完成了所有的Action,StartNextActionOrFinish(code),该方法比较简单,就不进行分析了。

到这里整体的Action的执行流程也就通了,下一篇开始会分析其他三个Action

.

update_engine-整体结构(三)的更多相关文章

  1. Nginx基本功能及其原理,配置原理

    Nginx基本功能及其原理,配置原理 一.正向代理.反向代理 二.Nginx配置文件的整体结构 三.Nginx配置SSL及HTTP跳转到HTTPS 四.nginx 配置管理 [nginx.conf 基 ...

  2. 阅读笔记:ImageNet Classification with Deep Convolutional Neural Networks

    概要: 本文中的Alexnet神经网络在LSVRC-2010图像分类比赛中得到了第一名和第五名,将120万高分辨率的图像分到1000不同的类别中,分类结果比以往的神经网络的分类都要好.为了训练更快,使 ...

  3. 车牌识别LPR(三)-- LPR系统整体结构

    第三篇:系统的整体架构 LPR系统大体上可由图像采集系统,图像处理系统,数据库管理系统三个子系统组成.它综合了通讯.信息.控制.传感.计算机等各种先进技术,构成一个智能电子系统. 图像采集系统:图像采 ...

  4. 浅谈Excel开发:三 Excel 对象模型

    前一篇文章介绍了Excel中的菜单系统,在创建完菜单和工具栏之后,就要着手进行功能的开发了.不论您采用何种方式来开发Excel应用程序,了解Excel对象模型尤其重要,这些对象是您与Excel进行交互 ...

  5. lucene学习笔记:三,Lucene的索引文件格式

    Lucene的索引里面存了些什么,如何存放的,也即Lucene的索引文件格式,是读懂Lucene源代码的一把钥匙. 当我们真正进入到Lucene源代码之中的时候,我们会发现: Lucene的索引过程, ...

  6. 《InsideUE4》UObject(三)类型系统设定和结构

    垃圾分类,从我做起! 引言 上篇我们谈到了为何设计一个Object系统要从类型系统开始做起,并探讨了C#的实现,以及C++中各种方案的对比,最后得到的结论是UE采用UHT的方式搜集并生成反射所需代码. ...

  7. Lucene.Net 2.3.1开发介绍 —— 二、分词(三)

    原文:Lucene.Net 2.3.1开发介绍 -- 二.分词(三) 1.3 分词器结构 1.3.1 分词器整体结构 从1.2节的分析,终于做到了管中窥豹,现在在Lucene.Net项目中添加一个类关 ...

  8. 【Beta】 第三次Daily Scrum Meeting

    一.本次会议为第三次meeting会议 二.时间:10:00AM-10:20AM 地点:禹州楼 三.会议站立式照片 四.今日任务安排 成员 昨日任务 今日任务 林晓芳 查询app提醒功能模块和用户登录 ...

  9. SSM框架开发web项目系列(三) MyBatis之resultMap及关联映射

    前言 在上篇MyBatis基础篇中我们独立使用MyBatis构建了一个简单的数据库访问程序,可以实现单表的基本增删改查等操作,通过该实例我们可以初步了解MyBatis操作数据库需要的一些组成部分(配置 ...

  10. 操作系统内核Hack:(三)引导程序制作

    操作系统内核Hack:(三)引导程序制作 关于本文涉及到的完整源码请参考MiniOS的v1_bootloader分支. 1.制作方法 现在我们已经了解了关于BootLoader的一切知识,让我们开始动 ...

随机推荐

  1. 利用postman 实现Get和Post测试

    通过之前对金字塔结构的学习,大概了解到了金字塔模型想告诉我们的几个道理: 1.越底层,越稳定. 金字塔主要观点认为单元测试的稳定性高,需要多投入. 2.越底层,越高效. 程序的问题,最终还得落在具体的 ...

  2. L360 Most People Spend Their Time in Just 25 Places

    Some people are always out on the town, going to concerts, restaurant openings, you name it. They're ...

  3. shell脚本判断安装包位置及类型

    Log() { LogFile=/tmp/``.log LogDate=$(date +"%F %T") echo -e "\n\n||| ${LogDate} ||| ...

  4. 从输入URL按下回车到页面展现,中间发生了什么?

    从输入URL按下回车到页面展现,总的来说发生了一下几个过程: DNS 解析:将域名解析成 IP 地址 TCP 连接:TCP 三次握手 发送 HTTP 请求 服务器处理请求并返回 HTTP 报文 浏览器 ...

  5. 运维shell脚本函数语法

    在fun.sh 文件里,使用函数来封装脚本内容 usege() { echo "hello world" echo "脚本怎么使用函数......"}usege ...

  6. Tmux会话-基本操作及原理

    一.Tmux命令介绍: Tmux (“Terminal Multiplexer”的简称), 是一款优秀的终端复用软件,类似 GNU screen,但比screen更出色. tmux来自于OpenBSD ...

  7. Linux 下SVN报错No repository found in 'svn://210.16.191.230/huandong_project'

    [root@xxxxxx~]# netstat -apn|grep 3690tcp        0      0 0.0.0.0:3690                0.0.0.0:*     ...

  8. canvas绘制随机验证码

    效果图: 思路: 1, 绘制canvas画布,进行基础设置 2.绘制一个矩形 3.设置验证码的随机数 4.设置验证码随机数的随机颜色 5.绘制随机干扰线 6,绘制随机干扰点 经过以上六个步骤,验证码的 ...

  9. 2019微软Power BI 每月功能更新系列——Power BI 4月版本功能完整解读

    Power BI4月份的更新对整个产品进行了重大更新.此版本增加了基于DAX表达式定义视觉效果标题和按钮URL的功能.本月Power BI也新增了许多新的连接器,现在可以使用几种预览连接器,包括Pow ...

  10. 为UITextField增加MaxLength特性

    iOS 实现方案 在 HTML 的世界里,输入框天生就有 MaxLength 属性,可以限制用户输入的最大字符数量 可惜 iOS 上对应的 UITextField 并没有这样方便的属性,只有自己动手来 ...