Aby zwiększyć odporność i niezawodność modułów dostawców, postępuj zgodnie z tymi wskazówkami. Postępowanie zgodnie z wielu wskazówkami może ułatwić określenie prawidłowej kolejności ładowania modułów i kolejności, w jakiej sterowniki muszą sprawdzać urządzenia.
Moduł może być biblioteką lub sterownikiem.
Moduły biblioteki to biblioteki, które udostępniają interfejsy API do użycia przez inne moduły. Takie moduły zwykle nie są związane z konkretnym sprzętem. Przykłady modułów biblioteki to moduł szyfrowania AES, platforma
remoteproc
skompilowana jako moduł oraz moduł logbuffer. Kod modułu wmodule_init()
jest uruchamiany w celu konfigurowania struktur danych, ale żaden inny kod nie jest uruchamiany, chyba że zostanie wywołany przez moduł zewnętrzny.Moduły sterownika to sterowniki, które sprawdzają lub łączą się z konkretnym typem urządzenia. Takie moduły są związane ze sprzętem. Przykłady modułów sterownika to UART, PCIe i sprzęt kodujący wideo. Moduły sterownika aktywują się tylko wtedy, gdy powiązane z nimi urządzenie jest obecne w systemie.
Jeśli urządzenie nie jest obecne, jedynym kodem modułu, który jest uruchamiany, jest kod
module_init()
, który rejestruje sterownik w ramach rdzenia sterownika.Jeśli urządzenie jest dostępne i sterownik może je wykryć lub z nim nawiązać połączenie, może uruchomić inny kod modułu.
Prawidłowe inicjowanie i zamykanie modułu
Moduł sterownika musi zarejestrować sterownika w module_init()
i usunąć go z module_exit()
. Jednym ze sposobów na wymuszenie tych ograniczeń jest używanie makr opakowania, które pozwala uniknąć bezpośredniego korzystania z makr module_init()
, *_initcall()
lub module_exit()
.
W przypadku modułów, które można zwolnić, użyj opcji
module_subsystem_driver()
. Przykłady:module_platform_driver()
,module_i2c_driver()
imodule_pci_driver()
.W przypadku modułów, których nie można odciążyć, użyj
builtin_subsystem_driver()
. Przykłady:builtin_platform_driver()
,builtin_i2c_driver()
ibuiltin_pci_driver()
.
Niektóre moduły sterownika używają module_init()
i module_exit()
, ponieważ rejestrują więcej niż 1 sterownik. Jeśli moduł sterownika używa funkcji module_init()
i module_exit()
do rejestrowania wielu sterowników, spróbuj połączyć sterowniki w jeden. Możesz na przykład rozróżnić je za pomocą ciągu znaków compatible
lub danych pomocniczych urządzenia, zamiast rejestrować osobne sterowniki.
Możesz też podzielić moduł sterownika na 2 moduły.
Wyjątki funkcji init i exit
Moduł biblioteki nie rejestruje sterowników i nie podlega ograniczeniom dotyczącym funkcji module_init()
i module_exit()
, ponieważ może potrzebować tych funkcji do konfigurowania struktur danych, kolejek roboczych lub wątków jądra.
Korzystanie z makra MODULE_DEVICE_TABLE
Moduły sterownika muszą zawierać makro MODULE_DEVICE_TABLE
, które pozwala w przestrzeni użytkownika określić urządzenia obsługiwane przez moduł sterownika przed jego wczytaniem. Android może używać tych danych do optymalizacji ładowania modułów, np. do unikania ładowania modułów na urządzeniach, których nie ma w systemie. Przykłady użycia makra znajdziesz w kodowaniu źródłowym.
Unikanie niezgodności CRC z powodu zadeklarowanych z wyprzedzeniem typów danych
Nie dołączaj plików nagłówków, aby uzyskać widoczność typów danych zadeklarowanych do przodu.
Niektóre struktury, uniezależnienia i inne typy danych zdefiniowane w pliku nagłówka (header-A.h
) mogą być zadeklarowane z wyprzedzeniem w innym pliku nagłówka (header-B.h
), który zwykle używa wskaźników do tych typów danych. Ten wzór kodu oznacza, że jądro celowo stara się zachować prywatność struktury danych dla użytkowników header-B.h
.
Użytkownicy header-B.h
nie powinni używać funkcji header-A.h
do bezpośredniego dostępu do wewnętrznych struktur tych zadeklarowanych z wyprzedzeniem struktur danych. Spowoduje to problemy z niezgodnością CONFIG_MODVERSIONS
CRC (które generują problemy ze zgodnością ABI), gdy inne jądro (np. jądro GKI) spróbuje załadować moduł.
Na przykład funkcja struct fwnode_handle
jest zdefiniowana w funkcji include/linux/fwnode.h
, ale jest zadeklarowana do przodu jako struct fwnode_handle;
w funkcji include/linux/device.h
, ponieważ jądro próbuje zachować szczegóły funkcji struct fwnode_handle
jako prywatne dla użytkowników funkcji include/linux/device.h
. W tym scenariuszu nie dodawaj #include <linux/fwnode.h>
w module, aby uzyskać dostęp do użytkowników grupy struct fwnode_handle
. Każdy projekt, w którym musisz uwzględnić takie pliki nagłówków, wskazuje na zły wzór projektowania.
Nie uzyskuj bezpośredniego dostępu do podstawowych struktur jądra
Bezpośredni dostęp do podstawowych struktur danych jądra lub ich modyfikacja mogą powodować niepożądane działanie, w tym wycieki pamięci, awarie i utratę zgodności z przyszłościowymi wersjami jądra. Struktura danych jest podstawową strukturą danych jądra, gdy spełnia co najmniej jeden z tych warunków:
Struktura danych jest zdefiniowana w sekcji
KERNEL-DIR/include/
. Na przykład:struct device
istruct dev_links_info
. Struktury danych zdefiniowane w poluinclude/linux/soc
są wykluczone.Struktura danych jest przydzielana lub inicjowana przez moduł, ale jest widoczna dla jądra, ponieważ jest przekazywana pośrednio (za pomocą wskaźnika w strukturze) lub bezpośrednio jako dane wejściowe w funkcji wyeksportowanej przez jądro. Na przykład moduł sterownika
cpufreq
inicjuje obiektstruct cpufreq_driver
, a następnie przekazuje go jako dane wejściowe docpufreq_register_driver()
. Po tym punkcie moduł sterownikacpufreq
nie powinien modyfikować bezpośredniostruct cpufreq_driver
, ponieważ wywołanie funkcjicpufreq_register_driver()
powoduje, żestruct cpufreq_driver
staje się widoczne dla jądra.Struktura danych nie została zainicjowana przez moduł. Na przykład:
struct regulator_dev
zwracany przezregulator_register()
.
Dostęp do podstawowych struktur danych jądra tylko za pomocą funkcji wyeksportowanych przez jądro lub parametrów przekazywanych wyraźnie jako dane wejściowe do funkcji dostawcy. Jeśli nie masz elementu zaczepienia interfejsu API ani dostawcy, które modyfikuje części podstawowej struktury danych jądra, jest to prawdopodobnie celowe i nie należy modyfikować struktury danych z modułów. Na przykład nie modyfikuj żadnych pól w sekcji struct device
ani struct device.links
.
Aby zmodyfikować
device.devres_head
, użyj funkcjidevm_*()
, takiej jakdevm_clk_get()
,devm_regulator_get()
lubdevm_kzalloc()
.Aby zmodyfikować pola w sekcji
struct device.links
, użyj interfejsu API linkowania urządzeń, takiego jakdevice_link_add()
lubdevice_link_del()
.
Nie analizuj węzłów drzewa urządzenia za pomocą właściwości zgodności
Jeśli węzeł drzewa urządzenia (DT) ma właściwość compatible
, struct device
jest dla niego przydzielany automatycznie lub gdy wywoływana jest funkcja of_platform_populate()
w nadrzędnym węźle DT (zazwyczaj przez sterownik urządzenia nadrzędnego). Oczekiwanie domyślne (z wyjątkiem niektórych urządzeń zainicjowanych wcześniej przez algorytm szeregowania) polega na tym, że węzeł przenoszenia danych z właściwością compatible
ma struct device
i pasujący sterownik urządzenia. Wszystkie inne wyjątki są już obsługiwane przez kod źródłowy.
Dodatkowo fw_devlink
(wcześniej of_devlink
) traktuje węzły DT z właściwością compatible
jako urządzenia z przypisanym struct device
, które jest sprawdzane przez sterownik. Jeśli węzeł DT ma właściwość compatible
, ale przydzielone struct device
nie jest sondowane, fw_devlink
może zablokować sondowanie urządzeń konsumenta lub zablokować wywołania sync_state()
na urządzeniach dostawcy.
Jeśli sterownik używa funkcji of_find_*()
(na przykład of_find_node_by_name()
lub of_find_compatible_node()
), aby bezpośrednio znaleźć węzeł DT z właściwością compatible
, a następnie przeanalizować ten węzeł DT, napraw moduł, pisząc sterownik urządzenia, który może zbadać urządzenie, lub usuń właściwość compatible
(możliwe tylko wtedy, gdy nie została przesłana w górę). Aby omówić alternatywne rozwiązania, skontaktuj się z zespołem Androida jądra pod adresem kernel-team@android.com i przygotuj się na uzasadnienie swoich przypadków użycia.
Wyszukiwanie dostawców za pomocą phandle’ów DT
W miarę możliwości odwołuj się do dostawcy za pomocą phandle (odwołania lub wskaźnika do węzła DT) w DT. Używanie standardowych powiązań DT i identyfikatorów phandle do odwoływania się do dostawców umożliwia fw_devlink
(wcześniej of_devlink
) automatyczne określanie zależności między urządzeniami przez analizowanie DT w czasie wykonywania. Rdzeń może automatycznie sprawdzać urządzenia w prawidłowej kolejności, co eliminuje potrzebę porządkowania ładowania modułów lub MODULE_SOFTDEP()
.
Starszy scenariusz (brak obsługi DT w rdzeniu ARM)
Wcześniej, zanim do jądra ARM dodano obsługę DT, konsumenci, np. urządzenia dotykowe, wyszukiwali dostawców, takich jak regulatorzy, za pomocą globalnie unikalnych ciągów znaków.
Na przykład sterownik ACME PMIC może rejestrować lub reklamować wiele regulatorów (np. acme-pmic-ldo1
do acme-pmic-ldo10
), a sterownik dotykowy może wyszukiwać regulatora za pomocą regulator_get(dev, "acme-pmic-ldo10")
.
Jednak na innej płycie LDO8 może zasilać urządzenie dotykowe, tworząc skomplikowany system, w którym ten sam sterownik dotykowy musi określać poprawny ciąg znaków wyszukiwania dla regulatora na każdej płycie, na której jest używane urządzenie dotykowe.
Obecny scenariusz (obsługa przenoszenia danych w jądrze ARM)
Po dodaniu obsługi DT do jąder ARM użytkownicy mogą identyfikować dostawców w DT, odwołując się do węzła drzewa urządzenia dostawcy za pomocą phandle.
Konsumenci mogą też nadać nazwę zasobom na podstawie tego, do czego służą, a nie tego, kto je udostępnia. Na przykład sterownik ekranu dotykowego z poprzedniego przykładu może użyć parametrów regulator_get(dev, "core")
i regulator_get(dev, "sensor")
, aby uzyskać źródła zasilające rdzeń i czujnik urządzenia dotykowego. Powiązany DT dla takiego urządzenia wygląda podobnie do tego przykładowego kodu:
touch-device {
compatible = "fizz,touch";
...
core-supply = <&acme_pmic_ldo4>;
sensor-supply = <&acme_pmic_ldo10>;
};
acme-pmic {
compatible = "acme,super-pmic";
...
acme_pmic_ldo4: ldo4 {
...
};
...
acme_pmic_ldo10: ldo10 {
...
};
};
Scenariusz najgorszej sytuacji
Niektóre sterowniki przeniesione ze starszych jąder zawierają w DT obecne w pliku danych starsze zachowanie, które zajmuje najgorszą część starszego schematu i wymusza zastosowanie nowszego schematu, który ma ułatwić sprawę. W takich sterownikach sterownik klienta odczytuje ciąg znaków do użycia w wyszukiwaniu za pomocą właściwości DT specyficznej dla urządzenia, a dostawca użyje innej właściwości specyficznej dla dostawcy, aby zdefiniować nazwę do użycia w rejestrowaniu zasobu dostawcy. Następnie klient i dostawca nadal używają tego samego starego schematu używania ciągów znaków do wyszukiwania dostawcy. W tym najgorszym scenariuszu:
Sterownik dotykowy używa kodu podobnego do tego:
str = of_property_read(np, "fizz,core-regulator"); core_reg = regulator_get(dev, str); str = of_property_read(np, "fizz,sensor-regulator"); sensor_reg = regulator_get(dev, str);
DT używa kodu podobnego do tego:
touch-device { compatible = "fizz,touch"; ... fizz,core-regulator = "acme-pmic-ldo4"; fizz,sensor-regulator = "acme-pmic-ldo4"; }; acme-pmic { compatible = "acme,super-pmic"; ... ldo4 { regulator-name = "acme-pmic-ldo4" ... }; ... acme_pmic_ldo10: ldo10 { ... regulator-name = "acme-pmic-ldo10" }; };
Nie modyfikuj błędów interfejsu API frameworku.
Interfejsy API platformy, takie jak regulator
, clocks
, irq
, gpio
, phys
i extcon
, zwracają -EPROBE_DEFER
jako zwracaną wartość błędu, aby wskazać, że urządzenie próbuje przeprowadzić sondowanie, ale w tej chwili nie może tego zrobić. Jądro powinno spróbować przeprowadzić sondę ponownie później. Aby mieć pewność, że funkcja .probe()
Twojego urządzenia działa zgodnie z oczekiwaniami w takich przypadkach, nie zastępuj ani nie zmieniaj mapowania wartości błędu.
Zastąpienie lub ponowne zmapowanie wartości błędu może spowodować odrzucenie wartości -EPROBE_DEFER
i to, że urządzenie nigdy nie zostanie zbadane.
Używaj wersji interfejsu API devm_*()
Gdy urządzenie pobiera zasób za pomocą interfejsu API devm_*()
, zasób jest automatycznie zwalniany przez jądro, jeśli urządzenie nie może przeprowadzić sondowania lub przeprowadzi je pomyślnie, a później zostanie odłączone. Ta funkcja sprawia, że kod obsługi błędów w funkcji probe()
jest czystszy, ponieważ nie wymaga skoków goto
w celu zwolnienia zasobów pozyskanych przez devm_*()
i upraszcza operacje usuwania powiązań sterownika.
Obsługa odłączania urządzenia od sterownika
Pamiętaj o odwiązaniu sterowników urządzeń i nie pozostawiaj tego parametru z wartością undefined, ponieważ niedefiniowana wartość nie oznacza, że odwiązanie jest niedozwolone. Musisz w pełni wdrożyć odłączenie urządzenia od sterownika lub wyraźnie wyłączyć odłączenie urządzenia od sterownika.
Stosowanie rozwiązania polegającego na odłączeniu urządzenia od sterownika
Jeśli zdecydujesz się na pełne wdrożenie odłączania sterowników urządzeń, odłącz je prawidłowo, aby uniknąć wycieków pamięci lub zasobów oraz problemów z bezpieczeństwem. Urządzenie możesz powiązać z kierowcą, wywołując funkcję probe()
kierowcy i usuwając powiązanie z urządzeniem przez wywołanie funkcji remove()
kierowcy. Jeśli nie istnieje żadna funkcja remove()
, jądro nadal może usunąć powiązanie urządzenia, a rdzeń sterownika zakłada, że po odłączeniu urządzenia od urządzenia nie wymaga to czyszczenia. Sterownik, który nie jest powiązany z urządzeniem, nie musi wykonywać żadnych czynności związanych z czyszczeniem, jeśli są spełnione oba te warunki:
Wszystkie zasoby pozyskiwane przez funkcję
probe()
sterownika są pozyskiwane za pomocą interfejsów APIdevm_*()
.Urządzenie sprzętowe nie wymaga sekwencji wyłączania ani wyciszania.
W takim przypadku rdzeń sterownika odpowiada za zwalnianie wszystkich zasobów uzyskanych za pomocą interfejsów API devm_*()
. Jeśli którekolwiek z poprzednich stwierdzeń jest nieprawdziwe, sterownik musi wykonać czyszczenie (zwolnić zasoby i wyłączyć lub wyciszyć sprzęt) po odwiązaniu od urządzenia. Aby prawidłowo usunąć powiązanie modułu sterownika przez urządzenie, skorzystaj z jednej z tych opcji:
Jeśli sprzęt nie wymaga sekwencji zamykania ani wyciszania, zmień
devm_*()
interfejs API urządzenia, aby pobierać zasoby.Zrealizuj operację
remove()
w sterowniku w tej samej strukturze co funkcjaprobe()
, a potem wykonaj czynności porządkujące za pomocą funkcjiremove()
.
Wyraźnie wyłączyć odłączanie urządzenia od sterownika (niezalecane)
Jeśli chcesz wyraźnie wyłączyć usuwanie powiązania sterownika urządzenia, musisz zabronić tego procesu oraz zabraniać odłączania modułu.
Aby uniemożliwić usuwanie powiązania, ustaw flagę
suppress_bind_attrs
natrue
wstruct device_driver
sterownika. To ustawienie zapobiega wyświetlaniu plikówbind
iunbind
w katalogusysfs
sterownika. Plikunbind
umożliwia przestrzeni użytkownika wywołanie odwiązania sterownika od urządzenia.Aby zabronić wyładowywania modułu, upewnij się, że ma on wartość
[permanent]
w plikulsmod
. Jeśli nie użyjesz właściwościmodule_exit()
lubmodule_XXX_driver()
, moduł zostanie oznaczony jako[permanent]
.
Nie wczytuj oprogramowania układowego w ramach funkcji sondowania
Sterownik nie powinien wczytywać oprogramowania z poziomu funkcji .probe()
, ponieważ może nie mieć do niego dostępu, jeśli sterownik sprawdza to przed zamontowaniem systemu plików na podstawie pamięci flash lub trwałej pamięci masowej. W takich przypadkach interfejs API request_firmware*()
może się blokować przez długi czas, a potem zakończyć działanie, co może niepotrzebnie spowolnić proces uruchamiania. Zamiast tego odłóż wczytywanie oprogramowania układowego do momentu, gdy klient zacznie korzystać z urządzenia. Na przykład sterownik wyświetlacza może wczytywać oprogramowanie układowe, gdy wyświetlacz jest otwarty.
W niektórych przypadkach użycie oprogramowania .probe()
do wczytania oprogramowania może być dozwolone, na przykład w sterowniku zegara, który do działania wymaga oprogramowania, ale urządzenie nie jest dostępne w przestrzeni użytkownika. Możliwe są też inne odpowiednie zastosowania.
Wdrożenie sondowania asynchronicznego
Obsługuj i używaj asynchronicznego sondowania, aby korzystać z przyszłych ulepszeń, takich jak równoległe ładowanie modułów czy sondowanie urządzenia w celu przyspieszenia czasu uruchamiania, które mogą zostać dodane do Androida w przyszłych wersjach. Moduł sterownika, który nie korzysta z przeszukania asynchronicznego, może obniżyć skuteczność takich optymalizacji.
Aby oznaczyć sterownik jako obsługiwany i preferowany sondowanie asynchroniczne, ustaw pole probe_type
w elemencie struct device_driver
sterownika. Poniższy przykład pokazuje obsługę włączoną dla sterownika platformy:
static struct platform_driver acme_driver = {
.probe = acme_probe,
...
.driver = {
.name = "acme",
...
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
},
};
Aby sterownik działał z wykorzystaniem sondowania asynchronicznego, nie trzeba pisać specjalnego kodu. Podczas dodawania obsługi sond asynchronicznego należy jednak pamiętać o poniższych kwestiach.
Nie rób założeń dotyczących wcześniej zbadanych zależności. Sprawdź bezpośrednio lub pośrednio (większość wywołań platformy) i zwróć
-EPROBE_DEFER
, jeśli co najmniej jeden z dostawców nie jest jeszcze gotowy.Jeśli dodasz urządzenia podrzędne w ramach funkcji sondowania urządzenia nadrzędnego, nie zakładaj, że urządzenia podrzędne zostaną od razu sondowane.
Jeśli sonda ulegnie awarii, wykonaj prawidłową obsługę błędów i wyczyść dane (patrz Używanie wariantów interfejsu API devm_*()).
Nie zamawiaj sond urządzenia za pomocą parametru MODULE_SOFTDEP
Funkcja MODULE_SOFTDEP()
nie jest niezawodnym rozwiązaniem gwarantującym prawidłowe działanie sondowania urządzeń i nie należy jej używać z podanych niżej powodów.
Odroczona sonda. Podczas wczytywania modułu może zostać odroczona próba uzyskania informacji o urządzeniu, ponieważ jeden z jego dostawców nie jest gotowy. Może to spowodować niezgodność między kolejnością ładowania modułów a kolejnością sprawdzania urządzeń.
1 kierowca, wiele urządzeń Moduł sterownika może zarządzać określonym typem urządzenia. Jeśli system zawiera więcej niż 1 występowanie typu urządzenia, a poszczególne urządzenia mają różne wymagania dotyczące kolejności sondowania, nie możesz uwzględnić tych wymagań, stosując kolejność wczytywania modułów.
Asynchroniczne sondowanie. Moduły sterowników, które przeprowadzają sondowanie asynchroniczne, nie przeprowadzają sondowania urządzenia od razu po załadowaniu modułu. Zamiast tego drugi wątek obsługuje sondowanie urządzeń, co może prowadzić do niezgodności między kolejnością wczytywania modułu i kolejności sond urządzenia. Jeśli na przykład moduł głównego sterownika I2C wykonuje wyszukiwanie asynchroniczne, a moduł sterownika dotykowego zależy od PMIC na magistrali I2C, nawet jeśli sterownik dotykowy i sterownik PMIC są ładowane we właściwej kolejności, próba wyszukiwania sterownika dotykowego może zostać podjęta przed próbą wyszukiwania sterownika PMIC.
Jeśli masz moduły sterownika, które korzystają z funkcji MODULE_SOFTDEP()
, napraw je tak, aby nie korzystały z tej funkcji. Aby Ci pomóc, zespół Androida wprowadził zmiany, które umożliwiają jądrowi rozwiązywanie problemów z kolejnością bez używania MODULE_SOFTDEP()
. W szczególności możesz użyć funkcji fw_devlink
, aby zapewnić kolejność próbkowania, a (po zakończeniu próbkowania przez wszystkich konsumentów urządzenia) użyć funkcji wywołania zwrotnego sync_state()
do wykonania niezbędnych zadań.
W przypadku konfiguracji używaj instrukcji #if IS_ENABLED() zamiast #ifdef.
Użyj #if IS_ENABLED(CONFIG_XXX)
zamiast #ifdef CONFIG_XXX
, aby mieć pewność, że kod wewnątrz bloku #if
będzie nadal kompilować, nawet jeśli w przyszłości konfiguracja zmieni się na Tristate. Różnice są następujące:
#if IS_ENABLED(CONFIG_XXX)
przyjmuje wartośćtrue
, gdyCONFIG_XXX
ma wartość module (=m
) lub wbudowany (=y
).Wartość
#ifdef CONFIG_XXX
jest obliczana jakotrue
, gdy parametrCONFIG_XXX
ma wartość wbudowana (=y
), ale nie wtedy, gdy ma wartość moduł (=m
). Używaj tego tylko wtedy, gdy masz pewność, że chcesz uzyskać ten sam efekt, gdy konfiguracja ma wartość moduł lub jest wyłączona.CONFIG_XXX
Użyj odpowiedniego makra do kompilacji warunkowej.
Jeśli pole CONFIG_XXX
jest ustawione na moduł (=m
), system kompilacji automatycznie definiuje CONFIG_XXX_MODULE
. Jeśli sterownik jest kontrolowany przez CONFIG_XXX
i chcesz sprawdzić, czy jest kompilowany jako moduł, postępuj zgodnie z tymi wytycznymi:
W pliku C (lub dowolnym pliku źródłowym, który nie jest plikiem nagłówka) sterownika nie używaj
#ifdef CONFIG_XXX_MODULE
, ponieważ jest to niepotrzebnie restrykcyjne i powoduje błąd, jeśli nazwa pliku konfiguracyjnego zostanie zmieniona naCONFIG_XYZ
. W przypadku każdego pliku źródłowego, który nie jest nagłówkiem, a został skompilowany w moduł, system kompilacji automatycznie definiujeMODULE
dla zakresu tego pliku. Dlatego, aby sprawdzić, czy plik C (lub dowolny plik źródłowy bez nagłówka) jest kompilowany jako część modułu, użyj polecenia#ifdef MODULE
(bez prefiksuCONFIG_
).W plikach nagłówków ta sama kontrola jest trudniejsza, ponieważ pliki nagłówków nie są kompilowane bezpośrednio w pliki binarne, ale kompilowane jako część pliku C (lub innych plików źródłowych). W plikach nagłówków stosuj te reguły:
W przypadku pliku nagłówka, który używa funkcji
#ifdef MODULE
, wynik zależy od tego, z którego pliku źródłowego jest on używany. Oznacza to, że ten sam plik nagłówka w ramach tej samej kompilacji może mieć różne części kodu skompilowane dla różnych plików źródłowych (moduł, wbudowany lub wyłączony). Może to być przydatne, gdy chcesz zdefiniować makro, które ma się rozwijać w jeden sposób w przypadku kodu wbudowanego, a w inny w przypadku modułu.W przypadku pliku nagłówka, który musi być skompilowany w fragmentie kodu, gdy określony parametr
CONFIG_XXX
ma wartość module (niezależnie od tego, czy plik źródłowy, który go zawiera, jest modułem), plik nagłówka musi używać parametru#ifdef CONFIG_XXX_MODULE
.