Razvan Deaconescu
7 septembrie 2005
In 1970, AT&T si Bell Laboratories au inceput proiectul Multics, un sistem de operare care promitea sa fie revolutionar din punct de vedere al caracteristicilor si mecanismelor folosite. Dupa esecul acestuia, echipa de proiectanti si programatori s-a retras. Totusi, unul dintre acestia, Ken Thompson, a continuat sa lucreze la o versiune simplificata a sistemului de operare care tocmai esuase. Intregul sistem a fost scris in limbajul de asamblare specific masinii DEC PDP-7 pe care o avea la dispozitie. Mai tarziu acesta a fost portat pe un PDP-11.
In 1973, Dennis Ritchie (bun prieten cu Ken Thompson) pune bazele limbajului C si, astfel, a uimitoarei portabilitati ulterioare a sistemului de operare denumit Unics (ca alternativa la Multics); numele a fost ulterior schimbat la Unix. In scurt timp, Unix a fost rescris in C.
Intregul sistem de operare fusese scris fara perceptii comerciale si, in scurt timp, numerosi pasionati si studenti la universitati de profil (in special Berkeley) au inceput sa-i adauge mai multe caracteristici.
In 1978 s-a scris pcc (portable C compiler) care a deschis calea Unix catre multe alte arhitecturi (circa 80% din Unix era scris, la acea vreme, in C). Dupa 1980, AT&T (detinatoarea Bell Laboratories) a reusit sa castige dreptul de a comercializa Unix. In acel moment, Unix-ul a devenit un sistem de operare proprietar, codul sursa nemaifiind disponibil ca pana atunci.
In paralel cu dezvoltarea Unix de la AT&T (denumit SystemV Unix), Berkeley a lansat propria sa distributie de Unix (tot comerciala) denumita BSD. Cele doua distributii au adus numeroase contributii sistemului de operare (memorie virtuala, demand-paging, interfata socket pentru lucrul intr-o retea de calculatoare, mecanisme IPC).
Multe companii au cumparat de la AT&T o parte din codul sursa si prosibilitatea de a distribui propriul lor sistem de operare; au aparut astfel IRIX, AIX, XENIX, SunOS (actualmente Solaris).
In 1992 AT&T a vandut toate drepturile sale UNIX catre SCO (Santa Cruz Operations).
Distributia BSD a dorit schimbarea modului de distributie de la unul comercial la unul free (echivalent open source). Totusi, desi aceasta initiativa a fost decisa in 1989, au mai durat 3 ani pana la definitivarea conditiilor legale. Acest ragaz a permis Linux-ului sa se afirme pe piata. Variantele BSD free de astazi (FreeBSD, NetBSD, OpenBSD) nu au la fel de mult succes.
Linux a fost scris in 1991 de catre Linus Torvalds, pe vremea cand era student la Universitatea din Helsinki. Linux a fost scris ca reactie la minisistemul de operare MINIX scris de Andrew Tanenbaum, care refusaze in mod repetat sa extinda facilitatile acestuia. In scurt timp de la prezentarea Linux-ului ca free software publicului larg, el a castigat o multime de dezvoltatori, ceea ce a condus la o maturizare rapida si continua a sistemului. Linux-ul este asadar o clona Unix, nu un Unix autentic, deoarece nu detine bucati din codul sursa de Unix.
Desi multe variante de UNIX persista in ziua de azi, cele mai cunoscute si cu grupuri de dezvoltatori mai numeroase sunt versiunile free: Linux si variantele BSD free.
Linux constituie numai nucleul (kernel-ul) sistemului de operare, restul componentelor provin de la proiectul GNU (Gnu's Not Unix) de la FSF (Free Software Foundation). De aceea, inca de la inceput a devenit clara necesitatea unei distributii care sa elimine timpul necesar pentru compilare, configurare si instalare de pachete software. O statistica a gradului de utilizare a diverselor distributii de Linux si link-uri catre site-urile acestora gasiti la DistroWatch http://www.distrowatch.com.
O lista cu distributiile cele mai cunoscute si site-urie asociate este prezentata mai jos:
Debian - http://www.debian.org Fedora - http://www.fedora.org RedHat - http://www.redhat.com SuSE - http://www.suse.com Mandriva - http://www.mandriva.com Slackware - http://www.slackware.com Ubuntu - http://www.ubuntu.org Gentoo - http://www.gentoo.org Mepis - http://www.mepis.com Knoppix - http://www.knoppix.com
Alegerea unei distributii tine, de obicei, de usurinta in instalare si configurare, dar si de distributiile persoanelor cunoscute si prietenilor, care pot oferi asistenta. O comparatie intre diversele distributii Linux gasiti la http://en.wikipedia.org/wiki/Distribution_comparison.
permite vizualizarea continutului directorului curent (sau al directorului primit ca parametru)
razvan@ragnarok:~$ ls Desktop bin contestapplet.conf mydocs people Templates books evolution package.lst php an4sem2 cfiles linux packages tmp razvan@ragnarok:~$ ls /usr X11R6 bin doc games include info java lib local sbin share src razvan@ragnarok:~$ ls -l total 60 drwxr-xr-x 4 razvan razvan 4096 Jun 19 23:29 Desktop drwxr-xr-x 2 razvan razvan 4096 Dec 28 2004 Templates drwxr-xr-x 10 razvan razvan 4096 Jun 20 13:06 an4sem2 drwxr--r-- 2 razvan razvan 4096 May 13 18:59 bin drwxr--r-- 10 razvan razvan 4096 Jun 30 18:59 books drwx------ 23 razvan razvan 4096 Aug 1 17:33 cfiles -rw-r--r-- 1 razvan razvan 461 Aug 1 17:40 contestapplet.conf drwx------ 4 razvan razvan 4096 May 13 22:17 evolution drwxr--r-- 5 razvan razvan 4096 Jun 12 23:19 linux drwx------ 12 razvan razvan 4096 Aug 6 23:51 mydocs -rw-r--r-- 1 razvan razvan 15 Jun 14 15:31 package.lst drwxr-xr-x 4 razvan razvan 4096 Jul 12 12:25 packages drwxr-xr-x 10 razvan razvan 4096 Jun 20 13:19 people drwxr-xr-x 3 razvan razvan 4096 Jul 6 00:29 php drwx------ 2 razvan razvan 4096 Aug 2 23:41 tmp razvan@ragnarok:~$ ls -a . .gimp-2.0 .mcoprc .xchm .. .gimp-2.2 .metacity .xemacs .ICEauthority .gksu.lock .mime.types .xine .Trash .gnome .mozilla .xinitrc .Xauthority .gnome2 .mozilla-thunderbird .xmms .appletviewer .gnome2_private .mpd.conf .xscreensaver .autosave .gnome_private .mplayer .xsession-errors .bash_history .gnucash .my.cnf .ymessenger .bash_profile .gstreamer-0.8 .mysql_history Desktop .bashrc .gtkrc-1.2-gnome2 .nautilus Templates .bluefish .httrack.ini .openoffice an4sem2 .dc .hyperb .qt bin .dmrc .icons .recently-used books .esd_auth .indent.pro .saves-3420-ragnarok cfiles .ethereal .java .shadow_todo contestapplet.conf .evolution .kde .smb evolution .face .kderc .ssh linux .fishsrv.pl .local .sversionrc mydocs .fonts .lyx .themes package.lst .fonts.cache-1 .macromedia .thumbnails packages .fonts.conf .mailcap .viminfo people .gconf .mc .vimrc php .gconfd .mcop .xchat2 tmp razvan@ragnarok:~$
permite schimbarea directorului curent
razvan@ragnarok:~$ cd bin/ razvan@ragnarok:~/bin$ ls chfilemod.sh maxfds myindent.sh myipcrm.sh mymore razvan_rullz seek.sh razvan@ragnarok:~/bin$ cd ../package bash: cd: ../package: No such file or directory razvan@ragnarok:~/bin$ cd ../packages/ razvan@ragnarok:~/packages$ ls WordBiz mplsadm2-1.938-1.src.rpm WordBiz18linux.zip ymessenger_1.0.4_1_i386.deb flashplugin-nonfree_7.0.25-5_i386.deb zebra-0.95 mpich2_1.0.1-1_i386.deb zebra-0.95.tar.gz razvan@ragnarok:~/packages$ cd razvan@ragnarok:~$
schimba drepturile de acces ale unui fisier/director
a - all u - user g - group o - other + - adaugare drepturi - - diminuare drepturi r - drept citire w - drept scriere x - drept executie
razvan@ragnarok:~/packages$ ls -l zebra-0.95.tar.gz -rw-r--r-- 1 razvan razvan 1379369 Jul 6 23:19 zebra-0.95.tar.gz razvan@ragnarok:~/packages$ chmod go+w zebra-0.95.tar.gz razvan@ragnarok:~/packages$ ls -l zebra-0.95.tar.gz -rw-rw-rw- 1 razvan razvan 1379369 Jul 6 23:19 zebra-0.95.tar.gz razvan@ragnarok:~/packages$ chmod 711 zebra-0.95.tar.gz razvan@ragnarok:~/packages$ ls -l zebra-0.95.tar.gz -rwx--x--x 1 razvan razvan 1379369 Jul 6 23:19 zebra-0.95.tar.gz razvan@ragnarok:~/packages$ chmod 644 zebra-0.95.tar.gz razvan@ragnarok:~/packages$ ls -l zebra-0.95.tar.gz -rw-r--r-- 1 razvan razvan 1379369 Jul 6 23:19 zebra-0.95.tar.gz razvan@ragnarok:~/packages$
schimba detinatorul (owner-ul) unui fisier/director
razvan@ragnarok:~/packages$ su Password: ragnarok:/home/razvan/packages# ls -l zebra-0.95.tar.gz -rw-r--r-- 1 razvan razvan 1379369 Jul 6 23:19 zebra-0.95.tar.gz ragnarok:/home/razvan/packages# chown root:root zebra-0.95.tar.gz ragnarok:/home/razvan/packages# ls -l zebra-0.95.tar.gz -rw-r--r-- 1 root root 1379369 Jul 6 23:19 zebra-0.95.tar.gz ragnarok:/home/razvan/packages# chown razvan:razvan zebra-0.95.tar.gz ragnarok:/home/razvan/packages# ls -l zebra-0.95.tar.gz -rw-r--r-- 1 razvan razvan 1379369 Jul 6 23:19 zebra-0.95.tar.gz ragnarok:/home/razvan/packages# exit exit razvan@ragnarok:~/packages$
permite vizualizarea continutului unui fisier (se afiseaza la iesirea standard continutul fisierului primit ca parametru);
permite schimbarea utilizatorului pentru terminalul curent;
razvan@ragnarok:~$ su Password: razvan@ragnarok:~$ su - Password:
sunt echivalente (permit schimbarea utilizatorului in root)
permite actualizarea datei ultimei accesari (modificari) la data curenta
razvan@ragnarok:~/mydocs/simple_dir$ ls razvan@ragnarok:~/mydocs/simple_dir$ touch simple_file.txt razvan@ragnarok:~/mydocs/simple_dir$ ls -l total 0 -rw-r--r-- 1 razvan razvan 0 Sep 8 20:13 simple_file.txt razvan@ragnarok:~/mydocs/simple_dir$ touch simple_file.txt razvan@ragnarok:~/mydocs/simple_dir$ ls -l total 0 -rw-r--r-- 1 razvan razvan 0 Sep 8 20:14 simple_file.txt razvan@ragnarok:~/mydocs/simple_dir$
se observa crearea fisierului in urma primei folosiri a comenzii touch si apoi schimbarea datei ultimei actualizari dupa cea de-a doua folosire.
utilitare pentru vizualizarea paginata a fisierelor.
Spre deosebire de utilitarul cat, aceste comenzi permit afisarea unui fisier pagina cu pagina; sunt utilitarele folosite in afisarea paginilor de manual. Apasarea tastei 'h' permite afisarea unei pagini de ajutor despre comenzile folosite pentru vizualizare. Printre cel mai utile sunt:
q - iesire /str - cauta sirul 'str' in fisier (tasta 'n' este folosita pentru a trece la urmatoarea prezenta a sirului) SPACE - pagina inainte b - pagina inapoi ENTER - linie jos y - linie sus h - help
Comenzile cp, mv si rm sunt folosite pentru interactiunea cu fisierele, adica pentru copierea, mutarea sau stergerea acestora. Exemple de folosire:
razvan@ragnarok:~$ cp package.lst tmp/ razvan@ragnarok:~$ cd tmp/ razvan@ragnarok:~/tmp$ ls package.lst razvan@ragnarok:~/tmp$ mv package.lst .. razvan@ragnarok:~/tmp$ ls razvan@ragnarok:~/tmp$ cd .. razvan@ragnarok:~$ ls -l package.lst -rw-r--r-- 1 razvan razvan 15 Sep 20 21:28 package.lst razvan@ragnarok:~$ cp package.lst tmp/ razvan@ragnarok:~$ cd tmp razvan@ragnarok:~/tmp$ ls package.lst razvan@ragnarok:~/tmp$ rm package.lst razvan@ragnarok:~/tmp$ ls razvan@ragnarok:~/tmp$
Sintaxele pentru cp si mv sunt asemanatoare:
mv [optiuni] sursa destinatie
-r - realizeaza recursiv trecerea prin directoare/subdirectoare; in felul acesta se pot copia/muta/sterge directoare -i - interactivitate; utilizatorului ii este ceruta confirmarea pentru fiecare actiune intreprinsa de comanda respectiva -f - fortarea operatiei -v - verbose; detalierea operatiilor
Comenzile de mai sus sunt folosite pentru crearea, respectiv stergerea unui director. Trebuie precizat faptul ca rmdir nu poate sterge un director decat in masura in care acesta este gol. Pentru stergerea unui director si a intregii sale structuri de fisiere si subdirectoare se va folosi comanda rm cu optiunea -r.
Crearea unei arhive este un lucru important in cadrul oricarui sistem de operare. Permite micsorarea dimensiunii, transmiterea unei intregi structuri de directoare intr-un singur fisier si pentru backup.
tar (tape archive) este principalul utilitar de obtinere a unei arhive (tarfile). Comanda poate fi folosita ca front-end pentru obtinerea unor arhive tar.gz sau tar.bz2. Intrucat comanda are un numar impresionant de optiuni, vom specifica numai cateva exemple in care se prezinta modurile de utilizare preponderente.
$ tar cvf arc1.tar file1 file2 dir1/ dir2/ ; crearea unui fisier .tar $ tar xvf arc1.tar ; extragerea unei arhive .tar $ tar czvf arc2.tgz file1 file2 dir1/ ; crearea unei arhive .tgz ; prezenta optiunii z inseamna filtrarea cu gzip $ tar xzvf arc2.tgz ; extragerea unei arhive .tgz $ tar cjvf arc3.tar.bz2 file1 dir1 ; crearea unei arhive .tar.bz2 $ tar xjvf arc3.tar.bz2
este principala forma de documentare intr-un sistem Unix.
Forma de utilizare este
razvan@ragnarok:~/mydocs/simple_dir$ man printf razvan@ragnarok:~/mydocs/simple_dir$ man 1 printf razvan@ragnarok:~/mydocs/simple_dir$ man 3 printf razvan@ragnarok:~/mydocs/simple_dir$ man write Reformatting write(1), please wait... razvan@ragnarok:~/mydocs/simple_dir$ man 2 write Reformatting write(2), please wait...
Paginile de manual folosesc pe post de utilitar de formatare more/less si comenzile de vizualizare vor fi aceleasi ca ale acestora.
este o noua forma de documentare cu prezentarea informatiilor intr-un format asemanator unei pagini web; printre cele mai utile comenzile de parcurgere a paginilor sunt:
u - up (un nivel mai sus in ierarhia de pagini) p - previous (pagina anterioara) n - next (pagina urmatoare) ENTER - urmarirea unui link (cross reference) SPACE - ecran jos Backspace - ecran sus q - quit /str - cauta sirul 'str' h - activarea paginilor de help; tutorialul este unul extrem de bine explicat, organizat si foarte intuitiv
Toate comenzile importante sunt documentate in format info.
Pentru vizualizarea paginilor de ajutor pentru TOATE comenzile importante UNIX folositi comanda
Sistemele Unix se remarca prin lipsa unor IDE (Integrated Development Environment). In mod traditional exista utilitare specializate pentru editare, compilare, depanare.
Printre cele mai utilizate editoare din lumea Unix amintim: Emacs (XEmacs), vi (vim, gvim, elvis, view), joe, nano, kwrite, kate, mcedit. Cele mai folosite editoare sunt vi si Emacs. Fanatismul utilizatorilor pentru folosirea unuia din aceste doua editoare a condus la ``editor wars''. Exista grupuri sau site-uri specializate care prezinta avantajele folosirii unui editor in fata celuilalt.
Emacs nu este numai un editor; are o multime de alte facilitati - de la citire, transmitere de e-mail-uri pana la citire de stiri, vizualizarea paginilor de documentare info; este una din cele mai mari aplicatii scrise vreodata (de unde si multe glume la adresa acestuia: "Emacs is a great operating system, but Unix has more applications").
Dispune de propriul sau limbaj - Emacs Lisp - cu care poate fi usor customizat; numarul de optiuni este impresionant, lasand la dispozitia utilizatorului o larga libertate.
Toate comenzile din Emacs sunt comenzi compuse utilizand tastele speciale
C (prescurtare de la Ctrl) si M (prescurtare de la Meta/Alt).
Un tutorial foarte bine pus la punct poate fi accesat folosind
comanda C-h t; cu ajutorul acestuia se pot deprinde foarte usor diversele
comenzi de baza.
O lista cu cele mai utilizate comenzi:
C-x C-s - salvarea fisierului curent C-x C-w - salvarea fisierului sub alt nume C-x C-c - iesire din Emacs C-x C-f - deschiderea unui nou fisier pentru editare C-x b - schimbare intre fisierele deja dechise C-s - cautare in fisier C-r - cautare inversa in fisier C-SPACE - crearea unui mark (pentru selectarea unei anumite zone) C-w - cut (yank) pentru zona selectata M-w - copierea zonei selectate C-y - paste M-x replace string - inlocuirea unui sir cu un altul M-x font-lock-mode - activarea (dezactivarea) syntax highlighting-ului
Pentru parcurgerea fisierului editat, o selectie a celor mai importante comenzi este prezentata mai jos (la parcurgere avem combinatii C-litera sau M-litera; in timp ce comenzile cu C se refera la unitati de un fel anume, cele cu M se refera la grupuri de astfel de unitati: de exemplu C-f inseamna litera la dreapta, M-f inseamna cuvant la dreapta; C-e inseamna sfarsit de linie, M-e inseamna sfarsit de paragraf)
C-n - linie jos C-p - linie sus C-v - ecran jos M-v - ecran sus C-f - litera la dreapta C-b - litera la stanga M-f - cuvant la dreapta M-b - cuvant la stanga C-e - sfarsit de rand C-a - inceput de rand
vi este un editor destul de dificil de invatat din cauza faptului
ca este un editor modal (un editor modal este un editor pentru care
exista diverse "moduri" de lucru care permit
executia anumitor actiuni); necesitatea intelegerii si invatarii lui
provine din faptul ca pe orice masina Unix (indiferent cat de veche/noua)
exista in mod sigur o versiune de vi; versiunea populara astazi este
vim care aduce multe imbunatatiri batranului vi; gvim este o versiune
cu interfata grafica (XWindow)
vimtutor este o comanda care dechide un tutorial pentru vi
care descrie o mare parte a comenzilor de baza
vi are trei moduri: comanda, ecran si inserare; trecerea intre cele trei moduri este prezentata mai jos (modul initial este cel ecran)
ecran -> inserare - se apasa "i", "o" sau "a" ecran -> comanda - se apasa ":" inserare -> ecran - se apasa ESC comanda -> ecran - se executa comanda daca se apasa "i" atunci se va intra in modul inserare cu cursorul in pozitia curenta; daca se apasa "a" se va intra in modul inserarea cu cursorul pe pozitia urmatoare; daca se apasa "o" cursorul va fi pozitionat pe o linie noua
h - stanga j - jos k - sus l - dreapta yy - copierea liniei curente p - paste dd - cut pentru linia curenta x - stergerea caracterului curent u - undo /str - cauta sirul str in fisierul curent
toate comenzile de mai sus pot fi precedate de un numar pentru multiplicarea comenzii (de exemplu comanda '8 x' sterge 8 caractere din drepata cursorului).
prev, next - face trecerea intre diversele fisiere editate q (quit) - se face iesirea din fisierul curent w (write) - se salveaza fisierul curentul '!' asezat dupa q, sau prev, sau next sau oricare alte comenzi forteaza executia acelei comenzi fara a mai atrage atentia utilizatorului despre salvarea fisierului editat
gcc este compilatorul implicit in distributiile de Linux; provine din efortul FSF de a genera un sistem Unix complet liber (free software); initial un compilator de C, acestuia i-au fost adaugate multe alte front-end-uri si este capabil acum sa compileze multe alte limbaje: C++, Objective C, Ada, Fortran, Pascal.
GCC are un numar impresionant de optiuni; multe dintre ele sunt optiuni referitoare la optimizarea codului, iar multe altele sunt optiuni specifice unei anumite arhitecturi. GCC este actualmente unul dintre cele mai portabile si portate compilatoare.
Pentru a urmari unele optiuni ale compilatorului, vom prezenta pe scurt etapele parcurse, de la un program scris in limbaj de nivel inalt la un executabil:
Astfel in lipsa unor alte optiuni comanda gcc realizeaza preprocesarea, compilarea, asamblarea si editarea de legaturi; din pagina de manual de la gcc:
When you invoke GCC, it normally does preprocessing, compilation, assembly and linking.Deci comanda
Cateva optiuni ale GCC permit oprirea acestuia la una din fazele intermediare:
$ gcc -E test.c ; oprire dupa faza de preprocesare (la iesire test.E - fisier preprocesat) $ gcc -S test.c ; oprire dupa faza de compilare (la iesire test.s - fisier in limbaj de asamblaer) $ gcc -c test.c ; oprire dupa faza de asamblare (la iesire test.o - fisier in cod obiect)
Putem folosi optiunea -o pentru a specifica numele fisierului de iesire:
$ gcc -E -o test_preproc.E test.c ; iesirea va fi acum test_preproc.E $ gcc -S -o test_assem.s test.c ; iesirea va fi test_assem.s $ gcc -c -o test_obj.o test.c ; iesirea va fi test_obj.o $ gcc -o test test.c ; executabilul va avea numele test (in locul numelui implicit a.out)
O optiune care nu trebuie NICIODATA omisa este -Wall. Aceasta afiseaza toate mesajele de avertizare (Warnings All). Optiunea este extrem de utila pentru ca sintaxa extrem de relaxata a C-ului permite conversii de tipuri de diferite, operatii intre intregi si pointeri, declaratii de functii implicite.
Daca dorim sa avem cod compatibil cu standardul ANSI (ISO C90) se foloseste optiunea -ansi, de obicei impreuna cu -pedantic. Daca dorim sa avem cod compatibil cu standardul ISO C99 folosim optiunea -std=c99 (pentru -ansi aveam optiunea echivalenta -std=c90). Standardul ISO C99 aduce multe imbunatatiri printre care stilul C++ de comentare, tipul long long int, tipul complex.
Alte cateva optiuni utile sunt:
-D nume=definitie - predefineste macro-ul 'nume' cu definitia 'definitie' -I cale - precizeaza o cale suplimentara unde se gasesc unele headere incluse in fisierele sursa -L cale - precizeaza o cale suplimentara unde sa gasesc unele biblioteci folosite la legare -l biblioteca - foloseste biblioteca 'biblioteca' la legare
Diferite exemple de utilizare a GCC:
razvan@ragnarok:~$ gcc -Wall -pedantic -ansi -o exec main.c
creeaza executabilul exec prin compilarea si link-area fisierului main.c
razvan@ragnarok:~$ gcc -Wall -pedantic -ansi -o server server.c sock.c handler.c
creeaza executabilul server prin compilarea si link-area fisierelor server.c, sock.c, handler.c.
Intotdeauna cand se doreste generarea unui fisier executabil, un singur fisier si numai unul trebuie sa contina functia ``main''.
razvan@ragnarok:~$ gcc -Wall -pedantic -ansi -c server.c razvan@ragnarok:~$ gcc -Wall -pedantic -ansi -c sock.c razvan@ragnarok:~$ gcc -Wall -pedantic -ansi -c handler.c razvan@ragnarok:~$ gcc -g -o server server.o sock.o handler.o
aici se obtine un executabil prin obtinerea in prealabil a modulelor obiect si apoi legarea acestora. Optiunea -g este utila pentru pastrarea diferitelor simboluri pentru debugging.
razvan@ragnarok:~$ gcc -I. -I.. -Wall -o server server.c sock.c handler.c
aici se cauta in plus fata de caile implicite headere in directorul curent si directorul parinte.
razvan@ragnarok:~$ gcc -L. -o server server.o sock.o handler.o -lm -lmylib
la caile implicite de cautare a bibliotecilor se cauta si directorul curent; pe langa modulele obiect se mai face legarea cu biblioteca matematica (-lm) si o biblioteca locala (-lmylib).
Utilitarul make este utilizat pentru a determina dependentele dintre modulele unui program si de a genera comenzi de recompilare. Spre exemplu, daca am compilat fisierele server.c, sock.c si handle.c si modificam fisierul handle.c, utilitarul isi va da seama de acest lucru si va recompila numai handle.c pentru obtinerea noului executabil.
Inca de la aparitia lui, make a devenit un standard de facto in lumea Unix; in ciuda mai multor deficiente, el este inca folosit pe scara larga, mai ales datorita altor tool-uri din proiectul GNU: autoconf, automake.
La baza utilitarului sta un fisier makefile care contine reguli de compilare. Desi sunt recunoscute implicit mai multe nume (Makefile, GNUMakefile si makefile - pentru GNU make, versiunea cea mai utilizata), se prefera folosirea Makefile, deoarece apare proeminent la listarea continutului unui director.
Un fisier Makefile este format din reguli si definiri; o regula este, la randul ei formata dintr-o linie de dependente si 0 sau mai multe actiuni (comenzi). Pentru o mai buna intelegere a acestora vom utiliza urmatorul exemplu:
# # exemplu Makefile # CC = gcc # compilatorul folosit CFLAGS = -Wall -pedantic -ansi # optiunile pentru compilare LDFLAGS = -lefence # optiunile pentru linking # creeaza executabilele client si server all: client server # leaga modulele client.o user.o sock.o in executabilul client client: client.o user.o sock.o log.o $(CC) $(LDFLAGS) -o $@ $^ # leaga modulele server.o cli_handler.o sock.o in executabilul server server: server.o cli_handler.o sock.o log.o $(CC) $(LDFLAGS) -o $@ $^ # compileaza fisierul client.c in modulul obiect client.o client.o: client.c sock.h user.h log.h $(CC) $(CFLAGS) -c $< # compileaza fisierul user.c in modulul obiect user.o user.o: user.c user.h $(CC) $(CFLAGS) -c $< # compileaza fisierul sock.c in modulul obiect sock.o sock.o: sock.c sock.h $(CC) $(CFLAGS) -c $< # compileaza fisierul server.c in modulul obiect server.o server.o: server.c cli_handler.h sock.h log.h $(CC) $(CFLAGS) -c $< # compileaza fisierul cli_handler.c in modulul obiect cli_handler.o cli_handler.o: cli_handler.c cli_handler.h $(CC) $(CFLAGS) -c $< # compileaza fisierul log.c in modulul obiect log.o log.o: log.c log.h $(CC) $(CFLAGS) -c $< clean: rm -fr *~ *.o server client
Pe acest exemplu, vom descrie in detaliu structura unui fisier Makefile.
Comentariile intr-un fisier Makefile incep de la caracterul # si dureaza pana la sfasitul liniei.
Dupa liniile cu comentariu urmeaza trei linii de definire; aici se specifica compilatorul (definit in variabila CC), optiunile pentru compilare (CFLAGS) si cele pentru linking (LDFLAGS). Compilatorul folosit este gcc, optiunile sunt -Wall -pedantic -ansi, iar pentru legare folosim biblioteca libefence. Electric Fence este un utilitar care ajuta la prevenirea erorilor de alocare sau eliberare a zonelor de memorie; intrucat este usor de folosit (trebuie doar legat la executabil), se recomanda folosirea lui.
Ceea ce urmeaza acum sunt regulile. Regulile au o sintaxa de forma:
TARGET: PREREQUISITES COMMANDS
TARGET este in general numele unui fisier (executabil, obiect) care se doreste obtinut. PREREQUISITES sunt fisiere care sunt necesare pentru obtinerea fisierului specificat de target. COMMAND este comanda (comenzile) executate de make pentru obtinerea fisierului indicat de TARGET. IMPORTANT: fiecare comanda incepe cu caracterul TAB.
In lipsa specificarii unei tinte la rularea make, se va considera prima regula intalnita. De aceea se obisnuieste folosirea unui ``phony target'' cu numele all ca prima regula; acesta va avea ca dependente executabilele ce se doresc obtinute. Obtinerea executabilului client necesita existenta modulelor obiect client.o, sock.o, user.o, log.o; actiunea necesara pentru obtinerea executabilului este legarea acestor module. Linia
Pentru obtinerea modulelor obiect, sintaxa este aceeasi: TARGET este numele fisierul obiect iar PREREQUISITES contine fisierul sursa care va fi compilat si fisierele header folosite. Comanda
Daca la un moment de timp, se va modifica fisierul user.c, atunci pasii urmati de make vor fi urmatorii (la rularea comenzii $ make):
Fisierul Makefile in forma simplificata va fi asadar:
# # exemplu Makefile - forma uzuala (simplificata) # CFLAGS = -Wall -pedantic -ansi LDFLAGS = -lefence all: client server client: client.o sock.o user.o log.o $(CC) $(LDFLAGS) -o $@ $^ server: server.o sock.o cli_handler.o log.o $(CC) $(LDFLAGS) -o $@ $^ client.o: sock.h user.h log.h user.o: user.h sock.o: sock.h log.o: log.h server.o: sock.h cli_handler.h log.h cli_handler.o: cli_handler.h clean: rm -fr *~ *.o client server
Informatii exhaustive despre utilitarul make gasiti accesand paginile de documentatie info
GDB este debugger-ul implicit pe distributiile Linux. Rolul unui debugger este de a permite programatorului sa vizualizeze ceea ce se intampla in cadrul unui program in timp ce se executa sau ce anume s-a intamplat inainte ca un program sa se termine cu eroare.
Dificultatea utilizarii GDB consta in faptul ca acesta este un depanator in linie de comanda si este mult mai putin intuitiv de folosit ca un depanator cu interfata grafica. Se recomanda utilizarea optiunii -g in momentul compilarii si legarii programelor pentru a furniza informatii suplimentare depanatorului.
Pentru depanarea unui program se va rula comanda:
quit - parasirea depanatorului run [arg list] - rularea programului depanat cu lista de argumente bt - (backtrace) afisarea continutului stivei programului print expr - afisarea valorii unei expresii break [file:]function - breakpoint in cadrul unei functii c - continuarea rularii programului dupa un breakpoint next - executia urmatoarei linii din program (pentru executie pas cu pas) help [command] - afisarea de informatii generale despre utilizarea GDB sau despre utilizarea unei comenzi
Vom prezenta un exemplu de utilizare a depanatorului pentru un program care genereaza segmentation fault. Programul folosit este prezentat mai jos
#include <stdio.h> #include <string.h> int main (void) { char *s; /* copiere intr-un sir care nu este alocat -> seg fault */ strcpy (s, "Hello, World!\n"); printf (s); return 0; }
Compilarea si depanarea acestui program decurge ca mai jos:
razvan@ragnarok:~/cfiles/solab/lab1$ vi gdb_test.c razvan@ragnarok:~/cfiles/solab/lab1$ gcc -Wall -pedantic -ansi -o gdb_test gdb_test.c razvan@ragnarok:~/cfiles/solab/lab1$ ./gdb_test Segmentation fault razvan@ragnarok:~/cfiles/solab/lab1$ gdb ./gdb_test GNU gdb 6.3-debian Copyright 2004 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i386-linux"...Using host libthread_db library "/lib/tls/libthread_db.so.1". (gdb) break main Breakpoint 1 at 0x80483ca (gdb) run Starting program: /home/razvan/cfiles/solab/lab1/gdb_test Breakpoint 1, 0x080483ca in main () (gdb) step Single stepping until exit from function main, which has no line number information. Program received signal SIGSEGV, Segmentation fault. 0xb7f1ed16 in strcpy () from /lib/tls/libc.so.6 (gdb) bt #0 0xb7f1ed16 in strcpy () from /lib/tls/libc.so.6 #1 0x080483e7 in main () (gdb) kill Kill the program being debugged? (y or n) y (gdb) quit razvan@ragnarok:~/cfiles/solab/lab1$
In UNIX/Linux, totul este un fisier (de fapt, aproape totul este un fisier). Cu alte cuvinte, marea parte a instructiunilor de mai jos vor putea fi folosite nu numai pe fisierele existente fizic pe disc, ci si pe dispozitive (pentru comunicatia pe seriala spre exemplu), pe terminale, si pe socketi. Un ``fisier'' in lumea UNIX poate fi:
Intr-o definitie simplificata un proces este un program aflat in executie, deci o entitate dinamica spre deosebire de un fisier executabil care rezida pe disc.
Din punctul de vedere al utilizatorului, un fisier poate fi manipulat prin intermediul unui handler denumit descriptor de fisier. La nivelul sistemului de operare acesta este o valoarea intreaga pozitiva. La nivelul unui preoces exista o tabela de descriptori de fisiere (tabela FILE0). Aceasta tabela are o dimensiune limitata (de obicei 1024). Fiecare intrare in aceasta tabela este un pointer la o structura ce descrie fisierul utilizat, sau NULL daca descriptorul nu indica un fisier valid.
In cadrul tabelei de descriptori a unui proces primele trei intrari sunt rezervate si sunt initializate la crearea procesului
0 - intrarea standard (stdin); valoarea este stocata de macro-ul STDIN_FILENO; 1 - iesirea standard (stdout); valoarea este stocata de macro-ul STDOUT_FILENO; 2 - eroarea standard (stderr); valoarea este stocata de macro-ul STDERR_FILENO;
Atunci cand se deschide un nou fisier se va aloca prima intrare libera din tabela de descriptori. Pentru primul fisier deschis aceasta intrare va fi intrarea 3 din tabela.
Vom prezenta in continuare, operatiile de I/E pe fisiere de nivel 1 (sau operatii sincrone). Se numesc operatii de nivel 1 deoarece un apel al unei astfel de functii conduce, in general, la folosirea unui apel de sistem pus la dispozitie de kernel. Denumirea de operatii sincrone provine de la faptul ca o cerere de scriere/citire intr-un/dintr-un fisier va rezulta in scrierea informatiei pe disc si asteptarea (in blocare) la terminarea acesteia. Operatiile asincrone (de genul fputs, fgets, fprintf, fread, etc.) au o functionare buffer-ata si se vor intoarce inainte ca informatia sa fie efectiv scrisa pe disc.
Pentru cea mai mare parte a acestor operatii o valoare pozitiva intoarsa inseamna succes, iar o valoare negativa inseamna eroare. Detalii despre eroare se pot determina prin inspectarea variabilei globale externe errno accesibila prin includerea fisierului header standard errno.h.
Apelul open este folosit pentru a deschide (eventual si a crea) un fisier. Se intoarce descriptorul asociat acelui fisier daca nu am avut eroare, sau o valoare negativa in caz de eroare. Descriptorul utilizat este cel cu valoarea cea mai mica posibila (care nu este asociat altui fisier) din cadrul tabelei de descriptori a procesului.
Sintaxa de apel este
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int open (const char *pathname, int flags); int open (const char *pathname, int flags, mode_t mode);
A doua forma este folosita atunci cand se creeaza fisierul si se doreste a fi prezentate drepturile cu care va fi creat.
pathname este calea catre fisierul care va fi deschis; poate fi in forma relativa la directorul curent sau forma absoluta;
flags reprezinta o serie de indicatori pentru a indica modul in care trebuie descris fisierul; acesta poate fi o combinatie de mai multe optiuni ce pot fi folosite impreuna (bitwise OR); o optiune trebuie sa fie neaparat una si numai una dintre
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> ... int fd; /* * deschidem fisierul pentru scriere; dorim sa cream fisierul si * punem conditia sa nu existe deja */ fd = open ("myfile.txt", O_WRONLY | O_CREAT | O_EXCL, 0644); if (fd < 0) { perror ("open"); exit (EXIT_FAILURE); } ... ... int fd2; /* * fisier simplu deschis pentru citire */ fd = open ("myotherfile.txt", O_RDONLY); if (fd < 0) { perror ("open"); exit (EXIT_FAILURE); } ...
Operatiile close inchide un descriptor de fisier. In acest fel el nu va mai referi nici un fisier si va putea fi refolosit. Sintaxa de apel este
#include <unistd.h> int close (int fd);
fd este descriptorul de fisier care se doreste inchis.
Operatiile read si write sunt folosite pentru a citi informatie dintr-un fisier intr-un buffer local, respectiv de a scrie informatia din buffer-ul local in fisier. Sintaxele de apel pentru cele doua operatii sunt
#include <unistd.h> ssize_t read (int fd, void *buf, size_t count); ssize_t write (int fd, const void *buf, size_t count);
Operatia lseek repozitioneaza cursorul de fisier, astfel incat o operatie read/write va lucra incepand cu aceasta noua pozitie. Sintaxa de apel este
#include <sys/types.h> #include <unistd.h> off_t lseek (int fildes, off_t offset, int whence);
fildes este descriptorul de fisier pentru care vom repozitiona cursorul;
offset este offset-ul repozitionarii (poate fi pozitiv sau negativ);
whence este o directiva care specifica de unde se incepe repozitionarea; poate avea valorile:
Exemplu de utilizare:
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> ... int fd; char buf[5]; /* * deschidem fisierul in modul APPEND (cursorul e la sfarsitul * fisierului) */ fd = open ("myfile.txt", O_RDWR | O_APPEND); if (fd < 0) { perror ("open"); exit (EXIT_FAILURE); } /* ne pozitionam inaintea ultimilor 4 octeti din fisier */ if (lseek (fd, -4, SEEK_CUR) < 0) { perror ("open"); exit (EXIT_FAILURE); } /* citim ultimii 4 octeti din fisier */ if (read (fd, buf, 4) < 0) { perror ("read"); exit (EXIT_FAILURE); } ...
O bucata de cod cu aceeasi functionalitate ca cea de mai sus este prezentata in continuare:
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> ... int fd; /* * fisierul este deschis pentru citire/scriere; cursorul este * pozitionat la inceputul fisierului */ fd = open ("myfile.txt", O_RDWR); if (fd < 0) { perror ("open"); exit (EXIT_FAILURE); } /* ne pozitionam inainte ultimilor 4 octeti din fisier */ if (lseek (fd, -4, SEEK_END) < 0) { perror ("lseek"); exit (EXIT_FAILURE); } /* citim ultimii octeti din fisier */ if (read (fd, buf, 4) < 0) { perror ("read"); exit (EXIT_FAILURE); } ...
Operatiile dup/dup2 au ca rezultat crearea unui duplicat al unui descriptor de fisier, in asa fel incat vechiul descriptor sau noul descriptor pot fi folositi oricand si in orice ordine, avand cea mai mare parte din caracterisitici identice.
Sintaxa de apel este
#include <unistd.h> int dup (int oldfd); int dup2 (int oldfd, int newfd);
dup va folosi descriptorul de fisier cu indexul cel mai mic posibil pentru noul descriptor; dup2 va duplica descriptorul oldfd in descriptorul newfd; daca newfd era un descriptor pentru un fisier deschis, acesta va fi inchis. Ambele intorc noul descriptor creat (in cazul lui dup2 chiar newfd), in caz de succes, sau -1, in caz de eroare.
Pentru a duplica spre exemplu un fisier in intrarea standard (stdin), adica o citire de la stdin sa insemne o citire din fisier, putem folosi dup sau dup2, ca mai jos:
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> ... int fd; fd = open ("myfile.txt", O_RDONLY); if (fd < 0) { perror ("open"); exit (EXIT_FAILURE); } /* * inchidem intrarea standard; aceasta are indexul 0 in tabela * de descriptori a procesului deci un apel dup va folosi acest * descriptor (este cel mai mic posibil) */ close (STDIN_FILENO); /* duplicam descriptorul fd in intrarea standard */ if (dup (fd) < 0) { perror ("dup"); exit (EXIT_FAILURE); } ... /* * alternativ, putem folosi dup2 fara a mai inchide explicit * intrarea standard */ if (dup2 (fd, 0) < 0) { perror ("dup2"); exit (EXIT_FAILURE); } ...
Trebuie retinut faptul ca o operatie dup conserva cursorul de fisier. Astfel, orice schimbare efectuata asupra cursorului de fisier pentru descriptorul original va afecta in mod identic cel de-al doilea descriptor. Astfel, in cadrul urmatorului exemplu,
... int fd1, fd2, fd3; char buf1[5], buf2[5]; fd1 = open ("myfile.txt", O_RDONLY); fd2 = dup (fd1); fd3 = dup (fd2); lseek (fd3, 1024, SEEK_SET); read (fd1, buf1, 4); read (fd2, buf2, 4); ...
se vor citi 4 caractere din fisierul myfile.txt incepand cu
al 1024-lea caracter si, apoi, alte 4 caractere incepand cu cel de-al
1028-lea caracter.
Informatii complete despre operatiile I/E sincrone
gasiti accesand pagina de manual info. Folositi comanda
Pentru lucrul cu directoare exista functii specializate care lucreaza asupra unei structuri specializate de tip stream, denumita DIR (echivalentul FILE). Exista functii de conversie a acestei structuri intr-un descriptor de fisier, insa descriptorul nu poate fi folosit decat in cazul unor functii care pot lucra si pe directoare (gen fstat sau fchdir). Cele mai importante functii pentru lucrul cu directoare sunt prezentate in continuare.
Functia opendir este folosita pentru a deschide un stream pentru un director. Stream-ul este pozitionat la prima intrare din director. Sintaxa de apel este
#include <sys/types.h> #include <dirent.h> DIR *opendir (const char *name);
name este numele directorului care se incearca a fi deschis; se intoarce un pointer la stream-ul de director, sau NULL in caz de eroare.
Functia closedir este folosita pentru a inchide un stream pentru un director. Sintaxa de apel este
#include <sys/types.h> #include <dirent.h> int closedir (DIR *dir);
Functia readdir este folosita pentru a citi informatii despre continutul unui director. Sintaxa de apel este
#include <sys/types.h> #include <dirent.h> struct dirent *readdir (DIR *dir);
Functia intoarce un pointer la o structura struct dirent (directory entry). Aceasta reprezinta urmatoarea intrare in cadrul stream-ului de director indicat de dir. Functia intoarce NULL cand s-a ajuns la sfarsit sau in caz de eroare. Structura struct dirent are formatul (simplificat) de mai jos:
struct dirent { unsigned char d_type; /* type of file */ char d_name[256]; /* filename */ }
Campul d_type indica tipul intrarii din director. Acesta poate avea una din valorile:
DT_BLK - fisier de tipul block device DT_CHR - fisier de tipul character device DT_DIR - director DT_FIFO - FIFO (pipe cu nume) DT_LNK - link simbolic DT_REG - fisier obisnuit (regular file) DT_SOCK - socket DT_UNKNOWN - tip necunoscut
Functia rewinddir este folosita pentru a se intoarce la inceputul stream-ului de director. Sintaxa de apel este
#include <sys/types.h> #include <dirent.h> void rewinddir (DIR *dir);
Functia dirfd este folosite pentru a face conversia dintr-un stream director intr-un descriptor in cadrul tabelei de descriptori, dar utilitatea unui astfel de descriptor este limitata. Sintaxa de apel este
#include <sys/types.h> #include <dirent.h> int dirfd (DIR *dir);
Vom prezenta in continuare doua exemple care sa reflecte utilizarea operatiilor sincrone si a celor de lucru pe directoare.
Primul exemplu este o versiune simplificata a utilitarului cat (denumit mycat).
/* * mycat.c - versiune simplificata a utilitarului cat pentru afisarea * continutului unui fisier */ #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #define BUF_SIZE 1024 /* dimensiunea buffer-ului */ int main (int argc, char **argv) { int fd; /* descriptorul de fisier */ char buf[BUF_SIZE]; /* buffer pentru citire */ int n_bytes; /* contorul pentru octeti cititi */ /* * trebuie sa avem un singur argument din linie de comanda: * numele fisierului care dorim sa-l afisam */ if (argc != 2) { fprintf (stderr, "Numar necorespunzator de argumente.\n"); exit (EXIT_FAILURE); } /* deschidem fisierul cu numele primit ca parametru */ fd = open (argv[1], O_RDONLY); if (fd < 0) { perror ("open"); exit (EXIT_FAILURE); } /* * citim din fisier in ciclu infinit; ne oprim atunci cand * am atins sfarsitul fisierului */ while (1) { n_bytes = read (fd, buf, BUF_SIZE); if (n_bytes < 0) { perror ("read"); exit (EXIT_FAILURE); } /* am atins sfarsitul fisierului */ if (n_bytes == 0) break; /* * afisam buffer-ul la iesirea standard; afisam cati * octeti am citit */ write (STDOUT_FILENO, buf, n_bytes); } close (fd);/* inchidem fisierul */ return 0; }
Cel de-al doilea exemplu este o versiune simplificata a utilitarului ls (denumit myls).
/* * myls.c - versiune simplificata a utilitarului ls pentru afisarea * continutului unui director */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <dirent.h> #include <unistd.h> int main (int argc, char **argv) { DIR *dir; /* structura pentru stream-ul director */ struct dirent *dirent; /* intrarea dintr-un director */ /* * verificam numarul de argumente din linia de comanda; * daca nu avem nici un argument (argc == 1) atunci * se va afisa continutul directorului curent (".") * daca avem un argument atunci acesta va fi directorul * al carui continut il vom afisa; * daca avem mai multe argumente se considera eroare */ switch (argc) { case 1: dir = opendir ("."); if (dir == NULL) { perror ("opendir"); exit (EXIT_FAILURE); } break; case 2: dir = opendir (argv[1]); if (dir == NULL) { perror ("opendir"); exit (EXIT_FAILURE); } break; default: fprintf (stderr, "Numar necorespunzator de argumente.\n"); exit (EXIT_FAILURE); } /* * avem directorul deschis si incepem sa citim toate intrarile * din cadrul acestuia si sa le afisam la iesirea standard; * ne oprim in momentul in care citim NULL, adica am ajuns * la sfarsitul stream-ului director */ while (1) { dirent = readdir (dir); if (dirent == NULL) /* am ajuns la sfarsit */ break; /* afisam informatia la iesirea standard */ write (STDOUT_FILENO, dirent->d_name, strlen (dirent->d_name)); write (STDOUT_FILENO, "\n", 1); } closedir (dir); return 0; }
In cele ce urmeaza vom descrie o serie de functii si structuri specifice dispozitivelor terminale. Terminalul este un dispozitiv, de obicei echipat cu tastatura si monitor, care permite interactiunea utilizatorului cu sistemul.
In Linux exista doua moduri de intrare pentru un terminal:
Toate informatiile referitoare la un dispozitiv terminal sunt stocate intr-o structura denumita struct termios. Aceasta are forma urmatoare
struct termios { ... tcflag_t c_iflag; /* input modes */ tcflag_t c_oflag; /* output modes */ tcflag_t c_cflag; /* control modes */ tcflag_t c_lflag; /* local modes */ cc_t c_cc[NCSS]; /* control chars */ }
Structura poate avea si alte campuri in afara celor prezentate, dar cele de aici sunt obligatorii. Tipul tcflag_t este o masca de biti. Fiecare bit controleaza o anumita caracteristica. Tipul cc_t este folosit pentru a reprezenta valorile unor caractere care indeplinesc diverse functionalitati in cadrul terminalului. Elementul c_cc[VMIN] reprezinta directiva MIN iar elementul c_cc[VTIME] reprezinta directiva TIME. Macro-ul NCCS reprezinta numarul de elemente din vectorul c_cc.
Ne intereseaza numai componenta c_lflag din cadrul celor patru campuri de tip tcflag_t pentru ca aceasta controleaza modurile (vom dori sa lucram in mod noncanonic).
Directivele MIN si TIME (deci elementele c_cc[VMIN] si c_cc[VTIME]) sunt folosite pentru a determina comportamentul unui apel read in mod necanonic. Directiva MIN precizeaza numarul minim de octeti care trebuie sa existe la intrare pentru ca un apel read sa se intoarca. Directiva TIME precizeaza cat timp trebuie sa se astepte primirea numarului de octeti cerut pana la intoarcerea apelului read, in zecimi de secunda (0.1 secunde). In functie de valorile acestor primitive se disting urmatoarele cazuri:
Functia ioctl manipuleaza diversi parametri de nivel scazut pentru fisierele speciale (terminale, socketi) si diverse dispozitive. Intrucat nu ar fi eficient crearea unor functii pentru fiecare caz sau dispozitiv, apelul ioctl multiplexeaza toate aceste operatii prin intermediul unor coduri numerice reprezentate de al doilea parametru din apel. Sintaxa de apel este
#include <sys/ioctl.h> int ioctl (int d, int request, ...);
Functia poate avea doi sau trei parametri. Primul parametru (d) trebuie sa fie un descriptor de fisier valid. Al doilea parametru este o cerere care este dependenta de dispozitiv si este reprezentat de un macro care defineste simplificat rolul indeplinit; o lista cu valorile posibile pentru al doilea parametru gasiti prin accesarea paginii de manual ($ man 2 ioctl_list). In functie de acest parametru este posibila prezenta parametrului trei sau nu. Acesta este, in general, un pointer la o structura specfica dispozitivului (in cazul terminalului aceasta este chiar struct termios).
Pentru ceea ce ne intereseaza avem doua valori posibile pentru parametrul 2 al apelului ioctl:
Mai jos este prezentat un exemplu care implementeaza un echivalent al functiei getch din DOS, folosind apelul ioctl. Exemplul citeste fara ecou caractere de la terminal si se opreste in momentul in care se apasa tasta 'q'.
/* * exemplu cu implementarea getch folosind ioctl pentru controlul * terminalului */ #include <stdio.h> #include <string.h> #include <stdlib.h> #include <sys/types.h> #include <sys/ioctl.h> #include <unistd.h> #include <termios.h> struct termios old_termios, new_termios; /* * stabilirea parametrilor structurii termios pentru lucrul * in modul necanonic fara ecou */ void make_raw (struct termios *termios_p) { /* * ~ICANON este o valoare in care toti bitii sunt 1, mai * putin bitul corespunzator lui ICANON; facand aici un * si, bitul ICANON este pozitionat pe 1 si dezactiveaza * astfel modul canonic (vom ajunge astfel in modul * non-canonic) */ termios_p->c_lflag &= (~ICANON); /* echo OFF - nu dorim ecou la apasarea unei taste */ termios_p->c_lflag &= (~ECHO); /* * vom pune MIN pe 1 si TIME pe 0 -> asteptare nedefinita * pana la aparitia unui caracter */ termios_p->c_cc[VMIN] = 1; termios_p->c_cc[VTIME] = 0; } /* * actualizare terminal pentru lucrul in modul necanonic fara ecou */ void start_getch (void) { /* retinem in old_termios starea curenta a terminalului */ if (ioctl (STDIN_FILENO, TCGETS, &old_termios) < 0) { perror ("ioctl"); exit (EXIT_FAILURE); } /* copiem in new_termios starea terminalului pentru actualizare */ memcpy (&new_termios, &old_termios, sizeof (struct termios)); /* stabilire mod necanonic fara ecou */ make_raw (&new_termios); /* actualizare terminal cu noile caracteristici */ if (ioctl (STDIN_FILENO, TCSETS, &new_termios) < 0) { perror ("ioctl"); exit (EXIT_FAILURE); } } /* * revenire la modul de lucru initial */ void stop_getch (void) { /* readucem terminalul in starea descrisa de old_termios */ if (ioctl (STDIN_FILENO, TCSETS, &old_termios) < 0) { perror ("ioctl"); exit (EXIT_FAILURE); } } /* * functia getch citeste un caracter de la terminal si se intoarce; * citirea este fara ecou (conform actualizarii terminalului) */ int getch (void) { char c; read (STDIN_FILENO, &c, 1); return c; } /* * in cadrul functiei main se citesc caractere in mod necanonic si * se afiseaza pe ecran; ne oprim la apasarea tastei 'q' sau 'Q' */ int main (void) { char c; start_getch (); /* intram in mod necanonic, fara ecou */ /* citim caractere pana cand se citeste 'q' sau 'Q' */ while (1) { c = getch (); /* afisam pe ecran ce s-a apasat */ write (STDOUT_FILENO, "Ati apasat ", 11); write (STDOUT_FILENO, &c, 1); write (STDOUT_FILENO, "\n", 1); /* la 'q' sau 'Q' ne oprim */ if (c == 'q' || c == 'Q') break; } /* revenim la starea initiala a terminalului */ stop_getch (); return 0; }
Informatii despre apelurile POSIX pentru controlul terminalului pot fi obtinute prin consultarea paginii de manual ($ man 3 termios). Ne vor interesa functiile tcgetattr (echivalent cu un apel ioctl care primeste TCGETS ca al doilea parametru) si tcsetattr (echivalent cu TCSETS). Sintaxele de apel pentru cele doua functii sunt
#include <termios.h> #include <unistd.h> int tcgetattr (int fd, struct termios *termios_p); int tcsetattr (int fd, int optional_actions, constr struct termios termios_p);
fd este descriptorul de fisier pentru terminalul utilizat (in cazul lucrului pe intrarea standard acesta va fi 0 sau STDIN_FILENO);
termios_p este structura termios utilizata pentru memorarea informatiilor despre terminal;
optional_actions (numai la tcsetattr) specifica momentul in care se vor actualiza parametrii de functionare ai terminalului; poate avea valorile TCSANOW, TCSADRAIN, TCSAFLUSH (de regula se va folosi TCSANOW).
Desi aceste functii permit o interactiune mai facila si intuitiva cu terminalul, exista unele terminale pentru care functionarea corecta este dependenta de folosirea unor apeluri ioctl.
Exemplul prezentat la ioctl transcris in apeluri POSIX (numai functiile start_getch si stop_getch) este:
/* * actualizare terminal pentru lucrul in modul necanonic fara ecou */ void start_getch (void) { /* retinem in old_termios starea curenta a terminalului */ if (tcgetattr (STDIN_FILENO, &old_termios) < 0) { perror ("tcgetattr"); exit (EXIT_FAILURE); } /* copiem in new_termios starea terminalului pentru actualizare */ memcpy (&new_termios, &old_termios, sizeof (struct termios)); /* stabilire mod necanonic fara ecou */ make_raw (&new_termios); /* actualizare terminal cu noile caracteristici */ if (tcsetattr (STDIN_FILENO, TCSANOW, &new_termios) < 0) { perror ("tcsetattr"); exit (EXIT_FAILURE); } } /* * revenire la modul de lucru initial */ void stop_getch (void) { /* readucem terminalul in starea descrisa de old_termios */ if (tcsetattr (STDIN_FILENO, TCSANOW, &old_termios) < 0) { perror ("tcsetattr"); exit (EXIT_FAILURE); } }
Comanda stty este folosita pentru controlul terminalului direct prin intermediul intepretorului de comenzi (shell-ului). Folosirea acesteia fara nici un parametru conduce la afisarea caracteristicilor curente ale terminalului.
razvan@ragnarok:~/cfiles/solab/lab1$ stty speed 38400 baud; line = 0; eol = M-^?; eol2 = M-^?; ixany
Comanda poate fi urmata de o serie de optiuni care corespund optiunilor posibile pentru manipularea structurii termios (ignbrk, inclr, opost, ofill, icanon, echo, etc.). Multe din aceste optiuni pot fi precedate de '-' pentru a indica lipsa acestora. Spre exemplu daca dorim sa lucram in modul noncanonic vom folosi comanda
/* * actualizare terminal pentru lucrul in modul necanonic fara ecou */ void start_getch (void) { system ("stty -icanon -echo min 1 time 0"); } /* * revenire la modul de lucru initial */ void stop_getch (void) { system ("stty icanon echo"); }
O mare parte din programe (in special cele folosite pentru testare de algoritmi sau a altor programe) folosesc, ca si date de intrare, informatii generate aleator.
In Linux exista doua modalitati de obtinere a numere generate aleator:
Apelurile POSIX sunt folosite pentru generare de numere pseudo-aleatoare. Denumirea de numere pseudo-aleatoare provine din faptul ca numerele sunt generate conform unor algoritmi care genereaza numere ``aleatoare'' prin probabilitatea lor de aparitie in cadrul unui interval dat. Intotdeauna se porneste de la o samanta (seed) care va constitui pasul de inceput de unde se aplica algoritmul. Sintaxa de apel pentru cele doua functii utilizate pentru obtinerea de numere pseudo-aleatoare este:
#include <stdlib.h> int rand (void); void srand (unsigned int seed);
Functia rand genereaza unui numar pseudo-aleator in intervalul [0, RAND_MAX].
Functia srand are ca parametru samanta folosita pentru a genera o noua secventa de numere aleatoare. Deoarece algoritmul de obtinere are pasi bine precizati, apelurile repetate ale srand pentru aceeasi samanta vor genera aceeasi secventa de numere. De aceea, daca in cadrul aceluiasi program dorim ca la rulari repetate sa generam secvente diferite de numere aleatoare, va trebui sa utilizam o valoare diferita pentru seed. Acest lucru se realizeaza foarte simplu prin obtinerea timpului curent in format intreg. Prin urmare, un apel obisnuit al functiei srand va fi
#include <stdlib.h> #include <time.h> ... srand (time (NULL)); ...
Functia time intoarce timpul de la inceputul erei UNIX (1 ianuarie 1970) pana acum, in secunde ($ man 2 time). Intrucat acesta va fi diferit de la apel la apel, va genera secvente diferite de fiecare data.
In continuare este prezentat codul pentru doua fisiere (unul header) pentru generarea de numere aleatoare intr-un interval dat.
/* * myrand.h * fisier header cu prototipuri de functii pentru generare de numere * aleatoare */ #ifndef _MYRAND_H #define _MYRAND_H 1 void init_seed (void); int get_rand (unsigned int max); #endif /* * myrand_rand.c * fisier ce contine funtii de lucru cu numere aleatoare generate * prin intermediul apelurilor rand si srand */ #include <stdlib.h> #include <time.h> /* initializare seed */ void init_seed (void) { srand (time (NULL)); } /* obtinere numar pseudo-aleator in intervalul [0, max) */ int get_rand (unsigned int max) { return rand () % max; }
Dispozitivul special (de tip caracter) /dev/random poate fi folosit pentru generarea de numere complet aleatoare. La baza lui sta un pool de numere care sunt obtinute din operatiile efectuate de catre kernel asupra diverselor dispozitive hardware cu functionare asincrona (gen harddisk, floppy, seriala, placa de retea). Diferitele semnale trimise de acestea vin la intervale neregulate si complet independente de timp constituind baza pentru obtinerea unui intreg set de numere generate aleator.
Citirea de astfel de numere se face direct din dispozitiv (cu apeluri de tipul read). Intrucat la baza acestuia se afla un pool de numere generate aleator, exista posibilitatea (desi infima) ca acesta sa se goleasca, spre deosebire de apelul rand, unde algoritmul genereaza fara oprire un alt numar pseudo-aleator. Acelasi exemplu de fisiere prezentat mai sus a fost rescris pentru utilizarea dispozitivului /dev/random.
/* * fisier ce contine funtii de lucru cu numere aleatoare generate * prin intermediul apelurilor rand si srand */ #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> void init_seed (void) { } /* * obtinem numarul aleator citind din /dev/random; apoi il calibram * in intervalul [0, max) */ int get_rand (unsigned int max) { int fd; unsigned int num; fd = open ("/dev/random", O_RDONLY); if (fd < 0) { perror ("open"); exit (EXIT_FAILURE); } if (read (fd, &num, sizeof (num)) < 0) { perror ("read"); exit (EXIT_FAILURE); } close (fd); return num % max; }
Un fisier de test simplu pentru functiile de generare de numere aleatoare (atat varianta POSIX cat si cea cu /dev/random) ar fi
/* * caz de test pentru numere (pseudo)aleatoare */ #include <stdio.h> #include <stdlib.h> #include "myrand.h" /* * vom genera o serie de 4 numere aleatoare si le vom afisa * la iesirea standard */ int main (void) { int i; init_seed (); for (i = 0; i < 4; i++) printf ("Numar obtinut: %d\n", get_rand (100)); return 0; }
Desi folosirea fisierului special /dev/random produce numere total aleatoare, overhead-ul implicat de citirea informatiilor, comparativ cu simple operatii aritmetice folosite de apelurile POSIX, il fac o optiune tentanta numai in situatiile in care folosirea numerelor pseudo-aleatoare nu este satisfacatoare.
This document was generated using the LaTeX2HTML translator Version 2002-2-1 (1.71)
Copyright © 1993, 1994, 1995, 1996,
Nikos Drakos,
Computer Based Learning Unit, University of Leeds.
Copyright © 1997, 1998, 1999,
Ross Moore,
Mathematics Department, Macquarie University, Sydney.
The command line arguments were:
latex2html -no_subdir -split 0 -show_section_numbers /tmp/lyx_tmpdir7638JeFlvb/lyx_tmpbuf1/lab1.tex
The translation was initiated by Razvan Adrian Deaconescu on 2005-10-01