Хорошее объяснение __read_mostly, __init, __exit macros
Макрорасширение __read_mostly
:
#define __read_mostly __attribute__((__section__(".data..read_mostly"))
Это от cache.h
__init
:
#define __init __section(.init.text) __cold notrace
от init.h
__exit
:
#define __exit __section(.exit.text) __exitused __cold notrace
После поиска через сеть я не нашел никакого хорошего объяснения
что там происходит.
Дополнительный вопрос: я слышал о различных "линкер-магии",
используемых в разработке ядра. Любая информация
в отношении этого будет замечательно.
У меня есть некоторые идеи об этих макросах о , что они делают. Как и __init
, предполагается, что код функции можно удалить после инициализации. __read_mostly
предназначен для указания того, что данные редко записываются и тем самым минимизирует промахи в кеше. Но я не знаю о Как они это делают. Я имею в виду, что они gcc
расширения. Поэтому теоретически их можно продемонстрировать с помощью небольшого кода пользователя userland c.
ОБНОВЛЕНИЕ 1:
Я попытался проверить __section__
на произвольное имя раздела. тестовый код:
#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;
}
Теперь с __read_mostly
сгенерированный код сборки:
.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
Теперь без макроса __read_mostly
код сборки остается более или менее одинаковым.
это diff
--- 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
Таким образом, по существу создается только один подраздел, ничего необычного.
Даже objdump disasmbly не показывает никакой разницы.
Итак, мой окончательный вывод о них, его компоновщик делают что-то для раздела данных, отмеченного специальным именем. Я думаю, что ядро linux использует какой-то пользовательский компоновщик script, чтобы достичь этих целей.
Одна из вещей в __read_mostly
, данные, которые были помещены туда, могут быть сгруппированы и управляться таким образом, чтобы можно было уменьшить количество промахов в кеше.
Кто-то из lkml отправил патч для удаления __read_mostly
. Который породил увлеченное обсуждение достоинств и недостатков __read_mostly
.
вот ссылка: https://lkml.org/lkml/2007/12/13/477
Я опубликую дополнительную информацию о __init
и __exit
.
ОБНОВЛЕНИЕ 2
Эти макросы __init
, __exit
и __read_mostly
помещают содержимое данных (в случае __read_mostly
) и текст (в случаях __init
и __exit
) помещаются в пользовательские именованные разделы. Эти разделы используются компоновщиком. Теперь, поскольку компоновщик не используется по умолчанию по различным причинам, для достижения целей этих макросов используется A linker script.
Можно узнать, как можно использовать собственный компоновщик script для устранения мертвого кода (код, связанный с компоновщиком, но никогда не исполняемый). Эта проблема имеет очень важное значение во встроенных сценариях. В этом документе обсуждается, как компоновщик script может быть настроен для удаления мертвого кода: elinux.org/images/2/2d/ELC2010-gc-sections_Denys_Vlasenko.pdf
В случае ядра исходный компоновщик script можно найти include/asm-generic/vmlinux.lds.h
. Это не окончательный script. Это своего рода начальная точка, компоновщик script дополнительно модифицируется для разных платформ.
Быстрый просмотр этого файла может сразу найти интересующие вас части:
#define READ_MOSTLY_DATA(align) \
. = ALIGN(align); \
*(.data..read_mostly) \
. = ALIGN(align);
Кажется, этот раздел использует раздел ".data..readmostly".
Также вы можете найти __init
и __exit
:
#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)
Связывание выглядит довольно сложной задачей:)
Ответы
Ответ 1
атрибуты GCC являются общим механизмом для предоставления инструкций компилятору, которые не соответствуют спецификации самого языка.
Общее средство, которое перечисляет макрос, это использование атрибута __section__
, который описывается как:
Атрибут section
указывает, что функция находится в определенном разделе. Например, объявление:
extern void foobar (void) __attribute__ ((section ("bar")));
помещает функцию foobar в секцию панели.
Итак, что значит помещать что-то в раздел? Объектный файл разделяется на разделы: .text
для исполняемого машинного кода, .data
для данных чтения-записи, .rodata
для данных только для чтения, .bss
для данных, инициализированных до нуля, и т.д. Названия и цели эти разделы являются предметом соглашения о платформе, и некоторые специальные разделы могут быть доступны только из C с использованием синтаксиса __attribute__ ((section))
.
В вашем примере вы можете предположить, что .data..read_mostly
является подразделением .data
для данных, которые будут в основном прочитаны; .init.text
- это текстовый (машинный код) раздел, который будет запускаться при инициализации программы и т.д.
В Linux решение о том, что делать с различными разделами, - это работа ядра; когда пользовательское пространство запрашивает exec
программу, оно будет поочередно прочитывать изображение программы и обрабатывать их соответствующим образом: разделы .data
отображаются в виде страниц для чтения и записи, .rodata
как только для чтения, .text
как execute-only и т.д. Предположительно .init.text
будет выполнен до запуска программы; это может быть сделано ядром или кодом пользовательского пространства, помещенным в точку входа программы (я предполагаю последнее).
Если вы хотите увидеть эффект этих атрибутов, хорошим испытанием является запуск gcc с опцией -S
для вывода кода ассемблера, который будет содержать директивы раздела. Затем вы можете запустить ассемблер с директивами раздела и без него и использовать objdump
или даже шестнадцатеричный дамп результирующего объектного файла, чтобы увидеть, как он отличается.
Ответ 2
Насколько я знаю, эти макросы используются исключительно ядром. Теоретически, они могут применяться к пользовательскому пространству, но я не верю, что это так. Все они группируют одинаковую переменную и кодируют вместе для разных эффектов.
INIT/выхода
Для настройки ядра требуется много кода; это происходит прежде, чем какое-либо пространство пользователя будет запущено вообще. То есть до запуска задачи init. Во многих случаях этот код никогда не используется снова. Поэтому было бы бесполезно тратить неиспользуемую оперативную память после загрузки. Знакомое сообщение ядра " Освобождение памяти инициализации" является результатом раздела init
. Некоторые драйверы могут быть настроены как модули. В этих случаях они выходят. Однако, если они скомпилированы в ядро, они не обязательно завершаются (они могут завершить работу). Это еще один раздел для группировки этого типа кода/данных.
холодный горячий
Каждая строка кэша имеет фиксированный размер. Вы можете увеличить кэш, поместив в него данные/функции того же типа. Идея в том, что часто используемый код может идти рядом. Если в кеше четыре инструкции, конец одной горячей подпрограммы должен объединиться с началом следующей горячей подпрограммы. Точно так же хорошо хранить редко используемый код вместе, так как мы надеемся, что он никогда не попадет в кеш.
read_mostly
Идея здесь похожа на горячую; Разница с данными мы можем обновить значения. Когда это сделано, вся строка кэша становится грязной и должна быть перезаписана в основную память. Это необходимо для согласованности нескольких процессоров и когда эта строка кэша становится устаревшей. Если ничего не изменилось в разнице между версией кеша процессора и основной памятью, то при выселении ничего не должно происходить. Это оптимизирует шину оперативной памяти, так что могут произойти другие важные вещи.
Эти пункты строго для ядра. Подобные трюки могут быть (есть?) Для пользовательского пространства. Это будет зависеть от используемого загрузчика; который часто отличается в зависимости от используемого libc.