OpenSBI的PMP

news2024/11/15 21:58:16

概述

在RISC-V体系架构中,PMP是用于保护物理内存访问权限的机制。PMP机制允许为不同的物理内存区域指定访问权限(读、写、执行)。这种机制使得运行在RISC-V处理器上的软件只能访问被明确授权的物理地址空间,从而提高了系统的安全性和稳定性。

SMEPMP机制是一个增强型的PMP扩展,是为了防止M模式内存访问和执行的PMP增强。因为对于一段内存,PMP机制无法实现只有U/S模式访问,而M模式无法访问的情况,SMEPMP机制就是为了解决这一点。

PMP

在RISC-V体系架构中,M模式权限最高,可以访问全部系统内存资源。在复位时,S/U模式默认没有对内存的访问权限(包括指令预取和数据访问),其需要M模式进行PMP权限配置。当处理器处于S/U模式时,PMP会对每次的访问进行权限检查。另外,如果PMP配置为锁定状态,M模式下软件访问内存也需要进行PMP权限检查。设计者可以配置PMP保护的粒度,但标准的PMP保护区域至少为4个字节。

PMA可以配合PMA一起进行检查和保护。另外如果访问的地址是虚拟地址,也需要进行PMP权限检查。

CSR寄存器

PMP使用配置表项(8位宽,用pmpNcfg表示,N表示表项数)和地址寄存器,定义了一个访问控制区域的配置属性。RISC-V体系架构最大支持64个表项,芯片设计可以根据实际情况实现0个、16个或者64个PMP表项。

以RV32为例,有16个CSR寄存器用于配置PMP,即pmpcfg0~pmpcfg15,每个CSR寄存器又划分为4个PMP表项,因此总共支持64(16*4)个PMP表项。

PMP配置寄存器

每个PMP表项(pmpNcfg)的总共8位,定义如下:

PMP配置表项

每个字段的含义如下:

字段说明
RBit[0]配置读权限
WBit[1]配置写权限
XBit[2]配置执行权限
ABit[4:3]]地址匹配模式,支持OFF/TOR/NA4/NAPOT四种模式
LBit[7]锁定状态,如果为0表示PMP只对S/U模式生效;如果为1针对所有模式,即对M/S/U模式均生效

关于地址匹配模式,定义如下:

A名称说明
0OFF不进行PMP权限检查
1TOR地址边界模式
2NA4固定4字节的地址粒度
3NAPOT2n的地址粒度,最少8字节

同样,有64个PMP地址寄存器的CSR,即pmpadr0-pmpaddr63。以RV32为例,如下所示,使用Bit[31:0]存储访问地址的Bit[33:2]。

PMP地址寄存器

锁定和特权模式

如果PMP表项中的L字段置位,再次配置表项寄存器和地址寄存器将无效,直至硬件复位。

另外如果PMP表项中的L字段置位,M模式的访问也需要进行PMP检查;如果L字段清零,M模式的PMP表项均会成功,即不进行PMP检查。

优先级与匹配逻辑

如果一个地址匹配到多个PMP表项,那么编号小的PMP表项优先级最高。另外PMP检查也是基于地址范围的,例如如果PMP配置的地址范围为4字节区域[0xC-0xF],那么一个8字节的访问[0x8-0xF]将会失败。

如果M模式没有匹配到任何PMP表项,其访问均会成功。而对于S/U模式,如果没有匹配到任何PMP表项,但只要设置了一个表项,其访问均会失败。

SMEPMP

RISC-V体系架构通过sstatus中的SUM比特和页表中的U比特提供两种机制:SMAP(Supervisor Memory Access Prevention)和SMEP(Supervisor Memory Execution Prevention)。前者防止S模式访问U模式下的内存数据,后者防止S模式执行U模式下的内存代码。

SMAP和SMEP机制只是针对S模式,而对于M模式,此前的标准没有提供这些特性。SMEPMP机制就是为了给M模式提供类似SMAP/SMEP支持。

为了便于理解,定义了一些术语。

名称说明
PMP Entry一对pmpcfgpmpaddr寄存器
PMP Rule寄存器pmpcfgpmpaddr中的内容,表示保护的物理内存区域
Ignored忽略PMP Rule检查规则,允许所有访问请求
Enforced只允许PMP Rule中声明的访问权限请求,PMP检查失败会引发异常
Denied忽略PMP Rule检查规则,不允许任何访问请求
LockedPMP表项中的pmpcfg.L置位
PMP reset所有PMP配置复位,恢复成默认值

SMEPMP机制

新增一个M模式的CSR寄存器mseccfg(Machine Security Configuration),用于配置不同的安全特性。mseccfg目前引入三个bit:

  • MML:M模式锁定(Machine Mode Lockdown)
  • MMWP:M模式白名单策略(Machine-Mode alloWhlist Policy)
  • RLB:规则锁定绕过(Rule Locking Bypass)

主要是新增了msecfg.MML位,其和之前的PMP中的mppcfg.L位,一起配合使用。先考虑请求的地址匹配到PMP表项(下图上半部分):

  • msecfg.MML=0时,访问检查机制还是和之前没有SMEPMP一样,即下图左半部分。
  • msecfg.MML=1时,即如下图右半部分(先暂时不考虑最后三行的情形):
    • 如果mppcfg.L=0,S/U模式根据PMP表项进行权限检查(Enforced),而禁止M模式访问(Denied)
    • 如果mppcfg.L=1,禁止S/U模式模式访问(Denied&Locked),而M模式据PMP表项进行权限检查(Enforced&Locked)

针对最后三行的情形,主要是保证写和执行权限不会同时存在,解决不同特权等级的共享内存问题,因为SMEPMP机制造成M模式与S/U模式之间无法共享内存,会对性能造成影响。

如果mppcfg.L=1msecfg.MML=1,即配置的区域上锁后,S/U模式就不能再次访问该区域。SMEPMP引入一个msecfg.RLB位,关闭锁定效果,注意RISC-V建议这只能用于调试或者简单的环境。

SMEPMP机制

再考虑请求的地址没有匹配到PMP表项(上图下半部分):

  • msecfg.MMWP=1时,拒绝所有模式的访问
  • msecfg.MMWP=0时:
    • 如果mppcfg.MML=0,拒绝S/U模式访问(Denied),M模式忽略权限检查(Ignored)
    • 如果mppcfg.MML=1,拒绝S/U模式访问(Denied),M模式只允许读写权限(RW Ignored),拒绝执行权限(X Denied)

代码

在OpenSBI的初始化代码中,对PMP进行了配置,针对是否具有SMEPMP扩展,有两种方式:只有PMP机制调用sbi_hart_oldpmp_configure函数,有SMEPMP机制调用sbi_hart_smepmp_configure函数:

int sbi_hart_pmp_configure(struct sbi_scratch *scratch)
{
	int rc;
	unsigned int pmp_bits, pmp_log2gran;
	unsigned int pmp_count = sbi_hart_pmp_count(scratch);
	unsigned long pmp_addr_max;

	if (!pmp_count)
		return 0;

	pmp_log2gran = sbi_hart_pmp_log2gran(scratch);
	pmp_bits = sbi_hart_pmp_addrbits(scratch) - 1;
	pmp_addr_max = (1UL << pmp_bits) | ((1UL << pmp_bits) - 1);

	if (sbi_hart_has_extension(scratch, SBI_HART_EXT_SMEPMP))
		rc = sbi_hart_smepmp_configure(scratch, pmp_count,
						pmp_log2gran, pmp_addr_max);
	else
		rc = sbi_hart_oldpmp_configure(scratch, pmp_count,
						pmp_log2gran, pmp_addr_max);

	/*
	 * As per section 3.7.2 of privileged specification v1.12,
	 * virtual address translations can be speculatively performed
	 * (even before actual access). These, along with PMP traslations,
	 * can be cached. This can pose a problem with CPU hotplug
	 * and non-retentive suspend scenario because PMP states are
	 * not preserved.
	 * It is advisable to flush the caching structures under such
	 * conditions.
	 */
	if (misa_extension('S')) {
		__asm__ __volatile__("sfence.vma");

		/*
		 * If hypervisor mode is supported, flush caching
		 * structures in guest mode too.
		 */
		if (misa_extension('H'))
			__sbi_hfence_gvma_all();
	}

	return rc;
}

PMP

下面是前一种PMP机制的实现:

static int sbi_hart_oldpmp_configure(struct sbi_scratch *scratch,
				     unsigned int pmp_count,
				     unsigned int pmp_log2gran,
				     unsigned long pmp_addr_max)
{
	struct sbi_domain_memregion *reg;
	struct sbi_domain *dom = sbi_domain_thishart_ptr();
	unsigned int pmp_idx = 0;
	unsigned int pmp_flags;
	unsigned long pmp_addr;

	sbi_domain_for_each_memregion(dom, reg) {
		if (pmp_count <= pmp_idx)
			break;

		pmp_flags = 0;

		/*
		 * If permissions are to be enforced for all modes on
		 * this region, the lock bit should be set.
		 */
		if (reg->flags & SBI_DOMAIN_MEMREGION_ENF_PERMISSIONS)
			pmp_flags |= PMP_L;

		if (reg->flags & SBI_DOMAIN_MEMREGION_SU_READABLE)
			pmp_flags |= PMP_R;
		if (reg->flags & SBI_DOMAIN_MEMREGION_SU_WRITABLE)
			pmp_flags |= PMP_W;
		if (reg->flags & SBI_DOMAIN_MEMREGION_SU_EXECUTABLE)
			pmp_flags |= PMP_X;

		pmp_addr = reg->base >> PMP_SHIFT;
		if (pmp_log2gran <= reg->order && pmp_addr < pmp_addr_max) {
			pmp_set(pmp_idx++, pmp_flags, reg->base, reg->order);
		} else {
			sbi_printf("Can not configure pmp for domain %s because"
				   " memory region address 0x%lx or size 0x%lx "
				   "is not in range.\n", dom->name, reg->base,
				   reg->order);
		}
	}

	return 0;
}

上面代码根据Domain域中定义的内存区域进行PMP表项配置,即权限标志和地址范围,如果所有模式(M/S/U)都需要进行PMP权限检查,需要将PMP的L比特置位pmpcfg.L=1

SMEPMP

如果有SMEPMP机制,实现如下:

static int sbi_hart_smepmp_configure(struct sbi_scratch *scratch,
				     unsigned int pmp_count,
				     unsigned int pmp_log2gran,
				     unsigned long pmp_addr_max)
{
	struct sbi_domain_memregion *reg;
	struct sbi_domain *dom = sbi_domain_thishart_ptr();
	unsigned int pmp_idx, pmp_flags;

	/*
	 * Set the RLB so that, we can write to PMP entries without
	 * enforcement even if some entries are locked.
	 */
	csr_set(CSR_MSECCFG, MSECCFG_RLB);

	/* Disable the reserved entry */
	pmp_disable(SBI_SMEPMP_RESV_ENTRY);

	/* Program M-only regions when MML is not set. */
	pmp_idx = 0;
	sbi_domain_for_each_memregion(dom, reg) {
		/* Skip reserved entry */
		if (pmp_idx == SBI_SMEPMP_RESV_ENTRY)
			pmp_idx++;
		if (pmp_count <= pmp_idx)
			break;

		/* Skip shared and SU-only regions */
		if (!SBI_DOMAIN_MEMREGION_M_ONLY_ACCESS(reg->flags)) {
			pmp_idx++;
			continue;
		}

		pmp_flags = sbi_hart_get_smepmp_flags(scratch, dom, reg);
		if (!pmp_flags)
			return 0;

		sbi_hart_smepmp_set(scratch, dom, reg, pmp_idx++, pmp_flags,
				    pmp_log2gran, pmp_addr_max);
	}

	/* Set the MML to enforce new encoding */
	csr_set(CSR_MSECCFG, MSECCFG_MML);

	/* Program shared and SU-only regions */
	pmp_idx = 0;
	sbi_domain_for_each_memregion(dom, reg) {
		/* Skip reserved entry */
		if (pmp_idx == SBI_SMEPMP_RESV_ENTRY)
			pmp_idx++;
		if (pmp_count <= pmp_idx)
			break;

		/* Skip M-only regions */
		if (SBI_DOMAIN_MEMREGION_M_ONLY_ACCESS(reg->flags)) {
			pmp_idx++;
			continue;
		}

		pmp_flags = sbi_hart_get_smepmp_flags(scratch, dom, reg);
		if (!pmp_flags)
			return 0;

		sbi_hart_smepmp_set(scratch, dom, reg, pmp_idx++, pmp_flags,
				    pmp_log2gran, pmp_addr_max);
	}

	/*
	 * All entries are programmed.
	 * Keep the RLB bit so that dynamic mappings can be done.
	 */

	return 0;
}

首先调用csr_set(CSR_MSECCFG, MSECCFG_RLB):配置规则锁定绕过,即使一些PMP表项被锁定,还是可以进行重配

然后调用pmp_disable(SBI_SMEPMP_RESV_ENTRY):由于SMEPMP机制使能,会导致M模式也无法访问S/U模式的内存,因此OpenSBI保留第一个PMP表项,并在启动时关闭PMP检查,实现如下:

int pmp_disable(unsigned int n)
{
	int pmpcfg_csr, pmpcfg_shift;
	unsigned long cfgmask, pmpcfg;

	if (n >= PMP_COUNT)
		return SBI_EINVAL;

#if __riscv_xlen == 32
	pmpcfg_csr   = CSR_PMPCFG0 + (n >> 2);
	pmpcfg_shift = (n & 3) << 3;
#elif __riscv_xlen == 64
	pmpcfg_csr   = (CSR_PMPCFG0 + (n >> 2)) & ~1;
	pmpcfg_shift = (n & 7) << 3;
#else
# error "Unexpected __riscv_xlen"
#endif

	/* Clear the address matching bits to disable the pmp entry */
	cfgmask = ~(0xffUL << pmpcfg_shift);
	pmpcfg	= (csr_read_num(pmpcfg_csr) & cfgmask);

	csr_write_num(pmpcfg_csr, pmpcfg);

	return SBI_OK;
}

当需要在M与S/U模式之间共享内存,首先调用sbi_hart_map_saddr函数配置PMP表项地址区域为读写权限,由于sbi_hart_smepmp_configure会开启msecfg.MML=1,根据SMEPMP机制图,M模式此时具有读写权限(Enforced & Locked),S/U模式拒绝访问(Denied & Locked),这样之后M模式代码可以访问这块内存区域,函数实现如下:

int sbi_hart_map_saddr(unsigned long addr, unsigned long size)
{
	/* shared R/W access for M and S/U mode */
	unsigned int pmp_flags = (PMP_W | PMP_X);
	unsigned long order, base = 0;
	struct sbi_scratch *scratch = sbi_scratch_thishart_ptr();

	/* If Smepmp is not supported no special mapping is required */
	if (!sbi_hart_has_extension(scratch, SBI_HART_EXT_SMEPMP))
		return SBI_OK;

	if (is_pmp_entry_mapped(SBI_SMEPMP_RESV_ENTRY))
		return SBI_ENOSPC;

	for (order = MAX(sbi_hart_pmp_log2gran(scratch), log2roundup(size));
	     order <= __riscv_xlen; order++) {
		if (order < __riscv_xlen) {
			base = addr & ~((1UL << order) - 1UL);
			if ((base <= addr) &&
			    (addr < (base + (1UL << order))) &&
			    (base <= (addr + size - 1UL)) &&
			    ((addr + size - 1UL) < (base + (1UL << order))))
				break;
		} else {
			return SBI_EFAIL;
		}
	}

	pmp_set(SBI_SMEPMP_RESV_ENTRY, pmp_flags, base, order);

	return SBI_OK;
}

当M模式完成对这块内存区域的读写操作后,需要调用sbi_hart_unmap_saddr函数,进而调用pmp_disable函数,将锁定pmpcfg.L=0,根据SMEPMP机制图,M模式拒绝访问(Denied),S/U模式根据PMP配置权限进行检查(Enforced),这样就实现了在M与S/U模式之间共享内存,同时也保证M模式不能随意访问S/U模式的内存区域:

int sbi_hart_unmap_saddr(void)
{
	struct sbi_scratch *scratch = sbi_scratch_thishart_ptr();

	if (!sbi_hart_has_extension(scratch, SBI_HART_EXT_SMEPMP))
		return SBI_OK;

	return pmp_disable(SBI_SMEPMP_RESV_ENTRY);
}

然后当msecfg.MML=0时,配置只有M模式可以访问区域的PMP表项,接着使能csr_set(CSR_MSECCFG, MSECCFG_MML),即msecfg.MML=1,最后配置共享区域和只有S/U模式访问区域的PMP表项。

Qemu支持PMP

由于我们采用的qemu模拟器不支持SMEPMP(最新的版本支持SMEPMP),因此只会使用简单的PMP机制,PMP配置的内存区域,打印输出结果如下:

Domain0 Name              : root
Domain0 Boot HART         : 0
Domain0 HARTs             : 0*
Domain0 Region00          : 0x0000000010000000-0x0000000010000fff M: (I,R,W) S/U: (R,W)
Domain0 Region01          : 0x0000000002000000-0x000000000200ffff M: (I,R,W) S/U: ()
Domain0 Region02          : 0x0000000080040000-0x000000008005ffff M: (R,W) S/U: ()
Domain0 Region03          : 0x0000000080000000-0x000000008003ffff M: (R,X) S/U: ()
Domain0 Region04          : 0x000000000c000000-0x000000000fffffff M: (I,R,W) S/U: (R,W)
Domain0 Region05          : 0x0000000000000000-0xffffffffffffffff M: () S/U: (R,W,X)
Domain0 Next Address      : 0x0000000080200000
Domain0 Next Arg1         : 0x0000000082200000
Domain0 Next Mode         : S-mode
Domain0 SysReset          : yes
Domain0 SysSuspend        : yes

参考

(R,X) S/U: ()
Domain0 Region04 : 0x000000000c000000-0x000000000fffffff M: (I,R,W) S/U: (R,W)
Domain0 Region05 : 0x0000000000000000-0xffffffffffffffff M: () S/U: (R,W,X)
Domain0 Next Address : 0x0000000080200000
Domain0 Next Arg1 : 0x0000000082200000
Domain0 Next Mode : S-mode
Domain0 SysReset : yes
Domain0 SysSuspend : yes


# 参考

1. [RISC-V 安全拓展调研(Part 1)](https://tinylab.org/rvsec-intro-part1/)
# 欢迎关注“安全有理”微信公众号。
![安全有理](https://img-blog.csdnimg.cn/c8320ffa587140b7a3f3b1deca83115b.png)

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

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

相关文章

5.登录功能的开发

登录功能的开发 一、前端1.1首页跳转到登录页面1.2登录界面处理 二、后端2.1创建User实体类2.2创建UserDto类2.3创建UserServlet类 三、效果演示四、轻提示组件 一、前端 1.1首页跳转到登录页面 登录页面是一个单独的页面&#xff0c;我们需要从项目的首页跳转到登录页面。具…

OpenAI Whisper Cannot Import Numpy

题意&#xff1a;“OpenAI Whisper 无法导入 Numpy” 问题背景&#xff1a; I am trying to run the OpenAI Whisper model but running into the following error when trying to run my script: “我正在尝试运行 OpenAI Whisper 模型&#xff0c;但在运行脚本时遇到了以下…

【全网行为管理解决方案】上网行为系统有哪些?

全网行为管理系统是一种用于监控、管理和优化企业内部网络中所有用户活动及网络流量的技术解决方案。 这类系统可以帮助企业提高网络安全、优化网络性能&#xff0c;并确保网络使用符合公司政策及法规要求。以下是几种常用的上网行为管理系统&#xff1a; 一、安企神 特点&am…

防范小程序隐私合规风险,筑牢用户信任防线

随着国内APP软件生态的成熟&#xff0c;依托于头部APP的小程序逐渐成为零售、娱乐、出行等行业必选的获客渠道之一。较低的开发成本和成熟的用户营销功能&#xff0c;令小程序的数量在过去几年呈指数级增长。截止2023年&#xff0c;头部APP内集成的小程序总量已超千万。然而&am…

【业余玩儿AI】【文档问答】实操记录0822

电梯 前文回顾继续踩坑实录从头来过,docker部署Ollama我一定是被偷听了,大数据之神把我拉出泥潭时间回到白天,模型初窥时间回到开心的链接上Ollama那一刻 按捺住乱撞的小鹿,我去准备下文的剧情了 前文回顾 这里书接上文,上文说到: 目标是文档问答,先是本地部署了Marker,然后又…

计算机二级题--指针 章节

1.概念 1.函数名代表函数的入口地址 2.交换地址 1.*s,说明s是一个指针变量 2.s&k;说明让s指向k地址 3.所以*sk实际上与上面那句是等价的,因此m一直都没有什么变化依然是3 4.k是全局变量所以是5 3&#xff0c;7&#xff1b;改变s指向之后&#xff0c;又将值赋给了s指向的…

WS2812B硬件电路设计总结

一、WS2812b的电压是多少&#xff1f; WS2812B的电压通常在3.5到5.3V之间。 WS2812B是一种流行的可编程LED&#xff0c;也称为NeoPixel。它集成了RGB LED和控制电路&#xff0c;可以通过单个数据线进行串联连接。这种LED的输入电源电压范围为3.5到5.3V&#xff0c;这意味着它…

WPF中的XAML是如何转换成对象的?

起因 最近有遇到有小伙伴在实现TreeView不同层级使用不同数据模板时&#xff0c;遇到了一些问题。 经过查阅资料&#xff0c;我提供了两种解决方案。 第一种是使用TemplateSelector&#xff0c;这种方式可以根据ViewModel设置不同的数据模板。 第二种是根据数据动态创建数据…

中兴 随身WIFI 5产品参数

产品参数 无线参数无线速率2.4GHz, 300Mbps天线类型内置Wi-Fi天线软件功能手机App中兴ZTE Link APP Pro更多功能移动网络&#xff08;4G/3G&#xff09;接入、Wi-Fi接入、Wi-Fi加密认证、WebUI、PIN保护、FOTA升级等硬件规格接口Micro USB/标准SIM卡&#xff08;2FF&#xff0…

软件测试 缺陷报告处理流程

系统软件 操作系统 软件缺陷 缺陷报告 当测试人员发现了一个缺陷&#xff0c;需要填写一份 缺陷报告 来记录这个缺陷&#xff0c;并通过这个缺陷报告告知开发人员所发生的问题————缺陷报告是测试人员和开发人员交流沟通的重要工具。 缺陷报告的组成 1、缺陷ID 缺陷编号&…

JuiceFS 在多云架构中加速大模型推理

在大模型的开发与应用中&#xff0c;数据预处理、模型开发、训练和推理构成四个关键环节。本文将重点探讨推理环节。在之前的博客中&#xff0c;社区用户 BentoML 和贝壳的案例提到了使用 JuiceFS 社区版来提高模型加载的效率。本文将结合我们的实际经验&#xff0c;详细介绍企…

Linux——网络(2)

一、通信 --- 不同主机上进程间的通信 1、IP和端口号 IP&#xff1a;标识网络中的一台主机 本质上 32位的整型数据 端口号: 标识某个进程 本质上 16位的整型数据 2、udp和tcp udp的特点: 1.无连接 2.不可靠 tcp的特点&#xff1a; 1.面…

【赵渝强老师】执行Oracle的冷备份与冷恢复

冷备份与冷恢复是指发生在数据库已经正常关闭的情况下进行的备份和恢复。由于此时数据库已经关闭&#xff0c;通过冷备份可以将数据库的关键性文件拷贝到另外存储位置。冷备份因为只是拷贝文件&#xff0c;因此备份的速度非常快。在执行恢复时&#xff0c;只需将文件再拷贝回去…

命令模式:如何利用命令模式实现手游后端架构?

成长路上不孤单&#x1f60a;【14后boy&#xff0c;C爱好者&#xff0c;持续分享所学&#xff0c;如有需要欢迎收藏转发&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#xff01;&#xff01;&#xff01;接上篇博文&#xf…

拍抖音在哪里去水印,三招教你快速掌握去水印技巧

在抖音上&#xff0c;我们经常会看到一些精彩的内容&#xff0c;想要保存下来&#xff0c;但往往视频上会有水印。本文将分享五个免费且高效的去除抖音视频水印的技巧&#xff0c;帮助你轻松保存无水印的视频。 技巧一&#xff1a;奈斯水印助手(小程序) 奈斯水印助手是一款专…

为技术博客添加评论功能:Gitalk 教程与实战

为技术博客添加评论功能&#xff1a;Gitalk 教程与实战 简介安装使用创建 Github Application方式1方式2 主页传送门&#xff1a;&#x1f4c0; 传送 简介 Gitalk是一个基于 GitHub Issue 和 Preact 开发的评论插件。   Gitalk是一个现代、无后端、基于GitHub Issue的评论系…

基于单片机的程控电源显示控制电路设计

摘要 : 介绍了基于单片机程控电源显示控制电路的硬件设计和软件实现 &#xff0c; 该设计可以实现程控电源的输出显示和手动控制功能。 实践验证 &#xff0c; 该设计具有很好的使用效果和工程价值 。 关键词 : 程控电源 ; 显示控制 ; 单片机 0 引言 程控电源广泛地应用在…

python怎么写乘法表

代码如下&#xff1a; 代码详解&#xff08;为了让自己理解&#xff09;&#xff1a; for i in range(1,10):# print(i,end )for j in range(1,i1):print(%s*%s%s %(i,j,i*j),end )print() 1. for i in range(1,10) 这是一个for循环语句&#xff0c;range&#xff08;&…

无线数传模块是啥东西?

一 、 产品概述 无线数传模块是用来替代传统数据采集、通讯、控制布线的占用工业级模块。 无线数传模块一款工作在免费频段、5000m传输距离模块发射功率158mW、具有高稳定性、低功耗、高性价比、工业级特点。 模块具有多种传输距离规格可供选择&#xff0c;根据应用场景需要&am…

【python】基础一

目录 数据类型数据类型转换-整数/浮点数/字符串标识符运算符字符串扩展字符串拼接字符串格式化字符串精度字符串格式化-快速写法对表达式进行格式化字面量变量数据输入 数据类型 查看数据类型&#xff1a;type(数据) #输出结果&#xff1a;classint> print(type(10))数据类型…