arm64 页表以及映射分析

news2024/9/20 1:01:46

arm64 页表映射分析

  • 1 linux 6.10 xilinx内核的内存配置
  • 2 arm64不同粒度页的页表
    • 2.1 4KB页面粒度的页表配置
    • 2.2 16KB页面粒度的页表配置
    • 2.3 64KB页面粒度的页表配置
  • 3 页表描述符
    • 3.1 无效页表描述符
    • 3.2 L0~L2页表描述符
    • 3.3 L3页表描述符
  • 4 linux arm64 页表映射
    • 4.1 __create_pgd_mapping
    • 4.2 __create_pgd_mapping_locked
    • 4.3 alloc_init_pud
    • 4.4 alloc_init_cont_pmd
    • 4.5 init_pmd
    • 4.6 alloc_init_cont_pte
    • 4.7 init_pte

1 linux 6.10 xilinx内核的内存配置

在.config配置文件中可以看到如下的配置
配置内存的虚拟地址和物理地址总线位宽为48位,页面粒度为4K大小。

  • 内存page size设置为4K
CONFIG_ARM64_4K_PAGES=y
  • 虚拟地址位宽为48位
CONFIG_ARM64_VA_BITS_48=y                                                                                                                                                                                  
CONFIG_ARM64_VA_BITS=48
  • 物理地址位宽为48位
CONFIG_ARM64_PA_BITS_48=y
CONFIG_ARM64_PA_BITS=48
  • 页表级别的配置:配置为4级页表
CONFIG_PGTABLE_LEVELS=4

2 arm64不同粒度页的页表

有3种不同粒度的内存页面设置:4KB16KB64KB

2.1 4KB页面粒度的页表配置

When you use a 4kB granule size, the hardware can use a 4-level look up process. The 48-bit address has nine address bits per level translated, that is 512 entries each, with the final 12 bits selecting a byte within the 4kB coming directly from the original address.
Bits 47:39 of the Virtual Address index into the 512 entry L0 table. Each of these table entries spans a 512 GB range and points to an L1 table. Within that 512 entry L1 table, bits 38:30 are used as index to select an entry and each entry points to either a 1GB block or an L2 table. Bits 29:21 index into a 512 entry L2 table and each entry points to a 2MB block or next table level. At the last level, bits 20:12 index into a 512 entry L2 table and each entry points to a 4kB block.
在这里插入图片描述
在这里插入图片描述

2.2 16KB页面粒度的页表配置

When you use a 16kB granule size, the hardware can use a 4-level look up process. The 48-bit address has 11 address bits per level translated, that is 2048 entries each, with the final 14 bits selecting a byte within the 4kB coming directly from the original address. The level 0 table contains only two entries. Bit 47 of the Virtual Address selects a descriptor from the two entry L0 table. Each of these table entries spans a 128 TB range and points to an L1 table. Within that 2048 entry L1 table, bits 46:36 are used as an index to select an entry and each entrypoints to an L2 table. Bits 35:25 index into a 2048 entry L2 table and each entry points to a 32 MB block or next table level. At the final translation stage, bits 24:14 index into a 2048 entry L2 table and each entry points to a 16kB block.
在这里插入图片描述
在这里插入图片描述

2.3 64KB页面粒度的页表配置

When you use a 64kB granule size, the hardware can use a 3-level look up process. The level 1 table contains only 64 entries.
Bits 47:42 of the Virtual Address select a descriptor from the 64 entry L1 table. Each of these table entries spans a 4TB range and points to an L2 table. Within that 8192 entry L2 table, bits 41:29 are used as index to select an entry and each entry points to either a 512 MB block or an L2 table. At the final translation stage, bits 28:16 index into an 8192 entry L3 table and each entry points to a 64kB block.
在这里插入图片描述
在这里插入图片描述

3 页表描述符

In the VMSAv8-64 translation table format, the difference in the formats of the level 0, level 1 and level 2
descriptors is:

  • Whether a Block descriptor is permitted.
  • If a Block descriptor is permitted, the size of the memory region described by that entry.
  • The maximum OA size, depending on whether ARMv8.2-LPA is implemented.

These differences depend on the translation granule, as follows:

  • 4KB granule Level 0 translation tables do not support Block descriptors.
    • A block descriptor:
      • In a level 1 table describes the mapping of the associated 1GB input address range.
      • In a level 2 table describes the mapping of the associated 2MB input address range.
    • The maximum OA size of a lookup is 48 bits.
  • 16KB granule Level 0 and level 1 translation tables do not support Block descriptors.
    • A Block descriptor in a level 2 table describes the mapping of the associated 32MB input address range.
    • The maximum OA size of a lookup is 48 bits.
  • 64KB granule Level 0 lookup is not supported.
    • In ARMv8.7 LPA is default is implemented
      • A block descriptor:
        • In a level 1 table describes the mapping of the associated 4TB input address range.
        • In a level 2 table describes the mapping of the associated 512MB input address range.
    • The maximum OA size of a lookup is 48 bits.

3.1 无效页表描述符

如果页表描述符的最低位为0则表示当前页表描述符是一个无效的页表描述符,对于L0 ~ L3页表描述符表都适用。
在这里插入图片描述

3.2 L0~L2页表描述符

根据L0 ~ L2页表描述符表的bit 1位为0还是1来区分当前的输出是一个块地址还是一个指向下一级页表的地址

  • 0 表示当前是一个块类型页表描述符,输出的为一个块地址
    • The descriptor gives the base address of a block of memory, and the attributes for that memory region.
  • 1 表示当前是一个页表类型,指向下一级页表的地址
    • The descriptor gives the address of the next level of translation table, and for a stage 1 translation, some attributes for that translation.

在这里插入图片描述

3.3 L3页表描述符

L3页表描述符表根据页面page size设置的不同,描述符表的格式有略微的区别

  • For the 4KB granule size, each entry in a level 3 table describes the mapping of the associated 4KB input address range.
  • For the 16KB granule size, each entry in a level 3 table describes the mapping of the associated 16KB input address range.
  • For the 64KB granule size, each entry in a level 3 table describes the mapping of the associated 64KB input address range.
    在这里插入图片描述
    Descriptor bit[1] identifies the descriptor type, and is encoded as:
  • 0, Reserved, invalid
    • Behaves identically to encodings with bit[0] set to 0.
    • This encoding must not be used in level 3 translation tables.
  • 1, Page Gives the address and attributes of a 4KB, 16KB, or 64KB page of memory.
    • At this level, the only valid format is the Page descriptor. The other fields in the Page descriptor are:
    • Page descriptor
      Gives the output address of a page of memory, as follows:
      • 4KB translation granule
        • Bits[47:12] are bits[47:12] of the output address for a page of memory
      • 16KB translation granule
        • Bits[47:14] are bits[47:14] of the output address for a page of memory.
      • 64KB translation granule
        • bits[47:16] are bits[47:16] of the output address for a page of memory

4 linux arm64 页表映射

在linux系统中,arm64的页表映射是通过__create_pgd_mapping函数实现的,在linux 系统中,页表的级别分为为PGD,PUD,PMD,PTE。这分别和arm64的L0,L1,L2,L3相对应。
以4KB页面4级页表为例来分析
在这里插入图片描述

4.1 __create_pgd_mapping

__create_pgd_mapping函数__create_pgd_mapping_locked实现后续的页表映射工作。

static void __create_pgd_mapping(pgd_t *pgdir, phys_addr_t phys,
				 unsigned long virt, phys_addr_t size,
				 pgprot_t prot,
				 phys_addr_t (*pgtable_alloc)(int),
				 int flags)
{
	mutex_lock(&fixmap_lock);
	__create_pgd_mapping_locked(pgdir, phys, virt, size, prot,
				    pgtable_alloc, flags);
	mutex_unlock(&fixmap_lock);
}

4.2 __create_pgd_mapping_locked

  • pgd_t *pgdir 表示的pgd页表的起始地址
  • pgd_t *pgdp = pgd_offset_pgd(pgdir, virt);获取当前pgd页表的地址
  • next = pgd_addr_end(addr, end);获取当前pgd管理页表地址的结束地址,其管理范围为512G(2^39)
  • alloc_init_pud(pgdp, addr, next, phys, prot, pgtable_alloc, flags);分配并配置当前的pud页表映射
  • phys += next - addr;下次要配置的pgd页表项的地址
  • while (pgdp++, addr = next, addr != end)这段代码中的 addr = next表示要获取下一个pgd页表项的起始地址
static void __create_pgd_mapping_locked(pgd_t *pgdir, phys_addr_t phys,
					unsigned long virt, phys_addr_t size,
					pgprot_t prot,
					phys_addr_t (*pgtable_alloc)(int),
					int flags)
{
	unsigned long addr, end, next;
	pgd_t *pgdp = pgd_offset_pgd(pgdir, virt);

	/*
	 * If the virtual and physical address don't have the same offset
	 * within a page, we cannot map the region as the caller expects.
	 */
	if (WARN_ON((phys ^ virt) & ~PAGE_MASK))
		return;

	phys &= PAGE_MASK;
	addr = virt & PAGE_MASK;
	end = PAGE_ALIGN(virt + size);

	do {
		next = pgd_addr_end(addr, end);
		alloc_init_pud(pgdp, addr, next, phys, prot, pgtable_alloc,
			       flags);
		phys += next - addr;
	} while (pgdp++, addr = next, addr != end);
}

4.3 alloc_init_pud

alloc_init_pud函数就是要在配置pud(即L1)级别的页表项

  • p4d_none(p4d)判断当前的pgd页表项是否为空,如果为空则需要配置当前的pgd页表项
  • pud_phys = pgtable_alloc(PUD_SHIFT);分配pud页表项
  • __p4d_populate(p4dp, pud_phys, p4dval);将申请的pud页表的地址配置到pgd页表项中
  • pudp = pud_set_fixmap_offset(p4dp, addr);获取pud页表项的地址
  • next = pud_addr_end(addr, end);获取当前pud页表项管理的结束地址,其管理范围为1G (2^30)
  • pud_set_huge(pudp, phys, prot);如果当前的页表描述符表类型为块设备,则输出当前的内存地址为一个1G大小粒度的huge 内存块。
  • alloc_init_cont_pmd(pudp, addr, next, phys, prot, pgtable_alloc, flags);如果当前内存是一个连续的内存,则需要继续设置其下一级页表PMD
static void alloc_init_pud(pgd_t *pgdp, unsigned long addr, unsigned long end,
			   phys_addr_t phys, pgprot_t prot,
			   phys_addr_t (*pgtable_alloc)(int),
			   int flags)
{
	unsigned long next;
	pud_t *pudp;
	p4d_t *p4dp = p4d_offset(pgdp, addr);
	p4d_t p4d = READ_ONCE(*p4dp);

	if (p4d_none(p4d)) {
		p4dval_t p4dval = P4D_TYPE_TABLE | P4D_TABLE_UXN;
		phys_addr_t pud_phys;

		if (flags & NO_EXEC_MAPPINGS)
			p4dval |= P4D_TABLE_PXN;
		BUG_ON(!pgtable_alloc);
		pud_phys = pgtable_alloc(PUD_SHIFT);
		__p4d_populate(p4dp, pud_phys, p4dval);
		p4d = READ_ONCE(*p4dp);
	}
	BUG_ON(p4d_bad(p4d));

	pudp = pud_set_fixmap_offset(p4dp, addr);
	do {
		pud_t old_pud = READ_ONCE(*pudp);

		next = pud_addr_end(addr, end);

		/*
		 * For 4K granule only, attempt to put down a 1GB block
		 */
		if (pud_sect_supported() &&
		   ((addr | next | phys) & ~PUD_MASK) == 0 &&
		    (flags & NO_BLOCK_MAPPINGS) == 0) {
			pud_set_huge(pudp, phys, prot);

			/*
			 * After the PUD entry has been populated once, we
			 * only allow updates to the permission attributes.
			 */
			BUG_ON(!pgattr_change_is_safe(pud_val(old_pud),
						      READ_ONCE(pud_val(*pudp))));
		} else {
			alloc_init_cont_pmd(pudp, addr, next, phys, prot,
					    pgtable_alloc, flags);

			BUG_ON(pud_val(old_pud) != 0 &&
			       pud_val(old_pud) != READ_ONCE(pud_val(*pudp)));
		}
		phys += next - addr;
	} while (pudp++, addr = next, addr != end);

	pud_clear_fixmap();
}

4.4 alloc_init_cont_pmd

alloc_init_cont_pmd函数用于设置其pmd页表

  • pud_none(pud)判断当前的pud页表是否为空,如果为空,则申请PMD页表,并将PMD页表的起始地址配置到pud页表项中
  • next = pmd_cont_addr_end(addr, end);获取当前pmd页表项管理的内存地址的结束地址,其范围为2M (2^21)
  • init_pmd(pudp, addr, next, phys, __prot, pgtable_alloc, flags);映射pmd页表项
static void alloc_init_cont_pmd(pud_t *pudp, unsigned long addr,
				unsigned long end, phys_addr_t phys,
				pgprot_t prot,
				phys_addr_t (*pgtable_alloc)(int), int flags)
{
	unsigned long next;
	pud_t pud = READ_ONCE(*pudp);

	/*
	 * Check for initial section mappings in the pgd/pud.
	 */
	BUG_ON(pud_sect(pud));
	if (pud_none(pud)) {
		pudval_t pudval = PUD_TYPE_TABLE | PUD_TABLE_UXN;
		phys_addr_t pmd_phys;

		if (flags & NO_EXEC_MAPPINGS)
			pudval |= PUD_TABLE_PXN;
		BUG_ON(!pgtable_alloc);
		pmd_phys = pgtable_alloc(PMD_SHIFT);
		__pud_populate(pudp, pmd_phys, pudval);
		pud = READ_ONCE(*pudp);
	}
	BUG_ON(pud_bad(pud));

	do {
		pgprot_t __prot = prot;

		next = pmd_cont_addr_end(addr, end);

		/* use a contiguous mapping if the range is suitably aligned */
		if ((((addr | next | phys) & ~CONT_PMD_MASK) == 0) &&
		    (flags & NO_CONT_MAPPINGS) == 0)
			__prot = __pgprot(pgprot_val(prot) | PTE_CONT);

		init_pmd(pudp, addr, next, phys, __prot, pgtable_alloc, flags);

		phys += next - addr;
	} while (addr = next, addr != end);
}

4.5 init_pmd

init_pmd函数用于配置pmd页表项

  • pmdp = pmd_set_fixmap_offset(pudp, addr);获取当前pmd页表的基地址
  • next = pmd_addr_end(addr, end);获取当前pmd页表项所管理范围的结束地址,其粒度为2M (2^21)
  • alloc_init_cont_pte(pmdp, addr, next, phys, prot, pgtable_alloc, flags);分配并映射pte页表
static void init_pmd(pud_t *pudp, unsigned long addr, unsigned long end,
		     phys_addr_t phys, pgprot_t prot,
		     phys_addr_t (*pgtable_alloc)(int), int flags)
{
	unsigned long next;
	pmd_t *pmdp;

	pmdp = pmd_set_fixmap_offset(pudp, addr);
	do {
		pmd_t old_pmd = READ_ONCE(*pmdp);

		next = pmd_addr_end(addr, end);

		/* try section mapping first */
		if (((addr | next | phys) & ~PMD_MASK) == 0 &&
		    (flags & NO_BLOCK_MAPPINGS) == 0) {
			pmd_set_huge(pmdp, phys, prot);

			/*
			 * After the PMD entry has been populated once, we
			 * only allow updates to the permission attributes.
			 */
			BUG_ON(!pgattr_change_is_safe(pmd_val(old_pmd),
						      READ_ONCE(pmd_val(*pmdp))));
		} else {
			alloc_init_cont_pte(pmdp, addr, next, phys, prot,
					    pgtable_alloc, flags);

			BUG_ON(pmd_val(old_pmd) != 0 &&
			       pmd_val(old_pmd) != READ_ONCE(pmd_val(*pmdp)));
		}
		phys += next - addr;
	} while (pmdp++, addr = next, addr != end);

	pmd_clear_fixmap();
}

4.6 alloc_init_cont_pte

alloc_init_cont_pte函数用于做pte页表的映射工作。

  • pmd_none(pmd)判断当前的pmd页表是否为空,如果为空,则分配pte页表并配置到pmd页表项中
  • init_pte(pmdp, addr, next, phys, __prot) pte页表项的映射配置
static void alloc_init_cont_pte(pmd_t *pmdp, unsigned long addr,
				unsigned long end, phys_addr_t phys,
				pgprot_t prot,
				phys_addr_t (*pgtable_alloc)(int),
				int flags)
{
	unsigned long next;
	pmd_t pmd = READ_ONCE(*pmdp);

	BUG_ON(pmd_sect(pmd));
	if (pmd_none(pmd)) {
		pmdval_t pmdval = PMD_TYPE_TABLE | PMD_TABLE_UXN;
		phys_addr_t pte_phys;

		if (flags & NO_EXEC_MAPPINGS)
			pmdval |= PMD_TABLE_PXN;
		BUG_ON(!pgtable_alloc);
		pte_phys = pgtable_alloc(PAGE_SHIFT);
		__pmd_populate(pmdp, pte_phys, pmdval);
		pmd = READ_ONCE(*pmdp);
	}
	BUG_ON(pmd_bad(pmd));

	do {
		pgprot_t __prot = prot;

		next = pte_cont_addr_end(addr, end);

		/* use a contiguous mapping if the range is suitably aligned */
		if ((((addr | next | phys) & ~CONT_PTE_MASK) == 0) &&
		    (flags & NO_CONT_MAPPINGS) == 0)
			__prot = __pgprot(pgprot_val(prot) | PTE_CONT);

		init_pte(pmdp, addr, next, phys, __prot);

		phys += next - addr;
	} while (addr = next, addr != end);
}

4.7 init_pte

init_pte函数用于做pte页表的映射工作

  • ptep = pte_set_fixmap_offset(pmdp, addr);获取当前pte页表的起始地址
  • set_pte(ptep, pfn_pte(__phys_to_pfn(phys), prot));配置pte页表项
  • phys += PAGE_SIZE;每次往后移一个PAGE,即配置下一个内存页面。
static void init_pte(pmd_t *pmdp, unsigned long addr, unsigned long end,
		     phys_addr_t phys, pgprot_t prot)
{
	pte_t *ptep;

	ptep = pte_set_fixmap_offset(pmdp, addr);
	do {
		pte_t old_pte = READ_ONCE(*ptep);

		set_pte(ptep, pfn_pte(__phys_to_pfn(phys), prot));

		/*
		 * After the PTE entry has been populated once, we
		 * only allow updates to the permission attributes.
		 */
		BUG_ON(!pgattr_change_is_safe(pte_val(old_pte),
					      READ_ONCE(pte_val(*ptep))));

		phys += PAGE_SIZE;
	} while (ptep++, addr += PAGE_SIZE, addr != end);

	pte_clear_fixmap();
}

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

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

相关文章

Java 字符串类型的JSON数组转List<Object>

1.依赖 <dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.7.21</version></dependency> 2.代码 import cn.hutool.json.JSONUtil; import java.util.List;/*** 字符串类型的JSON数组…

Java 把 Map 的值(Value)转换为 Array, List 或 Set

概述 在这篇短文中&#xff0c;我们将会展示如何把 Map 中的值取出来&#xff0c;转换为一个 Array,、List 或者一个 Set。 当然&#xff0c;你可以使用 Java JDK 来进行转换&#xff0c;你也可以使用 Guava 来进行转换。 首先&#xff0c;让我们来看看&#xff0c;如何使用原…

理解和实现简单的XOR神经网络

本文介绍了神经网络的基本知识&#xff0c;并以实现一个简单的XOR神经网络为例&#xff0c;详细解释了神经网络的工作原理和关键概念。我们将利用Python编写的代码来逐步理解并实现这个神经网络。 神经网络是一种模仿生物神经系统的计算模型&#xff0c;用于处理复杂的输入数据…

生产上的一次慢查询SQL优化

一、背景 MySQL版本5.6.16&#xff0c;InnoDB 生产上有个业务场景&#xff0c;需要每日拉取还款计划表里某些产品编号的所有状态为0&#xff0c;1数据的借据号和产品编号&#xff0c;SQL如下 select distinctloan_no,product_codefrom repay_plan<where>status in (0,1…

银行数字化转型导师坚鹏:银行业务数字化创新工作坊(简版)

银行业务数字化创新工作坊&#xff08;简版&#xff09; 课程背景&#xff1a; 很多银行存在以下问题&#xff1a; 不清楚如何进行业务数字化创新&#xff1f; 不知道如何开展银行数字化营销工作&#xff1f; 不知道零售业务数字化创新成功案例&#xff1f; 学员收获&a…

Filter 过滤器--基本原理--Filter 过滤器生命周期--过滤器链--注意事项和细节--全部应用实例--综合代码示例

目录 Filter 过滤器 Filter 过滤器说明 过滤器介绍 4. 应用场景 Filter 过滤器基本原理 代码示例 login.jsp LoginCLServlet.java admin.jsp ManageFilter.java xml配置 Filter 过滤器 url-pattern Filter 过滤器生命周期 ● Filter 生命周期图 FilterConfig ●…

RabbitMQ-后台使用

1、首先本地启动rabbitMQ,登录网址 http://localhost:15672/ 默认账号与密码都是gust 2、使用 user:创建用户 virtual-host:创建某个项目单独使用一个单独host和用户 3、命令添加用户与授权 添加用户 rabbitmqctl add_user admin admin 设置permissions rabbitmqctl set_…

Oracle系列之九:一文搞懂Oracle常用函数

Oracle常用系统函数 1. 字符串函数2. 数学函数3. 日期函数4. 聚合函数5. 其他 Oracle是一种关系型数据库管理系统&#xff0c;它提供了许多内置函数&#xff0c;以便用户可以更轻松地处理数据。 1. 字符串函数 &#xff08;1&#xff09;lengthb/length 计算字符串长度 len…

Linux常用命令整理

Linux常用命令整理 1. cat显示2. reboot 重启3. cd切换目录4. ls目录查看5. mkdir创建目录6. rm 删除文件7. mv目录修改8. cp拷贝9. find查找10. touch新建文件11. vi/vim修改文件12. chmod改变权限13. tar -zcvf 打包文件14. tar -xvf 解压文件15. grep文本搜索16. su, sudo17…

数据库学习总结(MySQL)

一.为什么要使用数据库? 在我们平时存储数据的时候,往往采用文件存储即可,所以,为什么要使用数据库呢? 原因在于,文件存储数据有以下缺点: 文件的安全性问题 文件不利于数据查询和管理 文件不利于存储海量数据 文件在程序中控制不方便 为了解决上述问题,我们创造了更适合…

Revit构件显隐:参数和插件控制构件显隐性操作

一、如何通过参数来控制族中不同构件的显隐性? 在这里&#xff0c;将它分享给大家~ 首先&#xff0c;我们在项目中任意绘制一道墙&#xff0c;然后任意布置一个带有门把手的门&#xff0c;如下图&#xff1a; 接着&#xff0c;我们【双击】进入这个门族的编辑界面&#xff0c;…

C++ 互斥锁原理以及实际使用介绍

兄弟姐妹们&#xff0c;我又回来了&#xff0c;今天带来实际开发中都需要使用的互斥锁的内容&#xff0c;主要聊一聊如何使用互斥锁以及都有哪几种方式实现互斥锁。实现互斥&#xff0c;可以有以下几种方式&#xff1a;互斥量&#xff08;Mutex&#xff09;、递归互斥量&#x…

掌握虚拟专用网络配置

目录 一、防火墙的IPSEC VPN 二、DSVPN 一、防火墙的IPSEC VPN 总体拓扑如下&#xff1a;实现PC间的加密通信。 FW1的配置&#xff1a;划分接口配置地址。 定义感兴趣流&#xff1a; 注&#xff1a;什么是感兴趣流&#xff1f;答&#xff1a;感兴趣流是VPN的术语&#xff0…

银行数字化转型导师坚鹏:数字化思维创新与金融业转型升级

数字化思维创新与金融业转型升级 课程背景&#xff1a; 很多金融机构存在以下问题&#xff1a; 金融机构的员工不知道需要具备什么样的数字化思维 不清楚数字化思维对金融机构转型升级的重要影响&#xff1f; 不清楚数字化背景下如何进行金融机构转型升级&#xff1f; …

Linux 块设备 EMMC 驱动介绍

目录: 高质量文章导航-持续更新中 前置:硬件接口 EMMC(Embedded Multi-Media Card)是一种用于存储和传输数据的嵌入式存储器(芯片),通常用于移动设备和嵌入式设备中。Linux内核提供了一个通用的EMMC驱动框架,可以支持各种不同的EMMC设备。 EMMC总线采用了典型的主从…

通过Python的PIL库给图片添加图片水印

文章目录 前言一、素材准备1.原图2.水印图 二、使用PIL库给图片添加图片水印1.引入库2.定义图片路径3.打开原图4.打开水印图片5.计算水印图片大小6.计算原图大小7.调整水印图片大小7.1调整前7.2调整后 8.计算水印图片位置8.1左上8.2左下8.3右上8.4右下8.5中间 9.添加水印10.保存…

Windows11台式机连接Type-C触摸屏显示器

我的设备是GoBiggerR便携触控屏&#xff0c;有1个mini-HDMI和2个USB-C接口。家用的是台式机&#xff0c;玩一些游戏用触控比较方便&#xff0c;于是想把触控屏利用上。 先说结论&#xff0c;我的方案是使用arpara VR DisplayPort 1.4数据线。 arpara 5K VR头显配件3.5米数据线…

U-Boot 初次编译

1.在 Ubuntu 中创建存放 uboot 的目录 &#xff0c;比如我的是/home/hsj/linux/IMX6ULL/uboot,然后在此目录 下新建一个名为“alientek_uboot”的文件夹用于存放 uboot 源码。alientek_uboot 文件夹创建成功以后使用 FileZilla 软件将正点原子提供的 uboot 源码拷贝到此目录中.…

Docker 部署 MySQL 一主多从

服务器规划&#xff1a;使用docker方式创建&#xff0c;主从服务器IP一致&#xff0c;端口号不一致 主服务器&#xff1a;容器名 mysql-master&#xff0c;端口 3306从服务器&#xff1a;容器名 mysql-slave1&#xff0c;端口 3307从服务器&#xff1a;容器名 mysql-slave2&am…

springboot+vue幼儿园管理系统(源码+文档)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的幼儿园管理系统。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 &#x1f495;&#x1f495;作者&#xff1a;风歌…