Differences

This shows you the differences between two versions of the page.

Link to this comparison view

so:cursuri:curs-02 [2014/02/21 12:30]
razvan.deaconescu [Modificarea cursorului de fișier]
so:cursuri:curs-02 [2019/04/14 09:08] (current)
dragos_florin.costea [Curs 02 - Interfața sistemului de fișiere]
Line 1: Line 1:
-====== Curs 02 - Sistemul ​de fișiere ======+====== Curs 02 - Interfața sistemului ​de fișiere ======
  
-<​html>​ +  * [[http://​prezi.com/​5-5ua6acyxv9/so-curs-2/?kw=view-5-5ua6acyxv9&rc=ref-31844697 | Curs 02 - Interfața sistemului de fișiere (vizualizare Prezi)]] 
-<iframe src="http://​prezi.com/embed/​5-5ua6acyxv9/?​bgcolor=ffffff&​amp;​lock_to_path=0&amp;​autoplay=no&​amp;​autohide_ctrls=0&​amp;​features=undefined&​amp;​disabled_features=undefined"​ width="​550"​ height="​400"​ frameBorder="​0"></​iframe>​ +  * [[http://​elf.cs.pub.ro/​so/​res/​cursuri/​SO_Curs-02.pdf | Curs 02 - Interfața sistemului de fișiere (PDF)]]
-</html>+
  
-  * [[http://prezi.com/5-5ua6acyxv9/so-curs-2/?kw=view-5-5ua6acyxv9&​rc=ref-31844697 ​Curs 02 - Sistemul ​de fișiere (vizualizare Prezi)]] +  * [[https://docs.google.com/document/d/1kH3Sg8OcW8oMUqsuG1jaicUxUze05vN_2wHaJix3W28/​edit?usp=sharing|Notițe ​de curs]]
-  * [[http://​elf.cs.pub.ro/​so/​res/​cursuri/​SO_Curs-02.pdf | Curs 02 - Sistemul de fișiere (PDF)]]+
  
   * Suport curs   * Suport curs
Line 12: Line 10:
       * Capitolul 9 - File-System Interface       * Capitolul 9 - File-System Interface
       * Capitolul 10, Secțiunea 10.1 - File-System Structure       * Capitolul 10, Secțiunea 10.1 - File-System Structure
-    * Modern Operating Systems 
-      * Capitolul 6 - File System Implementation (secțiunile 1 și 2) 
     * Linux System Programming     * Linux System Programming
       * Capitolul 2 (programatic)       * Capitolul 2 (programatic)
Line 19: Line 15:
       * Capitolul 2 (programatic)       * Capitolul 2 (programatic)
  
 +<​html>​
 +  <​center>​
 +    <iframe src="​https://​prezi.com/​embed/​5-5ua6acyxv9/?​bgcolor=ffffff&​amp;​lock_to_path=0&​amp;​autoplay=no&​amp;​autohide_ctrls=0&​amp;​features=undefined&​amp;​disabled_features=undefined"​ width="​550"​ height="​400"​ frameBorder="​0"></​iframe>​
 +  </​center>​
 +</​html>​
  
 ===== Demo-uri ===== ===== Demo-uri =====
  
 Pentru parcurgerea demo-urilor,​ folosim [[http://​elf.cs.pub.ro/​so/​res/​cursuri/​curs-02-demo.zip|arhiva aferentă]]. Demo-urile rulează pe Linux. Descărcăm arhiva folosind comanda<​code bash> Pentru parcurgerea demo-urilor,​ folosim [[http://​elf.cs.pub.ro/​so/​res/​cursuri/​curs-02-demo.zip|arhiva aferentă]]. Demo-urile rulează pe Linux. Descărcăm arhiva folosind comanda<​code bash>
-user@host:​~$ ​wget http://​elf.cs.pub.ro/​so/​res/​cursuri/​curs-02-demo.zip+wget http://​elf.cs.pub.ro/​so/​res/​cursuri/​curs-02-demo.zip
 </​code>​ și apoi decomprimăm arhiva<​code bash> </​code>​ și apoi decomprimăm arhiva<​code bash>
-user@host:​~$ ​unzip curs-02-demo.zip+unzip curs-02-demo.zip
 </​code>​ și accesăm directorul rezultat în urma decomprimării<​code bash> </​code>​ și accesăm directorul rezultat în urma decomprimării<​code bash>
-user@host:​~$ ​cd curs-02-demo/​+cd curs-02-demo/​
 </​code>​ </​code>​
  
Line 35: Line 36:
  
 Pentru a afișa tabela de descriptor de fișier a procesului shell current (PID-ul său este reținut în construcția ''​$$''​),​ folosim comanda<​code bash> Pentru a afișa tabela de descriptor de fișier a procesului shell current (PID-ul său este reținut în construcția ''​$$''​),​ folosim comanda<​code bash>
-user@host:​~$ ​lsof -a -d 0-1023 -p $$+lsof -a -d 0-1023 -p $$
 </​code>​ </​code>​
  
Line 50: Line 51:
 </​spoiler>​ </​spoiler>​
  
 +<​note>​
 +Pe anumite sisteme descriptorul este marcat cu ''​0u''​ adică este read-write; dar, în mod practic, este folosit doar pentru citire.
 +</​note>​
 ==== Descriptori de fișier pentru procesele daemon ==== ==== Descriptori de fișier pentru procesele daemon ====
  
 Vrem să investigăm descriptorii de fișiere pentru procesele daemon din sistem. Pentru început vrem să aflăm procesele daemon din sistem; procesele daemon au ca proces părinte procesul ''​init''​ (procesul cu PID-ul ''​1''​) și vom folosi comanda de mai jos pentru a le afla:<​code bash> Vrem să investigăm descriptorii de fișiere pentru procesele daemon din sistem. Pentru început vrem să aflăm procesele daemon din sistem; procesele daemon au ca proces părinte procesul ''​init''​ (procesul cu PID-ul ''​1''​) și vom folosi comanda de mai jos pentru a le afla:<​code bash>
-user@host:​~$ ​ps --ppid 1+ps --ppid 1
 </​code>​ </​code>​
  
-Pentru unul dintre procesele daemon descoperite prin rularea comenzii anterioare , afișam tabela de descriptori (e nevoie de drept de ''​root''​) folosind comanda ''​lsof'':<​code bash> +Pentru unul dintre procesele daemon descoperite prin rularea comenzii anterioare, afișăm tabela de descriptori (e nevoie de drept de ''​root''​) folosind comanda ''​lsof'':<​code bash> 
-root@host:​~# ​sudo lsof -a -d 0-1023 -p $PID+sudo lsof -a -d 0-1023 -p $PID
 </​code>​ </​code>​
  
Line 78: Line 82:
 ==== Descriptorii de fișier după redirectare ==== ==== Descriptorii de fișier după redirectare ====
  
-Vrem să vedem cum se modifică descriptorii de fișier în cazul redicterării. Pentru a putea vedea acest lucru vom rula o comandă de durată (''​sleep''​) și vom redirecta intrarea și ieșirea standard:<​code bash> +Vrem să vedem cum se modifică descriptorii de fișier în cazul redirectării. Pentru a putea vedea acest lucru vom rula o comandă de durată (''​sleep''​) și vom redirecta intrarea și ieșirea standard:<​code bash> 
-user@host:​~$ ​sleep 100 < /etc/passwd > f.txt+sleep 100 < /etc/passwd > f.txt
 </​code>​ </​code>​
  
-Pentru a investiga procesul ''​sleep''​ proaspăt ​porni, trebuie să știm PID-ul său. Deschidem o altă consolă și aflăm PID-ul procesului ''​sleep''​ creat folosind comanda:<​code bash> +Pentru a investiga procesul ''​sleep''​ proaspăt ​pornit, trebuie să știm PID-ul său. Deschidem o altă consolă și aflăm PID-ul procesului ''​sleep''​ creat folosind comanda:<​code bash> 
-user@host:​~$ ​pidof sleep+pidof sleep
 </​code>​ </​code>​
  
-Vom folosi ​onstrucția ''​$PID''​ referă PID-ul procesului ''​sleep''​ pe care-l investigăm. Ca și până acum, afișam tabela de descriptori de fișier a procesului folosind comanda:<​code bash> +Vom folosi ​construcția ''​$PID''​ referă PID-ul procesului ''​sleep''​ pe care-l investigăm. Ca și până acum, afișam tabela de descriptori de fișier a procesului folosind comanda:<​code bash> 
-user@host:​~$ ​lsof -a -o -d 0-1023 -p $PID+lsof -a -o -d 0-1023 -p $PID
 </​code>​ </​code>​
  
Line 103: Line 107:
 user@host:​~$ make user@host:​~$ make
 </​code>​ și obținem executabilul ''​c-file-ops''​. Rulăm executabilul ''​c-file-ops'':<​code bash> </​code>​ și obținem executabilul ''​c-file-ops''​. Rulăm executabilul ''​c-file-ops'':<​code bash>
-user@host:​~$ ​./​c-file-ops+./​c-file-ops
 </​code>​ </​code>​
  
 Pentru a urmări evoluția tabelei de descriptori și a cursorului de fișier, vom folosi comanda ''​lsof''​. Într-o altă consolă rulăm comanda<​code bash> Pentru a urmări evoluția tabelei de descriptori și a cursorului de fișier, vom folosi comanda ''​lsof''​. Într-o altă consolă rulăm comanda<​code bash>
-user@host:​~$ ​lsof -a -o -d 0-1023 -p $(pidof c-file-ops)+lsof -a -o -d 0-1023 -p $(pidof c-file-ops)
 </​code>​ Pentru început sunt afișați doar descriptorii standard. </​code>​ Pentru început sunt afișați doar descriptorii standard.
  
Line 121: Line 125:
  
 La finalul rulării programului urmărim dimensiunea fișierului ''​f.txt'':<​code bash> La finalul rulării programului urmărim dimensiunea fișierului ''​f.txt'':<​code bash>
-user@host:​~$ ​stat -c "​%s"​ f.txt+stat -c "​%s"​ f.txt
 256 256
 </​code>​ Observăm că fișierul are dimensiunea de 256 octeți, atât cât a primit ca argument apelul ''​ftruncate''​. </​code>​ Observăm că fișierul are dimensiunea de 256 octeți, atât cât a primit ca argument apelul ''​ftruncate''​.
Line 128: Line 132:
  
 <spoiler Răspuns>​ <spoiler Răspuns>​
-Apelul ''​ftruncate'' ​modifică ​modifică dimensiunea fișierului. Un fișier are un câmp de dimensiune alterat de comanda ''​ftruncate''​. Acest câmp este diferit de cursorul de fișier. Teoretic cursorul de fișier poate fi plasat dincolo de sfârșitul fișierului. Din acest motiv, apelul ''​ftruncate''​ nu are nici un efect asupra cursorului de fișier.+Apelul ''​ftruncate''​ modifică dimensiunea fișierului. Un fișier are un câmp de dimensiune alterat de comanda ''​ftruncate''​. Acest câmp este diferit de cursorul de fișier. Teoretic cursorul de fișier poate fi plasat dincolo de sfârșitul fișierului. Din acest motiv, apelul ''​ftruncate''​ nu are nici un efect asupra cursorului de fișier.
  
 Acest lucru este precizat și în [[http://​man7.org/​linux/​man-pages/​man2/​ftruncate.2.html#​DESCRIPTION|secțiunea DESCRIPTION a paginii de manual a apelului ftrunctate]]. Acest lucru este precizat și în [[http://​man7.org/​linux/​man-pages/​man2/​ftruncate.2.html#​DESCRIPTION|secțiunea DESCRIPTION a paginii de manual a apelului ftrunctate]].
Line 135: Line 139:
 ==== Modificarea cursorului de fișier (Python) ==== ==== Modificarea cursorului de fișier (Python) ====
  
-  - Intrați ​în directorul ​''​py-file-ops/''​ din arhiva ​cu demo-uri ​cursului+Vrem să vedem cum este alterat cursorul de fișier ​în cazul unui program Python. Pentru aceasta, accesăm subdirectorul ​''​py-file-ops/''​ din directorul ​cu demo-uri ​al cursului ​șparcurgem ​fișierul ''​py-file-ops.py''​. Observăm că structura este similară celei de la demo-ul anterior: se scriu date în fișier, se parcurge fișierul, se așteaptă apăsarea tastei ''​ENTER''​ din partea utilizatorului
-    * Parcurgeți fișierul ''​py-file-ops.py''​. + 
-    * Rulați ​programul folosind comanda:<​code bash>+Rulăm ​programul folosind comanda:<​code bash>
 python py-file-ops.py python py-file-ops.py
 </​code>​ </​code>​
-    * Într-o altă consolă, pentru ​a urmări evoluția tabelei de descriptori și a cursorului de fișier, ​rulați comenzile:<code bash> + 
-ps -ef | grep py-file-ops +Pentru ​a urmări evoluția tabelei de descriptori și a cursorului de fișier, ​deschidem o altă consolă în care rulăm comanda<code bash> 
-lsof -a -o -d 0-1023 -p $PID+lsof -a -o -d 0-1023 -p $(pgrep -f py-file-ops) 
 +</​code>​ Sunt afișați doar descriptorii standard pentru că încă nu a fost deschis fișierul. 
 + 
 +Pentru a deschide fișierul și a efectua oprații asupra sa, vom apăsa ''​ENTER''​ în consola în care am rulat programul ''​py-file-ops''​. Urmărim evoluția programului în consola în care ați rulat ''​lsof''​. 
 + 
 +<note tip> 
 +Coloana ''​OFFSET''​ indică poziția cursorului de fișier. 
 +</​note>​ 
 + 
 +Folosim de mai multe ori ''​ENTER''​ pentru a parcurge toți pașii din program și pentru a investiga evoluția cursorului de fișier. La fel ca la demo-ul anterior, fișierul va avea în final dimensiunea de ''​256''​ octeți:<​code bash> 
 +stat -c "​%s"​ f.txt  
 +256
 </​code>​ </​code>​
-      * ''​$PID''​ este process ID-ul obținut la prima comandă. 
-    * Folosiți ENTER în consola în care ați rulat programul ''​py-file-ops''​ și urmăriți evoluția programului în consola în care ați rulat ''​lsof''​. 
-      * Coloana ''​OFFSET''​ indică //file pointer//​-ul (cursorul de fișier, //file offset//). 
-    * De ce nu există tot timpul o corespondență între operațiile Python și modificarea //file pointer//​-ului?​ 
-    * Ce se întâmplă dacă nu se apelează ''​f.flush()''​ (după ciclul de scriere cu ''​f.write()''​)?​ 
  
 +De ce nu există tot timpul o corespondență între operațiile Python și modificarea cursorului de fișier? De exemplu, în cazul ''​f.read()'',​ deși trebuia să se incrementeze contorul cu ''​256''​ octeți, a fost incrementat cu ''​512''​.
 +
 +<spoiler Răspuns>​
 +Python nu folosește direct apelurile oferite de sistemul de operare. Biblioteca standard Python oferă wrappere peste apelurile expuse de sistemul de operare și biblioteca standard C. Astfel, în cazul unui apel ''​f.read()''​ se realizează,​ în fundal, un transfer de ''​512''​ octeți și se bufferează pentru viitoare operații.
 +</​spoiler>​
 +
 +Ce se întâmplă dacă nu se apelează ''​f.flush()''​ (după ciclul de scriere cu ''​f.write()''​)?​
 +
 +<spoiler Răspuns>​
 +Dacă nu se apelează ''​f.flush()''​ datele rămân în bufferele interne ale bibliotecii standard Python. Aceste date vor fi sincronizate (flushed) la un moment de timp ulterior: fie când se apelează ''​f.flush()'',​ fie când se închide fișierul, fie când se face o operație care impune sincronizarea datelor.
 +</​spoiler>​
 ==== Buffered I/O vs. System-Level I/O ==== ==== Buffered I/O vs. System-Level I/O ====
  
-  - Intrați în directorul ​''​buffered-system-io/''​ din arhiva ​cu demo-uri ​cursului+Ne propunem să investigăm diferențele dintre apelurile de tip buffered I/O șcele de tip system-level I/O. Intrăm ​în subdirectorul ​''​buffered-system-io/''​ din directorul ​cu demo-uri ​ale cursului ​șurmărim ​fișierele ''​buffered.c''​ și ''​system.c''​. ​Observăm că operațiile efectuate sunt similare; diferă doar funcțiile folosite, unele de tip buffered I/O, altele de tip system-level I/O. 
-    * Parcurgeți fișierele ''​buffered.c''​ și ''​system.c''​. + 
-    * Compilațcele două programe folosind comanda:<​code bash>+Compilăm ​cele două programe folosind comanda:<​code bash>
 make make
 </​code>​ </​code>​
-    * Într-o consolă ​rulați ​programul ''​buffered'':<​code bash>+ 
 +Pentru a investiga apelurile de bibliotecă ale programului ''​buffered''​ executăm rapid pașii: 
 +  - Într-o consolă ​rulăm ​programul ''​buffered'':<​code bash>
 ./buffered ./buffered
 </​code>​ </​code>​
-      * Rapid, într-o altă consolă, urmăriți **apelurile de bibliotecă** realizate de program, ​folosind comanda:<​code bash>+  - Rapid, într-o altă consolă, urmărim **apelurile de bibliotecă** realizate de programul ''​buffered'' ​folosind comanda:<​code bash>
 ltrace -e putchar,​fputc -p $(pidof buffered) ltrace -e putchar,​fputc -p $(pidof buffered)
 </​code>​ </​code>​
-      ​După încheiere, rulați din nou programul ​și, într-o altă consolă, urmăriți **apelurile de sistem** realizate de program, ​folosind comanda:<​code bash>+ 
 +Pentru a investiga ​**apelurile de sistem** ale programului ''​buffered''​ executăm rapid pașii: 
 +  - Într-o consolă rulăm programul ''​buffered'':<​code bash> 
 +./​buffered 
 +</​code>​ 
 +  - Rapid, într-o altă consolă, urmărim **apelurile de sistem** realizate de programul ''​buffered'' ​folosind comanda:<​code bash>
 strace -e write -p $(pidof buffered) strace -e write -p $(pidof buffered)
 </​code>​ </​code>​
-      * Ce observați? ​Care este numărul de apeluri de bibliotecă și de apeluri de sistem realizate+ 
-    * Într-o consolă rulați ​programul ''​system''​:<code bash+Care este numărul de apeluri de bibliotecă și numărul ​de apeluri de sistem realizate ​de programul ''​buffered'' ​în cadrul buclelor ''​for''?​ 
-./system + 
-</code> +<spoiler Răspuns
-      * Rapidîntr-o altă consolă, urmăriți **apelurile de bibliotecă** realizate ​de program, folosind ​comanda:<code bash>+Programul ''​buffered''​ realizează 10 apeluri de bibliotecă ''​putchar''​ și 20 apeluri de bibliotecă ''​putc'',​ conform celor două bucle ''​for''​Întrucât folosește buffered I/O, se face flush/sincronizareadică se face apel de sistem doar dacă se ajunge la un caracter //newline// (''​\n''​) sau dacă se apelează ''​fflush()''​. Deci se vor face doar două apeluri de sistem: unul la apelul ''​printf("​\n"​);''​ și altul la apelul ''​fflush(f);''​. 
 +</​spoiler>​ 
 + 
 +Realizați pașii de mai sus și pentru programul ''​system''​. Doar că pentru a detecta ​apelurile de bibliotecă realizate ​folosiți ​comanda<​code bash>
 ltrace -e write -p $(pidof system) ltrace -e write -p $(pidof system)
 </​code>​ </​code>​
-      * După încheiere, rulați din nou programul și, într-o altă consolă, urmăriți **apelurile de sistem** realizate de program, folosind comanda:<​code bash> 
-strace -e write -p $(pidof system) 
-</​code>​ 
-      * Ce observați? Care este numărul de apeluri de bibliotecă și de apeluri de sistem realizate? 
  
 +Care este numărul de apeluri de bibliotecă și numărul de apeluri de sistem realizate de programul ''​system''?​
 +
 +<spoiler Răspuns>​
 +La fel ca programul ''​buffered'',​ programul ''​system''​ realizează tot 10 + 20 = 30 apeluri de bibliotecă,​ dar ''​write''​ în acest caz. Întrucât folosește system-level I/O fiecărui apel de bibliotecă îi corespunde un apel de sistem, deci vor fi tot 30 de apeluri de sistem.
 +
 +Întrucât apelurile de sistem sunt costisitoare,​ este recomandată folosirea buffered I/O. Dar, dacă este nevoie ca datele să fie sincronizate/​flush imediat ce au fost scrise, va trebui folosit system-level I/O.
 +</​spoiler>​
 ==== Apelul dup și cursorul de fișier ==== ==== Apelul dup și cursorul de fișier ====
  
-  - Intrați în directorul ​''​open-dup/''​ din arhiva ​cu demo-uri a cursului+Ne propunem să urmărim efectul apelului ''​dup()''​ asupra cursorului de fișier. Pentru aceasta accesăm subdirectorul ​''​open-dup/''​ din directorul ​cu demo-uri a cursului ​șparcurgem ​fișierele ''​open.c''​ și ''​dup.c''​. ​În ambele fișiere se deschid doi descriptori de fișier (''​fd1''​ ș''​fd2''​):​ în cazul fișierului ''​open.c''​ descriptorul ''​fd2''​ este creat folosind apelul ''​open()'',​ iar în cazul fișierului ''​dup.c''​ descriptorul ''​fd2''​ este creat folosind apelul ''​dup()''​. 
-    * Parcurgeți fișierele ''​open.c''​ și ''​dup.c''​. + 
-    * Compilați programele folosind comanda<​code bash>+Compilăm ​programele folosind comanda<​code bash>
 make make
-</​code>​ +</​code> ​și obținem executabilele ''​open''​ și ''​dup''​. 
-      * Veți obține executabilele ''​open''​ și ''​dup''​. + 
-    * Rulați ​executabilul ''​open'':<​code bash>+Rulăm ​executabilul ''​open'':<​code bash>
 ./open ./open
 </​code>​ </​code>​
-    * Într-o altă consolă, pentru ​a urmări ​evoluția tabelei ​de descriptori și a cursorului ​de fișier, ​rulați comanda:<​code bash>+ 
 +Pentru ​a urmări ​tabela ​de descriptori ​de fișier ​și cursorul ​de fișier ​pentru procesul creatdeschidem o altă consolă șrulăm ​comanda:<​code bash>
 watch -d lsof -a -o -d 0-1023 -p $(pidof open) watch -d lsof -a -o -d 0-1023 -p $(pidof open)
 </​code>​ </​code>​
-    * Folosiți ENTER în consola în care ați rulat executabilul ''​open''​ și urmăriți evoluția ​programului în consola în care ațrulat ''​lsof''​+ 
-      ​* ​Coloana ''​OFFSET''​ indică ​//file pointer//​-ul (cursorul ​de fișier//file offset//). +În consola în care am rulat executabilul ''​open'' ​folosim comanda ''​ENTER''​ pentru a parcurge pașii din program. Urmărim în a doua consolă evoluția ​tabelei de descriptori șa cursorului de fișier
-    * **Urmați aceeași pași pentru executabilul ''​dup''​**+ 
-    ​* ​Ce diferențe apar?+<note tip> 
 +Coloana ''​OFFSET''​ indică ​poziția cursorului ​de fișier
 +</note> 
 + 
 +Realizați aceeași pași pentru executabilul ''​dup''​. 
 + 
 +Ce diferențe apar la nivelul tabelei de descriptori de fișier și cursor de fișier între programul ''​open''​ și programul ''​dup''​? 
 + 
 +<spoiler Răspuns>​ 
 +Ambele apeluri creează o intrare nouă în tabela de descriptori de fișier. În cazul apelului ''​dup''​ intrarea nou creată este o clonă a celei vechi. Alterarea cursorului de fișier pentru descriptorul ''​fd1''​ este vizibilă pentru descriptorul ''​fd2''​. Cei doi descriptori referă aceeași structură, care conține același cursor de fișier. În cazul apelului ''​open''​ intrările sunt distincte, chiar dacă în final referă același fișier. Modificarea cursorului de fișier pentru descriptorul ''​fd1''​ nu afectează cursorul de fișier pentru descriptorul ''​fd2''​. 
 +</​spoiler>​
 ===== Link-uri recomandate ===== ===== Link-uri recomandate =====
  
 [[http://​www.ibm.com/​developerworks/​aix/​library/​au-lsof.html|Finding open files with lsof]] ​ [[http://​www.ibm.com/​developerworks/​aix/​library/​au-lsof.html|Finding open files with lsof]] ​
- 
so/cursuri/curs-02.1392978616.txt.gz · Last modified: 2014/02/21 12:30 by razvan.deaconescu
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0