JSI是RN新架构实现JS与Native通信的基石,Turbomodules 也是基于 JSI 实现的。 对于了解RN新架构来说,先搞明白 JSI 是至关重要的,那下面就让我们来聊一聊 JSI。

一、什么是 JSI ?

JSI 的全称是 JavaScript Interface,即 JS Interface 接口,它是对 JS引擎 与 Native (C++) 之间相互调用的封装,通过 HostObject 接口实现双边映射,官方也称它为映射框架

有了这层封装,在 ReactNative 中有了两方面的提升:

  • 可以自由切换引擎,比如: JavaScriptCore、V8、Hermes等。
  • 在 JS 中调用 C++ 注入到 JS 引擎中的方法,数据载体格式是通过 HostObject 接口规范化后的,摒弃了旧架构中以 JSON 作为数据载体的异步机制,从而使得 JS 与 Native 之间的调用可以实现同步感知。

二、JS引擎注入原理

由于JSI 的实现是基于 JS 引擎提供的 API 来实现的,为此我们先来了解下 JS 引擎的注入原理。说到向引擎注入方法,相信大家都用过 console.log()、setTimeout()、setInterval() 等方法,像这些方法就是通过 polyfill 的方式注入到 JS 引擎里的,JS引擎内部本身是没有这些方法的 (这些方法的实现,我们会在后面进行讲解)。沿着这个思路,我们可以通过向引擎注入方法和变量的方式,来实现在 JS 中调用注入的 Native(C++) 方法。

这里我们会以 V8 引擎为例,了解一下 JS 引擎的注入原理。(C++ 嵌入 v8 引擎的教程,感兴趣的同学可以看这里 C++嵌入JS引擎)

1. 向v8引擎中注入方法

向引擎注入额外方法,并实现 JS 调用,基本思想都是一致的,都是要将 JS 所需的内容设法绑定到 JS 执行环境下的 全局对象 global 上。这样在 JS 侧就可以通过 global 来获取注入的内容了,当然我们也可以不直接在 global 中来获取,而是写个 js module,然后重新导出并用 declare 声明,用法类似直接使用 console.log()。

实现步骤如下:

1.1 我们用 C++ 实现一个打印字符串的方法 Print()

void Print(const v8::FunctionCallbackInfo<v8::Value>& args) {for (int i = 0; i < args.Length(); i++) {v8::HandleScope handle_scope(args.GetIsolate());v8::String::Utf8Value str(args.GetIsolate(), args[i]);const char* cstr = ToCString(str);printf("%s", cstr);}printf("\n");fflush(stdout);
}

1.2 将 Print() 方法加入到 Js global 中

// 根据 v8 实例 isolate 获取 JS 中的 global 对象
v8::Local<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate);// 将 global 关联到 JS 执行上下文 Context 中
v8::Local<v8::Context> context = v8::Context::New(isolate, NULL, global);// 向 global 中 set 一个属性名为 print 的方法。可以理解为: global.print = Print
context->Global()->Set(context, v8::String::NewFromUtf8(isolate, "print", v8::NewStringType::kNormal).ToLocalChecked(),v8::FunctionTemplate::New(isolate, Print));

ObjectTemplate 是一个 JS 引擎 提供的 JS 对象模版,通过它我们可以创建一个 JS 对象。通过 context.Global(),可以获取到 Js global 全局对象。

1.3 执行C++ 注入到 JS 的方法 print

// 转换成 v8::Local类型 -- (方便垃圾回收)
v8::Local<v8::String> source = v8::String::NewFromUtf8Literal(isolate, "print('Hello World')");// 将 Js 代码进行编译
v8::Local<v8::Script> script = v8::Script::Compile(context, source).ToLocalChecked();// 运行 Js 代码
v8::Local<v8::Value> result = script->Run(context).ToLocalChecked();// 输出 result 为:Hello World

2. 向v8引擎中注入变量

实现步骤如下:

2.1 注入一个变量到 v8 引擎

// 首先通过 Js 引擎实例 isolate 获取 Js global 对象
v8::Local<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate);// 将 global 绑定到 JS 执行上下文 Context 中
v8::Local<v8::Context> context = v8::Context::New(isolate, NULL, global);// 创建一个临时对象 temp
Local<v8::ObjectTemplate> temp = v8::ObjectTemplate::New(isolate);// temp 上加入 x 属性
temp->Set(isolate, "x", v8::Number::New(isolate, 12));// temp 上加入 y 属性
temp->Set(isolate, "y",v8::Number::New(isolate, 10));// 创建 temp 的实例 instance
Local<v8::Object> instance = temp->NewInstance(context).ToLocalChecked();// global 中加入属性 options,其值为 instance,可以理解为: global.options
context->Global()->Set(context, String::NewFromUtf8Literal(isolate, "options"),instance).FromJust();

2.2 执行C++ 注入到 JS 的对象

// 类型转换 (方便垃圾回收)
v8::Local<v8::String> source = v8::String::NewFromUtf8Literal(isolate, "options.x");// 将 Js 代码进行编译
v8::Local<v8::Script> script = v8::Script::Compile(context, source).ToLocalChecked();// 运行 Js 代码
v8::Local<v8::Value> result = script->Run(context).ToLocalChecked();// 输出 result 为:12

实现原理分析:v8 引擎中 方法和变量的注入,实际上是借助于 Js 执行上下文 Context 来实现的。首先需要获取这个 Context 上下文 和 JS global ,然后将 需要注入的 方法 和 变量 设置到 global 当中,最后再将 global 与 Context 进行关联,就完成了向引擎注入的操作。这样就可以在 Js 运行环境中,调用我们注入的方法和变量了。

三、JSI、JS、JS Runtime、Native(C++)的关系

Js是运行在 Js Runtime 中的,所谓的方法注入,也就是将所需方法注入到 Js Runtime 中去,JSI则负责具体的注入工作,通过 Js 引擎提供的 API,完成 C++ 方法的注入。上图就是 JS 与 Native(C++) 在 JSI 新架构中实现通信的简易架构。

那么接下来,就让我们继续来了解一下 JSI 是如何实现 JS 与 Native 互调通信的吧。

四、举例说明JSI

接下来我们通过一个实际的例子,来了解下 JSI 是如何实现 JS 与 Native (C++)通信的,首先我们先来看一下 JS 调用 Native(C++)的过程。

1. JS 调用 Native (C++)

步骤如下:

1.1 编写 .java 文件

package com.terrysahaidak.test.jsi;public class TestJSIInstaller {// native 方法public native void installBinding(long javaScriptContextHolder);// stringField 会被 JS 调用private String stringField = "Private field value";static {//注册 .so 动态库System.loadLibrary("test-jsi");}// runTest 会被 JS 调用public static String runTest() {return "Static field value";}}

1.2 编写 .h 文件,实现 .java 中的 native 方法,并在此声明一个 SampleModule 对象,该对象就是 TurboModule的实现。SampleModule 需要继承 JSI 中的 I (即 HostObject 接口,它定义了注入操作的细节以及双边数据的交换的逻辑),并实现 install 方法以及 get 方法。

#pragma once
#include <jni.h>
#include "../../../../../../node_modules/react-native/ReactCommon/jsi/jsi/jsi.h"using namespace facebook;extern "C" {JNIEXPORT void JNICALLJava_com_terrysahaidak_test_jsi_TestJSIInstaller_installBinding(JNIEnv* env,
jobject thiz, jlong runtimePtr);}// 声明 SampleModule 继承 HostObject,并实现 install 方法
class SampleModule : public jsi::HostObject {public:static void install(jsi::Runtime &runtime,const std::shared_ptr<SampleModule> sampleModule);// 每一个 TurboModule -- SampleModule 中的所有方法和属性,都需要通过声明式注册,在 get 中进行声明jsi::Value get(jsi::Runtime &runtime, const jsi::PropNameID &name) override;private:JNIEnv jniEnv_;};

1.3 编写 C++ 文件,实现 SampleModule 相关逻辑

#include <jsi/jsi.h>
#include <jni.h>
#include "TestJSIInstaller.h"// 虚拟机实例,用来获取 JNIenv 环境
JavaVM *jvm;// class 类实例
static jobject globalObjectRef;// class 类对象
static jclass globalClassRef;// native 方法 installBinding 的具体实现
extern "C" JNIEXPORT void JNICALLJava_com_terrysahaidak_test_jsi_TestJSIInstaller_installBinding(JNIEnv *env, jobject thiz, jlong runtimePtr){// runtimePtr 为 long 类型的值,这里强转成 Runtime类型,也就是 代表的 JS 引擎auto &runtime = *(jsi::Runtime *)runtimePtr;// 通过智能指针 实例化 SampleModuleauto testBinding = std::make_shared<SampleModule>();// 调用 SampleModule 的 install 方法,SampleModule::install(runtime, testBinding);// 获取并存储虚拟机实例 并存储到 &jvmenv->GetJavaVM(&jvm);// 创建一个全局对象的实例引用globalObjectRef = env->NewGlobalRef(thiz);//通过 class 类路径,创建一个 全局类对象引用auto clazz = env->FindClass("com/terrysahaidak/test/jsi/TestJSIInstaller");globalClassRef = (jclass)env->NewGlobalRef(clazz);}// install 方法的具体实现
void SampleModule::install(jsi::Runtime &runtime, const std::shared_ptr<SampleModule> sampleModule){// 定义 TurboModule 名称,也就是 JS 侧调用时使用的名称。auto testModuleName = "NativeSampleModule";// 创建一个 HostObject 实例,即 SampleModule 实例auto object = jsi::Object::createFromHostObject(runtime, sampleModule);// 通过 runtime 中的 global() 方法获取到 JS 世界的 global 对象,// runtime 是 JS 引擎的实例,通过 runtime.global() 获取到 JS 世界的 global 对象,// 进而调用 setProperty() 将 "NativeSampleModule" 注入到 global 中,// 从而完成 "NativeSampleModule" 的导出。runtime.global().setProperty(runtime, testModuleName, std::move(object));
}// TurboModule 的 get 方法,当 JS 侧开始使用 "." 来调用某个方法时,会执行到这里。
jsi::Value SampleModule::get(jsi::Runtime &runtime,const jsi::PropNameID &name){auto methodName = name.utf8(runtime);// 获取 需要调用的成员名称,并进行判断if (methodName == "getStaticField"){// 动态创建 HostFunction 对象return jsi::Function::createFromHostFunction(runtime,name,0,[](jsi::Runtime &runtime,const jsi::Value &thisValue,const jsi::Value *arguments,size_t count) -> jsi::Value {// 这里通过 反射 完成对 Java 侧 方法的调用auto runTest = env->GetStaticMethodID(globalClassRef, "runTest", "()Ljava/lang/String;");auto str = (jstring)env->CallStaticObjectMethod(globalClassRef, runTest);const char *cStr = env->GetStringUTFChars(str, nullptr);return jsi::String::createFromAscii(runtime, cStr);});}if (methodName == "getStringPrivateField"){return jsi::Function::createFromHostFunction(runtime,name,0,[](jsi::Runtime &runtime,const jsi::Value &thisValue,const jsi::Value *arguments,size_t count) -> jsi::Value {auto valId = env->GetFieldID(globalClassRef, "stringField", "Ljava/lang/String;");auto str = (jstring)env->GetObjectField(globalObjectRef, valId);const char *cStr = env->GetStringUTFChars(str, nullptr);return jsi::String::createFromAscii(runtime, cStr);});}return jsi::Value::undefined();
}

1.4 在 JS 中调用注入的方法

<Text style={styles.sectionTitle}>{global.NativeSampleModule.getStaticField()}
</Text><Text style={styles.sectionTitle}>{/* this is from C++ JSI bindings */}{global.NativeSampleModule.getStringPrivateField()}
</Text>

总结分析:TurboModule 需要注册 (注入) 到 JS 引擎才能够被 JS 调用,在执行静态方法 install 方法之后,最终通过 runtime.global() 将其注入到了 JS 引擎当中。JSI HostObject 向 JS 导出的方法并不是预先导出的,而是懒加载及时创建的。从 JSI 进入到 get 函数后,先是通过 methodName 判断之后,动态的创建一个 HostFunction ,作为 get 的返回结果。在 HostFunction 方法中 在通过反射的方式,实现对 Java 方法的调用,这样就完成了 JS 通过 JSI 调用 Java 的通信流程。

那么下面我们再来了解一下 Native (C++) 调用 JS 的通信方式。

2. Native (C++)调用 JS

Native调用 JS 主要是通过 JSI 中的 Runtime.global().getPropertyAsFunction(jsiRuntime, "jsMethod").call(jsiRuntime) 方法实现。那么接下来我们就来一起看下整个流程是怎么样的。

实现步骤如下:

2.1 在 JS module 中 增加一个 将被 Native 调用的 JS 方法 jsMethod()

import React from "react";
import type {Node} from "react";
import {Text, View, Button} from "react-native";
const App: () => Node = () => {// 等待被 Native 调用global.jsMethod = (message) => {alert("hello jsMethod");};const press = () => {setResult(global.multiply(2, 2));};return (<View style={{}}></View>);
};export default App;

2.2 Native 调用 JS 全局方法

runtime.global().getPropertyAsFunction(*runtime, "jsMethod").call(*runtime, "message内容!");

注意内容: 我们需要通过 JSI 中的 getPropertyAsFunction() 来获取 JS 中的方法,但需要注意,getPropertyAsFunction() 获取的是 global 全局变量下的某个属性或方法,因此,我们在 JS 中声明一个需要被 Native 调用的方法的时候,需要显式的指定它的作用域。

五、JSI 与 JSC(不是JavaScriptCore脚本引擎) 对比

相同点:

首先在底层实现上来说,JSI 与 JSC 都是通过向 JS 引擎中注入方法,来实现的 JS 与 Native 通信,同时 注入的方法也都是挂载到了 JS global 全局对象上面。

不同点:

旧架构中的 JSC 处理的注入对象是JSON 对象与C++ 对象,内部涉及复杂且频繁的类型转换。且在

JSBridge 这种异步传输的设计中存在三个线程之间的通信:UI线程、Layout线程、JS线程,在典型的列表快速滑动时出现空白页的例子中,效率低下得到明显的体现。

而对于 JSI 来讲,弃用了异步的bridge,传输的数据也不再依赖于 JSON 的数据格式,而是将HostObject 接口作为了双边通信的协议,实现了双边同步通信下的高效信息传输。

另外编写 NativeModule 的方式与旧架构中相比发生了改变,除了功能之外的逻辑,需要在一个 C++ 类中来完成。 因此,一个 TurboModule 的实现分为两部分: C++ & Java (OC)。

一句话概括 JSI 提效的本质: JSI 实现了通信桥 Bridge 的自定义,并通过 HostObjec 接口协议的方式取代了 旧架构中基于异步 bridge 的JSON数据结构,从而实现了同步通信,并且避免了 JSON 序列化与反序列化的繁琐操作,大大提升了 JS 与 Native 的通信效率。

六、对 RN global 对象的分析

1. RN 中的 global 与 JS 引擎中的 global 的关系

在 JS 引擎中操作的 全局对象 global 是挂在到 JS 执行上下文环境 Context 上的。

该 global 对象 与 RN 里使用的 global 是同一个 global 对象,HouseRN只是扩展了 global 的声明,下面我们通过一个例子来讲解一下:

在RN的开发中,我们调用一个NativeModule是这样调用的:

import { NativeModules } from "react-native";//调用获取数据NativeModules.BrokerData.user()

但是通过 NativeModules.js 的源码可以知道,也可以通过 global 来对NativeModules 进行调用,当然RN官网已经帮我们封装好了调用的方式,这里我们只是为了验证一下 global 这个全局对象的作用域,下面我们首先看一下 NativeModules.js 源码实现:

let NativeModules : {[moduleName: string]: Object} = {};
if (global.nativeModuleProxy) {NativeModules = global.nativeModuleProxy;
} else {const bridgeConfig = global.__fbBatchedBridgeConfig;invariant(bridgeConfig, '__fbBatchedBridgeConfig is not set, cannot invoke native modules');
(bridgeConfig.remoteModuleConfig || []).forEach((config: ModuleConfig, moduleID: number) => {// Initially this config will only contain the module name when running in JSC. The actual// configuration of the module will be lazily loaded.const info = genModule(config, moduleID);if (!info) {return;}if (info.module) {NativeModules[info.name] = info.module;}// If there's no module config, define a lazy getterelse {defineLazyObjectProperty(NativeModules, info.name, {get: () => loadModule(info.name, moduleID)});}});
}

阅读上面的源码可以知道,理论上我们也可以通过 global 来获取一个 NativeModule ,我们先来看一下,在调试模式下 global 中的内容,可以看到所有已注册的 NativeModule 都在里面。


那么我们可以通过 global 这样来获取一个已注册的 NativeModule:

// 索引 24 代表 BrokerData 的所在位置,索引 1 代表返回的数据体。// 这种调用方式 与上面的调用方式是等价的global.__fbBatchedBridgeConfig.remoteModuleConfig[24][1]

回过头来,我们看一下 __fbBatchedBridgeConfig 变量是在哪里被赋值的,来看下 C++ 侧的实现:

void ProxyExecutor::initializeRuntime() {// .......{SystraceSection t("setGlobalVariable");setGlobalVariable("__fbBatchedBridgeConfig",std::make_unique<JSBigStdString>(folly::toJson(config)));}
}
void JSCExecutor::setGlobalVariable(std::string propName, std::unique_ptr<const JSBigString> jsonValue) {// ....auto valueToInject = Value::fromJSON(m_context, jsStringFromBigString(m_context, *jsonValue));Object::getGlobalObject(m_context).setProperty(propName.c_str(), valueToInject);
}

由代码可知,最终是通过 JS 引擎的

Object : : getGlobalObject(context).setProperty("propertyName", value) 方法,将 "__fbBatchedBridgeConfig" 注入到了 global 全局变量当中。 其中 Object : : getGlobalObject(context) 是 JSC 脚本引擎提供的 API (与 V8 引擎提供的类似),用于获取一个 JSGlobalObject (即 JS的 global 全局对象)。

2. RN 工程中的 global、window、globalThis 的关系

首先我们来引用一个官方一点的说法:

听上去大家有什么感受呢,懂了而又模糊对吧。那么我们通过具体的例子感受一下:

global.window.alert("11111")  // 调用成功
global.alert(222)             // 调用成功
window.alert(333)             // 调用成功
alert(444) // 调用成功
globalThis.window.alert(555); // 调用成功
globalThis.alert(666)         // 调用成功
alert(global.globalThis)      // 调用成功
global.setTimeout(() =>{      // 调用成功console.log("aaaaaaa")
}, 1000);
window.setTimeout(() =>{      // 调用成功console.log("bbbbbbb")
}, 1000);
window.global.alert(999)      // 失败 (undefined)global.window.a = 122;
alert(a);                     // 调用成功
alert("hhh " + window.a)      // 调用成功
alert("xx " + global.a)       // 调用成功

RN中的 global 是一个顶级全局对象,windowglobalThis 以及常用的 API 包括:alert()、setTimeout、setInterval 等都挂在了 global 这个全局对象下面。而像 alert()、setTimeout、setInterval()、console、JSON 等这些顶层API,也可以无需倒入并直接调用,其原因则是在 RN core 包中通过 declare 做了相应类型的声明。

3. 聊聊 setTimeout 的实现

在 RN 中,像 setTimeout 这样的顶层函数是如何实现的呢?这里我们先给出答案,再来进行分析。其实 setTimeout 这种顶层函数,并不是由 JS 来实现的,而是由 Native 来实现,并借助 JS 引擎中的 polyfill 方式从外部注入到 JS 执行环境当中。接下来我们简单看一下源码实现:

首先我们通过 RN 源码,找到 setupTimer.js 文件,核心代码如下:


'use strict';
const {polyfillGlobal} = require('../Utilities/PolyfillFunctions');
const {isNativeFunction} = require('../Utilities/FeatureDetection');
//...........
if (global.RN$Bridgeless !== true) {const defineLazyTimer = (name:| $TEMPORARY$string<'cancelAnimationFrame'>| $TEMPORARY$string<'cancelIdleCallback'>| $TEMPORARY$string<'clearInterval'>| $TEMPORARY$string<'clearTimeout'>| $TEMPORARY$string<'requestAnimationFrame'>| $TEMPORARY$string<'requestIdleCallback'>| $TEMPORARY$string<'setInterval'>| $TEMPORARY$string<'setTimeout'>,) => {polyfillGlobal(name, () => require('./Timers/JSTimers')[name]);};// 定义 'setTimeout'defineLazyTimer('setTimeout');defineLazyTimer('clearTimeout');defineLazyTimer('setInterval');defineLazyTimer('clearInterval');defineLazyTimer('requestAnimationFrame');defineLazyTimer('cancelAnimationFrame');defineLazyTimer('requestIdleCallback');defineLazyTimer('cancelIdleCallback');
}

代码中我们可以找到定义 setTimeout 的地方:defineLazyTimer('setTimeout'),继而调用了 polyfillGlobal(), 它就是向 global 上注入成员的一个操作,同时我们看到与之对应的具体实现则交给了 JSTimers.js ,我们跟进去看一下代码

/*** JS implementation of timer functions. Must be completely driven by an* external clock signal, all that's stored here is timerID, timer type, and* callback.*/
const JSTimers = {/*** @param {function} func Callback to be invoked after `duration` ms.* @param {number} duration Number of milliseconds.*/setTimeout: function(func: Function, duration: number, ...args?: any): number {const id = _allocateCallback(() => func.apply(undefined, args),'setTimeout');RCTTiming.createTimer(id, duration || 0, Date.now(),/* recurring */ false);return id;},//...........clearTimeout: function(timerID: number) {_freeCallback(timerID);},//...........
};
module.exports = JSTimers;

由源码可知,最终调用的是 RCTTiming 的 createTimer(),而 Timing 则是 Nativemodules 中获取的一个 NativeModule,也就是 Timing 是由 Native 来实现的。因此也就是说 setTimeout 是由Native实现的,并通过 polyfill 的方式注入到 global 全局对象中,至于为什么像 setTimeout 这样挂在 global 下的全局变量为什么可以直接使用,这个其实是 RN 对其进行了 declare 声明:

 具体是在哪里执行的 global.setTimeout 呢? 看下面的文件:

好了,详细的实现流程我们在这里就不深究了,感兴趣的同学下来可以跟着源码更深入的研究一下。

源码地址: react-native/setUpTimers.js at 8bd3edec88148d0ab1f225d2119435681fbbba33 · facebook/react-native

RN新架构 JSI 介绍相关推荐

  1. RN:React Native原理以及新架构JSI、Fabric等概念

    说明 RN需要一个JS的运行环境, 在IOS上直接使用内置的javascriptcore, 在Android 则使用webkit.org官方开源的jsc.so. 此外还集成了其他开源组件,如fresc ...

  2. 细鹏系列裸金属服务器多核算力,鲲鹏凌云,开启多元计算新架构_外发版(40页)-原创力文档...

    鲲鹏凌云,开启多元计算新架构 华为云鲲鹏云服务整体介绍及实践 华为云鲲鹏凌云生态总监 白雁 这是最好的时代,这是最坏的时代 应用移动化和数据多样性,呼唤新的算力 移动智能终端逐渐取代传统PC 世界正在 ...

  3. 深信服副总裁张开翼:随需应变的IT新架构

    19日,由中国电子学会主办,ZD至顶网协办的第八届中国云计算大会进入了大会第二天.深信服副总裁张开翼在第二天的主会上以"随需应变的IT新架构"为主题,介绍了深信服从专注于网络安全到 ...

  4. 揭秘 Flink 1.9 新架构,Blink Planner 你会用了吗?

    本文为 Apache Flink 新版本重大功能特性解读之 Flink SQL 系列文章的开篇,Flink SQL 系列文章由其核心贡献者们分享,涵盖基础知识.实践.调优.内部实现等各个方面,带你由浅 ...

  5. Netflix如何设计一个能满足5倍增长量的时序数据存储新架构?

    2016年1月,Netflix在全球范围内扩展业务.越来越多的会员.越来越多的语言和越来越多的视频回放将时间序列数据存储架构扩展到了它的临界点(详见第1部分文章<Netflix实战指南:规模化时 ...

  6. 微服务架构总结性介绍 (深度好文)

    微服务架构总结性介绍 (深度好文) 资料来源:有架构给我的一些资料,以及自己百度和论坛.社区找来的一些资料,权当做一个总结式的简介... 目录如下: 一.微服务架构介绍 二.出现和发展 三.传统开发模 ...

  7. 常见的系统架构设计介绍

    系统架构设计介绍 分布式架构 初始阶段架构 特征 应用服务和数据服务以及文件服务分离 特征 问题 使用缓存改善性能 特征 问题 使用应用服务器集群 特征 描述 问题 数据库读写分离 特征 描述 问题 ...

  8. CPU架构:CPU架构详细介绍

    1 概述          CPU架构是CPU商给CPU产品定的一个规范,主要目的是为了区分不同类型的CPU.目前市场上的CPU分类主要分有两大阵营,一个是intel.AMD为首的复杂指令集CPU,另 ...

  9. Linux内核架构:CPU架构详细介绍(图例解析)

    一. 概述 CPU架构是CPU商给CPU产品定的一个规范,主要目的是为了区分不同类型的CPU.目前市场上的CPU分类主要分有两大阵营,一个是intel.AMD为首的复杂指令集CPU,另一个是以IBM. ...

最新文章

  1. wine安装lingoes
  2. 九项重要的职业规划提示
  3. 《此生未完成》痛句摘抄(4)
  4. 小程序 mpvue 使用canvas绘制环形图表
  5. IDEA配置jdk (SDK)
  6. Netty入门笔记-Linux网络I/O模型介绍
  7. 摄影测量学之航片中重要的点线面
  8. Java游戏程序设计 第3章 游戏程序的基本框架
  9. CSV 文件打开乱码?如何批量解决 CSV 文件乱码问题
  10. 皮尔逊/斯皮尔曼相关系数
  11. MODIS数据下载及图像处理教程
  12. 在拼多多上班,是一种什么样的体验?我心态崩了呀!
  13. 基于阿里云的一般性系统安全措施介绍
  14. Excel转PDF如何解决显示不全的问题?
  15. 赚钱的地方,除了工资,还有这些
  16. Android开发系统应用程序
  17. iOS 客户端基于 WebP 图片格式的流量优化(下)
  18. 计算机三级网络app,‎App Store 上的“计算机三级网络技术题库”
  19. 山寨山寨版手机安全卫士源码项目
  20. 服务器更新git版本报错 git-compat-util.h:14:2:error “Required C99 support is in a test phase“

热门文章

  1. PyTorch 在 Windows 10 系统下的环境配置及安装
  2. R语言25-Prosper 贷款数据分析1
  3. 单利终值和现值matlab,单利终值现值和复利终值现值公式
  4. 漫威电影和程序员、Git 到底有什么关系?
  5. UOS 录制电脑播放的音频 / 内录音频
  6. js二维数组定义和初始化的三种方法
  7. 台式电脑无法开机,一直显示未检测到信号/显示检测信号线解决方法
  8. 谷歌浏览器Chrome 100即将发布,但可能无法正常使用?
  9. java如何画五角星_Java——绘制五角星
  10. 如何关闭windows电脑里的休眠