Portál AbcLinuxu, 25. dubna 2024 03:37

Jaderné noviny – 10. 12. 2014: Otevření začleňovacího okna verze 3.19

18. 1. 2015 | Tadeáš Pelech
Články - Jaderné noviny – 10. 12. 2014: Otevření začleňovacího okna verze 3.19  

Stav vydání jádra. Citát týdne. Otevírá se začleňovací okno verze 3.19. Připojení programů eBPF k soketům. Rozhraní iov_iter.

Obsah

Stav vydání jádra

link

Je otevřené začleňovací okno 3.19, takže není žádné aktuální vývojové jádro. Do hlavního stromu začaly proudit opravy pro vývojový cyklus 3.19; podrobnosti viz samostatný článek níže.

Verze jádra 3.18 byla vydána dne 7. prosince (oznámení).

Stabilní aktualizace: Stabilní verze jádra 3.17.5 byla vydána 7. prosince s následujícím komentářem: „Nikdo by ji neměl používat“; místo ní by se měla použít verze 3.17.6 vydaná bezprostředně po ní, která obsahuje důležité odvolání oprav. K dispozici jsou také verze 3.14.26 a 3.10.62.

Citát týdne

link

Microsoft poskytne integrovaný ovladač a dodavatelé mají možnost ho použít nebo si certifikovat vlastní pomocí WHQL, což je trochu jako volba mezi zmrzlinou zdarma a bušením hlavou do prkna plného hřebíků.

Alan Cox (díky Vishalu Vermovi).

Otevírá se začleňovací okno verze 3.19

link

V době vzniku tohoto článku se začalo poněkud pomalu rozjíždět začleňovací okno vývojového cyklu verze 3.19; prozatím bylo do hlavního repozitáře přijato necelých 2 000 sad změn bez začlenění. Jak je patrné z níže uvedených seznamů, i to stačí na to, aby si do jádra našlo cestu několik nových zajímavých zdrojových kódů.

Mezi změny ve verzi 3.19 viditelné uživatelem patří:

Mezi změny viditelné pro vývojáře jádra patří:

Pokud bude zachován obvyklý postup, začleňovací okno zůstane otevřené do 21. prosince, kdy bude uvolněna první prepatch verze 3.19. LWN bude – jako obvykle – sledovat změny začleněné během tohoto okna v dalších článcích.

Připojení programů eBPF k soketům

link

V předchozích vývojových cyklech jádra jsme byli svědky přidání subsystému rozšířeného filtru paketů Berkeley (eBPF) do jádra. Od verze 3.18 však může program uživatelského prostoru načíst program eBPF, ale nedokáže ho spustit v žádném užitečném kontextu; programy lze načíst a ověřit, ale nic víc. Vývojář eBPF Alexej Starovojtov samozřejmě předpokládá, že tento subsystém bude hrát významnější roli. Jádro 3.19 by mělo zahrnovat novou sadu oprav, která poprvé předvede schopnosti, které má Alexej na mysli.

Hlavní funkcí, která má být přidána ve verzi 3.19, je možnost připojit programy eBPF k soketům. Operace probíhají tak, že se nejprve nastaví program eBPF v paměti, pak se pomocí nového systémového volání bpf() (od verze 3.18) nahraje program do jádra a získá se popisovač souboru, který na něj odkazuje. Program lze potom připojit k soketu pomocí nové možnosti SO_ATTACH_BPF setsockopt():

    setsockopt(socket, SOL_SOCKET, SO_ATTACH_BPF, &fd, sizeof(fd));

Kde socket představuje příslušný soket a fd obsahuje popisovač souboru pro načtení programu eBPF.

Program se po načtení spustí na každý paket, který se objeví na daném soketu. V současnosti jsou dostupné funkce stále omezeny v několika směrech:

Konečným důsledkem je, že programy eBPF budou ve verzi 3.19 použitelné v podstatě jen pro sběr statistických údajů.

Každopádně to je dobrý začátek. Jádro 3.19 by mělo v adresáři samples obsahovat několik příkladů s ukázkami použití této funkce. Dva z nich jsou verze jednoduchého programu, který z každého paketu získává nízkoúrovňové protokoly (UDP, TCP, ICMP,...) a udržuje v mapě eBPF počitadlo pro každý protokol. Pokud chce někdo takový program napsat přímo v jazyce virtuálních strojů eBPF, dopadne to nějak takto:

    struct bpf_insn prog[] = {
	BPF_MOV64_REG(BPF_REG_6, BPF_REG_1),
	BPF_LD_ABS(BPF_B, 14 + 9 /* R0 = ip->proto */),
	BPF_STX_MEM(BPF_W, BPF_REG_10, BPF_REG_0, -4), /* *(u32 *)(fp - 4) = r0 */
	BPF_MOV64_REG(BPF_REG_2, BPF_REG_10),
	BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -4), /* r2 = fp - 4 */
	BPF_LD_MAP_FD(BPF_REG_1, map_fd),
	BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_map_lookup_elem),
	BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 2),
	BPF_MOV64_IMM(BPF_REG_1, 1), /* r1 = 1 */
	BPF_RAW_INSN(BPF_STX | BPF_XADD | BPF_DW, BPF_REG_0, BPF_REG_1, 0, 0),
	BPF_MOV64_IMM(BPF_REG_0, 0), /* r0 = 0 */
	BPF_EXIT_INSN(),
    };

Je celkem zřejmé, že takové programy nepředstavují pro většinu z nás právě poučné čtení. Ale jak je uvedeno v tomto příkladu, program lze také napsat v omezující verzi jazyka C:

    int bpf_prog1(struct sk_buff *skb)
    {
	int index = load_byte(skb, 14 + 9);
	long *value;

	value = bpf_map_lookup_elem(&my_map, &index);
	if (value)
	    __sync_fetch_and_add(value, 1);

	return 0;
    }

Tento program lze předložit speciální verzi překladače LLVM, která vytvoří soubor objektu pro virtuální stroj eBPF. Prozatím je nutno použít Alexejovu verzi LLVM, Alexej však pracuje na začlenění těchto změn do hlavního stromu LLVM. Nástroj uživatelského prostoru může načíst program ze souboru objektu a nahrát ho do jádra obvyklým způsobem; není třeba používat přímo jazyk eBPF.

Výhoda možnosti práce s jazykem vyšší úrovně je zřejmá při pohledu na poslední příklad, který se přeloží do 300 instrukcí programu eBPF. Ten provádí sledování toku, počítá pakety podle adres IP. Program samotný má možná omezené použití, ale ukazuje, že některé poměrně složité věci lze provést s virtuálním strojem eBPF v jádře.

Do budoucna se počítá s použitím eBPF na řadě jiných míst včetně subsystému zabezpečených výpočtů („seccomp“) a pro filtrování nálezů trasovacích zarážek. Vzhledem k tomu, že se eBPF v jádru stává univerzálním prostředkem, dá se očekávat, že vývojáři přijdou na další místa, kde by se dal použít. Počítejte s tím, že se v následujících letech objeví další zajímavé možnosti využití eBPF.

Rozhraní iov_iter

link

Jedním z nejčastějších úkolů v jádře je zpracování vyrovnávací paměti dat poskytnutých uživatelským prostorem i v několika blocích. Asi nikoho nepřekvapí, že takový kód jádra často bývá nesprávný, což vede k chybám a případně i k bezpečnostním problémům. Jádro obsahuje základní operaci (jménem „iov_iter“), která by měla tento úkol zjednodušit. I když se iov_iter v současnosti používá většinou jen při správě paměti a vrstev souborových systémů, pomalu se šíří do dalších částí jádra. Toto rozhraní není zatím zdokumentované, tento článek se pokusí situaci napravit.

Koncept iov_iter není nový, poprvé ho přidal Nick Piggin pro verzi jádra 2.6.24 v roce 2007. V loňském roce ale probíhaly pokusy o rozšíření toto API a o jeho použití ve více částech jádra; v začleňovacím okně 3.19 by se například měly objevit první případy využití v síťovém subsystému.

Struktura iov_iter je v podstatě iterátor pro práci prostřednictvím struktury iovec definované v <uapi/linux/uio.h>:

    struct iovec
    {
	void __user *iov_base;
	__kernel_size_t iov_len;
    };

Tato struktura odpovídá struktuře uživatelského prostoru iovec definované v POSIX, kterou používají systémová volání jako readv(). Jak napovídá přípona „vec“, struktury iovec se obvykle používají v polích; iovec celkově popisuje vyrovnávací paměť, která může být rozptýlena ve fyzické i virtuální paměti.

Struktura iov_iter samotná je definována v <linux/uio.h>:

    struct iov_iter {
	int type;
	size_t iov_offset;
	size_t count;
	const struct iovec *iov; /* ZJEDNODUŠENO – viz níže */
	unsigned long nr_segs;
    };

Pole type popisuje typ iterátor. Je to bitová maska obsahující, mimo jiné, READ nebo WRITE podle toho, zda se data do iterátoru načítají nebo z něj zapisují. Směr dat tedy neodkazuje přímo na iterátor, ale na druhou část transakce dat; do iov_iter s typem READ se bude zapisovat.

Kromě toho iov_offset obsahuje pozici prvního bajtu zajímavých dat v prvním iovec, na který odkazuje iov. Celkové množství dat, na které odkazuje pole iovec, je uloženo v count a počet struktur iovec je uložen v nr_segs. Většina z těchto polí se změní, když kód „projde“ vyrovnávací paměti. Popisují kurzor do vyrovnávací paměti, nikoli celou vyrovnávací paměť.

Práce se strukturou iov_iter

Před použitím musí být iov_iter inicializována, aby obsahovala (již naplněnou) iovec:

    void iov_iter_init(struct iov_iter *i, int direction,
		       const struct iovec *iov, unsigned long nr_segs,
		       size_t count);

Potom lze například přesouvat data mezi iterátorem a uživatelským prostorem pomocí funkcí:

    size_t copy_to_iter(void *addr, size_t bytes, struct iov_iter *i);
    size_t copy_from_iter(void *addr, size_t bytes, struct iov_iter *i);

Názvy jsou možná trochu matoucí, dokud jim přijdete na kloub. Volání copy_to_iter() zkopíruje data bytes z vyrovnávací paměti na adrese addr do vyrovnávací paměti uživatelského prostoru zadané iterátorem. copy_to_iter() lze tedy považovat za obdobu copy_to_user(), která místo jedné vyrovnávací paměti používá iterátor. Podobně copy_from_iter() zkopírujete data z vyrovnávací paměti uživatelského prostoru do addr. Podobnost s copy_to_user() pokračuje i vrácenou hodnotou, která představuje počet nezkopírovaných bajtů.

Tato volání budou „posouvat“ iterátor vyrovnávací pamětí s ohledem na objem přenesených dat. Jinými slovy, pole iov_offset, count, nr_segs a iov iterátoru se všechna změní podle potřeby. Dvě volání copy_from_iter() tedy zkopírují dvě po sobě jdoucí oblasti z uživatelského prostoru. Mimo jiné to znamená, že kód vlastnící iterátor si musí pamatovat základní adresu pole iovec, protože hodnota iov ve struktuře iov_iter se může změnit.

K dispozici jsou i další funkce. Pro přesun dat odkazovaných pomocí struktury page do iterátoru nebo z něj použijte:

    size_t copy_page_to_iter(struct page *page, size_t offset, size_t bytes,
			     struct iov_iter *i);
    size_t copy_page_from_iter(struct page *page, size_t offset, size_t bytes,
			       struct iov_iter *i);

Zkopírována bude pouze jedna zadaná page, tyto funkce tedy nelze používat ke kopírování dat, která by překročila hranice stránky.

Kód spuštěný v atomickém kontextu se může pokusit získat data z uživatelského prostoru pomocí:

    size_t iov_iter_copy_from_user_atomic(struct page *page, struct iov_iter *i,
					  unsigned long offset, size_t bytes);

Protože tato kopie bude probíhat v atomickém režimu, uspěje pouze v případě, že data jsou již uložena v paměti RAM; volající proto musí být připraven na nadprůměrnou pravděpodobnost neúspěchu.

Pokud je to nezbytné k mapování vyrovnávací paměti uživatelského prostoru do jádra, lze použít jedno z těchto volání:

    ssize_t iov_iter_get_pages(struct iov_iter *i, struct page **pages,
                               size_t maxsize, unsigned maxpages, size_t *start);
    ssize_t iov_iter_get_pages_alloc(struct iov_iter *i, struct page ***pages,
    	    			     size_t maxsize, size_t *start);

Každá z těchto funkcí se změní na volání get_user_pages_fast(), které (doufejme) způsobí načtení stránek a uložení jejich umístění do pole pages. Rozdíl mezi nimi je, že iov_iter_get_pages() očekává, že pole pages alokuje volající, zatímco funkce iov_iter_get_pages_alloc() provede alokaci sama. Potom pole vrácené v pages musí být nakonec uvolněno pomocí volání kvfree(), protože mohlo být přiděleno pomocí kmalloc() nebo vmalloc().

Průchod iterátorem bez přesouvání dat lze provést pomocí:

    void iov_iter_advance(struct iov_iter *i, size_t size);

Vyrovnávací paměť, na kterou odkazuje iterátor (nebo jeho část), lze vyčistit pomocí:

    size_t iov_iter_zero(size_t bytes, struct iov_iter *i);

Informace o iterátoru je k dispozici od řady pomocných funkcí:

    size_t iov_iter_single_seg_count(const struct iov_iter *i);
    int iov_iter_npages(const struct iov_iter *i, int maxpages);
    size_t iov_length(const struct iovec *iov, unsigned long nr_segs);

Volání iov_iter_single_seg_count() vrátí délku dat v prvním segmentu vyrovnávací paměti. iov_iter_npages() hlásí počet stránek obsazených vyrovnávací pamětí v iterátoru a iov_length() vrátí celkovou délku dat. Poslední funkci je nutno používat opatrně, protože důvěřuje poli len ve strukturách iovec. Pokud tato data pochází z uživatelského prostoru, mohou v jádře způsobit celočíselné přetečení.

Nejen iovecs

Definice struct iov_iter uvedené výše se zcela neshodují se skutečnou situací v jádře. Namísto jediného políčka pro pole iov obsahuje skutečná struktura (ve verzi 3.18):

    union {
	const struct iovec *iov;
	const struct bio_vec *bvec;
    };

Struktura iov_iter je tedy také nastavena pro práci se strukturami BIO, které používá bloková vrstva. Takové iterátory jsou označeny zahrnutím ITER_BVEC do bitové masky pole type. Když je takový iterátor vytvořen, všechna výše uvedená volání s ním budou pracovat, jako by to byl „obyčejný“ iterátor, který používá struktury iovec. Prozatím je použití iterátorů využívajících BIO v jádře minimální; nacházejí se jen v kódu odkládací paměti a splice().

Novinky ve verzi 3.19

Jádro 3.19 pravděpodobně zaznamená značnou míru změn v kódu iov_iter, které by měly redukovat značný objem standardizovaného kódu potřebného k zavedení všech výše uvedených funkcí. Kód je skutečně kratší, ale za cenu zavedení poměrně velkého zapojení lehce děsivé magie maker preprocesoru, která pomáhá vygenerovat potřebný standardní kód na požádání.

Kód iov_iter již funguje, pokud je vyrovnávací paměť „user-space“ umístěna v prostoru jádra. Ve verzi 3.19 bude všechno trochu formalizováno a optimalizováno. Iterátor se potom bude vytvářet takto:

    void iov_iter_kvec(struct iov_iter *i, int direction,
		       const struct kvec *iov, unsigned long nr_segs,
		       size_t count);

Pro tento případ také do unionu přibude nové pole kvec uvedené výše.

Byly také konečně přidány některé funkce, které pomáhají se síťováním; v rámci procesu například bude možné zkopírovat vyrovnávací paměť a generovat kontrolní součet.

Znamená to tedy, že rozhraní iov_iter se pomalu stává standardním způsobem, jak skrýt mnoho složitostí spojených s manipulací s vyrovnávací pamětí uživatelského prostoru. Do budoucna můžeme očekávat, že bude doporučováno na více místech. Zabralo zhruba jen sedm let, ale zdá se, že iov_iter se blíží do stavu, kdy se stane rozhraním, o kterém bude chtít většina vývojářů jádra vědět.

Odkazy a zdroje

Kernel coverage at LWN.net: December 10, 2014

Další články z této rubriky

Jaderné noviny – přehled za březen 2024
Jaderné noviny – přehled za únor 2024
Jaderné noviny – přehled za leden 2024
Jaderné noviny – přehled za prosinec 2023
Jaderné noviny – přehled za listopad 2023

ISSN 1214-1267, (c) 1999-2007 Stickfish s.r.o.