operating system - El tamaño de la pila utilizada en el desarrollo del kernel
operating-system stack (5)
¿Por qué no hacer que el tamaño de la pila sea un elemento configurable, ya sea almacenado con el programa o especificado cuando un proceso crea otro proceso?
Hay varias maneras en que puede hacer que esto sea configurable.
Hay una directriz que dice "0, 1 o n", lo que significa que debe permitir cero, uno o cualquier número (limitado por otras restricciones, como la memoria) de un objeto; esto también se aplica a los tamaños de los objetos.
Estoy desarrollando un sistema operativo y en lugar de programar el núcleo, estoy diseñando el kernel. Este sistema operativo está dirigido a la arquitectura x86 y mi objetivo es para las computadoras modernas. La cantidad estimada de RAM requerida es de 256 Mb o más.
¿Cuál es un buen tamaño para hacer la pila para cada hilo ejecutado en el sistema? ¿Debo tratar de diseñar el sistema de tal manera que la pila se pueda extender automáticamente si se alcanza la longitud máxima?
Creo que si recuerdo correctamente, una página en RAM tiene 4k o 4096 bytes y eso no me parece demasiado. Definitivamente puedo ver veces, especialmente cuando uso mucha recursividad, que me gustaría tener más de 1000 integars en RAM a la vez. Ahora, la verdadera solución sería hacer que el programa lo haga utilizando malloc
y administrar sus propios recursos de memoria, pero realmente me gustaría saber la opinión del usuario al respecto.
¿Es 4k lo suficientemente grande para una pila con los modernos programas de computadora? ¿Debería la pila ser más grande que eso? ¿La pila debe expandirse automáticamente para adaptarse a cualquier tipo de tamaño? Estoy interesado en esto tanto desde el punto de vista de un desarrollador práctico como desde un punto de vista de seguridad.
¿Es 4k demasiado grande para una pila? Teniendo en cuenta la ejecución normal del programa, especialmente desde el punto de vista de las clases en C ++, observo que un buen código fuente tiende a malloc/new
los datos que necesita cuando se crean las clases, para minimizar los datos que se lanzan en una llamada de función.
Lo que aún no he entendido es el tamaño de la memoria caché del procesador. Idealmente, creo que la pila residiría en la caché para acelerar las cosas y no estoy seguro de si necesito lograr esto, o si el procesador puede manejarlo por mí. Solo estaba planeando usar una memoria RAM aburrida antigua con fines de prueba. No puedo decidir ¿Cuales son las opciones?
Busque KERNEL_STACK_SIZE en el código fuente del kernel de Linux y encontrará que depende mucho de la arquitectura: PAGE_SIZE o 2 * PAGE_SIZE, etc. (a continuación se muestran algunos resultados, se eliminan muchos resultados intermedios).
./arch/cris/include/asm/processor.h:
#define KERNEL_STACK_SIZE PAGE_SIZE
./arch/ia64/include/asm/ptrace.h:
# define KERNEL_STACK_SIZE_ORDER 3
# define KERNEL_STACK_SIZE_ORDER 2
# define KERNEL_STACK_SIZE_ORDER 1
# define KERNEL_STACK_SIZE_ORDER 0
#define IA64_STK_OFFSET ((1 << KERNEL_STACK_SIZE_ORDER)*PAGE_SIZE)
#define KERNEL_STACK_SIZE IA64_STK_OFFSET
./arch/ia64/include/asm/mca.h:
u64 mca_stack[KERNEL_STACK_SIZE/8];
u64 init_stack[KERNEL_STACK_SIZE/8];
./arch/ia64/include/asm/thread_info.h:
#define THREAD_SIZE KERNEL_STACK_SIZE
./arch/ia64/include/asm/mca_asm.h:
#define MCA_PT_REGS_OFFSET ALIGN16(KERNEL_STACK_SIZE-IA64_PT_REGS_SIZE)
./arch/parisc/include/asm/processor.h:
#define KERNEL_STACK_SIZE (4*PAGE_SIZE)
./arch/xtensa/include/asm/ptrace.h:
#define KERNEL_STACK_SIZE (2 * PAGE_SIZE)
./arch/microblaze/include/asm/processor.h:
# define KERNEL_STACK_SIZE 0x2000
El tamaño de la pila depende de lo que estén haciendo sus hilos. Mi consejo:
- hacer que el tamaño de la pila sea un parámetro al momento de la creación del hilo (diferentes hilos harán cosas diferentes, y por lo tanto necesitarán diferentes tamaños de pila)
- proporcione un valor por defecto razonable para aquellos que no quieren molestarse en especificar el tamaño de una pila (4K atrae al fanático del control en mí, ya que causará que el profiláctico de la pila, por ejemplo, obtenga la señal rápidamente)
- considere cómo detectará y manejará el desbordamiento de pila. La detección puede ser engañosa. Puedes poner páginas de guardia, vacías, en los extremos de tu pila, y eso generalmente funcionará. Pero confías en el comportamiento de Bad Thread para no saltar sobre ese foso y comenzar a contaminar lo que queda más allá. En general, eso no sucederá ... pero eso es lo que hace que los bichos realmente difíciles sean difíciles. Un mecanismo hermético implica piratear su compilador para generar código de comprobación de pila. En cuanto a tratar con un desbordamiento de pila, necesitarás una pila dedicada en otro lugar en la que se ejecutará el hilo ofensivo (o su ángel guardián, quienquiera que decidas que es, eres el diseñador del sistema operativo, después de todo).
- Recomiendo marcar los extremos de tu pila con un patrón distintivo, de modo que cuando tus hilos pasen por las puntas (y siempre lo hagan), al menos puedas verlo en la autopsia y ver que algo se escapó de su apilar. Una página de 0xDEADBEEF o algo así es útil.
Por cierto, los tamaños de página x86 generalmente son 4k, pero no tienen que serlo. Puedes ir con un tamaño de 64k o incluso más grande. La razón habitual para páginas más grandes es evitar errores de TLB. De nuevo, lo convertiría en una configuración de kernel o en un parámetro de tiempo de ejecución.
Pondré mis dos centavos para que la bola ruede:
No estoy seguro de qué tamaño de pila "típico" sería. Supongo que tal vez 8 KB por hilo, y si un hilo excede esta cantidad, solo lanza una excepción. Sin embargo, de acuerdo con esto , Windows tiene un tamaño predeterminado de pila reservada de 1 MB por subproceso, pero no se compromete de una vez (las páginas se comprometen a medida que se necesitan). Además, puede solicitar un tamaño de pila diferente para un EXE dado en tiempo de compilación con una directiva de compilación. No estoy seguro de lo que Linux hace, pero he visto referencias a pilas de 4 KB (aunque creo que esto se puede cambiar cuando compilas el kernel y no estoy seguro de cuál es el tamaño predeterminado de la pila ...)
Esto se relaciona con el primer punto. Es probable que desee un límite fijo sobre la cantidad de pila que puede obtener cada subproceso. Por lo tanto, es probable que no desee asignar automáticamente más espacio de pila cada vez que un hilo excede su espacio de pila actual, porque un programa con errores que se atasca en una recursión infinita va a devorar toda la memoria disponible.
Si está utilizando la memoria virtual, sí desea hacer que la pila sea factible. Forzar la asignación estática del tamaño de la pila, como es común en el subprocesamiento de nivel de usuario como Qthreads y Windows Fibers es un desastre. Difícil de usar, fácil de romper. Todos los sistemas operativos modernos hacen crecer la pila de forma dinámica, creo que normalmente tener una página de protección protegida contra escritura o dos debajo del puntero de la pila actual. Escribe allí y luego le dice al sistema operativo que la pila ha pasado por debajo de su espacio asignado, y asigna una nueva página de guardia debajo de eso y hace que la página que recibió el golpe pueda escribirse. Siempre que ninguna función asigna más que una página de datos, funciona bien. O puede usar dos o cuatro páginas de guardia para permitir marcos de pila más grandes.
Si quieres una forma de controlar el tamaño de la pila y tu objetivo es un entorno realmente controlado y eficiente, pero no te importa programar con el mismo estilo que Linux, busca un modelo de ejecución de una sola toma donde una tarea se inicie cada vez se detecta un evento relevante, se ejecuta hasta su finalización y luego almacena cualquier información persistente en su estructura de datos de tareas. De esta forma, todos los hilos pueden compartir una sola pila. Utilizado en muchos sistemas operativos finos en tiempo real para control automotriz y similares.