Fuchsia X86 kernel启动代码分析

article/2025/9/18 10:38:02

Google整Fuchsia代码整了好些年了,近期是有看到说Fuchsia可能会正式商用了,所以抽了空把Fuchsia代码下了下来,想从kernel起好好捋一捋代码,想从根本上理解其kernel部分的实现。

理解任何的系统,都是得从启动开始,先简单看了下Fuchsia平台上的启动部分。同样的,任何的系统启动都会分成芯片初始化–bootloader–kernel—应用程序,这样一个完整的过程。

而在X86平台上,因为BIOS的存在,所以芯片平台初始化的工作会由BIOS来完成,而且往往BIOS是闭源的,所以我们只能从bootloader开始。而fushia系统的bootloader逻辑也比较简单,由UEFI BIOS load起来,再通过UEFI接口找到启动分区,load kernel到内存,然后跳入kernel启动。

今天重点分析 kernel启动代码Start.S的代码逻辑:

// Copyright 2016 The Fuchsia Authors
// Copyright (c) 2009 Corey Tabaka
// Copyright (c) 2015 Intel Corporation
// Copyright (c) 2016 Travis Geiselbrecht
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT#include <asm.h>
#include <arch/x86/asm.h>
#include <arch/x86/descriptor.h>
#include <arch/x86/mmu.h>
#include <arch/x86/registers.h>
#include <zircon/tls.h>#define ADDR_OFFSET_MASK ((1 << ADDR_OFFSET)-1)
#define SHIFT_OFFSET(_s) ((_s) >> 3)
#define SHIFT_REMAIN(_s) ((_s) - (SHIFT_OFFSET(_s) << 3))// Set a page table entry for the kernel module relocated 64-bit virtual
// address in 32-bit code. Clobbers the %ecx register.
.macro set_relocated_page_table_entry table, shift, value// Extract 32-bit chunk of kernel_relocated_base containing the index bits// for this page level shift.mov PHYS(kernel_relocated_base + SHIFT_OFFSET(\shift)), %ecx// Get the exact portion of the 32-bit value that is the indexshrl $SHIFT_REMAIN(\shift), %ecxandl $ADDR_OFFSET_MASK, %ecx// Get the address on the page table of index * 8 and set the valueshll $3, %ecxaddl $PHYS(\table), %ecxmovl \value, (%ecx)
.endm// Clobbers %rax, %rdx.
.macro sample_ticks outrdtscshl $32, %rdxor %rdx, %raxmov %rax, \out
.endm// This section name is known specially to kernel.ld and gen-kaslr-fixups.sh.
// This code has relocations for absolute physical addresses, which do not get
// adjusted by the boot-time fixups (which this code calls at the end).
.section .text.boot, "ax", @progbits
.align 8
FUNCTION_LABEL(_start)// As early as possible collect the time stamp.sample_ticks %r15  //取当前时间/* set up a temporary stack pointer */mov $PHYS(_kstack_end), %rsp  //设置栈指针,编译的时候,预留了栈段// Save off the bootdata pointer in a register that won't get clobbered.mov %rsi, %rbx// The fixup code in image.S runs in 64-bit mode with paging enabled,// so we can't run it too early.  But it overlaps the bss, so we move// it before zeroing the bss.  We can't delay zeroing the bss because// the page tables we're about to set up are themselves in bss.// The first word after the kernel image (at __data_end in our view)// gives the size of the following code.  Copy it to _end.mov PHYS(__data_end), %ecxmov $PHYS(__data_end+4), %esimov $PHYS(_end), %edirep movsb // while (ecx-- > 0) *edi++ = *esi++;// Now it's safe to zero the bss.            //bss段内存清0movl $PHYS(__bss_start), %edimovl $PHYS(_end), %ecxsub %edi, %ecx              // Compute the length of the bss in bytes.xor %eax, %eaxrep stosb // while (ecx-- > 0) *edi++ = al;// _zbi_base is in bss, so now it's safe to set it.mov %rbx, PHYS(_zbi_base)          //rbx是启动时,bootloader给的第二个参数mov %r15, PHYS(kernel_entry_ticks)  //保存启动时间/* give the boot allocator a chance to compute the physical address of the kernel */call boot_alloc_init//  在x86上,内存映射采用了四级映射表,一般分别命名是pml4, pdp,pd,跟PT,但是google这段代码,命名了一个PTE表,我开始一直认为PTE表是PT,但是捋下面这段代码的时候一直捋不通。后来才发现google的这个PTE表,应该是通俗一点的PD表,尼玛害死人了.Lpaging_setup64:/* initialize the default page tables *//* Setting the First PML4E with a PDP table reference*///好理解,pdp表其实就是一个物理页,讲pdp这个物理页的地址放到pml4这个物理页上,因为一个物理页是4KB,每个物理页可以放512个64位的指针数组,比如一个pml4物理页就能放512个指向pdp页的地址指针。movl $PHYS(pdp), %eax        orl  $X86_KERNEL_PD_FLAGS, %eaxmovl %eax, PHYS(pml4)//同样,把PTE物理页的地址送到PDP表中,所以感觉PTE应该叫PD。。/* Setting the First PDPTE with a Page table reference*/movl $PHYS(pte), %eaxorl  $X86_KERNEL_PD_FLAGS, %eaxmovl %eax, PHYS(pdp)
//一个PTE页可以索引到2M*512 = 1GB的内存,而一个PTE页是由一个PDP页中的一个条目指向的。//pdp_high是另外一个PDP表,由pml4表中某一个项的指针指过来/* point the pml4e at the second high PDP (for -2GB mapping) */movl $PHYS(pdp_high),   %eaxorl  $X86_KERNEL_PD_FLAGS, %eaxset_relocated_page_table_entry pml4, PML4_SHIFT, %eax//pdp_high里指向与上一个PDP表中同样的PTE表,没明白这是什么个操作/* point the second pdp at the same low level page table */movl $PHYS(pte), %eaxorl  $X86_KERNEL_PD_FLAGS, %eaxset_relocated_page_table_entry pdp_high, PDP_SHIFT, %eax//更新从0开始的2M跳一次的地址到PTE表中,一个PTE物理页有512个(0x200)64位指针。/* map the first 1GB in this table */movl $PHYS(pte), %esimovl $0x200, %ecxxor  %eax, %eax
//左移21位是以2M为间隔
0:mov  %eax, %ebxshll $21, %ebxorl  $X86_KERNEL_PD_LP_FLAGS, %ebxmovl %ebx, (%esi)addl $8,%esiinc  %eaxloop 0b// 这个linear_map_pdp感觉是PD表,因为要索引64G的内存,索引需要64个物理页来作为PD表,一个PD表页可以索引1G内存/* set up a linear map of the first 64GB at 0xffffff8000000000 */movl $PHYS(linear_map_pdp), %esimovl $32768, %ecxxor  %eax, %eax/* loop across these page tables, incrementing the address by 2MB */
0:mov  %eax, %ebxshll $21, %ebxorl  $X86_KERNEL_PD_LP_FLAGS, %ebx    // lower word of the entrymovl %ebx, (%esi)mov  %eax, %ebxshrl $11, %ebx      // upper word of the entrymovl %ebx, 4(%esi)addl $8,%esiinc  %eaxloop 0b// 将linear_map_pdp中的64个PD页表分别指向pdp_high中的不停条目/* point the high pdp at our linear mapping page tables */movl $PHYS(pdp_high), %esimovl $64, %ecxmovl $PHYS(linear_map_pdp), %eaxorl  $X86_KERNEL_PD_FLAGS, %eax0:movl %eax, (%esi)add  $8, %esiaddl $4096, %eaxloop 0b/** Set PGE to enable global kernel pages*/mov   %cr4, %raxor    $(X86_CR4_PGE), %raxmov   %rax, %cr4
//将pml4放进cr3中,物理内存与虚拟内存映射生效/* load the physical pointer to the top level page table */mov  $PHYS(pml4), %raxmov  %rax, %cr3// Load our new GDT by physical pointer.// _temp_gdtr has it as a virtual pointer, so copy it and adjust.movw PHYS(_temp_gdtr), %axmovl PHYS(_temp_gdtr+2), %ecxsub $PHYS_ADDR_DELTA, %ecxmovw %ax, -6(%esp)movl %ecx, -4(%esp)lgdt -6(%esp)// 将high_entry函数的虚拟地址压入栈中,因为这个是函数执行的返回地址,函数返回后直接执行该地址,用这种方式让high_entry函数执行起来。/* long jump to our code selector and the high address relocated */push  $CODE_64_SELECTORmov  $PHYS(high_entry), %raxaddq PHYS(kernel_relocated_base), %raxpushq %raxlretq// 在虚拟地址空间执行代码。设置GDT/IDT,最重要的是最终通过call lk_main函数跳入到C代码当中。// This code runs at the final virtual address, so it should be pure PIC.
.text
high_entry:/* zero our kernel segment data registers */xor %eax, %eaxmov %eax, %dsmov %eax, %esmov %eax, %fsmov %eax, %gsmov %eax, %ss/* load the high kernel stack */lea _kstack_end(%rip), %rsp// move_fixups_and_zero_bss copied the fixup code to _end.// It expects %rdi to contain the actual runtime address of __code_start.lea __code_start(%rip), %rdicall _end// The fixup code won't be used again, so the memory can be reused now./* reload the gdtr after relocations as it relies on relocated VAs */lgdt _temp_gdtr(%rip)// Set %gs.base to &bp_percpu.  It's statically initialized// with kernel_unsafe_sp set, so after this it's safe to call// into C code that might use safe-stack and/or stack-protector.lea bp_percpu(%rip), %raxmov %rax, %rdxshr $32, %rdxmov $X86_MSR_IA32_GS_BASE, %ecxwrmsr/* set up the idt */lea _idt_startup(%rip), %rdicall idt_setupcall load_startup_idt/* assign this core CPU# 0 and initialize its per cpu state */xor %edi, %edicall x86_init_percpu// Fill the stack canary with a random value as early as possible.// This isn't done in x86_init_percpu because the hw_rng_get_entropy// call would make it eligible for stack-guard checking itself.  But// %gs is not set up yet in the prologue of the function, so it would// crash if it tried to use the stack-guard.call choose_stack_guard// Move it into place.mov %rcx, %gs:ZX_TLS_STACK_GUARD_OFFSET// Don't leak that value to other code.xor %ecx, %ecx// configure the kernel base address// TODO: dynamically figure this out once we allow the x86 kernel to be loaded anywheremovl $PHYS_LOAD_ADDRESS, kernel_base_phys(%rip)// Collect the time stamp of entering "normal" C++ code in virtual space.sample_ticks kernel_virtual_entry_ticks(%rip)/* call the main module */call lk_main0:                          /* just sit around waiting for interrupts */hlt                     /* interrupts will unhalt the processor */pausejmp 0b                  /* so jump back to halt to conserve power */.bss
.align 16
DATA(_kstack).skip 4096
DATA(_kstack_end)// These symbols are used by image.S
.global IMAGE_ELF_ENTRY
IMAGE_ELF_ENTRY = _start// This symbol is used by gdb python to know the base of the kernel module
.global KERNEL_BASE_ADDRESS
KERNEL_BASE_ADDRESS = KERNEL_BASE - KERNEL_LOAD_OFFSET

上面这段汇编代码的整体逻辑,_start函数被作为IMAGE_ELF_ENTRY了,也就是说fuchsia的kernl zircon被编译成了一个ELF文件,而这个ELF文件的入口地址为设置成了_start函数,
我们先看下在bootloader里是怎么找到这个入口的:

static int header_check(void* image, size_t sz, uint64_t* _entry, size_t* _flen, size_t* _klen) {zbi_header_t* bd = image;size_t flen, klen;uint64_t entry;if (!(bd->flags & ZBI_FLAG_VERSION)) {printf("boot: v1 bootdata kernel no longer supported\n");return -1;}zircon_kernel_t* kernel = image;if ((sz < sizeof(zircon_kernel_t)) || (kernel->hdr_kernel.type != ZBI_TYPE_KERNEL_X64) ||((kernel->hdr_kernel.flags & ZBI_FLAG_VERSION) == 0)) {printf("boot: invalid zircon kernel header\n");return -1;}flen = ZBI_ALIGN(kernel->hdr_file.length);klen = ZBI_ALIGN(kernel->hdr_kernel.length);entry = kernel->data_kernel.entry;  //ELF 的入口地址,由编译器决定if (flen > (sz - sizeof(zbi_header_t))) {printf("boot: invalid zircon kernel header (bad flen)\n");return -1;}if (klen > (sz - (sizeof(zbi_header_t) * 2))) {printf("boot: invalid zircon kernel header (bad klen)\n");return -1;}if (_entry) {*_entry = entry;}if (_flen) {*_flen = flen;*_klen = klen;}return 0;
}

kernel->data_kernel.entry 这部分会是一个ELF格式的文件头,编译器在编译的时候会放置正确的入口地址放在kernel->data_kernel.entry处。

跳入Start.S的代码如下:

static void start_zircon(uint64_t entry, void* bootdata) {
#if __x86_64__// ebx = 0, ebp = 0, edi = 0, esi = bootdata__asm__ __volatile__("movl $0, %%ebp \n""cli \n""jmp *%[entry] \n" ::[entry] "a"(entry),   //You jump。。。[ bootdata ] "S"(bootdata), "b"(0), "D"(0));
#elif defined(__aarch64__)__asm__("mov x0, %[zbi]\n"  // Argument register."mov x29, xzr\n"    // Clear FP."mov x30, xzr\n"    // Clear LR."br %[entry]\n" ::[entry] "r"(entry),[ zbi ] "r"(bootdata): "x0", "x29", "x30");
#else
#error "add code for other arches here"
#endif__builtin_unreachable();
}
      "jmp *%[entry] \n" ::[entry] "a"(entry),   //[ bootdata ] "S"(bootdata), "b"(0), "D"(0));

这个就根据入口地址直接跳进去了。

现在再回到Start.S代码中,比较头疼的时候构建虚拟地址与物理地址映射表的这部分:

.Lpaging_setup64:/* initialize the default page tables *//* Setting the First PML4E with a PDP table reference*/movl $PHYS(pdp), %eaxorl  $X86_KERNEL_PD_FLAGS, %eaxmovl %eax, PHYS(pml4)/* Setting the First PDPTE with a Page table reference*/movl $PHYS(pte), %eaxorl  $X86_KERNEL_PD_FLAGS, %eaxmovl %eax, PHYS(pdp)/* point the pml4e at the second high PDP (for -2GB mapping) */movl $PHYS(pdp_high),   %eaxorl  $X86_KERNEL_PD_FLAGS, %eaxset_relocated_page_table_entry pml4, PML4_SHIFT, %eax/* point the second pdp at the same low level page table */movl $PHYS(pte), %eaxorl  $X86_KERNEL_PD_FLAGS, %eaxset_relocated_page_table_entry pdp_high, PDP_SHIFT, %eax/* map the first 1GB in this table */movl $PHYS(pte), %esimovl $0x200, %ecxxor  %eax, %eax0:mov  %eax, %ebxshll $21, %ebxorl  $X86_KERNEL_PD_LP_FLAGS, %ebxmovl %ebx, (%esi)addl $8,%esiinc  %eaxloop 0b/* set up a linear map of the first 64GB at 0xffffff8000000000 */movl $PHYS(linear_map_pdp), %esimovl $32768, %ecxxor  %eax, %eax/* loop across these page tables, incrementing the address by 2MB */
0:mov  %eax, %ebxshll $21, %ebxorl  $X86_KERNEL_PD_LP_FLAGS, %ebx    // lower word of the entrymovl %ebx, (%esi)mov  %eax, %ebxshrl $11, %ebx      // upper word of the entrymovl %ebx, 4(%esi)addl $8,%esiinc  %eaxloop 0b/* point the high pdp at our linear mapping page tables */movl $PHYS(pdp_high), %esimovl $64, %ecxmovl $PHYS(linear_map_pdp), %eaxorl  $X86_KERNEL_PD_FLAGS, %eax0:movl %eax, (%esi)add  $8, %esiaddl $4096, %eaxloop 0b

最终虚拟内存与物理内存映射出来的图大概是这样:
Zircon在X86平台上的内存映射表
movl $PHYS(pdp), %eax
orl $X86_KERNEL_PD_FLAGS, %eax
movl %eax, PHYS(pml4)

这是图中的1部分,

    /* Setting the First PDPTE with a Page table reference*/movl $PHYS(pte), %eaxorl  $X86_KERNEL_PD_FLAGS, %eaxmovl %eax, PHYS(pdp)

这个是图中的3部分

    /* point the pml4e at the second high PDP (for -2GB mapping) */movl $PHYS(pdp_high),   %eaxorl  $X86_KERNEL_PD_FLAGS, %eaxset_relocated_page_table_entry pml4, PML4_SHIFT, %eax

这个是PTE页到1G的地址空间的部分

    /* map the first 1GB in this table */movl $PHYS(pte), %esimovl $0x200, %ecxxor  %eax, %eax0:mov  %eax, %ebxshll $21, %ebxorl  $X86_KERNEL_PD_LP_FLAGS, %ebxmovl %ebx, (%esi)addl $8,%esiinc  %eaxloop 0b

这是图中的6的部分

   /* set up a linear map of the first 64GB at 0xffffff8000000000 */movl $PHYS(linear_map_pdp), %esimovl $32768, %ecxxor  %eax, %eax/* loop across these page tables, incrementing the address by 2MB */
0:mov  %eax, %ebxshll $21, %ebxorl  $X86_KERNEL_PD_LP_FLAGS, %ebx    // lower word of the entrymovl %ebx, (%esi)mov  %eax, %ebxshrl $11, %ebx      // upper word of the entrymovl %ebx, 4(%esi)addl $8,%esiinc  %eaxloop 0b

这是图中的4与5的部分

  /* point the high pdp at our linear mapping page tables */movl $PHYS(pdp_high), %esimovl $64, %ecxmovl $PHYS(linear_map_pdp), %eaxorl  $X86_KERNEL_PD_FLAGS, %eax0:movl %eax, (%esi)add  $8, %esiaddl $4096, %eaxloop 0b

最后再简单归纳总结下Fuchsia zircon kernel在X86平台上的物理内存与虚拟内存的映射关系。
1)zircon kernel在编译的时候已经在连接脚本里给出了一个基于虚拟地址空间的地址:

SECTIONS
{. = KERNEL_BASE;PROVIDE_HIDDEN(__code_start = .);

而KERNEL_BASE定义:

./kernel/BUILD.gn:95:    "KERNEL_BASE=$kernel_base"

而kernel_base则为:

  # Virtual address where the kernel is mapped statically.  This is the# base of addresses that appear in the kernel symbol table.  At runtime# KASLR relocation processing adjusts addresses in memory from this base# to the actual runtime virtual address.if (current_cpu == "arm64") {kernel_base = "0xffffffff00000000"} else if (current_cpu == "x64") {kernel_base = "0xffffffff80100000"  # Has KERNEL_LOAD_OFFSET baked into it.}

2)zircon kernel被装载进内存的时候,有一个装载的指定的物理地址:

  kernel_zone_base = 0x100000;kernel_zone_size = 6 * 1024 * 1024;。。。。。。。。。。。。。。。if (kernel_zone_size == 0) {kernel_zone_size = 3 * 1024 * 1024;efi_status status = gBS->AllocatePages(AllocateAddress, EfiLoaderData,BYTES_TO_PAGES(kernel_zone_size), &kernel_zone_base);if (status) {printf("boot: cannot obtain %zu bytes for kernel @ %p\n", kernel_zone_size,(void*)kernel_zone_base);kernel_zone_size = 0;}}

3)也就是说zircon被装进内存的时候物理地址也确定了,虚拟地址也是被确定的,上面的Start.S这个文件,就是在X86平台上跟据这两个确定好了的地址建立一个PML4映射表,把定义好的虚拟地址与物理地址的映射关系给做好。


http://chatgpt.dhexx.cn/article/N1k2PflH.shtml

相关文章

Fuchsia 的 Rust 代码占比超 50%

谷歌近日正式面向公众推出了 Fuchsia OS&#xff0c;获得 Fuchsia 1.0 推送的设备是第一代 Nest Hub。 与基于 Linux 内核的 Chrome OS 和 Android 等操作系统不同&#xff0c;Fuchsia 使用了全新的、自研的 Zircon 微内核&#xff0c;其采用 C 编写。内核的组件则使用…

数论基础(1)扩展欧几里得定理

一、引言 扩欧在朴素欧几里得定理中扩展得到&#xff0c;主要用于解决什么问题&#xff1f; 1.求两个数的最大公约数&#xff08;朴素欧也可以解决这个问题&#xff09; 2.axbygcd(a,b),求解这个线性不定方程的一组特解。 &#xff08;补充&#xff1a;贝祖定理&#xff1a;裴…

欧几里得定理及扩展

我们都知道欧几里得算法是用来快速求两个数的最大公约数的算法&#xff0c;效率较高&#xff1a;2O(logn)。 我们先给出算法的实现&#xff1a; 1 int gcd_1(int a, int b)2 {3 if(b0) return a;4 return gcd_1(b, a%b);5 }6 7 int gcd_2(int a, int b)8 {9 while(…

欧几里得算法和唯一分解定理

gcd算法 我们通常利用gcd算法来计算两个数的最大公约数。 gcd求法有很多种&#xff0c;通常我们利用辗转相除法&#xff0c;辗转相除法又称欧几里得算法。其计算原理依赖于下面的定理&#xff1a; 定理&#xff1a;两个整数的最大公约数等于其中较小的那个数和两数相除余数的…

欧几里得定理、扩展欧几里德定义及中国剩余定理(数列和一些数学方面的概念)

一、 欧几里得扩展&#xff1a;是欧几里得算法的扩展&#xff0c;已知整数a&#xff0c;b&#xff0c;扩展欧几里得算法可以 在求得a&#xff0c;b的最大公约数的同时&#xff0c;能找到整数x、y&#xff08;其中一个可能为负数&#xff09;&#xff0c;使得他们满足贝祖等式 …

《欧几里德算法》原理及应用

欧几里德算法又称辗转相除法&#xff0c;用于计算两个整数a&#xff0c;b的最大公约数。其计算原理依赖于下面的定理&#xff1a; 定理&#xff1a;gcd(a,b) gcd(b,a mod b) 解释&#xff1a;a和b的最大公约数&#xff0c;等于b和a除以b余数的最大公约数 证明&#xff1a;a…

数学--数论--欧几里得定理和拓展欧几里得定理

欧几里得定理: gcd(a, b) gcd(b, a%b) 证明&#xff1a; 我们首先约定&#xff1a;m gcd(a,b) , n gcd(b, q) , a b*p q。&#xff08;这里的gcd含义跟上面一样&#xff0c;q的含义跟后面式子同&#xff09; 1. m 是a,b的最大公约数&#xff0c;那么m整除a,b q a…

欧几里得定理与扩展欧几里得

3,欧几里德定理:&#xff08;射影定理&#xff09; 定理指出素数是无限的。 a*b*c1要么是素数要么其质因子就是素数。 扩展欧几里得&#xff1a; 扩展欧几里得算法是欧几里得&#xff08;又叫辗转相除法&#xff09;的扩展。已知整数a、b&#xff0c;扩展欧几里得算法可以在…

SpringData Jpa、Hibernate、Jpa 三者之间的关系

JPA规范与ORM框架之间的关系是怎样的呢&#xff1f; JPA规范本质上就是一种ORM规范&#xff0c;注意不是ORM框架——因为JPA并未提供ORM实现&#xff0c;它只是制订了一些规范&#xff0c;提供了一些编程的API接口&#xff0c;但具体实现则由服务厂商来提供实现&#xff0c;JBo…

JPA(一):十分钟入门 JPA

一.JPA的概念 为了节省时间&#xff0c;更加具体的解释我们就略过吧。 二.在IDEA中使用JPA 2.1.添加JAP依赖 添加相关的maven依赖 <properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>1.7<…

【Tools】管理JPA数据模型的最先进的IntelliJ插件:JPA Buddy

目录 说明基础操作JPA BuddyEasyCode 说明 使用IDEA版本为 2022.2.1。由于IDEA版本或插件版本的不同&#xff0c;操作界面可能略有不同。了解了JPA Buddy和EasyCode&#xff0c;个人更倾向于JPA Buddy&#xff0c;功能更强大&#xff0c;操作简单。 JPA Buddy通过以下方式简化了…

JPA和Spring-Data-JPA简介

什么是JPA JPA(Java Persistence API)是Sun官方提出的Java持久化规范。它为Java开发人员提供了一种对象/关联映射工具来管理Java应用中的关系数据。它的出现主要是为了简化现有的持久化开发工作和整合ORM技术 ORM&#xff1a;通过使用描述对象和数据库之间映射的元数据&#x…

SpringBoot 一文搞懂Spring JPA

一文搞懂Spring JPA 什么是 JPA spirng data jpa是spring提供的一套简化JPA开发的框架&#xff0c;按照约定好的【方法命名规则】写dao层接口&#xff0c;就可以在不写接口实现的情况下&#xff0c;实现对数据库的访问和操作。同时提供了很多除了CRUD之外的功能&#xff0c;如…

JPA基础知识----JPA 基本注解,JPA API

JPA 是什么 Java Persistence API&#xff1a;用于对象持久化的 API Java EE 5.0 平台标准的 ORM 规范&#xff0c;使得应用程序以统一的方式访问持久层 JPA和Hibernate的关系 JPA 是 hibernate 的一个抽象&#xff08;就像JDBC和JDBC驱动的关系&#xff09;&…

java jpa是什么_什么是JPA?

JDBC jdbc是一组规范&#xff0c;是接口&#xff0c;由不同的数据库厂商各自提供相应的实现类&#xff0c;打包成jar包&#xff0c;也就是所谓的数据库驱动。而我们的java应用程序&#xff0c;只需要调用jdbc的接口就可以了。 而JPA是和jdbc类似的东西 什么是JPA Java Persiste…

JPA与Mybatis的区别

其实JPA和mybatis大体上没什么区别&#xff0c;架构上很相似&#xff0c;mybatis就是mapper层&#xff0c;JPA就是repository层&#xff0c;其他都一样的 JPA就是把mapper层的接口换成repository的接口: 那么接口具体长什么样呢&#xff1f; mapper层 自己写sql语句 JPA的re…

什么是JPA?Java持续性介绍

新钛云服已累计为您分享723篇技术干货 本文将了解基于 Hibernate 的 Java 持久化标准&#xff0c;学习如何使用 JPA 在关系数据库或 NoSQL 数据库中存储和管理 Java 对象。 作为一种规范&#xff0c;Jakarta Persistence API&#xff08;以前称为 Java Persistence API&#xf…

spring boot 中使用 jpa以及jpa介绍

最近在项目中使用了一下jpa&#xff0c;发现还是挺好用的。这里就来讲一下jpa以及在spring boot中的使用。 在这里我们先来了解一下jpa。 1.什么是jpa呢&#xff1f; JPA顾名思义就是Java Persistence API的意思&#xff0c;是JDK 5.0注解或XML描述对象&#xff0d;关系表的…

JPA是什么

JPA仅仅是一种规范&#xff0c;也就是说JPA仅仅定义了一些接口&#xff0c;而接口是需要实现才能工作的。所以底层需要某种实现&#xff0c;而Hibernate就是实现了JPA接口的ORM框架。 也就是说&#xff1a; JPA是一套ORM规范&#xff0c;Hibernate实现了JPA规范&#xff01;如…

数据中台(一)数据中台详解

1.数据中台的由来 数据库阶段 ---> 传统数仓 ---> 大数据平台 ----> 大数据中台 1.1.数据存储起源&#xff1a;数据库 1979年&#xff1a;Oracle1.0商用数据库发布 1996年&#xff1a;MySQL1.0发布&#xff0c;到2000年以后开始火起来。 特点&#xff1a;数据库主要面…