【机密计算-大厂有话说】AMD

news2024/10/6 14:35:09

基于 VirTEE/SEV 的 SEV-SNP 平台证明

刊号 58217,版本 v1.2,发布于 2023.7

1. 介绍

        VirTEE/sev 工具箱提供了一套基于 rust 语言的简单易用的 API 来访问 AMD EPYC 处理器内的安全处理器,这个库已经早已经支持传统的 SEV 固件,最近在 VirTEE 社区中又增加了新的 SEV-SNP 固件的支持,主要是第三代和新上市的 AMD EPYC 处理器。用户手册中包含了使用 AMD SEV-SNP 技术对可信执行环境 TEE 进行平台证明的一些潜在的解决方案,主要针对以下用户:

  • 平台所有者:管理用来部署虚拟机、容器系统软件,比如主机或者云服务商。这些系软件主要指的是运行机密虚拟机、机器容器的虚拟机监视器 hypervisor。
  • 来宾所有者:指的是部署工作负载者

        这篇用户指南探讨了 VirTEE/sev 工具箱中 SEV-SNP 技术更新带给平台所有者和来宾所有者带来的一些新的能力,同时提供了一些用例以及解释了一些用户关心的配置选项。

2. 平台所有者

        平台所有者是包括 hypervisor 在内的系统软件,用来部署机密虚拟机或者容器。

2.1 系统要求

  • 硬件:系统必须是第三代或者更新的 AMD EPYC 处理器
  • 固件:必须使用支持最新 SEV 固件的 BIOS 版本,保证具有对应的安全保护功能。VirTEE/sev SEV-SNP 特性在版本1.54.01 中兼容
  • 内核:SEV-SNP 正处在开发中,ADM 建议使用上游最新的内核补丁
  • 软件:通常来讲 AMD 建议使用上游的 OVMF/EDK II 和 QEMU 7.1,不过对于平台所有者或者来宾所有者有以下需求的,建议使用 OVMF/EDK II 最新和 QEMU 最新
    •  证明报告中需要包含验证来宾身份        
    • OVMF/EDK II 需要验证内核、initrd、cmdline 等参数

2.2 API 能力

        平台所有者应该:

  • 请求 AMD 安全处理器状态
  • 加载新扩展配置
  • 请求既存的扩展配置

2.2.1 请求 AMD 安全处理器状态

        在配置主机平台来缓存证书前,AMD 建议检查 AMD 安全处理器的状态,包括以下步骤:

1.包含 VirTEE/sev 到用户 rust 工程里

// Import library
use sev::firmware::host::*;

2.连接到固件并请求 AMD 安全处理 snp 的状态

// Open a connection to the firmware.
let mut firmware: Firmware = Firmware::open().unwrap();
// Request the current snp status of the AMD Secure Processor.
let snp_status: SnpPlatformStatus = firmware.snp_platform_status().unwrap();

2.2.2 加载新的扩展配置

        hypervisor 内存里的存储策略和证书链使得平台所有者提升了来宾所有者的使用体验,避免了向 AMD 密钥发布服务器 KDS 请求证书的麻烦,同时也减少了 CSP 对于 AMD KDS 的依赖,更重要的,这避免了在大规模部署虚拟机/容器时手动请求证书链的麻烦。

 1.包含 VirTEE/sev 到 Rust 工程中

// Import library
use sev::firmware::host::*;

2.读取证书

// Read certificate bytes.
pub const ARK: &[u8] = include_bytes!("ark.pem");
pub const ASK: &[u8] = include_bytes!("ask.pem");
pub const VCEK: &[u8] = include_bytes!("vcek.pem");

3.创建来扩展报告的配置

  • 仅证书:允许 hypervisor 在不修改报告的 TCB 的前提下存储证书链
// Generate a vector of certificates to store in hypervisor memory.
let certificates: Vec<CertTableEntry> = vec![
 CertTableEntry::new(CertType::ARK, ARK.to_vec()),
 CertTableEntry::new(CertType::ASK, ASK.to_vec()),
 CertTableEntry::new(CertType::VCEK, VCEK.to_vec()),
];
// Call the `new_certs_only` constructor to generate the extended configuration.
let ext_config: ExtConfig = ExtConfig::new_certs_only(
 certificates
);
  • 仅配置:不存储证书链的前提下配置和更新报告的 TCB 内容
// Specify the desired configuration
let configuration: Config = Config::new(
 TcbVersion::new(3, 0, 10, 169),
 0,
);
// Call the `new_config_only` constructor to generate the extended configuration.
let ext_config: ExtConfig = ExtConfig::new_config_only(
 configuration
);
  • 配置和证书:更改 TCB 报告并存储证书链
// Specify the desired configuration
let configuration: Config = Config::new(
 TcbVersion::new(3, 0, 10, 169),
 0,
);
// Generate a vector of certificates to store in hypervisor memory.
let certificates: Vec<CertTableEntry> = vec![
 CertTableEntry::new(CertType::ARK, ARK.to_vec()),
 CertTableEntry::new(CertType::ASK, ASK.to_vec()),
 CertTableEntry::new(CertType::VCEK, VCEK.to_vec()),
];
// Call the `new` constructor to generate the extended configuration.
let ext_config: ExtConfig = ExtConfig::new(
 configuration,
 certificates
);

4.连接固件并将扩展请求转发给 AMD 安全处理器

// Open a connection to the firmware.
let mut fw: Firmware = Firmware::open().unwrap();
// Forward the certificates to the AMD Secure Processor to be loaded.
if let Err(error) = fw.snp_reset_config(&ext_config) {
 // Handle an error if one is encountered.
 ...
}

2.2.3 请求既存扩展配置

请求既存扩展配置包含以下步骤:

  1.包含 VirTEE/sev 到 Rust 工程中

// Import library
use sev::firmware::host::*;

2.连接到固件并请求当前配置

// Open a connection to the firmware.
let mut fw: Firmware = Firmware::open().unwrap();
// Request the current configuration.
let current_configuration: ExtConfig = fw.snp_get_ext_config().unwrap();

3.来宾所有者

        来宾所有者是虚拟化提供商的租户,它可能拥有一个或者多个机密虚拟机或者容器不属于平台所有者的环境中。

3.1 系统要求

来宾级的支持已经全部完成,使用 Linux Kernel 5.19 或者更新的内核来访问来宾驱动。

3.2 API 能力

来宾所有者可以:

  • 请求标准的证明报告
  • 请求扩展证明报告
  • 请求通过硬件密钥派生的唯一密码学密钥

3.2.1 标准证明保证请求和证明

1.从工具箱导入需要的代码模块

// Import the modules
use sev::firmware::guest::* ;

2.创建用于证明报告的 64 字节唯一数据

// This could be a unique message, a public key, etc.
let unique_data: [u8; 64] = [
 65, 77, 68, 32, 105, 115, 32, 101, 120, 116, 114, 101, 109, 101, 108, 121, 32, 97, 
119,
 101, 115, 111, 109, 101, 33, 32, 87, 101, 32, 109, 97, 107, 101, 32, 116, 104, 101, 
32,
 98, 101, 115, 116, 32, 67, 80, 85, 115, 33, 32, 65, 77, 68, 32, 82, 111, 99, 107, 
115,
 33, 33, 33, 33, 33, 33,
];

3.连接到固件并请求 SEV-SNP 证明报告

// Open a connection to the firmware.
let mut fw: Firmware = Firmware::open()?;
// Request a standard attestation report.
let attestation_report: AttestationReport = fw.get_report(None, Some(unique_data), 
None);

4.验证信任根。验证信任根是整个证明过程中最重要的一步,opehttps://crates.io/crates/opensslnssl 工具箱提供了所有用来验证证书链签名的工具,VirTEE/sev 库也包含了一些简化证书处理的结构和函数。当前 AMD 信任根是:

  • AMD 根密钥 ARK 是自签名的
  • ARK 给 ADM 签名密钥 ASK 签名
  • ASK 给芯片版本背书密钥 VCEK 签名

比如:

  • 导入必要的证书处理的逻辑
use sev::{
 certs::snp::{ca, Certificate, Chain},
 firmware::host::CertType,
};
  • 导入相关 openssl 库
 ecdsa::EcdsaSig,
 pkey::{PKey, Public},
 sha::Sha384,
 x509::X509,
  • 从 AMD 密钥分发系统 AKS 拉取证书链,参考 VCEK 规范。所有域都至少是 2 字节长度,并且用 0 补全。证明报告中 hwid 和 chip_id 是匹配的
const KDS_CERT_SITE: &str = "https://kdsintf.amd.com";
const KDS_VCEK: &str = "/vcek/v1";
const KDS_CERT_CHAIN: &str = "cert_chain";
/// Requests the certificate-chain (AMD ASK + AMD ARK)
/// These may be used to verify the downloaded VCEK is authentic.
pub fn request_cert_chain (sev_prod_name: &str) -> (ask, ark) {
 // Should make -> https://kdsintf.amd.com/vcek/v1/{SEV_PROD_NAME}/cert_chain
 let url: String = format!("{KDS_CERT_SITE}{KDS_VCEK}/{sev_prod_name}/
{KDS_CERT_CHAIN}");
 println!("Requesting AMD certificate-chain from: {url}");
 let rsp: Response = get(&url).unwrap();
let body: Vec<u8> = rsp.bytes().unwrap().to_vec(); 
let chain: Vec<x509> = X509::stack_from_pem(&body).unwrap();
// Create a ca chain with ark and ask
let ca_chain: ca::Chain = ca::Chain::from_pem(&chain[1].to_pem, &chain[0].to_pem);
ca_chain
};
/// Requests the VCEK for the specified chip and TCP
pub fn request_vcek(chip_id: [u8; 64], reported_tcb: TcbVersion) -> X509 {
 let hw_id: String = hexify(&chip_id);
 let url: String = format!(
 "{KDS_CERT_SITE}{KDS_VCEK}/{SEV_PROD_NAME}/\
 {hw_id}?blSPL={:02}&teeSPL={:02}&snpSPL={:02}&ucodeSPL={:02}",
 reported_tcb.boot_loader,
 reported_tcb.microcode
 ); 
 println!("Requesting VCEK from: {url}\n");
 let rsp_bytes = get(&url).unwrap().bytes().unwrap().to_vec();
 Certificate::from_der(&rsp_bytes)
};
  • 验证信任根
let ca_chain: ca::Chain = request_cert_chain("milan");
// chip_id and reported_tcb should be pulled from the host machine,
// or an attestation report. let vcek: Certificate = request_vcek(
chip_id,
reported_tcb
);
// Create a full-chain with the certificates:
Let cert_chain = Chain{ca: ca_chain, vcek: vcek};
//Now you can simply verify the whole chain in one command.
cert_chain.verify().unwrap();
//Or you can verify each certificate individually
let ark = cert_chain.ca.ark;
let ask = cert_chain.ca.ask;
if (&ark,&ark).verify().unwrap() {
 println!("The AMD ARK was self-signed...");
 if (&ark,&ask).verify().unwrap() {
 iprintln!("The AMD ASK was signed by the AMD ARK...");
 f (&ask,&vcek).verify().unwrap() {
 println!("The VCEK was signed by the AMD ASK...");
} else {
 eprintln!("The VCEK was not signed by the AMD ASK!");
 }
} else {
 eprintln!("The AMD ASK was not signed by the AMD ARK!");
 }
} else {
 eprintln!("The AMD ARK is not self-signed!");
}

5.通过验证证明报告中以下域和 VCEK 来验证来宾可信计算机 TCB

  • Bootloader
  • TEE
  • SNP
  • Microcode
  • Chip ID

openssl 和 VirTEE/sev 工具箱都不支持 x509v3 扩展验证,一个可信的解决方案是使用 x509解析器工具箱的 asn1 rs。下面示例就是基于这些定义构建起来的:

/
******************************************************************************************
 * RELEVANT X509v3 EXTENSION OIDS
******************************************************************************************
/
use asn1_rs::{oid, Oid};
use x509_parser::{
 self,
 certificate::X509Certificate,
 pem::{parse_x509_pem, Pem},
 prelude::X509Extension,
};
enum SnpOid {
 BootLoader,
 Tee,
 Snp,
 Ucode,
 HwId,
}
impl SnpOid {
 fn oid(&self) -> Oid {
 match self {
 SnpOid::BootLoader => oid!(1.3.6.1.4.1.3704.1.3.1),
 SnpOid::Tee => oid!(1.3.6.1.4.1.3704.1.3.2),
 SnpOid::Snp => oid!(1.3.6.1.4.1.3704.1.3.3),
 SnpOid::Ucode => oid!(1.3.6.1.4.1.3704.1.3.8),
 SnpOid::HwId => oid!(1.3.6.1.4.1.3704.1.4),
 }
 }
}
impl std::fmt::Display for SnpOid {
 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 write!(f, "{}", self.oid().to_id_string())
 }
}

/
******************************************************************************************
 * HELPER FUNCTIONS
******************************************************************************************
/
fn check_cert_ext_byte(ext: &X509Extension, val: u8) -> bool {
 if ext.value[0] != 0x2 {
 panic!("Invalid type encountered!");
 }
 
 if ext.value[1] != 0x1 && ext.value[1] != 0x2 {
 panic!("Invalid octet length encountered");
 }
 
 if let Some(byte_value) = ext.value.last() {
 *byte_value == val
 } else {
 false
 }
}
fn check_cert_ext_bytes(ext: &X509Extension, val: &[u8]) -> bool {
 ext.value == val
}
/
******************************************************************************************
 * EXAMPLE ATTESTATION FUNCTION:
******************************************************************************************
/
fn validate_cert_metadata(
 cert: &X509Certificate,
 report: &AttestationReport,
) -> bool {
 let extensions: HashMap<Oid, &X509Extension> = cert.extensions_map().unwrap();
 
 if let Some(cert_bl) = extensions.get(&SnpOid::BootLoader.oid()) {
 if !check_cert_ext_byte(cert_bl, report.reported_tcb.boot_loader) {
 eprintln!("Report TCB Boot Loader and Certificate Boot Loader mismatch 
encountered.");
 return false;
 }
 println!("Reported TCB Boot Loader from certificate matches the attestation 
report.");
 }
 
 if let Some(cert_tee) = extensions.get(&SnpOid::Tee.oid()) {
 if !check_cert_ext_byte(cert_tee, report.reported_tcb.tee) {
 eprintln!("Report TCB TEE and Certificate TEE mismatch encountered.");
 return false;
 }
 println!("Reported TCB TEE from certificate matches the attestation report.");
 }
 
 if let Some(cert_snp) = extensions.get(&SnpOid::Snp.oid()) {
 if !check_cert_ext_byte(cert_snp, report.reported_tcb.snp) {
 eprintln!("Report TCB SNP and Certificate SNP mismatch encountered.");
 return false;
 }
 println!("Reported TCB SNP from certificate matches the attestation report.");
 }
 
 if let Some(cert_ucode) = extensions.get(&SnpOid::Ucode.oid()) {
 if !check_cert_ext_byte(cert_ucode, report.reported_tcb.microcode) {
 eprintln!("Report TCB Microcode and Certificate Microcode mismatch 
encountered.");
 return false;
 }
 println!("Reported TCB Microcode from certificate matches the attestation 
report.");
 }
 
 if let Some(cert_hwid) = extensions.get(&SnpOid::HwId.oid()) {
 if !check_cert_ext_bytes(cert_hwid, &report.chip_id) {
 eprintln!("Report TCB Microcode and Certificate Microcode mismatch 
encountered.");
 return false;
 }
 println!("Chip ID from certificate matches the attestation report.");
 }
 
 true
}

6.验证报告中的签名确实是由 VCEK 签发的

let ar_signature: EcdsaSig = EcdsaSig::try_from(&report.signature).unwrap();
let signed_bytes: &[u8] = &bincode::serialize(&report).unwrap()[0x0..0x2A0];
let amd_vcek_pubkey: EcKey<Public> = vcek.public_key().unwrap().ec_key().unwrap();
let mut hasher: Sha384 = Sha384::new()
hasher.update(signed_bytes);
let base_message_digest: [u8; 48] = hasher.finish();
if ar_signature.verify(base_message_digest.as_ref(), vcek_pubkey.as_ref()).unwrap() {
 println!("VCEK signed the Attestation Report!");
} else {
 eprintln!("VCEK did NOT sign the Attestation Report!");
}
// Or you can use a complete certificate chain to verify the attestation report
(&report, &certificate_chain).verify().unwrap()

3.2.2 扩展证明报告的请求和证明

1.创建 64 字节唯一数据用来证明报告中

// This could be a unique message, a public key, etc.
let unique_data: [u8; 64] = [
 65, 77, 68, 32, 105, 115, 32, 101, 120, 116, 114, 101, 109, 101, 108, 121, 32, 97, 
119,
 101, 115, 111, 109, 101, 33, 32, 87, 101, 32, 109, 97, 107, 101, 32, 116, 104, 101, 
32,
 98, 101, 115, 116, 32, 67, 80, 85, 115, 33, 32, 65, 77, 68, 32, 82, 111, 99, 107, 
115,
 33, 33, 33, 33, 33, 33,
];

2. 连接到固件并请求扩展报告

let mut fw: Firmware = Firmware::open().unwrap();
let (extended_report, certificates): (AttestationReport, Vec<CertTableEntry>) = 
fw.get_ext_report(None, Some(unique_data), 0)

3.使用 VirTEE/SEV 库从 AMD 安全处理器中解析 ARK、ASK、VCEK

// Assumes all certificates are in PEM format (for simplicity).
let certs: Chain = Chain::from_cert_table_pem(certificates).unwrap();

4.使用标准证明报告信任根进行验证,跳过连接到 AMD 密钥发布服务器。

3.2.3 请求一把派生密钥

        来宾所有者根据硬件信任根衍生唯一的加密密钥有很多使用场景。这个派生的密钥可能依赖多个 TCB 部件参数,只有相同的参数才能派生出相同的密钥。

1.根据规范勾走一个 DerivedKey

let request: DerivedKey = DerivedKey::new(false, GuestFieldSelect(1), 0, 0, 0);

2.连接到固件并请求一把派生密钥

let mut fw: Firmware = Firmware::open().unwrap();
let derived_key: [u8; 32]= fw.get_derived_key(None, request).unwrap();

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/842579.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

代理模式:静态代理+JDK/CGLIB 动态代理

文章目录 1. 代理模式2. 静态代理3. 动态代理3.1. JDK 动态代理机制3.1.1. 介绍 3.1.2. JDK 动态代理类使用步骤3.1.3. 代码示例3.2. CGLIB 动态代理机制3.2.1. 介绍3.2.2. CGLIB 动态代理类使用步骤3.2.3. 代码示例 3.3. JDK 动态代理和 CGLIB 动态代理对比 4. 静态代理和动态…

uniapp 微信小程序 封装公共的请求js(api版本)

一、新建api文件夹 在项目目录下创建api文件夹&#xff0c;内放files跟index.js文件夹&#xff0c;files文件夹内放每个页面对应的js请求接口 1、index.js /*** api接口的统一出口*/ const api {}; const requireComponent require.context(./files, false, /\.js$/) requi…

3.2 防火墙

数据参考&#xff1a;CISP官方 目录 防火墙基础概念防火墙的典型技术防火墙企业部署防火墙的局限性 一、防火墙基础概念 防火墙基础概念&#xff1a; 防火墙&#xff08;Firewall&#xff09;一词来源于早期的欧式建筑&#xff0c;它是建筑物之间的一道矮墙&#xff0c;用…

【基础IO】文件系统 {磁盘的物理结构,存储结构,逻辑结构;CHS 和 LBA 寻址方式;磁盘分区和块组;文件inode;软硬链接}

文件系统 文件分为&#xff1a; 内存文件&#xff1a;被进程打开的文件&#xff0c;文件被加载到内存中供进程快速读写。磁盘文件&#xff1a;没有被打开的文件&#xff0c;保存在磁盘上。磁盘文件被分门别类的存储和管理&#xff0c;用于支持更好的存取。 提示&#xff1a; …

Amazon CodeWhisperer亚马逊云代码生成器idea体验使用

阿丹&#xff1a; 自从接触到微服务以来发现要写的代码越来越多了&#xff0c;之前一直面向ChatGPT来编程&#xff0c;今天找到了一个新的ai代码生成器。体验一下。安装的过程给兄弟们演示一下。 关键还是免费的。 连接如下:AI 代码生成器 - Amazon CodeWhisperer - AWS 查看…

记录第一篇被”华为开发者联盟鸿蒙专区 “收录的文章

记录第一篇被”华为开发者联盟鸿蒙专区 “社区收录的文章。 坚持写作的动力是什么&#xff1f; 是记录、分享&#xff0c;以及更好的思考 。

地理信息系统空间分析实验教程 第三版 第八章示例与练习 寻找最佳路径

寻找最佳路径 背景 随着社会经济的发展&#xff0c;公路的重要性日益提高。在一些交通欠发达的地区&#xff0c;公路 设迫在眉睫。如何根据实际地形情况设计出比较合理的公路&#xff0c;是一个值得研究的问题 目的 通过练习&#xff0c;熟悉 ArcGIS 栅格数据距离制图、表…

Docker搭建zookeeper

问题背景 前言 本文参考自&#xff1a;docker-compose快速搭建Zookeeper集群还有一种更加详细更加全面的部署方式&#xff1a;Docker之docker-compose一键部署Zookeeper集群&#xff0c;但笔者还未验证&#xff0c;先记录下来 搭建 安装docker-ce 此处不赘述 安装docker-co…

skywalking日志收集

文章目录 一、介绍二、添加依赖三、修改日志配置1. 添加链路表示traceId2. 添加链路上下文3. 异步日志 四、收集链路日志 一、介绍 在上一篇文章skywalking全链路追踪中我们介绍了在微服务项目中使用skywalking进行服务调用链路的追踪。 本文在全链路追踪的基础上&#xff0c…

小研究 - 基于 MySQL 数据库的数据安全应用设计(一)

信息系统工程领域对数据安全的要求比较高&#xff0c;MySQL 数据库管理系统普遍应用于各种信息系统应用软件的开发之中&#xff0c;而角色与权限设计不仅关乎数据库中数据保密性的性能高低&#xff0c;也关系到用户使用数据库的最低要求。在对数据库的安全性进行设计时&#xf…

vscode连接远程Linux服务器

文章目录 一、环境安装1.1 下载vscode1.2 下载vscode-sever 二、ssh链接2.1 安装Remote-SSH2.2 设置vscode ssh2.3 设置免密登录2.3.1 本地生成公私钥2.3.2 服务器端添加公钥 三、安装插件3.1 vscode安装插件3.1.1 在线安装插件3.1.2.1 下载插件3.1.2.2 安装插件 3.2 vscode-se…

SQL注入实操三(SQLilabs Less41-65)

文章目录 一、sqli-labs靶场1.轮子模式总结2.Less-41 stacked Query Intiger type blinda.注入点判断b.轮子测试c.获取数据库名称d.堆叠注入e.堆叠注入外带注入获取表名f.堆叠注入外带注入获取列名g.堆叠注入外带注入获取表内数据 3.Less-42 Stacked Query error baseda.注入点…

小学语文思维导图:如何写一篇好的作文

大家都知道&#xff0c;思维导图是一款非常高效的工具。我们利用思维导图不仅可以做读书笔记、还可以运用到很多具体细分的场景。今天我们就“如何利用思维导图写好一篇作文”和大家进行分享。 思维导图在写作文的过程中&#xff0c;可以帮助我们整理思路。提高效率。将混乱的内…

排序八卦炉之总复习

文章目录 1.总代码1.1Stack.h1.2Stack.c1.3Sort.h1.4Sort.c1.5Test.c 2.总结 1.总代码 点击 排序&#xff08;C&#xff09; 跳转码云获取完整代码 1.1Stack.h #pragma once #include <stdio.h> #include <assert.h> #include <stdlib.h> #include <st…

web前端html

文章目录 快捷方式一、html5的声明二、html5基本骨架 2.1 html标签 2.2 head标签 2.3 body和head同级 2.4 body标签 2.5 title标签 2.6 meta标签 三、标题标签介绍与应用 3.1 标题的介绍 3.2 标题标签位置摆放 3.3 标签之段落、换行、水平线 3.3 标签之图片 3.3.1 图…

【快应用】list组件如何区分滑动的方向?

【关键词】 list组件、滑动方向、scroll 【问题背景】 有cp反馈list这个组件在使用的时候&#xff0c;不知道如何区分它是上滑还是下滑。 【问题分析】 list组件除了通用事件之外&#xff0c;还提供了scroll、scrollbottom、scrolltop、scrollend、scrolltouchup事件&#x…

Spark官方调优三部曲之三:其它优化思路

前言 前面介绍了关于spark性能调优两个最重要的点: 数据序列化内存调优这两个方向都进行调优后,如果想进一步继续优化你的程序,可以参考下面的思路。 设置合理的并行度 除非将每个操作的并行级别设置得足够高,否则集群资源不会得到充分利用。Spark根据每个文件的大小自动…

【5G NR】逻辑信道、传输信道和物理信道的映射关系

博主未授权任何人或组织机构转载博主任何原创文章&#xff0c;感谢各位对原创的支持&#xff01; 博主链接 本人就职于国际知名终端厂商&#xff0c;负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作&#xff0c;目前牵头6G算力网络技术标准研究。 博客…

微信开发之朋友圈自动点赞的技术实现

简要描述&#xff1a; 朋友圈点赞 请求URL&#xff1a; http://域名地址/snsPraise 请求方式&#xff1a; POST 请求头Headers&#xff1a; Content-Type&#xff1a;application/jsonAuthorization&#xff1a;login接口返回 参数&#xff1a; 参数名必选类型说明wId…

WSL安装

WSL安装 1.Microsoft store 安装 1.1 启动WSL功能 在【程序和功能 -> 启用或关闭 Windows 功能】中勾选【适用于 Linux 的 Windows 子系统】 1.2 Store中下载安装 在 Microsoft Store 中下载并安装需要的 Linux 发行版 2.不使用Store安装WSL 注&#xff1a;1.1也要…