GCC sections usage
Нехай перед нами стоїть наступна задача. Програма може складатися з набору модулів, які комбінуються в залежності від потреб. Кожен модуль має функцію, яка викликається при його виборі в простому меню на терміналі. Також є текстовий рядок та літера для меню. Ми хочемо автоматизувати процес збирання програми таким чином, що при підключення модуля в проект він «сам» ініціалізується і «реєструється» в програмі до початку роботи main()
. В C++ це робиться за допомогою конструкторів, але при цьому розмір програми росте. В C можна в окремому файлі створити масив структур опису модулів і ініціалізувати його статично. Щоправда, при цьому доведеться ініціалізувати модулі окремим циклом на початку функції main()
(теж трохи додаткового коду) та для формування масиву залежно від потреб використовувати #ifdef / #endif
.
Зрозуміло, якщо «з першого акту на стіні висить рушниця» моделі «використання секцій», то тут стрілятиме саме вона.
Автоматизувати ініціалізацію модуля при його підключенні до проекту нескладно, ми це розглянули у попередній статті про секції .init в avr-gcc. Використаємо аналогічний механізм для автоматизації збору інформації про модулі. Створимо структуру опису модуля (файл src/sections_demo/sections_demo.h
в доданому до цього повідомелення проекті):
20 21 22 23 24 25 26 | typedef void (*pvfunc_t)(); typedef struct { const prog_char *description; pvfunc_t func; char code; } module_decriptor_t; |
Також можна додати текст довідки по модулю, константні поля властивостей модуля та вказівник на змінювані дані модуля, які необхідно передавати по каналу зв’язку чи зберігати в EEPROM і т.д. Головне, щоб всі модулі описувалися однаковими структурами. У випадку традиційної для C статичної ініціалізації масиву структур тут мав би бути ще й вказівник на функцію ініціалізації модуля.
Структури опису модулів незмінні, тому заносимо їх у флеш-пам’ять у секцію module_list
. Включаємо до складу проекту власний лінкерний скрипт. У прикріпленому до статті прикладі це робиться додаванням змінної в основному мейк-файлі проекту src/Makefile
:
41 42 | # Custom linker script with user section placement LINK_SCRIPT := mega168.ld |
Пізніше цей скрипт буде додано до ключів лінкера у файлі src/makefiles/gcc-avr.mak
, який однаковий для всіх проектів:
99 100 101 | ifneq ($(LINK_SCRIPT),) LDFLAGS += -Wl,-T,$(LINK_SCRIPT) endif |
Сам лінкерний скрипт слід взяти з пакету avr-gcc і підредагувати. Лінкерні скрипти «за умовчанням» розміщено в каталозі /usr/lib/ldscripts
для avr-gcc в Linux та WinAVR\avr\lib\ldscripts
для пакету WinAVR у Windows. Скрипти там лежать під «архітектуру» AVR, а не під кожну модель мікроконтролера.
Відповідність моделі мікроконтролера та типу архітектури можна подивитися в розділі документації Machine-specific options for the AVR.
В прикладі використовується ATmega168, тому копіюємо в проект файл avr5.x
. Я відразу міняю файлу ім’я, щоб він точно не плутався зі стандартними.
Додаємо нашу секцію в регіон пам’яті .text
, відразу після секцій .progmem*
. Прямого доступу до цих структур нема, лише через вказівник, який проходить по набраному в секції .module_list
масиву. Тому, з точки зору лінкера, до них звертання нема і при наявності ключа –gc-sections їх можна викинути з вихідного файлу. Для збереження секції необхідно вказати KEEP() в лінкерному скрипті.
80 81 82 83 84 85 86 87 88 89 | .text : { *(.vectors) KEEP(*(.vectors)) /* For data that needs to reside in the lower 64k of progmem. */ *(.progmem.gcc*) *(.progmem*) PROVIDE (__module_list_start = .) ; KEEP(*(.module_list)) PROVIDE (__module_list_end = .) ; |
Якби ми хотіли набирати секції в оперативній пам’яті даних мікроконтролера, нам було б потрібно розмістити відповідні рядки в розділі скрипту для .data
.
До та після команди збору секцій .module_list
з усіх файлів проекту (*
) та зберігання їх у вихідному файлі (KEEP
) стоять команди скрипта PROVIDE()
, якими ми просимо лінкер створити дві мітки. Першій з них присвоїти адресу першого байту зібраних секцій, в другій — адресу першого байту після них. По цих мітках ми в програмі дізнаємося адреси початку та кінця набраного масиву структур. Для цього достатньо десь в програмі зробити оголошення двох «змінних»:
Для доступу до масиву потрібно брати їх адреси і приводити до типу структури:
(const module_decriptor_t *)(&__module_list_start);
Щоб спростити роботу, в файлі src/sections_demo/sections.h
задано макроси:
14 15 16 17 18 19 20 21 22 23 | // Declare external symbols for section start and end points #define SECTION_DECL(S) \ extern unsigned char __ ## S ## _start, __ ## S ## _end; // Section start and end pointers #define SECTION_START(S) (& __ ## S ## _start) #define SECTION_END(S) (& __ ## S ## _end) // Section attribute for variable declaration. #define SECTION(S) __attribute__((__section__( "." #S), used)) |
В рядку 15 задано макрос, який по імені секції створює необхідні оголошення змінних.
Наступні два макроси дають доступ до адрес початку та кінця масиву, але до байтових адрес. Файл sections.h
зроблено максимально універсальним, бо він є кандидатом на розміщення в бібліотеці h-файлів, які використовуватимуться з усіма проектами, які збираються за допомогою gcc (причому не лише avr-gcc, підтримка роботи з секцями однакова для всіх портів GCC та binutils).
Макрос SECTION(S)
з рядку 23 створює атрибут розміщення змінної для заданого імені секції. Атрибут used
додано для того, щоб компілятор не викидав змінну опису модуля, позначену як static
. Справа в тому, що опис модулів та їх функції в текстах усіх модулів можна зробити статичними, бо однак ззовні доступ до них іде лише через поля структур module_descriptor_t
. Після цього навіть не обов’язково давати їм унікальні в проекті імена, досить унікальності в межах файлу, та й то лише якщо файл реалізує кілька модулів.
У файлі проекту src/sections_demo/sections_demo.h
ми не лише задаємо тип структури опису модуля, а й «спеціалізуємо» макроси з sections.h
.
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | #ifndef SECTIONS_DEMO_H #define SECTIONS_DEMO_H #include <avr/pgmspace.h> #include "sections.h" #include "avrgcc_macros.h" #include "uart.h" typedef void (*pvfunc_t)(); typedef struct { const prog_char *description; pvfunc_t func; char code; } module_decriptor_t; SECTION_DECL(module_list) #define MODULE_LIST_START \ ((const module_decriptor_t *)SECTION_START(module_list)) #define MODULE_LIST_END \ ((const module_decriptor_t *)SECTION_END(module_list)) #define MODULE_DESCRIPTOR(name,func,code) \ static module_decriptor_t module SECTION(module_list) = \ { name, func, code } #endif /* SECTIONS_DEMO_H */ |
Крім автоматизації приведень вказівників до типу структури опису модуля ми в 36-му рядку задаємо макрос, який спрощує створення та розміщення у відповідній секції екземпляра структури.
Тепер файл реалізації якогось модуля може виглядати так:
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | #include "sections_demo.h" static const prog_char name[] = "Module 0"; static void func() { uart_putstr_P(PSTR("Module 0 function called\n")); } MODULE_DESCRIPTOR(name, func, 'a'); INIT(8) { uart_putstr_P(PSTR("Module 0 initialised\n")); } |
Цей приклад базується на тому, який було дано в попередній статті. Модуль UART ініціалізуєтсья автоматично в секції .init5
, тому при ініціалізації модулів в секції .init8
ми вже можемо використати видачу діагностичних повідомлень на термінал.
Функція проекту, яка формує на терміналі меню з переліком підключених модулів, може виглядати так (файл src/main.c
):
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | static uint8_t print_modules() { const module_decriptor_t *pmodule = MODULE_LIST_START; if (pmodule == MODULE_LIST_END) { uart_putstr_P(PSTR("No modules registered\n")); return 0; } while (pmodule < MODULE_LIST_END) { uart_putchar(pgm_read_byte(&pmodule->code)); uart_putstr_P(PSTR(" - ")); uart_putstr_P((const prog_char*)pgm_read_word(&pmodule->description)); uart_putchar('\n'); ++pmodule; } uart_putstr_P(PSTR("? - this help\n")); return 1; } |
За умовчанням секції лінкуватимуться згідно порядку обробки файлів лінкером. Цей порядок, в свою чергу, визначається порядком файлів у командному рядку. Для Makefile
, який іде з прикладом, порядок каталогів визначається послідовністю їх додавання до змінної MODULES
, файли в межах каталогу видаватимуться функцією $(wildcard )
у файлі src/makefiles/gcc-avr.mak
в алфавітному порядку імен файлів. Подивитися цей порядок у запропонованому прикладі можна командою make objlist
. Щоб було цікавіше, в файлі src/Makefile
Module 2 спеціально додано раніше за Module 1:
46 47 48 | MODULES := sections_demo uart c_lib/avr MODULES += module2 MODULES += module1 |
При цьому список на екрані терміналу виглядає так:
x - Module 2-C
w - Module 2-A
W - Module 2-B
y - Module 1-A
k - Module 1-B
? - this help
Файл module0.c
знаходиться в каталозі sections_demo
, тому Module 0 підключається завжди і першим. В межах файлу структури потрапляють в секцію в порядку їх визначення, тому з module2.c
в спискові спочатку друкуєтьcя інформація про субмодуль C, потім про субмодулі A та B.
Якщо з якоїсь причини необхідно керувати порядком переліку модулів в списку незалежно від порядку їх додавання в Makefile, то можна використати сортування фрагментів секції .module_list
по імені файлу:
87 88 89 | PROVIDE (__module_list_start = .) ; KEEP(SORT(*)(.module_list)) PROVIDE (__module_list_end = .) ; |
Список на екрані терміналу змінюється. Тепер Module 2 йде після Module 1, але порядок субмодулів в ньому залишається:
y - Module 1-A
k - Module 1-B
x - Module 2-C
w - Module 2-A
W - Module 2-B
? - this help
Можна зробити інакше — створювати секції, імена яких починаються з .module_list
, а в лінкерному скрипті задати сортування не по імені файлу, а по імені секції.
Зверніть увагу на зірочку в кінці імені секції.
Тепер можна при створенні опису модуля в кінці імені секції вручну вказати «пріоритет» цього модуля:
name, func, 'a'
};
Краще ж зробити ще один макрос MODULE_DESCRIPTOR_PRIO
, в якому додано аргумент пріоритету модуля:
40 41 42 | #define MODULE_DESCRIPTOR_PRIO(name,func,code,prio) \ static module_decriptor_t module SECTION(module_list. ## prio) = \ { name, func, code } |
В прикладі показано всі варіанти створення структури опису модуля, в тому числі створення структур для двох субмодулів одним записом (файл src/module2/module2.c
):
40 41 42 43 | static module_decriptor_t module2ab[] SECTION(module_list.020) = { { name2a, func_a, 'w' }, { name2b, func_b, 'W' } }; |
Якщо тепер змінним опису модулів 1-A, 1-B, 2-AB та 2-C задати пріоритети 035, 030, 020, 025 відповідно, то незалежно від порядку файлів список виглядатиме наступним чином:
W - Module 2-B
x - Module 2-C
k - Module 1-B
y - Module 1-A
a - Module 0
? - this help
Також є можливість відсортувати одночасно і по імені файлу, і, в межах файлу, по імені секції
але це вже незрозуміло нащо. В межах файлу і так не важко розмістити все в потрібному порядку, хіба що файли зовсім вже великі.
Замовлене сортування буде проводитися лише для секцій з іменами .module_list*
, всі інші секції лінкуватимуться в своєму порядку — або згідно своїх команд SORT
, або згідно порядку переліку в командному рядку. Слід бути уважним, наприклад, функції ініціалізації модулів в блоках INIT()
одного рівня лінкуватимуться в порядку переліку файлів в командному рядку незалежно від сортування .module_list*
. При необхідності слід підкоригувати поведінку лінкера відповідними командами для секцій .init
.
Все це можна було б зробити і «звичайним» способом, за допомогою препроцесора та купи #ifdef
, але, залежно від задачі, такий спосіб може бути набагато зручнішим і логічнішим. Вибір за вами. Навіть якщо ви не користуватиметеся цим інструментом, знати про нього однак корисно, ширша база дає вищу стійкість 🙂
© Alexandr Redchuk aka ReAl, 2011 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License. |
Attached Files:
- GCC sections usage demo
avr-gcc, atmega168. Code::Blocks project with external makefile (can be used with any IDE or without IDE)