Sunteți pe pagina 1din 62

Laborator Sisteme de Operare.

Lucrarea 5

LUCRAREA 5

VIRTUALIZARE.

Virtualizarea este tehnologia ce permite crearea unei abstractizări a resurselor(hardware sau software) și
expunerea acestor resurse către alte resurse (sisteme de operare, aplicații). În contextul sistemelor de operare
virtualizarea presupune existența unui nivel ce poate expune resursele hardware disponibile către unui sau mai
multe sisteme de operare. Astfel prin intermediul unei mașini virtuale (Virtual Machine VM) se pot rula mai multe
sisteme de operare virtuale pe aceeași mașină fizică (host). Virtualizarea vine astfel cu o serie de avantaje precum:
- Scalabilitate: resursele hardware se pot aloca sistemelor de operare virtuale în funcție de cerințele de
performanță, sau mașinile virtuale se pot reloca pe un alt hardware
- Siguranță: se pot crea rapid copii de siguranță, restaura copiile de siguranță, mașinile virtuale pot migra
rapid pe alte sisteme hardware în caz de defecțiune
- Administrare facilă: prin intermediul aplicațiilor de management se pot administra cu ușurință mașini
virtuale multiple într-un mod centralizat, inclusiv se pot realiza rapid operații (re)alocare de resurse în
funcție de cerințe, realiza copii de siguranță, migra mașini virtuale pe alte sisteme, clona mașini virtuale,
etc

1. INSTALAREA HYPERV
Pentru realizarea acestei lucrări vom utiliza sistemul de operare Windows 10 și caracteristica acestuia pentru
gestionarea mașinilor virtuale HyperV.
HyperV reprezintă soluția Microsoft de virtualizare. Acesta a fost disponibilă începând cu sistemul de operare
Windows Server 2008 și Windows 8. HyperV este un hipervizor (hypervizor) ce poate rula/virtualiza sisteme de
operare pe platforme x86 și x64. Acesta rulează pe sisteme Windows (desktop sau server) dar poate gestiona
mașini virtuale eterogene [3]: sisteme Windows (sisteme desktop XP, 7, Vista, 8, 10 sau sisteme server 2003,
2008, 2012, 2016, 2019), sisteme Linux (Red Hat, Ubuntu, Debian, CentOS, FreeBSD, SUSE, Oracle Linux).

Notă – se va avea în vedere faptul că atât kit-ul de instalare al sistemului de operare Windows 10 (fișierul ISO)
cât și mașina virtuală aferentă unei instanțe de Windows 10 (fișierul VHD) ocupă spațiu semnificativ pe disk.
Astfel înainte de parcurgerea pașilor din lucrare să vă asigurați că aveți spațiu disponibil pe disk (minim 20GB).

Se verifică dacă sistemul permite virtualizare în managerul de activități:

Pagina 1
Laborator Sisteme de Operare. Lucrarea 5

- activare ->
Fig. 1. Verificarea suportului pentru virtualizare
Dacă nu este activată virtualizarea, se verifică în BIOS și se activează (Enable Virtualization Technology -
https://www.technorms.com/8208/check-if-processor-supports-virtualization). Este posibil ca după activarea
virtualizării în BIOS să fie necesară închiderea și repornirea PC-ului, restartul nefiind suficient.

Notă – activarea virtualizării trebuie realizată înaintea instalării HyperV. Dacă HyperV este deja instalat și nu
funcționează, acesta se dezinstalează, se activează virtualizarea și se reinstalează HyperV

Pentru instalarea HyperV se verifică cerințele necesare [2]. Pentru activarea caracteristicii HyperV, dacă nu este
deja activată, se accesează Control Panel -> Turn Windows features on or off [4]. Este necesară repornirea
calculatorului după activarea HyperV:

Fig. 2. Activarea caracteristicii HyperV


După repornirea calculatorului, managerul HyperV poate fi accesat în meniul Start:

Fig. 3. Pornirea managerului HyperV

Pagina 2
Laborator Sisteme de Operare. Lucrarea 5

Interfața managerului HyperV permite crearea și gestionarea mașinilor virtuale:

Fig. 4. Managerul HyperV

Pentru a instala un sistem de operare într-o mașină virtuală este necesar să aveti un kit de instalare CD/DVD sau
format ISO. Dacă nu aveți deja un kit de instalare, se poate folosi instrumentul de creare suporturi de instalare
de la Microsoft la adresa https://www.microsoft.com/ro-ro/software-download/windows10:

Fig. 5. Pagina Microsoft pentru descărcarea instrumentului de creare suporturi de instalare


După descărcarea instrumentului se pornește și se urmează pașii necesari pentru crearea unui fișier ISO pe disk
necesar în continuare pentru instalarea unei mașini virtuale Windows 10 în HyperV:

Pagina 3
Laborator Sisteme de Operare. Lucrarea 5

Fig. 6. Instrumentul Microsoft de creare suporturi de instalare


La final se va regăsi fișierul ISO în directorul specificat:

Fig. 7. Finalizarea descărcării fișierului ISO Windows 10


2. CREAREA UNEI MAȘINI VIRTUALE
Utilizând managerul HyperV și kit-ul de instalare pentru Windows 10 vom crea și configura o mașină virtuală
Windows 10 ce va rula în hipervizorul HyperV pe mașina fizică (hostul) Windows 10,
Se pornește managerul HyperV și se creează o nouă mașină virtuală:

Fig. 8. Crearea unei mașini virtuale utilizând managerul HyperV


Se urmăresc cu atenție pașii, acordând atenție resurselor alocate noii mașini virtuale (spațiu pe disk, RAM, CPU,
etc) și ținând cont de resursele minime necesare fiecărui sistem de operare (în carul Windows 10 64 bit: 2GB
RAM, 1CPU 1GHz, 20GB disk):

Pagina 4
Laborator Sisteme de Operare. Lucrarea 5

Fig. 9. Crearea unei mașini virtuale utilizând managerul HyperV


La finalizarea configurării noua mașină virtuală va apărea în consola de management HyperV:

Fig. 10. Mașina virtuală în managerul HyperV


Ne vom conecta la noua mașină virtuală prin click dreapta și alegerea opțiunii Connect:

Fig. 11. Conectarea la mașina virtuală


Se va conecta la mașina virtuală și se va porni:

Pagina 5
Laborator Sisteme de Operare. Lucrarea 5

Fig. 11. Conectarea la mașina virtuală și instalarea sistemului de operare

Se va instala Windows 10 în această mașină virtuală:

......
Fig. 12. Instalarea sistemului de operare
Se observă proprietățile sistemului de operare virtual (memorie, disk, procesoare), acestea respectă setările de
la crearea mașinii virtuale:

Pagina 6
Laborator Sisteme de Operare. Lucrarea 5

Fig. 13. Setările mașinii virtuale

EXERCIȚIUL 1:
Modificați resursele alocate mașinii virtuale (RAM, CPU), reporniți și verificați ulterior în mașina virtuală Windows
resursele utilizate.

Unul din avantajele virtualizării îl reprezintă ușurința lucrului cu mașinile virtuale: backup, restaurare, multiplicare
(clonare), administrare centralizată, alocarea resurselor în mod dinamic. Astfel consola HyperV permite pornirea,
oprirea, backupul, restaurarea, copierea unei mașini virtuale.
De exemplu plecând de la o mașină virtuală gata configurată se pot crea mai multe mașini virtuale identice
(clonare). Din consola de management HyperV opriți mașina virtuala. Exportați mașina:

Fig. 14. Exportul unei mașini virtuale

Pagina 7
Laborator Sisteme de Operare. Lucrarea 5

Fig. 15. Importul unei mașini existente

Notă – se va avea în vedere faptul că operațiile cu mașini virtuale (export/import) durează mai multe minute și
ocupă aprox. 10GB spațiu pe disk.

După finalizarea cu succes a importului se poate redenumi noua clonă:

Fig. 16. Redenumirea unei mașini virtuale


După ce ambele mașini virtuale au fost pornite ne putem conecta la fiecare:

Pagina 8
Laborator Sisteme de Operare. Lucrarea 5

Fig. 17. Gestionarea mai multor mașini virtuale

EXERCIȚIUL 2 (OPȚIONAL):
Repetați pașii de mai sus folosind instrumental de virtualizare Oracle VM VirtualBox [5]. Înainte de instalarea
VirtualBox va trebui dezinstalat HyperV.

BIBLIOGRAFIE
[1]. Introduction to Hyper-V on Windows 10 https://docs.microsoft.com/en-us/virtualization/hyper-v-on-
windows/about/
[2]. Windows 10 Hyper-V System Requirements https://docs.microsoft.com/en-us/virtualization/hyper-v-on-
windows/reference/hyper-v-requirements
[3]. Supported OS https://docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/about/supported-
guest-os
[4]. Install Hyper-V on Windows 10 https://docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/quick-
start/enable-hyper-v
[5] Oracle VM VirtualBox https://www.virtualbox.org/

Pagina 9
Laborator Sisteme de Operare. Android

LUCRAREA 6

ANDROID

Android este un sistem de operare open source destinat în principal dispozitivelor mobile cu ecran tactil
(touchscreen) precum telefoane mobile sau tablete [1]. Versiuni speciale de Android sunt destinate și altor tipuri
de dispozitive precum ceasurile (dispozitive wereables), televizoarele, automobilele. De asemenea versiuni de
Android pot rula și pe dispozitive PC, console gaming, camere digitale.

1. ARHITECTURA ANDROID
Arhitectura sistemului Android conține următoarele componente:
- Kernel. Această componentă este bazată pe un nucleu Linux având rolul de a gestiona resursele hardware
și software precum procesorul, memoria ram, spațiul de stocare, procesele, fișierele, resursele,
dispozitivele I/O, conectivitatea.
- Nivelul de abstractizare (Hardware abstraction layer - HAL). Această componentă asigură interfața dintre
dispozitivele hardware produse de diverși producători și nivelele software superioare. HAL
definește/impune interfețe standard între dispozitivele hardware (de nivel scăzut) și cele software (de
nivel mai înalt).
- Serviciile sistem (System Services). Sunt servicii oferite de platforma Android pentru nivelele superioare.
Aceste servicii sunt grupate în două categorii: servicii sistem (search, notification, windows, etc) și servicii
media (camera, play, record, etc).
- Nivelul de legătură pentru comunicarea interproces (Binder Inter-Process Communication - IPC). Acesta
asigură abstractizarea nivelelor inferioare precum serviciile Android și punerea la dispoziție a acestora
pentru nivelul superior (cel de aplicație).
- Nivelul de aplicație. Acest nivel este de obicei expus către dezvoltatori sub forma unui API (Application
Programming Interface) pentru crearea și rularea aplicațiilor Android.

Pagina 1
Laborator Sisteme de Operare. Android

Fig. 1. Arhitectura sistemului Android [2]


Sistemul Android este un sistem open source, dezvoltarea acestuia este coordonată de către Android Open
Source Project (AOSP), condusă de Google. Această organizație are rolul de a se asigura că dispozitivele și
dezvoltările aduse sistemului Android îndeplinesc cerințele de compatibilitate, securitate necesare unui sistem
Android destinat milioanelor de utilizatori. Aceasta se adresează comunității Android, compusă din utilizatori,
dezvoltatori, producători hardware pentru:
- Crearea unui mediu coerent și unitar atât la nivel software cât și la nivel hardware necesar dezvoltatorilor
de aplicații.
- Crearea unei experiențe unitare pentru utilizatorii dispozitivelor și aplicațiilor Android.
- Sprijin acordat producătorilor pentru crearea de dispozitive diferite dar totuși compatibile cu celelalte
dispozitive, cu sistemul Android și aplicațiile Android.
- Minimizarea costurilor de dezvoltare și compatibilizare prin punerea la dispoziție a unor unelte de testare
gratuite.
Dezvoltatori sau companiile pot participa la dezvoltarea sistemului Android prin înscrierea în programul Android
Compatibility Program (https://source.android.com/compatibility/overview). Aceștia pot dezvolta dispozitive
hardware sau pot contribui la dezvoltarea componentelor sistemului Android (kernel, HAL, etc.).
Dar Android este mai mult decât un sistem de operare, este un ecosistem bazat pe dispozitivele compatibile
Android și pe aplicațiile dezvoltate pentru platforme Android având ca punct central utilizatorul, care
beneficiază atât de dispozitivele hardware Android cât și de aplicațiile ce rulează pe dispozitivele Android:

Pagina 2
Laborator Sisteme de Operare. Android

Fig. 2. Ecosistemul Android


Astfel o a doua posibilitate de a aduce contribuții la ecosistemul Android o reprezintă dezvoltarea de aplicații
native Android ce pot rula pe platforma Android, și această direcție o vom explora în capitolul următor.
Aplicațiile Android pot fi scrise folosind limbajele Java, Kotlin sau C++. SDK-ul Andoid compilează codul și
include rezultatul alături de alte resurse și date într-un fișier APK (Android package) care este defapt o arhivă cu
extensia .apk. Un fișier APK conține toate resursele necesare instalării unei aplicații pe un dispozitiv Android.
Fiecare aplicație Android într-un context de securitate propriu (security sandbox), separat de alte aplicații:
- sistemul de operare Android este un sistem Linux multiuser, în care fiecare aplicație reprezintă un
utilizator
- sistemul de operare alocă implicit unei aplicații un identificator unic de utilizator (user ID) cunoscut doar
sistemului de operare și necunoscut aplicației. Prin acest identificator sistemul alocă permisiunile pentru
toate fișierele folosite de către aplicație și numai utilizatorul cu acel identificator poate accesa fișierele
respective
- fiecare proces rulează în propria mașină virtuală (VM), astfel fiecare aplicație rulează izolat față de alte
aplicații
- fiecare aplicație pornește implicit un proces propriu. Sistemul Android pornește procesul când una din
componentele aplicației trebuie să se execute, și îl oprește când procesul nu mai este utilizat sau pentru
a recupera memorie pentru alte aplicații
- fiecare aplicație poate accesa implicit doar componentele sau resursele la care are de care are nevoie
pentru a rula, o aplicație nu poate accesa alte resurse la care nu are acordate permisiuni explicite. Dar
sistemul Android permite anumitor aplicații să acceseze date ale altor aplicații sau resurse/servicii de
sistem. De exemplu două aplicații pot împărții același identificator unic de utilizator deci fiecare poate
accesa fișierele celeilalte aplicații, sau aplicația poate solicita permisiuni pentru a accesa servicii ale
sistemului precum locația, camera foto sau o conexiune Bluetooth.

2. CREAREA APLICAȚIILOR ANDROID


Pentru crearea de aplicații Android este necesară instalarea SDK-ului Android. Acesta poate fi utilizat în linie de
comandă (command line), sau se poate utiliza un IDE precum Android Studio, pentru gestionarea proiectului, a
codului sursă, compilării, rulării și simulării aplicațiilor Android. Kitul Android Studio conține și kitul SDK, acestea
se pot descărca de la adresa: https://developer.android.com/studio

Notă – se vor verifica cerințele de sistem necesare instalării Android Studio. Se va avea în vedere faptul că
Android Studio necesită cel puțin 4GB spațiu disponibil pe disk, iar un emulator specific unui anumit model de
dispozitiv Android necesită 1.5GB.

Pagina 3
Laborator Sisteme de Operare. Android

La crearea unei aplicații Android este obligatoriu să se specifice versiunea minimă de SDK necesară pentru
rularea aplicației. O aplicație dezvoltată pentru o versiune poate rula pe dispozitive care au cel puțin acea
versiune instalată, nu poate rula pe dispozitive având versiune inferioară, dar poate rula pe dispozitive cu
versiuni mai noi (https://developer.android.com/guide/practices/compatibility). Magazinul Google Play poate
restricționa accesul la aplicație în funcție de versiune, dispozitive/funcționalitățile disponibile, configurația
ecranului. Dezvoltatorul unei noi aplicații trebuie să ia o decizie privind versiunea minimă pentru care dezvoltă
o aplicație. Pe de o parte este de dorit ca versiunea utilizată să fie cât mai nouă astfel încât aplicația să
beneficieze de ultimele funcționalități disponibile în platforma Android, dar pe de altă parte este posibil ca un
număr important de utilizatori și/sau dispozitive de pe piață să nu fie actualizate sau să nu aibă disponibile
ultimele versiuni de Android, astfel că aplicația se va adresa unui public relativ restrâns. O posibilă decizie poate
fi aceea de a alege o versiune cat mai mică deci cu o piață cât mai largă, dar fără a renunța la unele facilități
necesare aplicației. De asemenea o altă posibilă soluție este de a realiza versiuni separate ale aplicației, una
care să acopere versiuni mai vechi de Android, cu facilități reduse, alta pentru versiuni mai noi de Android cu
facilități sporite, și astfel cu un efort suplimentar de dezvoltare se poate acoperi optim un număr cât mai mare
și mai diversificat de utilizatori și dispozitive Android.

O aplicație Android poate conține o serie de componente distincte specifice arhitecturii Android. Există patru
tipuri de componente ale unei aplicații Android
(https://developer.android.com/guide/components/fundamentals):
- Activități (activities) pentru interacțiunea cu utilizatorul
- Servicii (Services) pentru rularea de aplicații în bakground
- Broadcast receivers pentru interacțiunea cu evenimente externe
- Furnizori de conținut (Content providers) pentru acces la resurse externe aplicației precum sistemul de
fișiere, baza de date SQLite, resurse WEB.
Fiecare tip de componentă este gestionată diferit de către sistemul Android, având un ciclu de viață bine definit
în funcție de care aplicația este creată, rulează și este distrusă.
În continuare vom exemplifica crearea unei aplicații de tip activitate. O activitate reprezintă un punct de
intrare, o interfață de interacțiune cu utilizatorul. O activitate se implementează sub forma unui ecran
conținând interfața cu utilizatorul, conține controale pentru afișarea de informații, preluarea de date sau
acțiuni de la utilizator.
Se pornește Android Studio și se alege opțiunea de a crea un nou proiect [5]. Se selectează un tip de proiect, în
cazul de față se alege tipul Empty Project. Se specifică numele proiectului, locația, limbajul utilizat și versiunea
minimă de Android SDK:

Fig. 3. Crearea unui nou proiect în Android Studio

Pagina 4
Laborator Sisteme de Operare. Android

După ce un proiect a fost creat se va afișa interfața IDE-ului Android Studio și se poate vedea structura
proiectului. Template-ul ales creează un proiect cu o componentă de tip activitate (activity), MainActivity:

Fig. 4. Proiect în IDE-ul Android Studio


Rularea unei aplicații se poate realiza pe un dispozitiv real conectat la stația de lucru ce rulează Android Studio
sau pe un emulator configurat.
Pentru a rula pe un dispozitiv real acesta trebuie conectat la stația de lucru printr-un cablu USB. Dispozitivul
trebuie în prealabil să permită rularea USB Debugging: în meniul Settings sau System alegeți About phone ->
Build number -> Developer options -> USB debugging
(https://developer.android.com/training/basics/firstapp/running-app).
Pentru a rula într-un emulator, acesta trebuie configurat în prealabil. La instalarea Android Studio se poate
configura emulatorul dorit sau oricând se poate adăuga unul nou folosind AVD Manager
(https://developer.android.com/studio/run/managing-avds#createavd):

Fig. 5. AVD manager


În continuare vom utiliza rularea în emulatorul Nexus 5:

Pagina 5
Laborator Sisteme de Operare. Android

Fig. 6. Rularea unei aplicații în emulator

La rularea aplicației, Android Studio instalează aplicația în emulator sau pe dispozitivul fizic selectat și o
pornește. În cazul exemplului de mai sus, activitatea principală afișează mesajul Hello World!.
Layoutul interfeței este descris în fișierul activity_main.xml:

Fig. 7. Definirea layoutului aplicației


Pentru crearea de interfețe complexe se poate utiliza editorul Layout Editor din Android Studio
(https://developer.android.com/training/basics/firstapp/building-ui). Se selectează fișierul activity_main.xml,

Pagina 6
Laborator Sisteme de Operare. Android

modul Design și Blueprint. Se observă în structura ce definește layoutul (Component tree) că interfața conține
un singur control de tip TextView:

Fig. 8. Layout Editor din Android Studio


Se pot adăuga diverse controale disponibile în tab-ul Palette:

Fig. 9. Layout Editor din Android Studio


Adăugați două noi controale, unul de tip PlainText și unul de tip Button. Dați click pe cele două controale în
timp ce țineți apăsată tasta SHIFT, apăsați butonul dreapta al mouseului pe unul din controale și alegeți Chains -
> Create Horizontal Chain pentru a conecta cele două controale și a le alinia central:

Fig. 10. Layout Editor din Android Studio

Pagina 7
Laborator Sisteme de Operare. Android

Pentru a realiza o acțiune la apăsarea butonului Button, accesați activitatea principală (clasa MainActivity), și
completați cu metoda setText:
public void setText(View view) {
// Do something in response to button
}

Țineți cursorul pe View și selectați opțiunea More actions -> Import Class, sau completați manual fișierul
MainActivity.java și importați clasa View:

Fig. 11. Importul clasei utilizate


import android.view.View;
..
Comutați în modul design pe layoutul activității, selectați butonul Button, și în tabul Common attributes ->
onClick selectați metoda setText:

Fig. 12. Tratarea evenimentului onClick


Pentru a accesa controalele din layout se folosește ID-ul aflat în fișierul ce definește layoutul:

Fig. 13. ID-urile controalelor


Completați metoda setText astfel încât la apăsarea butonului să modifice textul controlului EditText:
EditText t = (EditText)findViewById(R.id.editText);
t.setText("Hello World!");

Pagina 8
Laborator Sisteme de Operare. Android

Compilați și rulați aplicația:

Fig. 14. Afișarea unui text în controlul EditText


EXERCIȚIUL 1:
Adăugați un nou control de tip EditText. Modificați metoda setText astfel încât la apăsarea butonului să copieze
în al doilea câmp textul introdus de utilizator în primul câmp.
EXERCIȚIUL 2:
Adăugați mai multe tipuri de controale, ca de exemplu checkbox, radiobutton, calendar. Modificați metoda
setText astfel încât la apăsarea butonului să afișeze opțiunile selectate în câmpul text (se concatenează fiecare
opțiune selectată sub forma unui text, valorile vor fi separate cu “,”).
EXERCIȚIUL 3 (OPȚIONAL):
Utilizați un dispozitiv Android real (telefon sau tabletă), configurați dispozitivul astfel încât să permită rularea
aplicațiilor, configurați dispozitivul în Android Studio astfel încât să poată fi utilizat pentru rularea aplicației. Rulați
aplicația pe dispozitivul Android.

BIBLIOGRAFIE
[1]. Sistemul Android https://www.android.com/what-is-android/
[2]. Arhitectura sistemului Android https://source.android.com/devices/architecture
[3]. Android Developer https://developer.android.com/
[4]. Android API reference https://developer.android.com/reference?hl=en
[5]. Create an Android project https://developer.android.com/training/basics/firstapp/creating-project?hl=en

Pagina 9
Laborator Sisteme de Operare. Lucrarea 1

LUCRAREA 1

PROCESE. THREADURI.
Sistemele de operare multitasking sunt bazate pe procese și thread-uri. Sistemele de operare pot controla
funcționarea concurentă a mai multor procese și procesoare. În sistemele Windows, entitatea de alocare a
timpului procesorului (sau procesoarelor) este thread-ul. Fiecare proces conține cel puțin un thread de execuție
(thread-ul principal) și poate crea thread-uri noi, care partajează spațiul de adresă al procesului care le-a creat,
precum și resursele acestuia (fișiere, obiecte de sincronizare). Un proces Windows este identificat atât printr-un
instrument de acces, un handle, care este o intrare în tabela de resurse a sistemului, cât și printr-un identificator
(id sau pid), care este un număr unic, atribuit unui proces, și care îl caracterizează pe toată durata de existență a
acestuia [1].
Lansarea în execuție a unui program executabil creează un proces nou, cu propriul spațiu de adrese, și cu
minimum un thread de execuție (thread-ul principal).

1. CREAREA PROCESELOR
Un proces este creat și lansat în execuție de către sistemul de operare. Fiecare proces execută un anumit cod
conform scopului aplicației respective. De exemplu sistemul de operare Windows poate lansa în execuție
programe conținute în fișiere executabile (.exe). Un astfel de fișier se poate genera în urma editării și compilării
unui cod program cu ajutorul unui mediu de dezvoltare. În continuare vom utiliza mediul de dezvoltare Microsoft
Visual Studio ediția 2015 pentru a edita, compila și genera cod executabil (programe, aplicații).
Se va crea o aplicație mod consolă:

Fig. 1. Crearea unui nou proiect C# de tip Windows - Console Application


Se observă structura codului creat automat de către mediul de dezvoltare conform tipului de proiect selectat:

Pagina 1
Laborator Sisteme de Operare. Lucrarea 1
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
}
}
}
Funcția membră Main a clasei Program reprezintă punctul de intrare în aplicație, aceasta este prima metodă
care se apelează la rularea programului [2].
Se va completa codul astfel încât să afișeze un mesaj și să aștepte apăsarea unei taste pentru a termina
execuția programului:
...
Console.WriteLine("Hello world!");
Console.ReadLine();

...

Se compilează codul și dacă nu sunt erori se va rula aplicația:

Fig. 2. Compilarea codului


Codul executabil generat se salvează de către mediul de dezvoltare în directorul proiectului, în subdirectorul
\bin\Debug:

Pagina 2
Laborator Sisteme de Operare. Lucrarea 1

Fig. 3. Accesarea directorului proiectului


Rularea aplicației se poate realiza fie din mediul de dezvoltare (F5 sau Debug – Start debugging), fie rulând
aplicația direct din locația acesteia:

Fig. 4. Aplicația (programul executabil)


Fiind creată de forma unei aplicații tip consola (Console Application, aplicație fără interfață grafică), la rulare va
apărea consola Windows si nu o interfață grafică (fereastră corespunzătoare aplicațiilor Windows):

Fig. 5. Rularea aplicației


De asemenea aplicația poate fi rulată dintr-o consolă linie de comandă:

Fig. 6. Rularea aplicației din linia de comandă


Pagina 3
Laborator Sisteme de Operare. Lucrarea 1

Se observă că în acest caz nu se mai deschide o nouă fereastră mod consolă ci mesajele și interacțiunea cu
utilizatorul au loc în această consolă. Pentru rularea aplicației este necesară specificarea căii complete până la
fișierul executabil, nu este suficientă specificarea numelui executabilului decât daca ne aflăm în directorul
acestuia:

Fig. 7. Rularea aplicației din directorul acesteia

2. CREAREA PROGRAMATICĂ A PROCESELOR


Procesele pot fi create și rulate și prin intermediul altor aplicații sau scripturi (de ex. scripturi Shell). În continuare
vom testa rularea procesului creat în cap. anterior prin intermediul unei alte aplicații C#. Se va adăuga un nou
proiect de tip Console Application în soluția existentă:

Fig. 8. Adăugarea unui nou proiect în soluție


Se va seta acest proiect ca proiect de pornire:

Pagina 4
Laborator Sisteme de Operare. Lucrarea 1

Fig. 9. Setarea proiectului de pornire


Se va completa codul astfel încât să ruleze aplicația realizată în cap. anterior folosind clasa Process:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.Diagnostics;

namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Start master!");

Process.Start(@"E:\Facultate\SO\ConsoleApplication1\ConsoleApplication1\bin\Debug\ConsoleApplication1.e
xe");
Console.WriteLine("Stop master!");
Console.ReadLine();
}
}
}

EXERCIȚIUL 1:
Rulați aplicația dintr-o fereastra consola Windows.

EXERCIȚIUL 2:
Modificați programul astfel încât să porniți un browser Web instalat pe stația de lucru. Modificați programul
astfel încât să porniți un browser Web care să acceseze o pagină de Internet.

EXERCIȚIUL 3:

Pagina 5
Laborator Sisteme de Operare. Lucrarea 1

Modificați programul astfel încât să deschideți fișierul cod sursă al programului de mai sus în editorul text
Notepad.

EXERCIȚIUL 4:
Modificați programul ConsoleApplication1 din cap. anterior astfel încât să primească un parametru de tip string
pe care sa îl afiseze. Rulați aplicația ConsoleApplication1 cu un parametru oarecare de tip string.

3. CONTROLUL PROCESELOR
Durata de viață și proprietățile proceselor pot fi controlate programatic. Procesele pot fi pornite, se pot
transmite parametri, pot fi oprite prin intermediul clasei Process prezentată în cap. precedent. Aceasta se poate
face printr-un obiect de tip Process obținut la pornirea procesului [3], respectiv printr-un obiect
ProcessStartInfo pentru controlul parametrilor de rulare [4]:
Process p = Process.Start(@"..\ConsoleApplication1.exe");

ProcessStartInfo startInfo = new ProcessStartInfo(@"..\ConsoleApplication1.exe");


Process.Start(startInfo);

Se observă că pornirea aplicațiilor ConsoleApplication2 și ConsoleApplication1 generează două ferestre


distincte. Controlarea modului cum se generează fereastra pentru procesul lansat se poate face prin
intermediul obiectului ProcessStartInfo.

Fig. 10. Ferestre create la pornirea aplicațiilor


Prin setarea proprietății CreateNoWindow = true (care este activă doar dacă UseShellExecute = false) se poate
controla modul de pornire a aplicației cu/fără fereastră. Deși pornirea aplicației ConsoleApplication1 nu mai
generează fereastră, consultând lista de procese se poate identifica noul proces astfel creat:
startInfo.CreateNoWindow = true;
startInfo.UseShellExecute = false;

Pagina 6
Laborator Sisteme de Operare. Lucrarea 1

Fig. 11. Pornirea aplicației fără fereeastră


Lista de procese se poate vizualiza într-o consolă Windows prin comanda Tasklist:

Fig. 12. Lista de procese Windows la rularea comenzii Tasklist


Însă în acest caz procesul nu mai are o fereastră asociată astfel utilizatorul nu mai poate controla aplicația, de
exemplu nu o mai poate închide prin închiderea ferestrei acesteia. Închiderea se poate realiza din managerul de
activități Windows (Task Manager), sau prin comanda Taskkill /PID 1234 din linia de comanda, unde 1234 este
ID-ul procesului vizibil la rularea comenzii Tasklist.

Atenție – nu opriți procese ale sistemului de operare sau alte procese importante din sistem!

De asemenea procesul poate fi oprit și programatic prin apelul metodei Kill atât timp cât aplicația care l-a creat
încă rulează:
Process p = Process.Start(@"..\ConsoleApplication1.exe");

...

p.Kill();

Procesele pot fi accesate chiar dacă procesul curent nu le-a creat. De exemplu metoda statica GetProcesses
generează lista proceselor ce rulează în sistem:
Process[] localProcesesses = Process.GetProcesses();
foreach(Process p in localProcesesses)
{
Console.WriteLine(string.Format("Process PID {0} is {1}", p.Id, p.ProcessName));
}

Pentru o mai bună vizibilitate sortăm procesele alfabetic după nume, și dacă procesul este ConsoleApplication1
îl putem opri:
Pagina 7
Laborator Sisteme de Operare. Lucrarea 1
ProcessStartInfo startInfo = new
ProcessStartInfo(@"E:\Facultate\SO\ConsoleApplication1\ConsoleApplication1\bin\Debug\ConsoleApplication
1.exe");
startInfo.CreateNoWindow = true;
startInfo.UseShellExecute = false;
Process.Start(startInfo);

Process[] localProcesesses = Process.GetProcesses();


foreach(Process p in localProcesesses.OrderBy(x => x.ProcessName))
{
Console.WriteLine(string.Format("Process PID {0} is {1}", p.Id, p.ProcessName));
if (p.ProcessName == "ConsoleApplication1")
{
Console.WriteLine("Closing PID {0}...", p.Id);
p.Kill();
Console.WriteLine("PID {0} closed", p.Id);
}
}

Atenție – nu opriți procese ale sistemului de operare sau alte procese importante din sistem!

Sistemul oferă acces și la alte proprietăți ale proceselor ca de exemplu memoria utilizată sau timpul de
procesor. Pentru a simula o aplicație care utilizează intens aceste resurse modificați aplicația
ConsoleApplication1 pentru a aloca memorie si a utiliza timpul de procesor pentru accesarea memoriei alocate:
Console.WriteLine("Hello world!");
List<byte[]> list = new List<byte[]>();
for (int i = 0; i < 20; i++)
{
byte[] b = new byte[1024 * 1024 * 100];
for (int j = 0; j < 1024 * 1024 * 100; j++)
b[j] = (byte)(j % 256);

list.Add(b);

Console.WriteLine(string.Format("Running..."));
Thread.Sleep(1000);
}

Console.ReadLine();

Din aplicația master se accesează proprietățile procesului creat, în cazul de mai jos se preia memoria utilizată și
timpul total de procesor:
Console.WriteLine("Start master!");

Process p = Process.Start(@"...\ConsoleApplication1.exe");

for (int i = 0; i < 20; i++)


{
p.Refresh();
Console.WriteLine(string.Format("Process memory of PID {0} is {1} B using {2} CPU", p.Id,
p.PagedMemorySize64, p.TotalProcessorTime));
Thread.Sleep(1000);
}
Console.WriteLine("Closing child....");
p.Kill();
Console.WriteLine("Child closed");

Pagina 8
Laborator Sisteme de Operare. Lucrarea 1
Console.WriteLine("Stop master!");
Console.ReadLine();

Notă – pentru utilizarea anumitor metode sau clase este necesară includerea namespace-ului corespunzator.
De exemplu Thread.Sleep() necesită adăugarea namespace-ului System.Threading:

using System.Threading;

..

Thread.Sleep(1000);

EXERCIȚIUL 5:
Modificați programul astfel încât să afișeze memoria utilizată în KB, MB și GB.

EXERCIȚIUL 6:
Modificați programul astfel încât să afișeze și alte proprietăți ale procesului, de exemplu calea către executabil,
prioritatea, etc.

EXERCIȚIUL 7:
Modificați programul astfel încât să afișeze memoria utilizată și timpul total de procesor pentru toate procesele
ce rulează. Sortați descrescător după memoria ocupată.

EXERCIȚIUL 8:
Modificați programul astfel încât să afișeze la fiecare 5 secunde procesul care utilizează cel mai mult procesorul
(se poate folosi clasa PerformanceCounter).

BIBLIOGRAFIE
[1]. PRINCIPIILE CALCULULUI PARALEL, Felicia Ionescu, EDITURA TEHNICĂ, București, 1999
[2]. C# Programming Guide – main function https://docs.microsoft.com/en-us/dotnet/csharp/programming-
guide/main-and-command-args/

[3] C# Programming Guide – Process Class https://docs.microsoft.com/en-


us/dotnet/api/system.diagnostics.process?view=netframework-4.8

[4] C# Programming Guide – ProcessStartInfoClass https://docs.microsoft.com/en-


us/dotnet/api/system.diagnostics.processstartinfo?view=netframework-4.8

Pagina 9
Laborator Sisteme de Operare. Lucrarea 2

LUCRAREA 2

PROCESE. THREADURI.
Sistemele de operare multitasking sunt bazate pe procese și threaduri. Sistemele de operare pot controla
funcționarea concurentă a mai multor procese și procesoare. În sistemele Windows, entitatea de alocare a
timpului procesorului (sau procesoarelor) este threadul. Fiecare proces conține cel puțin un thread de execuție
(threadul principal) și poate crea threaduri noi, care partajează spațiul de adresă al procesului care le-a creat,
precum și resursele acestuia (fișiere, obiecte de sincronizare). Un proces Windows este identificat atât printr-un
instrument de acces, un handle, care este o intrare în tabela de resurse a sistemului, cât și printr-un identificator
(id sau pid), care este un număr unic, atribuit unui proces, și care îl caracterizează pe toată durata de existență a
acestuia [1]. Lansarea în execuție a unui program executabil creează un proces nou, cu propriul spațiu de adrese,
și cu minimum un thread de execuție (threadul principal).

Sistemul de operare vede execuţia unui proces tipic ca o trecere printr-o succesiune de stări: starea dormantă
(dormant), starea gata de execuţie (ready), starea de execuţie (running) şi starea suspendată (suspended).
Starea dormantă este starea în care procesul nu este cunoscut de sistemul de operare, pentru că nu a fost încă
creat. Toate programele care nu au fost încă lansate în execuţie pot fi privite ca procese dormante.
În starea gata de execuţie, procesul are alocate toate resursele necesare pentru execuţie, cu excepţia
procesorului. Planificatorul sistemului de operare (scheduler) selectează un proces gata de execuţie, pe care îl
trece în starea de execuţie, operaţie numită planificarea procesului (process scheduling).
Un proces în starea de execuţie posedă toate resursele necesare, inclusiv procesorul. În sistemele uniprocesor,
cel mult un proces poate să se afle în starea de execuţie, în orice moment de timp. Procesul în execuţie parcurge
secvenţa instrucţiunilor maşină şi poate, la un moment dat să execute un apel către sistemul de operare pentru
execuţia unui serviciu, ca, de exemplu, o operaţie de intrare/ieşire sau o operaţie de sincronizare. În funcţie de
politica de planificare a sistemului de operare, acesta poate să redea imediat controlul procesului după execuţia
serviciului, sau poate alege alt proces disponibil şi să-l planifice în execuţie.
Un proces suspendat este un proces care aşteaptă un eveniment, ca de exemplu un semnal de sincronizare. Astfel
de procese sunt excluse din competiţia pentru execuţie, până ce condiţia de suspendare nu este eliminată. Un
proces în execuţie poate deveni suspendat prin apelul unei operaţii de intrare/ieşire al cărui rezultat este necesar
pentru continuarea activităţii, sau prin aşteptarea unui eveniment care nu s-a produs încă. Sistemul de operare
memorează cauza suspendării procesului, astfel încât să-l poată relua atunci când condiţia de suspendare a
dispărut, prin acţiunea altor procese sau prin apariţia unui eveniment extern.
Sistemul de operare grupează toate informaţiile despre un proces într-o structură de date numită blocul de
control al procesului (process control block, PCB). Atunci când este creat un proces nou, sistemul creează un bloc
de control nou, pentru a-l utiliza ca descriptor de-a lungul existenței procesului. Când procesul se termină, blocul
lui de control este distrus şi eliminat din memorie. În starea dormantă, un proces nu are un bloc de control şi nu
poate intra în competiție pentru obținerea de resurse. În mod tipic, blocul de control menține următoarele
informaţii despre proces:
• Numele procesului (identificator)
• Prioritatea
• Starea de execuţie (gata, în execuţie, suspendat)
• Starea hardware (registrele procesorului şi flag-uri)

Pagina 1
Laborator Sisteme de Operare. Lucrarea 2

• Informaţii de planificare.
• Informaţii despre resursele utilizate (fişiere, intrări/ieşiri).
Tranziţia între două procese active într-un sistem de operare cu multitasking sau multiprocesare se numeşte
comutarea proceselor (process switch), şi are loc ca răspuns la un eveniment din sistem. Comutarea proceselor
este o operaţie complexă, care implică un cost (overhead) important şi, datorită frecvenţei cu care are loc într-
un sistem, poate influenţa semnificativ performanţele acestuia. Comutarea proceselor este necesară deoarece
numărul de procese ce rulează pe o platformă poate fi (mult) mai mare decât numărul de procesoare sau de
nuclee de execuție (core) astfel că se acordă pe rând timp de procesor (timp de execuție) fiecărui proces pe baza
unui algoritm și a unei strategii specifice sistemului de operare.
Eficienţa operaţiei de comutare a proceselor poate fi crescută prin prevederea unor facilităţi hardware (ca, de
exemplu seturi de registre multiple sau circuite hardware care să faciliteze comutarea rapidă), sau printr-o
modalitate de structurare a procesului, de exemplu utilizând threaduri ce pot fi comutate mai eficient.
Threadurile reprezintă o modalitate software de a îmbunătăţi performanţele sistemului prin reducerea costului
de comutare a proceselor. Un thread este un proces “mai uşor”, cu o stare redusă, mai mică, comparativ cu un
proces. Reducerea stării se obţine prin gruparea unui număr de threaduri corelate între ele pentru a partaja
diferite resurse de calcul, ca de exemplu, memoria şi fişierele. În sistemele bazate pe threaduri, threadul devine
cea mai mică entitate de planificare și execuție, iar procesul sau task-ul serveşte ca un mediu de execuţie a
threadurilor. În astfel de sisteme, un proces cu un singur thread este identic cu un proces clasic.
Fiecare thread reprezintă un flux separat de execuţie şi este caracterizat prin propria sa stivă şi prin stare
hardware (registre, flag-uri).
De vreme ce toate celelalte resurse, cu excepţia procesorului, sunt gestionate de către procesul care le
înglobează, comutarea între threadurile care aparţin aceluiaşi proces, care implică doar salvarea, respectiv
restaurarea stării hardware şi a stivei, este rapidă şi eficientă. Totuşi, comutarea între threadurile care aparţin
unor procese diferite implică tot costul de comutare a proceselor.
Threadurile reprezintă un mecanism eficient de exploatare a concurenţei programelor. Un program poate fi
împărţit în mai multe threaduri, fiecare cu o execuţie mai mult sau mai puţin independentă, şi pot comunica între
ele prin accesul la spaţiul de adresă a memoriei procesului, pe care îl partajează între ele.

1. CREAREA THREADURILOR
În continuare vom utiliza mediul de dezvoltare Microsoft Visual Studio ediția 2015 pentru a edita, compila și
genera cod executabil (programe, aplicații). În C# se folosește clasa System.Threading.Thread pentru lucrul cu
theaduri [2]. Se va crea o aplicație mod consolă:

Pagina 2
Laborator Sisteme de Operare. Lucrarea 2

Fig. 1. Crearea unui nou proiect C# de tip Windows - Console Application


Se observă structura codului creat automat de către mediul de dezvoltare conform tipului de proiect selectat:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
}
}
}
Se va completa codul astfel încât să afișeze un mesaj și să aștepte apăsarea unei taste pentru a termina
execuția programului:
Console.WriteLine("Hello world!");
Console.ReadLine();

Se compilează codul și dacă nu sunt erori se va rula aplicația ConsoleApplication1.exe aflată în directorul
proiectului – subdirectorul \bin\Debug:

Fig. 2. Rularea aplicației


Când se lansează în execuție un program C# se creează threadul principal. Se pornește managerul de activități,
se selectează coloana Fire de executie (threaduri) apăsând butonul dreapta al mouseului pe lista de coloane:

Pagina 3
Laborator Sisteme de Operare. Lucrarea 2

Fig. 3. Managerul de activități


Se observă că pe lângă threadul principal, procesul ConsoleApplication.exe mai conține și alte threaduri
suplimentare specifice frameworkului .net (Garbage Collector, Finalizer, VisualStudio, Debugger, etc.):

Fig. 4. procesul ConsoleApplication.exe


Threadurile create de către threadul principal utilizând clasa Thread sunt denumite threaduri copil (child threads).
Threadul curent se poate accesa utilizând proprietatea CurrentTread a clasei Thread. Completați funcția membră
Main a clasei Program, compilați și rulați aplicația:
Thread th = Thread.CurrentThread;
th.Name = "MainThread";
Console.WriteLine("This is {0}", th.Name);
Console.ReadLine();

Notă – pentru utilizarea anumitor metode sau clase este necesară includerea namespace-ului corespunzator.
De exemplu clasa Thread necesită adăugarea namespace-ului System.Threading:

using System.Threading;

2. CREAREA PROGRAMATICĂ A THREADURILOR


Threadurile pot fi create în interior unui proces în mod programatic. În proiectul creat la punctul anterior adăugați
o metoda statică în clasa existentă (clasa Program), aceasta va fi funcția threadului copil:
public static void ThreadFunction(){
Console.WriteLine("ChildThread: this is child thread");
}

Completați funcția Main pentru a crea un thread ce va rula funcția ThreadFunction:


Console.WriteLine("MainThread: creating child thread");
Thread childThread = new Thread(ThreadFunction);
Pagina 4
Laborator Sisteme de Operare. Lucrarea 2
childThread.Start();
Console.WriteLine("MainThread: this is main thread");
Console.ReadLine();

Compilați și executați programul. În acest moment aplicația (procesul) conține doua threaduri distincte, fiecare
executând cod diferit: threadul principal execută funcția Main iar threadul copil metoda ThreadFunction.

EXERCIȚIUL 1:
Modificați programul astfel încât să creați și să rulați mai multe threaduri care să ruleze metoda statică
ThreadFunction.

EXERCIȚIUL 2:
Modificați programul astfel încât să creați și să rulați mai multe threaduri și fiecare să ruleze o metodă diferită.

Threadurile se execută concurent în cadrul sistemului de operare, acesta decide modul (ordinea) de execuție,
cele două threaduri se pot executa simultan dacă există resurse disponibile (procesoare/core-uri disponibile), sau
secvențial (sistemul de operare serializează execuția) dacă nu există resurse disponibile.
După apelul metodei childThread.Start() în sistem vor fi două threaduri ce rulează concurent, mesajele
“MainThread: this is main thread” și “ChildThread: this is child thread” se vor afișa într-o ordine aleatoare în
funcție de threadul ales de către sistemul de operare să se execute primul. Pentru a observa mai clar acest lucru
completati codul celor două metode statice și adăugați linia Thread.Sleep(100); înainte de afișarea celor două
mesaje menționate anterior, compilați codul. Rulați de mai multe ori aplicația ConsoleApplication1.exe aflată în
directorul proiectului – subdirectorul \bin\Debug și observați ordinea de afișare a mesajelor:

Fig. 5. Rularea concurentă a threadurilor


Fiecare thread are un număr unic (tid), un identificator de thread care identifică threadul în cadrul procesului,
acesta este fix pe întreaga durată de execuție a procesului. Modificați codul și afișați ID-ul fiecărui thread, inclusiv
al celui principal:
Console.WriteLine(string.Format("Current thread ID{0}", Thread.CurrentThread.ManagedThreadId));

3. CONTROLUL PROGRAMATIC AL THREADURILOR


Odată pornit un thread (prin apelul metodei Start) sistemul de operare are controlul asupra modului de execuție
a threadului, acesta decide când să îl ruleze, când să îl oprească sau suspende, etc. Apelul metodei Start nu

Pagina 5
Laborator Sisteme de Operare. Lucrarea 2

garantează rularea imediată a threadului, threadul principal poate rula multe alte intrucțiuni până când sistemul
de operare decide execuția threadului copil. Acest lucru poate fi observat dacă modificăm codul astfel:
public static void ThreadFunction(){
for (int i = 0; i < 100; i++)
Console.WriteLine(string.Format("ChildThread: this is child thread {0}", i));
}
static void Main(string[] args){
Console.WriteLine("MainThread: creating child thread");
Thread childThread = new Thread(ThreadFunction);
childThread.Start();
for (int i = 0; i < 100; i++)
Console.WriteLine(string.Format("MainThread: this is main thread {0}", i));

Console.ReadLine();
}

Compilați și rulați de mai multe ori aplicația, observați momentul (impredictibil) când threadul copil rulează
prima instrucțiune, precum și rularea concurentă a celor două threaduri prin modul de intercalare a mesajelor
“MainThread: this is main thread #” și “ChildThread: this is child thread #”:

Fig. 6. Controlul sistemului de operare asupra threadurilor


Un thread poate avea mai multe stări de-a lungul existenței sale de la creare și până la terminare. În mod uzual
un thread este creat, este pornit (prin apelul metodei Start) și se termină automat când se finalizează execuția
funcției sale (în cazul codului de mai sus, la finalizarea execuției metodei statice ThreadFunction).
Un thread .net poate avea stările prezentate în tabelul următor, conform enumerării ThreadState:

Aborted The thread state includes AbortRequested and the thread is now dead, but its state has not yet
changed to Stopped.

Pagina 6
Laborator Sisteme de Operare. Lucrarea 2

AbortRequested The Abort(Object) method has been invoked on the thread, but the thread has not yet received the
pending ThreadAbortException that will attempt to terminate it.

Background The thread is being executed as a background thread, as opposed to a foreground thread. This state
is controlled by setting the IsBackground property.

Running The thread has been started and not yet stopped.

Stopped The thread has stopped.

StopRequested The thread is being requested to stop. This is for internal use only.

Suspended The thread has been suspended.

SuspendRequested The thread is being requested to suspend.

Unstarted The Start() method has not been invoked on the thread.

WaitSleepJoin The thread is blocked. This could be the result of calling Sleep(Int32) or Join(), of requesting a lock -
for example, by calling Enter(Object) or Wait(Object, Int32, Boolean) - or of waiting on a thread
synchronization object such as ManualResetEvent.
Tab. 1. Stările unui thread .net
Starea curentă a unui thread poate fi accesată prin proprietatea ThreadState a clasei Thread. Modificați codul
aplicației astfel:
public static void ThreadFunction(){
for (int i = 0; i < 100; i++)
Console.WriteLine(string.Format("ChildThread: this is child thread {0}", i));
}
static void Main(string[] args){
Console.WriteLine("MainThread: creating child thread");
Thread childThread = new Thread(ThreadFunction);
Console.WriteLine(string.Format("MainThread: child thread state {0}", childThread.ThreadState));
childThread.Start();
for (int i = 0; i < 100; i++)
Console.WriteLine(string.Format("MainThread: child thread state {0}",
childThread.ThreadState));

Thread.Sleep(100);
Console.WriteLine(string.Format("MainThread: child thread state {0}", childThread.ThreadState));
Console.ReadLine();
}

Compilați și rulați aplicația, observați evoluția stării threadului copil:

Pagina 7
Laborator Sisteme de Operare. Lucrarea 2

Fig. 7. Starea threadului


Dar starea unui thread poate fi controlată programatic din alt thread și nu doar de către sistemul de oprare, astfel
clasa Thread pune la dispoziție o serie de metode pentru crearea, pornirea, oprirea, suspendarea (diferită de
oprire, punerea în “pauză”), reluarea (după suspendare), etc.
Modificați codul aplicației:
public static void ThreadFunction(){
for (int i = 0; i < 100; i++) {
Thread.Sleep(100);
Console.WriteLine(string.Format("ChildThread: this is child thread {0}", i));
}
}
static void Main(string[] args){
Console.WriteLine("MainThread: creating child thread");
Thread childThread = new Thread(ThreadFunction);
Console.WriteLine(string.Format("MainThread: child thread state {0}", childThread.ThreadState));
childThread.Start();
for (int i = 0; i < 100; i++) {
Thread.Sleep(100);
if (i == 25)
childThread.Suspend();
if (i == 50)
childThread.Resume();
if (i == 75)
childThread.Abort();
Console.WriteLine(string.Format("MainThread: child thread state {0}", childThread.ThreadState));
}
Console.ReadLine();
}

Compilați și rulați aplicația, observați evoluția stării threadului copil:

Fig. 8. Controlul stării unui thread


Pagina 8
Laborator Sisteme de Operare. Lucrarea 2

De asemenea observați modul cum execuția threadului copil este reluată de la instrucțiunea unde a rămas la
momentul suspendării:

Fig. 9. Controlul stării unui thread


Pe lângă controlul stării unui thread se pot controla programatic și parametrii ce se transmit unui thread. Sunt
situații când este nevoie să se transmită parametrii diferiți fiecărui thread. Pentru aceasta modificați codul:
public static void ThreadFunction(object parameter){
Console.WriteLine(string.Format("Current thread {0} with parameter {1}",
Thread.CurrentThread.ManagedThreadId, parameter));
}
static void Main(string[] args){
for (int i = 0; i< 10; i++) {
Thread childThread = new Thread(ThreadFunction);
childThread.Start(i);
}
Console.ReadLine();
}

4. PARTAJAREA MEMORIEI
Threadurile copil partajează spațiul de memorie al procesului în care rulează, acestea pot accesa memoria pentru
citire și/sau scriere. Threadurile pot comunica între ele prin citirea și scrierea memoriei comune, pot transmite și
pot primi parametri, date, variabile în vederea realizării sarcini de calcul complexe în mod concurent și/sau
colaborativ. Pot fi situații sau aplicații în care fiecare thread realizează o sarcină de lucru în mod independent de
celelalte threaduri sau de threadul principal, acestea pot sa nu primească și să nu transmită date către alte
threaduri. Dar în cazul când threadurile trebuie să comunice între ele, sistemul de operare facilitează accesul la
spațiul de memorie comun partajat și astfel acestea pot comunica eficient utilizând memoria partajată în cadrul
procesului curent. Fiecare proces are un spațiu de memorie propriu, acesta este partajat de către toate
threadurile sale.
class Program{
private static int a = 1;
private static int b = 0;

public static void ThreadFunction(object parameter){


b = a + (int)parameter;
}
static void Main(string[] args){
Console.WriteLine(string.Format("a={0}, b={1}", a, b));
Thread childThread = new Thread(ThreadFunction);

Pagina 9
Laborator Sisteme de Operare. Lucrarea 2
childThread.Start(10);
Thread.Sleep(1000);
Console.WriteLine(string.Format("a={0}, b={1}", a, b));
Console.ReadLine();
}
}

Fig. 10. Accesul la date

EXERCIȚIUL 3:
Modificați programul astfel încât să implementați adunarea a doi vectori de aceeași lungime N (C = A + B), fiecare
thread realizând adunarea unui element (numărul de threaduri este egal cu numărul de elemente, vectorii A, B,
C, respectiv valoarea N pot fi declarate ca variabile statice).

EXERCIȚIUL 4:
Modificați programul astfel încât să implementați adunarea a doi vectori de aceeași lungime N (C = A + B)
folosind două threaduri (fiecare realizează adunarea pe o partiție de N/2 elemente).

EXERCIȚIUL 5:
Modificați programul de la exercițiul 4 astfel încât să utilizați un număr de K threaduri (K ≥ 1, K ≤ N/2, pentru
simplificare se presupune ca N si K sunt puteri ale lui 2).

5. ACCESUL CONCURENT LA RESURSE, SINCRONIZAREA THREADURILOR


Următorul cod generează un vector de K threaduri, le lansează în execuție, fiecare adună valoarea parameter la
variabila a, la final threadul principal afișează valoarea lui a.
class Program{
private static int a = 0;
private static int K = 1000;
public static void ThreadFunction(object parameter){
int b = a;
b = b + (int)parameter;
Thread.Sleep(1);
a = b;
}
static void Main(string[] args){
Console.WriteLine(string.Format("a={0}", a));
Thread[] childThreads = new Thread[K];
for (int i = 0; i < K; i++){
Thread childThread = new Thread(ThreadFunction);
childThread.Start(i);
}
Console.WriteLine(string.Format("a={0}", a));
Console.ReadLine();
}
Pagina 10
Laborator Sisteme de Operare. Lucrarea 2
}

Compilați și rulați. Observați rezultatul afișat al variabilei a, acesta diferă de la rulare la rulare deoarece este
posibil ca nu toate threadurile lansate să se și execute până în momentul când threadul principal afișează
valoarea lui a. La o rulare secvențială, corectă, suma primelor K numere naturale este K(K+1)/2, comparați cu
rezultatul rulării multithreading. De notat că bucla for iterează de la 0 până la valoarea K-1 inclusiv deci sunt
adunate primele K-1 numere naturale.

Fig. 10. Rezultate eronate în cazul lispesi sincronizării între threaduri


Este necesar ca threadul principal să se asigure că toate threadurile lansate și-au finalizat rularea și numai după
aceea să își continue rularea. Aceasta se poate realiza apelând metoda Join pentru fiecare obiect Thread în
threadul principal, threadul principal se va bloca până când fiecare thread copil se finalizează, apelul metodei
Join este blocant pentru threadul apelant până când threadul pentru care s-a apelat metoda Join își termină
execuția.
class Program{
private static int a = 0;
private static int K = 1000;
public static void ThreadFunction(object parameter){
int b = a;
b = b + (int)parameter;
Thread.Sleep(1);
a = b;
}
static void Main(string[] args){
Console.WriteLine(string.Format("a={0}", a));
Thread[] childThreads = new Thread[K];
for (int i = 0; i < K; i++){
childThreads [i]= new Thread(ThreadFunction);
childThreads[i].Start(i);
}
for (int i = 0; i < K; i++){
childThreads[i].Join();
}
Console.WriteLine(string.Format("a={0}", a));
Console.ReadLine();
}
}

Însă chiar dacă threadul principal așteaptă finalizarea tuturor threadurilor copil totuși există posibilitatea rulării
eronate. La o execuție multithreading (paralelă, concurentă) fiecare thread citește valoarea lui a, o
incrementează utilizând b și o salvează, însă în același timp și alte threaduri pot citi aceeași valoare a lui a și
incrementa cu propria valoare a parametrului parameter specifică acelui thread, astfel la final se obțin rezultate
incorecte (nu sunt efectuate toate adunările). Fără un mecanism de sincronizare a accesului concurent la
variabila a, rularea unui astfel de cod produce rezultate incorecte. Incrementarea variabilei a (citirea,

Pagina 11
Laborator Sisteme de Operare. Lucrarea 2

incrementarea și salvarea) trebuie realizată atomic de către fiecare thread. Acest lucru nu este oferit automat
de către sistemul de operare sau de către hardware, dar sistemul de operare pune la dispoziție mecanisme ce
permit implementarea accesului securizat, exclusiv sau atomic la date partajate.
Pentru controlul accesului exclusiv la resurse comune sau pentru implementarea atomicității operațiilor se
poate folosi un mutex. Un mutex este o resursă de sincronizare (primitivă de sincronizare) gestionată de către
sistemul de operare, procesul sau threadul care deține mutexul poate rula în continuare, în același timp un alt
proces sau thread care încearcă să obțină același mutex va fi blocat până când acesta este eliberat de procesul
sau threadul care l-a deținut.
class Program{
..
private static Mutex mutex = new Mutex();
...
public static void ThreadFunction(object parameter){
mutex.WaitOne();
int b = a;
b = b + (int)parameter;
Thread.Sleep(1);
a = b;
mutex.ReleaseMutex();
}
..
Primitiva de sincronizare mutex, prin apelul WaitOne, asigură theadului căruia i se acordă mutexul posibilitatea
exclusivă să incrementeze variabila a. După ce a finalizat operațiile, threadul eliberează mutexul prin apelul
ReleaseMutex, astfel se permite altui thread să obțină mutexul și deci să incrementeze a. Daca un thread nu
obține un mutex, apelul WaitOne este blocant, nu poate continua execuția (și deci accesul la variabila a) până
când mutexul nu este eliberat. Sistemul de operare asigură că cel mult un thread deține acel mutex. În acest fel
se realizează un acces exclusiv, o serializare a accesului la variabila a și astfel se calculează corect suma primelor
K numere naturale.

EXERCIȚIUL 6:
Modificați programul astfel încât să implementați calculul sumei elementelor unui vector de N numere, folosind
N threaduri, suma va fi afișată de către un thread copil (unul singur va afișa, la alegere).

EXERCIȚIUL 7:
Modificați programul astfel încât să implementați calculul mediei elementelor unui vector de N numere, folosind
N threaduri, valoarea va fi afișată de către threadul principal.

EXERCIȚIUL 8:
Modificați programul astfel încât să determinați valoarea minimă a elementelor unui vector de N numere,
folosind N threaduri, valoarea va fi afișată de către threadul principal.

BIBLIOGRAFIE
[1]. PRINCIPIILE CALCULULUI PARALEL, Felicia Ionescu, EDITURA TEHNICĂ, București, 1999
[2]. C# Programming Guide – Thread Class https://docs.microsoft.com/en-
us/dotnet/api/system.threading.thread?view=netframework-4.8

Pagina 12
Laborator Sisteme de Operare. Lucrarea 3

LUCRAREA 3

FIȘIERE. FLUXURI I/O.

Sistemele de fișiere reprezintă soluția pentru structurarea și organizarea datelor în mod persistent. Memoria
RAM este o memorie volatilă astfel că procesele au nevoie de un mecanism de persistență a datelor după ce
aceste procese își încetează activitatea. În general fișierele sunt organizate în structuri ierarhice, directoare,
organizate sub formă de arbore (directoare -> subdirectoare -> … -> fișier), întreaga structură fiind administrată
de către sistemul de operare. Acesta face legătura dintre structura abstractă de directoare și fișiere și capacitățile
fizice de stocare (disk, resurse de rețea precum NAS – Network Attached Storage, File Servere - servere de fișiere,
memorii USB, carduri de memorie, etc.).

1. LUCRUL CU DIRECTOARE
Platforma .Net, ca de altfel majoritatea platformelor de dezvoltare sau limbaje de programare, permite lucrul
directoare și fișiere. Uzual aplicațiile pot crea, muta, șterge directoare, pot afla lista subdirectoarelor dintr-un
director sau proprietățile unui director, pot crea, citi, scrie, copia, muta sau șterge fișiere, pot accesa proprietățile
unui fișier [4].
În continuare vom utiliza mediul de dezvoltare Microsoft Visual Studio ediția 2015 pentru a edita, compila și
genera cod executabil (programe, aplicații). Se va crea o aplicație mod consolă:

Fig. 1. Crearea unui nou proiect C# de tip Windows - Console Application


Se observă structura codului creat automat de către mediul de dezvoltare conform tipului de proiect selectat:
using System;
using System.Collections.Generic;

Pagina 1
Laborator Sisteme de Operare. Lucrarea 3
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
}
}
}
Se va completa codul astfel încât să afișeze un mesaj și să aștepte apăsarea unei taste pentru a termina
execuția programului:
Console.WriteLine("Hello world!");
Console.ReadLine();

Se compilează codul și dacă nu sunt erori se va rula aplicația ConsoleApplication1.exe aflată în directorul
proiectului – subdirectorul \bin\Debug:

Fig. 2. Rularea aplicației


Structura de directoare este una arborescentă având una sau mai multe rădăcini – drive-urile din sistem.

Fig. 3. Structura de drive-uri Windows


Completați codul de mai sus cu o funcționalitate ce listează drive-urile din sistem și informații despre acestea. Se
va folosi clasa DriveInfo și metodele și proprietățile acesteia [1]:
DriveInfo[] allDrives = DriveInfo.GetDrives();
Pagina 2
Laborator Sisteme de Operare. Lucrarea 3
foreach (DriveInfo d in allDrives){
Console.WriteLine("Drive {0}", d.Name);
Console.WriteLine(" Drive type: {0}", d.DriveType);
if (d.IsReady == true) {
Console.WriteLine(" Volume label: {0}", d.VolumeLabel);
Console.WriteLine(" Total size of drive: {0, 15} bytes ", d.TotalSize);
}
}
Console.ReadLine();

Compilați și rulați aplicația:

Fig. 4. Informații despre drive-urile Windows

Notă – pentru utilizarea anumitor metode sau clase este necesară includerea namespace-ului corespunzator.
De exemplu clasele pentru lucrul cu directoare sau fișiere necesită adăugarea namespace-ului System.IO:

using System.IO;

EXERCIȚIUL 1:
Consultați proprietățile clasei DriveInfo. Modificați programul astfel încât să afișați la consolă diversele
informații despre dive-uri, precum formatul, spațiul liber disponibil.

EXERCIȚIUL 2:
Modificați programul de la exercițiul 1 și afișați informațiile despre dimensiune și spațiu in KB, MB respectiv GB.

EXERCIȚIUL 3:
Introduceți un memory stick, memory card sau un drive extern (conectat prin USB) și rulați din nou programele
de la exercițiile 1 și 2.

Structura arborescentă de directoare are drept rădăcină drive-urile. Astfel plecând de la un drive (de ex. C:)
sistemul de operare gestionează structura de directoare și subdirectoare precum și fișierele aflate în aceste
directoare. Putem utiliza clasa Directory pentru lucrul cu directoare. Aceasta permite accesul la informații
despre directoarea, crearea, mutarea sau ștergerea de directoare.
Pentru listarea subdirectoarelor dintr-un director se apelează metoda statică EnumerateDirectories:
string directory = @"E:\Facultate\SO\ConsoleApplication1";

Pagina 3
Laborator Sisteme de Operare. Lucrarea 3
List<string> dirs = new List<string>(Directory.EnumerateDirectories(directory));
foreach (var dir in dirs){
Console.WriteLine("Director: {0}", dir);
}

Compilați și rulați programul:

Fig. 5. Listarea directoarelor dintr-un director


Se observă că metoda EnumerateDirectories listează doar subdirectoarele aflate pe primul nivel în directorul
dat ca parametru, nu se listează si subdirectoarele de pe nivelele următoare.

Notă – pentru utilizarea anumitor metode sau clase este necesară includerea namespace-ului corespunzator.
De exemplu clasele pentru lucrul cu colecții necesită adăugarea namespace-ului System.Collections.Generic:

using System.Collections.Generic;

EXERCIȚIUL 4:
Creați o funcție recursivă pentru afișarea tuturor subdirectoarelor dintr-un director, indiferent de nivelul pe
care acestea se află (afișați întreaga arborescență).

Pentru crearea unui subdirector într-un director se apelează metoda statică CreateDirectory. Completați codul
cu următoarea secvență ce creează un nou director în calea specificată:
string newDirectory = @"Test";
string fullPath = string.Format("{0}{1}{2}", directory, Path.DirectorySeparatorChar, newDirectory);
if (Directory.Exists(fullPath)){
Console.WriteLine("That path exists already.");
return;
}
Directory.CreateDirectory(fullPath);

După crearea directorului afișați lista de subdirectoare. Compilați și rulați aplicația:

Fig. 6. Listarea directoarelor dintr-un director


Fiecare sistem de operare sau sistem de organizare a fișierelor pe disk (FT32, NTFS, etc.) folosește o serie de
convenții de denumire a directoarelor și a fișierelor.

Pagina 4
Laborator Sisteme de Operare. Lucrarea 3

De exemplu convențiile pentru formatul NTFS sunt:

• numele fișierelor sau al directoarelor nu poate depăși 255 de caractere (inclusiv extensia).
• numele păstrează literele mici/mari, dar nu sunt case sensitive.
• numele nu poate conține caracterele:
? " / \ < > * | :

Fiecare sistem particular poate avea convenții diferite. De exemplu utilizând metoda
Path.GetInvalidFileNameChars se pot obține caracterele invalide pentru sistemul curent:
char[] invalidFileChars = Path.GetInvalidFileNameChars();
foreach (char c in invalidFileChars){
if (Char.IsWhiteSpace(someChar)) {
Console.WriteLine(",\t{0:X4}", (int)c);
}
else {
Console.WriteLine("{0:c},\t{1:X4}", c, (int)c);
}
}

Separatorul de directoare folosit în sistemul curent poate fi obținut prin apelul metodei
Path.DirectorySeparatorChar:
Console.WriteLine("Path.DirectorySeparatorChar: {0}", Path.DirectorySeparatorChar);

Pentru ștergerea unui subdirector se apelează metoda statică Delete. Completați codul cu următoarea secvență
ce șterge un director aflat la calea specificată:
string directory = @"E:\Facultate\SO\ConsoleApplication1";
string newDirectory = @"Test";
string fullPath = string.Format("{0}{1}{2}", directory, Path.DirectorySeparatorChar, newDirectory);
if (!Directory.Exists(fullPath)){
Console.WriteLine("That path do not exists");
return;
}
Directory.Delete(fullPath);
List<string> dirs = new List<string>(Directory.EnumerateDirectories(directory));
foreach (var dir in dirs){
Console.WriteLine("Director: {0}", dir);
}
Console.ReadLine();

Atenție – nu ștergeți sau modificați directoare sau fișiere ale sistemului de operare sau alte directoare sau
fișiere importante din sistem! Lucrați cu directoare sau fișiere create special pentru teste.

Accesul la directoare se poate face specificând calea completă (calea absolută) sau calea parțială (relativ la
calea aplicației). În exemplele anterioare s-a utilizat calea absolută. Calea curentă a aplicației se poate afla
apelând metoda statică GetCurrentDirectory:
string directory = @"E:\Facultate\SO\ConsoleApplication1";
string newDirectory = @"Test";
string fullPath = string.Format("{0}{1}{2}", directory, Path.DirectorySeparatorChar, newDirectory);
Directory.CreateDirectory(fullPath);

newDirectory = @"Test2";

Pagina 5
Laborator Sisteme de Operare. Lucrarea 3
fullPath = string.Format("{0}{1}{2}", Directory.GetCurrentDirectory(), Path.DirectorySeparatorChar,
newDirectory);
Directory.CreateDirectory(fullPath);

newDirectory = @"Test3";
Directory.CreateDirectory(newDirectory);

List<string> dirs = new List<string>(Directory.EnumerateDirectories(directory));


foreach (var dir in dirs){
Console.WriteLine("Director: {0}", dir);
}
dirs = new List<string>(Directory.EnumerateDirectories(Directory.GetCurrentDirectory()));
foreach (var dir in dirs){
Console.WriteLine("Director: {0}", dir);
}
Console.ReadLine();

În cazul directoarelor Test2 si Test3 calea este cea curentă, a aplicației, aflată în directorul proiectului,
subdirectorul bin\Debug.

Fig. 6. Listarea directoarelor create

EXERCIȚIUL 5:
Plecând de la un director sursă care conține subdirectoare pe mai multe nivele, creați o structură identică de
subdirectoare într-un nou director aflat pe alt drive. La final ștergeți subdirectoarele și directorul sursă.

EXERCIȚIUL 6:
Se dă un director sursă care conține subdirectoare, redenumiți directorul sursă.

2. LUCRUL CU FIȘIERE
Pentru lucrul cu fișiere se poate folosi clasa File [2]. Clasa File poate fi folosită pentru operații de copiere,
mutare, redenumire, creare, deschidere, ștergere, sau scriere aplicate unui singur fișier la un moment dat. De
asemenea clasa File permite citirea sau scrierea atributelor unui fișier sau acces la informațiile privind data
creării, accesului sau scrierii unui fișier [3].
Platforma .Net oferă mai multe posibilități pentru crearea și scrierea de fișiere. De exemplu utilizând metoda
statică File.WriteAllLines se poate scrie o linie de text sau mai multe într-un nou fișier:
string directory = Directory.GetCurrentDirectory();
string newFile = @"Test.txt";
string fullPath = string.Format("{0}{1}{2}", directory, Path.DirectorySeparatorChar, newFile);
string[] lines = { "New line 1", "New line 2" };
File.WriteAllLines(fullPath, lines);

Codul de mai sus creează un fișier text și scrie în acesta două linii de text.

Pagina 6
Laborator Sisteme de Operare. Lucrarea 3

Fig. 7. Crearea unui fișier text


Dacă fișierul există, codul de mai sus suprascrie fișierul. Modificați textul sau numărul liniilor de text, compilați
și rulați din nou aplicația, observați conținutul fișierului.
Pentru a scrie un text în continuarea unuia existent (append) se poate utiliza metoda statică
File.AppendAllLines. Înlocuiți în programul anterior metoda WriteAllLines cu AppendAllLines, compilați și rulați
programul de mai multe ori, observați conținutul fișierului:

Fig. 8. Scrierea în continuarea unui fișier existent


Pentru a citi în mod text un fișier se poate utiliza metoda statică ReadAllLines:
string[] lines = File.ReadAllLines(fullPath);
foreach (string line in lines)
Console.WriteLine(line);

EXERCIȚIUL 6:
Completați codul de mai sus pentru a citi liniile de text din fișier si a scrie în același fișier o noua linie de text
între două linii existente.

EXERCIȚIUL 7:
Modificați codul de mai sus pentru crea un fișier HTML care să conțină un document HTML cu elemente
specifice acestui tip de format (body, head, table, div, etc). Deschideți fișierul cu un browser Web.

EXERCIȚIUL 8:
Modificați codul de mai sus pentru crea un fișier CSV (comma separated values) care să conțină un tabel cu trei
coloane text și numeric, tabelul să conțină pe prima linie un cap de tabel, completați cel puțin 10 linii cu diverse
date numerice și text conform capului de tabel. Deschideți fișierul cu un program de calcul tabelar tip Excel.

Pagina 7
Laborator Sisteme de Operare. Lucrarea 3

Pentru copierea, mutarea sau ștergerea fișierelor se pot folosi metodele statice Copy, Move, Delete ale clasei
File.

Operații cu fișiere multiple se pot realiza utilizând Directory.GetFiles sau DirectoryInfo.GetFiles. În continuare
este prezentat un exemplu de apel și de obținere a listei de fișiere dintr-un director:
string directory = Directory.GetCurrentDirectory();
string[] fileEntries = Directory.GetFiles(directory);
foreach (string fileName in fileEntries)
Console.WriteLine("File name '{0}', file extension '{1}'",
Path.GetFileNameWithoutExtension(fileName), Path.GetExtension(fileName));
Console.ReadLine();

EXERCIȚIUL 9:
Creați o aplicație care copiază toate fișierele dintr-un director într-un director nou creat.

EXERCIȚIUL 10:
Modificați aplicația de la exercițiul anterior pentru a copia doar fișiere cu o anumita extensie (de ex. HTML).

BIBLIOGRAFIE
[1]. C# Programming Guide – DriveInfo Class https://docs.microsoft.com/en-
us/dotnet/api/system.threading.thread?view=netframework-4.8
[2]. C# Programming Guide – File Class https://docs.microsoft.com/en-
us/dotnet/api/system.io.file?view=netframework-4.8
[3]. C# Programming Guide – operații uzuale I/O https://docs.microsoft.com/en-
us/dotnet/standard/io/common-i-o-tasks?view=netframework-4.8
[4]. C# Programming Guide – File and Stream I/O https://docs.microsoft.com/en-us/dotnet/standard/io/

Pagina 8
Laborator Sisteme de Operare. Lucrarea 3

LUCRAREA 4

APLICAȚIE DE TIP WINDOWS EXPLORER.

În această lucrare ne propunem realizarea unei aplicații de tip Windows Explorer pentru lucrul cu directoare și
fișiere. Vor fi folosite noțiunile din lucrările precedente privind lucrul cu procese, directoare sau fișiere.
Vom utiliza mediul de dezvoltare Microsoft Visual Studio ediția 2015 pentru a edita, compila și genera cod
executabil (programe, aplicații). Se va crea o aplicație Visual C# de tip Windows Form Application. Vom denumi
aceasta aplicație WindowsExplorer:

Fig. 1. Crearea unui nou proiect C# de tip Windows - Windows Form Application
Codul creat implicit de către IDE este:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsExplorer
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();

Pagina 1
Laborator Sisteme de Operare. Lucrarea 3
}
}
}

Codul va crea o interfață windows (formular – Form Windows) Form1 derivată din clasa Form.

Fig. 2. Aplicația inițială WindowsExplorer

1. LUCRUL CU STRUCTURA DE DIRECTOARE


Interfața utilizator a aplicației va conține două componente:
- Strunctura arborescentă de drive-uri și directoare (stânga) implementată sub forma unui control
arborescent (TreeView)
- Lista de directoare și fișiere dintr-un director (dreapta) implementată sub forma unui control de tip listă
(ListView)
Pentru adăugarea celor două componente se comută în modul Design (dacă nu este deja), și din panoul Toolbox
se adaugă (drag and drop) cele două controale de tip TreeView și respectiv ListView. De asemenea se adaugă un
control de tip TextBox pentru afișarea directorului curent selectat:

Pagina 2
Laborator Sisteme de Operare. Lucrarea 3

Fig. 3. Adăugarea controalelor


Fig. 2. Structura aplicației WindowsExplorer
Funcționalitatea aplicației prevede afișarea structurii arborescente de directoare pornind de la drive-urile din
sistem (controlul TreeView) având ca rădăcină computerul (My PC), și pentru un drive sau director ales din acest
arbore să se afișeze lista de subdirectoare și fișiere în controlul ListView. Afișarea în listă a elementelor se va face
pe trei coloane: nume, dimensiune, tip. Pentru a configura lista se apasă mouseul (right click) pe controlul
ListView și în tabul Properties se alege secțiunea Columns și se apasă butonul :

Fig. 4. Definirea coloanelor controlului ListView


Se adaugă cele trei coloane Nume, Dimensiune, Tip:

Fig. 5. Definirea coloanelor

Se setează modul de vizualizare Details pentru controlul ListView astfel încât să fie afișate elementele sub forma
tabelară (tabel cu cele trei coloane Nume, Dimensiune, Tip):

Pagina 3
Laborator Sisteme de Operare. Lucrarea 3

Fig. 6. Stabilirea detaliilor afișate


În continuare vom defini evenimentul Load pentru fereastra principală a aplicației. În modul Design se apasă
butonul dreapta al mouseului (right click) pe o zona din fereastra principală, ca în fig. de mai jos. Se alege opțiunea
Properties din meniul care apare, și în tabul Properties, se alege secțiunea Events , pentru evenimentul Load,
se completează numele unei metode ce va trata evenimentul de încărcare a ferestrei:

Fig. 7. Tratarea evenimentului Load


Se completează numele metodei, OnLoad , în câmpul text din dreptul evenimentului Load, se apasă Enter. Dorim
astfel ca la încărcarea interfeței aplicației să se execute o serie de operații descrise în continuare. Metoda OnLoad
se va crea de către IDE în fișierul sursă:
namespace WindowsExplorer
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void OnLoad(object sender, EventArgs e)
{
}
}
}

Pagina 4
Laborator Sisteme de Operare. Lucrarea 3

Se va completa metoda OnLoad cu o secvență de cod pentru încărcarea arborelui TreeView. Rădăcina se va crea
cu numele My PC, si pe următorul nivel se vor adăuga drive-urile din sistem:
DriveInfo[] allDrives = DriveInfo.GetDrives();

TreeNode mainNode = new TreeNode();


mainNode.Name = "mainNode";
mainNode.Text = "My PC";
mainNode.ToolTipText = "My PC";

foreach (DriveInfo d in allDrives)


{
TreeNode node = new TreeNode();
node.Name = d.Name;
node.Text = string.Format("{0} ({1})", d.Name, d.VolumeLabel);
node.ToolTipText = d.Name;
mainNode.Nodes.Add(node);
}
mainNode.Expand();
this.treeView1.Nodes.Add(mainNode);

În continuare se va completa metoda OnLoad cu o secvență de cod pentru definirea imaginilor (iconițelor)
specifice drive-urilor, directoarelor și fișierelor. În acest fel elementele afișate în controlul ListView vor fi
diferențiate în funcție de tipul lor:
ImageList imageListSmall = new ImageList();
ImageList imageListLarge = new ImageList();

imageListSmall.Images.Add(Bitmap.FromFile(@"E:\..\WindowsExplorer\Drive.bmp"));
imageListSmall.Images.Add(Bitmap.FromFile(@"E:\..\WindowsExplorer\Director.bmp"));
imageListSmall.Images.Add(Bitmap.FromFile(@"E:\..\WindowsExplorer\Fisier.bmp"));

imageListLarge.Images.Add(Bitmap.FromFile(@"E:\..\WindowsExplorer\Drive.bmp"));
imageListLarge.Images.Add(Bitmap.FromFile(@"E:\..\WindowsExplorer\Director.bmp"));
imageListLarge.Images.Add(Bitmap.FromFile(@"E:\..\WindowsExplorer\Fisier.bmp"));

listView1.LargeImageList = imageListLarge;
listView1.SmallImageList = imageListSmall;

În prealabil se vor crea trei imagini BMP de dimensiuni reduse (ex. 32x32 pixeli) utilizând aplicația Paint și se vor
salva în directorul proiectului. Se vor completa căile exacte către aceste fișiere în codul prezentat mai sus.

Fig. 6. Editarea unei imagini utilizând Paint

Se compilează și se execută aplicația:

Pagina 5
Laborator Sisteme de Operare. Lucrarea 3

Fig. 9. Interfața aplicației WindowsExplorer


Este necesar ca în continuare să afișăm în controlul ListView elementele conținute în directorul selectat de
utilizator din arborele de drive-uri și directoare. Alegem astfel ca evenimentul MouseClick să determine acest
comportament. În modul Design se apasă butonul dreapta al mouseului (right click) pe controlul TreeView și se
implementează evenimentul NodeMouseClick printr-o metodă denumită treeView1_NodeMouseClick:

Fig. 10. Tratarea evenimentului de selectare a unui nod (drive sau director) din TreeView
IDEul va crea metoda treeView1_NodeMouseClick:
private void treeView1_NodeMouseClick(object sender, TreeNodeMouseClickEventArgs e){
}

Se va completa metoda cu codul necesar tratării cazurilor:


- se dă click pe My PC. În acest caz se vor afișa drive-urile și informațiile acestora (nume, dimensiune);
- se dă click pe un drive sau un folder. În acest caz se vor afișa subdirectoarele din driveul sau directorul
curent precum și fișierele din acest drive sau director.
textBox1.Text = e.Node.ToolTipText;

listView1.Items.Clear();

Pagina 6
Laborator Sisteme de Operare. Lucrarea 3
if (e.Node.ToolTipText == "My PC")
{
DriveInfo[] allDrives = DriveInfo.GetDrives();
foreach (DriveInfo d in allDrives)
{
ListViewItem item = new ListViewItem(d.Name,0);
item.ToolTipText = Path.Combine(e.Node.ToolTipText, d.Name);
item.SubItems.Add("Drive");
item.SubItems.Add((d.TotalSize / 1024 / 1024).ToString()); //MB
listView1.Items.Add(item);
}
}
else
{
List<string> dirs = new
List<string>(Directory.EnumerateDirectories(e.Node.ToolTipText));
foreach (var dir in dirs)
{
ListViewItem item = new ListViewItem(dir,1);
item.ToolTipText = Path.Combine(e.Node.ToolTipText, dir);
item.SubItems.Add("Director");
item.SubItems.Add(String.Empty);
listView1.Items.Add(item);
}

string[] fileEntries = Directory.GetFiles(e.Node.ToolTipText);


foreach (string file in fileEntries)
{
long length = new System.IO.FileInfo(Path.Combine(e.Node.ToolTipText,
file)).Length;

ListViewItem item = new ListViewItem(file,2);


item.ToolTipText = Path.Combine(e.Node.ToolTipText, file);
item.SubItems.Add("Fisier");
item.SubItems.Add((length / 1024 ).ToString()); //KB
listView1.Items.Add(item);
}
}

În codul de mai sus se va observa tratarea diferită a celor trei tipuri de elemente: drive, director, fișier. De
asemenea se va observa apelul constructorului ListViewItem unde al doilea parametru reprezintă indexul din lista
de iconițe aferente tipului respectiv: 0 – drive, 1 – director, 2 – fișier.

Până în acest moment controlul de tip TreeView afișează doar primele două nivele din arborescența complexă a
drive-urilor dintr-un calculator:

Pagina 7
Laborator Sisteme de Operare. Lucrarea 3

Fig. 11. Arborescența inițială


O implementare completă necesită și o funcție de avansare în arborescență (drill down, expandare). Aceasta se
poate realiza de exemplu la dublu click pe un nod din arbore. În modul Design se va trata evenimentul
NodeMouseDoubleClick al controlului TreeView prin crearea unei noi metode treeView1_NodeMouseDoubleClick:

Fig. 12. Tratarea evenimentului NodeMouseDoubleClick


IDEul va crea metoda treeView1_NodeMouseDoubleClick:
private void treeView1_NodeMouseDoubleClick(object sender, TreeNodeMouseClickEventArgs e){

Se va completa metoda astfel încât la selectarea unui nod din arbore să încarce în norul curent lista de
subdirectoare și să expandeze acest nod:
textBox1.Text = e.Node.ToolTipText;
if (e.Node.Nodes.Count > 0)
return;

List<string> dirs = new List<string>(Directory.EnumerateDirectories(e.Node.ToolTipText));


foreach (var dir in dirs)
{
TreeNode node = new TreeNode();
node.Name = dir;
node.Text = dir;
node.ToolTipText = Path.Combine(e.Node.ToolTipText, dir);
Pagina 8
Laborator Sisteme de Operare. Lucrarea 3
e.Node.Nodes.Add(node);
}
e.Node.Expand();

Compilați și rulați aplicația, observați completarea arborelui pe măsură ce explorați subdirectoarele:

Fig. 13. Aplicația WindowsExplorer

EXERCIȚIUL 1:
Completați lista de iconițe pentru a afișa imagini particularizate pentru anumite tipuri de fișiere (ex. DOCX, PDF,
XLSX, TXT, etc.).

EXERCIȚIUL 2:
Modificați programul astfel încât să se afișeze informațiile despre dimensiune utilizând B, KB, MB respectiv GB,
în funcție de dimensiunea fiecărui element. Ex.: daca fișierul are dimensiune de ordinul B să fie afișat 23 B, dacă
fișierul are dimensiune de ordinul KB să fie afișat 123 KB, samd.

2. LUCRUL CU FIȘIERE ȘI DIRECTOARE


În capitolul precedent am realizat funcțiile de afișare și parcurgere a arborescenței de drive-uri, directoare,
subdirectoare și fișiere. O aplicație de tip Windows Explorer are nevoie și de operații de lucru individual cu
directoarele sau cu fișierele. Vom completa aplicația cu funcții de ștergere, redenumire fișiere sau directoare,
sau de deschidere fișiere.
Dar pentru a lucra cu un director sau un fișier anume din lista ListView este necesar în primul rând să cunoaștem
elementul asupra căruia actionăm. Astfel se completează clasa Form1 cu o dată membră:
public partial class Form1 : Form{
private ListViewItem selectedListItem;
public Form1(){
InitializeComponent();

Pagina 9
Laborator Sisteme de Operare. Lucrarea 3
}

..

Se va trata evenimentul ItemSelectionChanged care se declanșează la selectarea unui element din controlul
ListView, evenimentul se va trata în metoda listView1_ItemSelectionChanged:

Fig. 14. Tratarea evenimentului de selecție item


Se completează codul generat de către IDE astfel:
private void listView1_ItemSelectionChanged(object sender,
ListViewItemSelectionChangedEventArgs e)
{
selectedListItem = e.Item;
textBox1.Text = e.Item.ToolTipText;
}

Pentru a implementa operațiile cu fișiere și directoare vom crea în continuare meniul contextual necesar. Din
modul Design se adaugă un control de tip ContextMenuStrip în formularul aplicației:

Fig. 15. Adăugarea unui meniu contextual


Se va completa meniul cu trei opțiuni: Sterge, Redenumire, Deschide prin click dreapta pe control și alegând
opțiunea Edit items..:

Pagina 10
Laborator Sisteme de Operare. Lucrarea 3

Fig.16 . Adăugarea opțiunilor în meniul contextual


Meniul se va afișa prin apăsarea butonului dreapta al mouseului (right click) în controlul ListView. Se va trata
evenimentul MouseClick pentru controlul ListView în metoda listView1_MouseClick:

Fig. 17. Tratarea evenimentelor MouseClick

IDEul va crea metoda:


private void listView1_MouseClick(object sender, MouseEventArgs e){
}

Se completează metoda pentru afișarea meniului contextual:


if (e.Button == MouseButtons.Right)
{
Point p = e.Location;
p.Offset(this.Location);
p.Offset(listView1.Location);
p.Offset(new Point(0, contextMenuStrip1.Height));
contextMenuStrip1.Show(p);
}

Tratarea opțiunilor de meniu presupune tratarea evenimentului Click pentru fiecare din cele trei opțiuni. În modul
Design selectați controlul contextMenuStrip1 și dublu click pe fiecare din cele trei opțiuni de meniu, astfel se vor
cerea metodele de implementare a evenimentului Click pentru fiecare din acestea:

Pagina 11
Laborator Sisteme de Operare. Lucrarea 3

Fig. 18. Tratarea evenimentului de Click pentru meniul contextual


IDEul va crea cele trei metode:

private void toolStripMenuItem1_Click(object sender, EventArgs e) {


}
private void toolStripMenuItem2_Click(object sender, EventArgs e) {
}
private void toolStripMenuItem3_Click(object sender, EventArgs e) {
}

Vom implementa operația Open, care deschide un fișier, de exemplu deschizând browserul pentru vizualizarea
unui fișier HTML:
private void toolStripMenuItem3_Click(object sender, EventArgs e)
{
if (selectedListItem.SubItems[1].Text.ToString() == "Fisier")
{
if (!string.IsNullOrEmpty(selectedListItem.ToolTipText) &&
Path.GetExtension(selectedListItem.ToolTipText) == ".HTML")
{
Process.Start("chrome.exe", selectedListItem.ToolTipText);
}
}
}

Vom implementa operația Sterge, care șterge fișierul sau directorul:


private void toolStripMenuItem1_Click(object sender, EventArgs e)
{
if (selectedListItem.SubItems[1].Text.ToString() == "Fisier")
{
File.Delete(selectedListItem.ToolTipText);
listView1.Items.Remove(selectedListItem);
listView1.Refresh();
}
if (selectedListItem.SubItems[1].Text.ToString() == "Director")
{
Directory.Delete(selectedListItem.ToolTipText, true);
listView1.Items.Remove(selectedListItem);
listView1.Refresh();

Pagina 12
Laborator Sisteme de Operare. Lucrarea 3

}
}

Pentru a implementa modificarea numelui unui fișier sau director este necesar să avem un formular (Windows
Form) suplimentar pentru specificarea noului nume. Se accesează panoul Solution explorer si click dreapta pe
numele proiectului, se adaugă un nou element Form2:

Fig. 19. Adăugarea unui nou formular în proiect


Aceasta se va completa cu un element de tip Label, TextBox și un Button:

Fig. 20. Designul formularului Form2


Se va trata evenimentul Click pentru buton într-o metodă button1_Click:

Fig. 21. Tratarea butonului OK

Pagina 13
Laborator Sisteme de Operare. Lucrarea 3

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsExplorer
{
public partial class Form2 : Form
{
public string Nume;
public Form2(string text)
{
InitializeComponent();
textBox1.Text = text;
}

private void button1_Click(object sender, EventArgs e)


{
Nume = textBox1.Text;
this.Close();
}
}
}
În continuare se va completa metoda ce implementează opțiunea de meniu Redenumeste. Aceasta trebuie să
gestioneze diferit funcția de redenumire a unui fișier respectiv de redenumire a unui director:
private void toolStripMenuItem2_Click(object sender, EventArgs e)
{

if (selectedListItem.SubItems[1].Text.ToString() == "Fisier")
{
Form2 form = new Form2(Path.GetFileName(selectedListItem.Text));
form.ShowDialog();

if (string.IsNullOrEmpty(form.Nume) || form.Nume ==
Path.GetFileName(selectedListItem.Text))
return;

string newFile = Path.Combine(Path.GetDirectoryName(selectedListItem.ToolTipText),


form.Nume);
System.IO.File.Move(selectedListItem.ToolTipText, newFile);
selectedListItem.ToolTipText = newFile;
selectedListItem.Text = newFile;
listView1.Refresh();
}
if (selectedListItem.SubItems[1].Text.ToString() == "Director")
{
Form2 form = new Form2(new DirectoryInfo(selectedListItem.Text).Name);
form.ShowDialog();

if (string.IsNullOrEmpty(form.Nume) || form.Nume == new


DirectoryInfo(selectedListItem.Text).Name)
return;

string newFolder =
Path.Combine(Directory.GetParent(selectedListItem.ToolTipText).FullName, form.Nume);

Pagina 14
Laborator Sisteme de Operare. Lucrarea 3
Directory.Move(selectedListItem.ToolTipText, newFolder);
selectedListItem.ToolTipText = newFolder;
selectedListItem.Text = newFolder;
listView1.Refresh();
}
}

EXERCIȚIUL 3:
Completați metoda toolStripMenuItem3_Click pentru a putea trata și alte tipuri de fișiere, ca de exemplu XLSX,
DOCX, PDF. Completați metoda toolStripMenuItem3_Click pentru a putea trata fișiere executabil (rularea
acestora).

EXERCIȚIUL 4:
Completați metoda toolStripMenuItem3_Click pentru a putea trata și directoare, în acest caz să deschidă aplicația
Explorer din Windows la directorul respectiv.

Pagina 15

S-ar putea să vă placă și