Teoria

**Poniższy tekst będzie tłumaczony z oryginału (/Documents/CodingStyle w katalogu z kodem źródłowym w kernelu co najmniej 2.6.21). **

Styl kodowania kernela

Ten dokument opisuje preferowany styl kodowania kernela. Oczywiście styl kodowania jest rzeczą indywidualną i nikomu nie zamierzamy narzucać naszego poglądu, ale ten styl jest obowiązujący i czytany przez ciebie kod będzie z nim kompatybilny. W ostateczności powinieneś się z nim chociaż zapoznać.

Ale najpierw proponuję ci wydrukowanie kopii "GNU coding standards" i spalenie jej BEZ czytania. Po prostu zrób to, w symbolicznym geście.

Wcięcia

Tabulator jest elementem ośmioznakowym (8), dlatego wcięcia też są ośmioznakowe. Są podejmowane próby zmiany wcięć na 4 (nawet 2!) znaki głębokości, a są one próbą zmiany wartości PI na 3.

Uzasadnienie: Celem wcięć jest jasne i niezawodne pokazanie kiedy zaczynają się i kończą bloki kontrolne. Szczególnie, gdy siedzisz przed monitorem 20 godzin, będzie ci łatwo zrozumieć ideę dużych wcięć.

W skrócie 8-znakowe wcięcia ułatwiają czytanie kodu i ostrzegają przed błędami.

The preferred way to ease multiple indentation levels in a switch statement is
to align the "switch" and its subordinate "case" labels in the same column
instead of "double-indenting" the "case" labels. E.g.:

Proponowane tłumaczenie:
[
Preferowany sposób na wyeliminowanie zbyt dużych wcięć w konstrukcjach switch polega na wyrównaniu "switch" i odpowiednich etykiet "case" do tego samego poziomu zamiast podwójnych wcięć. Np.:
]

switch (suffix) {
case 'G':
case 'g':
        mem <<= 30;
        break;
case 'M':
case 'm':
        mem <<= 20;
        break;
case 'K':
case 'k':
        mem <<= 10;
        /* fall through */
default:
        break;
}

Nie pisz paru instrukcji w linii chyba że masz coś do ukrycia:

if (warunek) zrób_to;
  rób_to_za_każdym_razem;

Poza komentarzami, dokumentacją i Kconfig nie używamy spacji do robienia wcięć!

Używaj edytora który nie zostawia białych znaków na końcach linii.

Przerywanie długich linii i łańcuchów.

Styl kodowania dotyczy czytelności i zarządzania przy użyciu popularnych narzędzi.

Limit długości linii wynosi 80 kolumn i jest nieprzekraczalny. Wyrażenia, których długość przekracza 80 znaków będą łamane na sensowne kawałki. Potomkowie są zawsze znacznie krótsi od rodziców i przesuwani są trochę w prawo. To samo dotyczy się funkcji z dużą ilością argumentów. Długi stringi są również łamane do krótszych.

void fun(int a, int b, int c)
{
        if (condition)
                printk(KERN_WARNING "Warning this is a long printk with "
                                                "3 parameters a: %u b: %u "
                                                "c: %u \n", a, b, c);
        else
                next_statement;
}

Ułożenie nawiasów i spacji

Kolejną rzeczą która wiąże się ze stylem stosowanym w C jest umiejscowienie nawiasów. Nie licząc wcięć, jest kilka powodów natury technicznej by wybrać dany sposób umiejscowienia nawiasów a nie inny. Aczkolwiek preferowane jest, co pokazuje Kernighan i Ritchie (Linuksowi guru), by umiejscowić nawias otwierający jako ostatni w linijce, a nawias zamykający jako pierwszy. Pokazane jest to w poniższym przykładzie.

if (x is true) {
        we do y

}

Stosuje się to do wszystkich wyrażeń blokowych nie będących funkcjami(if, switch, for, while, do). Np.:

switch (action) {
case KOBJ_ADD:
        return "add";
case KOBJ_REMOVE:
        return "remove";
case KOBJ_CHANGE:
        return "change";
default:
        return NULL;
}

Jednakże, w niektórych przypadkach odchodzi się od tej reguły, a dokładnie chodzi o przypadek funkcji. Pisząc jakąś funkcję nawias otwierający umieszczasz jako pierwszy w nowej linijce, czyli:

int function(int x)
{
        body of function
}

'Heretycy' z całego świata wytykają niekonsekwencję, ale wszyscy poprawnie myślący ludzie wiedzą, że po pierwsze K i R mają rację, a po drugie K i R mają rację. Poza tym, funkcje różnią się od innych bloków kontrolnych (w C nie można ich zagnieżdżać).

Zauważ, że nawias zamykający jest jedynym znakiem w linijce, _poza_ sytuacją kiedy za nim znajduje się kontynuacja wyrażenia, do którego należy, np. "while" w wyrażeniu 'do' albo "else" w wyrażeniu 'if' (wyrażenie warunkowe), czyli tak jak tutaj:

do {
         body of do-loop
} while (condition);

oraz

if (x == y) {
        ..
} else if (x > y) {
        ...
} else {
        ....
}

Uzasadnienie: K&R.

Zauważ również że takie ułożenie nawiasów minimalizuje ilość pustych (lub prawie pustych) linijek bez straty czytelności. Tak więc, jako że zapas nowych linii na twoim ekranie nie jest nieskończony (mowa tutaj o 25-liniowych terminalach), masz do dyspozycji więcej pustych linii by umieszczać w nich komentarze.

Spacje

Umiejscowienie spacji w kodzie kernela zależy głównie od użycia funkcji lub słów kluczowych. Używaj spacji po większości słów kluczowych. Wyjątkami godnymi uwagi są są sizeof, typeof, alignof i attribute, które wyglądają w pewien sposób jak funkcje (są często używane z zaokrąglonymi nawiasami w Linuksie, chociaż nie jest to wymagane przez język, np.: "sizeof info" po deklaracji struct fileinfo info).

Tak więc używaj spacji po poniższych słowach kluczowych:

if, switch, case, for, do, while

ale już nie po sizeof, typeof, alignof lub attribute. Np.:

s = sizeof(struct file);

Nie dodawaj spacji w obrębie wyrażenia w nawiasach okrągłych. Np.: (Tak nie rób)

s = sizeof( struct file );

When declaring pointer data or a function that returns a pointer type, the
peferred use of '*' is adjacent to the data name or function name and not
adjcent to the type name. Examples:

Proponowane tłumaczenie:
[
Kiedy deklarujesz wskaźnik na dane lub funkcję która zwraca wskaźnik, preferowanym użyciem '*' jest brak odstępu między nazwą a '*' oraz odstęp pomiędzy typem wskaźnika a '*'. Przykłady:
]

char *linux_banner;
unsigned long long memparse(char *ptr, char **retptr);
char *match_strdup(substring_t *s);

Use one space around (on each side of) most binary and ternary operators,
such as any of these:

Proponowane tłumaczenie:
[
Używaj spacji przed i po dwu- i trójargumentowych operatorach, takich jak poniższe:
]

=  +  -  <  >  *  /  %  |  &  ^  <=  >=  ==  !=  ?  :

but no space after unary operators:

Proponowane tłumaczenie:
[
Ale już nie po operatorach jednoargumentowych.
]

&  *  +  -  ~  !  sizeof  typeof  alignof  __attribute__  defined

no space before the postfix increment & decrement unary operators:

Proponowane tłumaczenie:
[
Nie używaj spacji również przed operatorami postinkrementacji i postdekrementacji:
]

++  --

no space after the prefix increment & decrement unary operators:

Proponowane tłumaczenie:
[
A także po operatorach preinkrementacji i predekrementacji:
]

++  --

and no space around the '.' and "->" structure member operators.

Proponowane tłumaczenie:
[
Jak i dookoła '.' i '->' czyli operatorów odwołania do składowej struktury.
]

Nazwy

C jest językiem "spartańskim" i takie też powinny być twoje nazwy. W przeciwieństwie do programistów Modula-2 czy Pascala programiści C nie używają miłych nazw jak na przykład ThisVariableIsTemporaryCounter ("TaZmiennaJestTymczasowymLicznikiem"). Programiści C nazwaliby tą zmienną "tmp", ponieważ tak łatwiej napisać i wcale nie trudniej zrozumieć.

Jednakże, podczas gdy nazwy wykorzystujące metodę mieszania wielkości znaków nie są mile widziane, opisowe nazwy dla globalnych zmiennych są koniecznością. Nazwa "foo" dla zmiennej globalnej to zbrodnia.

Globalne zmienne (wykorzystywane tylko jeżeli naprawdę ich potrzebujesz) należy opatrzyć nazwami opisowymi. Jeśli masz na przykład funkcję, która liczy liczbę aktywnych użytkowników, powinieneś nazwać ją tak: "count_active_users()" lub podobnie, nie powinieneś używać nazwy "cntusr()".

Zaszywanie typu funkcji czy zmiennej w nazwie (notacja węgierska) jest łamaniem sobie głowy - kompilator zna typ zmiennej i bez tego może to zawsze sprawdzić, a myli to tylko programistę. Nic dziwnego że Microsoft robi błędy. :)

Lokalne zmienne powinny mieć krótkie nazwy. Jeżeli deklarujes jakąś nieistotną zmienną służącą za licznik, powinna nazywać się "i". Nazywanie jej "loop_counter" jest nieekonomiczne, jeżeli nie ma szansy by była źle zrozumiana. Podobnie, "tmp" może służyć za dowolny typ zmiennej używany do przechowywania tymczasowej wartości.

Jeżeli obawiasz się wymieszania lokalnych nazw zmiennych, posiadasz kolejny problem, jest to tak zwany syndrom FGHI (function-growth-hormone-imbalance). Patrz rozdział o Funkcjach.

Typedefs

Please don't use things like "vps_t".

It's a _mistake_ to use typedef for structures and pointers. When you see a

Proponowane tłumaczenie:
[
Proszę, nie używaj nazw takich jak "vps_t" .

Użycie typedef dla struktur i wskaźników to pomyłka . Kiedy widzisz:
]

vps_t a;

in the source, what does it mean?

In contrast, if it says

Proponowane tłumaczenie:
[
w kodzie, skąd możesz wiedzieć, co to znaczy?

Jeśli natomiast widzisz:
]

struct virtual_container *a;

you can actually tell what "a" is.

Proponowane tłumaczenie:
[
możesz powiedzieć czym "a" naprawdę jest.
]

Lots of people think that typedefs "help readability". Not so. They are
useful only for:

Proponowane tłumaczenie:
[
Dużo ludzi myśli że typedef zwiększa czytelność. Niezupełnie. typedef jest pomocny tylko do:
]

(a) totally opaque objects (where the typedef is actively used to _hide_
what the object is).

Example: "pte_t" etc. opaque objects that you can only access using
the proper accessor functions.

NOTE! Opaqueness and "accessor functions" are not good in themselves.
The reason we have them for things like pte_t etc. is that there
really is absolutely _zero_ portably accessible information there.

Proponowane tłumaczenie:
[
(a) totalnego zaciemnienia obiektu - (typedef w tym przypadku jest użyty do schowania informacji o typie obiektu)

Przykład: "pte_t" etc. zaciemnia obiekty tak że masz tylko dostęp do nich przez odpowiednie funkcje "dostępu" (ang. accessor functions).

ZAUWAŻ! Zaciemnianie i "odpowiednie funkcje dostępu" nie są dobre same w sobie. Powód dla którego jstnieją żeczy takie jak "pte_t" etc. jest taki że nie
przechowują one żadnych informacji przenośnych z jednej architektury na inną.
]

(b) Clear integer types, where the abstraction _helps_ avoid confusion
whether it is "int" or "long".

u8/u16/u32 are perfectly fine typedefs, although they fit into
category (d) better than here.

NOTE! Again - there needs to be a _reason_ for this. If something is
"unsigned long", then there's no reason to do

Proponowane tłumaczenie:
[
(b) Wyjaśniają liczby całkowite, kiedy abstrakcja pomaga omijać zamieszanie związane kiedy typ jest "int" lub "long".

u8/u16/u32 są perfekcyjnymi dobrymi typedefsami, pomimo tego one lepiej pasują do podpunktu (d) niż tutaj.

ZAUWAŻ! Znowu musi być powód dla użycia. Jeżeli coś jest typu unsigned long, to tego powodu nie ma!
]

typedef unsigned long myflags_t;

but if there is a clear reason for why it under certain circumstances
might be an "unsigned int" and under other configurations might be
"unsigned long", then by all means go ahead and use a typedef.

Proponowane tłumaczenie:
[
ale jeżeli jest jasny powód że czasami może być "unsigned int" a przy innej konfiguracji "unsigned long", wtedy oznacza to że powinieneś użyć typedef.
]

(c) when you use sparse to literally create a _new_ type for
type-checking.

Proponowane tłumaczenie:
[
(c) kiedy tworzysz nowy typ dla lepszego sprawdzania typów.
]

(d) New types which are identical to standard C99 types, in certain
exceptional circumstances.

Although it would only take a short amount of time for the eyes and
brain to become accustomed to the standard types like 'uint32_t',
some people object to their use anyway.

Therefore, the Linux-specific 'u8/u16/u32/u64' types and their
signed equivalents which are identical to standard types are
permitted — although they are not mandatory in new code of your
own.

When editing existing code which already uses one or the other set
of types, you should conform to the existing choices in that code.

Proponowane tłumaczenie: (niepewne)
[
(d) Nowe typy które są identyczne ze standardem C99, z paroma odstępstawmi.

Pomimo tego że przyswojenie sobie typów "uint32_t" zajęło by mało czasu jednak niektórzy ludzie nie chcą ich używać.

Dlatego też specyficzne dla Linuksa typy 'u8/u16/u32/u64' i ich odpowiedniki ze znakiem które są identyczne ze standardem są dopuszczalne, aczkolwiek nie są
obowiązujące w twoim nowym kodzie (?!? although they are not mandatory in new code of your own. ?!?)

Kiedy edytujesz istniejący kod w którym występują różne zestawy typów powinieneś wybrać obowiązujący zestaw.
]

(e) Types safe for use in userspace.

In certain structures which are visible to userspace, we cannot
require C99 types and cannot use the 'u32' form above. Thus, we
use __u32 and similar types in all structures which are shared
with userspace.

Proponowane tłumaczenie:
[
(e) Typy beżpieczne dla użycia w przestrzeni użytkownika (ang. userspace)

W pewnych strukturach widocznych w przestrzeni użytkownika, nie możemy wymagać typów C99 i nie możemy używać formy 'u32'. Dlatego używamy __32
i podobnych typów we wszystkich struktórach widocznych w przestrzeni użytkownika.
]

Maybe there are other cases too, but the rule should basically be to NEVER
EVER use a typedef unless you can clearly match one of those rules.

In general, a pointer, or a struct that has elements that can reasonably
be directly accessed should _never_ be a typedef.

Proponowane tłumaczenie:
[
Możliwe że są jeszcze inne przypadki ale zasada jest prosta: nigdy przenigdy nie używaj typedef chyba że potrafisz jasno przypasować swuj przypadek do jednego z powyższych.

Ogulnie, wskaźnik czy struktura która ma elementy do których , z pewnych powodów, trzeba uzyskiwać dostęp bezpośredni nie powinny nigdy być zdefiniowane przez typedef.
]

Funkcje

Nazwy funkcji powinny być krótkie, konkretne i wykonywać jakąś konkretną robotę. Powinny mieścić się na 2 ekranach (rozmiar ekranu(ISO/ANSI) to 80*24 jak powszechnie wiadomo), robić jedno, ale robić to dobrze.

Maksymalna długość nazwy funkcji jest odwrotnie proporcjonalna do złożoności i poziomu wcięcia tej funkcji. Więc, jeżeli masz teoretycznie prostą funkcję, której nazwa jest długa ( zawiera znaki różnej wielkości ), a wykonujesz w niej dużo małych instrukcji dla wielu różnych przypadków, to nazwanie takiej funkcji przy pomocy długiego ciągu znaków jest OK.

Inną miarą wielkości funkcji jest liczba zmiennych lokalnych w niej występujących. Ich wielkość nie powinna przekraczać zakresu 5-10. Ludzki mózg może z łatwością śledzić około 7 różnych rzeczy, przy odrobinę większej ilości zaczyna się gubić. Wiemy, że jesteś błyskotliwy, ale może po dwóch tygodniach nie będziesz wiedzieć co zrobiłeś. Więc w takim wypadku doradzamy przemyślenie działanie funkcji i podziel jej na mniejsze części.

W plikach z kodem źródłowym, oddzielaj poszczególne funkcje jedną pustą linijką. Jeżeli funkcja jest eksportowana, makro EXPORT* powinno nastąpić zaraz po nawiasie zamykającym funkcję. Np.:

int system_is_up(void)
{
        return system_state == SYSTEM_RUNNING;
}
EXPORT_SYMBOL(system_is_up);

W prototypach funkcji, dołączaj nazwy argumentów funkcji oraz ich typy. Pomimo tego, że nie jest to wymagane w języku C, jest to preferowane w kodzie Linuksa, gdyż jest to prosty sposób na dodanie istotnych informacji dla czytającego.

Scentralizowany powrót z funkcji

Aczkolwiek potępiany przez pewnych ludzi, odpowiednik instrukcja goto jest często używany przez kompilatory do wykonywania skoków bezwarunkowych.
Instrukcja goto jest pomocna gdy funkcje powracają w paru miejscach(zależnie np. od błędów) i parę codziennych prac (takich jak sprzątanie) musi być wykonanych.???

Pomimo porzucenia przez niektórych ludzi, odpowiednik funkcji goto() często używany jest przez programistów jako element skoku bezwarunkowego. Rozkaz goto() przydatny jest zazwyczaj gdy funkcja wychodzi z wielu lokacji i należy wykonać jakieś szczególne zadanie jak na przykład oczyszczenie.

Przesłanki ku temu są następujące:

- bezwarunkowe skoki są łatwiejsze do zrozumienia i wykonania,
- zmniejszona jest rozległość sieci,
- ogranicza to błędy powstające przy modyfikacjach i nie zaktualizowaniu poszczególnych punktów wyjściowych,
- oszczędza pracę kompilatora przez wyrzucenie zbytecznego kodu ;)

int fun(int a)
{
        int result = 0;
        char *buffer = kmalloc(SIZE);

        if (buffer == NULL)
                return -ENOMEM;

        if (condition1) {
                while (loop1) {
                        ...
        }
                result = 1;
                goto out;
    }
        ...
out:
        kfree(buffer);
        return result;
}

Komentarze

Komentarze to dobra rzecz, ale istnieje niebezpieczeństwo robienia ich w zbyt dużej ilości. NIGDY przez komentarz, nie próbuj tłumaczyć działania kodu, znacznie lepiej napisać coś co po prostu działa. Poza tym komentowanie źle napisanego kodu jest stratą czasu.

Generalnie ważne jest, żebyś pisał Co twój kod robi, a nie jak to robi. Postaraj się również unikać dodawania komentarzy do ciała funkcji. Jeśli funkcja jest na tyle rozbudowana, że uznasz za stosowne komentowanie jej części wróć do 5 rozdziału. Oczywiście możesz robić małe komentarze lub ostrzeżenia do jakichś szczególnych sprytnych (lub odwrotnie) rozwiązań, ale staraj się unikać nadmiaru. Zamiast tego staraj się dodawać komentarze przy nazwie funkcji, opisując CO ona robi, oraz możliwie, PO CO to robi.

Podczas komentowania funkcji API kernela, użyj formatu kerneldoc. By zdobyć więcej informacji przeczytaj:

  • Documentation/kernel-doc-nano-HOWTO.txt
  • Documnetation/scripts/kernel-doc.

Obowiązującym stylem komentowania w Linuksie jest C89 "/*……*/".
Nie używaj zatem stylu C99 "//……".

Preferowanym stylem dla długich (wielo-linijkowych) komentarzy jest:

/*
         * This is the preferred style for multi-line
         * comments in the Linux kernel source code.
         * Please use it consistently.
         *
         * Description:  A column of asterisks on the left side,
         * with beginning and ending almost-blank lines.
         */

Ważne jest również abyś komentował dane, czy są to typy wbudowane czy nie. Ważne jest również abyś deklarował jeden obiekt (nie używaj przecinków do deklarowania wielu obiektów na raz [np.: int a,b,c; źle]) w jednej linii co pozostawi Ci trochę miejsca na komentarz wyjaśniający użycie do każdego obiektu.

Namieszałeś ?

W porządku, my wszyscy mieszamy. Pewnie twój przyjaciel ( doświadczony, używający od dłuższego czau Uniksa) powiedział Ci że "GNU emacs" automatycznie formatuje dla Ciebie źródła programów napisanych w C. Ponadto zauważyłeś, że rzeczywiście robi to, ale ustawienia standardowe, których używa są mniej niż wymagane ( w rzeczywistości, są gorsze niż przypadkowe wpisanie czegoś - nawet nieskończona liczba małpek piszących w GNU emacs nigdy nie napisze dobrego programu (dosłowne tłumaczenie :-) ) ).

Problem możesz rozwiązać w dwojaki sposób: albo pozbyć się GNU emacs lub doprowadzić do tego by używał sensowniejszych wartości. Aby zrobić to drugie, możesz wkleić poniższy tekst do swojego pliku .emacs:

(defun linux-c-mode ()
"C mode with adjusted defaults for use with the Linux kernel."
(interactive)
(c-mode)
(c-set-style "K&R")
(setq tab-width 8)
(setq indent-tabs-mode t)
(setq c-basic-offset 8))

Te polecenia zdefiniują polecenie M-x linux-c-mode. Kiedy hackujesz jakiś moduł jądra i jeśli dodasz łańcuch

-*- linux-c -*-

gdzieś w pierwszych dwóch linijkach hackowanego pliku, emacs automatycznie przestawi się do tego trybu.
Również możesz chcieć dodać

(setq auto-mode-alist (cons '("/usr/src/linux.*/.*\\.[ch]$" . linux-c-mode)
                        auto-mode-alist))

do twojego pliku .emacs jeżeli chcesz przełączać się do tego trybu automatycznie kiedy edytujesz /usr/scr/linux. Oczywiście możesz ustawić dowolny inny katalog zamiast /usr/src/linux.

Jednakże jeżeli nie uda ci się zmusić emacsa do poprawnego formatowania, to nie wszystko jest stracone! Wykorzystaj "indent".

Teraz znów, GNU indent posiada takie same bezmyślne ustawienia standardowe jak GNU emacs, dlatego musisz zmienić kilka opcji korzystając z linii poleceń. Jednakże, nie jest tak źle jak mogłoby się wydawać, ponieważ nawet twórcy GNU indent poznali się na mocy K&R (ludzie z GNU nie są źli, po prostu w tej sprawie kierują się w inną stronę), więc dodaj podaj indent opcje "-kr -i8" (opiera się to na "K&R, 8 character indents") lub użyj "scripts/Lindent", który wcina się w najnowszy styl. "indent" posiada wiele opcji i jeśli chodzi o przeformatowanie komentarzy, możesz chcieć spojrzeć w stronę man. Ale zapamiętaj: "indent" nie stanowi poprawki do złego kodowania.

Pliki konfiguracyjne

Dla opcji konfiguracyjnych (arch/xxx/Kconfig, i pozostałych plików Kconfig) używamy nieco innych wcięć.

Tekst pomocy powinien być wcięty dwoma spacjami.

if CONFIG_EXPERIMENTAL
        tristate CONFIG_BOOM
        default n
        help
          Apply nitroglycerine inside the keyboard (DANGEROUS)
        bool CONFIG_CHEER
        depends on CONFIG_BOOM
        default y
        help
          Output nice messages when you explode
endif

Generalnie, CONFIG_EXPERIMENTAL powinien otaczać wszystkie opcje nie uznane za stabilne. Wszystkie opcje, o których wiemy że niszczą dane (np. experymentalny zapis dla systemów plików) powinny być oznaczone (DANGEROUS), inne eksperymentalne opcje oznaczamy jako(EXPERIMENTAL).

Struktury danych

Struktury danych, które posiadają widoczne wyjście, tworzone i niszczone w jednowątkowym środowisku powinny zawsze posiadać liczniki odniesień. Kolekcjonowanie śmieci w jądrze nie istnieje ( a kolekcjonowanie ich poza nim jest wolne i nieefektywne ), co znaczy że kategorycznie musisz zliczać odniesienia wszystkich swoich użyć.

Zliczanie odniesień umożliwia omijanie blokad i pozwala na dostęp równoległy użytkowników do struktur. Nie musisz martwić się o struktury, które nagle przestały istnieć przez to, że zostały uśpione lub robią coś innego przez jakiś czas.

Zauważ, że blokowanie nie stanowi zastępstwa dla zliczania odnośników. Blokowanie używane jest do utrzymywania spójności danych, zaś zliczanie wątku jest techniką zarządzania pamięcią. Zazwyczaj wymagane są obie i nie powodują wzajemnej dezorientacji.

Wiele struktur danych w rzeczywistości może mieć dwa poziomy zliczania odniesień, gdy mamy do czynienia z użytkownikami różnych "klas". Licznik podklas zlicza liczbę użytkowników tej podklasy i dekrementuje globalny licznik tylko raz, kiedy licznik podklasy dojdzie do zera.

Różne przykłady wielopoziomowego zliczania odniesień możesz znaleźć w części zajmującej się zarządzaniem pamięci w jądrze ( Struktury: mm_struct: mm_user i mm_count ) oraz kodzie systemu plików ( Struktury super_block: s_count i s_active).

Pamiętaj: jeśli inny wątek może znaleźć twoją strukturę danych i nie posiadasz licznika odniesień, jesteśmy niemal pewni, że posiadasz błąd.

Makra, typy wyliczeniowe i RTL

Nazwy makr definiujące stałe i etykiety typów wyliczeniowych pisane są wielką literą.

#define CONSTANT 0x12345

Preferowane zastosowanie typów wyliczeniowych to definicja kilku powiązanych stałych.

Pisane dużymi literami nazwy makr jest doceniane ale nazwy podobnych makr mogą być pisane małymi literami.
Ogólnie, preferowane jest wykorzystanie funkcji typu inline nad podobnymi makrami.
Makra z wieloma stanami nie powinny być zamknięte ( jak na poniższym przykładzie ):

#define macrofun(a, b, c)                       \
           do {                                         \
                   if (a == 5)                         \
                           do_this(b, c);            \
           } while (0)

Rzeczy, których należy unikać przy tworzeniu makr:

1) makra które mają wpływ na kontrolę strumienia:

#define FOO(x)                                       \
           do {                                           \
                   if (blah(x) < 0)                     \
                          return -EBUGGERED;       \
           } while(0)

są bardzo złym pomysłem. Wygląda to jak funkcja wezwania ale z wzywanej funkcji; nie przerywaj wewnętrznego parser tym, którzy będą czytać kod.

2) makra, które zależą od lokalnej zmiennej o magicznej nazwie:

#define FOO(val) bar(index, val)

może wyglądać że to jest dobrze, ale jest strasznie mylące kiedy ktoś czyta kod i jest on podatny na uszkodzenie przez pozornie niewinne zmiany.

3) makra z argumentami, które użyte są jako l-values:

FOO(x) = y;

odgryzą się na tobie jeśli ktoś na przykład zmieni FOO na funkcję typu inline.

4) zapominanie o precedensie: makra definiujące stałe wykorzystując wyrażenie muszą "otoczyć" wyrażenie nawiasem klamrowym. Wystrzegaj się podobnych spraw kiedy używasz makr wykorzystujących parametry.

#define CONSTANT 0x4000
#define CONSTEXP (CONSTANT | 3)

Podręcznik man cpp opisuje makra wystarczająco. Podręcznik man gcc również przedstawia RTL, które często używane jest w jądrze przy wykorzystywaniu języka asembler.

Wypisywanie wiadomości kernela

Deweloperzy jądra znani są z umiejętności poprawnego pisania. Zwracaj więc uwagę na pisownię komunikatów jądra by zrobić dobre wrażenie. Nie używaj zniekształconych słów jak "dont", zamiast tego postaraj się używać "do not" lub "don't". Wiadomości pochodzące z kernela nie muszą się kończyć kropką.

Wypisywanie liczb w nawiasach okrągłych nic nie daje i powinno być omijane.

Alokacja pamięci

Kernel zapewnia/dostarcza następujące funkcje ogólnego przeznaczenia alokujące pamięć:
kmalloc(), kzalloc(), kcalloc() oraz vmalloc().
Przejrzyj dokumentację API kernela w celu uzyskania dodatkowych informacji o nich.

Poniżej przedstawiamy preferowany sposób na przekazywanie rozmiaru struktury:

p = kmalloc(sizeof(*p), ...);

Alternatywna forma kiedy nazwa jest wykorzystana wcześniej obniża czytelność i wprowadza okazję dla błędów kiedy typ zmiennej się zmienił ale odpowiedni rozmiar przesłany do alokatora pamięci nie.

//These is wrong!!!!!!!
t=sizeof(*p);
// ... jakieś instrukcje
p=kmaloc(t, ...);
//These is wrong!!!!!!!

Jawna konwersja zwracanej wartości, która jest wskaźnikiem typu void jest zbyteczna. Konwersja wskaźnika typu void na jakikolwiek inny typ wskaźnika jest zagwarantowana przez język programowania C.

Inline'owa zaraza

Często pojawia się błędne postrzeganie, że gcc posiada magiczną opcję przyśpieszającądziałanie zwaną "inline". Podczas gdy inline często może przyśpieszyć działanie programu (na przykład jako sposób na zamienianie makr (zwrot macros), patrz rozdział 11), często może działać dokładnie odwrotnie. Zbyt obfite używanie inline prowadzi do o wiele większego jądra, które z kolei spowalnia system, przez zbyt dużą ilość zajmowanej pamięci czyli po prostu mniejszej ilości pamięci na wymianę stron.

Użycie inline jest dobrym posunięciem jedynie gdy funkcja nie posiada więcej niż 3 linijki. Wyjątkiem od tej reguły są wypadki gdy wiem, że użyte parametry są stałe w czasie kompilacji i rezultatem tej stałości jest to, że ty wiesz że kompilator będzie mógł zoptymalizować większość twoich funkcji podczas kompilacji. Dobrym przykładem będzie poznana później funkcja kmalloc() inline.

Często ludzie kłócą się o to, że nadawanie funkcjom, które są statyczne i używane wyłącznie raz, typu inline jest zawsze dobrym rozwiązaniem. Podczas gdy jest to technicznie poprawne, gcc potrafi dodać zwrot inline do takich funkcji automatycznie bez żadnej pomocy, ale już nie jest łatwo skasować słowo inline przez nas dodane gdy inny programista postanowi użyć funkcji.

Function return values and names Funkcje zwracające wartości i nazwy.

Functions can return values of many different kinds, and one of the
most common is a value indicating whether the function succeeded or
failed. Such a value can be represented as an error-code integer
(-Exxx = failure, 0 = success) or a "succeeded" boolean (0 = failure,
non-zero = success).

Proponowane tłumaczenie:
[
Funkcje mogą zwracać wartości różnego rodzaju, i jedną z najpopularniejszych jest wartość określająca kiedy funkcja zakończyła się z błędem a kiedy poprawnie. Takie wartości mogą być reprezentowane przez kod błędu (ang. error-code integer) (-Exx = porażka, 0 = sukces) lub jako wartość logiczna (ang. "succeeded" boolean) (0 = porażka, 0 != sukces (gdy wartość różna od zera)).
]

Mixing up these two sorts of representations is a fertile source of
difficult-to-find bugs. If the C language included a strong distinction
between integers and booleans then the compiler would find these mistakes
for us… but it doesn't. To help prevent such bugs, always follow this
convention:

Proponowane tłumaczenie:
[
Mieszanie tych dwóch powyższych rodzajów reprezentacji jest idealnym źródłem trudnych do wytropienia błędów. Jeżeli kompilator C rozrużniał silniej typ całkowity od logicznego wychwycił by takie pomyłki… ale tak nie jest. Aby pomóc zapobiegać takim błędom zawsze trzymaj się poniższej konwencji:
]

If the name of a function is an action or an imperative command,
the function should return an error-code integer. If the name
is a predicate, the function should return a "succeeded" boolean.

Proponowane tłumaczenie:
[
Jeżeli nazwa funkcji zawiera akcję lub rozkaz, funkcja powinna zwracać kod błędu. Jeżeli nazwa funkcji jest twierdzeniem (ang. predicate), funkcja powinna zwracać
wartość logiczną.
]

For example, "add work" is a command, and the add_work() function returns 0
for success or -EBUSY for failure. In the same way, "PCI device present" is
a predicate, and the pci_dev_present() function returns 1 if it succeeds in
finding a matching device or 0 if it doesn't.

Proponowane tłumaczenie:
[

]Na przykład, "add work" jest komędą, i funkcja add_work() powinna zwracać 0 dla sukcesu lub _EBUSY dla porażki. Na tej samej zasadzie, "PCI device present" jest twierdzeniem, i funkcja pci_dev_present() powinna zwrócić 1 jeżeli znalazła pasujące urządzenie lub 0 jeżeli urządzenia nie znalazła.

All EXPORTed functions must respect this convention, and so should all
public functions. Private (static) functions need not, but it is
recommended that they do.

Proponowane tłumaczenie:
[
Wszystki EXPORTowane funkcje muszą respektować tą konwencję, tak samo jak funkcje publiczne. Prywatne (static) funkcje nie muszą, ale konwencja jest dla nich rekomendowana.
]

Functions whose return value is the actual result of a computation, rather
than an indication of whether the computation succeeded, are not subject to
this rule. Generally they indicate failure by returning some out-of-range
result. Typical examples would be functions that return pointers; they use
NULL or the ERR_PTR mechanism to report failure.

Proponowane tłumaczenie:
[
Funkcje które zwracają rzeczywisty wynik z obliczeń, a nie wartość oznajmiającą sukces czy porażkę, nie podlegają tej zasadzie. Generalnie oznajmiają one porażkę przez wartość z-poza-zakresu (ang. out-of- range). Typowymi funkcjami mogą być funkcje które zwracają wskaźniki; używają one mechanizmów NULL lub ERR_PTR do informowania o porażce.
]

Nie odkrywaj już raz odkrytych makr!

Plik nagłówkowy /include/kernel.h zawiera ogrom makr, które powinny być używane zamiast implementowania ich samemu. Na przykład jeżeli potrzebujesz wyliczyć długość tablicy skorzystaj z poniższego makra:

#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))

Podobnie, jeżeli potrzebujesz wyliczyć rozmiar składnika struktury, użyj:

#define FIELD_SIZEOF(t, f) (sizeof(((t*)0)->f))

Poza tym są jeszcze makra min() i max() które sprawdzają typ jeżeli ich potrzebujesz. Czuj się wolny w przeglądaniu pliku nagłówkowego, żeby zobaczyć co jeszcze jest zdefiniowanego aby nie otwierać raz otwartych drzwi.

Powrót do Spisu treści.

O ile nie zaznaczono inaczej, treść tej strony objęta jest licencją Creative Commons Attribution-Share Alike 2.5 License.