Seg煤n lo prometido , seguimos hablando sobre el desarrollo
de procesadores Elbrus . Este art铆culo es t茅cnico. La informaci贸n proporcionada en el art铆culo no es documentaci贸n oficial, ya que se obtuvo durante el estudio de Elbrus como una caja negra. Pero sin duda ser谩 interesante para una mejor comprensi贸n de la arquitectura de Elbrus, porque aunque ten铆amos documentaci贸n oficial, muchos detalles quedaron claros solo despu茅s de largos experimentos, cuando
Embox funcion贸.
Recuerde que en el
art铆culo anterior hablamos sobre el arranque b谩sico del sistema y el controlador del puerto serie. Embox comenz贸, pero para avanzar a煤n m谩s necesit谩bamos interrupciones, un temporizador del sistema y, por supuesto, me gustar铆a incluir un conjunto de pruebas unitarias, y para esto necesitamos setjmp. Este art铆culo se enfocar谩 en registros, pilas y otros detalles t茅cnicos necesarios para implementar todas estas cosas.
Comencemos con una breve introducci贸n a la arquitectura, que es la informaci贸n m铆nima necesaria para comprender lo que se discutir谩 m谩s adelante. En el futuro, nos referiremos a la informaci贸n de esta secci贸n.
Breve introducci贸n: pilas
Hay tres pilas en Elbrus:
- Pila de procedimientos (PS)
- Pila de cadena de procedimiento (PCS)
- Pila de usuario (EE. UU.)
Analicemos con m谩s detalle. Las direcciones en la figura son condicionales, muestran en qu茅 direcci贸n se dirigen los movimientos, desde una direcci贸n m谩s grande a una m谩s peque帽a o viceversa.

La pila de procedimientos (PS) est谩 destinada a datos asignados a registros "operativos".
Por ejemplo, pueden ser argumentos de funci贸n, en arquitecturas "ordinarias", este concepto est谩 m谩s cerca de los registros de prop贸sito general. A diferencia de las arquitecturas de procesador "normales", en E2K, los registros utilizados en las funciones se apilan en una pila separada.
La Pila de informaci贸n de enlace (PCS) est谩 dise帽ada para colocar informaci贸n sobre el procedimiento (de llamada) anterior y se utiliza al regresar. Los datos en la direcci贸n del remitente, as铆 como en el caso de los registros, se colocan en un lugar separado. Por lo tanto, la promoci贸n de la pila (por ejemplo, salir por excepci贸n en C ++) es un proceso que consume m谩s tiempo que en las arquitecturas "ordinarias". Por otro lado, esto elimina los problemas de desbordamiento de pila.
Ambas pilas (PS y PCS) se caracterizan por una direcci贸n base, tama帽o y desplazamiento actual. Estos par谩metros se establecen en los registros PSP y PCSP, son de 128 bits y en el ensamblador debe hacer referencia a campos espec铆ficos (por ejemplo, alto o bajo). Adem谩s, el funcionamiento de las pilas est谩 estrechamente relacionado con el concepto de un archivo de registro, m谩s sobre eso a continuaci贸n. La interacci贸n con el archivo ocurre a trav茅s del mecanismo de bombeo / intercambio de registros. El llamado "puntero de hardware a la parte superior de la pila" del procedimiento o la pila de informaci贸n vinculante desempe帽a un papel activo en este mecanismo, respectivamente. Sobre esto tambi茅n a continuaci贸n. Es importante que en cada momento los datos de estas pilas est茅n en RAM o en un archivo de registro.
Tambi茅n vale la pena se帽alar que estas pilas (la pila de procedimientos y la pila de informaci贸n de enlace) crecen. Nos encontramos con esto cuando implementamos context_switch.
La pila de usuarios tambi茅n recibe la direcci贸n base y el tama帽o. El puntero actual est谩 en el registro USD.lo. En esencia, es una pila cl谩sica que crece hacia abajo. Solo que, a diferencia de las arquitecturas "ordinarias", la informaci贸n de otras pilas (registros y direcciones de retorno) no cabe all铆.
Un requisito no est谩ndar, en mi opini贸n, para los l铆mites y tama帽os de las pilas es la alineaci贸n de 4Kb, y tanto la direcci贸n base de la pila como su tama帽o deben estar alineados a 4Kb. En otras arquitecturas, no he cumplido esa restricci贸n. Nos encontramos con este detalle, nuevamente, cuando implementamos context_switch.
Breve introducci贸n: registros. Registrar archivos Registrar ventanas
Ahora que hemos descubierto un poco las pilas, necesitamos entender c贸mo se presenta la informaci贸n en ellas. Para hacer esto, necesitamos introducir algunos conceptos m谩s.
Un archivo de registro (RF) es un conjunto de todos los registros. Hay dos archivos de registro que necesitamos: un archivo de informaci贸n de conexi贸n (archivo de cadena - CF), el otro se llama archivo de registro (RF), almacena registros "operativos", que se almacenan en la pila de procedimientos.
La ventana de registro es el 谩rea (conjunto de registros) del archivo de registro que est谩 disponible actualmente.
Explicar茅 con m谩s detalle. Creo que es un conjunto de registros que nadie necesita explicar.
Es bien sabido que uno de los cuellos de botella en la arquitectura x86 es precisamente un peque帽o n煤mero de registros. En arquitecturas RISC con registros es m谩s simple, generalmente alrededor de 16 registros, de los cuales varios (2-3) est谩n ocupados para necesidades oficiales. 驴Por qu茅 no simplemente hacer 128 registros, porque parece que esto aumentar谩 el rendimiento del sistema? La respuesta es bastante simple: una instrucci贸n de procesador necesita un lugar para almacenar la direcci贸n de registro, y si hay muchos de ellos, tambi茅n se necesitan muchos bits para esto. Por lo tanto, recurren a todo tipo de trucos, hacen registros de sombra, registran bancos, ventanas, etc. Por registros sombra, me refiero al principio de organizaci贸n de registros en ARM. Si ocurre una interrupci贸n u otra situaci贸n, entonces est谩 disponible un conjunto diferente de registros con los mismos nombres (n煤meros), mientras la informaci贸n almacenada en el conjunto original permanece all铆. Los bancos de registros, de hecho, son muy similares a los registros sombra, simplemente no hay cambio de hardware de los conjuntos de registros, y el programador elige a qu茅 banco (conjunto de registros) contactar ahora.
Las ventanas de registro est谩n dise帽adas para optimizar el trabajo con la pila. Como probablemente comprenda, en una arquitectura "normal" usted ingresa un procedimiento, guarda registros en la pila (o el procedimiento de llamada guarda, depende del acuerdo) y puede usar registros, porque la informaci贸n ya est谩 almacenada en la pila. Pero el acceso a la memoria es lento y, por lo tanto, debe evitarse. Al ingresar al procedimiento, hagamos que un nuevo conjunto de registros est茅 disponible, los datos del anterior se guardar谩n, lo que significa que no necesita volcarlos en la memoria. Adem谩s, cuando regrese al procedimiento de llamada, la ventana de registro anterior tambi茅n devolver谩, por lo tanto, todos los datos en los registros ser谩n relevantes. Este es el concepto de una ventana de registro.

Est谩 claro que a煤n necesita guardar los registros en la pila (en la memoria), pero esto puede hacerse cuando las ventanas de registro libre hayan finalizado.
驴Y qu茅 hacer con los registros de entrada y salida (argumentos al ingresar la funci贸n y el resultado devuelto)? Deje que la ventana contenga parte de los registros visibles desde la ventana anterior, m谩s precisamente, parte de los registros estar谩 disponible para ambas ventanas. Luego, en general, cuando se llama a la funci贸n, no tiene que acceder a la memoria. Supongamos que nuestros registros se ven as铆

Es decir, r0 en la primera ventana ser谩 el mismo registro que r2 en cero, y r1 desde la primera ventana en el mismo registro que r3. Es decir, escribiendo en r2 antes de llamar al procedimiento (cambiando el n煤mero de ventana) obtenemos el valor en r0 en el procedimiento llamado. Este principio se llama mecanismo de ventanas giratorias.
Optimicemos un poco m谩s, porque los creadores de Elbrus hicieron exactamente eso. Deje que las ventanas que tenemos no sean de un tama帽o fijo, sino variable, el tama帽o de la ventana se puede establecer en el momento de la entrada en el procedimiento. Haremos lo mismo con el n煤mero de registros rotados. Por supuesto, esto nos llevar谩 a algunos problemas, porque si en las ventanas giratorias cl谩sicas, hay un 铆ndice de ventana a trav茅s del cual se determina que necesita guardar los datos del archivo de registro en la pila o cargarlos. Pero si ingresa no el 铆ndice de la ventana, sino el 铆ndice de registro desde el cual comienza nuestra ventana actual, entonces este problema no surgir谩. En Elbrus, estos 铆ndices est谩n contenidos en los registros PSHTP (para la pila de procedimientos PS) y PCSHTP (para la pila de informaci贸n de procedimientos PCS). La documentaci贸n se refiere a "punteros de hardware en la parte superior de la pila". Ahora puedes intentar leer de nuevo sobre las pilas, creo que ser谩 m谩s claro.
Como comprender谩, dicho mecanismo implica que tiene la capacidad de controlar lo que est谩 en la memoria. Es decir, sincronice el archivo de registro y la pila. Me refiero a un programador de sistemas. Si usted es un programador de aplicaciones, el equipo proporcionar谩 una entrada y una salida transparentes del procedimiento. Es decir, si no hay suficientes registros al intentar seleccionar una nueva ventana, la ventana de registro se "bombear谩" autom谩ticamente. En este caso, todos los datos del archivo de registro se guardar谩n en la pila apropiada (en la memoria), y el "puntero a la parte superior del hardware de la pila" (铆ndice de compensaci贸n) se restablecer谩 a cero. Del mismo modo, el intercambio de un archivo de registro de la pila se produce autom谩ticamente. Pero si est谩 desarrollando, por ejemplo, el cambio de contexto, que es exactamente lo que hicimos, entonces necesita un mecanismo para trabajar con la parte oculta del archivo de registro. En Elbrus, los comandos FLUSHR y FLUSHC se utilizan para esto. FLUSHR: al borrar el archivo de registro, todas las ventanas, excepto la actual, se vac铆an a la pila de procedimientos, por lo que el 铆ndice PSHTP se restablece a cero. FLUSHC: limpia el archivo de informaci贸n de enlace, todo, excepto la ventana actual, se descarga en la pila de informaci贸n de enlace, el 铆ndice PCSHTP tambi茅n se restablece a cero.
Breve introducci贸n: implementaci贸n en Elbrus
Ahora que hemos discutido el trabajo no obvio con registros y pilas, hablaremos m谩s espec铆ficamente sobre varias situaciones en Elbrus.
Cuando ingresamos a la siguiente funci贸n, el procesador crea dos ventanas: una ventana en la pila PS y una ventana en la pila PCS.
Una ventana en la pila PCS contiene la informaci贸n necesaria para regresar de una funci贸n: por ejemplo, IP (puntero de instrucci贸n) de la instrucci贸n en la que deber谩 regresar de la funci贸n. Con esto, todo est谩 m谩s o menos claro.
La ventana en la pila PS es un poco m谩s complicada. Se introduce el concepto de registros de la ventana actual. En esta ventana, tendr谩 acceso a los registros de la ventana actual:% dr0,% dr1, ...,% dr15, ... Es decir, para nosotros, como usuario, siempre est谩n numerados desde 0, pero esta es una numeraci贸n relativa a la direcci贸n base de la ventana actual. A trav茅s de estos registros, los argumentos se pasan cuando se llama a la funci贸n, se devuelve el valor y la funci贸n se utiliza como registros de prop贸sito general dentro de la funci贸n. En realidad, esto se explic贸 al considerar el mecanismo de rotaci贸n de las ventanas de registro.
El tama帽o de la ventana de registro en Elbrus se puede controlar. Esto, como dije, es necesario para la optimizaci贸n. Por ejemplo, en una funci贸n solo necesitamos 4 registros para pasar argumentos y algunos c谩lculos, en este caso el programador (o compilador) decide cu谩ntos registros asignar para la funci贸n, y en funci贸n de esto establece el tama帽o de la ventana. El tama帽o de la ventana lo establece la operaci贸n setwd:
setwd wsz=0x10
Especifica el tama帽o de la ventana en t茅rminos de registros cu谩druples (registros de 128 bits).

Ahora, supongamos que desea llamar a una funci贸n desde una funci贸n. Para esto, se aplica el concepto ya descrito de una ventana de registro girada. La imagen de arriba muestra un fragmento de un archivo de registro donde una funci贸n con la ventana 1 (verde) llama a una funci贸n con la ventana 2 (naranja). En cada una de estas dos funciones tendr谩 acceso a su% dr0,% dr1, ... Pero los argumentos se pasar谩n por los llamados registros rotativos. En otras palabras, parte de los registros de la ventana 1 se convertir谩n en los registros de la ventana 2 (tenga en cuenta que estas dos ventanas se cruzan). Estos registros tambi茅n los establece la ventana (consulte Registros rotativos en la imagen) y tienen la direcci贸n% db [0],% db [1], ... Por lo tanto, el registro% dr0 en la ventana 2 no es m谩s que el registro% db [0] en ventana 1.
La ventana de registro de rotaci贸n se establece mediante la operaci贸n setbn:
setbn rbs = 0x3, rsz = 0x8
rbs establece el tama帽o de la ventana girada y rsz establece la direcci贸n base, pero en relaci贸n con la ventana de registro actual. Es decir Aqu铆 hemos asignado 3 registros, comenzando desde el 8.
En base a lo anterior, mostramos c贸mo se ve la llamada a la funci贸n. Por simplicidad, suponemos que la funci贸n toma un argumento:
void my_func(uint64_t a) { }
Luego, para llamar a esta funci贸n, debe preparar una ventana de registros rotativos (ya lo hemos hecho a trav茅s de setbn). Luego, en el registro% db0 colocamos el valor que se pasar谩 a my_func. Despu茅s de esto, debe llamar a la instrucci贸n CALL y no olvide decirle d贸nde comienza la ventana de registros rotados. Omitimos la preparaci贸n para la llamada (el comando disp) ahora, porque no distingue entre may煤sculas y min煤sculas. Como resultado, en ensamblador, una llamada a esta funci贸n deber铆a verse as铆:
addd 0, %dr9, %db[0] disp %ctpr1, my_func call %ctpr1, wbs = 0x8
Entonces, con registros un poco resueltos. Ahora veamos la pila de informaci贸n vinculante. Almacena los llamados registros CR. De hecho, dos: CR0, CR1. Y ya contienen la informaci贸n necesaria para regresar de la funci贸n.

Los registros CR0 y CR1 de la ventana de la funci贸n que llam贸 a la funci贸n con los registros marcados en naranja son verdes. Los registros CR0 contienen el puntero de instrucci贸n de la funci贸n de llamada y un determinado archivo de predicado (PF-Predicate File), una historia al respecto definitivamente est谩 fuera del alcance de este art铆culo.
Los registros CR1 contienen datos como PSR (estado del procesador de textos), n煤mero de ventana, tama帽os de ventana, etc. En Elbrus, todo es tan flexible que cada procedimiento almacena informaci贸n en CR1, incluso si la operaci贸n de punto flotante est谩 incluida en el procedimiento, y un registro que contiene informaci贸n sobre excepciones de software, pero para esto, por supuesto, debe pagar para guardar informaci贸n adicional.
Es muy importante no olvidar que el archivo de registro y el archivo de informaci贸n de enlace se pueden extraer y cambiar de la memoria principal y viceversa (de las pilas PS y PCS descritas anteriormente). Este punto es importante al implementar setjmp que se describe m谩s adelante.
SETJMP / LONGJMP
Y finalmente, al menos de alguna manera entendiendo c贸mo se organizan las pilas y los registros en Elbrus, puede comenzar a hacer algo 煤til, es decir, agregar una nueva funcionalidad a Embox.
En Embox, el sistema de prueba de la unidad requiere setjmp / longjmp, por lo que tuvimos que implementar estas funciones.
Para la implementaci贸n, se requiere guardar / restaurar los registros: CR0, CR1, PSP, PCSP, USD, que ya conocemos en una breve introducci贸n. De hecho, guardar / restaurar se implementa en nuestra frente, pero hay un matiz significativo que a menudo se insinu贸 en la descripci贸n de pilas y registros, a saber: las pilas deben estar sincronizadas, porque est谩n ubicadas no solo en la memoria, sino tambi茅n en el archivo de registro. Este matiz significa que necesita cuidar varias caracter铆sticas, sin las cuales nada funcionar谩.
La primera caracter铆stica es deshabilitar las interrupciones durante el guardado y la restauraci贸n. Al restaurar una interrupci贸n, es obligatorio prohibirla; de lo contrario, puede surgir una situaci贸n en la que ingresemos el controlador de interrupciones con pilas a medio cambiar (refiri茅ndose a bombear el intercambio de archivos de registro descrito en la "breve descripci贸n"). Y al guardar, el problema es que despu茅s de ingresar y salir de la interrupci贸n, el procesador puede intercambiar nuevamente parte del archivo de registro de la RAM (y esto arruinar谩 las condiciones invariantes PSHTP = 0 y PSCHTP = 0, un poco m谩s sobre ellas). Es por eso que, tanto en setjmp como en longjmp, las interrupciones deben deshabilitarse. Tambi茅n debe se帽alarse aqu铆 que los especialistas del MCST nos aconsejaron usar corchetes at贸micos en lugar de deshabilitar las interrupciones, pero por ahora usamos la implementaci贸n m谩s simple (comprensible para nosotros).
La segunda caracter铆stica est谩 relacionada con el bombeo / bombeo de un archivo de registro de la memoria. Es como sigue. El archivo de registro tiene un tama帽o limitado y, por lo tanto, a menudo se bombea a la memoria y viceversa. Por lo tanto, si simplemente guardamos los valores de los registros PSP y PSHTP, entonces fijaremos el valor del puntero actual en la memoria y en el archivo de registro. Pero dado que el archivo de registro est谩 cambiando, en el momento de la restauraci贸n del contexto indicar谩 datos ya incorrectos (no los que hemos "guardado"). Para evitar esto, debe vaciar todo el archivo de registro en la memoria. Por lo tanto, al guardar en setjmp, tenemos registros PSP.ind en la memoria y registros PSHTP.ind en la ventana de registro. Resulta que necesita guardar todos los registros PCSP.ind + PCSHTP.ind. La siguiente es la funci贸n que realiza esta operaci贸n:
.type update_pcsp_ind,@function $update_pcsp_ind: setwd wsz = 0x4, nfx = 0x0 shld %dr1, (64 - 10), %dr1 shrd %dr1, (64 - 10), %dr1 addd %dr1, %dr0, %dr0 E2K_ASM_RETURN
Tambi茅n es necesario aclarar un peque帽o punto en este c贸digo descrito en el comentario, es decir, es necesario expandir mediante programaci贸n el car谩cter en el 铆ndice PCSHTP.ind, porque el 铆ndice puede ser negativo y almacenarse en un c贸digo adicional. Para hacer esto, primero cambiamos a (64-10) a la izquierda (registro de 64 bits), a un campo de 10 bits y luego regresamos.
Lo mismo ocurre con la PSP (pila de procedimientos)
.type update_psp_ind,@function $update_psp_ind: setwd wsz = 0x4, nfx = 0x0 shld %dr1, (64 - 12), %dr1 shrd %dr1, (64 - 12), %dr1 muld %dr1, 2, %dr1 addd %dr1, %dr0, %dr0 E2K_ASM_RETURN
Con una ligera diferencia (el campo es de 12 bits y los registros se cuentan all铆 en t茅rminos de 128 bits, es decir, el valor debe multiplicarse por 2).
C贸digo setjmp en s铆
C_ENTRY(setjmp): setwd wsz = 0x14, nfx = 0x0 setbn rsz = 0x3, rbs = 0x10, rcur = 0x0 disp %ctpr1, ipl_save ipd 3 call %ctpr1, wbs = 0x10 addd 0, %db[0], %dr9 rrd %cr0.hi, %dr1 rrd %cr1.lo, %dr2 rrd %cr1.hi, %dr3 rrd %usd.lo, %dr4 rrd %usd.hi, %dr5 rrd %psp.hi, %dr6 rrd %pshtp, %dr7 addd 0, %dr6, %db[0] addd 0, %dr7, %db[1] disp %ctpr1, update_psp_ind ipd 3 call %ctpr1, wbs = 0x10 addd 0, %db[0], %dr6 rrd %pcsp.hi, %dr7 rrd %pcshtp, %dr8 addd 0, %dr7, %db[0] addd 0, %dr8, %db[1] disp %ctpr1, update_pcsp_ind ipd 3 call %ctpr1, wbs = 0x10 addd 0, %db[0], %dr7 std %dr1, [%dr0 + E2K_JMBBUFF_CR0_HI] std %dr2, [%dr0 + E2K_JMBBUFF_CR1_LO] std %dr3, [%dr0 + E2K_JMBBUFF_CR1_HI] std %dr4, [%dr0 + E2K_JMBBUFF_USD_LO] std %dr5, [%dr0 + E2K_JMBBUFF_USD_HI] std %dr6, [%dr0 + E2K_JMBBUFF_PSP_HI] std %dr7, [%dr0 + E2K_JMBBUFF_PCSP_HI] addd 0, %dr9, %db[0] disp %ctpr1, ipl_restore ipd 3 call %ctpr1, wbs = 0x10 adds 0, 0, %r0 E2K_ASM_RETURN
Al implementar longjmp, es importante no olvidarse de la sincronizaci贸n de ambos archivos de registro, por lo tanto, debe vaciar no solo la ventana de registro (flushr), sino tambi茅n el archivo de carpeta (flushc). Describamos la macro:
#define E2K_ASM_FLUSH_CPU \ flushr; \ nop 2; \ flushc; \ nop 3;
Ahora que toda la informaci贸n est谩 en la memoria, podemos registrar de forma segura la recuperaci贸n en longjmp.
C_ENTRY(longjmp): setwd wsz = 0x14, nfx = 0x0 setbn rsz = 0x3, rbs = 0x10, rcur = 0x0 disp %ctpr1, ipl_save ipd 3 call %ctpr1, wbs = 0x10 addd 0, %db[0], %dr9 E2K_ASM_FLUSH_CPU ldd [%dr0 + E2K_JMBBUFF_CR0_HI], %dr2 ldd [%dr0 + E2K_JMBBUFF_CR1_LO], %dr3 ldd [%dr0 + E2K_JMBBUFF_CR1_HI], %dr4 ldd [%dr0 + E2K_JMBBUFF_USD_LO], %dr5 ldd [%dr0 + E2K_JMBBUFF_USD_HI], %dr6 ldd [%dr0 + E2K_JMBBUFF_PSP_HI], %dr7 ldd [%dr0 + E2K_JMBBUFF_PCSP_HI], %dr8 rwd %dr2, %cr0.hi rwd %dr3, %cr1.lo rwd %dr4, %cr1.hi rwd %dr5, %usd.lo rwd %dr6, %usd.hi rwd %dr7, %psp.hi rwd %dr8, %pcsp.hi addd 0, %dr9, %db[0] disp %ctpr1, ipl_restore ipd 3 call %ctpr1, wbs = 0x10 adds 0, %r1, %r0 E2K_ASM_RETURN
Cambio de contexto
Despu茅s de descubrir setjmp / longjmp, la implementaci贸n b谩sica de context_switch nos pareci贸 bastante clara. De hecho, como en el primer caso, necesitamos guardar / restaurar los registros de informaci贸n y pilas de conexi贸n, adem谩s de restaurar correctamente el registro de estado del procesador (UPSR).
Te lo explicar茅. Como en el caso de setjmp, al guardar registros, primero debe restablecer el archivo de registro y el archivo de informaci贸n de enlace en la memoria (flushr + flushc). Despu茅s de eso, necesitamos guardar los valores actuales de los registros CR0 y CR1 para que cuando regresemos, saltemos exactamente a donde se cambi贸 la corriente actual. A continuaci贸n, guardamos los descriptores de pila para PS, PCS y EE. UU. Y finalmente, debe encargarse de la restauraci贸n correcta del modo de interrupci贸n; para estos fines, tambi茅n guardamos el registro UPSR.
C贸digo de ensamblador context_switch:
C_ENTRY(context_switch): setwd wsz = 0x10, nfx = 0x0 rrd %upsr, %dr2 std %dr2, [%dr0 + E2K_CTX_UPSR] rrd %upsr, %dr2 andnd %dr2, (UPSR_IE | UPSR_NMIE), %dr2 rwd %dr2, %upsr E2K_ASM_FLUSH_CPU rrd %cr0.lo, %dr2 rrd %cr0.hi, %dr3 rrd %cr1.lo, %dr4 rrd %cr1.hi, %dr5 std %dr2, [%dr0 + E2K_CTX_CR0_LO] std %dr3, [%dr0 + E2K_CTX_CR0_HI] std %dr4, [%dr0 + E2K_CTX_CR1_LO] std %dr5, [%dr0 + E2K_CTX_CR1_HI] rrd %usd.lo, %dr3 rrd %usd.hi, %dr4 rrd %psp.lo, %dr5 rrd %psp.hi, %dr6 rrd %pcsp.lo, %dr7 rrd %pcsp.hi, %dr8 std %dr3, [%dr0 + E2K_CTX_USD_LO] std %dr4, [%dr0 + E2K_CTX_USD_HI] std %dr5, [%dr0 + E2K_CTX_PSP_LO] std %dr6, [%dr0 + E2K_CTX_PSP_HI] std %dr7, [%dr0 + E2K_CTX_PCSP_LO] std %dr8, [%dr0 + E2K_CTX_PCSP_HI] ldd [%dr1 + E2K_CTX_CR0_LO], %dr2 ldd [%dr1 + E2K_CTX_CR0_HI], %dr3 ldd [%dr1 + E2K_CTX_CR1_LO], %dr4 ldd [%dr1 + E2K_CTX_CR1_HI], %dr5 rwd %dr2, %cr0.lo rwd %dr3, %cr0.hi rwd %dr4, %cr1.lo rwd %dr5, %cr1.hi ldd [%dr1 + E2K_CTX_USD_LO], %dr3 ldd [%dr1 + E2K_CTX_USD_HI], %dr4 ldd [%dr1 + E2K_CTX_PSP_LO], %dr5 ldd [%dr1 + E2K_CTX_PSP_HI], %dr6 ldd [%dr1 + E2K_CTX_PCSP_LO], %dr7 ldd [%dr1 + E2K_CTX_PCSP_HI], %dr8 rwd %dr3, %usd.lo rwd %dr4, %usd.hi rwd %dr5, %psp.lo rwd %dr6, %psp.hi rwd %dr7, %pcsp.lo rwd %dr8, %pcsp.hi ldd [%dr1 + E2K_CTX_UPSR], %dr2 rwd %dr2, %upsr E2K_ASM_RETURN
Otro punto importante es la inicializaci贸n del hilo del sistema operativo. En Embox, cada hilo tiene un cierto procedimiento primario
void _NORETURN thread_trampoline(void);
en el que se ejecutar谩 todo el trabajo adicional de la secuencia. Por lo tanto, necesitamos preparar de alguna manera las pilas para llamar a esta funci贸n, es aqu铆 donde nos enfrentamos con el hecho de que hay tres pilas, y que no crecen en la misma direcci贸n. Por arquitectura, creamos una secuencia con una sola pila, o m谩s bien, tiene un solo lugar debajo de la pila, en la parte superior tenemos una estructura que describe la secuencia en s铆, y as铆 sucesivamente, aqu铆 tuvimos que ocuparnos de diferentes pilas, sin olvidar que deber铆an alinearse en 4 kB, no olvide todo tipo de derechos de acceso, etc.
Como resultado, en este momento decidimos que dividiremos el espacio debajo de la pila en tres partes, una cuarta parte debajo de la pila de informaci贸n vinculante, una cuarta parte debajo de la pila de procedimientos y la mitad debajo de la pila de usuarios.
Traigo el c贸digo para que pueda evaluar qu茅 tan grande es, debe tener en cuenta que esta es una inicializaci贸n m铆nima. #define E2K_STACK_ALIGN (1UL << 12) #define round_down(x, bound) ((x) & ~((bound) - 1)) /* Reserve 1/4 for PSP stack, 1/4 for PCSP stack, and 1/2 for USD stack */ #define PSP_CALC_STACK_BASE(sp, size) binalign_bound(sp - size, E2K_STACK_ALIGN) #define PSP_CALC_STACK_SIZE(sp, size) binalign_bound((size) / 4, E2K_STACK_ALIGN) #define PCSP_CALC_STACK_BASE(sp, size) \ (PSP_CALC_STACK_BASE(sp, size) + PSP_CALC_STACK_SIZE(sp, size)) #define PCSP_CALC_STACK_SIZE(sp, size) binalign_bound((size) / 4, E2K_STACK_ALIGN) #define USD_CALC_STACK_BASE(sp, size) round_down(sp, E2K_STACK_ALIGN) #define USD_CALC_STACK_SIZE(sp, size) \ round_down(USD_CALC_STACK_BASE(sp, size) - PCSP_CALC_STACK_BASE(sp, size),\ E2K_STACK_ALIGN) static void e2k_calculate_stacks(struct context *ctx, uint64_t sp, uint64_t size) { uint64_t psp_size, pcsp_size, usd_size; log_debug("Stacks:\n"); ctx->psp_lo |= PSP_CALC_STACK_BASE(sp, size) << PSP_BASE; ctx->psp_lo |= E2_RWAR_RW_ENABLE << PSP_RW; psp_size = PSP_CALC_STACK_SIZE(sp, size); assert(psp_size); ctx->psp_hi |= psp_size << PSP_SIZE; log_debug(" PSP.base=0x%lx, PSP.size=0x%lx\n", PSP_CALC_STACK_BASE(sp, size), psp_size); ctx->pcsp_lo |= PCSP_CALC_STACK_BASE(sp, size) << PCSP_BASE; ctx->pcsp_lo |= E2_RWAR_RW_ENABLE << PCSP_RW; pcsp_size = PCSP_CALC_STACK_SIZE(sp, size); assert(pcsp_size); ctx->pcsp_hi |= pcsp_size << PCSP_SIZE; log_debug(" PCSP.base=0x%lx, PCSP.size=0x%lx\n", PCSP_CALC_STACK_BASE(sp, size), pcsp_size); ctx->usd_lo |= USD_CALC_STACK_BASE(sp, size) << USD_BASE; usd_size = USD_CALC_STACK_SIZE(sp, size); assert(usd_size); ctx->usd_hi |= usd_size << USD_SIZE; log_debug(" USD.base=0x%lx, USD.size=0x%lx\n", USD_CALC_STACK_BASE(sp, size), usd_size); } static void e2k_calculate_crs(struct context *ctx, uint64_t routine_addr) { uint64_t usd_size = (ctx->usd_hi >> USD_SIZE) & USD_SIZE_MASK; /* Reserve space in hardware stacks for @routine_addr */ /* Remark: We do not update psp.hi to reserve space for arguments, * since routine do not accepts any arguments. */ ctx->pcsp_hi |= SZ_OF_CR0_CR1 << PCSP_IND; ctx->cr0_hi |= (routine_addr >> CR0_IP) << CR0_IP; ctx->cr1_lo |= PSR_ALL_IRQ_ENABLED << CR1_PSR; /* Divide on 16 because it field contains size in terms * of 128 bit values. */ ctx->cr1_hi |= (usd_size >> 4) << CR1_USSZ; } void context_init(struct context *ctx, unsigned int flags, void (*routine_fn)(void), void *sp, unsigned int stack_size) { memset(ctx, 0, sizeof(*ctx)); e2k_calculate_stacks(ctx, sp, stack_size); e2k_calculate_crs(ctx, (uint64_t) routine_fn); if (!(flags & CONTEXT_IRQDISABLE)) { ctx->upsr |= (UPSR_IE | UPSR_NMIE); } }
El art铆culo tambi茅n conten铆a trabajo con interrupciones, excepciones y temporizadores, pero como result贸 ser tan grande, decidimos hablar sobre ello en la
siguiente parte .
Por si acaso, repito, este material no es documentaci贸n oficial. Para soporte oficial, documentaci贸n y el resto, necesita contactar al ICST directamente. El c贸digo en
Embox , por supuesto, est谩 abierto, pero para compilarlo, necesitar谩 un compilador cruzado, que, nuevamente, se puede obtener del
MCST .