buena explicación de__read_mostly,__init,__exit macros
gcc linux-kernel (2)
Hasta donde yo sé, estas macros son utilizadas exclusivamente por el kernel. En teoría, podrían aplicarse al espacio de usuario , pero no creo que este sea el caso. La variable y el código similares de todo el grupo juntos para diferentes efectos.
init / exit
Se necesita mucho código para configurar el kernel ; esto sucede antes de que se ejecute ningún espacio de usuario . Es decir, antes de que se ejecute la tarea init . En muchos casos, este código nunca se usa nuevamente. Entonces sería un desperdicio consumir RAM no intercambiable después del arranque. El mensaje de kernel familiar Freeing init memory es el resultado de la sección init
. Algunos controladores pueden configurarse como módulos . En estos casos, salen . Sin embargo, si se compilan en el kernel, no necesariamente salen (pueden apagarse ). Esta es otra sección para agrupar este tipo de código / datos.
frio calor
Cada línea de caché tiene un tamaño fijo. Puede maximizar un caché colocando el mismo tipo de datos / función en él. La idea es que el código que se usa a menudo puede ir de la mano. Si la memoria caché tiene cuatro instrucciones, el final de una rutina activa debería fusionarse con el comienzo de la siguiente rutina activa. Del mismo modo, es bueno mantener juntos el código poco utilizado, ya que esperamos que nunca entre en el caché .
read_mostly
La idea aquí es similar a la caliente ; la diferencia con los datos podemos actualizar los valores. Cuando se hace esto, toda la línea de caché se ensucia y se debe volver a escribir en la RAM principal. Esto es necesario para la consistencia de múltiples CPU y cuando esa línea de caché se queda obsoleta. Si nada ha cambiado en la diferencia entre la versión de la memoria caché de la CPU y la memoria principal, entonces no es necesario que ocurra nada en un desalojo . Esto optimiza el bus RAM para que otras cosas importantes puedan suceder.
Estos artículos son estrictamente para el kernel. Trucos similares podrían (¿son?) Implementarse para el espacio del usuario . Eso dependería del cargador en uso; que a menudo es diferente dependiendo de la libc en uso.
La macro expansión de __read_mostly
:
#define __read_mostly __attribute__((__section__(".data..read_mostly"))
Este es de cache.h
__init
:
#define __init __section(.init.text) __cold notrace
desde init.h
__exit
:
#define __exit __section(.exit.text) __exitused __cold notrace
Después de buscar a través de la red no he encontrado ninguna buena explicación de lo que está sucediendo allí.
Pregunta adicional: He escuchado acerca de varios "magia del enlazador" empleados en el desarrollo del kernel. Cualquier información con respecto a esto será maravillosa.
Tengo algunas ideas sobre estas macros sobre lo que hacen . Al igual que __init
supone que indica que el código de función se puede eliminar después de la inicialización. __read_mostly
es para indicar que los datos rara vez se escriben y, con esto, minimiza los errores de caché. Pero no tengo idea de cómo lo hacen . Quiero decir que son extensiones gcc
. Entonces, en teoría, pueden demostrarse mediante un pequeño código c de usuario.
ACTUALIZACIÓN 1:
He intentado probar la sección __section__
con un nombre de sección arbitrario. el código de prueba:
#include <stdio.h>
#define __read_mostly __attribute__((__section__("MY_DATA")))
struct ro {
char a;
int b;
char * c;
};
struct ro my_ro __read_mostly = {
.a = ''a'',
.b = 3,
.c = NULL,
};
int main(int argc, char **argv) {
printf("hello");
printf("my ro %c %d %p /n", my_ro.a, my_ro.b, my_ro.c);
return 0;
}
Ahora con __read_mostly
el código de ensamblaje generado:
.file "ro.c"
.globl my_ro
.section MY_DATA,"aw",@progbits
.align 16
.type my_ro, @object
.size my_ro, 16
my_ro:
.byte 97
.zero 3
.long 3
.quad 0
.section .rodata
.LC0:
.string "hello"
.LC1:
.string "my ro %c %d %p /n"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
pushq %rbx
subq $24, %rsp
movl %edi, -20(%rbp)
movq %rsi, -32(%rbp)
movl $.LC0, %eax
movq %rax, %rdi
movl $0, %eax
.cfi_offset 3, -24
call printf
movq my_ro+8(%rip), %rcx
movl my_ro+4(%rip), %edx
movzbl my_ro(%rip), %eax
movsbl %al, %ebx
movl $.LC1, %eax
movl %ebx, %esi
movq %rax, %rdi
movl $0, %eax
call printf
movl $0, %eax
addq $24, %rsp
popq %rbx
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (GNU) 4.4.6 20110731 (Red Hat 4.4.6-3)"
.section .note.GNU-stack,"",@progbits
Ahora sin la macro __read_mostly
el código de ensamblado permanece más o menos igual.
esta es la diferencia
--- rm.S 2012-07-17 16:17:05.795771270 +0600
+++ rw.S 2012-07-17 16:19:08.633895693 +0600
@@ -1,6 +1,6 @@
.file "ro.c"
.globl my_ro
- .section MY_DATA,"aw",@progbits
+ .data
.align 16
.type my_ro, @object
.size my_ro, 16
Entonces esencialmente solo se crea una subsección, nada lujoso.
Incluso el objdump de desactivación no muestra ninguna diferencia.
Así que mi conclusión final sobre ellos, es el trabajo del enlazador hacer algo para la sección de datos marcados con un nombre especial. Creo que Linux kernel usa algún tipo de script enlazador personalizado para lograr estas cosas.
Una de las cosas de __read_mostly
, los datos que se colocaron allí se pueden agrupar y administrar de forma que se puedan reducir los errores de caché.
Alguien en lkml envió un parche para eliminar __read_mostly
. Lo cual engendró una discusión fascinada sobre los méritos y deméritos de __read_mostly
.
aquí está el enlace: https://lkml.org/lkml/2007/12/13/477
Publicaré más actualizaciones en __init
y __exit
.
ACTUALIZACIÓN 2
Estas macros __init
, __exit
y __read_mostly
ponen el contenido de los datos (en el caso de __read_mostly
) y el texto (en los casos de __init
y __exit
) se colocan en secciones personalizadas con nombre. Estas secciones son utilizadas por el enlazador. Ahora, como el enlazador no se usa como su comportamiento predeterminado por varias razones, se utiliza una secuencia de comandos del enlazador para lograr los propósitos de estas macros.
Se puede encontrar un fondo sobre cómo se puede usar un script de enlazador personalizado para eliminar el código muerto (código que está enlazado por el vinculador pero nunca se ejecutó). Este problema es de suma importancia en los escenarios integrados. Este documento explica cómo se puede ajustar un script del enlazador para eliminar el código muerto : elinux.org/images/2/2d/ELC2010-gc-sections_Denys_Vlasenko.pdf
En caso de kernel, la secuencia de comandos del enlazador inicial puede ser include/asm-generic/vmlinux.lds.h
. Este no es el guion final. Este es un punto de partida , el script del enlazador se modifica aún más para diferentes plataformas.
Una mirada rápida a este archivo, las porciones de interés se pueden encontrar inmediatamente:
#define READ_MOSTLY_DATA(align) /
. = ALIGN(align); /
*(.data..read_mostly) /
. = ALIGN(align);
Parece que esta sección está usando la sección ".data..readmostly".
También puede encontrar __init
y __exit
comandos de vinculador relacionados con la sección:
#define INIT_TEXT /
*(.init.text) /
DEV_DISCARD(init.text) /
CPU_DISCARD(init.text) /
MEM_DISCARD(init.text)
#define EXIT_TEXT /
*(.exit.text) /
DEV_DISCARD(exit.text) /
CPU_DISCARD(exit.text) /
MEM_DISCARD(exit.text)
Vinculación parece algo bastante complejo que hacer :)
Los atributos GCC son un mecanismo general para dar instrucciones al compilador que están fuera de la especificación del lenguaje en sí.
La facilidad común de que las macros que enumera es el uso del atributo __section__
que se describe como:
El atributo de
section
especifica que una función vive en una sección particular. Por ejemplo, la declaración:
extern void foobar (void) __attribute__ ((section ("bar")));
pone la función foobar en la sección de la barra.
Entonces, ¿qué significa poner algo en una sección? Un archivo objeto se divide en secciones: .text
para código de máquina ejecutable, .data
para datos de lectura-escritura, .rodata
para datos de solo lectura, .bss
para datos inicializados a cero, etc. Los nombres y propósitos de estas secciones son una El asunto de la convención de la plataforma, y algunas secciones especiales solo se puede acceder desde C usando la __attribute__ ((section))
.
En su ejemplo, puede adivinar que .data..read_mostly
es una subsección de .data
para datos que serán principalmente leídos; .init.text
es una sección de texto (código de máquina) que se ejecutará cuando se inicialice el programa, etc.
En Linux, decidir qué hacer con las diversas secciones es el trabajo del kernel; cuando el espacio de usuario solicite exec
un programa, leerá la imagen del programa sección por sección y las procesará de manera apropiada: las secciones .data
se asignan como páginas de lectura-escritura, .rodata
como de solo lectura, .text
como ejecución-solo, etc. Presumiblemente .init.text
se ejecutará antes de que comience el programa; eso podría ser hecho por el kernel o por el código de espacio de usuario colocado en el punto de entrada del programa (supongo que esto último).
Si desea ver el efecto de estos atributos, una buena prueba es ejecutar gcc con la opción -S
para dar salida al código del ensamblador, que contendrá las directivas de la sección. A continuación, puede ejecutar el ensamblador con y sin las directivas de sección y usar objdump
o incluso volcar hexadecimalmente el archivo de objeto resultante para ver cómo difiere.