This shows you the differences between two versions of the page.
so2:laboratoare:lab05:exercitii [2018/03/21 08:43] ionel.ghita |
so2:laboratoare:lab05:exercitii [2019/03/20 11:49] (current) gabriel.bercaru [1. [2.5p] Alocare de porturi I/O] - fixed typo ('kdb' instead of 'kbd') |
||
---|---|---|---|
Line 104: | Line 104: | ||
Obiectivul exercițiilor următoare este să creați un driver pentru a intercepta tastele apăsate (//keylogger//). Driver-ul va intercepta IRQ-ul destinat controller-ului de tastatură și va inspecta codurile tastelor primite, stocându-le într-un buffer. | Obiectivul exercițiilor următoare este să creați un driver pentru a intercepta tastele apăsate (//keylogger//). Driver-ul va intercepta IRQ-ul destinat controller-ului de tastatură și va inspecta codurile tastelor primite, stocându-le într-un buffer. | ||
- | ==== 1. [1.5p] Alocare de porturi I/O ==== | + | ==== 1. [2.5p] Alocare de porturi I/O ==== |
Pentru început ne propunem să alocăm spații din zona I/O pentru dispozitive hardware. Vom vedea că nu putem să alocăm spațiu pentru tastatură întrucât regiunea aferentă e deja alocată. Apoi vom aloca spațiu I/O pentru porturi care nu sunt folosite. | Pentru început ne propunem să alocăm spații din zona I/O pentru dispozitive hardware. Vom vedea că nu putem să alocăm spațiu pentru tastatură întrucât regiunea aferentă e deja alocată. Apoi vom aloca spațiu I/O pentru porturi care nu sunt folosite. | ||
Line 110: | Line 110: | ||
În fișierul ''kbd.c'' avem un schelet de implementare a unui driver de tastatură. Parcurgeți codul sursă din acest fișier și urmăriți funcția ''kbd_init''. Observați că porturile de care avem nevoie sunt ''I8042_STATUS_REG'' și ''I8042_DATA_REG''. | În fișierul ''kbd.c'' avem un schelet de implementare a unui driver de tastatură. Parcurgeți codul sursă din acest fișier și urmăriți funcția ''kbd_init''. Observați că porturile de care avem nevoie sunt ''I8042_STATUS_REG'' și ''I8042_DATA_REG''. | ||
- | Urmăriți comentariile marcate cu ''TODO 1'' pentru a înregistra porturile de I/O și asigurați-vă că ați tratat corect erorile. De asementea, deînregistrați porturile în funcția ''kbd_exit''. | + | Urmăriți comentariile marcate cu ''TODO 1'' pentru a înregistra porturile de I/O și asigurați-vă că ați tratat corect erorile. De asemenea, deînregistrați porturile în funcția ''kbd_exit''. |
<note tip> | <note tip> | ||
Line 119: | Line 119: | ||
make build | make build | ||
</code>și, după ce l-ați copiat pe mașina virtuală, încărcați-l în nucleu, în mașina virtuală QEMU, folosind comanda<code> | </code>și, după ce l-ați copiat pe mașina virtuală, încărcați-l în nucleu, în mașina virtuală QEMU, folosind comanda<code> | ||
- | insmod so2_kbd.ko | + | insmod kbd.ko |
</code> | </code> | ||
- | Observați că obțineți eroare la încărcare: //%%insmod: Can't insert '.../so2_kbd.ko': Device or resource busy%%//. Acest lucru se întâmplă întrucât avem deja un driver care a întregistrat spațiul de I/O și kernel-ul poate deja accesa porturile I/O aferente. Ca să validăm că, într-adevăr, adresele aferente celor două registre (''STATUS_REG'' și ''DATA_REG'') sunt înregistrate, rulăm, în mașina virtuală QEMU comanda<code> | + | Observați că obțineți eroare la încărcare: //%%insmod: Can't insert 'kbd.ko': Device or resource busy%%//. Acest lucru se întâmplă întrucât avem deja un driver care a întregistrat spațiul de I/O și kernel-ul poate deja accesa porturile I/O aferente. Ca să validăm că, într-adevăr, adresele aferente celor două registre (''STATUS_REG'' și ''DATA_REG'') sunt înregistrate, rulăm, în mașina virtuală QEMU comanda<code> |
cat /proc/ioports | grep keyboard | cat /proc/ioports | grep keyboard | ||
</code> | </code> | ||
Line 141: | Line 141: | ||
Descărcați modulul din kernel folosind comanda<code> | Descărcați modulul din kernel folosind comanda<code> | ||
- | rmmod kdb | + | rmmod kbd |
</code> și verificați acum conținutul fișierului ''/proc/ioports''; observți că a dispărut alocarea spațiului I/O pentru cele două porturi. | </code> și verificați acum conținutul fișierului ''/proc/ioports''; observți că a dispărut alocarea spațiului I/O pentru cele două porturi. | ||
<note important> | <note important> | ||
- | Ștergeți liniile care înregistrează și eliberează cele două porturi înainte de a trece la exercițiul următor. | + | Comentați liniile care înregistrează și eliberează cele două porturi înainte de a trece la exercițiul următor. |
</note> | </note> | ||
- | ==== 2. [1.5p] Rutina de tratare a întreruperii ==== | + | ==== 2. [2p] Rutina de tratare a întreruperii ==== |
Urmărim să înregistrăm o rutină de tratare a întreruperii de tastatură. Veți scrie o funcție (rutină) de tratare a înteruperii și o veți înregistra folosind ''[[https://elixir.bootlin.com/linux/v4.15/source/include/linux/interrupt.h#L145|request_irq]]''. | Urmărim să înregistrăm o rutină de tratare a întreruperii de tastatură. Veți scrie o funcție (rutină) de tratare a înteruperii și o veți înregistra folosind ''[[https://elixir.bootlin.com/linux/v4.15/source/include/linux/interrupt.h#L145|request_irq]]''. | ||
Line 174: | Line 174: | ||
</note> | </note> | ||
- | Compilați modulul și încărcați-l în nucleu. Ulterior încărcării în nucleu, puteți observa înregistrarea liniei de întrerupere prin consultarea fișierului ''/proc/interrupts''. Urmăriți linia din output aferentă //IRQ line//-ului pentru tastatură (definit în macro-ul ''I8042_KBD_IRQ''). Observați că sunt două drivere înregistrate la această linie de întreruper (deci avem o linie de întrerupere partajată): driverul inițial ''i8042'' și driverul nostru ''so2_kbd''. Descărcați modulul din nucleu. | + | Compilați modulul și încărcați-l în nucleu. Ulterior încărcării în nucleu, puteți observa înregistrarea liniei de întrerupere prin consultarea fișierului ''/proc/interrupts''. Urmăriți linia din output aferentă //IRQ line//-ului pentru tastatură (definit în macro-ul ''I8042_KBD_IRQ''). Observați că sunt două drivere înregistrate la această linie de întreruper (deci avem o linie de întrerupere partajată): driverul inițial ''i8042'' și driverul nostru ''kbd''. Descărcați modulul din nucleu. |
<note tip> | <note tip> | ||
Line 189: | Line 189: | ||
- Veți copia caracterele ASCII corespunzătoare tastelor apăsate și le veți stoca în buffer-ul ''buf'' din structura de tip ''struct kbd'' aferentă dispozitivului. | - Veți copia caracterele ASCII corespunzătoare tastelor apăsate și le veți stoca în buffer-ul ''buf'' din structura de tip ''struct kbd'' aferentă dispozitivului. | ||
- | Implementarea o veți face în fișierul ''so2_kbd.c''. Zonele din program în care trebuie să acționați au comentarii marcate cu ''%%/* TODO 3: ... */%%'' și indicații despre ce trebuie să urmăriți. | + | Implementarea o veți face în fișierul ''kbd.c''. Zonele din program în care trebuie să acționați au comentarii marcate cu ''%%/* TODO 3: ... */%%'' și indicații despre ce trebuie să urmăriți. |
La fiecare pas compilați modulul, încărcați modulul în kernel, apăsați în mașina virtuală taste pentru a verifica funcționalitatea, și apoi descărcați modulul. | La fiecare pas compilați modulul, încărcați modulul în kernel, apăsați în mașina virtuală taste pentru a verifica funcționalitatea, și apoi descărcați modulul. | ||
Line 203: | Line 203: | ||
</note> | </note> | ||
- | Apelați funcția ''i8042_read_data()'' în handler-ul de întrerupere ''so2_kbd_interrupt_handler()'' (adică la fiecare tastă apăsată) și afișați valoarea citită din registru. | + | Apelați funcția ''i8042_read_data()'' în handler-ul de întrerupere ''kbd_interrupt_handler()'' (adică la fiecare tastă apăsată) și afișați valoarea citită din registru. |
<note tip> | <note tip> | ||
Pentru afișare în cadrul handler-ului de întrerupere folosiți o construcție de forma:<code> | Pentru afișare în cadrul handler-ului de întrerupere folosiți o construcție de forma:<code> | ||
- | printk(LOG_LEVEL "IRQ: %d, scancode=0x%x (%u, %c)\n", | + | pr_info("IRQ %d: scancode=0x%x (%u) pressed=%d ch=%c\n", |
- | irq_no, scancode, scancode, scancode); | + | irq_no, scancode, scancode, pressed, ch); |
</code> | </code> | ||
unde ''scancode'' este valoarea registrului citit cu ajutorul funcției ''i8042_read_data()''. | unde ''scancode'' este valoarea registrului citit cu ajutorul funcției ''i8042_read_data()''. | ||
Line 248: | Line 248: | ||
</note> | </note> | ||
- | În handler-ul de întrerupere (''so2_kbd_interrupt_handler'') interpretați scancode-ul pentru a vedea dacă este o apăsare sau o ridicare de tastă și obțineți caracterul ASCII aferent. Afișați informațiile obținute. | + | În handler-ul de întrerupere (''kbd_interrupt_handler'') interpretați scancode-ul pentru a vedea dacă este o apăsare sau o ridicare de tastă și obțineți caracterul ASCII aferent. Afișați informațiile obținute. |
<note tip> | <note tip> | ||
Line 256: | Line 256: | ||
<note tip> | <note tip> | ||
Pentru afișare în cadrul handler-ului de întrerupere folosiți o construcție de forma:<code> | Pentru afișare în cadrul handler-ului de întrerupere folosiți o construcție de forma:<code> | ||
- | printk(LOG_LEVEL "IRQ %d: scancode=0x%x (%u) pressed=%d ch=%c\n", | + | pr_info(LOG_LEVEL "IRQ %d: scancode=0x%x (%u) pressed=%d ch=%c\n", |
irq_no, scancode, scancode, pressed, ch); | irq_no, scancode, scancode, pressed, ch); | ||
</code> | </code> | ||
Line 268: | Line 268: | ||
=== c. [1p] Colectarea caracterelor în buffer === | === c. [1p] Colectarea caracterelor în buffer === | ||
- | Dorim să colectăm caracterele apăsate (nu și celelalte taste) într-un buffer care apoi va fi citit din user space. | + | Dorim să colectăm caracterele apăsate (nu și celelalte taste) într-un buffer circular care apoi va fi citit din user space. |
- | Actualizați handler-ul de tratare a întreruperii (''so2_kbd_interrupt_handler'') astfel încât un caracter ASCII citit să fie adăugat la sfârșitul buffer-ului aferent dispozitivului. Dacă buffer-ul este plin, caracterul va fi ignorat. | + | Actualizați handler-ul de tratare a întreruperii (''kbd_interrupt_handler'') astfel încât un caracter ASCII citit să fie adăugat la sfârșitul buffer-ului aferent dispozitivului. Dacă buffer-ul este plin, caracterul cel mai vechi o sa fie suprascris. |
<note tip> | <note tip> | ||
Line 277: | Line 277: | ||
</code> | </code> | ||
- | Dimensiunea curentă a buffer-ului este dată de câmpul ''buf_idx'' din structura de tip ''struct kbd'' aferentă dispozitivului. Va trebui să incrementați valoarea acestei variabile la fiecare caracter adăugat în buffer. | + | Dimensiunea curentă a buffer-ului este dată de câmpul ''count'' din structura de tip ''struct kbd'' aferentă dispozitivului. Câmpul ''put_idx'' specifică următoarea poziție de scriere, iar ''get_idx'' următoarea poziție de citire. Urmăriți implementarea funcției ''put_char'' pentru a observa cum sunt adăugate datele în buffer-ul circular. |
</note> | </note> | ||
- | |||
- | <note tip> | ||
- | Capacitatea buffer-ului, peste care nu puteți scrie, este dată de macro-ul ''BUFFER_SIZE''. | ||
- | </note> | ||
- | |||
==== 4. [2p] Citirea bufferului printr-un apel ''read'' ==== | ==== 4. [2p] Citirea bufferului printr-un apel ''read'' ==== | ||
- | Pentru a avea acces la datele colectate de keylogger va trebui să le transmitem în user space. Facem acest lucru prin intermediului unui dispozitiv de tip caracter (''/dev/so2_kbd''). La citirea din acest dispozitiv de tip caracter vom primi datele din buffer-ul din kernel space în care am colectat tastele apăsate. | + | Pentru a avea acces la datele colectate de keylogger va trebui să le transmitem în user space. Facem acest lucru prin intermediului unui dispozitiv de tip caracter (''/dev/kbd''). La citirea din acest dispozitiv de tip caracter vom primi datele din buffer-ul din kernel space în care am colectat tastele apăsate. |
- | Implementarea o veți face în fișierul ''so2_kbd.c''. Zonele din program în care trebuie să acționați au comentarii marcate cu ''%%/* TODO 4: ... */%%'' în funcția ''so2_kbd_read()'' și indicații despre ce trebuie să urmăriți. | + | Implementarea o veți face în fișierul ''kbd.c''. Zonele din program în care trebuie să acționați au comentarii marcate cu ''%%/* TODO 4: ... */%%'' în funcția ''kbd_read()'' și indicații despre ce trebuie să urmăriți. |
La fiecare pas compilați modulul, încărcați modulul în kernel, apăsați în mașina virtuală taste pentru a verifica funcționalitatea, și apoi descărcați modulul. | La fiecare pas compilați modulul, încărcați modulul în kernel, apăsați în mașina virtuală taste pentru a verifica funcționalitatea, și apoi descărcați modulul. | ||
- | Este permis un singur cititor la un moment dat astfel încât protejăm codului funcției ''so2_kbd_read()'' cu un mutex. | + | Este permis un singur cititor la un moment dat astfel încât protejăm codului funcției ''kbd_read()'' cu un mutex. |
- | Întrucât nu dorim modificări la nivelul buffer-ului din partea handler-ului de întrerupere în timp ce facem citirea sa în funcția ''so2_kbd_read()'' veți folosiți un spinlock și veți dezactiva întreruperile în funcția ''so2_kbd_read''. | + | Întrucât nu dorim modificări la nivelul buffer-ului din partea handler-ului de întrerupere în timp ce facem citirea sa în funcția ''kbd_read()'' veți folosiți un spinlock și veți dezactiva întreruperile în funcția ''kbd_read''. |
<note tip> | <note tip> | ||
- | Definiți spinlock-ul în structura de dispozitiv (''struct kbd'') și inițializați-l în ''so2_kbd_init''. Folosiți funcțiile [[https://elixir.bootlin.com/linux/v4.15/source/include/linux/spinlock.h#L338|spin_lock_irqsave]] și [[https://elixir.bootlin.com/linux/v4.15/source/include/linux/spinlock.h#L363|spin_unlock_irqrestore]] pentru locking cu dezactivarea întreruperilor pe parcursul lucrului cu buffer-ul în rutina ''so2_kbd_read()''. | + | Definiți spinlock-ul în structura de dispozitiv (''struct kbd'') și inițializați-l în ''kbd_init''. Folosiți funcțiile [[https://elixir.bootlin.com/linux/v4.15/source/include/linux/spinlock.h#L338|spin_lock_irqsave]] și [[https://elixir.bootlin.com/linux/v4.15/source/include/linux/spinlock.h#L363|spin_unlock_irqrestore]] pentru locking cu dezactivarea întreruperilor pe parcursul lucrului cu buffer-ul în rutina ''kbd_read()''. |
Folosiți funcțiile [[https://elixir.bootlin.com/linux/v4.15/source/include/linux/spinlock.h#L308|spin_lock]] si [[https://elixir.bootlin.com/linux/v4.15/source/include/linux/spinlock.h#L348|spin_unlock]] pentru protejarea buffer-ulu în rutina de tratare a întreruperii. | Folosiți funcțiile [[https://elixir.bootlin.com/linux/v4.15/source/include/linux/spinlock.h#L308|spin_lock]] si [[https://elixir.bootlin.com/linux/v4.15/source/include/linux/spinlock.h#L348|spin_unlock]] pentru protejarea buffer-ulu în rutina de tratare a întreruperii. | ||
Line 304: | Line 299: | ||
</note> | </note> | ||
- | Pentru copierea în user space veți folosi apelul [[https://elixir.bootlin.com/linux/v4.15/source/include/linux/uaccess.h#L152|copy_to_user]]. Întrucât nu puteți folosi ''copy_to_user'' în timp ce aveți un spinlock luat, trebuie să folosiți un buffer temporar. | + | Completați funcția ''get_char'' pentru a obține următorul caracter care trebuie citit din buffer. Aveți grijă la implementarea buffer-ului circular (să nu citiți dincolo de dimensiunea buffer-ului și de poziția de scriere). |
- | <note tip> | + | Pentru copierea în user space veți folosi apelul [[https://elixir.bootlin.com/linux/v4.15/source/arch/x86/include/asm/uaccess.h#L249|put_user]] pentru fiecare caracter în parte, obținut folosind funcția ''get_char''. |
- | Buffer-ul temporar și dimensiunea acestuia sunt indicate de câmpurile ''tmp_buf'' și ''tmp_buf_idx'' din structura de tip dispozitiv. | + | |
+ | <note warning> | ||
+ | Nu puteți folosi ''copy_to_user'' sau ''put_user'' în timp ce aveți un spinlock luat. | ||
Urmăriți în laboratorul 4 secțiunea [[:so2:laboratoare:lab04#accesul_la_spatiul_de_adresa_al_procesului|Accesul la spațiul de adresă al procesului]]. | Urmăriți în laboratorul 4 secțiunea [[:so2:laboratoare:lab04#accesul_la_spatiul_de_adresa_al_procesului|Accesul la spațiul de adresă al procesului]]. | ||
Line 318: | Line 315: | ||
</note> | </note> | ||
- | Pentru testare, va trebui să creați fișierul de tip caracter ''/dev/so2_kbd'' folosind utilitarul ''mknod'' cu majorul și minorul dispozitivului sunt definiți în macro-urile din ''so2_kbd.c'': ''SO2_KBD_MAJOR'' și ''SO2_KBD_MINOR''. Adică va trebui să rulați comanda<code> | + | Pentru testare, va trebui să creați fișierul de tip caracter ''/dev/kbd'' folosind utilitarul ''mknod'' cu majorul și minorul dispozitivului sunt definiți în macro-urile din ''kbd.c'': ''KBD_MAJOR'' și ''KBD_MINOR''. Adică va trebui să rulați comanda<code> |
- | mknod /dev/so2_kbd c 42 0 | + | mknod /dev/kbd c 42 0 |
</code> | </code> | ||
Compilați modulul pe sistemul fizic, apoi porniți mașina virtuală și încărcați modulul în nucleul mașinii virtuale. Testați folosind comanda<code> | Compilați modulul pe sistemul fizic, apoi porniți mașina virtuală și încărcați modulul în nucleul mașinii virtuale. Testați folosind comanda<code> | ||
- | cat /dev/so2_kbd | + | cat /dev/kbd |
</code> | </code> | ||
O să obțineți conținutul bufferului keylogger-ului din kernel space. | O să obțineți conținutul bufferului keylogger-ului din kernel space. | ||
- | ==== 5. [3p] Curățarea buffer-ului dacă este tastată o parolă ==== | + | ==== 5. [1.5p] Curățarea buffer-ului la scriere ==== |
- | Dorim să %%"curățăm"%% buffer-ul dacă este tastată o parolă. În acel moment buffer-ul va fi resetat: conținutul său va fi resetat și dimensiunea va fi ''0''. | + | Dorim să %%"curățăm"%% buffer-ul atunci când se scrie în device node-ul asociat driver-ului. În acel moment buffer-ul va fi resetat: conținutul său va fi resetat și dimensiunea va fi ''0''. |
- | Implementarea o veți face în fișierul ''so2_kbd.c''. În rutina de tratare a întreruperii veți reține în câmpul ''passcnt'' câte caractere din parolă au fost potrivite. Parola este definită de macro-urile ''MAGIC_WORD'' și ''MAGIC_WORD_LEN''. | + | Implementarea o veți face în fișierul ''kbd.c''. Urmăriți comentariile marcate cu ''TODO 5''. Implementați funcția ''reset_buffer'' și adăugați operația ''write'' la ''kbd_fops''. |
- | + | ||
- | Când se detectează întreaga parolă, curătați buffer-ul. Pentru curățare, reinițializați câmpul ''buf_idx'' la valoarea ''0'' și umpleți cu ''0''-uri buffer-ul. | + | |
- | + | ||
- | La primirea unui caracter nou, verificați dacă acesta corespunde cu caracterul de pe poziția curentă din parolă. În caz afirmativ, incrementați contorul; altfel, resetați counterul la 0. | + | |
<note tip> | <note tip> | ||
- | În momentul resetării buffer-ului, în handler-ul de întrerupere va trebui să folosiți spinlock-ul pentru accesul exclusiv la buffer. Este posibil ca rutina de read să fie procesată de pe alt procesor și să acceseze buffer-ul în momentul în care handler-ul de întrerupere îl modifică. Veți folosi funcțiile [[https://elixir.bootlin.com/linux/v4.15/source/include/linux/spinlock.h#L308|spin_lock]] și [[https://elixir.bootlin.com/linux/v4.15/source/include/linux/spinlock.h#L348|spin_unlock]]. | + | În momentul resetării buffer-ului, va trebui să folosiți spinlock-ul cu dezactivarea întreruperilor pentru accesul exclusiv la buffer. |
Parcurgeți secțiunea [[:so2:laboratoare:lab05#utilizare_spinlock-uri|Utilizarea spinlock-uri]]. | Parcurgeți secțiunea [[:so2:laboratoare:lab05#utilizare_spinlock-uri|Utilizarea spinlock-uri]]. | ||
</note> | </note> | ||
- | Pentru testare, va trebui să creați fișierul de tip caracter ''/dev/so2_kbd'' folosind utilitarul ''mknod'' cu majorul și minorul dispozitivului sunt definiți în macro-urile din ''so2_kbd.c'': ''SO2_KBD_MAJOR'' și ''SO2_KBD_MINOR''. Adică va trebui să rulați comanda<code> | + | Pentru testare, va trebui să creați fișierul de tip caracter ''/dev/kbd'' folosind utilitarul ''mknod'' cu majorul și minorul dispozitivului sunt definiți în macro-urile din ''kbd.c'': ''KBD_MAJOR'' și ''KBD_MINOR''. Adică va trebui să rulați comanda<code> |
- | mknod /dev/so2_kbd c 42 0 | + | mknod /dev/kbd c 42 0 |
</code> | </code> | ||
Compilați modulul pe sistemul fizic, apoi porniți mașina virtuală și încărcați modulul în nucleul mașinii virtuale. Consultați conținutul buffer-ului folosind comanda<code> | Compilați modulul pe sistemul fizic, apoi porniți mașina virtuală și încărcați modulul în nucleul mașinii virtuale. Consultați conținutul buffer-ului folosind comanda<code> | ||
- | cat /dev/so2_kbd | + | cat /dev/kbd |
</code> | </code> | ||
- | Apăsați pe taste și apoi apăsați conținutul macro-ului ''MAGIC_WORD'' (adică ''root'') și apoi consultați din nou conținutul buffer-ului. Ar trebui să fie resetat. | + | Apăsați pe taste și apoi rulați comanda ''echo "clear" > /dev/kbd'' și apoi consultați din nou conținutul buffer-ului. Ar trebui să fie resetat. |
===== Extra ===== | ===== Extra ===== | ||
- | |||
- | ==== [1 karma] Resetarea buffer-ului în rutina de read ==== | ||
- | |||
- | Implementarea curentă este problematică pentru că rutina de tratare a întreruperii este cea care face resetarea buffer-ului, lucru care este consumator de timp și nerecomandat să fie făcut în context întrerupere. | ||
- | |||
- | O soluție este să actualizăm comportamentul astfel încât resetarea buffer-ului să se producă la apelul funcției ''so2_kbd_read()''. După ce informația este copiată în user space, buffer-ul este resetat. Nu mai resetăm în momentul tastării | ||
- | |||
- | Simplificați implementarea în așa fel încât resetarea buffer-ului să se producă în funcția ''so2_kbd_read()'' imediat după copierea datele în user space. | ||
- | |||
- | <note tip> | ||
- | În momentul în care copiați datele în buffer-ul temporare ''tmp_buf'' resetați buffer-ul principal: adică umpleți buffer-ul ''buf'' cu zero-uri și inițialiați ''buf_idx'' la ''0''. | ||
- | </note> | ||
- | |||
- | Pentru verificarea după citirea din ''/dev/so2_kbd'' veți avea buffer-ul resetat. Citiri ulterioare vor găsi buffer-ul gol. | ||
==== [3 karma] Utilizare kfifo ==== | ==== [3 karma] Utilizare kfifo ==== |