Van nekem Linuxon egy /dev/ttyACM0 karakter eszközfile-om (USB CDC). Szeretném olvasni, de nem blokkolósan. Tehát azt szeretném, hogy a karakter olvasása ne akkor térjen vissza hozzám, ha van olvasható karakter, hanem akkor is, ha nincs, s akkor tudjam, hogy az nincs. Ennélfogva tudjak rá timeout-ot csinálni.
Próbáltam így:
c = fgetc_unlocked(dfd);
Ez vagy nem az, ami nekem kell, vagy nem tudom, hogyan kell használni, de ez is megvárja alapesetben, amíg jön egy karakter. Hogyan kell ezt csinálni? Mi kell nekem? poll(), epoll(), select()? Esetleg
fcntl(fileno(dfd), F_SETFL, O_NONBLOCK);
vagy mi a megoldás?
- 546 megtekintés
Hozzászólások
Egy mai utolsó próba alapján úgy tűnik, hogy az
fcntl(fileno(dfd), F_SETFL, O_NONBLOCK);
megoldás lesz. Bár részletesebb próbálgatásra nem volt időm. Olyan, mintha valami bufferben lapulna a válasz, s nem kerülne a karakteres eszközbe. Vagy nem tudom.
tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE
- A hozzászóláshoz be kell jelentkezni
Ez még működik, az eseményvezéreltség és más csatornákkal multiplexelés (epoll) lesz a problémás talán.
Milyen sávszélességed van, és milyen válaszidőket kell produkálni? Szorít a cípő, vagy bármilyen megoldás jó, ami működik?
- A hozzászóláshoz be kell jelentkezni
Bármi jó. PC-n fut valahány GHz-es CPU-n, miközben egy műszernek elküldök USB 2.0-n egy pár byte-os parancsot, amelyre jön egy néhány tíz byte-os válasz.
A gondom az, hogy eddig megállt, most visszatér ugyan, tudok timeout-ot csinálni, de útközben szerintem bufferel valahol, mert nem látom a vett karaktereket. A válasz bináris, nem ASCII. Valahogy rá kellene vennem, hogy adja oda nekem azokat a byte-okat, ne üljön rajta.
tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE
- A hozzászóláshoz be kell jelentkezni
>nem látom
Ha tényleg látni akarod, akkor az stdoutot flusholni kell. A serial nekem még sose bufferelt. Esetleg kódot ha megosztasz az segít...
- A hozzászóláshoz be kell jelentkezni
Ha úgy is karakterenként szeretnél olvasni, miért nem használsz inkább read()-et? Ez syscall, és kikerülsz minden bufferelést és egyéb csodát, amit a libc aládtesz.
- A hozzászóláshoz be kell jelentkezni
Az lett, mert az fgetc() nem volt barátja a poll()-nak. Azt tapasztaltam, hogy a poll() visszatér pozitív értékkel, az első fgetc() felszed egy karaktert, de a következő poll() már 0-val tér vissza, timeout-ol. Értem, hogy nem olvastam ki mindent a karakteres eszközből, de nem tudom, mennyi adat van benne. Viszont read()-del működött akkor is, ha egyszerre csak egy karaktert olvastam fel.
tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE
- A hozzászóláshoz be kell jelentkezni
A poll() hívható többször is, szint- és nem élérzékeny (szóval amíg van kiolvasatlan karakter visszatér POLLIN-el). A FILE I/O viszont több helyen pufferelhet (pl. lehet, hogy valahol sorvégére vár), ilyen esetekben felesleges overhead és bonyolítás.
- A hozzászóláshoz be kell jelentkezni
Végül is valami ilyesmi lett:
ready = read(fds.fd, &c, 1);
A poll() fds struktúrájában úgyis kéznél van a fileno(). A c az char, 1 byte-ot tetetek bele. :) Tudom, a sizeof(c) szebb lenne, de mindennek van határa. ;)
tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE
- A hozzászóláshoz be kell jelentkezni
Teljesen jó. Arra érdemes figyelni, hogy elméletileg kaphatsz vissza EAGAIN vagy EWOULDBLOCK hibát (mindkettőre lehet tesztelni, nem biztos, hogy ugyanaz a konstans érték), valamint nagyon kis valószínűséggel EINTR is lehet, ha épp egy signal talál el. Ez a három errno érték egyike sem valódi hiba, nyugodtan poll-ozhatod tovább.
- A hozzászóláshoz be kell jelentkezni
Köszönöm, akkor ezeket is kezelnem kell. Ha jól értelek, visszatér -1-gyel, s ha e három hiba valamelyike áll fenn, akkor tovább zavarom a loop-ban, mintha nem jött volna karakter, azaz nullával tért volna vissza.
tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE
- A hozzászóláshoz be kell jelentkezni
Egyébként szépen:
fcntl(fileno(dfd), F_SETFL, fcntl(fileno(dfd), F_GETFL) | O_NONBLOCK);
nehogy olyan flag-et is törölj amit nem is akarsz. Bár ez is picit slampos, mert nem törődik a hibával. :-)
- A hozzászóláshoz be kell jelentkezni
Jogos, de a poll() miatt már nem kell.
tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE
- A hozzászóláshoz be kell jelentkezni
Én szívtam már ilyennel jócskán. A Linuxban van egy csalás, hogy nem mindenre működik az epoll! Azt hinné az ember, hogy kellene neki, de nem működik! Teljesen beleszerettem az epollba, mert egyébként tényleg jó API, mindent is azzal akartam megcsinálni, de nem minden működött.
Ha jól emlékszem én a /dev/input/eventX-re akartam epoll-t csinálni, és nem működött. De lehet, hogy igaz se volt, nem emlékszem tisztán.
Csináltam IO-t ilyen serial device-okkal is, és már nem emlékszem pontosan az okára, de végül szálakkal csináltam meg inkább. El tudom képzelni, hogy nem megy velük az epoll. Persze kipróbálni nem kerül semmibe - csak 1-2 nap szarakodás :-)
A helyedben az olvasásra csinálnék egy szálat, ami olvassa a device input sztreamet és egy ringbuffer-be teszi az adatot, és a ringbuffert lehet a fő szálról non-blocking pollozgatni. Ha szükséges az eseményvezéreltség, akkor egy epoll képes pipe-ba lehet beírni mindent ami kijön az USB serialról, vagy egy signallal jelezni a fő szálnak, hogy van adat.
Ha jól emlékszem legutóbb úgy csináltam USB serialt, hogy egy socat processzt indítottam - megfelelően felparaméterezve, ami stdin/stdout-ra passzolja a serialt, és annak a processznek az stdin stdout-ját kezeltem a programomból (ami Java volt). Ez a legegyszerűbb, mert nem kell ioctl-ekkel vacakolni. Talán a menet közbeni lehúzás detektálása az ami így nem működött, de ebben sem vagyok már biztos. Nekem nem kellett ilyen, mert én csak játszós programokat csinálok ezekkel a techikákkal - hála a jó égnek!
(Java alatt is van egy epoll-ra épített non-blocking IO API, ami persze processz redirectre nem működik, ha jól emlékszem. Nem tudom, hogy az epoll működik-e szubprocessz IO-ra C-ben, szerencsére ilyet még nem kellett csinálnom.)
A legjobb, hogy nincs rendesen dokumentálva (legalábbis nem találtam meg), hogy mire kellene működnie és mire nem az epollnak. Csak ilyen blog posztok vannak, amikből meg lehet sejteni: https://darkcoding.net/software/linux-what-can-you-epoll/
- A hozzászóláshoz be kell jelentkezni
Inkabb select(). A nonblock tulajdonsag magara az fd-re szinten lehet jo/hasznos, de soros eszkozokre, foleg ha alacsony a baud rate, akkor inkabb select() + valami fizikailag relevans timeout. Azaz valami ilyesmi:
struct timeval tv;
fd_set set;
tv.tv_sec=0;
tv.tv_usec=10000;
FD_ZERO(&set);
FD_SET(dfd,&set);
r=select(dfd+1,&set,NULL,NULL,&tv);
Ha r=0, akkor timeout volt (10 millisec-en belul nem jott adat), ha 0<r es a &set-ben benne marad(t) a dfd, akkor meg van adat. Ezutobbira lasd: FD_ISSET(dfd,&set) es hasonloak.
- A hozzászóláshoz be kell jelentkezni
Ha egyszerűség is elég, akkor már inkább poll(), mint select(). A select()-el iszonyat nagyokat lehet szívni, ha túl sok nyitott fájlod van. És most akármilyen fájlról beszélek, nem csak socket és device amit tényleg figyelni akarsz (hint: FD_SETSIZE).
Ha sok monitorozott dolgod van, akkor tényleg epoll() a jó irány.
- A hozzászóláshoz be kell jelentkezni
Ugy emlekszem, hogy Linuxon a select egy wrapper a poll kore. Szoval nyugodtan hasznalhato a poll, meg kenyelmesebb is.
A strange game. The only winning move is not to play. How about a nice game of chess?
- A hozzászóláshoz be kell jelentkezni
A paraméterezésük teljesen más, a select() egy halmazt használ, ahol is minden bit egy fd-t jelöl. Ez a halmaz maximálisan FD_SETSIZE elemet tartalmaz, azaz ha ennél több nyitott fájlod van (egészen pontosan van egy használandó fd-d aminek az azonosítója legalább ekkora), akkor elég nagy baj van. Most megnéztem, és szerencsére már védett (ilyenkor csak nem működés van), régebben ilyenkor kicímzés volt (ami ugye buffer overflow). A poll() paraméterezése más, ott ez a probléma nem lép fel.
- A hozzászóláshoz be kell jelentkezni
Off: saját méretezésű fd_set-et valahogy így lehet allokálni, ha van egy 'maxhandle' érték:
#define FD_BYTES(nsock) ((((nsock)+(int)(8*sizeof(long)-1))&~(int)(8*sizeof(long)-1))/8)
fd_set *myset= calloc(1, FD_BYTES(maxhandle+1));
Vigyázat, Windows-ra nem jó.
- A hozzászóláshoz be kell jelentkezni
Igen, poll() irányába indultam el.
tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE
- A hozzászóláshoz be kell jelentkezni
Nem biztos, hogy működik:
sys/select.h: #define FD_SET(fd, fdsetp) __FD_SET (fd, fdsetp)
bits/select.h: #define __FD_SET(d, s) \
((void) (__FDS_BITS (s)[__FD_ELT(d)] |= __FD_MASK(d)))
bits/select2.h: #define __FD_ELT(d) \
__extension__ \
({ long int __d = (d); \
(__builtin_constant_p (__d) \
? (0 <= __d && __d < __FD_SETSIZE \
? (__d / __NFDBITS) \
: __fdelt_warn (__d)) \
: __fdelt_chk (__d)); })
Szóval FD_SET() és társai újabban védettek az overflow ellen, tehát akkor nem csak az allokációra, hanem a használatra is saját makrók kellenek. Azt szokták csinálni, hogy
#undef FD_SETSIZE
#define FD_SETSIZE nagyobb_szam
az összes fd_set és társai előtt, de azért ezek egyikét sem mondanám szépnek. Miért nem szereti senki sem a poll()-t, amivel nem kell így mókolni? :-)
- A hozzászóláshoz be kell jelentkezni
Mert a select egy "jól bevált" (nem) ősövület, amit az ember megtanult óvodás korában, a poll az meg olyan újmódi úri huncutság.
- A hozzászóláshoz be kell jelentkezni
A man page is a poll()-t javasolta, ezért választottam azt. Meg a poll() paraméterezése kényelmesebbnek tűnik.
tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE
- A hozzászóláshoz be kell jelentkezni
Nagyon borzasztóan, szedett-vedett módon már működik, a hibakezelések még csak részlegesek és esetlegesek, előfordulhat, hogy más hibaüzenetet mond, mint annak valós oka, szóval van vele munka rendesen, de az elv jó, működik a poll().
Az üzenet kifelé írását követően ilyeneket írtam:
fflush(dfd);
fdatasync(fileno(dfd));
Lehet, hogy nem kell, de baj nem lehet belőle.
tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE
- A hozzászóláshoz be kell jelentkezni