Gestión de memoria análisis de código fuente 1-ARMV8-AARCH64 MMU y proceso de mapeo de tablas de páginas de Linux

La función de la MMU es principalmente completar la traducción de la dirección , ya sea la dirección de la memoria principal (dirección DDR) o la dirección IO (dirección del dispositivo), en el sistema con la MMU habilitada, el comando de lectura y la lectura de datos. y la escritura iniciada por la CPU son todos Es una dirección virtual Dentro del ARM Core, la dirección virtual se convertirá automáticamente en una dirección física a través de la MMU, y luego la dirección física se enviará al bus AXI para completar la lectura y acceso de escritura de memoria física real y dispositivos físicos.
 

1. Descripción general de MMU/TLB/caché

  1. MMU: El trabajo completo es la conversión de una dirección virtual a una dirección física, lo que permite que múltiples programas en el sistema se ejecuten en su propio espacio de direcciones virtual independiente sin afectarse entre sí. El programa no puede saber nada acerca de la memoria física subyacente y la dirección física puede ser discontinua, pero no impide la asignación del espacio de direcciones virtuales continuas.
  2. TLB: MMUEl proceso de trabajo es el proceso de consultar la tabla de páginas. Cuando la tabla de páginas se coloca en la memoria, la sobrecarga de consulta es demasiado grande, por lo que hay un área pequeña con acceso más rápido para el almacenamiento para mejorar la eficiencia de la búsqueda 地址转换条目. Cuando cambia el contenido de la tabla de páginas, debe borrarse TLBpara evitar errores de asignación de direcciones.      

En principio, cada acceso a la memoria virtual puede causar dos secciones de acceso físico: una sección obtiene la entrada de la tabla de páginas correspondiente y la otra sección obtiene los datos requeridos .

Para superar este problema, se usa un caché, generalmente llamado Translation Lookaside Buffer (TLB).

  1. Cache: El mecanismo de almacenamiento en caché entre el procesador y la memoria se utiliza para aumentar la tasa de acceso. Habrá un caché de varios niveles en el ARMv8, que se L1 Cachedivide en 指令Cachey 数据Cache, CPU Coreen su interior, admite el direccionamiento de direcciones virtuales; L2 Cachela capacidad es mayor y tanto las instrucciones como los datos se almacenan, lo cual es CPU Corecompartido por múltiplos, y estos múltiplos CPU Coretambién forman uno Cluster.

La imagen de arriba no refleja la relación entre los cachés L1 y L2 y la MMU, aquí hay otra imagen:

¿Cómo acceder?, vea la siguiente imagen:

2 Conversión de dirección virtual a dirección física

El mapeo de la dirección virtual a la dirección física se realiza mediante el mecanismo de la tabla de búsqueda. En ARMv8, la Kernel Spacedirección base de la tabla de páginas se almacena en TTBR1_EL1el registro, y User Spacela dirección base de la tabla de páginas se almacena en TTBR0_EL0el registro. Los bits altos del espacio de direcciones del kernel son todos 1, (0xFFFF0000_00000000 ~ 0xFFFFFFFF_FFFFFFFF)la dirección del usuario Los bits altos del espacio son todos 0,(0x00000000_00000000 ~ 0x0000FFFF_FFFFFFFF)

为什么高16位可以用来区分内核空间和用户空间虚拟地址:

64位系统的地址线有64位,但是在具体实现上,并没有使用这么多,一般来说,48位的地址线就够了 

TTBR寄存器:Registro base de la tabla de traducción

Si la dirección física o la dirección virtual se almacenan en TTBRx_ELx :

El registro TTBR1_EL1 almacena la dirección física del directorio global de la página del kernel, y el registro TTBR0_EL1 almacena la dirección física del directorio global de la página del proceso.

Niveles de privilegio en armv8:

Escriba la descripción de la imagen aquí

Nivel de excepción:

EL0: modo de usuario, ejecutando programas de usuario ordinarios, nivel no privilegiado, con permisos de ejecución, acceso a memoria restringida (los programas de usuario de Linux en Armv8 se ejecutan en el nivel EL0, el nivel EL1 del kernel de Linux).

EL1: Privilegio del sistema, ejecutando el kernel del sistema operativo. Si el sistema admite extensiones virtuales, ejecute el kernel del sistema operativo de la máquina virtual.

EL2: un monitor de máquina virtual (hipervisor) que ejecuta extensiones de máquina virtual

EL3: Gestión de la Seguridad Operacional

Luego está EL3, que es más potente y tiene permisos relativamente grandes, básicamente puede acceder a todos los registros y también incluye administración de energía. Además, esto es similar a un ascensor, abriendo pasajes seguros y no seguros.

En ARMv8, se introdujeron dos estados de seguridad:

estado seguro

estado no seguro

El estado de seguridad aquí afecta principalmente el acceso a los recursos, como la memoria y los registros del sistema.

Para la memoria, a veces se requiere el aislamiento de datos para proteger la seguridad de los datos. Por lo tanto, la memoria se puede dividir en dos áreas, el área segura y el área no segura. Para el área de memoria segura, solo se permite el acceso al estado seguro, mientras que para el área de memoria no segura, se permite el acceso tanto al estado seguro como al estado no seguro. Esto mantiene los datos seguros.

Para EL1 y EL0, puede estar en estado seguro o no seguro.

Para EL2, solo puede estar en un estado no seguro. Se dice que el último ARMv8.4, EL2 puede estar en un estado seguro.

Para EL3, solo puede estar en estado seguro.

El cambio entre estados seguro y no seguro solo se puede cambiar a través de EL3, es decir, si desea cambiar de EL0 no seguro a EL0 seguro, primero debe cambiar de EL0 no seguro a EL3 a través de una excepción, y luego A través de la excepción, regrese a EL0 seguro.
Para obtener más información, consulte la arquitectura armv8

En ARMv8:

  • La dirección virtual admite
    la dirección virtual de 64 bits, no se utilizan todos los bits, excepto los 16 bits superiores que se utilizan para distinguir el espacio del kernel y el espacio del usuario, la configuración de los bits efectivos puede ser: 36, 39, 42, 47. Esto determina el tamaño del espacio de direcciones en el kernel de Linux. Por ejemplo, la configuración de bits efectiva en el kernel que uso es CONFIG_ARM64_VA_BITS=39, el rango de direcciones del espacio de usuario: 0x00000000_00000000 ~ 0x0000007f_ffffffff, el tamaño es 512G, el rango de direcciones del espacio del kernel: 0xffffff80_00000000 ~ 0xffffffff_ffffffff, el tamaño es 512G, 512G=2^39.

  • Soporte de tamaño de página
    Admite 3 tamaños de página: 4KB, 16KB, 64KB.

  • Compatibilidad con tablas de páginas
    Admite al menos dos niveles de tablas de páginas, hasta cuatro niveles de tablas de páginas, Level 0 ~ Level 3.

El proceso de búsqueda de direcciones virtuales es el siguiente:

Modelo de tabla de páginas de 4 niveles de referencia:

39 bits efectivos, tamaño de página de 4 KB, tabla de páginas de 3 niveles, así que presentaré esta combinación

 4K: 2^12

4096/8 bytes = 512 entradas = 2^9

  1. La dirección virtual [63:39] se utiliza para distinguir entre el espacio del kernel y el espacio del usuario, para seleccionar diferentes TTBRn寄存器para obtener Level 1页表基地址;
  2. Se coloca la dirección virtual [38:30] Level 1页表中的索引, para encontrar la dirección del descriptor correspondiente y obtener el contenido del descriptor, y obtenerlo según el contenido del descriptor Level 2页表基地址;
  3. Dirección virtual [29:21] Level 2页表中的索引, para encontrar la dirección del descriptor correspondiente y obtener el contenido del descriptor, y obtenerlo de acuerdo con el contenido del descriptor Level 3页表基地址;
  4. Dirección virtual [20:12] Level 3页表中的索引, para encontrar la dirección del descriptor correspondiente y obtener el contenido del descriptor, obtener los 36 bits superiores de la dirección física según el contenido del descriptor y alinearlo con una dirección 4K; obtener el destino marco de página
  5. La dirección virtual [11:0] coloca el desplazamiento de la dirección física, combinado con los bits altos obtenidos de la dirección física, finalmente se obtiene la dirección física.

No se termina aquí, es hora de echar un vistazo Table Descriptor, es decir, el contenido almacenado en la tabla de páginas, hay los siguientes cuatro tipos:

El tipo está determinado por los dos bits inferiores, el medio Level 0solo Table Descriptorpuede generar Level 1la dirección de la tabla de páginas y Level 3el medio Table Descriptorsolo puede generar block addresses.
¿Ves los de la imagen attributes? Estos se pueden usar para el control de permisos de memoria, ordenamiento de memoria, operaciones de política de caché, etc.

En ARMv8, los registros asociados a la tabla de páginas son: TCR_EL1, TTBRx_EL1.

3. Asignación de tablas de páginas de Linux

3.1 Funcionamiento básico de la tabla de páginas de Linux 

Básicamente, las operaciones en la tabla de páginas en el kernel se realizarán alrededor de la figura anterior. Parece que no es apropiado separarse del código, así que analicemos el puto código fuente, principalmente hablando de varias API relacionadas con la tabla de páginas.

pgtable define la ruta del código:

arch/arm64/include/asm/pgtable-types.h: definir pgd_t, pud_t, pmd_t, pte_ty otros tipos, llamar a nopmd.h nopud.h y otros archivos de encabezado;
arch/arm64/include/asm/pgtable-prot.h: establecer el contenido del permiso en la entrada de la tabla de páginas;
arch/arm64/include/asm/pgtable-hwdef.h: incluir principalmente la división de PGD/PMD/PUD en la dirección virtual, que está relacionada al bit efectivo de la dirección virtual Está relacionado con el tamaño de la página, y también incluye la definición de la tabla de páginas de hardware, la configuración en el registro TCR, etc.;
arch/arm64/include/asm/pgtable.hrelacionado con la configuración de la tabla de páginas;

Como se puede ver en estos códigos,

  • En ese momentoCONFIG_PGTABLE_LEVELS=4 : pgd-->pud-->pmd-->pte;
  • En ese momentoCONFIG_PGTABLE_LEVELS=3 , no había PUDtablas de páginas: pgd(pud)-->pmd-->pte;
  • En ese momentoCONFIG_PGTABLE_LEVELS=2 , no había tablas de página PUDy PMD:pgd(pud, pmd)-->pte

Definiciones de macros de uso común
 

procesamiento de tablas de páginas 

/*描述各级页表中的页表项*/
typedef struct { pteval_t pte; } pte_t;
typedef struct { pmdval_t pmd; } pmd_t;
typedef struct { pudval_t pud; } pud_t;
typedef struct { pgdval_t pgd; } pgd_t;

/*  将页表项类型转换成无符号类型 */
#define pte_val(x)	((x).pte)
#define pmd_val(x)	((x).pmd)
#define pud_val(x)	((x).pud)
#define pgd_val(x)	((x).pgd)

/*  将无符号类型转换成页表项类型 */
#define __pte(x)	((pte_t) { (x) } )
#define __pmd(x)	((pmd_t) { (x) } )
#define __pud(x)	((pud_t) { (x) } )
#define __pgd(x)	((pgd_t) { (x) } )

/* 获取页表项的索引值 */
#define pgd_index(addr)		(((addr) >> PGDIR_SHIFT) & (PTRS_PER_PGD - 1))
#define pud_index(addr)		(((addr) >> PUD_SHIFT) & (PTRS_PER_PUD - 1))
#define pmd_index(addr)		(((addr) >> PMD_SHIFT) & (PTRS_PER_PMD - 1))
#define pte_index(addr)		(((addr) >> PAGE_SHIFT) & (PTRS_PER_PTE - 1))

/*  获取页表中entry的偏移值 */
#define pgd_offset(mm, addr)	(pgd_offset_raw((mm)->pgd, (addr)))
#define pgd_offset_k(addr)	pgd_offset(&init_mm, addr)
#define pud_offset_phys(dir, addr)	(pgd_page_paddr(*(dir)) + pud_index(addr) * sizeof(pud_t))
#define pud_offset(dir, addr)		((pud_t *)__va(pud_offset_phys((dir), (addr))))
#define pmd_offset_phys(dir, addr)	(pud_page_paddr(*(dir)) + pmd_index(addr) * sizeof(pmd_t))
#define pmd_offset(dir, addr)		((pmd_t *)__va(pmd_offset_phys((dir), (addr))))
#define pte_offset_phys(dir,addr)	(pmd_page_paddr(READ_ONCE(*(dir))) + pte_index(addr) * sizeof(pte_t))
#define pte_offset_kernel(dir,addr)	((pte_t *)__va(pte_offset_phys((dir), (addr))))

Una dirección lineal se puede dividir en varias partes para formar tablas de páginas de varios niveles y desplazamientos de página. Para ayudar con el corte de direcciones lineales, también se define una macro para cada nivel:

/* PAGE_SHIFT determines the page size */
	#define PAGE_SHIFT	12
	#define PAGE_SIZE	(1UL << PAGE_SHIFT)
	#define PAGE_MASK	(~(PAGE_SIZE-1))

Cómo usar las entradas de la tabla de páginas (Uso de las entradas de la tabla de páginas)

Para recorrer el directorio de páginas, se definen las siguientes tres macros para separar rápidamente una dirección lineal en sus componentes internos.

  • pgd_offset (): ingrese la dirección lineal y mm_struct, y devuelva el elemento PGD correspondiente en la dirección lineal.
#define pgd_offset(mm, address) ((mm)->pgd+pgd_index(address))
#define pgd_index(address) (((address) >> PGDIR_SHIFT) & (PTRS_PER_PGD-1))

pmd_offset (): ingrese un elemento PGD (encuentre la dirección del marco de la página) y una dirección lineal (encuentre el desplazamiento de pmd) y devuelva un PMD correspondiente

#define pmd_offset(dir, address) ((pmd_t *) pgd_page(*(dir)) + pmd_index(address))
#define pgd_page(pgd) ((unsigned long) __va(pgd_val(pgd) & PAGE_MASK))

pte_offset_kernel(): Ingrese un PMD (buscar dirección de marco de página) y una dirección lineal (buscar desplazamiento de página):

#define pte_offset_kernel(pmd, address) ((pte_t *) pmd_page_kernel(*(pmd)) +  pte_index(address))
#define pmd_page_kernel(pmd) ((unsigned long) __va(pmd_val(pmd) & PAGE_MASK))
#define pte_index(address) (((address) >> PAGE_SHIFT) & (PTRS_PER_PTE - 1))

La segunda ronda de funciones de macro se usa para verificar si la entrada de la tabla de páginas existe o si alguien la está usando:

  • pte_none(), pmd_none() y pgd_none(): devuelven 1 si el elemento correspondiente no existe
  • pte_present(), pmd_present() y pgd_present(): Devolver 1 si se establece PRESENTE del elemento correspondiente
  • pte_clear(), pmd_clear() y pgd_clear(): borrarán el elemento correspondiente
  • pmd_bad() y pgd_bad() : se utilizan para verificar si las entradas de la tabla de páginas cumplen con los requisitos

Las rutinas de uso de los grupos de macros anteriores:

pgd_t *pgd;
        pmd_t *pmd;
        pte_t *ptep, pte;
        pgd = pgd_offset(mm, address);
        if (pgd_none(*pgd) || pgd_bad(*pgd))
                 goto out;
        pmd = pmd_offset(pgd, address);
        if (pmd_none(*pmd) || pmd_bad(*pmd))
                 goto out;
         ptep = pte_offset(pmd, address);
         if (!ptep)
                goto out;
         pte = *ptep;

La tercera ronda de macros se utiliza para comprobar los permisos de las entradas de la tabla de páginas y para establecer los permisos de las entradas de la tabla de páginas. Estos permisos determinan lo que un proceso de espacio de usuario puede y no puede hacer en una página. Ejemplo: los procesos de usuario nunca pueden leer las entradas de la tabla de páginas del kernel.

  • pte_read(): se usa para probar el permiso de lectura de pte, pte_mkread() establece el permiso de lectura, pte_rdprotect() cancela el permiso de lectura
  • pte_write(): se usa para probar el permiso de escritura de pte, pte_mkwrite() establece el permiso de lectura, pte_wrprotect() cancela el permiso de lectura
  • pte_dirty(): se usa para probar si se ha escrito, pte_mkdirty() establece el bit sucio, pte_mkclean() borra el bit sucio
  • pte_young(): Se usa para probar si es una página nueva, pte_mkyoung() establece la página nueva, pte_old() establece la página anterior (verifique el bit de acceso)

Traducir y configurar las entradas de la tabla de páginas

  • mk_pte(): Ingrese una página de estructura y bits de protección para formar pte_t.
#define page_to_pfn(page)	((unsigned long)((page) - mem_map)) //mem_map中的偏移就是PFN
	#define pfn_pte(pfn, prot)	__pte(((pfn) << PAGE_SHIFT) | pgprot_val(prot)) //将PFN与权限bit为合并形成pte_t
	#define mk_pte(page, pgprot)	pfn_pte(page_to_pfn(page), (pgprot))
  • set_pte(): ingrese una dirección en un marco de página PDM y luego asigne pte_t a esta dirección.
#define set_pte(pteptr, pteval) (*(pteptr) = pteval)
  • pte_page(): Convierte pte_t a página de estructura

Asignación y liberación de tablas de páginas

Funciones de asignación: pgd_alloc(), pmd_alloc() y pte_alloc()

Funciones gratuitas: pgd_free(), pmd_free() y pte_free()

 Asignación de tabla de 4 páginas en head.S

 4.1 tablas de páginas temporales idmap_pg_dir y swapper_pg_dir

Es hora de un análisis de ejemplo, observe el proceso de creación de la tabla de páginas, la ruta del código: arch/arm64/kernel/head.S.
Durante el proceso de inicio del kernel, antes de que se haya agregado la memoria física real al sistema y la tabla de páginas no se haya inicializado, para garantizar el funcionamiento normal del sistema, se deben establecer dos tablas de páginas globales temporales: idmap_pg_diryswapper_pg_dir

¿Por qué necesitamos tablas de páginas temporales?

  • idmap_pg_dir es la tabla de página utilizada por el mapeo de identidad, es decir, la dirección física y la dirección virtual son iguales. Resuelve principalmente el problema de convertir la dirección física en una dirección virtual después de encender la MMU, para evitar que la página la tabla no se pueda obtener después de encender la MMU.
  • swapper_pg_dir es la tabla de páginas utilizada en la etapa inicial del mapeo de imágenes del kernel.Después de compilar el kernel de Linux swapper_pg_dir, la imagen del kernel debe mapearse, incluido el texto, los datos, etc. Tenga en cuenta que la memoria aquí es una pieza de memoria contigua. Es decir, las tablas de páginas (PGD/PUD/PMD) están todas conectadas entre sí y las direcciones difieren en PAGE_SIZE (4k)

idmap_pg_dir

El código relacionado con la activación de MMU se coloca en una sección especial denominada .idmap.text, que en realidad corresponde al bloque IDMAP_TEXT en el espacio de direcciones físicas de la figura anterior. El código en esta área se asigna dos veces. Como parte de la imagen del núcleo, se asigna a la dirección virtual a partir de __idmap_text_start (dirección de asignación de imagen del núcleo), y se puede ejecutar normalmente cuando se enciende la MMU.

Además, suponiendo que la dirección física del bloque IDMAP_TEXT es la dirección A, también se asigna a la dirección virtual a partir de la dirección A (dirección constante), es decir: VA:PA=(9000:9000), cuando la dirección virtual se ejecuta, obtendrá los datos de la dirección física.
A través de System.map, puede verificar qué funciones se colocan en la sección ".idmap.text".
inserte la descripción de la imagen aquí

mapeo de secciones

La macro ARM64_SWAPPER_USES_SECTION_MAPS define si swapper/idpmap usa el mapa de secciones; ¿qué es el mapeo de secciones?

Lo describimos con un ejemplo práctico. Suponga que VA es de 48 bits, el tamaño de página es 4K, niveles = 4, luego, en el proceso de mapeo de direcciones, la dirección se divide en 9 (nivel 0) + 9 (nivel 1) + 9 (nivel 2) + 9 (nivel 3) + 12 (desplazamiento de página), para una región de memoria de bloque grande, como una imagen del kernel, no vale la pena perder el uso de una página de 4K para el mapeo. En este caso, el último nivel de la tabla de páginas (entrada de la tabla de traducción de nivel 2) se puede considerar que apunta a una región de memoria de 2 M, no a la tabla de traducción del siguiente nivel. El llamado mapa de sección se refiere al uso de 2M como unidad de mapeo.

La razón principal es: si usa la tabla de páginas de segundo nivel, debe solicitar memoria para el bloque (nivel 2) de la entrada de la tabla de páginas de segundo nivel. En este momento, no existe un método de administración de memoria, por lo que es más conveniente usar el mapa de secciones directamente.

Por supuesto, el mapa de sección no se puede usar en todos los casos. Para la imagen del kernel, su dirección de inicio está alineada en 2 M, por lo que está bien cuando el tamaño del bloque es de 2 M. Para el TAMAÑO DE PÁGINA es 16 K, su descriptor de bloque apunta a un 32M Para el bloque de memoria, cuando el TAMAÑO DE LA PÁGINA es de 64 K, el descriptor del bloque apunta a un bloque de memoria de 512 M. Por lo tanto, el mapa de sección solo se puede habilitar cuando el tamaño de la página es de 4 K.

Descripción: PAGE_SIZE=16K 4 niveles

16K /8 bytes = 4*4k/8bytes=4*512=2048= 2^11

11+11+11+15 PMD->2^15=32M

Proceso de mapeo de la tabla de páginas Hemos descrito el proceso de mapeo de la dirección virtual a la dirección física. Todavía lo describimos con un ancho de dirección virtual de 39 bits y un tamaño de página de 4 K. Hay 3 niveles de tablas de páginas. PGD–>PMD–>PTE, el tamaño del mapa PTE es 4K. Sin embargo, en la fase de inicio, el sistema no mapea en unidades de 4 K, porque el propósito de la fase de inicio es leer la imagen del kernel lo antes posible y mapear en 4 K. Si el tamaño del kernel es de 16 M, el mapeo la imagen del kernel requiere 4096 veces. Por lo tanto, el mapa de sección se usará en el proceso de inicio y la tabla de páginas se reducirá un nivel para convertirse en PGD–>PMD–>offset.El tamaño del mapeo de PMD es 2M (para VA_BIT=39, offset=21bits), y para una imagen de kernel de 16M, el mínimo Solo se necesitan 8 veces para completar el mapeo.
Ahora está claro que se requieren dos niveles de tablas de páginas, PGD y PMD, en la fase de inicio, y se requieren dos tablas de páginas para identificar el mapeo y el kernel, es decir, se requieren 4 tamaños de página para almacenar la tabla de páginas en el fase de inicio ¿Cuál es la asignación de este espacio de direcciones? está en el script del enlazador vmlinux.lds.S

Explicación: VA_BIT=48bit  9+9+9+9+12 2^21=2M, solo se necesita una tabla de páginas de 3 niveles para cumplir con el requisito de mapeo de la imagen del núcleo 512*512*2M

Tome 48 bits (9+9+9+9+12) y un tamaño de página de 4K como ejemplo, si se trata de un mapa de secciones, las entradas en la tabla de traducción de PGD (Nivel 0), PUD (Nivel 1) y PMD (Nivel 2) son todos Hay 512 elementos, y cada descriptor tiene 8 bytes, por lo que estas tablas de traducción tienen 4 KB, lo que resulta ser un tamaño de página; un total de 3 tamaños de página


Dos de las tablas de páginas globales se definen arch/arm64/kernel/vmlinux.lds.Sy se colocan BSS段después de:

1. BSS_SECTION(0, 0, 0)  
 2.   
 3. . = ALIGN(PAGE_SIZE);  
 4. idmap_pg_dir = .;  
 5. . += IDMAP_DIR_SIZE;  
 6. swapper_pg_dir = .;  
 7. . += SWAPPER_DIR_SIZE;  
 8. idmap_full_pg_dir = .;  
 9. . += IDMAP_FULL_DIR_SIZE; 

/* Definir varias páginas consecutivas, almacenar respectivamente PGD, PUD, PMD, etc., que son continuas entre sí. Esto también se completa en head.S */ #define SWAPPER_DIR_SIZE (SWAPPER_PGTABLE_LEVELS * PAGE_SIZE ) #define IDMAP_DIR_SIZE (IDMAP_PGTABLE_LEVELS *
PAGE_SIZE
)

Para mapas de sección:

#define SWAPPER_PGTABLE_LEVELS (CONFIG_PGTABLE_LEVELS - 1)

4.2 Análisis del código fuente

En head.S, hay tres macros relacionadas con la creación de tablas de páginas:

  1. Análisis ARM64 __create_page_tables

premisa:

CONFIG_ARM64_PAGE_SHIFT=12

CONFIG_ARM64_VA_BITS=48

CONFIG_ARM64_PA_BITS=48

CONFIG_PGTABLE_LEVELS=4

2^32 = 4GB

2^48 = 256 TB

2^47 = 128 TB

__create_page_tables

1) Etapa de preparación

__create_page_tables:
    adrp x25, idmap_pg_dir------------------------(1)
    adrp x26, swapper_pg_dir
    mov x27, yo

    mover x0, x25-----------------------------(2)
    añadir x1, x26, #SWAPPER_DIR_SIZE
    bl __inval_cache_range

    mover x0, x25------------------------------(3)
    agregar x6, x26, #SWAPPER_DIR_ TALLA
1: stp xzr, xzr [x0], #16
    paso xzr, xzr, [x0], #16
    paso xzr, xzr, [x0], #16
    paso xzr, xzr, [x0], #16
    cmp x0, x6
    b.lo 1b

    ldr x7, =SWAPPER_MM_MMUFLAGS-----------------(4)

(1) Tome la dirección física del símbolo idmap_pg_dir y guárdela en x25. Tome la dirección física del símbolo swapper_pg_dir y guárdela en x26. No hay nada especial en este código, excepto por la instrucción adrp. adrp es para calcular el desplazamiento relativo desde la dirección de símbolo especificada hasta el valor de PC de tiempo de ejecución (sin embargo, este desplazamiento no es tan preciso, está en unidades de 4K, o los 12 bits inferiores son 0). Cuando se codifica la instrucción, el valor inmediato (es decir, el desplazamiento) ocupa 21 bits. Además, dado que el cálculo del desplazamiento se realiza de acuerdo con 4K, la dirección del símbolo final calculado debe estar entre -4G y 4G de la instrucción. Dado que la MMU no se ha activado cuando se ejecuta la instrucción, la dirección física se obtiene a través de adrp y, por supuesto, los 12 bits inferiores de la dirección física son todos ceros. Además, dado que idmap_pg_dir y swapper_pg_dir están alineados en tamaño de página en la secuencia de comandos del enlace, está bien usar el comando adrp.

La instrucción adrp calcula la dirección de la página de destino en función de la dirección de desplazamiento de la PC. Primero, adrp desplaza un valor inmediato de 21 bits con signo hacia la izquierda 12 bits para obtener un número con signo de 33 bits (el bit más alto es el bit de signo) y luego borra los 12 bits más bajos de la dirección de la PC, obteniendo así el dirección actual de la PC La dirección de la página, y luego agregue la dirección de la página donde se encuentra la dirección actual de la PC a un número con signo de 33 bits para obtener la dirección de la página de destino, y finalmente escriba la dirección de la página de destino en el registro general. El tamaño de página aquí es de 4 KB, solo para obtener un rango de direcciones más grande, y no tiene nada que ver con el tamaño de página de la memoria virtual. A través del comando adrp, se puede obtener la dirección dentro del rango de ±4GB de la dirección actual de la PC. El escenario de uso habitual es obtener primero una dirección base a través de adrp y luego obtener la dirección de una variable específica a través de la dirección de compensación de la dirección base.
El siguiente es el formato de codificación del comando adrp. El valor inmediato ocupa 21 bits. Cuando se ejecuta, el valor inmediato de 21 bits se expandirá a un número con signo de 33 bits. El bit más alto es 1, lo que indica que se trata de una instrucción aarch64.

¿Por qué adrp obtiene la dirección física?Antes de que se encienda la MMU, se accede a la memoria de la dirección física mediante la búsqueda de instrucciones de la CPU o el acceso a datos.

Modo de trabajo BRAZO:

usr (modo de usuario)

sys (modo de sistema)

svc (modo administrativo, modo protegido)

La mayoría de los programas se ejecutan en modo usr, uboot requiere altos privilegios y se ejecuta en modo sistema
 

(2) Este código es para realizar una operación de caché no válida. El alcance específico de la operación es el área de la tabla de páginas correspondiente al mapeo de identidad y el mapeo de imágenes del núcleo. La dirección inicial es idmap_pg_dir y la dirección final es swapper_pg_dir+SWAPPER_DIR_SIZE.

¿Por qué llamar a __inval_cache_range para invalidar el caché correspondiente al espacio de la tabla de páginas de idmap_pg_dir y swapper_pg_dir? De acuerdo con el protocolo de arranque, el código se ejecuta hasta este punto. El requisito para el caché es que la línea de caché correspondiente al espacio de la imagen del kernel esté limpia para el PoC, pero el espacio de la tabla de la página correspondiente a idmap_pg_dir y swapper_pg_dir no lo esté. parte de la imagen del núcleo, por lo que su línea de caché correspondiente Lo más probable es que haya algunos datos antiguos no válidos que deben limpiarse.

(3) Tiene sentido establecer el contenido de la tabla de páginas de intercambio e idmap en 0. De hecho, la mayoría de las entradas en estas tablas de traducción no se usan.Tanto PGD como PUD tienen solo una entrada que es útil, y el número de entradas válidas en PMD está relacionado con el tamaño de la dirección del mapeo. Borrar el contenido de la tabla de páginas significa configurar todos los descriptores en la tabla de páginas como inválidos (el bit 0 del descriptor indica si es válido y el igual a 0 indica un descriptor no válido).

(4) Para crear un mapeo, además de VA y PA, también se requieren parámetros del atributo de memoria, este parámetro se define de la siguiente manera:

#si ARM64_SWAPPER_USES_SECTION_MAPS

#define SWAPPER_MM_MMUFLAGS (PMD_ATTRINDX(MT_NORMAL) | SWAPPER_PMD_FLAGS)

#demás

#define SWAPPER_MM_MMUFLAGS (PTE_ATTRINDX(MT_NORMAL) | SWAPPER_PTE_FLAGS)

#terminara si

 2), establecer el mapeo de identidad

    mov x0, x25 -------------------------(1)
    adrp x3, __idmap_text_start ----- --------- ------(2)

#ifndef CONFIG_ARM64_VA_BITS_48--------------------(3)
#define EXTRA_SHIFT (PGDIR_SHIFT + PAGE_SHIFT - 3)-- --------(4)
# definir EXTRA_PTRS (1 << (48 - EXTRA_SHIFT)) -------------(5)


#if VA_BITS != EXTRA_SHIFT-----------------------(6)
#error "Discordancia entre VA_BITS y tamaño de página/número de niveles de traducción"
#endif

    adrp x5, __idmap_text_end-----------------------(7)
    clz x5, x5
    cmp x5, TCR_T0SZ(VA_BITS) -- ------- -----------(8)
    b.ge 1f

    adr_l x6, idmap_t0sz-------------------------(9)
    str x5, [x6]
    dmb si
    dc ivac, x6

    create_table_entry x0, x3, EXTRA_SHIFT, EXTRA_PTRS, x5, x6------(10)
1:
#endif

    create_pgd_entry x0, x3, x5, x6 //x0, tbl dirección física; x3 dirección virtual --- (11)
    mov x5, x3 // __pa(__idmap_text_start)
    adr_l x6, __idmap_text_end // __pa(__idmap_text_end)
    create_block_map x0, x7, x3, x5, x6------------------(12)
(1) x0 guarda la dirección física de la variable idmap_pg_dir, que es el PGD del mapeo de identidad.

(2) x3 guarda la dirección física de __idmap_text_start Para el mapeo de identidad, x3 también guarda la dirección virtual, porque la dirección virtual es igual a la dirección física.

(3) Básicamente, no hay gran problema en la creación de un mapeo de identidad. Sin embargo, si la dirección de la memoria física está ubicada en una posición muy alta, entonces hay un problema con el mapeo de identidad, porque es posible que los VA_BITS que configuró sean no lo suficientemente grande como para exceder el rango de direcciones virtuales. En este momento, necesita expandir el rango de direcciones virtuales. Por supuesto, si se configura VA_BITS de 48 bits, no habrá tal problema, porque el máximo de VA BITS que admite ARMv8 es de 48, y es imposible expandirlo.

(4) La dirección de la dirección virtual no es de 48 bits, y la dirección física de la memoria del sistema se coloca en una posición muy, muy alta. En este momento, para completar el mapeo de identidad, debemos expandir la dirección virtual, por lo que ¿cuánto cuesta? Extendido a 48 bits. Después de la extensión, se agrega un nivel EXTRA y la relación de asignación de direcciones es EXTRA--->PGD--->..., donde EXTRA_SHIFT es igual a (PGDIR_SHIFT + PAGE_SHIFT - 3).

(5) Después de la extensión, el mapeo de direcciones tiene un nivel más, lo llamamos nivel EXTRA, ¿cuántas entradas hay en la tabla de traducción de este nivel? EXTRA_PTRS da la respuesta.

(6) De hecho, en el kernel de Linux actual, existen requisitos para el mapeo de direcciones, es decir, se requiere que PGD esté completo. Por ejemplo: dirección virtual de 48 bits, tamaño de página de 4k, la relación de asignación correspondiente es PGD (9 bits) + PUD (9 bits) + PMD (9 bits) + PTE (9 bits) + desplazamiento de página (12 bits). bit), para una dirección virtual de 42 bits y un tamaño de página de 64k, la relación de mapeo correspondiente es PGD (13 bits) + PTE (13 bits) + desplazamiento de página (16 bits). Estos dos ejemplos tienen una característica común de que el número de entradas en PGD está lleno, es decir, el número de bits indexados a PGD es PAGE_SIZE-3. Si esta relación no se cumple, el kernel de Linux pensará que su configuración es problemática. Nota: Este es un requisito del kernel, de hecho, el hardware ARM64 no tiene tal requisito.

Debido a la configuración correcta, PGD está lleno, por lo que después de la expansión, EXTRA_SHIFT debe ser igual a VA_BITS; de lo contrario, debe haber algún problema con su configuración. Continuamos con el ejemplo anterior para ilustrar cómo expandir el número de bits de la dirección virtual. Para direcciones virtuales de 42 bits, tamaño de página de 64k, después de la expansión, la dirección virtual es de 48 bits y la relación de asignación de direcciones es EXTRA (6 bits) + PGD (13 bits) + PTE (13 bits) + desplazamiento de página (16 bits). -poco).

(7) x5 guarda la dirección física de __idmap_text_end. La razón de esto es determinar la dirección física más alta del mapeo de identidad y calcular cuántos 0 iniciales hay en la dirección física, de modo que se pueda juzgar si la dirección se encuentra en el espacio de direcciones físicas posición relativamente alta.

(8) La definición de macro TCR_T0SZ puede calcular el número de ceros iniciales bajo un número dado de direcciones virtuales. Si la dirección virtual es 48, entonces el 0 inicial es 16. Si la cantidad de 0 iniciales en la dirección física actual (el valor de x5) es menor que la cantidad de 0 iniciales en la dirección virtual configurada actualmente, se requiere expansión.

(9) OK, ahora ingrese la rama que necesita ser expandida, por supuesto, la configuración específica de la dirección virtual se realiza a través del campo T0SZ en el registro TCR_EL1, aún no es el momento (la configuración específica está en __cpu_setup Aquí, solo necesitamos Está bien establecer el valor de la variable de idmap_t0sz. En la función __cpu_setup, el valor se tomará de esta variable y se establecerá en el registro TCR_EL1. En el código, x6 es la dirección física de la variable idmap_t0sz y x5 es el número de ceros iniciales de la dirección física, que se guarda en la variable idmap_t0sz.

(10) Cree una entrada para la tabla de traducción adicional. Los parámetros específicos pasados ​​son los siguientes:

x0: dirección de la tabla de páginas idmap_pg_dir

x3: La dirección virtual a mapear (aunque x3 guarda la dirección física, pero el mapeo de identidad, VA y PA son los mismos)

EXTRA_SHIFT: cuando normalmente se establece el mapeo de nivel más alto, el turno es PGDIR_SHIFT. Sin embargo, debido a que la ubicación de la dirección física es demasiado alta, se requiere un mapeo adicional, por lo que aquí se necesita un mapeo de nivel adicional, por lo que el turno necesita PGDIR_SHIFT + (PAGE_SHIFT - 3).

EXTRA_PTRS: se agregó un nivel de tabla de traducción, necesitamos determinar la cantidad de descriptores contenidos en este nivel aumentado de tabla de traducción, EXTRA_PTRS proporciona este parámetro.

(11) La función create_pgd_entry se ha explicado anteriormente para crear descriptores de tablas para cada nivel intermedio.

(12) Cree la entrada de la tabla de traducción del último nivel. La entrada puede ser un descriptor de página o un descriptor de bloque, y los parámetros específicos que se pasan son los siguientes:

x0: apunta a la tabla de traducción del último nivel

x7: el atributo de memoria para crear el mapeo

x3: dirección física

x5: la dirección inicial de la dirección virtual (en realidad, la misma que x3)

x6: la dirección final de la dirección virtual

 

 3) Crear mapeo directo del kernel

 Verifique las siguientes 3 imágenes, mapeo de imágenes del kernel, swapper_pg_dir como tabla pgd, tabla de páginas PUD de relleno, descriptor de bloque PMD:

 

 

    /* 
     * Mapear la imagen del kernel (comenzando con PHYS_OFFSET). 
     */ 
    adrp x0, swapper_pg_dir -------------------------------(1) 
    mov_q x5, KIMAGE_VADDR + TEXT_OFFSET // tiempo de compilación __va(_texto) 
    agregar x5, x5, x23 // agregar desplazamiento KASLR ------------(2) 
    create_pgd_entry x0, x5, x3, x6 ------------ (3) 
    adrp x6, _end // tiempo de ejecución __pa(_end) ------------(4) 
    adrp x3, _text // tiempo de ejecución __pa(_text) ---------- --(5) 
    sub x6, x6, x3 // _fin - _texto ------------(6) 
    agregar x6, x6, x5 // tiempo de ejecución __va(_fin) ------ ------(7)
    crear_bloque_mapa x0, x7, x3, x5, x6 ------------(8)

(3)

x0 tbl (dirección física swapper_pg_dir)

dirección virtual de inicio del núcleo x5

(8)

Complete PMD, mapee todo el espacio de direcciones de la imagen del kernel

x0 swapper_pg_dir dirección física

Atributo de memoria x7  para crear mapeo

x3 _text dirección física

dirección virtual de inicio del núcleo x5

dirección virtual final del núcleo x6

2 create_pgd_entry

/*
 * Macro to populate the PGD (and possibily PUD) for the corresponding
 * block entry in the next level (tbl) for the given virtual address.
 *
 * Preserves:    tbl, next, virt
 * Corrupts:    tmp1, tmp2
 */
    .macro    create_pgd_entry, tbl, virt, tmp1, tmp2
    create_table_entry \tbl, \virt, PGDIR_SHIFT, PTRS_PER_PGD, \tmp1, \tmp2
#if SWAPPER_PGTABLE_LEVELS > 3
    create_table_entry \tbl, \virt, PUD_SHIFT, PTRS_PER_PUD, \tmp1, \tmp2
#endif
#if SWAPPER_PGTABLE_LEVELS > 2
    create_table_entry \tbl, \virt, SWAPPER_TABLE_SHIFT, PTRS_PER_PTE, \tmp1, \tmp2
#endif
    .endm

#if ARM64_SWAPPER_USES_SECTION_MAPS
#define SWAPPER_PGTABLE_LEVELS (CONFIG_PGTABLE_LEVELS - 1)
#define IDMAP_PGTABLE_LEVELS (ARM64_HW_PGTABLE_LEVELS(PHYS_MASK_SHIFT) - 1) #else #define SWAPPER_PGTABLE_LEVELS (CONFIG_PGTABLE_LEVELS) #de fino
IDMAP_PGTABLE_LEVELS
(
ARM64_HW_PGTABLE_LEVELS(PHYS_MASK_SHIFT))
#endif

Al usar SECCIÓN, SWAPPER_PGTABLE_LEVELS = (CONFIG_PGTABLE_LEVELS - 1)

La función anterior se llama principalmente create_table_entry. Dado que SWAPPER_PGTABLES_LEVELSla configuración es 3, es equivalente a crear pgd和puduna tabla de páginas de dos niveles. Cabe señalar aquí que create_table_entrydespués de ejecutar la función, tbllos parámetros se agregarán automáticamente PAGE_SIZE, lo que significa que pgd和pudlos dos la tabla de páginas de nivel es físicamente continua.

(1) create_table_entry se ha descrito en la siguiente sección, llamando aquí a esta función para crear un descriptor de tipo de tabla para la dirección virtual virt en PGD.

(2) La definición de macro de SWAPPER_PGTABLE_LEVELS está relacionada con ARM64_SWAPPER_USES_SECTION_MAPS, por lo que no hablaré de eso aquí. SWAPPER_PGTABLE_LEVELS en realidad define el nivel de la tabla de páginas en el espacio de direcciones del proceso de intercambio, que puede ser 3 o 2. ¿Cuántos niveles de la tabla de traducción en el medio están relacionados con la configuración? Si se trata de un mapeo de secciones, el nivel medio incluye PGD y PUD Está bien, PMD es el último nivel. Si se trata de mapeo de páginas, entonces se requieren tres niveles intermedios de PGD, PUD y PMD, y PTE es el último nivel. Por supuesto, si el nivel de toda la página es 3 o 2, es posible que no haya nivel PUD o PMD.

(3) Cuando SWAPPER_PGTABLE_LEVELS > 3, es necesario crear una tabla de traducción a nivel de PUD.

(4) Cuando SWAPPER_PGTABLE_LEVELS > 2, debe crear una tabla de traducción en el nivel de PMD.

Lo anterior es demasiado aburrido, damos algunos ejemplos:

Ejemplo 1: cuando la dirección virtual es de 48 bits, tamaño de página de 4k, el nivel de página es igual a 4 y la relación de asignación es PGD (L0) --->PUD (L1) --->PMD (L2) - -->Tabla de páginas (L3) ---> página, pero si se usa el mapeo de secciones (las páginas de 4k definitivamente usarán el mapeo de secciones), la relación de mapeo es PGD (L0) ---> PUD (L1) ---> PMD (L2) --->sección. Se crearán dos niveles intermedios, PGD y PUD, en la función create_pgd_entry.

Ejemplo 2: cuando la dirección virtual es de 48 bits, tamaño de página de 16k (no se puede usar el mapeo de secciones), el nivel de página es igual a 4 y la relación de mapeo es PGD (L0) --->PUD (L1) -- ->PMD (L2 )--->Tabla de páginas (L3)--->página. Se crearán tres niveles intermedios de PGD, PUD y PMD en la función create_pgd_entry.

Ejemplo 3: cuando la dirección virtual es de 39 bits, tamaño de página de 4k, el nivel de página es igual a 3 y la relación de asignación es PGD (L1) --->PMD (L2) --->Tabla de página (L3) ---> página. Dado que es una página de 4k, se usa el mapeo de secciones y la relación de mapeo es PGD (L1)--->PMD (L2)--->sección. Se creará un nivel intermedio de PGD en la función create_pgd_entry.

2. create_table_entry

 La definición de macro de create_table_entry se usa principalmente para crear un descriptor en una tabla de traducción de nivel intermedio. Si usa términos de Linux, es para crear un descriptor de PGD, PUD o PMD. Si usa la terminología ARM64, es para crear un descriptor de L0, L1 o L2. El nivel de descriptor de la tabla de traducción que se crea se especifica mediante el parámetro tbl

/*
 * Macro para crear una entrada de tabla a la página siguiente.
 *
 * tbl: dirección de tabla de página, dirección de tabla de página
 * virt: dirección virtual, dirección virtual asignada
 * shift: #imm cambio de tabla de página, bit asignado en palabra Offset
 * ptrs : punteros #imm por página de tabla, ancho de bit de asignación
 *
 * Conserva: virt
 * Corrompe: tmp1, tmp2
 * Devuelve: tbl -> dirección de página de tabla del siguiente nivel
 */
    .macro create_table_entry, tbl, virt, shift, ptrs, tmp1, tmp2
/* virt mueve la posición de cambio a la derecha, listo para obtener el índice de entrada de la tabla */
    lsr \tmp1, \virt, #\shift    
 
/* índice de la tabla, operación AND bit a bit, conserva los ptrs correspondientes en tmp1 1 bit de datos y se borran los demás bits, es decir, se obtiene el índice de la entrada de la tabla de páginas */
    y \tmp1, \tmp1, #\ptrs - 1    
 
/* tmp2 = tbl + PAGE_SIZE, idmap_pg_dir contiene tablas de páginas de varios niveles (cada tabla de páginas ocupa una página), por lo que el propósito aquí es obtener la dirección de entrada de la entrada de la tabla del siguiente nivel */ add \tmp2, \tbl, # PAGE_SIZE
    /    
 
* dirección de la tabla siguiente y tipo de entrada, operación OR bit a bit, establece el indicador de entrada de la tabla de la página siguiente PMD_TYPE_TABLE */    
    orr \tmp2, \tmp2, #PMD_TYPE_TABLE    
 
/* 
 * Escribe la dirección base de la entrada de la tabla de la página siguiente en entrada de la tabla de páginas,
 * dirección de entrada de la tabla de páginas actual = tbl + (tmp1 << 3) 
 * Dado que cada entrada de la tabla de páginas ocupa 8 bytes, el desplazamiento de la tabla de páginas correspondiente en relación con la dirección base de la tabla de páginas debe ser tmp1 << 3
 * El desplazamiento debe ser For tmp1 << 3
*/
    str \tmp2, [\tbl, \tmp1, lsl #3]    
 
/* página de la tabla del siguiente nivel, apuntando a la tabla de la página del siguiente nivel*/                                
    add \tbl, \tbl, # PAGE_SIZE    
    
    .endm

La función anterior crea una entrada en la tabla de páginas y devuelve la dirección de la tabla de páginas del siguiente nivel:

La función principal create_table_entry para crear una tabla de páginas es la siguiente: en la estructura de tablas de páginas de tres niveles, esta función completa principalmente la creación de tablas de páginas de dos niveles, PGD y PMD.

                Tome el proceso de creación de una tabla de páginas PGD como ejemplo,

                             Descripción del parámetro de entrada: tbl se refiere a la dirección física de la tabla de páginas que debe crearse

                                                virt es la dirección virtual asignada a

                                                shift es el desplazamiento del índice PGD asignado en la dirección virtual, es decir, PGDIR_SHIFT

                                                ptrs es el ancho de bits del índice PGD en la dirección virtual, es decir, PTRS_PER_PGD

                                                tmp1 y tmp2 son variables temporales

                               Proceso de creación: 1) Obtenga el índice de la tabla de páginas PGD de virt 

                                                   2) Obtenga la dirección base de la tabla de páginas de nivel inferior, es decir, la dirección base física de la tabla de páginas PMD

                                                   3) Establecer un indicador válido para la dirección física de la tabla de páginas PMD, indicando que la dirección es una tabla de tipo PMD válida

                                                   4) Escriba la dirección base de la tabla de páginas PMD en PGD[índice]

                                                   5) Señale tbl a la dirección base de la tabla de páginas del siguiente nivel para prepararse para la creación de la tabla de páginas del siguiente nivel

El parámetro tbl indica la dirección física de la tabla de páginas actual, virt indica la dirección virtual a mapear, shift indica el desplazamiento (PGD_SHIFT) de este nivel de tabla de páginas en la dirección virtual y ptrs indica el número de bits de este nivel de la tabla de páginas (por ejemplo: el ancho de bits del índice PGD en la dirección virtual, es decir, PTRS_PER_PGD). tmp1 y tmp2 son dos variables temporales
(1) La dirección virtual almacenada en tmp1 corresponde al índice de entrada en la tabla de traducción.
(2) Saque la dirección de la tabla de páginas del siguiente nivel. Las tablas de páginas (PGD/PUD/PMD/PTE) en la etapa inicial están todas juntas, cada una ocupando una página. Es decir, si la operación actual de create_table_entry es PGD, entonces tmp2 guarda la tabla de páginas del siguiente nivel en este momento, es decir, PUD (
3) Este paso es el valor del descriptor compuesto. No es suficiente tener la dirección de la tabla de traducción del siguiente nivel, sino también saber si el descriptor es válido (bit 0) y de qué tipo es el descriptor (bit 1). Para la tabla de páginas del nivel medio, el descriptor no puede ser una entrada de bloque, solo puede ser un descriptor de tipo de tabla, por lo que los dos bits más bajos del descriptor son 0b11

#define PMD_TYPE_TABLE        (_AT(pmdval_t, 3) << 0)

(4) Este es el paso más crítico, escribir el descriptor en la tabla de páginas. El motivo de la operación "lsl #3" es que un descriptor ocupa 8 bytes.
(5) Al final, tbl agregará un PAGE_SIZE, es decir, tbl se convierte en la dirección de la tabla de páginas del siguiente nivel

Mueva la dirección de la tabla de traducción al siguiente nivel para el siguiente paso de configuración

3. crear_mapa_de_bloques

El nombre de create_block_map es bueno. Esta función es para crear un descriptor de bloque en la tabla de traducción especificada por tbl para completar el mapeo de direcciones. El contenido de mapeo específico es mapear el VA de principio a fin hasta el PA a partir de phys

/*
 * Macro para completar las entradas de bloque en la tabla de páginas para el inicio... el final
 * rango virtual (inclusive)
 * Dirección base física de la entrada de la tabla de páginas tbl
 * flags Necesidad de mapear el flag de la tabla de páginas
 * phys Dirección física a mapear
 * start La dirección de inicio del espacio virtual al que se asigna la dirección física
 * end La dirección final del espacio virtual al que se asigna la dirección física
 * Conserva: tbl, flags
 * Daña: phys, start, end, pstate
 */
    . macro create_block_map, tbl, flags, phys , start, end
    /* Obtener el PFN correspondiente a la dirección física a mapear */
    lsr \phys, \phys, #SWAPPER_BLOCK_SHIFT //phys=phys>>21
    
    /* table start-index , obtenga el índice de inicio en la tabla tbl */ 
    lsr \start, \start, #SWAPPER_BLOCK_SHIFT //start=start>>21
    y \start, \start, #PTRS_PER_PTE - 1 //índice de la tabla, start = start & 0x1FF // hasta ahora start es igual a [29:21]bit de su valor inicial
    
    /* entrada de la tabla, para obtener la dirección de la página física donde se encuentra la dirección física, y establezca el indicador de la tabla de páginas para la dirección de la página física */
    orr \phys, \flags, \phys, lsl #SWAPPER_BLOCK_SHIFT entrada de tabla // phys = flags | (phys << 21)
    
    /* table end- index, obtener inicio en tabla tbl End index*/
    lsr \end, \end, #SWAPPER_BLOCK_SHIFT //end=end>>21
    and \end, \end, #PTRS_PER_PTE - 1 table end index // end = end & 0x1FF / / hasta el final es igual a su valor inicial [29:21]bit
    
    /* Escriba la dirección de la página física que debe asignarse a la dirección virtual correspondiente a tbl[index]*/
9999: str \phys, [\tbl, \start, lsl #3] // almacena la entrada, almacena el valor de phys en la memoria en la dirección tbl + start * 8, es decir, en la tabla de páginas de 8 bytes indexada por el bit [29:21] de start add
    \ inicio, \inicio, #1 // próxima entrada
    agregar \phys, \phys, #SWAPPER_BLOCK_SIZE // siguiente bloque,phys = phys + 0x200000
    cmp \start, \end
    b.ls 9999b
    .endm

Las tres funciones aisladas anteriores no son intuitivas, así que aquí viene la imagen:

   El siguiente es el idmap simplificado y el código de mapeo del kernel, que se puede conocer a partir de la lógica del código:   

       idmap_pg_dir es el espacio de entrada de la tabla de páginas donde la dirección física del código del núcleo desde __idmap_text_start hasta __idmap_text_end se asigna a la dirección virtual. Debido a que x5 = x3 = __pa(__idmap_text_start), la dirección virtual asignada por el espacio de la tabla de páginas es consistente con la dirección física DIRECCIÓN. El espacio de memoria __idmap_text_start a __idmap_text_end guarda el código de la sección ".idmap.text", que implementa principalmente el cambio de la MMU.

       swapper_pg_dir es la dirección física de _texto para _finalizar el código del núcleo para crear un espacio de tablas de páginas asignadas, la dirección de inicio virtual del espacio de tablas de páginas es KIMAGE_VADDR + TEXT_OFFSET, el tamaño del espacio virtual es el tamaño del espacio del núcleo (_text -_end ), el inicio físico La dirección es la dirección donde se carga el kernel en la memoria física _text.

       Después de asignar esta dirección y encender la MMU, la dirección virtual del código del kernel al que accede la CPU es coherente con la dirección del enlace del kernel de vmlinux.lds y la dirección de la tabla de símbolos system.map (la dirección de vmlinux.lds es la dirección virtual).

        Comentarios: 1) ¿Cómo usar las tablas de páginas guardadas por idmap_pg_dir y swapper_pg_dir?

                      En la función enable_mmu, las direcciones de idmap_pg_dir y swapper_pg_dir se almacenan en ttbr0_el1 y ttbr1_el1 respectivamente, y Arm64 decidirá qué ttbr (registro base de la tabla de traducción) usar de acuerdo con otros registros de estado.

                2) ¿Se puede acceder al espacio desde __idmap_text_start hasta __idmap_text_end a través de las tablas de dos páginas de idmap_pg_dir y swapper_pg_dir?

                        Opinión personal: Sí, el espacio físico correspondiente a la tabla de la página idmap_pg_dir es __idmap_text_start a __idmap_text_end. El espacio físico correspondiente a la entrada de la tabla de la página swapper_pg_dir es el segmento del núcleo completo, y los segmentos __idmap_text_start a __idmap_text_end están incluidos en el segmento del núcleo.

Supongo que te gusta

Origin blog.csdn.net/y13182588139/article/details/125924283
Recomendado
Clasificación