Byly vyhlášeny výsledky (YouTube) 28. ročníku D.I.C.E. (Design, Innovate, Communicate, Entertain) Awards: Hrou roku 2024 je Astro Bot.
Všem na AbcLinuxu vše nejlepší k Valentýnu aneb Dni lásky ke svobodnému softwaru (I love Free Software Day, Mastodon, 𝕏).
Vývojáři openSUSE Tumbleweed oznámili, že u nových instalací se ve výchozím stavu přechází z AppArmor na SELinux. Uživatelé, kteří chtějí zůstat na AppArmor si mohou AppArmor vybrat v instalátoru.
Hector "marcan" Martin skončil jako vedoucí projektu Asahi Linux aneb Linux na Apple Siliconu. Projekt ale pokračuje dál.
PostgreSQL byl vydán ve verzích 17.3, 16.7, 15.11, 14.16 a 13.19. Řešena je zranitelnost CVE-2025-1094 s CVSS 8.1 a více než 70 chyb.
Dnes je Světový den rádia. Použili jste někdy GNU Radio?
Před 33 lety, ve čtvrtek 13. února 1992, se tehdejší Česká a Slovenská Federativní Republika oficiálně (a slavnostně) připojila k Internetu.
Byla vydána nová verze 9.10 z Debianu vycházející linuxové distribuce DietPi pro (nejenom) jednodeskové počítače. Přehled novinek v poznámkách k vydání.
Český LibreOffice tým vydává překlad příručky LibreOffice Math 24.8. Math je modul editoru vzorců v kancelářském balíku LibreOffice a poskytuje možnosti rozvržení pro zobrazení matematických, chemických, elektrických nebo vědeckých vzorců ve standardní písemné notaci. Příručka je ke stažení na stránce dokumentace.
Společnost Backblaze zveřejnila statistiky spolehlivosti pevných disků používaných ve svých datových centrech za rok 2024. Ke konci roku 2024 vlastnila 305 180 pevných disků. Průměrná AFR (Annualized Failure Rate), tj. pravděpodobnost, že disk během roku selže, byla 1,57 %. V roce 2023 to bylo 1,70 %. V roce 2022 to bylo 1,37 %.
U více začátečníků (včetně sebe kdysi) jsem se setkal se zmatením, jak se liší funkce read
a fread
, open
a fopen
, a jak je správně používat.
Pro porozumění je potřeba vysvětlit, jak funguje libc. libc je standardní knihovna jazyka C a poskytuje funkce jako třeba printf
, strlen
nebo malloc
. Existuje mnoho implementací libc, na Linuxu se nejčastěji setkáte s glibc, malé distribuce jako OpenWRT používají musl libc.
glibc poskytuje jednak funkce, které něco složitého dělají (například qsort
nebo printf
), a jednak funkce, které jsou jenom wrapperem na nějaký konkrétní syscall, aby člověk mohl zavolat syscall jménem a předat mu normálně parametry - jinak by bylo potřeba používat komplikovanou funkci syscall
. To, do které ze dvou zmíněných tříd daná funkce patří, zjistíte z toho, v jakém oddílu manuálových stránek se nachází - 2. oddíl jsou syscally, 3. oddíl jsou skutečné knihovní funkce. Například malloc(3)
nebo mmap(2)
.
Funkce open(2)
, read(2)
a write(2)
jsou wrappery nad syscally a pracují přímo s file deskriptory, což je nějaké nízké číslo - například po spuštění má proces standardně otevřené deskriptory 0 (stdin), 1 (stdout) a 2 (stderr). Čísla jsou taky vidět v /proc/PID/fd
a dají se strkat třeba do poll(2)
.
Funkce fopen
, fread
a fwrite
operují nad složitějšími objekty FILE*. Ten má nějaký vnitřní buffer a umožňuje složitější operace, například fgetc(3)
, ungetc(3)
a fscanf(3)
. FILE*
nemusí nutně odpovídat 1:1 nějakému file descriptoru, například se dá pomocí funkce fmemopen(3)
otevřít soubor který je jenom virtuální v paměti.
Předem otevřené jsou streamy stdin
, stdout
a stderr
.
Surové I/O je víc low-level a není tolik portable na ne-unixové systémy. Z hlediska uživatele (programátora uživatelské aplikace) je důležité třeba to, že read
a write
můžou vrátit jenom částečně naplněný buffer (jako návratovou hodnotu vrátí, kolik dat se skutečně přečetlo/zapsalo, a pokud chcete víc, tak je musíte spustit znova se zbytkem dat) - to se děje když používáte síť, čtete data z roury atd. a víc dat není ještě dostupných. Oproti tomu streamové FILE* I/O blokuje dokud nepřečte všechno (nebo nedojde k chybě).
Další rozdíl, a to je ten, na který jsem u začátečníků nejvíc narazil, je rychlost pokud čteme malé prvky - například po bajtech nebo po něčem jako 4bajtových integerech. Syscally mají docela režii, nedávno se ještě zpomalily kvůli opravám chyby Meltdown, a typicky je možné udělat maximálně pár milionů syscallů za sekundu. Proto bude následující program, který čte po bajtech a zapisuje (nezměněné) velmi pomalý - u mě dává 2.4 MB/s.
#include <unistd.h> #include <inttypes.h> int main() { uint8_t a; while(1) { if(read(0, &a, 1) <= 0) { // EOF reached break; } // udělej něco s a write(1, &a, 1); } }
To stejné přepsané pomocí fread
dává 44 MB/s, tedy 20x víc.
#include <stdio.h> #include <inttypes.h> int main() { uint8_t a; while(1) { if(fread(&a, 1, 1, stdin) <= 0) { break; } fwrite(&a, 1, 1, stdout); } }
Rozdíl je v syscallech, což můžeme zjistit například pomocí
cat /dev/zero | strace -e read ./test-read > /dev/null
U prvního programu vidíme, že každý syscall přečte jeden bajt, přesně jak jsme napsali:
read(0, "\0", 1) = 1 read(0, "\0", 1) = 1 read(0, "\0", 1) = 1 read(0, "\0", 1) = 1
Oproti tomu fread, díky tomu, že je bufferovaný, čte do svého vnitřního bufferu vždy 4096 bajtů najednou:
read(0, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 4096 read(0, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 4096 read(0, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 4096 read(0, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 4096 read(0, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 4096
(tohle bychom samozřejmě mohli ošetřit i na aplikační úrovni - nezpracovávat prvky v cyklu po jednom, ale vždy si jich načíst větší množství, zpracovat a zapsat. Opruz je s tím, že jak write může zapsat jenom část výsledku, tak se minimálně ten zápis musí opakovat a buffer správně managovat…)
I tak je 44 MB/s docela málo (na druhou stranu jde o prvky o velikosti 1 bajt, pokud čtete třeba 4bajtové inty, bude to lepší). Ukazuje se, že bottleneck je v tom, že fread
a fwrite
musí být thread-safe - mají ten vnitřní buffer a samozřejmě by se to pokazilo, kdyby z něj četla dvě vlákna současně. Proto při každém volání zamykají a odemykají. Existují varianty fread_unlocked
a fwrite_unlocked
, po jejich použití mi to dává 460 MB/s. Samozřejmě si pak musíme sami ošetřit, abychom nepoužívali jeden soubor ve více vláknech současně.
Další zrychlení se pak už dosáhne jen načítáním více prvků najednou. Už při načítání 64B bufferů se dostaneme na 1.8 GB/s.
Pokud by nám šlo pouze o maximální výkon I/O a nestačily by nám ani tyto GB/s, tak pro další optimalizace se naopak hodí napsat si vlastní bufferování s read
a write
, protože fread
a fwrite
mají trochu overhead tím vlastním bufferováním. V takovém případě by ale už bylo lepší porozhlédnout se po nějakém jiném způsobu optimalizovaném na masivní I/O - například soubor mapovaný do paměti pomocí mmap, komunikaci mezi procesy pomocí vmsplice a I/O pomocí io_uring.
Tiskni
Sdílej:
Uz ma Rust dynamicky linkovane knihovny? Uz je v ABI kompatibilni aspon zpetne sam se sebou?ABI stabilní není. Dynamické linkování funguje už velmi dlouho, akorát ti s tim nepomůže tooling a musíš si sestavování, kompatibilitu etc. řešit do značný míry sám. Osobně bych nečekal, že by vůbec někdy Rust v nějaký větší míře přecházel na dynamické linkování vzhledem k širokému používání generik. Dynamického linkování v Rustu bych spíš viděl jako doplněk pro případy jako třeba nějaké binární pluginy apod.
Syntakticky blízké je třeba D nebo Java. Zrovna Rust je dost jiný (a často mi přijde, že poněkud samoúčelně, podobně jako Go… trochu z toho čiší snaha dělat věci za každou cenu jinak a vymezovat se proti stávajícím jazykům).
První příspěvek a hned rust-katolíkSpíš je to troll, kterýmu je to reálně jedno, jen si našel způsob trollení.Víš jak poznáš že někdo dělá v rUsTU? řekne ti to.
aje teda jakoby víc lepšejší rust nebo c????? :O :O ;D ;D
No, tak čistě osobně jsem rustem nadšen od doby, co jsem s tim začal něco dělat (ca. 2014), takže samozřejmě rust :Đ, ale to je subjektivní, objektivní názor neexistuje a záleží, jaký máš požadavky, preference a tak...aje teda jakoby víc lepšejší rust nebo c????? :O :O ;D ;D
nm
), tak zjistíš, že to podporuje Unicode a konverzi kódování UTF-8, UTF-16, ISO-8859-1, ASCII... nebo že tam máš garbage collector a další věci. Jasně, můžeš říct, že pro hello world to je zbytečné a navíc, ale v drtivé většině aplikací zrovna tyhle věci potřebuješ a do té céčkovské aplikace by sis příslušné knihovny musel přidat a ta binárka by ti taky nakynula. Výsledek je pak srovnatelný, akorát v té Javě se píše o dost pohodlněji a máš tam lepší správu paměti, tu podporu Unicodu a další věci, které moderní vysokoúrovňový jazyk podporuje.
Jinak ano, taky je mi bližší, když si člověk explicitně přibalí jen to, co potřebuje, a ta základní povinná výbava je minimální. Ale těch 2,4 MB je na dnešní poměry docela málo a zbytečností tam moc není. Takže za mě je tohle fajn cesta. Určitě lepší než smolit nějaké céčko jak v 70. letech a každou chvíli se střílet do nohy.
Z hlediska uživatele (programátora uživatelské aplikace) je důležité třeba to, že read a write můžou vrátit jenom částečně naplněný buffer (jako návratovou hodnotu vrátí, kolik dat se skutečně přečetlo/zapsalo, a pokud chcete víc, tak je musíte spustit znova se zbytkem dat)to je skutecne u site nebou roury ten pripad, jak pises, ale u souboru na harrdisku si myslim je mozne rici, ze pokud se neprecte tolik, kolik se zadalo v tom odpovidajicim parametru, tak je problem ...
read()
je vážně nízkouúrovňová věc, která toho moc neumí. Proto máme fread()
, který takové ošklivosti řeší a drží si vlastní buffer, který se do integeru (file descriptoru) prostě nevejde (narozdíl od FILE*
).
Konkrétně viz read(3p)
:
If a read() is interrupted by a signal before it reads any data, it shall return −1 with errno set to [EINTR].
If a read() is interrupted by a signal after it has successfully read some data, it shall return the number of bytes read.
read(3p)
je posixová manuálová stránka, která popisuje, jak by se ten syscall měl chovat podle POSIXu. read(2)
je linuxová manuálová stránka, která (je-li dostatečně aktuální) popisuje, jak se aktuálně chová na Linuxu (včetně případných rozšíření).
EINTR
The call was interrupted by a signal before any data was read; see signal(7).
Dává to tak i víc smyslu. Pokud byste vrátil -1/EINTR
, nebude mít volající šanci poznat, jestli se něco přečetlo a pokud ano, kolik toho bylo.
u souboru na harrdisku si myslim je mozne rici, ze pokud se neprecte tolik, kolik se zadalo v tom odpovidajicim parametru, tak je problem
Ne tak úplně:
mike@lion:/srv/ram> ls -l file8 -rw-r--r-- 1 mike users 8589934592 zář 5 01:12 file8 mike@lion:/srv/ram> strace dd if=file8 of=/dev/null bs=8G ... read(0, "t\371\246\323\310\34\365\332\202\200'X\366\234GI\327N9\353\224\361\n\315\200)z(?\221\220&"..., 8589934592) = 2147479552 write(1, "t\371\246\323\310\34\365\332\202\200'X\366\234GI\327N9\353\224\361\n\315\200)z(?\221\220&"..., 2147479552) = 2147479552 read(0, "\234]Z\213\217\214B\247 SZ\320\20\361\273i\376\250?\26\324\320\333\213\n\256^=\26{0\325"..., 8589934592) = 2147479552 write(1, "\234]Z\213\217\214B\247 SZ\320\20\361\273i\376\250?\26\324\320\333\213\n\256^=\26{0\325"..., 2147479552) = 2147479552 read(0, "\"\177l\254\316\212\325\342]\378\244;*\\\365l\25A\352\6\t\310\311/\313\336.\23\250D<"..., 8589934592) = 2147479552 write(1, "\"\177l\254\316\212\325\342]\378\244;*\\\365l\25A\352\6\t\310\311/\313\336.\23\250D<"..., 2147479552) = 2147479552 read(0, "\204\232\271\5\225\10/J\346\333\236\326/~{\264\214\217\244\31\367\212SK\220\2421\220\302n<A"..., 8589934592) = 2147479552 write(1, "\204\232\271\5\225\10/J\346\333\236\326/~{\264\214\217\244\31\367\212SK\220\2421\220\302n<A"..., 2147479552) = 2147479552 read(0, "\372\271\247_\252K\32\253\253\372\366\300\34\37\362\33\332\363\r{\3\6-\24\20\344\302\376\333~\366\245"..., 8589934592) = 16384 write(1, "\372\271\247_\252K\32\253\253\372\366\300\34\37\362\33\332\363\r{\3\6-\24\20\344\302\376\333~\366\245"..., 16384) = 16384 read(0, "", 8589934592) = 0 close(0) = 0 close(1) = 0 ... 0+5 records out 8589934592 bytes (8,6 GB, 8,0 GiB) copied, 1,06331 s, 8,1 GB/s)
read()
a fread()
je v tom, že read()
je dost těžké použít zcela správně. Takže hlavní poučení je používat knihovny, například fread()
, kde už se s tím trápil někdo jiný.
read()
, dostanete nekonzistentní obsah (a dost možná i obsah, který vůbec nedává smysl).
read()
nemusí přijít vůbec a pak byste tam měl leak za každé takové nedokončené čtení.
Nebylo by pak lepší napoprvé vracet chybu, pokud se nepovede načíst vše najednou?
Třeba informace z alsy jsou někdy docela dlouhé:
$ cat /proc/asound/card0/codec#0 | wc 113 527 3958
Jindy je to třeba jen jedno číslo nebo jeden krátký řádek.
Nebylo by pak lepší napoprvé vracet chybu, pokud se nepovede načíst vše najednou?
Někde by to asi smysl mělo, ale určitě ne obecně. Co když userspace opravdu zajímá jen začátek? Navíc existují soubory jako /proc/kcore
, které celé najednou ani přečíst nejdou.
Mimochodem, 3958 není tak moc, to se pořád vejde do 4096, což je IIRC defautní délka bloku, po kterém čte glibc, pokud si neřeknete o víc (např. když zavoláte fgetc()
). Ale jsou i soubory, které mohou být podstatně delší, např. v /proc/net
.
To je pravda, někdo asi může chtít číst jen začátek a nemusí to být chyba. Správné řešení by nejspíš bylo to vázat na otevřený FD a dokud ho uživatel nezavře, tak držet tu původní verzi konzistentní (zatímco jiným už poskytovat novější data). Akorát by se musela paměť alokovat dynamicky a bylo by to náročnější na implementaci.
readfile
syscall, který načte soubor a tečka - takže věci co čtou periodicky hodně z /proc nemusí dělat open, (p)read, close. Ale zatím to nevypadá hotově.
Hodi se to spis pro aplikace typu grep, co prohledava vsechny soubory.AFAIK rychlý alternativy ke grepu (ag, ripgrep) se na tohle snažej používat memory mapping. ripgrep v komentářích tvrdí, že používá heuristiky, ale jestli to dobře čtu, tak jen vypne mmaping na 32 bitech a na macosu, ale jinak se snaží používat mmap. Nevim, jestli readfile má tohle změnit...
EINVAL
. (Což možná není úplně košér / serióznější projekt by to asi udělal jinak, ale zas mi nepřijde, že možností by bylo kdovíkolik, protože jak píšeš, člověk si tam nechce skladovat někde nějaký rozdělaný "transakce".)
select()
/ poll()
/ epoll()
je příklad toho, kdy je potřeba jít na nižší úroveň a knihovní nadstavby použít nejde. Vlastně už samotné neblokující čtení by se s fread()
kombinovalo dost obtížně.
fileno()
a vesele ho předat select()
. Například Qt toto umí také. Také jde naopak někde sebrat file descriptor a pomocí fdopen()
si nad ním nachystat tu nadstavbu.
Neblokující IO je však trochu jiná kapitola, tam už to je komplikovanější.
fileno()
nebo fdopen()
je ta jednoduchá část. Problém je v tom, že otázka, jestli read()
bude blokovat, není stejná jako jestli fread()
bude blokovat. A kterýkoli z těch tří syscallů mi odpoví jen na tu první, zatímco mne, pokud budu používat ke čtení fread()
bude zajímat ta druhá.
supr fígl to ulocked dělání se souboramama :O :O snad stim jako někde neudělám neštěstí :D