Подтвердить что ты не робот

Хорошее объяснение __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)

Связывание выглядит довольно сложной задачей:)

4b9b3361

Ответ 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.