<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://so.v2.cs.unibo.it/wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Matt</id>
	<title>Sistemi Operativi - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="https://so.v2.cs.unibo.it/wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Matt"/>
	<link rel="alternate" type="text/html" href="https://so.v2.cs.unibo.it/wiki/index.php/Special:Contributions/Matt"/>
	<updated>2026-05-04T09:23:00Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.35.5</generator>
	<entry>
		<id>https://so.v2.cs.unibo.it/wiki/index.php?title=2017.09.11&amp;diff=2195</id>
		<title>2017.09.11</title>
		<link rel="alternate" type="text/html" href="https://so.v2.cs.unibo.it/wiki/index.php?title=2017.09.11&amp;diff=2195"/>
		<updated>2018-05-09T15:51:39Z</updated>

		<summary type="html">&lt;p&gt;Matt: Created page with &amp;quot;Pdf completo qui: [http://www.cs.unibo.it/~renzo/so/compiti/2017.09.11.tot.pdf 2017.09.11.tot.pdf]  =Esercizio c1= &amp;lt;syntaxhighlight lang=c&amp;gt; Un semaforo intelligente controlla ...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Pdf completo qui: [http://www.cs.unibo.it/~renzo/so/compiti/2017.09.11.tot.pdf 2017.09.11.tot.pdf]&lt;br /&gt;
&lt;br /&gt;
=Esercizio c1=&lt;br /&gt;
&amp;lt;syntaxhighlight lang=c&amp;gt;&lt;br /&gt;
Un semaforo intelligente controlla un incrocio al quale si può accedere da 4 direzioni N, E, S, W. &lt;br /&gt;
Un solo veicolo alla volta deve occupare l’area dell’incrocio. &lt;br /&gt;
Se sono presenti veicoli da tutte le direzioni, il semaforo fa avanzare ciclicamente un veicolo da ogni direzione (NESWNESWNESW…). &lt;br /&gt;
Se nessun veicolo è in attesa da una direzione, il turno viene saltato.&lt;br /&gt;
Quella che segue è la porzione di di programma eseguita dai veicoli per passare attraverso l’incrocio:&lt;br /&gt;
&lt;br /&gt;
 	crossing.enter(direction)&lt;br /&gt;
 	…critical section&lt;br /&gt;
 	crossing.exit(direction)&lt;br /&gt;
&lt;br /&gt;
Nonostante sia un semaforo, questo esercizio deve venir risolto coi monitor.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Soluzione proposta 1'''&lt;br /&gt;
&amp;lt;syntaxhighlight lang=c&amp;gt;&lt;br /&gt;
monitor crossing{&lt;br /&gt;
	&lt;br /&gt;
	enum direction = {N, E, S, W};&lt;br /&gt;
	condition ok2cross[4];&lt;br /&gt;
	boolean busy;&lt;br /&gt;
	int waiting[4];&lt;br /&gt;
&lt;br /&gt;
	procedure_entry void enter(enum direction d){&lt;br /&gt;
		waiting[d]++;&lt;br /&gt;
		if(waiting[d] &amp;gt; 1) ok2cross[d].wait();&lt;br /&gt;
		busy = true;&lt;br /&gt;
		waiting[d]--;&lt;br /&gt;
&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	procedure_entry void exit(enum direction d){&lt;br /&gt;
		busy = false;&lt;br /&gt;
		for(int i = 1; i &amp;lt; 5; i++){ &lt;br /&gt;
			if(waiting[(d + i) % 4]) ok2cross[(d + i) % 4].signal();&lt;br /&gt;
		}&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	crossing(){&lt;br /&gt;
		for(int i = 0; i &amp;lt; 4; i++) waiting[i] = 0;&lt;br /&gt;
		busy = false;&lt;br /&gt;
	}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>Matt</name></author>
	</entry>
	<entry>
		<id>https://so.v2.cs.unibo.it/wiki/index.php?title=Prove_svolte_e_soluzioni_proposte&amp;diff=2194</id>
		<title>Prove svolte e soluzioni proposte</title>
		<link rel="alternate" type="text/html" href="https://so.v2.cs.unibo.it/wiki/index.php?title=Prove_svolte_e_soluzioni_proposte&amp;diff=2194"/>
		<updated>2018-05-09T15:48:06Z</updated>

		<summary type="html">&lt;p&gt;Matt: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Questa pagina serve a raccogliere prove d'esame svolte (che possono essere utili alla preparazione) e soluzioni proposte a tali prove da sottoporre a peer-review.&lt;br /&gt;
&lt;br /&gt;
[[2017.09.11]]&lt;br /&gt;
&lt;br /&gt;
[[2017.07.17]]&lt;/div&gt;</summary>
		<author><name>Matt</name></author>
	</entry>
	<entry>
		<id>https://so.v2.cs.unibo.it/wiki/index.php?title=2017.07.17&amp;diff=2193</id>
		<title>2017.07.17</title>
		<link rel="alternate" type="text/html" href="https://so.v2.cs.unibo.it/wiki/index.php?title=2017.07.17&amp;diff=2193"/>
		<updated>2018-05-09T13:25:01Z</updated>

		<summary type="html">&lt;p&gt;Matt: /* Soluzione proposta 1 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Pdf completo qui: [http://www.cs.unibo.it/~renzo/so/compiti/2017.07.17.tot.pdf 2017.07.17.tot.pdf]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=Esercizio c.1=&lt;br /&gt;
&amp;lt;syntaxhighlight lang=c&amp;gt;&lt;br /&gt;
In una conferenza il coordinatore decide l’ordine degli oratori e li chiama uno ad uno per poter fare il proprio intervento.&lt;br /&gt;
Se l’oratore arriva in ritardo (non sta attendendo al momento della chiamata) perde il diritto di poter parlare.&lt;br /&gt;
&lt;br /&gt;
Coordinatore: process&lt;br /&gt;
	while True:&lt;br /&gt;
		chiamato = next(); //next è la funzione che restituisce il nome del prossimo oratore&lt;br /&gt;
		print(“Chiamo ora a parlare “,chiamato);&lt;br /&gt;
		if (conf.chiama(chiamato))&lt;br /&gt;
			print(“ringrazio “, chiamato,” per la relazione”);&lt;br /&gt;
		else&lt;br /&gt;
			print(“mi dispiace che “, chiamato, “non sia presente”);&lt;br /&gt;
&lt;br /&gt;
		Oratore[nome]: for nome in set_of_speakers&lt;br /&gt;
			if conf.arrivato(nome):&lt;br /&gt;
				//presentazione&lt;br /&gt;
				conf.finepresentazione(nome)&lt;br /&gt;
&lt;br /&gt;
Scrivere il monitor conf. &lt;br /&gt;
La funzione chiama aspetta che il relatore chiamato abbia completato l’intervento, se prensente, e restituisce vero altrimenti restituisce falso.&lt;br /&gt;
La funzione arrivato segnala la presenza e pone il relatore in attesa del proprio turno. &lt;br /&gt;
Se il relatore è già stato chiamato ed era assente restituisce falso.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Soluzione proposta 1 ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=c&amp;gt;&lt;br /&gt;
Monitor conf{&lt;br /&gt;
&lt;br /&gt;
	boolean called[N_SPEAKERS];&lt;br /&gt;
	condition ok2speak[N_SPEAKERS];&lt;br /&gt;
	condition ok2call;&lt;br /&gt;
&lt;br /&gt;
	procedure_entry boolean chiama(int chiamato){&lt;br /&gt;
		ok2call.wait();&lt;br /&gt;
		if(ok2speak[chiamato].signal()) {&lt;br /&gt;
			called[nome] = true;&lt;br /&gt;
			return true;&lt;br /&gt;
		}else return false;&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	procedure_entry boolean arrivato(int nome){&lt;br /&gt;
		if(called[nome]) return false;&lt;br /&gt;
		else ok2speak[nome].wait();&lt;br /&gt;
		return true;&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	procedure_entry void finepresentazione(int nome){&lt;br /&gt;
		ok2call.signal();&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	conf(int N_SPEAKERS){&lt;br /&gt;
		for(int i = 0; i &amp;lt; N_SPEAKERS; i++) called[i] = false;&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>Matt</name></author>
	</entry>
	<entry>
		<id>https://so.v2.cs.unibo.it/wiki/index.php?title=2017.07.17&amp;diff=2192</id>
		<title>2017.07.17</title>
		<link rel="alternate" type="text/html" href="https://so.v2.cs.unibo.it/wiki/index.php?title=2017.07.17&amp;diff=2192"/>
		<updated>2018-05-09T13:17:44Z</updated>

		<summary type="html">&lt;p&gt;Matt: /* Esercizio c.1: */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Pdf completo qui: [http://www.cs.unibo.it/~renzo/so/compiti/2017.07.17.tot.pdf 2017.07.17.tot.pdf]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=Esercizio c.1=&lt;br /&gt;
&amp;lt;syntaxhighlight lang=c&amp;gt;&lt;br /&gt;
In una conferenza il coordinatore decide l’ordine degli oratori e li chiama uno ad uno per poter fare il proprio intervento.&lt;br /&gt;
Se l’oratore arriva in ritardo (non sta attendendo al momento della chiamata) perde il diritto di poter parlare.&lt;br /&gt;
&lt;br /&gt;
Coordinatore: process&lt;br /&gt;
	while True:&lt;br /&gt;
		chiamato = next(); //next è la funzione che restituisce il nome del prossimo oratore&lt;br /&gt;
		print(“Chiamo ora a parlare “,chiamato);&lt;br /&gt;
		if (conf.chiama(chiamato))&lt;br /&gt;
			print(“ringrazio “, chiamato,” per la relazione”);&lt;br /&gt;
		else&lt;br /&gt;
			print(“mi dispiace che “, chiamato, “non sia presente”);&lt;br /&gt;
&lt;br /&gt;
		Oratore[nome]: for nome in set_of_speakers&lt;br /&gt;
			if conf.arrivato(nome):&lt;br /&gt;
				//presentazione&lt;br /&gt;
				conf.finepresentazione(nome)&lt;br /&gt;
&lt;br /&gt;
Scrivere il monitor conf. &lt;br /&gt;
La funzione chiama aspetta che il relatore chiamato abbia completato l’intervento, se prensente, e restituisce vero altrimenti restituisce falso.&lt;br /&gt;
La funzione arrivato segnala la presenza e pone il relatore in attesa del proprio turno. &lt;br /&gt;
Se il relatore è già stato chiamato ed era assente restituisce falso.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Soluzione proposta 1 ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=c&amp;gt;&lt;br /&gt;
Monitor conf{&lt;br /&gt;
&lt;br /&gt;
	boolean called[N_SPEAKERS];&lt;br /&gt;
	condition ok2speak[N_SPEAKERS];&lt;br /&gt;
	condition ok2call;&lt;br /&gt;
&lt;br /&gt;
	procedure_entry boolean chiama(chiamato){&lt;br /&gt;
		ok2call.wait();&lt;br /&gt;
		if(ok2speak[chiamato].signal()) {&lt;br /&gt;
			called[nome] = true;&lt;br /&gt;
			return true;&lt;br /&gt;
		}else return false;&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	procedure_entry boolean arrivato(nome){&lt;br /&gt;
		if(called[nome]) return false;&lt;br /&gt;
		else ok2speak[nome].wait();&lt;br /&gt;
		return true;&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	procedure_entry void finepresentazione(nome){&lt;br /&gt;
		ok2call.signal();&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	conf(){&lt;br /&gt;
		for(int i = 0; i &amp;lt; N_SPEAKERS; i++) called[i] = false;&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>Matt</name></author>
	</entry>
	<entry>
		<id>https://so.v2.cs.unibo.it/wiki/index.php?title=2017.07.17&amp;diff=2191</id>
		<title>2017.07.17</title>
		<link rel="alternate" type="text/html" href="https://so.v2.cs.unibo.it/wiki/index.php?title=2017.07.17&amp;diff=2191"/>
		<updated>2018-05-09T13:15:35Z</updated>

		<summary type="html">&lt;p&gt;Matt: Created page with &amp;quot;Pdf completo qui: [http://www.cs.unibo.it/~renzo/so/compiti/2017.07.17.tot.pdf 2017.07.17.tot.pdf]   == Esercizio c.1: ==  &amp;lt;syntaxhighlight lang=c&amp;gt; In una conferenza il coordi...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Pdf completo qui: [http://www.cs.unibo.it/~renzo/so/compiti/2017.07.17.tot.pdf 2017.07.17.tot.pdf]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Esercizio c.1: ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=c&amp;gt;&lt;br /&gt;
In una conferenza il coordinatore decide l’ordine degli oratori e li chiama uno ad uno per poter fare il proprio intervento.&lt;br /&gt;
Se l’oratore arriva in ritardo (non sta attendendo al momento della chiamata) perde il diritto di poter parlare.&lt;br /&gt;
&lt;br /&gt;
Coordinatore: process&lt;br /&gt;
	while True:&lt;br /&gt;
		chiamato = next(); //next è la funzione che restituisce il nome del prossimo oratore&lt;br /&gt;
		print(“Chiamo ora a parlare “,chiamato);&lt;br /&gt;
		if (conf.chiama(chiamato))&lt;br /&gt;
			print(“ringrazio “, chiamato,” per la relazione”);&lt;br /&gt;
		else&lt;br /&gt;
			print(“mi dispiace che “, chiamato, “non sia presente”);&lt;br /&gt;
&lt;br /&gt;
		Oratore[nome]: for nome in set_of_speakers&lt;br /&gt;
			if conf.arrivato(nome):&lt;br /&gt;
				//presentazione&lt;br /&gt;
				conf.finepresentazione(nome)&lt;br /&gt;
&lt;br /&gt;
Scrivere il monitor conf. &lt;br /&gt;
La funzione chiama aspetta che il relatore chiamato abbia completato l’intervento, se prensente, e restituisce vero altrimenti restituisce falso.&lt;br /&gt;
La funzione arrivato segnala la presenza e pone il relatore in attesa del proprio turno. &lt;br /&gt;
Se il relatore è già stato chiamato ed era assente restituisce falso.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Soluzione proposta 1 ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=c&amp;gt;&lt;br /&gt;
Monitor conf{&lt;br /&gt;
&lt;br /&gt;
	boolean called[N_SPEAKERS];&lt;br /&gt;
	condition ok2speak[N_SPEAKERS];&lt;br /&gt;
	condition ok2call;&lt;br /&gt;
&lt;br /&gt;
	procedure_entry boolean chiama(chiamato){&lt;br /&gt;
		ok2call.wait();&lt;br /&gt;
		if(ok2speak[chiamato].signal()) {&lt;br /&gt;
			called[nome] = true;&lt;br /&gt;
			return true;&lt;br /&gt;
		}else return false;&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	procedure_entry boolean arrivato(nome){&lt;br /&gt;
		if(called[nome]) return false;&lt;br /&gt;
		else ok2speak[nome].wait();&lt;br /&gt;
		return true;&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	procedure_entry void finepresentazione(nome){&lt;br /&gt;
		ok2call.signal();&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	conf(){&lt;br /&gt;
		for(int i = 0; i &amp;lt; N_SPEAKERS; i++) called[i] = false;&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>Matt</name></author>
	</entry>
	<entry>
		<id>https://so.v2.cs.unibo.it/wiki/index.php?title=Prove_svolte_e_soluzioni_proposte&amp;diff=2190</id>
		<title>Prove svolte e soluzioni proposte</title>
		<link rel="alternate" type="text/html" href="https://so.v2.cs.unibo.it/wiki/index.php?title=Prove_svolte_e_soluzioni_proposte&amp;diff=2190"/>
		<updated>2018-05-09T13:06:54Z</updated>

		<summary type="html">&lt;p&gt;Matt: /* Prove d'esame svolte e/o soluzioni proposte */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Questa pagina serve a raccogliere prove d'esame svolte (che possono essere utili alla preparazione) e soluzioni proposte a tali prove da sottoporre a peer-review.&lt;br /&gt;
&lt;br /&gt;
[[2017.07.17]]&lt;/div&gt;</summary>
		<author><name>Matt</name></author>
	</entry>
	<entry>
		<id>https://so.v2.cs.unibo.it/wiki/index.php?title=Prove_svolte_e_soluzioni_proposte&amp;diff=2189</id>
		<title>Prove svolte e soluzioni proposte</title>
		<link rel="alternate" type="text/html" href="https://so.v2.cs.unibo.it/wiki/index.php?title=Prove_svolte_e_soluzioni_proposte&amp;diff=2189"/>
		<updated>2018-05-09T13:06:31Z</updated>

		<summary type="html">&lt;p&gt;Matt: Created page with &amp;quot;=Prove d'esame svolte e/o soluzioni proposte=  Questa pagina serve a raccogliere prove d'esame svolte (che possono essere utili alla preparazione) e soluzioni proposte a tali ...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=Prove d'esame svolte e/o soluzioni proposte=&lt;br /&gt;
&lt;br /&gt;
Questa pagina serve a raccogliere prove d'esame svolte (che possono essere utili alla preparazione) e soluzioni proposte a tali prove da sottoporre a peer-review.&lt;br /&gt;
&lt;br /&gt;
[[2017.07.17]]&lt;/div&gt;</summary>
		<author><name>Matt</name></author>
	</entry>
	<entry>
		<id>https://so.v2.cs.unibo.it/wiki/index.php?title=Main_Page&amp;diff=2188</id>
		<title>Main Page</title>
		<link rel="alternate" type="text/html" href="https://so.v2.cs.unibo.it/wiki/index.php?title=Main_Page&amp;diff=2188"/>
		<updated>2018-05-09T13:02:07Z</updated>

		<summary type="html">&lt;p&gt;Matt: /* ANNO ACCADEMICO 2017/18 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Questo &amp;amp;egrave; il Wiki del Corso di Sistemi Operativi&lt;br /&gt;
&lt;br /&gt;
== ANNO ACCADEMICO 2017/18 ==&lt;br /&gt;
&lt;br /&gt;
[[Lezioni Anno Accademico 2017/18 I semestre]]&lt;br /&gt;
&lt;br /&gt;
[[Lezioni Anno Accademico 2017/18 II semestre]]&lt;br /&gt;
&lt;br /&gt;
[[Esercizi ed Esperimenti 2017/18]]&lt;br /&gt;
&lt;br /&gt;
-----&lt;br /&gt;
&lt;br /&gt;
[[Prove svolte e soluzioni proposte]]&lt;br /&gt;
&lt;br /&gt;
== ANNI PRECEDENTI ==&lt;br /&gt;
[[Materiale dell'AA 2016-17]]&lt;br /&gt;
&lt;br /&gt;
[[Materiale dell'AA 2015-16]]&lt;br /&gt;
&lt;br /&gt;
[[Materiale dell'AA 2014-15]]&lt;br /&gt;
&lt;br /&gt;
[[Materiale dell'AA 2013-14]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
Ricordate che per creare un account o quando viene richiesto di risolvere un semplice calcolo occorre ricordare quanto scritto [[qui]]&lt;/div&gt;</summary>
		<author><name>Matt</name></author>
	</entry>
	<entry>
		<id>https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_II_semestre&amp;diff=2186</id>
		<title>Lezioni Anno Accademico 2017/18 II semestre</title>
		<link rel="alternate" type="text/html" href="https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_II_semestre&amp;diff=2186"/>
		<updated>2018-04-04T18:17:53Z</updated>

		<summary type="html">&lt;p&gt;Matt: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Lezione del 28 febbraio 2018 ==&lt;br /&gt;
Inizia oggi la seconda parte del corso di Sistemi Operativi che si terrà dal 28/02/18 al 25/05/18.&amp;lt;br&amp;gt;&lt;br /&gt;
Le lezioni si svolgeranno, il mercoledì e il giovedì, in aula E1. Il venerdì invece ci sposteremo in M1.&amp;lt;br&amp;gt;&lt;br /&gt;
La suddivisione degli argomenti trattati sarà: '''mercoledì''' teoria, '''giovedì''' tendenzialmente parte progettuale (potrebbe iniziare con un po' di teoria) e '''venerdì''', data la scarsa dotazione dell'aula assegnataci, verranno svolti esercizi scritti in preparazione all'esame.&lt;br /&gt;
&lt;br /&gt;
Nel corso della lezione ci siamo chiesti, riprendendo un po' il filo logico delle lezioni dello scorso semestre, quali siano i servizi che ci aspettiamo da un sistema operativo.&amp;lt;br&amp;gt;&lt;br /&gt;
Questa volta, tuttavia, non li analizzeremo dal punto di vista dell'utilizzatore, ma da un punto di vista interno al sistema stesso.&amp;lt;br&amp;gt;&lt;br /&gt;
I servizi elencati sono stati:&lt;br /&gt;
* Scheduling (Gestione CPU)&lt;br /&gt;
* Gestione dei processi&lt;br /&gt;
* Gestione della memoria primaria&lt;br /&gt;
* Gestione dei device di I/O&lt;br /&gt;
* Gestione della memoria secondaria&lt;br /&gt;
* Filesystem&lt;br /&gt;
* Networking&lt;br /&gt;
* Protezione&lt;br /&gt;
* Shell&lt;br /&gt;
&lt;br /&gt;
Come in tutta l'informatica spesso succede, la complessità di questi servizi viene gestita tramite la creazione di livelli di astrazione. &amp;lt;br&amp;gt;&lt;br /&gt;
Il livello zero di astrazione, quello più basso, sarà l''''hardware''' che noi dovremo considerare come un linguaggio (l'ISA del processore).&amp;lt;br&amp;gt;&lt;br /&gt;
Seguito poi da due blocchi:&lt;br /&gt;
* '''Kernel'''&lt;br /&gt;
* '''Utente'''&lt;br /&gt;
&lt;br /&gt;
Cosa va incluso nel kernel di un S.O.? Cosa nella parte utente? Meglio massimizzare o minimizzare le operazioni nello livello kernel?&amp;lt;br&amp;gt; '''KERNEL MONOLITICO''' O '''MICRO-KERNEL'''?&lt;br /&gt;
&lt;br /&gt;
I kernel monolitici (tipo Linux) hanno il vantaggio di essere leggermente più performanti, mentre i microkernel (che presentano più livelli di astrazione) sono molto più flessibili e facili da manutenere.&lt;br /&gt;
Ad esempio, nei microkernel un bug non obbliga necessariamente a riavviare la macchina perché è possibile riavviare il singolo &amp;quot;modulo&amp;quot; che presenta l'errore.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 1 marzo 2018 ==&lt;br /&gt;
La lezione tace come concordato con gli studenti.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 2 marzo 2018 ==&lt;br /&gt;
La lezione tace causa chiusura dell'intero Ateneo per condizioni climatiche avverse.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 7 marzo 2018 ==&lt;br /&gt;
Tutto incomincia dal boot, la fase che carica il kernel, la quale termina subito dopo la creazione del primo processo (init) tramite la compilazione della tabella (come si vede sotto) che verrà passata allo scheduler. Una volta caricato, il kernel ha davanti una gradissima diversità di modelli hardware, ma come utilizzare tutti questi dispositivi hardware? Una prima soluzione sarebbe quella di includere nel kernel tutti i relativi device driver, tuttavia il kernel diventerbbe davvero corposo, a meno che non si crei un kernel ad hoc per una specifica macchina. Un'altra soluzione (quella comunemente adottata) utilizza i moduli. I moduli vengono caricati dal kernel all'occorrenza senza essere inseriti direttamente in esso (non è quindi necessaria la ricompilazione).&lt;br /&gt;
Tuttavia con quest'ultima soluzione si viene a creare un dilemma, ovvero:&amp;lt;br&amp;gt;&lt;br /&gt;
Chi carica i moduli? Il kernel.&amp;lt;br&amp;gt;&lt;br /&gt;
Chi carica il kernel? Il boot tramite moduli.&lt;br /&gt;
&lt;br /&gt;
In verità non esiste solo il kernel ma anche un filesystem che contiene i moduli chiamato &amp;quot;init.rd&amp;quot; (inutile se il kernel è configurato per una specifica macchina).&lt;br /&gt;
Arrivati a questo punto ci si chiede: come si compila un kernel? Prima di tutto occorrono i sorgenti (quelli di linux sono reperibili su https://www.kernel.org). Una volta scaricati, si deve configurare il file “.config” che sostanzialmente descrive le specifiche del kernel. Questo file lo si può configurare a mano o tramite il “menuconfig” che dovrà essere prima di tutto opportunatamente compilato. All'interno del menuconfig tra le altre cose c'è una lista di tutti i device driver supportati e possono essere spuntati con:  * (driver inserito nel kernel) o M (creazione del relativo modulo). Una volta configurato il “.config” non resta che compilare il kernel per la macchina ospite o per una specifica architettura. Ad esempio:&lt;br /&gt;
&lt;br /&gt;
wget link_of_kernel_source #scarica i sorgenti&amp;lt;br&amp;gt;&lt;br /&gt;
tar xf source_compressed_file # decomprime i relativi sorgenti, in questo caso dal file.tar.xz&amp;lt;br&amp;gt;&lt;br /&gt;
cd linux #entra nella directory dei sorgenti&amp;lt;br&amp;gt;&lt;br /&gt;
(Di regola esiste già un .config standard ma per configurarlo)&amp;lt;br&amp;gt;&lt;br /&gt;
make menuconfig&amp;lt;br&amp;gt;&lt;br /&gt;
make #compila il kernel per la macchina ospite&amp;lt;br&amp;gt;&lt;br /&gt;
(make -arch=armhf, compila secondo l'architettura dei processori armhf)&amp;lt;br&amp;gt;&lt;br /&gt;
( make -j n, compila il kernel per la macchina ospite utilizzando i suoi n-1 core)&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Una volta compilato l'immagine del kernel si troverà in arch/nome_architettura/boot/bzImage.&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ora si ricordi la differenza tra processo e thread: Il processo è l'entità titolare delle risorse, mentre il thread è la componente sequenziale dell'elaborazione.&lt;br /&gt;
Nei sistemi monothread il concetto di thread si sovrappone al concetto di processo ma in linea generale un processo può avere anche più di un thread. Ma quali sono le caratteristiche principali dei processi e dei thread?&lt;br /&gt;
&lt;br /&gt;
&amp;lt;table&amp;gt;&lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;Processo (PCB)&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Thread (TCB)&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;ID (self, parent,child etc)&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;ID&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;Info sulla memoria (puntatore alla tabella dei segmenti)&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Stato del processore&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;File aperti&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Stato: Ready, Running,Block&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;Ownership&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Puntatori&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;Interprocess comunication&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;     &lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;Informazioni sull'accounting &amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;/table&amp;gt;                          &lt;br /&gt;
&lt;br /&gt;
Modo Kernel e Modo User&amp;lt;br&amp;gt;&lt;br /&gt;
Si definisce Mode Switch il passaggio da Kernel Mode a User Mode e viceversa (esiste tale differenza per motivi di sicurrenzza). Attenzione però a non confondere la kernel mode con i permessi di root (root è l'utente con più permessi in assoluto ma è comunque gestito in user mode).&lt;br /&gt;
Si definisce Context Switch il passaggio di processi. Ad esempio un processo A in user mode si ferma e passa il controllo al trap che tramite lo scheduler chiama un altro processo.&lt;br /&gt;
Da qui si capische che è lo scheduler a decidere il prossimo processo da mandare avanti (tramite la Ready Queue). Un tipico scheduler è lo scheduler FIFO non preemptive (ossia il processo in esecuzione utilizzerà il processore finché non passerà allo stato di wait o termina l'esecuzione) per sistemi batch.&lt;br /&gt;
I thread posso essere creati sia in modo kernel che in modo user.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 8 marzo 2018 ==&lt;br /&gt;
Lo scheduler è quella parte del sistema operativo che si occupa di assegnare le risorse (specialmente il processore) ai vari processi, decidendone l'ordine e il tempo di esecuzione. Di scheduler ne esistono di 2 tipi; quelli non-preemptive (o cooperative) dove il context switch avviene solo se il processo in esecuzione fa I/O o una system call bloccante passando allo stato di wait o termina e quelli preemptive quando il processo in esecuzione può passare allo stato ready mediante un interrupt.&lt;br /&gt;
&lt;br /&gt;
I criteri con cui vengono scelti gli scheduler sono:&lt;br /&gt;
&lt;br /&gt;
* Throughput: la quantità di processi completati per unità di tempo&amp;lt;br&amp;gt;&lt;br /&gt;
* Turnaround: il tempo che intercorre da quando un processo entra nella coda ready fino alla fine della sua esecuzione&amp;lt;br&amp;gt;&lt;br /&gt;
* Percentuale di utilizzo del processore&amp;lt;br&amp;gt;&lt;br /&gt;
* Tempo di attesa: quanto tempo tempo un processo rimane nella coda ready prima di iniziare la sua esecuzione&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Partendo dal più semplice da realizzare, troviamo lo scheduler FCFS (First Come, First Served), il quale usa una politica FIFO quindi i processi vengono eseguiti in ordine di arrivo, è cooperative e non è per nulla efficiente.&lt;br /&gt;
&lt;br /&gt;
Uno scheduler più avanzato, ma sempre cooperative, è lo SJF (Shortest Job First) che da la priorità ai processi con meno tempo di CPU burst (tempo di utilizzo del processore). Il tempo di utilizzo della CPU viene stimato basandosi sulle precedenti esecuzioni e calcolato mediante la media esponenziale dando maggior peso alle esecuzioni più recenti.&lt;br /&gt;
Una variante preemptive del SJF è lo Shortest-Remeaning-Time First il quale fa passare dallo stato running a ready un processo se nel frattempo arriva nella coda ready un altro processo con un tempo di CPU burst più breve. Il problema principale però è che entrambi possono generare starvation.&lt;br /&gt;
&lt;br /&gt;
Lo scheduler Round-Robin, di tipo preemptive, fa uso della Time-Slice (ovvero un quanto di tempo). I processi vengono eseguiti in ordine di arrivo ma possono rilasciare il processore anticipatamente nel caso in cui finisca la time-slice a loro disposizione. Per implementare questo scheduler, tuttavia, è necessario un supporto hardware chiamato &amp;quot;interval time&amp;quot;, il quale non fa altro che generare un input ogni tot tempo scelto dal sistema operativo, questo input non ha nessuno scopo se non quello di generare un interrupt che possa interrompere l'esecuzione di un processo. La time-slice non piò essere troppo breve perché il context-switch ha un costo, non è istantaneo, ma nemmeno un troppo lunga altrimenti non si avrebbe l'idea di un sistema fluido ed istantaneo.&lt;br /&gt;
&lt;br /&gt;
Esistono anche gli scheduler che assegnano una priorità ai processi ed eseguono prima quelli con priorità più elevata. Questi scheduler esistono sia con priorità statica, che con priorità dinamica, ma potendo generare starvation, quelli con priorità dinamica possono utilizzare l'aging, ossia un processo con priorità bassa aumenta di volta in volta la sua priorità fino a diventare massima per poi, una volta eseguito, tornare alla priorità originaria.&lt;br /&gt;
Un po' più complessi sono gli scheduler con classi di priorità, in cui la coda ready è divisa in diverse sottocode, una per ogni classe di processi. Gli scheduler multilivello utilizzano sempre le classi di priorità ma assegnano ad ogni classe delle politiche diverse, adatte per i processi che contiene.&lt;br /&gt;
&lt;br /&gt;
Per applicazioni particolari, in cui la correttezza del programma dipende da tempo entro i quale viene data la risposta, esistono gli scheduler real-time.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 9 marzo 2018 ==&lt;br /&gt;
Esercizi su monitor e semafori degli esami di gennaio e febbraio 2018.&lt;br /&gt;
&lt;br /&gt;
Testo:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=c&amp;gt;&lt;br /&gt;
Monitor bridge&lt;br /&gt;
	boolean bridge_is_down&lt;br /&gt;
	int crossingCars&lt;br /&gt;
	int crossingBoat&lt;br /&gt;
	int waitingCars&lt;br /&gt;
	int waitingBoats&lt;br /&gt;
	condition ok2cars&lt;br /&gt;
	condition ok2boats&lt;br /&gt;
&lt;br /&gt;
procedure_entry car_enter(direction)&lt;br /&gt;
	if(crossingBoat || waitingBoats &amp;gt; 0){&lt;br /&gt;
		waitingCars++&lt;br /&gt;
		ok2cars.wait()&lt;br /&gt;
		waitingCars--&lt;br /&gt;
	}&lt;br /&gt;
	bridge_is_down = true&lt;br /&gt;
	crossingCars++&lt;br /&gt;
	ok2cars.signal()&lt;br /&gt;
&lt;br /&gt;
procedure_entry car_exit(direction)&lt;br /&gt;
	crossingCars--&lt;br /&gt;
	if(crossingCars == 0) ok2boat.signal()&lt;br /&gt;
&lt;br /&gt;
procedure_entry boat_enter(direction)&lt;br /&gt;
	if(crossingCars &amp;gt; 0 || crossingBoat &amp;gt; 0){&lt;br /&gt;
		waitingBoats++&lt;br /&gt;
		ok2boat.wait()&lt;br /&gt;
		waitingBoats--&lt;br /&gt;
	}&lt;br /&gt;
	bridge_is_down = false&lt;br /&gt;
	crossingBoat++&lt;br /&gt;
&lt;br /&gt;
procedure_entry boat_exit(direction)&lt;br /&gt;
	crossingBoat--&lt;br /&gt;
	ok2cars.signal()&lt;br /&gt;
	if(crossingCars == 0) ok2boat.signal()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Testo: implementare, utilizzando semafori ordinari, i semafori ternari&lt;br /&gt;
&amp;lt;syntaxhighlight lang=c&amp;gt;&lt;br /&gt;
class ternarySemaphore&lt;br /&gt;
&lt;br /&gt;
	semaphore pos&lt;br /&gt;
	semaphore neg&lt;br /&gt;
	&lt;br /&gt;
	void ternarySemaphore(int init)&lt;br /&gt;
	void P(void)&lt;br /&gt;
	void V(void)&lt;br /&gt;
&lt;br /&gt;
	void ternarySemaphore(void init)&lt;br /&gt;
		// init is -1 or 1&lt;br /&gt;
		pos = new semaphore(1 + init)&lt;br /&gt;
		neg = new semaphore(1 - init)&lt;br /&gt;
&lt;br /&gt;
	void P(void)&lt;br /&gt;
		pos.P()&lt;br /&gt;
		neg.V()&lt;br /&gt;
&lt;br /&gt;
	void V(void)&lt;br /&gt;
		neg.P()&lt;br /&gt;
		pos.V()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Soluzione alternativa, meno di stile ma ugualmente funzionante&lt;br /&gt;
&amp;lt;syntaxhighlight lang=c&amp;gt;&lt;br /&gt;
class ternarySemaphore&lt;br /&gt;
&lt;br /&gt;
	semaphore queue&lt;br /&gt;
	semaphore mutex&lt;br /&gt;
	int value&lt;br /&gt;
&lt;br /&gt;
	void ternarySemaphore(int init)&lt;br /&gt;
	void P(void)&lt;br /&gt;
	void V(void)&lt;br /&gt;
&lt;br /&gt;
	void ternarySemaphore(void init)&lt;br /&gt;
		mutex = new semaphore(1)&lt;br /&gt;
		value = init&lt;br /&gt;
		queue = new sem_queue&lt;br /&gt;
&lt;br /&gt;
	void P(void)&lt;br /&gt;
		mutex.P()&lt;br /&gt;
		if(value == 1 &amp;amp;&amp;amp; !queue.empty()){&lt;br /&gt;
			s = queue.dequeue()&lt;br /&gt;
			s.V()&lt;br /&gt;
		}else{&lt;br /&gt;
			if(value == -1){&lt;br /&gt;
				s = new semaphore(0)&lt;br /&gt;
				queue.enqueue(s)&lt;br /&gt;
				mutex.V()&lt;br /&gt;
				s.P()&lt;br /&gt;
			}else{&lt;br /&gt;
				value--&lt;br /&gt;
				mutex.V()&lt;br /&gt;
			}&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
	void V(void)&lt;br /&gt;
		mutex.P()&lt;br /&gt;
		if(value == -1 &amp;amp;&amp;amp; !queue.empty()){&lt;br /&gt;
			s = queue.dequeue()&lt;br /&gt;
			s.V()&lt;br /&gt;
		}else{&lt;br /&gt;
			if(value == -1){&lt;br /&gt;
				s = new semaphore(0)&lt;br /&gt;
				queue.enqueue(s)&lt;br /&gt;
				mutex.V()&lt;br /&gt;
				s.P()&lt;br /&gt;
				free(s)&lt;br /&gt;
			}else{&lt;br /&gt;
				value++&lt;br /&gt;
				mutex.V()&lt;br /&gt;
			}&lt;br /&gt;
		}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Lezione del 14 marzo 2018 ==&lt;br /&gt;
'''RISORSE'''&lt;br /&gt;
&lt;br /&gt;
Cosa intendiamo per risorsa? Quali sono i problemi comuni e le tecniche per risolverli?&amp;lt;br&amp;gt;&lt;br /&gt;
Nel frattempo definiamo come '''CLASSI DI RISORSE''' l'insieme formato da risorse tra loro equivalenti (byte della memoria, stampanti dello stesso tipo, etc.).&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Una prima distinzione può essere fatta tra risorse ad assegnazione '''''statica''''' e ad assegnazione '''''dinamica''''': le prime, una volta assegnate, non possono essere più prelevate finché il processo che le possiede non termina. Nel secondo caso, invece, l'assegnazione può mutare nel tempo.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Si può distinguere il tipo di richiesta che viene fatta alla risorsa: '''''singola''''' o '''''multipla''''', '''''bloccante''''' o '''''non bloccante'''''. Intuitivamente, la prima distinzione si riferisce alla molteplicità di richieste che la risorsa può eseguire nello stesso momento mentre, la seconda distinzione, si riferisce al comportamento della richiesta.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Altre distinzioni possono essere fatte tra risorse '''''non condivisibili''''' (dette anche seriali) e risorse '''''condivisibili''''' (o non seriali), tra risorsa '''''non pre-emptible''''' e '''''pre-emptible'''''. Una risorsa può essere pre-emptible se e solo se non ha uno stato interno o se lo stato può essere salvato per poi essere ripristinato.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Può accadere che un processo A effettui una ''richiesta bloccante'' ad una ''risorsa non-preemptible'' e ''non condivisibile'' che al momento è utilizzata da un qualche processo B. B, a sua volta, fa la stessa cosa ma, con una risorsa in possesso di A. Cosa accade?&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
'''DEADLOCK'''&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Le condizioni necessarie affinché un deadlock si verifichi sono:&lt;br /&gt;
*'''RICHIESTA BLOCCANTE'''&lt;br /&gt;
*'''RISORSA NON CONDIVISIBILE'''&lt;br /&gt;
*'''RISORSA NON-PREEMPITIBLE'''&lt;br /&gt;
*'''ATTESA CIRCOLARE'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
work in progress&lt;br /&gt;
&lt;br /&gt;
== Lezione del 15 marzo 2018 ==&lt;br /&gt;
Nessuna lezione.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 16 marzo 2018 ==&lt;br /&gt;
== Lezione del 21 marzo 2018 ==&lt;br /&gt;
== Lezione del 22 marzo 2018 ==&lt;br /&gt;
&lt;br /&gt;
Alcuni errori comuni della fase 1 del progetto:&lt;br /&gt;
* file sparsi nell'archivio, invece di una sola cartella che li contiene tutti&lt;br /&gt;
* uso del Makefile senza regole automatiche&lt;br /&gt;
* uso di sentinelle globali nella ricorsione&lt;br /&gt;
* uso di licenze non compatibili tra di loro&lt;br /&gt;
* indentazione sbagliata&lt;br /&gt;
&lt;br /&gt;
=== Memoria Principale ===&lt;br /&gt;
&lt;br /&gt;
La MMU è una componente hardware che implementa una funzione che associa gli indirizzi usati dal&lt;br /&gt;
sorgente (logici) a quelli reali in memoria (fisici).&lt;br /&gt;
&lt;br /&gt;
Un modo per calcolare gli indirizzi fisici corrispondenti agli indirizzi logici di un processo è&lt;br /&gt;
dare un registro base ad ogni processo; quando un processo accede all'indirizzo della variabile A,&lt;br /&gt;
ad esempio, la MMU calcola il suo indirizzo fisico aggiungendo al suo indirizzo logico il&lt;br /&gt;
valore del registro base. &lt;br /&gt;
&lt;br /&gt;
Qual è il problema con questo approccio? Non si hanno indirizzi più piccoli dell'indirizzo&lt;br /&gt;
base, ma si può sforare nello spazio di indirizzi di un altro processo, dato che non c'è un limite&lt;br /&gt;
superiore. Questo problema si risolve facilmente aggiungendo al registro base un registro limite.&lt;br /&gt;
Un processo può accedere a tutti gli indirizzi compresi tra i due indirizzi, e, se va fuori, si&lt;br /&gt;
genera una trap di memoria. Questo fornisce anche un meccanismo di protezione della memoria.&lt;br /&gt;
&lt;br /&gt;
Se adottiamo questo approccio quando allochiamo spazio in memoria per un processo dobbiamo farlo in&lt;br /&gt;
maniera contigua.&lt;br /&gt;
Come? Ci sono vari metodi:&lt;br /&gt;
&lt;br /&gt;
* '''Partizionamento statico''': in una sezione iniziale della memoria sta il kernel, il resto della memoria è divisa in parti; man mano che arrivano i processi vengono assegnati alle varie parti di memoria.  Che problemi ha questo approccio? È statico, ovvero la disposizione iniziale è quella definitiva. Inoltre dobbiamo scegliere il segmento dove inserire il nuovo processo allocato.  Spesso la memoria disponibile è maggiore di quella necessaria; di conseguenza ci sarà uno spreco.  Quando ci sono aree di memoria inutilizzate all'interno di porzioni di memoria di dimensioni stabilite a priori si parla di '''frammentazione interna'''. È' un problema che vogliamo risolvere;&lt;br /&gt;
&lt;br /&gt;
* '''Partizionamento dinamico''': potremmo, ad esempio, stackare i blocchi di memoria dei processi, così da non sprecare memoria. Problema: come facciamo quando un processo termina? In questo caso avremmo degli spazi di memoria inutilizzati sparsi per la memoria, che potrebbero dare spazio ad un altro processo se uniti assieme, ma che da soli non riescono perché sono troppo piccoli. Si parla in questo caso di '''frammentazione esterna'''. Si può risolvere il problema compattando le zone di memoria. Per fare ciò è necessario:&lt;br /&gt;
** copiare il PCB dal posto in cui si trova alla cima dello stack;&lt;br /&gt;
** cambiare base register e limit register;&lt;br /&gt;
** aspettare che il processo sia &amp;quot;fermo&amp;quot;; se fosse in esecuzione spostarlo ovviamente creerebbe delle inconsistenze.&lt;br /&gt;
&lt;br /&gt;
Se il processo è interattivo e si prova a fare compattazione, si parla di Giga da muovere: altamente&lt;br /&gt;
inefficiente. L'approccio migliore è quello di limitare la frammentazione esterna.  Come?&lt;br /&gt;
Scegliendo, tra le varie aree libere, quella migliore per contenere un processo. Per fare queste&lt;br /&gt;
operazioni è necessario qualcuno che tenga traccia della contabilità della memoria del sistema. Ci&lt;br /&gt;
pensa un programma chiamato Memory Manager. Come fa? Con una lista delle aree di memoria libere.&lt;br /&gt;
Questo permette di vedere se esistono zone di memoria libere contigue e di unirle assieme&lt;br /&gt;
eventualmente. Si possono usare anche le tabelle di bit: se consideriamo la memoria divisa in unita'&lt;br /&gt;
di allocazione possiamo associare ad ogni unità un bit, acceso o spento se libera o meno. &lt;br /&gt;
&lt;br /&gt;
Per scegliere le aree di memoria ci sono vari approcci:&lt;br /&gt;
* Best Fit: scelgo l'area più piccola che contiene il mio processo;&lt;br /&gt;
* First Fit: scelgo la prima area capace di contenere il mio processo che trovo dall'inizio della memoria;&lt;br /&gt;
* Next Fit: come First Fit ma parto dall'ultimo processo a cui sono arrivato con la precedente scansione;&lt;br /&gt;
* Worst Fit: scelgo l'area più grande che contiene il mio processo.&lt;br /&gt;
Quale tecnica è la migliore? Statisticamente First Fit.&lt;br /&gt;
&lt;br /&gt;
Questi meccanismi funzionavano per sistemi batch; per sistemi molto dinamici queste cose ci fanno&lt;br /&gt;
sprecare memoria o è richiesta la compattazione, che richiede tempo. La MMU con base register e&lt;br /&gt;
limit register non permettono altro. Si può fare di meglio? Sì.&lt;br /&gt;
&lt;br /&gt;
Come? Con la paginazione della memoria. La memoria viene divisa in blocchi di dimensione fissata&lt;br /&gt;
dette '''pagine'''. Ovviamente l'ampiezza della pagina è una potenza di 2. L'indirizzo logico visto dal&lt;br /&gt;
programma viene diviso in due parti: il numero di pagina e l'offset all'interno della pagina. La MMU&lt;br /&gt;
usa il numero della pagina come indice all'interno di una tabella. A quell'indice è associato,&lt;br /&gt;
nella tabella, l'indirizzo fisico che contiene la pagina. La parte di memoria fisica che contiene la&lt;br /&gt;
pagina è detta '''frame'''. L'indirizzo fisico corrispondente sarà dato, una volta ottenuto&lt;br /&gt;
l'indirizzo del frame che lo contiene, dai bit che precedono l'offset e, subito dopo, dall'offset&lt;br /&gt;
stesso, che viene copiato identico alla fine dell'indirizzo fisico. &lt;br /&gt;
&lt;br /&gt;
Questo meccanismo è molto più flessibile del partizionamento della memoria. C'è frammentazione&lt;br /&gt;
interna? Sì, ma poca: nel peggiore dei casi abbiamo un processo che ha bisogno di n pagine + 1 bit.&lt;br /&gt;
In questo caso verranno allocate n+1 pagine. Quindi la frammentazione interna che abbiamo sarà al&lt;br /&gt;
massimo dell'ordine della dimensione di una pagina per ogni processo.  C'è frammentazione esterna?&lt;br /&gt;
Potrebbe, ma sarebbe irrisoria: potrebbe esserci all'inizio della memoria dove risiede il kernel. &lt;br /&gt;
&lt;br /&gt;
Il problema della frammentazione è risolto. Ma dove sta il costo nascosto? Nella MMU. Come si&lt;br /&gt;
gestisce la sua tabella? La cosa migliore sarebbe una matrice associativa, sarebbe molto veloce, e&lt;br /&gt;
questa caratteristica è necessaria visto che la tabella va gestita ad ogni accesso in memoria.&lt;br /&gt;
Problema: il costo elevato di una componente hardware capace di mantenere una tabella delle pagine.&lt;br /&gt;
Mettiamo la tabella in memoria? Non è ideale, perché dopo servirebbero due accessi in memoria per&lt;br /&gt;
ogni accesso in memoria. Le prestazioni della memoria risultano quindi dimezzate. &lt;br /&gt;
&lt;br /&gt;
La soluzione? Una cache: il '''Translation Lookahead Buffer'''.  È una matrice associativa, ma fa da&lt;br /&gt;
cache alla tabella della MMU, che è salvata in memoria. È quindi molto più piccola della tabella.&lt;br /&gt;
Quando viene fatta una operazione in memoria si può avere un TLB hit: la velocità è alta.&lt;br /&gt;
Potremmo avere un TLB miss. In questo caso l'indirizzo richiesto non è in cache.  Questo genera un&lt;br /&gt;
interrupt, quindi il controllo passa al kernel, che va a vedere nella tabella in memoria se&lt;br /&gt;
l'indirizzo richiesto è legale, nel qual caso lo carica. Questo meccanismo è ragionevolmente&lt;br /&gt;
veloce e ha prezzi accessibili. Va tuttavia fatto notare che una macchina che usa allocazione&lt;br /&gt;
contigua senza paginazione è più veloce di una macchina con paginazione. Ovviamente quello che&lt;br /&gt;
abbiamo con questo meccanismo è un tradeoff. Paghiamo due prezzi: uno in termini di performance, un&lt;br /&gt;
altro in termini di linearità di esecuzioni, in cambio di un miglior utilizzo della memoria. &lt;br /&gt;
&lt;br /&gt;
Gli effetti positivi della paginazione sono tanti, ma rimangono alcuni problemi da risolvere. Se noi&lt;br /&gt;
abbiamo una libreria dinamica con la paginazione tradizionale avremmo tante copie della stessa&lt;br /&gt;
libreria in memoria, e quindi un enorme spreco. Per fare questo si usa il codice reentrant. La&lt;br /&gt;
memoria allocata per un processo è così divisa:&lt;br /&gt;
* area text: testo dell'eseguibile; solitamente read-only, dato che può anche essere sharable;&lt;br /&gt;
* area data: variabili globali; condivisibile per scelta;&lt;br /&gt;
* area stack: area dove cresce lo stack; MAI condivisibile.&lt;br /&gt;
&lt;br /&gt;
Come risolviamo questo problema? Col loading dinamico, ovvero loading di codice dalla memoria a&lt;br /&gt;
run-time. È una funzionalità fornita agli sviluppatori per scrivere codice più efficiente.&lt;br /&gt;
&lt;br /&gt;
Un'altra funzionalità è il linking dinamico. Le librerie dinamiche servono a questo: quando il&lt;br /&gt;
codice usa una libreria non la linka nell'eseguibile, ma si segna che è presente e si segna la&lt;br /&gt;
versione che usa; a run time, raggiunta quella parte di codice, si va a recuperare la libreria&lt;br /&gt;
dinamica già caricata in memoria (o casomai si carica); questo viene fatto dal linker.&lt;br /&gt;
&lt;br /&gt;
È possibile attaccarsi alla libreria già caricata in memoria grazie a memory mapping. Questo pero'&lt;br /&gt;
non è possibile con la paginazione tradizionale. Occorre introdurre un nuovo concetto: la&lt;br /&gt;
'''segmentazione'''. L'idea è di dividere l'area richiesta dai processi in segmenti adibiti a scopi&lt;br /&gt;
specifici. Si possono avere segmenti testo, data, stack, shared data, etc., ognuno con le sue&lt;br /&gt;
proprietà di leggibilità o di scrittura. Una MMU che supporta segmentazione è così&lt;br /&gt;
fatta: la parte iniziale (piccola) dell'indirizzo identifica il segmento, la parte finale identifica&lt;br /&gt;
l'offset all'interno del segmento. Per ogni segmento vi è una entry in una tabella, in cui vengono&lt;br /&gt;
mantenute le varie proprietà del segmento. Tra le varie cose viene mantenuto l'indirizzo base del&lt;br /&gt;
segmento (da sommare all'offset). Di segmenti ce ne sono pochi, di pagine tante.&lt;br /&gt;
&lt;br /&gt;
Facciamo un confronto tra paginazione e segmentazione:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Pagine&lt;br /&gt;
! Segmenti&lt;br /&gt;
|-&lt;br /&gt;
|Limita frammentazione&lt;br /&gt;
|Permette la condivisione e la protezione&lt;br /&gt;
|-&lt;br /&gt;
|Hanno tutte la stessa ampiezza (e.g. 4k)&lt;br /&gt;
|Hanno ampiezza (molto) variabile O(k),....,O(G)&lt;br /&gt;
|-&lt;br /&gt;
|Hanno un indirizzo&lt;br /&gt;
|Hanno un nome&lt;br /&gt;
|-&lt;br /&gt;
|Possono contenere dati disomogenei&lt;br /&gt;
|Contengono informazioni omogenee&lt;br /&gt;
|-&lt;br /&gt;
|Divisione in pagine automatica&lt;br /&gt;
|Divisione in segmenti; alcune divisioni sono strutturali, &lt;br /&gt;
altre sono decise dal programmatore&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Nella pratica che tecnica si usa? Entrambe: paginiamo i segmenti. Gli indirizzi logici contengono&lt;br /&gt;
l'identificatore del segmento, della pagina, l'offset ed il contesto. In caso di miss si usa il numero&lt;br /&gt;
di segmento per accedere ad una tabella con i bit di controllo. Se l'indirizzo è legale la entry&lt;br /&gt;
punta ad una page table. Si usa poi il numero di pagina per identificare la pagina e l'offset per&lt;br /&gt;
ottenere la parte di memoria desiderata.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 23 marzo 2018 ==&lt;br /&gt;
== Lezione del 28 marzo 2018 ==&lt;br /&gt;
== Lezione del 29 marzo 2018 ==&lt;br /&gt;
Pausa di primavera.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 30 marzo 2018 ==&lt;br /&gt;
Pausa di primavera.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 4 aprile 2018 ==&lt;/div&gt;</summary>
		<author><name>Matt</name></author>
	</entry>
	<entry>
		<id>https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_II_semestre&amp;diff=2185</id>
		<title>Lezioni Anno Accademico 2017/18 II semestre</title>
		<link rel="alternate" type="text/html" href="https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_II_semestre&amp;diff=2185"/>
		<updated>2018-03-28T20:11:28Z</updated>

		<summary type="html">&lt;p&gt;Matt: /* Lezione del 30 marzo 2018 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Lezione del 28 febbraio 2018 ==&lt;br /&gt;
Inizia oggi la seconda parte del corso di Sistemi Operativi che si terrà dal 28/02/18 al 25/05/18.&amp;lt;br&amp;gt;&lt;br /&gt;
Le lezioni si svolgeranno, il mercoledì e il giovedì, in aula E1. Il venerdì invece ci sposteremo in M1.&amp;lt;br&amp;gt;&lt;br /&gt;
La suddivisione degli argomenti trattati sarà: '''mercoledì''' teoria, '''giovedì''' tendenzialmente parte progettuale (potrebbe iniziare con un po' di teoria) e '''venerdì''', data la scarsa dotazione dell'aula assegnataci, verranno svolti esercizi scritti in preparazione all'esame.&lt;br /&gt;
&lt;br /&gt;
Nel corso della lezione ci siamo chiesti, riprendendo un po' il filo logico delle lezioni dello scorso semestre, quali siano i servizi che ci aspettiamo da un sistema operativo.&amp;lt;br&amp;gt;&lt;br /&gt;
Questa volta, tuttavia, non li analizzeremo dal punto di vista dell'utilizzatore, ma da un punto di vista interno al sistema stesso.&amp;lt;br&amp;gt;&lt;br /&gt;
I servizi elencati sono stati:&lt;br /&gt;
* Scheduling (Gestione CPU)&lt;br /&gt;
* Gestione dei processi&lt;br /&gt;
* Gestione della memoria primaria&lt;br /&gt;
* Gestione dei device di I/O&lt;br /&gt;
* Gestione della memoria secondaria&lt;br /&gt;
* Filesystem&lt;br /&gt;
* Networking&lt;br /&gt;
* Protezione&lt;br /&gt;
* Shell&lt;br /&gt;
&lt;br /&gt;
Come in tutta l'informatica spesso succede, la complessità di questi servizi viene gestita tramite la creazione di livelli di astrazione. &amp;lt;br&amp;gt;&lt;br /&gt;
Il livello zero di astrazione, quello più basso, sarà l''''hardware''' che noi dovremo considerare come un linguaggio (l'ISA del processore).&amp;lt;br&amp;gt;&lt;br /&gt;
Seguito poi da due blocchi:&lt;br /&gt;
* '''Kernel'''&lt;br /&gt;
* '''Utente'''&lt;br /&gt;
&lt;br /&gt;
Cosa va incluso nel kernel di un S.O.? Cosa nella parte utente? Meglio massimizzare o minimizzare le operazioni nello livello kernel?&amp;lt;br&amp;gt; '''KERNEL MONOLITICO''' O '''MICRO-KERNEL'''?&lt;br /&gt;
&lt;br /&gt;
I kernel monolitici (tipo Linux) hanno il vantaggio di essere leggermente più performanti, mentre i microkernel (che presentano più livelli di astrazione) sono molto più flessibili e facili da manutenere.&lt;br /&gt;
Ad esempio, nei microkernel un bug non obbliga necessariamente a riavviare la macchina perché è possibile riavviare il singolo &amp;quot;modulo&amp;quot; che presenta l'errore.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 1 marzo 2018 ==&lt;br /&gt;
La lezione tace come concordato con gli studenti.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 2 marzo 2018 ==&lt;br /&gt;
La lezione tace causa chiusura dell'intero Ateneo per condizioni climatiche avverse.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 7 marzo 2018 ==&lt;br /&gt;
Tutto incomincia dal boot, la fase che carica il kernel, la quale termina subito dopo la creazione del primo processo (init) tramite la compilazione della tabella (come si vede sotto) che verrà passata allo scheduler. Una volta caricato, il kernel ha davanti una gradissima diversità di modelli hardware, ma come utilizzare tutti questi dispositivi hardware? Una prima soluzione sarebbe quella di includere nel kernel tutti i relativi device driver, tuttavia il kernel diventerbbe davvero corposo, a meno che non si crei un kernel ad hoc per una specifica macchina. Un'altra soluzione (quella comunemente adottata) utilizza i moduli. I moduli vengono caricati dal kernel all'occorrenza senza essere inseriti direttamente in esso (non è quindi necessaria la ricompilazione).&lt;br /&gt;
Tuttavia con quest'ultima soluzione si viene a creare un dilemma, ovvero:&amp;lt;br&amp;gt;&lt;br /&gt;
Chi carica i moduli? Il kernel.&amp;lt;br&amp;gt;&lt;br /&gt;
Chi carica il kernel? Il boot tramite moduli.&lt;br /&gt;
&lt;br /&gt;
In verità non esiste solo il kernel ma anche un filesystem che contiene i moduli chiamato &amp;quot;init.rd&amp;quot; (inutile se il kernel è configurato per una specifica macchina).&lt;br /&gt;
Arrivati a questo punto ci si chiede: come si compila un kernel? Prima di tutto occorrono i sorgenti (quelli di linux sono reperibili su https://www.kernel.org). Una volta scaricati, si deve configurare il file “.config” che sostanzialmente descrive le specifiche del kernel. Questo file lo si può configurare a mano o tramite il “menuconfig” che dovrà essere prima di tutto opportunatamente compilato. All'interno del menuconfig tra le altre cose c'è una lista di tutti i device driver supportati e possono essere spuntati con:  * (driver inserito nel kernel) o M (creazione del relativo modulo). Una volta configurato il “.config” non resta che compilare il kernel per la macchina ospite o per una specifica architettura. Ad esempio:&lt;br /&gt;
&lt;br /&gt;
wget link_of_kernel_source #scarica i sorgenti&amp;lt;br&amp;gt;&lt;br /&gt;
tar xf source_compressed_file # decomprime i relativi sorgenti, in questo caso dal file.tar.xz&amp;lt;br&amp;gt;&lt;br /&gt;
cd linux #entra nella directory dei sorgenti&amp;lt;br&amp;gt;&lt;br /&gt;
(Di regola esiste già un .config standard ma per configurarlo)&amp;lt;br&amp;gt;&lt;br /&gt;
make menuconfig&amp;lt;br&amp;gt;&lt;br /&gt;
make #compila il kernel per la macchina ospite&amp;lt;br&amp;gt;&lt;br /&gt;
(make -arch=armhf, compila secondo l'architettura dei processori armhf)&amp;lt;br&amp;gt;&lt;br /&gt;
( make -j n, compila il kernel per la macchina ospite utilizzando i suoi n-1 core)&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Una volta compilato l'immagine del kernel si troverà in arch/nome_architettura/boot/bzImage.&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ora si ricordi la differenza tra processo e thread: Il processo è l'entità titolare delle risorse, mentre il thread è la componente sequenziale dell'elaborazione.&lt;br /&gt;
Nei sistemi monothread il concetto di thread si sovrappone al concetto di processo ma in linea generale un processo può avere anche più di un thread. Ma quali sono le caratteristiche principali dei processi e dei thread?&lt;br /&gt;
&lt;br /&gt;
&amp;lt;table&amp;gt;&lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;Processo (PCB)&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Thread (TCB)&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;ID (self, parent,child etc)&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;ID&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;Info sulla memoria (puntatore alla tabella dei segmenti)&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Stato del processore&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;File aperti&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Stato: Ready, Running,Block&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;Ownership&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Puntatori&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;Interprocess comunication&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;     &lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;Informazioni sull'accounting &amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;/table&amp;gt;                          &lt;br /&gt;
&lt;br /&gt;
Modo Kernel e Modo User&amp;lt;br&amp;gt;&lt;br /&gt;
Si definisce Mode Switch il passaggio da Kernel Mode a User Mode e viceversa (esiste tale differenza per motivi di sicurrenzza). Attenzione però a non confondere la kernel mode con i permessi di root (root è l'utente con più permessi in assoluto ma è comunque gestito in user mode).&lt;br /&gt;
Si definisce Context Switch il passaggio di processi. Ad esempio un processo A in user mode si ferma e passa il controllo al trap che tramite lo scheduler chiama un altro processo.&lt;br /&gt;
Da qui si capische che è lo scheduler a decidere il prossimo processo da mandare avanti (tramite la Ready Queue). Un tipico scheduler è lo scheduler FIFO non preemptive (ossia il processo in esecuzione utilizzerà il processore finché non passerà allo stato di wait o termina l'esecuzione) per sistemi batch.&lt;br /&gt;
I thread posso essere creati sia in modo kernel che in modo user.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 8 marzo 2018 ==&lt;br /&gt;
Lo scheduler è quella parte del sistema operativo che si occupa di assegnare le risorse (specialmente il processore) ai vari processi, decidendone l'ordine e il tempo di esecuzione. Di scheduler ne esistono di 2 tipi; quelli non-preemptive (o cooperative) dove il context switch avviene solo se il processo in esecuzione fa I/O o una system call bloccante passando allo stato di wait o termina e quelli preemptive quando il processo in esecuzione può passare allo stato ready mediante un interrupt.&lt;br /&gt;
&lt;br /&gt;
I criteri con cui vengono scelti gli scheduler sono:&lt;br /&gt;
&lt;br /&gt;
* Throughput: la quantità di processi completati per unità di tempo&amp;lt;br&amp;gt;&lt;br /&gt;
* Turnaround: il tempo che intercorre da quando un processo entra nella coda ready fino alla fine della sua esecuzione&amp;lt;br&amp;gt;&lt;br /&gt;
* Percentuale di utilizzo del processore&amp;lt;br&amp;gt;&lt;br /&gt;
* Tempo di attesa: quanto tempo tempo un processo rimane nella coda ready prima di iniziare la sua esecuzione&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Partendo dal più semplice da realizzare, troviamo lo scheduler FCFS (First Come, First Served), il quale usa una politica FIFO quindi i processi vengono eseguiti in ordine di arrivo, è cooperative e non è per nulla efficiente.&lt;br /&gt;
&lt;br /&gt;
Uno scheduler più avanzato, ma sempre cooperative, è lo SJF (Shortest Job First) che da la priorità ai processi con meno tempo di CPU burst (tempo di utilizzo del processore). Il tempo di utilizzo della CPU viene stimato basandosi sulle precedenti esecuzioni e calcolato mediante la media esponenziale dando maggior peso alle esecuzioni più recenti.&lt;br /&gt;
Una variante preemptive del SJF è lo Shortest-Remeaning-Time First il quale fa passare dallo stato running a ready un processo se nel frattempo arriva nella coda ready un altro processo con un tempo di CPU burst più breve. Il problema principale però è che entrambi possono generare starvation.&lt;br /&gt;
&lt;br /&gt;
Lo scheduler Round-Robin, di tipo preemptive, fa uso della Time-Slice (ovvero un quanto di tempo). I processi vengono eseguiti in ordine di arrivo ma possono rilasciare il processore anticipatamente nel caso in cui finisca la time-slice a loro disposizione. Per implementare questo scheduler, tuttavia, è necessario un supporto hardware chiamato &amp;quot;interval time&amp;quot;, il quale non fa altro che generare un input ogni tot tempo scelto dal sistema operativo, questo input non ha nessuno scopo se non quello di generare un interrupt che possa interrompere l'esecuzione di un processo. La time-slice non piò essere troppo breve perché il context-switch ha un costo, non è istantaneo, ma nemmeno un troppo lunga altrimenti non si avrebbe l'idea di un sistema fluido ed istantaneo.&lt;br /&gt;
&lt;br /&gt;
Esistono anche gli scheduler che assegnano una priorità ai processi ed eseguono prima quelli con priorità più elevata. Questi scheduler esistono sia con priorità statica, che con priorità dinamica, ma potendo generare starvation, quelli con priorità dinamica possono utilizzare l'aging, ossia un processo con priorità bassa aumenta di volta in volta la sua priorità fino a diventare massima per poi, una volta eseguito, tornare alla priorità originaria.&lt;br /&gt;
Un po' più complessi sono gli scheduler con classi di priorità, in cui la coda ready è divisa in diverse sottocode, una per ogni classe di processi. Gli scheduler multilivello utilizzano sempre le classi di priorità ma assegnano ad ogni classe delle politiche diverse, adatte per i processi che contiene.&lt;br /&gt;
&lt;br /&gt;
Per applicazioni particolari, in cui la correttezza del programma dipende da tempo entro i quale viene data la risposta, esistono gli scheduler real-time.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 9 marzo 2018 ==&lt;br /&gt;
Esercizi su monitor e semafori degli esami di gennaio e febbraio 2018.&lt;br /&gt;
&lt;br /&gt;
Testo:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=c&amp;gt;&lt;br /&gt;
Monitor bridge&lt;br /&gt;
	boolean bridge_is_down&lt;br /&gt;
	int crossingCars&lt;br /&gt;
	int crossingBoat&lt;br /&gt;
	int waitingCars&lt;br /&gt;
	int waitingBoats&lt;br /&gt;
	condition ok2cars&lt;br /&gt;
	condition ok2boats&lt;br /&gt;
&lt;br /&gt;
procedure_entry car_enter(direction)&lt;br /&gt;
	if(crossingBoat || waitingBoats &amp;gt; 0){&lt;br /&gt;
		waitingCars++&lt;br /&gt;
		ok2cars.wait()&lt;br /&gt;
		waitingCars--&lt;br /&gt;
	}&lt;br /&gt;
	bridge_is_down = true&lt;br /&gt;
	crossingCars++&lt;br /&gt;
	ok2cars.signal()&lt;br /&gt;
&lt;br /&gt;
procedure_entry car_exit(direction)&lt;br /&gt;
	crossingCars--&lt;br /&gt;
	if(crossingCars == 0) ok2boat.signal()&lt;br /&gt;
&lt;br /&gt;
procedure_entry boat_enter(direction)&lt;br /&gt;
	if(crossingCars &amp;gt; 0 || crossingBoat &amp;gt; 0){&lt;br /&gt;
		waitingBoats++&lt;br /&gt;
		ok2boat.wait()&lt;br /&gt;
		waitingBoats--&lt;br /&gt;
	}&lt;br /&gt;
	bridge_is_down = false&lt;br /&gt;
	crossingBoat++&lt;br /&gt;
&lt;br /&gt;
procedure_entry boat_exit(direction)&lt;br /&gt;
	crossingBoat--&lt;br /&gt;
	ok2cars.signal()&lt;br /&gt;
	if(crossingCars == 0) ok2boat.signal()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Testo: implementare, utilizzando semafori ordinari, i semafori ternari&lt;br /&gt;
&amp;lt;syntaxhighlight lang=c&amp;gt;&lt;br /&gt;
class ternarySemaphore&lt;br /&gt;
&lt;br /&gt;
	semaphore pos&lt;br /&gt;
	semaphore neg&lt;br /&gt;
	&lt;br /&gt;
	void ternarySemaphore(int init)&lt;br /&gt;
	void P(void)&lt;br /&gt;
	void V(void)&lt;br /&gt;
&lt;br /&gt;
	void ternarySemaphore(void init)&lt;br /&gt;
		// init is -1 or 1&lt;br /&gt;
		pos = new semaphore(1 + init)&lt;br /&gt;
		neg = new semaphore(1 - init)&lt;br /&gt;
&lt;br /&gt;
	void P(void)&lt;br /&gt;
		pos.P()&lt;br /&gt;
		neg.V()&lt;br /&gt;
&lt;br /&gt;
	void V(void)&lt;br /&gt;
		neg.P()&lt;br /&gt;
		pos.V()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Soluzione alternativa, meno di stile ma ugualmente funzionante&lt;br /&gt;
&amp;lt;syntaxhighlight lang=c&amp;gt;&lt;br /&gt;
class ternarySemaphore&lt;br /&gt;
&lt;br /&gt;
	semaphore queue&lt;br /&gt;
	semaphore mutex&lt;br /&gt;
	int value&lt;br /&gt;
&lt;br /&gt;
	void ternarySemaphore(int init)&lt;br /&gt;
	void P(void)&lt;br /&gt;
	void V(void)&lt;br /&gt;
&lt;br /&gt;
	void ternarySemaphore(void init)&lt;br /&gt;
		mutex = new semaphore(1)&lt;br /&gt;
		value = init&lt;br /&gt;
		queue = new sem_queue&lt;br /&gt;
&lt;br /&gt;
	void P(void)&lt;br /&gt;
		mutex.P()&lt;br /&gt;
		if(value == 1 &amp;amp;&amp;amp; !queue.empty()){&lt;br /&gt;
			s = queue.dequeue()&lt;br /&gt;
			s.V()&lt;br /&gt;
		}else{&lt;br /&gt;
			if(value == -1){&lt;br /&gt;
				s = new semaphore(0)&lt;br /&gt;
				queue.enqueue(s)&lt;br /&gt;
				mutex.V()&lt;br /&gt;
				s.P()&lt;br /&gt;
			}else{&lt;br /&gt;
				value--&lt;br /&gt;
				mutex.V()&lt;br /&gt;
			}&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
	void V(void)&lt;br /&gt;
		mutex.P()&lt;br /&gt;
		if(value == -1 &amp;amp;&amp;amp; !queue.empty()){&lt;br /&gt;
			s = queue.dequeue()&lt;br /&gt;
			s.V()&lt;br /&gt;
		}else{&lt;br /&gt;
			if(value == -1){&lt;br /&gt;
				s = new semaphore(0)&lt;br /&gt;
				queue.enqueue(s)&lt;br /&gt;
				mutex.V()&lt;br /&gt;
				s.P()&lt;br /&gt;
				free(s)&lt;br /&gt;
			}else{&lt;br /&gt;
				value++&lt;br /&gt;
				mutex.V()&lt;br /&gt;
			}&lt;br /&gt;
		}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Lezione del 14 marzo 2018 ==&lt;br /&gt;
'''RISORSE'''&lt;br /&gt;
&lt;br /&gt;
Cosa intendiamo per risorsa? Quali sono i problemi comuni e le tecniche per risolverli?&amp;lt;br&amp;gt;&lt;br /&gt;
Nel frattempo definiamo come '''CLASSI DI RISORSE''' l'insieme formato da risorse tra loro equivalenti (byte della memoria, stampanti dello stesso tipo, etc.).&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Una prima distinzione può essere fatta tra risorse ad assegnazione '''''statica''''' e ad assegnazione '''''dinamica''''': le prime, una volta assegnate, non possono essere più prelevate finché il processo che le possiede non termina. Nel secondo caso, invece, l'assegnazione può mutare nel tempo.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Si può distinguere il tipo di richiesta che viene fatta alla risorsa: '''''singola''''' o '''''multipla''''', '''''bloccante''''' o '''''non bloccante'''''. Intuitivamente, la prima distinzione si riferisce alla molteplicità di richieste che la risorsa può eseguire nello stesso momento mentre, la seconda distinzione, si riferisce al comportamento della richiesta.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Altre distinzioni possono essere fatte tra risorse '''''non condivisibili''''' (dette anche seriali) e risorse '''''condivisibili''''' (o non seriali), tra risorsa '''''non pre-emptible''''' e '''''pre-emptible'''''. Una risorsa può essere pre-emptible se e solo se non ha uno stato interno o se lo stato può essere salvato per poi essere ripristinato.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Può accadere che un processo A effettui una ''richiesta bloccante'' ad una ''risorsa non-preemptible'' e ''non condivisibile'' che al momento è utilizzata da un qualche processo B. B, a sua volta, fa la stessa cosa ma, con una risorsa in possesso di A. Cosa accade?&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
'''DEADLOCK'''&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Le condizioni necessarie affinché un deadlock si verifichi sono:&lt;br /&gt;
*'''RICHIESTA BLOCCANTE'''&lt;br /&gt;
*'''RISORSA NON CONDIVISIBILE'''&lt;br /&gt;
*'''RISORSA NON-PREEMPITIBLE'''&lt;br /&gt;
*'''ATTESA CIRCOLARE'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
work in progress&lt;br /&gt;
&lt;br /&gt;
== Lezione del 15 marzo 2018 ==&lt;br /&gt;
Nessuna lezione.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 16 marzo 2018 ==&lt;br /&gt;
== Lezione del 21 marzo 2018 ==&lt;br /&gt;
== Lezione del 22 marzo 2018 ==&lt;br /&gt;
&lt;br /&gt;
Alcuni errori comuni della fase 1 del progetto:&lt;br /&gt;
* file sparsi nell'archivio, invece di una sola cartella che li contiene tutti&lt;br /&gt;
* uso del Makefile senza regole automatiche&lt;br /&gt;
* uso di sentinelle globali nella ricorsione&lt;br /&gt;
* uso di licenze non compatibili tra di loro&lt;br /&gt;
* indentazione sbagliata&lt;br /&gt;
&lt;br /&gt;
=== Memoria Principale ===&lt;br /&gt;
&lt;br /&gt;
La MMU è una componente hardware che implementa una funzione che associa gli indirizzi usati dal&lt;br /&gt;
sorgente (logici) a quelli reali in memoria (fisici).&lt;br /&gt;
&lt;br /&gt;
Un modo per calcolare gli indirizzi fisici corrispondenti agli indirizzi logici di un processo è&lt;br /&gt;
dare un registro base ad ogni processo; quando un processo accede all'indirizzo della variabile A,&lt;br /&gt;
ad esempio, la MMU calcola il suo indirizzo fisico aggiungendo al suo indirizzo logico il&lt;br /&gt;
valore del registro base. &lt;br /&gt;
&lt;br /&gt;
Qual è il problema con questo approccio? Non si hanno indirizzi più piccoli dell'indirizzo&lt;br /&gt;
base, ma si può sforare nello spazio di indirizzi di un altro processo, dato che non c'è un limite&lt;br /&gt;
superiore. Questo problema si risolve facilmente aggiungendo al registro base un registro limite.&lt;br /&gt;
Un processo può accedere a tutti gli indirizzi compresi tra i due indirizzi, e, se va fuori, si&lt;br /&gt;
genera una trap di memoria. Questo fornisce anche un meccanismo di protezione della memoria.&lt;br /&gt;
&lt;br /&gt;
Se adottiamo questo approccio quando allochiamo spazio in memoria per un processo dobbiamo farlo in&lt;br /&gt;
maniera contigua.&lt;br /&gt;
Come? Ci sono vari metodi:&lt;br /&gt;
&lt;br /&gt;
* '''Partizionamento statico''': in una sezione iniziale della memoria sta il kernel, il resto della memoria è divisa in parti; man mano che arrivano i processi vengono assegnati alle varie parti di memoria.  Che problemi ha questo approccio? È statico, ovvero la disposizione iniziale è quella definitiva. Inoltre dobbiamo scegliere il segmento dove inserire il nuovo processo allocato.  Spesso la memoria disponibile è maggiore di quella necessaria; di conseguenza ci sarà uno spreco.  Quando ci sono aree di memoria inutilizzate all'interno di porzioni di memoria di dimensioni stabilite a priori si parla di '''frammentazione interna'''. È' un problema che vogliamo risolvere;&lt;br /&gt;
&lt;br /&gt;
* '''Partizionamento dinamico''': potremmo, ad esempio, stackare i blocchi di memoria dei processi, così da non sprecare memoria. Problema: come facciamo quando un processo termina? In questo caso avremmo degli spazi di memoria inutilizzati sparsi per la memoria, che potrebbero dare spazio ad un altro processo se uniti assieme, ma che da soli non riescono perché sono troppo piccoli. Si parla in questo caso di '''frammentazione esterna'''. Si può risolvere il problema compattando le zone di memoria. Per fare ciò è necessario:&lt;br /&gt;
** copiare il PCB dal posto in cui si trova alla cima dello stack;&lt;br /&gt;
** cambiare base register e limit register;&lt;br /&gt;
** aspettare che il processo sia &amp;quot;fermo&amp;quot;; se fosse in esecuzione spostarlo ovviamente creerebbe delle inconsistenze.&lt;br /&gt;
&lt;br /&gt;
Se il processo è interattivo e si prova a fare compattazione, si parla di Giga da muovere: altamente&lt;br /&gt;
inefficiente. L'approccio migliore è quello di limitare la frammentazione esterna.  Come?&lt;br /&gt;
Scegliendo, tra le varie aree libere, quella migliore per contenere un processo. Per fare queste&lt;br /&gt;
operazioni è necessario qualcuno che tenga traccia della contabilità della memoria del sistema. Ci&lt;br /&gt;
pensa un programma chiamato Memory Manager. Come fa? Con una lista delle aree di memoria libere.&lt;br /&gt;
Questo permette di vedere se esistono zone di memoria libere contigue e di unirle assieme&lt;br /&gt;
eventualmente. Si possono usare anche le tabelle di bit: se consideriamo la memoria divisa in unita'&lt;br /&gt;
di allocazione possiamo associare ad ogni unità un bit, acceso o spento se libera o meno. &lt;br /&gt;
&lt;br /&gt;
Per scegliere le aree di memoria ci sono vari approcci:&lt;br /&gt;
* Best Fit: scelgo l'area più piccola che contiene il mio processo;&lt;br /&gt;
* First Fit: scelgo la prima area capace di contenere il mio processo che trovo dall'inizio della memoria;&lt;br /&gt;
* Next Fit: come First Fit ma parto dall'ultimo processo a cui sono arrivato con la precedente scansione;&lt;br /&gt;
* Worst Fit: scelgo l'area più grande che contiene il mio processo.&lt;br /&gt;
Quale tecnica è la migliore? Statisticamente First Fit.&lt;br /&gt;
&lt;br /&gt;
Questi meccanismi funzionavano per sistemi batch; per sistemi molto dinamici queste cose ci fanno&lt;br /&gt;
sprecare memoria o è richiesta la compattazione, che richiede tempo. La MMU con base register e&lt;br /&gt;
limit register non permettono altro. Si può fare di meglio? Sì.&lt;br /&gt;
&lt;br /&gt;
Come? Con la paginazione della memoria. La memoria viene divisa in blocchi di dimensione fissata&lt;br /&gt;
dette '''pagine'''. Ovviamente l'ampiezza della pagina è una potenza di 2. L'indirizzo logico visto dal&lt;br /&gt;
programma viene diviso in due parti: il numero di pagina e l'offset all'interno della pagina. La MMU&lt;br /&gt;
usa il numero della pagina come indice all'interno di una tabella. A quell'indice è associato,&lt;br /&gt;
nella tabella, l'indirizzo fisico che contiene la pagina. La parte di memoria fisica che contiene la&lt;br /&gt;
pagina è detta '''frame'''. L'indirizzo fisico corrispondente sarà dato, una volta ottenuto&lt;br /&gt;
l'indirizzo del frame che lo contiene, dai bit che precedono l'offset e, subito dopo, dall'offset&lt;br /&gt;
stesso, che viene copiato identico alla fine dell'indirizzo fisico. &lt;br /&gt;
&lt;br /&gt;
Questo meccanismo è molto più flessibile del partizionamento della memoria. C'è frammentazione&lt;br /&gt;
interna? Sì, ma poca: nel peggiore dei casi abbiamo un processo che ha bisogno di n pagine + 1 bit.&lt;br /&gt;
In questo caso verranno allocate n+1 pagine. Quindi la frammentazione interna che abbiamo sarà al&lt;br /&gt;
massimo dell'ordine della dimensione di una pagina per ogni processo.  C'è frammentazione esterna?&lt;br /&gt;
Potrebbe, ma sarebbe irrisoria: potrebbe esserci all'inizio della memoria dove risiede il kernel. &lt;br /&gt;
&lt;br /&gt;
Il problema della frammentazione è risolto. Ma dove sta il costo nascosto? Nella MMU. Come si&lt;br /&gt;
gestisce la sua tabella? La cosa migliore sarebbe una matrice associativa, sarebbe molto veloce, e&lt;br /&gt;
questa caratteristica è necessaria visto che la tabella va gestita ad ogni accesso in memoria.&lt;br /&gt;
Problema: il costo elevato di una componente hardware capace di mantenere una tabella delle pagine.&lt;br /&gt;
Mettiamo la tabella in memoria? Non è ideale, perché dopo servirebbero due accessi in memoria per&lt;br /&gt;
ogni accesso in memoria. Le prestazioni della memoria risultano quindi dimezzate. &lt;br /&gt;
&lt;br /&gt;
La soluzione? Una cache: il '''Translation Lookahead Buffer'''.  È una matrice associativa, ma fa da&lt;br /&gt;
cache alla tabella della MMU, che è salvata in memoria. È quindi molto più piccola della tabella.&lt;br /&gt;
Quando viene fatta una operazione in memoria si può avere un TLB hit: la velocità è alta.&lt;br /&gt;
Potremmo avere un TLB miss. In questo caso l'indirizzo richiesto non è in cache.  Questo genera un&lt;br /&gt;
interrupt, quindi il controllo passa al kernel, che va a vedere nella tabella in memoria se&lt;br /&gt;
l'indirizzo richiesto è legale, nel qual caso lo carica. Questo meccanismo è ragionevolmente&lt;br /&gt;
veloce e ha prezzi accessibili. Va tuttavia fatto notare che una macchina che usa allocazione&lt;br /&gt;
contigua senza paginazione è più veloce di una macchina con paginazione. Ovviamente quello che&lt;br /&gt;
abbiamo con questo meccanismo è un tradeoff. Paghiamo due prezzi: uno in termini di performance, un&lt;br /&gt;
altro in termini di linearità di esecuzioni, in cambio di un miglior utilizzo della memoria. &lt;br /&gt;
&lt;br /&gt;
Gli effetti positivi della paginazione sono tanti, ma rimangono alcuni problemi da risolvere. Se noi&lt;br /&gt;
abbiamo una libreria dinamica con la paginazione tradizionale avremmo tante copie della stessa&lt;br /&gt;
libreria in memoria, e quindi un enorme spreco. Per fare questo si usa il codice reentrant. La&lt;br /&gt;
memoria allocata per un processo è così divisa:&lt;br /&gt;
* area text: testo dell'eseguibile; solitamente read-only, dato che può anche essere sharable;&lt;br /&gt;
* area data: variabili globali; condivisibile per scelta;&lt;br /&gt;
* area stack: area dove cresce lo stack; MAI condivisibile.&lt;br /&gt;
&lt;br /&gt;
Come risolviamo questo problema? Col loading dinamico, ovvero loading di codice dalla memoria a&lt;br /&gt;
run-time. È una funzionalità fornita agli sviluppatori per scrivere codice più efficiente.&lt;br /&gt;
&lt;br /&gt;
Un'altra funzionalità è il linking dinamico. Le librerie dinamiche servono a questo: quando il&lt;br /&gt;
codice usa una libreria non la linka nell'eseguibile, ma si segna che è presente e si segna la&lt;br /&gt;
versione che usa; a run time, raggiunta quella parte di codice, si va a recuperare la libreria&lt;br /&gt;
dinamica già caricata in memoria (o casomai si carica); questo viene fatto dal linker.&lt;br /&gt;
&lt;br /&gt;
È possibile attaccarsi alla libreria già caricata in memoria grazie a memory mapping. Questo pero'&lt;br /&gt;
non è possibile con la paginazione tradizionale. Occorre introdurre un nuovo concetto: la&lt;br /&gt;
'''segmentazione'''. L'idea è di dividere l'area richiesta dai processi in segmenti adibiti a scopi&lt;br /&gt;
specifici. Si possono avere segmenti testo, data, stack, shared data, etc., ognuno con le sue&lt;br /&gt;
proprietà di leggibilità o di scrittura. Una MMU che supporta segmentazione è così&lt;br /&gt;
fatta: la parte iniziale (piccola) dell'indirizzo identifica il segmento, la parte finale identifica&lt;br /&gt;
l'offset all'interno del segmento. Per ogni segmento vi è una entry in una tabella, in cui vengono&lt;br /&gt;
mantenute le varie proprietà del segmento. Tra le varie cose viene mantenuto l'indirizzo base del&lt;br /&gt;
segmento (da sommare all'offset). Di segmenti ce ne sono pochi, di pagine tante.&lt;br /&gt;
&lt;br /&gt;
Facciamo un confronto tra paginazione e segmentazione:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Pagine&lt;br /&gt;
! Segmenti&lt;br /&gt;
|-&lt;br /&gt;
|Limita frammentazione&lt;br /&gt;
|Permette la condivisione e la protezione&lt;br /&gt;
|-&lt;br /&gt;
|Hanno tutte la stessa ampiezza (e.g. 4k)&lt;br /&gt;
|Hanno ampiezza (molto) variabile O(k),....,O(G)&lt;br /&gt;
|-&lt;br /&gt;
|Hanno un indirizzo&lt;br /&gt;
|Hanno un nome&lt;br /&gt;
|-&lt;br /&gt;
|Possono contenere dati disomogenei&lt;br /&gt;
|Contengono informazioni omogenee&lt;br /&gt;
|-&lt;br /&gt;
|Divisione in pagine automatica&lt;br /&gt;
|Divisione in segmenti; alcune divisioni sono strutturali, &lt;br /&gt;
altre sono decise dal programmatore&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Nella pratica che tecnica si usa? Entrambe: paginiamo i segmenti. Gli indirizzi logici contengono&lt;br /&gt;
l'identificatore del segmento, della pagina, l'offset ed il contesto. In caso di miss si usa il numero&lt;br /&gt;
di segmento per accedere ad una tabella con i bit di controllo. Se l'indirizzo è legale la entry&lt;br /&gt;
punta ad una page table. Si usa poi il numero di pagina per identificare la pagina e l'offset per&lt;br /&gt;
ottenere la parte di memoria desiderata.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 23 marzo 2018 ==&lt;br /&gt;
== Lezione del 28 marzo 2018 ==&lt;br /&gt;
== Lezione del 29 marzo 2018 ==&lt;br /&gt;
Pausa di primavera.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 30 marzo 2018 ==&lt;br /&gt;
Pausa di primavera.&lt;/div&gt;</summary>
		<author><name>Matt</name></author>
	</entry>
	<entry>
		<id>https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_II_semestre&amp;diff=2184</id>
		<title>Lezioni Anno Accademico 2017/18 II semestre</title>
		<link rel="alternate" type="text/html" href="https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_II_semestre&amp;diff=2184"/>
		<updated>2018-03-28T20:11:07Z</updated>

		<summary type="html">&lt;p&gt;Matt: /* Lezione del 29 marzo 2018 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Lezione del 28 febbraio 2018 ==&lt;br /&gt;
Inizia oggi la seconda parte del corso di Sistemi Operativi che si terrà dal 28/02/18 al 25/05/18.&amp;lt;br&amp;gt;&lt;br /&gt;
Le lezioni si svolgeranno, il mercoledì e il giovedì, in aula E1. Il venerdì invece ci sposteremo in M1.&amp;lt;br&amp;gt;&lt;br /&gt;
La suddivisione degli argomenti trattati sarà: '''mercoledì''' teoria, '''giovedì''' tendenzialmente parte progettuale (potrebbe iniziare con un po' di teoria) e '''venerdì''', data la scarsa dotazione dell'aula assegnataci, verranno svolti esercizi scritti in preparazione all'esame.&lt;br /&gt;
&lt;br /&gt;
Nel corso della lezione ci siamo chiesti, riprendendo un po' il filo logico delle lezioni dello scorso semestre, quali siano i servizi che ci aspettiamo da un sistema operativo.&amp;lt;br&amp;gt;&lt;br /&gt;
Questa volta, tuttavia, non li analizzeremo dal punto di vista dell'utilizzatore, ma da un punto di vista interno al sistema stesso.&amp;lt;br&amp;gt;&lt;br /&gt;
I servizi elencati sono stati:&lt;br /&gt;
* Scheduling (Gestione CPU)&lt;br /&gt;
* Gestione dei processi&lt;br /&gt;
* Gestione della memoria primaria&lt;br /&gt;
* Gestione dei device di I/O&lt;br /&gt;
* Gestione della memoria secondaria&lt;br /&gt;
* Filesystem&lt;br /&gt;
* Networking&lt;br /&gt;
* Protezione&lt;br /&gt;
* Shell&lt;br /&gt;
&lt;br /&gt;
Come in tutta l'informatica spesso succede, la complessità di questi servizi viene gestita tramite la creazione di livelli di astrazione. &amp;lt;br&amp;gt;&lt;br /&gt;
Il livello zero di astrazione, quello più basso, sarà l''''hardware''' che noi dovremo considerare come un linguaggio (l'ISA del processore).&amp;lt;br&amp;gt;&lt;br /&gt;
Seguito poi da due blocchi:&lt;br /&gt;
* '''Kernel'''&lt;br /&gt;
* '''Utente'''&lt;br /&gt;
&lt;br /&gt;
Cosa va incluso nel kernel di un S.O.? Cosa nella parte utente? Meglio massimizzare o minimizzare le operazioni nello livello kernel?&amp;lt;br&amp;gt; '''KERNEL MONOLITICO''' O '''MICRO-KERNEL'''?&lt;br /&gt;
&lt;br /&gt;
I kernel monolitici (tipo Linux) hanno il vantaggio di essere leggermente più performanti, mentre i microkernel (che presentano più livelli di astrazione) sono molto più flessibili e facili da manutenere.&lt;br /&gt;
Ad esempio, nei microkernel un bug non obbliga necessariamente a riavviare la macchina perché è possibile riavviare il singolo &amp;quot;modulo&amp;quot; che presenta l'errore.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 1 marzo 2018 ==&lt;br /&gt;
La lezione tace come concordato con gli studenti.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 2 marzo 2018 ==&lt;br /&gt;
La lezione tace causa chiusura dell'intero Ateneo per condizioni climatiche avverse.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 7 marzo 2018 ==&lt;br /&gt;
Tutto incomincia dal boot, la fase che carica il kernel, la quale termina subito dopo la creazione del primo processo (init) tramite la compilazione della tabella (come si vede sotto) che verrà passata allo scheduler. Una volta caricato, il kernel ha davanti una gradissima diversità di modelli hardware, ma come utilizzare tutti questi dispositivi hardware? Una prima soluzione sarebbe quella di includere nel kernel tutti i relativi device driver, tuttavia il kernel diventerbbe davvero corposo, a meno che non si crei un kernel ad hoc per una specifica macchina. Un'altra soluzione (quella comunemente adottata) utilizza i moduli. I moduli vengono caricati dal kernel all'occorrenza senza essere inseriti direttamente in esso (non è quindi necessaria la ricompilazione).&lt;br /&gt;
Tuttavia con quest'ultima soluzione si viene a creare un dilemma, ovvero:&amp;lt;br&amp;gt;&lt;br /&gt;
Chi carica i moduli? Il kernel.&amp;lt;br&amp;gt;&lt;br /&gt;
Chi carica il kernel? Il boot tramite moduli.&lt;br /&gt;
&lt;br /&gt;
In verità non esiste solo il kernel ma anche un filesystem che contiene i moduli chiamato &amp;quot;init.rd&amp;quot; (inutile se il kernel è configurato per una specifica macchina).&lt;br /&gt;
Arrivati a questo punto ci si chiede: come si compila un kernel? Prima di tutto occorrono i sorgenti (quelli di linux sono reperibili su https://www.kernel.org). Una volta scaricati, si deve configurare il file “.config” che sostanzialmente descrive le specifiche del kernel. Questo file lo si può configurare a mano o tramite il “menuconfig” che dovrà essere prima di tutto opportunatamente compilato. All'interno del menuconfig tra le altre cose c'è una lista di tutti i device driver supportati e possono essere spuntati con:  * (driver inserito nel kernel) o M (creazione del relativo modulo). Una volta configurato il “.config” non resta che compilare il kernel per la macchina ospite o per una specifica architettura. Ad esempio:&lt;br /&gt;
&lt;br /&gt;
wget link_of_kernel_source #scarica i sorgenti&amp;lt;br&amp;gt;&lt;br /&gt;
tar xf source_compressed_file # decomprime i relativi sorgenti, in questo caso dal file.tar.xz&amp;lt;br&amp;gt;&lt;br /&gt;
cd linux #entra nella directory dei sorgenti&amp;lt;br&amp;gt;&lt;br /&gt;
(Di regola esiste già un .config standard ma per configurarlo)&amp;lt;br&amp;gt;&lt;br /&gt;
make menuconfig&amp;lt;br&amp;gt;&lt;br /&gt;
make #compila il kernel per la macchina ospite&amp;lt;br&amp;gt;&lt;br /&gt;
(make -arch=armhf, compila secondo l'architettura dei processori armhf)&amp;lt;br&amp;gt;&lt;br /&gt;
( make -j n, compila il kernel per la macchina ospite utilizzando i suoi n-1 core)&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Una volta compilato l'immagine del kernel si troverà in arch/nome_architettura/boot/bzImage.&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ora si ricordi la differenza tra processo e thread: Il processo è l'entità titolare delle risorse, mentre il thread è la componente sequenziale dell'elaborazione.&lt;br /&gt;
Nei sistemi monothread il concetto di thread si sovrappone al concetto di processo ma in linea generale un processo può avere anche più di un thread. Ma quali sono le caratteristiche principali dei processi e dei thread?&lt;br /&gt;
&lt;br /&gt;
&amp;lt;table&amp;gt;&lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;Processo (PCB)&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Thread (TCB)&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;ID (self, parent,child etc)&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;ID&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;Info sulla memoria (puntatore alla tabella dei segmenti)&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Stato del processore&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;File aperti&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Stato: Ready, Running,Block&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;Ownership&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Puntatori&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;Interprocess comunication&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;     &lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;Informazioni sull'accounting &amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;/table&amp;gt;                          &lt;br /&gt;
&lt;br /&gt;
Modo Kernel e Modo User&amp;lt;br&amp;gt;&lt;br /&gt;
Si definisce Mode Switch il passaggio da Kernel Mode a User Mode e viceversa (esiste tale differenza per motivi di sicurrenzza). Attenzione però a non confondere la kernel mode con i permessi di root (root è l'utente con più permessi in assoluto ma è comunque gestito in user mode).&lt;br /&gt;
Si definisce Context Switch il passaggio di processi. Ad esempio un processo A in user mode si ferma e passa il controllo al trap che tramite lo scheduler chiama un altro processo.&lt;br /&gt;
Da qui si capische che è lo scheduler a decidere il prossimo processo da mandare avanti (tramite la Ready Queue). Un tipico scheduler è lo scheduler FIFO non preemptive (ossia il processo in esecuzione utilizzerà il processore finché non passerà allo stato di wait o termina l'esecuzione) per sistemi batch.&lt;br /&gt;
I thread posso essere creati sia in modo kernel che in modo user.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 8 marzo 2018 ==&lt;br /&gt;
Lo scheduler è quella parte del sistema operativo che si occupa di assegnare le risorse (specialmente il processore) ai vari processi, decidendone l'ordine e il tempo di esecuzione. Di scheduler ne esistono di 2 tipi; quelli non-preemptive (o cooperative) dove il context switch avviene solo se il processo in esecuzione fa I/O o una system call bloccante passando allo stato di wait o termina e quelli preemptive quando il processo in esecuzione può passare allo stato ready mediante un interrupt.&lt;br /&gt;
&lt;br /&gt;
I criteri con cui vengono scelti gli scheduler sono:&lt;br /&gt;
&lt;br /&gt;
* Throughput: la quantità di processi completati per unità di tempo&amp;lt;br&amp;gt;&lt;br /&gt;
* Turnaround: il tempo che intercorre da quando un processo entra nella coda ready fino alla fine della sua esecuzione&amp;lt;br&amp;gt;&lt;br /&gt;
* Percentuale di utilizzo del processore&amp;lt;br&amp;gt;&lt;br /&gt;
* Tempo di attesa: quanto tempo tempo un processo rimane nella coda ready prima di iniziare la sua esecuzione&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Partendo dal più semplice da realizzare, troviamo lo scheduler FCFS (First Come, First Served), il quale usa una politica FIFO quindi i processi vengono eseguiti in ordine di arrivo, è cooperative e non è per nulla efficiente.&lt;br /&gt;
&lt;br /&gt;
Uno scheduler più avanzato, ma sempre cooperative, è lo SJF (Shortest Job First) che da la priorità ai processi con meno tempo di CPU burst (tempo di utilizzo del processore). Il tempo di utilizzo della CPU viene stimato basandosi sulle precedenti esecuzioni e calcolato mediante la media esponenziale dando maggior peso alle esecuzioni più recenti.&lt;br /&gt;
Una variante preemptive del SJF è lo Shortest-Remeaning-Time First il quale fa passare dallo stato running a ready un processo se nel frattempo arriva nella coda ready un altro processo con un tempo di CPU burst più breve. Il problema principale però è che entrambi possono generare starvation.&lt;br /&gt;
&lt;br /&gt;
Lo scheduler Round-Robin, di tipo preemptive, fa uso della Time-Slice (ovvero un quanto di tempo). I processi vengono eseguiti in ordine di arrivo ma possono rilasciare il processore anticipatamente nel caso in cui finisca la time-slice a loro disposizione. Per implementare questo scheduler, tuttavia, è necessario un supporto hardware chiamato &amp;quot;interval time&amp;quot;, il quale non fa altro che generare un input ogni tot tempo scelto dal sistema operativo, questo input non ha nessuno scopo se non quello di generare un interrupt che possa interrompere l'esecuzione di un processo. La time-slice non piò essere troppo breve perché il context-switch ha un costo, non è istantaneo, ma nemmeno un troppo lunga altrimenti non si avrebbe l'idea di un sistema fluido ed istantaneo.&lt;br /&gt;
&lt;br /&gt;
Esistono anche gli scheduler che assegnano una priorità ai processi ed eseguono prima quelli con priorità più elevata. Questi scheduler esistono sia con priorità statica, che con priorità dinamica, ma potendo generare starvation, quelli con priorità dinamica possono utilizzare l'aging, ossia un processo con priorità bassa aumenta di volta in volta la sua priorità fino a diventare massima per poi, una volta eseguito, tornare alla priorità originaria.&lt;br /&gt;
Un po' più complessi sono gli scheduler con classi di priorità, in cui la coda ready è divisa in diverse sottocode, una per ogni classe di processi. Gli scheduler multilivello utilizzano sempre le classi di priorità ma assegnano ad ogni classe delle politiche diverse, adatte per i processi che contiene.&lt;br /&gt;
&lt;br /&gt;
Per applicazioni particolari, in cui la correttezza del programma dipende da tempo entro i quale viene data la risposta, esistono gli scheduler real-time.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 9 marzo 2018 ==&lt;br /&gt;
Esercizi su monitor e semafori degli esami di gennaio e febbraio 2018.&lt;br /&gt;
&lt;br /&gt;
Testo:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=c&amp;gt;&lt;br /&gt;
Monitor bridge&lt;br /&gt;
	boolean bridge_is_down&lt;br /&gt;
	int crossingCars&lt;br /&gt;
	int crossingBoat&lt;br /&gt;
	int waitingCars&lt;br /&gt;
	int waitingBoats&lt;br /&gt;
	condition ok2cars&lt;br /&gt;
	condition ok2boats&lt;br /&gt;
&lt;br /&gt;
procedure_entry car_enter(direction)&lt;br /&gt;
	if(crossingBoat || waitingBoats &amp;gt; 0){&lt;br /&gt;
		waitingCars++&lt;br /&gt;
		ok2cars.wait()&lt;br /&gt;
		waitingCars--&lt;br /&gt;
	}&lt;br /&gt;
	bridge_is_down = true&lt;br /&gt;
	crossingCars++&lt;br /&gt;
	ok2cars.signal()&lt;br /&gt;
&lt;br /&gt;
procedure_entry car_exit(direction)&lt;br /&gt;
	crossingCars--&lt;br /&gt;
	if(crossingCars == 0) ok2boat.signal()&lt;br /&gt;
&lt;br /&gt;
procedure_entry boat_enter(direction)&lt;br /&gt;
	if(crossingCars &amp;gt; 0 || crossingBoat &amp;gt; 0){&lt;br /&gt;
		waitingBoats++&lt;br /&gt;
		ok2boat.wait()&lt;br /&gt;
		waitingBoats--&lt;br /&gt;
	}&lt;br /&gt;
	bridge_is_down = false&lt;br /&gt;
	crossingBoat++&lt;br /&gt;
&lt;br /&gt;
procedure_entry boat_exit(direction)&lt;br /&gt;
	crossingBoat--&lt;br /&gt;
	ok2cars.signal()&lt;br /&gt;
	if(crossingCars == 0) ok2boat.signal()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Testo: implementare, utilizzando semafori ordinari, i semafori ternari&lt;br /&gt;
&amp;lt;syntaxhighlight lang=c&amp;gt;&lt;br /&gt;
class ternarySemaphore&lt;br /&gt;
&lt;br /&gt;
	semaphore pos&lt;br /&gt;
	semaphore neg&lt;br /&gt;
	&lt;br /&gt;
	void ternarySemaphore(int init)&lt;br /&gt;
	void P(void)&lt;br /&gt;
	void V(void)&lt;br /&gt;
&lt;br /&gt;
	void ternarySemaphore(void init)&lt;br /&gt;
		// init is -1 or 1&lt;br /&gt;
		pos = new semaphore(1 + init)&lt;br /&gt;
		neg = new semaphore(1 - init)&lt;br /&gt;
&lt;br /&gt;
	void P(void)&lt;br /&gt;
		pos.P()&lt;br /&gt;
		neg.V()&lt;br /&gt;
&lt;br /&gt;
	void V(void)&lt;br /&gt;
		neg.P()&lt;br /&gt;
		pos.V()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Soluzione alternativa, meno di stile ma ugualmente funzionante&lt;br /&gt;
&amp;lt;syntaxhighlight lang=c&amp;gt;&lt;br /&gt;
class ternarySemaphore&lt;br /&gt;
&lt;br /&gt;
	semaphore queue&lt;br /&gt;
	semaphore mutex&lt;br /&gt;
	int value&lt;br /&gt;
&lt;br /&gt;
	void ternarySemaphore(int init)&lt;br /&gt;
	void P(void)&lt;br /&gt;
	void V(void)&lt;br /&gt;
&lt;br /&gt;
	void ternarySemaphore(void init)&lt;br /&gt;
		mutex = new semaphore(1)&lt;br /&gt;
		value = init&lt;br /&gt;
		queue = new sem_queue&lt;br /&gt;
&lt;br /&gt;
	void P(void)&lt;br /&gt;
		mutex.P()&lt;br /&gt;
		if(value == 1 &amp;amp;&amp;amp; !queue.empty()){&lt;br /&gt;
			s = queue.dequeue()&lt;br /&gt;
			s.V()&lt;br /&gt;
		}else{&lt;br /&gt;
			if(value == -1){&lt;br /&gt;
				s = new semaphore(0)&lt;br /&gt;
				queue.enqueue(s)&lt;br /&gt;
				mutex.V()&lt;br /&gt;
				s.P()&lt;br /&gt;
			}else{&lt;br /&gt;
				value--&lt;br /&gt;
				mutex.V()&lt;br /&gt;
			}&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
	void V(void)&lt;br /&gt;
		mutex.P()&lt;br /&gt;
		if(value == -1 &amp;amp;&amp;amp; !queue.empty()){&lt;br /&gt;
			s = queue.dequeue()&lt;br /&gt;
			s.V()&lt;br /&gt;
		}else{&lt;br /&gt;
			if(value == -1){&lt;br /&gt;
				s = new semaphore(0)&lt;br /&gt;
				queue.enqueue(s)&lt;br /&gt;
				mutex.V()&lt;br /&gt;
				s.P()&lt;br /&gt;
				free(s)&lt;br /&gt;
			}else{&lt;br /&gt;
				value++&lt;br /&gt;
				mutex.V()&lt;br /&gt;
			}&lt;br /&gt;
		}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Lezione del 14 marzo 2018 ==&lt;br /&gt;
'''RISORSE'''&lt;br /&gt;
&lt;br /&gt;
Cosa intendiamo per risorsa? Quali sono i problemi comuni e le tecniche per risolverli?&amp;lt;br&amp;gt;&lt;br /&gt;
Nel frattempo definiamo come '''CLASSI DI RISORSE''' l'insieme formato da risorse tra loro equivalenti (byte della memoria, stampanti dello stesso tipo, etc.).&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Una prima distinzione può essere fatta tra risorse ad assegnazione '''''statica''''' e ad assegnazione '''''dinamica''''': le prime, una volta assegnate, non possono essere più prelevate finché il processo che le possiede non termina. Nel secondo caso, invece, l'assegnazione può mutare nel tempo.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Si può distinguere il tipo di richiesta che viene fatta alla risorsa: '''''singola''''' o '''''multipla''''', '''''bloccante''''' o '''''non bloccante'''''. Intuitivamente, la prima distinzione si riferisce alla molteplicità di richieste che la risorsa può eseguire nello stesso momento mentre, la seconda distinzione, si riferisce al comportamento della richiesta.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Altre distinzioni possono essere fatte tra risorse '''''non condivisibili''''' (dette anche seriali) e risorse '''''condivisibili''''' (o non seriali), tra risorsa '''''non pre-emptible''''' e '''''pre-emptible'''''. Una risorsa può essere pre-emptible se e solo se non ha uno stato interno o se lo stato può essere salvato per poi essere ripristinato.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Può accadere che un processo A effettui una ''richiesta bloccante'' ad una ''risorsa non-preemptible'' e ''non condivisibile'' che al momento è utilizzata da un qualche processo B. B, a sua volta, fa la stessa cosa ma, con una risorsa in possesso di A. Cosa accade?&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
'''DEADLOCK'''&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Le condizioni necessarie affinché un deadlock si verifichi sono:&lt;br /&gt;
*'''RICHIESTA BLOCCANTE'''&lt;br /&gt;
*'''RISORSA NON CONDIVISIBILE'''&lt;br /&gt;
*'''RISORSA NON-PREEMPITIBLE'''&lt;br /&gt;
*'''ATTESA CIRCOLARE'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
work in progress&lt;br /&gt;
&lt;br /&gt;
== Lezione del 15 marzo 2018 ==&lt;br /&gt;
Nessuna lezione.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 16 marzo 2018 ==&lt;br /&gt;
== Lezione del 21 marzo 2018 ==&lt;br /&gt;
== Lezione del 22 marzo 2018 ==&lt;br /&gt;
&lt;br /&gt;
Alcuni errori comuni della fase 1 del progetto:&lt;br /&gt;
* file sparsi nell'archivio, invece di una sola cartella che li contiene tutti&lt;br /&gt;
* uso del Makefile senza regole automatiche&lt;br /&gt;
* uso di sentinelle globali nella ricorsione&lt;br /&gt;
* uso di licenze non compatibili tra di loro&lt;br /&gt;
* indentazione sbagliata&lt;br /&gt;
&lt;br /&gt;
=== Memoria Principale ===&lt;br /&gt;
&lt;br /&gt;
La MMU è una componente hardware che implementa una funzione che associa gli indirizzi usati dal&lt;br /&gt;
sorgente (logici) a quelli reali in memoria (fisici).&lt;br /&gt;
&lt;br /&gt;
Un modo per calcolare gli indirizzi fisici corrispondenti agli indirizzi logici di un processo è&lt;br /&gt;
dare un registro base ad ogni processo; quando un processo accede all'indirizzo della variabile A,&lt;br /&gt;
ad esempio, la MMU calcola il suo indirizzo fisico aggiungendo al suo indirizzo logico il&lt;br /&gt;
valore del registro base. &lt;br /&gt;
&lt;br /&gt;
Qual è il problema con questo approccio? Non si hanno indirizzi più piccoli dell'indirizzo&lt;br /&gt;
base, ma si può sforare nello spazio di indirizzi di un altro processo, dato che non c'è un limite&lt;br /&gt;
superiore. Questo problema si risolve facilmente aggiungendo al registro base un registro limite.&lt;br /&gt;
Un processo può accedere a tutti gli indirizzi compresi tra i due indirizzi, e, se va fuori, si&lt;br /&gt;
genera una trap di memoria. Questo fornisce anche un meccanismo di protezione della memoria.&lt;br /&gt;
&lt;br /&gt;
Se adottiamo questo approccio quando allochiamo spazio in memoria per un processo dobbiamo farlo in&lt;br /&gt;
maniera contigua.&lt;br /&gt;
Come? Ci sono vari metodi:&lt;br /&gt;
&lt;br /&gt;
* '''Partizionamento statico''': in una sezione iniziale della memoria sta il kernel, il resto della memoria è divisa in parti; man mano che arrivano i processi vengono assegnati alle varie parti di memoria.  Che problemi ha questo approccio? È statico, ovvero la disposizione iniziale è quella definitiva. Inoltre dobbiamo scegliere il segmento dove inserire il nuovo processo allocato.  Spesso la memoria disponibile è maggiore di quella necessaria; di conseguenza ci sarà uno spreco.  Quando ci sono aree di memoria inutilizzate all'interno di porzioni di memoria di dimensioni stabilite a priori si parla di '''frammentazione interna'''. È' un problema che vogliamo risolvere;&lt;br /&gt;
&lt;br /&gt;
* '''Partizionamento dinamico''': potremmo, ad esempio, stackare i blocchi di memoria dei processi, così da non sprecare memoria. Problema: come facciamo quando un processo termina? In questo caso avremmo degli spazi di memoria inutilizzati sparsi per la memoria, che potrebbero dare spazio ad un altro processo se uniti assieme, ma che da soli non riescono perché sono troppo piccoli. Si parla in questo caso di '''frammentazione esterna'''. Si può risolvere il problema compattando le zone di memoria. Per fare ciò è necessario:&lt;br /&gt;
** copiare il PCB dal posto in cui si trova alla cima dello stack;&lt;br /&gt;
** cambiare base register e limit register;&lt;br /&gt;
** aspettare che il processo sia &amp;quot;fermo&amp;quot;; se fosse in esecuzione spostarlo ovviamente creerebbe delle inconsistenze.&lt;br /&gt;
&lt;br /&gt;
Se il processo è interattivo e si prova a fare compattazione, si parla di Giga da muovere: altamente&lt;br /&gt;
inefficiente. L'approccio migliore è quello di limitare la frammentazione esterna.  Come?&lt;br /&gt;
Scegliendo, tra le varie aree libere, quella migliore per contenere un processo. Per fare queste&lt;br /&gt;
operazioni è necessario qualcuno che tenga traccia della contabilità della memoria del sistema. Ci&lt;br /&gt;
pensa un programma chiamato Memory Manager. Come fa? Con una lista delle aree di memoria libere.&lt;br /&gt;
Questo permette di vedere se esistono zone di memoria libere contigue e di unirle assieme&lt;br /&gt;
eventualmente. Si possono usare anche le tabelle di bit: se consideriamo la memoria divisa in unita'&lt;br /&gt;
di allocazione possiamo associare ad ogni unità un bit, acceso o spento se libera o meno. &lt;br /&gt;
&lt;br /&gt;
Per scegliere le aree di memoria ci sono vari approcci:&lt;br /&gt;
* Best Fit: scelgo l'area più piccola che contiene il mio processo;&lt;br /&gt;
* First Fit: scelgo la prima area capace di contenere il mio processo che trovo dall'inizio della memoria;&lt;br /&gt;
* Next Fit: come First Fit ma parto dall'ultimo processo a cui sono arrivato con la precedente scansione;&lt;br /&gt;
* Worst Fit: scelgo l'area più grande che contiene il mio processo.&lt;br /&gt;
Quale tecnica è la migliore? Statisticamente First Fit.&lt;br /&gt;
&lt;br /&gt;
Questi meccanismi funzionavano per sistemi batch; per sistemi molto dinamici queste cose ci fanno&lt;br /&gt;
sprecare memoria o è richiesta la compattazione, che richiede tempo. La MMU con base register e&lt;br /&gt;
limit register non permettono altro. Si può fare di meglio? Sì.&lt;br /&gt;
&lt;br /&gt;
Come? Con la paginazione della memoria. La memoria viene divisa in blocchi di dimensione fissata&lt;br /&gt;
dette '''pagine'''. Ovviamente l'ampiezza della pagina è una potenza di 2. L'indirizzo logico visto dal&lt;br /&gt;
programma viene diviso in due parti: il numero di pagina e l'offset all'interno della pagina. La MMU&lt;br /&gt;
usa il numero della pagina come indice all'interno di una tabella. A quell'indice è associato,&lt;br /&gt;
nella tabella, l'indirizzo fisico che contiene la pagina. La parte di memoria fisica che contiene la&lt;br /&gt;
pagina è detta '''frame'''. L'indirizzo fisico corrispondente sarà dato, una volta ottenuto&lt;br /&gt;
l'indirizzo del frame che lo contiene, dai bit che precedono l'offset e, subito dopo, dall'offset&lt;br /&gt;
stesso, che viene copiato identico alla fine dell'indirizzo fisico. &lt;br /&gt;
&lt;br /&gt;
Questo meccanismo è molto più flessibile del partizionamento della memoria. C'è frammentazione&lt;br /&gt;
interna? Sì, ma poca: nel peggiore dei casi abbiamo un processo che ha bisogno di n pagine + 1 bit.&lt;br /&gt;
In questo caso verranno allocate n+1 pagine. Quindi la frammentazione interna che abbiamo sarà al&lt;br /&gt;
massimo dell'ordine della dimensione di una pagina per ogni processo.  C'è frammentazione esterna?&lt;br /&gt;
Potrebbe, ma sarebbe irrisoria: potrebbe esserci all'inizio della memoria dove risiede il kernel. &lt;br /&gt;
&lt;br /&gt;
Il problema della frammentazione è risolto. Ma dove sta il costo nascosto? Nella MMU. Come si&lt;br /&gt;
gestisce la sua tabella? La cosa migliore sarebbe una matrice associativa, sarebbe molto veloce, e&lt;br /&gt;
questa caratteristica è necessaria visto che la tabella va gestita ad ogni accesso in memoria.&lt;br /&gt;
Problema: il costo elevato di una componente hardware capace di mantenere una tabella delle pagine.&lt;br /&gt;
Mettiamo la tabella in memoria? Non è ideale, perché dopo servirebbero due accessi in memoria per&lt;br /&gt;
ogni accesso in memoria. Le prestazioni della memoria risultano quindi dimezzate. &lt;br /&gt;
&lt;br /&gt;
La soluzione? Una cache: il '''Translation Lookahead Buffer'''.  È una matrice associativa, ma fa da&lt;br /&gt;
cache alla tabella della MMU, che è salvata in memoria. È quindi molto più piccola della tabella.&lt;br /&gt;
Quando viene fatta una operazione in memoria si può avere un TLB hit: la velocità è alta.&lt;br /&gt;
Potremmo avere un TLB miss. In questo caso l'indirizzo richiesto non è in cache.  Questo genera un&lt;br /&gt;
interrupt, quindi il controllo passa al kernel, che va a vedere nella tabella in memoria se&lt;br /&gt;
l'indirizzo richiesto è legale, nel qual caso lo carica. Questo meccanismo è ragionevolmente&lt;br /&gt;
veloce e ha prezzi accessibili. Va tuttavia fatto notare che una macchina che usa allocazione&lt;br /&gt;
contigua senza paginazione è più veloce di una macchina con paginazione. Ovviamente quello che&lt;br /&gt;
abbiamo con questo meccanismo è un tradeoff. Paghiamo due prezzi: uno in termini di performance, un&lt;br /&gt;
altro in termini di linearità di esecuzioni, in cambio di un miglior utilizzo della memoria. &lt;br /&gt;
&lt;br /&gt;
Gli effetti positivi della paginazione sono tanti, ma rimangono alcuni problemi da risolvere. Se noi&lt;br /&gt;
abbiamo una libreria dinamica con la paginazione tradizionale avremmo tante copie della stessa&lt;br /&gt;
libreria in memoria, e quindi un enorme spreco. Per fare questo si usa il codice reentrant. La&lt;br /&gt;
memoria allocata per un processo è così divisa:&lt;br /&gt;
* area text: testo dell'eseguibile; solitamente read-only, dato che può anche essere sharable;&lt;br /&gt;
* area data: variabili globali; condivisibile per scelta;&lt;br /&gt;
* area stack: area dove cresce lo stack; MAI condivisibile.&lt;br /&gt;
&lt;br /&gt;
Come risolviamo questo problema? Col loading dinamico, ovvero loading di codice dalla memoria a&lt;br /&gt;
run-time. È una funzionalità fornita agli sviluppatori per scrivere codice più efficiente.&lt;br /&gt;
&lt;br /&gt;
Un'altra funzionalità è il linking dinamico. Le librerie dinamiche servono a questo: quando il&lt;br /&gt;
codice usa una libreria non la linka nell'eseguibile, ma si segna che è presente e si segna la&lt;br /&gt;
versione che usa; a run time, raggiunta quella parte di codice, si va a recuperare la libreria&lt;br /&gt;
dinamica già caricata in memoria (o casomai si carica); questo viene fatto dal linker.&lt;br /&gt;
&lt;br /&gt;
È possibile attaccarsi alla libreria già caricata in memoria grazie a memory mapping. Questo pero'&lt;br /&gt;
non è possibile con la paginazione tradizionale. Occorre introdurre un nuovo concetto: la&lt;br /&gt;
'''segmentazione'''. L'idea è di dividere l'area richiesta dai processi in segmenti adibiti a scopi&lt;br /&gt;
specifici. Si possono avere segmenti testo, data, stack, shared data, etc., ognuno con le sue&lt;br /&gt;
proprietà di leggibilità o di scrittura. Una MMU che supporta segmentazione è così&lt;br /&gt;
fatta: la parte iniziale (piccola) dell'indirizzo identifica il segmento, la parte finale identifica&lt;br /&gt;
l'offset all'interno del segmento. Per ogni segmento vi è una entry in una tabella, in cui vengono&lt;br /&gt;
mantenute le varie proprietà del segmento. Tra le varie cose viene mantenuto l'indirizzo base del&lt;br /&gt;
segmento (da sommare all'offset). Di segmenti ce ne sono pochi, di pagine tante.&lt;br /&gt;
&lt;br /&gt;
Facciamo un confronto tra paginazione e segmentazione:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Pagine&lt;br /&gt;
! Segmenti&lt;br /&gt;
|-&lt;br /&gt;
|Limita frammentazione&lt;br /&gt;
|Permette la condivisione e la protezione&lt;br /&gt;
|-&lt;br /&gt;
|Hanno tutte la stessa ampiezza (e.g. 4k)&lt;br /&gt;
|Hanno ampiezza (molto) variabile O(k),....,O(G)&lt;br /&gt;
|-&lt;br /&gt;
|Hanno un indirizzo&lt;br /&gt;
|Hanno un nome&lt;br /&gt;
|-&lt;br /&gt;
|Possono contenere dati disomogenei&lt;br /&gt;
|Contengono informazioni omogenee&lt;br /&gt;
|-&lt;br /&gt;
|Divisione in pagine automatica&lt;br /&gt;
|Divisione in segmenti; alcune divisioni sono strutturali, &lt;br /&gt;
altre sono decise dal programmatore&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Nella pratica che tecnica si usa? Entrambe: paginiamo i segmenti. Gli indirizzi logici contengono&lt;br /&gt;
l'identificatore del segmento, della pagina, l'offset ed il contesto. In caso di miss si usa il numero&lt;br /&gt;
di segmento per accedere ad una tabella con i bit di controllo. Se l'indirizzo è legale la entry&lt;br /&gt;
punta ad una page table. Si usa poi il numero di pagina per identificare la pagina e l'offset per&lt;br /&gt;
ottenere la parte di memoria desiderata.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 23 marzo 2018 ==&lt;br /&gt;
== Lezione del 28 marzo 2018 ==&lt;br /&gt;
== Lezione del 29 marzo 2018 ==&lt;br /&gt;
Pausa di primavera.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 30 marzo 2018 ==&lt;/div&gt;</summary>
		<author><name>Matt</name></author>
	</entry>
	<entry>
		<id>https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_II_semestre&amp;diff=2179</id>
		<title>Lezioni Anno Accademico 2017/18 II semestre</title>
		<link rel="alternate" type="text/html" href="https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_II_semestre&amp;diff=2179"/>
		<updated>2018-03-20T23:25:26Z</updated>

		<summary type="html">&lt;p&gt;Matt: /* Lezione del 14 marzo 2018 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Lezione del 28 febbraio 2018 ==&lt;br /&gt;
Inizia oggi la seconda parte del corso di Sistemi Operativi che si terrà dal 28/02/18 al 25/05/18.&amp;lt;br&amp;gt;&lt;br /&gt;
Le lezioni si svolgeranno, il mercoledì e il giovedì, in aula E1. Il venerdì invece ci sposteremo in M1.&amp;lt;br&amp;gt;&lt;br /&gt;
La suddivisione degli argomenti trattati sarà: '''mercoledì''' teoria, '''giovedì''' tendenzialmente parte progettuale (potrebbe iniziare con un po' di teoria) e '''venerdì''', data la scarsa dotazione dell'aula assegnataci, verranno svolti esercizi scritti in preparazione all'esame.&lt;br /&gt;
&lt;br /&gt;
Nel corso della lezione ci siamo chiesti, riprendendo un po' il filo logico delle lezioni dello scorso semestre, quali siano i servizi che ci aspettiamo da un sistema operativo.&amp;lt;br&amp;gt;&lt;br /&gt;
Questa volta, tuttavia, non li analizzeremo dal punto di vista dell'utilizzatore, ma da un punto di vista interno al sistema stesso.&amp;lt;br&amp;gt;&lt;br /&gt;
I servizi elencati sono stati:&lt;br /&gt;
* Scheduling (Gestione CPU)&lt;br /&gt;
* Gestione dei processi&lt;br /&gt;
* Gestione della memoria primaria&lt;br /&gt;
* Gestione dei device di I/O&lt;br /&gt;
* Gestione della memoria secondaria&lt;br /&gt;
* Filesystem&lt;br /&gt;
* Networking&lt;br /&gt;
* Protezione&lt;br /&gt;
* Shell&lt;br /&gt;
&lt;br /&gt;
Come in tutta l'informatica spesso succede, la complessità di questi servizi viene gestita tramite la creazione di livelli di astrazione. &amp;lt;br&amp;gt;&lt;br /&gt;
Il livello zero di astrazione, quello più basso, sarà l''''hardware''' che noi dovremo considerare come un linguaggio (l'ISA del processore).&amp;lt;br&amp;gt;&lt;br /&gt;
Seguito poi da due blocchi:&lt;br /&gt;
* '''Kernel'''&lt;br /&gt;
* '''Utente'''&lt;br /&gt;
&lt;br /&gt;
Cosa va incluso nel kernel di un S.O.? Cosa nella parte utente? Meglio massimizzare o minimizzare le operazioni nello livello kernel?&amp;lt;br&amp;gt; '''KERNEL MONOLITICO''' O '''MICRO-KERNEL'''?&lt;br /&gt;
&lt;br /&gt;
I kernel monolitici (tipo Linux) hanno il vantaggio di essere leggermente più performanti, mentre i microkernel (che presentano più livelli di astrazione) sono molto più flessibili e facili da manutenere.&lt;br /&gt;
Ad esempio, nei microkernel un bug non obbliga necessariamente a riavviare la macchina perché è possibile riavviare il singolo &amp;quot;modulo&amp;quot; che presenta l'errore.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 1 marzo 2018 ==&lt;br /&gt;
La lezione tace come concordato con gli studenti.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 2 marzo 2018 ==&lt;br /&gt;
La lezione tace causa chiusura dell'intero Ateneo per condizioni climatiche avverse.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 7 marzo 2018 ==&lt;br /&gt;
Tutto incomincia dal boot, la fase che carica il kernel, la quale termina subito dopo la creazione del primo processo (init) tramite la compilazione della tabella (come si vede sotto) che verrà passata allo scheduler. Una volta caricato, il kernel ha davanti una gradissima diversità di modelli hardware, ma come utilizzare tutti questi dispositivi hardware? Una prima soluzione sarebbe quella di includere nel kernel tutti i relativi device driver, tuttavia il kernel diventerbbe davvero corposo, a meno che non si crei un kernel ad hoc per una specifica macchina. Un'altra soluzione (quella comunemente adottata) utilizza i moduli. I moduli vengono caricati dal kernel all'occorrenza senza essere inseriti direttamente in esso (non è quindi necessaria la ricompilazione).&lt;br /&gt;
Tuttavia con quest'ultima soluzione si viene a creare un dilemma, ovvero:&amp;lt;br&amp;gt;&lt;br /&gt;
Chi carica i moduli? Il kernel.&amp;lt;br&amp;gt;&lt;br /&gt;
Chi carica il kernel? Il boot tramite moduli.&lt;br /&gt;
&lt;br /&gt;
In verità non esiste solo il kernel ma anche un filesystem che contiene i moduli chiamato &amp;quot;init.rd&amp;quot; (inutile se il kernel è configurato per una specifica macchina).&lt;br /&gt;
Arrivati a questo punto ci si chiede: come si compila un kernel? Prima di tutto occorrono i sorgenti (quelli di linux sono reperibili su https://www.kernel.org). Una volta scaricati, si deve configurare il file “.config” che sostanzialmente descrive le specifiche del kernel. Questo file lo si può configurare a mano o tramite il “menuconfig” che dovrà essere prima di tutto opportunatamente compilato. All'interno del menuconfig tra le altre cose c'è una lista di tutti i device driver supportati e possono essere spuntati con:  * (driver inserito nel kernel) o M (creazione del relativo modulo). Una volta configurato il “.config” non resta che compilare il kernel per la macchina ospite o per una specifica architettura. Ad esempio:&lt;br /&gt;
&lt;br /&gt;
wget link_of_kernel_source #scarica i sorgenti&amp;lt;br&amp;gt;&lt;br /&gt;
tar xf source_compressed_file # decomprime i relativi sorgenti, in questo caso dal file.tar.xz&amp;lt;br&amp;gt;&lt;br /&gt;
cd linux #entra nella directory dei sorgenti&amp;lt;br&amp;gt;&lt;br /&gt;
(Di regola esiste già un .config standard ma per configurarlo)&amp;lt;br&amp;gt;&lt;br /&gt;
make menuconfig&amp;lt;br&amp;gt;&lt;br /&gt;
make #compila il kernel per la macchina ospite&amp;lt;br&amp;gt;&lt;br /&gt;
(make -arch=armhf, compila secondo l'architettura dei processori armhf)&amp;lt;br&amp;gt;&lt;br /&gt;
( make -j n, compila il kernel per la macchina ospite utilizzando i suoi n-1 core)&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Una volta compilato l'immagine del kernel si troverà in arch/nome_architettura/boot/bzImage.&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ora si ricordi la differenza tra processo e thread: Il processo è l'entità titolare delle risorse, mentre il thread è la componente sequenziale dell'elaborazione.&lt;br /&gt;
Nei sistemi monothread il concetto di thread si sovrappone al concetto di processo ma in linea generale un processo può avere anche più di un thread. Ma quali sono le caratteristiche principali dei processi e dei thread?&lt;br /&gt;
&lt;br /&gt;
&amp;lt;table&amp;gt;&lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;Processo (PCB)&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Thread (TCB)&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;ID (self, parent,child etc)&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;ID&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;Info sulla memoria (puntatore alla tabella dei segmenti)&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Stato del processore&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;File aperti&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Stato: Ready, Running,Block&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;Ownership&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Puntatori&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;Interprocess comunication&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;     &lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;Informazioni sull'accounting &amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;/table&amp;gt;                          &lt;br /&gt;
&lt;br /&gt;
Modo Kernel e Modo User&amp;lt;br&amp;gt;&lt;br /&gt;
Si definisce Mode Switch il passaggio da Kernel Mode a User Mode e viceversa (esiste tale differenza per motivi di sicurrenzza). Attenzione però a non confondere la kernel mode con i permessi di root (root è l'utente con più permessi in assoluto ma è comunque gestito in user mode).&lt;br /&gt;
Si definisce Context Switch il passaggio di processi. Ad esempio un processo A in user mode si ferma e passa il controllo al trap che tramite lo scheduler chiama un altro processo.&lt;br /&gt;
Da qui si capische che è lo scheduler a decidere il prossimo processo da mandare avanti (tramite la Ready Queue). Un tipico scheduler è lo scheduler FIFO non preemptive (ossia il processo in esecuzione utilizzerà il processore finché non passerà allo stato di wait o termina l'esecuzione) per sistemi batch.&lt;br /&gt;
I thread posso essere creati sia in modo kernel che in modo user.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 8 marzo 2018 ==&lt;br /&gt;
Lo scheduler è quella parte del sistema operativo che si occupa di assegnare le risorse (specialmente il processore) ai vari processi, decidendone l'ordine e il tempo di esecuzione. Di scheduler ne esistono di 2 tipi; quelli non-preemptive (o cooperative) dove il context switch avviene solo se il processo in esecuzione fa I/O o una system call bloccante passando allo stato di wait o termina e quelli preemptive quando il processo in esecuzione può passare allo stato ready mediante un interrupt.&lt;br /&gt;
&lt;br /&gt;
I criteri con cui vengono scelti gli scheduler sono:&lt;br /&gt;
&lt;br /&gt;
* Throughput: la quantità di processi completati per unità di tempo&amp;lt;br&amp;gt;&lt;br /&gt;
* Turnaround: il tempo che intercorre da quando un processo entra nella coda ready fino alla fine della sua esecuzione&amp;lt;br&amp;gt;&lt;br /&gt;
* Percentuale di utilizzo del processore&amp;lt;br&amp;gt;&lt;br /&gt;
* Tempo di attesa: quanto tempo tempo un processo rimane nella coda ready prima di iniziare la sua esecuzione&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Partendo dal più semplice da realizzare, troviamo lo scheduler FCFS (First Come, First Served), il quale usa una politica FIFO quindi i processi vengono eseguiti in ordine di arrivo, è cooperative e non è per nulla efficiente.&lt;br /&gt;
&lt;br /&gt;
Uno scheduler più avanzato, ma sempre cooperative, è lo SJF (Shortest Job First) che da la priorità ai processi con meno tempo di CPU burst (tempo di utilizzo del processore). Il tempo di utilizzo della CPU viene stimato basandosi sulle precedenti esecuzioni e calcolato mediante la media esponenziale dando maggior peso alle esecuzioni più recenti.&lt;br /&gt;
Una variante preemptive del SJF è lo Shortest-Remeaning-Time First il quale fa passare dallo stato running a ready un processo se nel frattempo arriva nella coda ready un altro processo con un tempo di CPU burst più breve. Il problema principale però è che entrambi possono generare starvation.&lt;br /&gt;
&lt;br /&gt;
Lo scheduler Round-Robin, di tipo preemptive, fa uso della Time-Slice (ovvero un quanto di tempo). I processi vengono eseguiti in ordine di arrivo ma possono rilasciare il processore anticipatamente nel caso in cui finisca la time-slice a loro disposizione. Per implementare questo scheduler, tuttavia, è necessario un supporto hardware chiamato &amp;quot;interval time&amp;quot;, il quale non fa altro che generare un input ogni tot tempo scelto dal sistema operativo, questo input non ha nessuno scopo se non quello di generare un interrupt che possa interrompere l'esecuzione di un processo. La time-slice non piò essere troppo breve perché il context-switch ha un costo, non è istantaneo, ma nemmeno un troppo lunga altrimenti non si avrebbe l'idea di un sistema fluido ed istantaneo.&lt;br /&gt;
&lt;br /&gt;
Esistono anche gli scheduler che assegnano una priorità ai processi ed eseguono prima quelli con priorità più elevata. Questi scheduler esistono sia con priorità statica, che con priorità dinamica, ma potendo generare starvation, quelli con priorità dinamica possono utilizzare l'aging, ossia un processo con priorità bassa aumenta di volta in volta la sua priorità fino a diventare massima per poi, una volta eseguito, tornare alla priorità originaria.&lt;br /&gt;
Un po' più complessi sono gli scheduler con classi di priorità, in cui la coda ready è divisa in diverse sottocode, una per ogni classe di processi. Gli scheduler multilivello utilizzano sempre le classi di priorità ma assegnano ad ogni classe delle politiche diverse, adatte per i processi che contiene.&lt;br /&gt;
&lt;br /&gt;
Per applicazioni particolari, in cui la correttezza del programma dipende da tempo entro i quale viene data la risposta, esistono gli scheduler real-time.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 9 marzo 2018 ==&lt;br /&gt;
Esercizi su monitor e semafori degli esami di gennaio e febbraio 2018.&lt;br /&gt;
&lt;br /&gt;
Testo:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=c&amp;gt;&lt;br /&gt;
Monitor bridge&lt;br /&gt;
	boolean bridge_is_down&lt;br /&gt;
	int crossingCars&lt;br /&gt;
	int crossingBoat&lt;br /&gt;
	int waitingCars&lt;br /&gt;
	int waitingBoats&lt;br /&gt;
	condition ok2cars&lt;br /&gt;
	condition ok2boats&lt;br /&gt;
&lt;br /&gt;
procedure_entry car_enter(direction)&lt;br /&gt;
	if(crossingBoat || waitingBoats &amp;gt; 0){&lt;br /&gt;
		waitingCars++&lt;br /&gt;
		ok2cars.wait()&lt;br /&gt;
		waitingCars--&lt;br /&gt;
	}&lt;br /&gt;
	bridge_is_down = true&lt;br /&gt;
	crossingCars++&lt;br /&gt;
	ok2cars.signal()&lt;br /&gt;
&lt;br /&gt;
procedure_entry car_exit(direction)&lt;br /&gt;
	crossingCars--&lt;br /&gt;
	if(crossingCars == 0) ok2boat.signal()&lt;br /&gt;
&lt;br /&gt;
procedure_entry boat_enter(direction)&lt;br /&gt;
	if(crossingCars &amp;gt; 0 || crossingBoat &amp;gt; 0){&lt;br /&gt;
		waitingBoats++&lt;br /&gt;
		ok2boat.wait()&lt;br /&gt;
		waitingBoats--&lt;br /&gt;
	}&lt;br /&gt;
	bridge_is_down = false&lt;br /&gt;
	crossingBoat++&lt;br /&gt;
&lt;br /&gt;
procedure_entry boat_exit(direction)&lt;br /&gt;
	crossingBoat--&lt;br /&gt;
	ok2cars.signal()&lt;br /&gt;
	if(crossingCars == 0) ok2boat.signal()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Testo: implementare, utilizzando semafori ordinari, i semafori ternari&lt;br /&gt;
&amp;lt;syntaxhighlight lang=c&amp;gt;&lt;br /&gt;
class ternarySemaphore&lt;br /&gt;
&lt;br /&gt;
	semaphore pos&lt;br /&gt;
	semaphore neg&lt;br /&gt;
	&lt;br /&gt;
	void ternarySemaphore(int init)&lt;br /&gt;
	void P(void)&lt;br /&gt;
	void V(void)&lt;br /&gt;
&lt;br /&gt;
	void ternarySemaphore(void init)&lt;br /&gt;
		// init is -1 or 1&lt;br /&gt;
		pos = new semaphore(1 + init)&lt;br /&gt;
		neg = new semaphore(1 - init)&lt;br /&gt;
&lt;br /&gt;
	void P(void)&lt;br /&gt;
		pos.P()&lt;br /&gt;
		neg.V()&lt;br /&gt;
&lt;br /&gt;
	void V(void)&lt;br /&gt;
		neg.P()&lt;br /&gt;
		pos.V()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Soluzione alternativa, meno di stile ma ugualmente funzionante&lt;br /&gt;
&amp;lt;syntaxhighlight lang=c&amp;gt;&lt;br /&gt;
class ternarySemaphore&lt;br /&gt;
&lt;br /&gt;
	semaphore queue&lt;br /&gt;
	semaphore mutex&lt;br /&gt;
	int value&lt;br /&gt;
&lt;br /&gt;
	void ternarySemaphore(int init)&lt;br /&gt;
	void P(void)&lt;br /&gt;
	void V(void)&lt;br /&gt;
&lt;br /&gt;
	void ternarySemaphore(void init)&lt;br /&gt;
		mutex = new semaphore(1)&lt;br /&gt;
		value = init&lt;br /&gt;
		queue = new sem_queue&lt;br /&gt;
&lt;br /&gt;
	void P(void)&lt;br /&gt;
		mutex.P()&lt;br /&gt;
		if(value == 1 &amp;amp;&amp;amp; !queue.empty()){&lt;br /&gt;
			s = queue.dequeue()&lt;br /&gt;
			s.V()&lt;br /&gt;
		}else{&lt;br /&gt;
			if(value == -1){&lt;br /&gt;
				s = new semaphore(0)&lt;br /&gt;
				queue.enqueue(s)&lt;br /&gt;
				mutex.V()&lt;br /&gt;
				s.P()&lt;br /&gt;
			}else{&lt;br /&gt;
				value--&lt;br /&gt;
				mutex.V()&lt;br /&gt;
			}&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
	void V(void)&lt;br /&gt;
		mutex.P()&lt;br /&gt;
		if(value == -1 &amp;amp;&amp;amp; !queue.empty()){&lt;br /&gt;
			s = queue.dequeue()&lt;br /&gt;
			s.V()&lt;br /&gt;
		}else{&lt;br /&gt;
			if(value == -1){&lt;br /&gt;
				s = new semaphore(0)&lt;br /&gt;
				queue.enqueue(s)&lt;br /&gt;
				mutex.V()&lt;br /&gt;
				s.P()&lt;br /&gt;
				free(s)&lt;br /&gt;
			}else{&lt;br /&gt;
				value++&lt;br /&gt;
				mutex.V()&lt;br /&gt;
			}&lt;br /&gt;
		}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Lezione del 14 marzo 2018 ==&lt;br /&gt;
'''RISORSE'''&lt;br /&gt;
&lt;br /&gt;
Cosa intendiamo per risorsa? Quali sono i problemi comuni e le tecniche per risolverli?&amp;lt;br&amp;gt;&lt;br /&gt;
Nel frattempo definiamo come '''CLASSI DI RISORSE''' l'insieme formato da risorse tra loro equivalenti (byte della memoria, stampanti dello stesso tipo, etc.).&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Una prima distinzione può essere fatta tra risorse ad assegnazione '''''statica''''' e ad assegnazione '''''dinamica''''': le prime, una volta assegnate, non possono essere più prelevate finché il processo che le possiede non termina. Nel secondo caso, invece, l'assegnazione può mutare nel tempo.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Si può distinguere il tipo di richiesta che viene fatta alla risorsa: '''''singola''''' o '''''multipla''''', '''''bloccante''''' o '''''non bloccante'''''. Intuitivamente, la prima distinzione si riferisce alla molteplicità di richieste che la risorsa può eseguire nello stesso momento mentre, la seconda distinzione, si riferisce al comportamento della richiesta.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Altre distinzioni possono essere fatte tra risorse '''''non condivisibili''''' (dette anche seriali) e risorse '''''condivisibili''''' (o non seriali), tra risorsa '''''non pre-emptible''''' e '''''pre-emptible'''''. Una risorsa può essere pre-emptible se e solo se non ha uno stato interno o se lo stato può essere salvato per poi essere ripristinato.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Può accadere che un processo A effettui una ''richiesta bloccante'' ad una ''risorsa non-preemptible'' e ''non condivisibile'' che al momento è utilizzata da un qualche processo B. B, a sua volta, fa la stessa cosa ma, con una risorsa in possesso di A. Cosa accade?&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
'''DEADLOCK'''&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Le condizioni necessarie affinché un deadlock si verifichi sono:&lt;br /&gt;
*'''RICHIESTA BLOCCANTE'''&lt;br /&gt;
*'''RISORSA NON CONDIVISIBILE'''&lt;br /&gt;
*'''RISORSA NON-PREEMPITIBLE'''&lt;br /&gt;
*'''ATTESA CIRCOLARE'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
work in progress&lt;br /&gt;
&lt;br /&gt;
== Lezione del 15 marzo 2018 ==&lt;br /&gt;
Nessuna lezione.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 16 marzo 2018 ==&lt;br /&gt;
== Lezione del 21 marzo 2018 ==&lt;br /&gt;
== Lezione del 22 marzo 2018 ==&lt;br /&gt;
== Lezione del 23 marzo 2018 ==&lt;br /&gt;
== Lezione del 28 marzo 2018 ==&lt;br /&gt;
== Lezione del 29 marzo 2018 ==&lt;br /&gt;
== Lezione del 30 marzo 2018 ==&lt;/div&gt;</summary>
		<author><name>Matt</name></author>
	</entry>
	<entry>
		<id>https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_II_semestre&amp;diff=2178</id>
		<title>Lezioni Anno Accademico 2017/18 II semestre</title>
		<link rel="alternate" type="text/html" href="https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_II_semestre&amp;diff=2178"/>
		<updated>2018-03-20T23:24:18Z</updated>

		<summary type="html">&lt;p&gt;Matt: /* Lezione del 14 marzo 2018 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Lezione del 28 febbraio 2018 ==&lt;br /&gt;
Inizia oggi la seconda parte del corso di Sistemi Operativi che si terrà dal 28/02/18 al 25/05/18.&amp;lt;br&amp;gt;&lt;br /&gt;
Le lezioni si svolgeranno, il mercoledì e il giovedì, in aula E1. Il venerdì invece ci sposteremo in M1.&amp;lt;br&amp;gt;&lt;br /&gt;
La suddivisione degli argomenti trattati sarà: '''mercoledì''' teoria, '''giovedì''' tendenzialmente parte progettuale (potrebbe iniziare con un po' di teoria) e '''venerdì''', data la scarsa dotazione dell'aula assegnataci, verranno svolti esercizi scritti in preparazione all'esame.&lt;br /&gt;
&lt;br /&gt;
Nel corso della lezione ci siamo chiesti, riprendendo un po' il filo logico delle lezioni dello scorso semestre, quali siano i servizi che ci aspettiamo da un sistema operativo.&amp;lt;br&amp;gt;&lt;br /&gt;
Questa volta, tuttavia, non li analizzeremo dal punto di vista dell'utilizzatore, ma da un punto di vista interno al sistema stesso.&amp;lt;br&amp;gt;&lt;br /&gt;
I servizi elencati sono stati:&lt;br /&gt;
* Scheduling (Gestione CPU)&lt;br /&gt;
* Gestione dei processi&lt;br /&gt;
* Gestione della memoria primaria&lt;br /&gt;
* Gestione dei device di I/O&lt;br /&gt;
* Gestione della memoria secondaria&lt;br /&gt;
* Filesystem&lt;br /&gt;
* Networking&lt;br /&gt;
* Protezione&lt;br /&gt;
* Shell&lt;br /&gt;
&lt;br /&gt;
Come in tutta l'informatica spesso succede, la complessità di questi servizi viene gestita tramite la creazione di livelli di astrazione. &amp;lt;br&amp;gt;&lt;br /&gt;
Il livello zero di astrazione, quello più basso, sarà l''''hardware''' che noi dovremo considerare come un linguaggio (l'ISA del processore).&amp;lt;br&amp;gt;&lt;br /&gt;
Seguito poi da due blocchi:&lt;br /&gt;
* '''Kernel'''&lt;br /&gt;
* '''Utente'''&lt;br /&gt;
&lt;br /&gt;
Cosa va incluso nel kernel di un S.O.? Cosa nella parte utente? Meglio massimizzare o minimizzare le operazioni nello livello kernel?&amp;lt;br&amp;gt; '''KERNEL MONOLITICO''' O '''MICRO-KERNEL'''?&lt;br /&gt;
&lt;br /&gt;
I kernel monolitici (tipo Linux) hanno il vantaggio di essere leggermente più performanti, mentre i microkernel (che presentano più livelli di astrazione) sono molto più flessibili e facili da manutenere.&lt;br /&gt;
Ad esempio, nei microkernel un bug non obbliga necessariamente a riavviare la macchina perché è possibile riavviare il singolo &amp;quot;modulo&amp;quot; che presenta l'errore.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 1 marzo 2018 ==&lt;br /&gt;
La lezione tace come concordato con gli studenti.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 2 marzo 2018 ==&lt;br /&gt;
La lezione tace causa chiusura dell'intero Ateneo per condizioni climatiche avverse.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 7 marzo 2018 ==&lt;br /&gt;
Tutto incomincia dal boot, la fase che carica il kernel, la quale termina subito dopo la creazione del primo processo (init) tramite la compilazione della tabella (come si vede sotto) che verrà passata allo scheduler. Una volta caricato, il kernel ha davanti una gradissima diversità di modelli hardware, ma come utilizzare tutti questi dispositivi hardware? Una prima soluzione sarebbe quella di includere nel kernel tutti i relativi device driver, tuttavia il kernel diventerbbe davvero corposo, a meno che non si crei un kernel ad hoc per una specifica macchina. Un'altra soluzione (quella comunemente adottata) utilizza i moduli. I moduli vengono caricati dal kernel all'occorrenza senza essere inseriti direttamente in esso (non è quindi necessaria la ricompilazione).&lt;br /&gt;
Tuttavia con quest'ultima soluzione si viene a creare un dilemma, ovvero:&amp;lt;br&amp;gt;&lt;br /&gt;
Chi carica i moduli? Il kernel.&amp;lt;br&amp;gt;&lt;br /&gt;
Chi carica il kernel? Il boot tramite moduli.&lt;br /&gt;
&lt;br /&gt;
In verità non esiste solo il kernel ma anche un filesystem che contiene i moduli chiamato &amp;quot;init.rd&amp;quot; (inutile se il kernel è configurato per una specifica macchina).&lt;br /&gt;
Arrivati a questo punto ci si chiede: come si compila un kernel? Prima di tutto occorrono i sorgenti (quelli di linux sono reperibili su https://www.kernel.org). Una volta scaricati, si deve configurare il file “.config” che sostanzialmente descrive le specifiche del kernel. Questo file lo si può configurare a mano o tramite il “menuconfig” che dovrà essere prima di tutto opportunatamente compilato. All'interno del menuconfig tra le altre cose c'è una lista di tutti i device driver supportati e possono essere spuntati con:  * (driver inserito nel kernel) o M (creazione del relativo modulo). Una volta configurato il “.config” non resta che compilare il kernel per la macchina ospite o per una specifica architettura. Ad esempio:&lt;br /&gt;
&lt;br /&gt;
wget link_of_kernel_source #scarica i sorgenti&amp;lt;br&amp;gt;&lt;br /&gt;
tar xf source_compressed_file # decomprime i relativi sorgenti, in questo caso dal file.tar.xz&amp;lt;br&amp;gt;&lt;br /&gt;
cd linux #entra nella directory dei sorgenti&amp;lt;br&amp;gt;&lt;br /&gt;
(Di regola esiste già un .config standard ma per configurarlo)&amp;lt;br&amp;gt;&lt;br /&gt;
make menuconfig&amp;lt;br&amp;gt;&lt;br /&gt;
make #compila il kernel per la macchina ospite&amp;lt;br&amp;gt;&lt;br /&gt;
(make -arch=armhf, compila secondo l'architettura dei processori armhf)&amp;lt;br&amp;gt;&lt;br /&gt;
( make -j n, compila il kernel per la macchina ospite utilizzando i suoi n-1 core)&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Una volta compilato l'immagine del kernel si troverà in arch/nome_architettura/boot/bzImage.&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ora si ricordi la differenza tra processo e thread: Il processo è l'entità titolare delle risorse, mentre il thread è la componente sequenziale dell'elaborazione.&lt;br /&gt;
Nei sistemi monothread il concetto di thread si sovrappone al concetto di processo ma in linea generale un processo può avere anche più di un thread. Ma quali sono le caratteristiche principali dei processi e dei thread?&lt;br /&gt;
&lt;br /&gt;
&amp;lt;table&amp;gt;&lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;Processo (PCB)&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Thread (TCB)&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;ID (self, parent,child etc)&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;ID&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;Info sulla memoria (puntatore alla tabella dei segmenti)&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Stato del processore&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;File aperti&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Stato: Ready, Running,Block&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;Ownership&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Puntatori&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;Interprocess comunication&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;     &lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;Informazioni sull'accounting &amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;/table&amp;gt;                          &lt;br /&gt;
&lt;br /&gt;
Modo Kernel e Modo User&amp;lt;br&amp;gt;&lt;br /&gt;
Si definisce Mode Switch il passaggio da Kernel Mode a User Mode e viceversa (esiste tale differenza per motivi di sicurrenzza). Attenzione però a non confondere la kernel mode con i permessi di root (root è l'utente con più permessi in assoluto ma è comunque gestito in user mode).&lt;br /&gt;
Si definisce Context Switch il passaggio di processi. Ad esempio un processo A in user mode si ferma e passa il controllo al trap che tramite lo scheduler chiama un altro processo.&lt;br /&gt;
Da qui si capische che è lo scheduler a decidere il prossimo processo da mandare avanti (tramite la Ready Queue). Un tipico scheduler è lo scheduler FIFO non preemptive (ossia il processo in esecuzione utilizzerà il processore finché non passerà allo stato di wait o termina l'esecuzione) per sistemi batch.&lt;br /&gt;
I thread posso essere creati sia in modo kernel che in modo user.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 8 marzo 2018 ==&lt;br /&gt;
Lo scheduler è quella parte del sistema operativo che si occupa di assegnare le risorse (specialmente il processore) ai vari processi, decidendone l'ordine e il tempo di esecuzione. Di scheduler ne esistono di 2 tipi; quelli non-preemptive (o cooperative) dove il context switch avviene solo se il processo in esecuzione fa I/O o una system call bloccante passando allo stato di wait o termina e quelli preemptive quando il processo in esecuzione può passare allo stato ready mediante un interrupt.&lt;br /&gt;
&lt;br /&gt;
I criteri con cui vengono scelti gli scheduler sono:&lt;br /&gt;
&lt;br /&gt;
* Throughput: la quantità di processi completati per unità di tempo&amp;lt;br&amp;gt;&lt;br /&gt;
* Turnaround: il tempo che intercorre da quando un processo entra nella coda ready fino alla fine della sua esecuzione&amp;lt;br&amp;gt;&lt;br /&gt;
* Percentuale di utilizzo del processore&amp;lt;br&amp;gt;&lt;br /&gt;
* Tempo di attesa: quanto tempo tempo un processo rimane nella coda ready prima di iniziare la sua esecuzione&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Partendo dal più semplice da realizzare, troviamo lo scheduler FCFS (First Come, First Served), il quale usa una politica FIFO quindi i processi vengono eseguiti in ordine di arrivo, è cooperative e non è per nulla efficiente.&lt;br /&gt;
&lt;br /&gt;
Uno scheduler più avanzato, ma sempre cooperative, è lo SJF (Shortest Job First) che da la priorità ai processi con meno tempo di CPU burst (tempo di utilizzo del processore). Il tempo di utilizzo della CPU viene stimato basandosi sulle precedenti esecuzioni e calcolato mediante la media esponenziale dando maggior peso alle esecuzioni più recenti.&lt;br /&gt;
Una variante preemptive del SJF è lo Shortest-Remeaning-Time First il quale fa passare dallo stato running a ready un processo se nel frattempo arriva nella coda ready un altro processo con un tempo di CPU burst più breve. Il problema principale però è che entrambi possono generare starvation.&lt;br /&gt;
&lt;br /&gt;
Lo scheduler Round-Robin, di tipo preemptive, fa uso della Time-Slice (ovvero un quanto di tempo). I processi vengono eseguiti in ordine di arrivo ma possono rilasciare il processore anticipatamente nel caso in cui finisca la time-slice a loro disposizione. Per implementare questo scheduler, tuttavia, è necessario un supporto hardware chiamato &amp;quot;interval time&amp;quot;, il quale non fa altro che generare un input ogni tot tempo scelto dal sistema operativo, questo input non ha nessuno scopo se non quello di generare un interrupt che possa interrompere l'esecuzione di un processo. La time-slice non piò essere troppo breve perché il context-switch ha un costo, non è istantaneo, ma nemmeno un troppo lunga altrimenti non si avrebbe l'idea di un sistema fluido ed istantaneo.&lt;br /&gt;
&lt;br /&gt;
Esistono anche gli scheduler che assegnano una priorità ai processi ed eseguono prima quelli con priorità più elevata. Questi scheduler esistono sia con priorità statica, che con priorità dinamica, ma potendo generare starvation, quelli con priorità dinamica possono utilizzare l'aging, ossia un processo con priorità bassa aumenta di volta in volta la sua priorità fino a diventare massima per poi, una volta eseguito, tornare alla priorità originaria.&lt;br /&gt;
Un po' più complessi sono gli scheduler con classi di priorità, in cui la coda ready è divisa in diverse sottocode, una per ogni classe di processi. Gli scheduler multilivello utilizzano sempre le classi di priorità ma assegnano ad ogni classe delle politiche diverse, adatte per i processi che contiene.&lt;br /&gt;
&lt;br /&gt;
Per applicazioni particolari, in cui la correttezza del programma dipende da tempo entro i quale viene data la risposta, esistono gli scheduler real-time.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 9 marzo 2018 ==&lt;br /&gt;
Esercizi su monitor e semafori degli esami di gennaio e febbraio 2018.&lt;br /&gt;
&lt;br /&gt;
Testo:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=c&amp;gt;&lt;br /&gt;
Monitor bridge&lt;br /&gt;
	boolean bridge_is_down&lt;br /&gt;
	int crossingCars&lt;br /&gt;
	int crossingBoat&lt;br /&gt;
	int waitingCars&lt;br /&gt;
	int waitingBoats&lt;br /&gt;
	condition ok2cars&lt;br /&gt;
	condition ok2boats&lt;br /&gt;
&lt;br /&gt;
procedure_entry car_enter(direction)&lt;br /&gt;
	if(crossingBoat || waitingBoats &amp;gt; 0){&lt;br /&gt;
		waitingCars++&lt;br /&gt;
		ok2cars.wait()&lt;br /&gt;
		waitingCars--&lt;br /&gt;
	}&lt;br /&gt;
	bridge_is_down = true&lt;br /&gt;
	crossingCars++&lt;br /&gt;
	ok2cars.signal()&lt;br /&gt;
&lt;br /&gt;
procedure_entry car_exit(direction)&lt;br /&gt;
	crossingCars--&lt;br /&gt;
	if(crossingCars == 0) ok2boat.signal()&lt;br /&gt;
&lt;br /&gt;
procedure_entry boat_enter(direction)&lt;br /&gt;
	if(crossingCars &amp;gt; 0 || crossingBoat &amp;gt; 0){&lt;br /&gt;
		waitingBoats++&lt;br /&gt;
		ok2boat.wait()&lt;br /&gt;
		waitingBoats--&lt;br /&gt;
	}&lt;br /&gt;
	bridge_is_down = false&lt;br /&gt;
	crossingBoat++&lt;br /&gt;
&lt;br /&gt;
procedure_entry boat_exit(direction)&lt;br /&gt;
	crossingBoat--&lt;br /&gt;
	ok2cars.signal()&lt;br /&gt;
	if(crossingCars == 0) ok2boat.signal()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Testo: implementare, utilizzando semafori ordinari, i semafori ternari&lt;br /&gt;
&amp;lt;syntaxhighlight lang=c&amp;gt;&lt;br /&gt;
class ternarySemaphore&lt;br /&gt;
&lt;br /&gt;
	semaphore pos&lt;br /&gt;
	semaphore neg&lt;br /&gt;
	&lt;br /&gt;
	void ternarySemaphore(int init)&lt;br /&gt;
	void P(void)&lt;br /&gt;
	void V(void)&lt;br /&gt;
&lt;br /&gt;
	void ternarySemaphore(void init)&lt;br /&gt;
		// init is -1 or 1&lt;br /&gt;
		pos = new semaphore(1 + init)&lt;br /&gt;
		neg = new semaphore(1 - init)&lt;br /&gt;
&lt;br /&gt;
	void P(void)&lt;br /&gt;
		pos.P()&lt;br /&gt;
		neg.V()&lt;br /&gt;
&lt;br /&gt;
	void V(void)&lt;br /&gt;
		neg.P()&lt;br /&gt;
		pos.V()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Soluzione alternativa, meno di stile ma ugualmente funzionante&lt;br /&gt;
&amp;lt;syntaxhighlight lang=c&amp;gt;&lt;br /&gt;
class ternarySemaphore&lt;br /&gt;
&lt;br /&gt;
	semaphore queue&lt;br /&gt;
	semaphore mutex&lt;br /&gt;
	int value&lt;br /&gt;
&lt;br /&gt;
	void ternarySemaphore(int init)&lt;br /&gt;
	void P(void)&lt;br /&gt;
	void V(void)&lt;br /&gt;
&lt;br /&gt;
	void ternarySemaphore(void init)&lt;br /&gt;
		mutex = new semaphore(1)&lt;br /&gt;
		value = init&lt;br /&gt;
		queue = new sem_queue&lt;br /&gt;
&lt;br /&gt;
	void P(void)&lt;br /&gt;
		mutex.P()&lt;br /&gt;
		if(value == 1 &amp;amp;&amp;amp; !queue.empty()){&lt;br /&gt;
			s = queue.dequeue()&lt;br /&gt;
			s.V()&lt;br /&gt;
		}else{&lt;br /&gt;
			if(value == -1){&lt;br /&gt;
				s = new semaphore(0)&lt;br /&gt;
				queue.enqueue(s)&lt;br /&gt;
				mutex.V()&lt;br /&gt;
				s.P()&lt;br /&gt;
			}else{&lt;br /&gt;
				value--&lt;br /&gt;
				mutex.V()&lt;br /&gt;
			}&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
	void V(void)&lt;br /&gt;
		mutex.P()&lt;br /&gt;
		if(value == -1 &amp;amp;&amp;amp; !queue.empty()){&lt;br /&gt;
			s = queue.dequeue()&lt;br /&gt;
			s.V()&lt;br /&gt;
		}else{&lt;br /&gt;
			if(value == -1){&lt;br /&gt;
				s = new semaphore(0)&lt;br /&gt;
				queue.enqueue(s)&lt;br /&gt;
				mutex.V()&lt;br /&gt;
				s.P()&lt;br /&gt;
				free(s)&lt;br /&gt;
			}else{&lt;br /&gt;
				value++&lt;br /&gt;
				mutex.V()&lt;br /&gt;
			}&lt;br /&gt;
		}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Lezione del 14 marzo 2018 ==&lt;br /&gt;
'''RISORSE'''&lt;br /&gt;
&lt;br /&gt;
Cosa intendiamo per risorsa? Quali sono i problemi comuni e le tecniche per risolverli?&amp;lt;br&amp;gt;&lt;br /&gt;
Nel frattempo definiamo come '''CLASSI DI RISORSE''' l'insieme formato da risorse tra loro equivalenti (byte della memoria, stampanti dello stesso tipo, etc.).&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Una prima distinzione può essere fatta tra risorse ad assegnazione '''''statica''''' e ad assegnazione '''''dinamica''''': le prime, una volta assegnate, non possono essere più prelevate finché il processo che le possiede non termina. Nel secondo caso, invece, l'assegnazione può mutare nel tempo.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Si può distinguere il tipo di richiesta che viene fatta alla risorsa: '''''singola''''' o '''''multipla''''', '''''bloccante''''' o '''''non bloccante'''''. Intuitivamente, la prima distinzione si riferisce alla molteplicità di richieste che la risorsa può eseguire nello stesso momento mentre, la seconda distinzione, si riferisce al comportamento della richiesta.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Altre distinzioni possono essere fatte tra risorse '''''non condivisibili''''' (dette anche seriali) e risorse '''''condivisibili''''' (o non seriali), tra risorsa '''''non pre-emptible''''' e '''''pre-emptible'''''. Una risorsa può essere pre-emptible se e solo se non ha uno stato interno o se lo stato può essere salvato per poi essere ripristinato.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Può accadere che un processo A effettui una ''richiesta bloccante'' ad una ''risorsa non-preemptible'' e ''non condivisibile'' che al momento è utilizzata da un qualche processo B. B, a sua volta, fa la stessa cosa ma, con una risorsa in possesso di A. Cosa accade?&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
'''DEADLOCK'''&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Le condizioni necessarie affinché un deadlock si verifichi sono:&lt;br /&gt;
*'''RICHIESTA BLOCCANTE'''&lt;br /&gt;
*'''RISORSA NON CONDIVISIBILE'''&lt;br /&gt;
*'''RISORSA NON-PREEMPITIBLE'''&lt;br /&gt;
*'''ATTESA CIRCOLARE'''&lt;br /&gt;
&lt;br /&gt;
== Lezione del 15 marzo 2018 ==&lt;br /&gt;
Nessuna lezione.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 16 marzo 2018 ==&lt;br /&gt;
== Lezione del 21 marzo 2018 ==&lt;br /&gt;
== Lezione del 22 marzo 2018 ==&lt;br /&gt;
== Lezione del 23 marzo 2018 ==&lt;br /&gt;
== Lezione del 28 marzo 2018 ==&lt;br /&gt;
== Lezione del 29 marzo 2018 ==&lt;br /&gt;
== Lezione del 30 marzo 2018 ==&lt;/div&gt;</summary>
		<author><name>Matt</name></author>
	</entry>
	<entry>
		<id>https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_II_semestre&amp;diff=2177</id>
		<title>Lezioni Anno Accademico 2017/18 II semestre</title>
		<link rel="alternate" type="text/html" href="https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_II_semestre&amp;diff=2177"/>
		<updated>2018-03-20T21:37:50Z</updated>

		<summary type="html">&lt;p&gt;Matt: /* Lezione del 9 marzo 2018 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Lezione del 28 febbraio 2018 ==&lt;br /&gt;
Inizia oggi la seconda parte del corso di Sistemi Operativi che si terrà dal 28/02/18 al 25/05/18.&amp;lt;br&amp;gt;&lt;br /&gt;
Le lezioni si svolgeranno, il mercoledì e il giovedì, in aula E1. Il venerdì invece ci sposteremo in M1.&amp;lt;br&amp;gt;&lt;br /&gt;
La suddivisione degli argomenti trattati sarà: '''mercoledì''' teoria, '''giovedì''' tendenzialmente parte progettuale (potrebbe iniziare con un po' di teoria) e '''venerdì''', data la scarsa dotazione dell'aula assegnataci, verranno svolti esercizi scritti in preparazione all'esame.&lt;br /&gt;
&lt;br /&gt;
Nel corso della lezione ci siamo chiesti, riprendendo un po' il filo logico delle lezioni dello scorso semestre, quali siano i servizi che ci aspettiamo da un sistema operativo.&amp;lt;br&amp;gt;&lt;br /&gt;
Questa volta, tuttavia, non li analizzeremo dal punto di vista dell'utilizzatore, ma da un punto di vista interno al sistema stesso.&amp;lt;br&amp;gt;&lt;br /&gt;
I servizi elencati sono stati:&lt;br /&gt;
* Scheduling (Gestione CPU)&lt;br /&gt;
* Gestione dei processi&lt;br /&gt;
* Gestione della memoria primaria&lt;br /&gt;
* Gestione dei device di I/O&lt;br /&gt;
* Gestione della memoria secondaria&lt;br /&gt;
* Filesystem&lt;br /&gt;
* Networking&lt;br /&gt;
* Protezione&lt;br /&gt;
* Shell&lt;br /&gt;
&lt;br /&gt;
Come in tutta l'informatica spesso succede, la complessità di questi servizi viene gestita tramite la creazione di livelli di astrazione. &amp;lt;br&amp;gt;&lt;br /&gt;
Il livello zero di astrazione, quello più basso, sarà l''''hardware''' che noi dovremo considerare come un linguaggio (l'ISA del processore).&amp;lt;br&amp;gt;&lt;br /&gt;
Seguito poi da due blocchi:&lt;br /&gt;
* '''Kernel'''&lt;br /&gt;
* '''Utente'''&lt;br /&gt;
&lt;br /&gt;
Cosa va incluso nel kernel di un S.O.? Cosa nella parte utente? Meglio massimizzare o minimizzare le operazioni nello livello kernel?&amp;lt;br&amp;gt; '''KERNEL MONOLITICO''' O '''MICRO-KERNEL'''?&lt;br /&gt;
&lt;br /&gt;
I kernel monolitici (tipo Linux) hanno il vantaggio di essere leggermente più performanti, mentre i microkernel (che presentano più livelli di astrazione) sono molto più flessibili e facili da manutenere.&lt;br /&gt;
Ad esempio, nei microkernel un bug non obbliga necessariamente a riavviare la macchina perché è possibile riavviare il singolo &amp;quot;modulo&amp;quot; che presenta l'errore.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 1 marzo 2018 ==&lt;br /&gt;
La lezione tace come concordato con gli studenti.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 2 marzo 2018 ==&lt;br /&gt;
La lezione tace causa chiusura dell'intero Ateneo per condizioni climatiche avverse.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 7 marzo 2018 ==&lt;br /&gt;
Tutto incomincia dal boot, la fase che carica il kernel, la quale termina subito dopo la creazione del primo processo (init) tramite la compilazione della tabella (come si vede sotto) che verrà passata allo scheduler. Una volta caricato, il kernel ha davanti una gradissima diversità di modelli hardware, ma come utilizzare tutti questi dispositivi hardware? Una prima soluzione sarebbe quella di includere nel kernel tutti i relativi device driver, tuttavia il kernel diventerbbe davvero corposo, a meno che non si crei un kernel ad hoc per una specifica macchina. Un'altra soluzione (quella comunemente adottata) utilizza i moduli. I moduli vengono caricati dal kernel all'occorrenza senza essere inseriti direttamente in esso (non è quindi necessaria la ricompilazione).&lt;br /&gt;
Tuttavia con quest'ultima soluzione si viene a creare un dilemma, ovvero:&amp;lt;br&amp;gt;&lt;br /&gt;
Chi carica i moduli? Il kernel.&amp;lt;br&amp;gt;&lt;br /&gt;
Chi carica il kernel? Il boot tramite moduli.&lt;br /&gt;
&lt;br /&gt;
In verità non esiste solo il kernel ma anche un filesystem che contiene i moduli chiamato &amp;quot;init.rd&amp;quot; (inutile se il kernel è configurato per una specifica macchina).&lt;br /&gt;
Arrivati a questo punto ci si chiede: come si compila un kernel? Prima di tutto occorrono i sorgenti (quelli di linux sono reperibili su https://www.kernel.org). Una volta scaricati, si deve configurare il file “.config” che sostanzialmente descrive le specifiche del kernel. Questo file lo si può configurare a mano o tramite il “menuconfig” che dovrà essere prima di tutto opportunatamente compilato. All'interno del menuconfig tra le altre cose c'è una lista di tutti i device driver supportati e possono essere spuntati con:  * (driver inserito nel kernel) o M (creazione del relativo modulo). Una volta configurato il “.config” non resta che compilare il kernel per la macchina ospite o per una specifica architettura. Ad esempio:&lt;br /&gt;
&lt;br /&gt;
wget link_of_kernel_source #scarica i sorgenti&amp;lt;br&amp;gt;&lt;br /&gt;
tar xf source_compressed_file # decomprime i relativi sorgenti, in questo caso dal file.tar.xz&amp;lt;br&amp;gt;&lt;br /&gt;
cd linux #entra nella directory dei sorgenti&amp;lt;br&amp;gt;&lt;br /&gt;
(Di regola esiste già un .config standard ma per configurarlo)&amp;lt;br&amp;gt;&lt;br /&gt;
make menuconfig&amp;lt;br&amp;gt;&lt;br /&gt;
make #compila il kernel per la macchina ospite&amp;lt;br&amp;gt;&lt;br /&gt;
(make -arch=armhf, compila secondo l'architettura dei processori armhf)&amp;lt;br&amp;gt;&lt;br /&gt;
( make -j n, compila il kernel per la macchina ospite utilizzando i suoi n-1 core)&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Una volta compilato l'immagine del kernel si troverà in arch/nome_architettura/boot/bzImage.&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ora si ricordi la differenza tra processo e thread: Il processo è l'entità titolare delle risorse, mentre il thread è la componente sequenziale dell'elaborazione.&lt;br /&gt;
Nei sistemi monothread il concetto di thread si sovrappone al concetto di processo ma in linea generale un processo può avere anche più di un thread. Ma quali sono le caratteristiche principali dei processi e dei thread?&lt;br /&gt;
&lt;br /&gt;
&amp;lt;table&amp;gt;&lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;Processo (PCB)&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Thread (TCB)&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;ID (self, parent,child etc)&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;ID&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;Info sulla memoria (puntatore alla tabella dei segmenti)&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Stato del processore&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;File aperti&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Stato: Ready, Running,Block&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;Ownership&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Puntatori&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;Interprocess comunication&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;     &lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;Informazioni sull'accounting &amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;/table&amp;gt;                          &lt;br /&gt;
&lt;br /&gt;
Modo Kernel e Modo User&amp;lt;br&amp;gt;&lt;br /&gt;
Si definisce Mode Switch il passaggio da Kernel Mode a User Mode e viceversa (esiste tale differenza per motivi di sicurrenzza). Attenzione però a non confondere la kernel mode con i permessi di root (root è l'utente con più permessi in assoluto ma è comunque gestito in user mode).&lt;br /&gt;
Si definisce Context Switch il passaggio di processi. Ad esempio un processo A in user mode si ferma e passa il controllo al trap che tramite lo scheduler chiama un altro processo.&lt;br /&gt;
Da qui si capische che è lo scheduler a decidere il prossimo processo da mandare avanti (tramite la Ready Queue). Un tipico scheduler è lo scheduler FIFO non preemptive (ossia il processo in esecuzione utilizzerà il processore finché non passerà allo stato di wait o termina l'esecuzione) per sistemi batch.&lt;br /&gt;
I thread posso essere creati sia in modo kernel che in modo user.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 8 marzo 2018 ==&lt;br /&gt;
Lo scheduler è quella parte del sistema operativo che si occupa di assegnare le risorse (specialmente il processore) ai vari processi, decidendone l'ordine e il tempo di esecuzione. Di scheduler ne esistono di 2 tipi; quelli non-preemptive (o cooperative) dove il context switch avviene solo se il processo in esecuzione fa I/O o una system call bloccante passando allo stato di wait o termina e quelli preemptive quando il processo in esecuzione può passare allo stato ready mediante un interrupt.&lt;br /&gt;
&lt;br /&gt;
I criteri con cui vengono scelti gli scheduler sono:&lt;br /&gt;
&lt;br /&gt;
* Throughput: la quantità di processi completati per unità di tempo&amp;lt;br&amp;gt;&lt;br /&gt;
* Turnaround: il tempo che intercorre da quando un processo entra nella coda ready fino alla fine della sua esecuzione&amp;lt;br&amp;gt;&lt;br /&gt;
* Percentuale di utilizzo del processore&amp;lt;br&amp;gt;&lt;br /&gt;
* Tempo di attesa: quanto tempo tempo un processo rimane nella coda ready prima di iniziare la sua esecuzione&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Partendo dal più semplice da realizzare, troviamo lo scheduler FCFS (First Come, First Served), il quale usa una politica FIFO quindi i processi vengono eseguiti in ordine di arrivo, è cooperative e non è per nulla efficiente.&lt;br /&gt;
&lt;br /&gt;
Uno scheduler più avanzato, ma sempre cooperative, è lo SJF (Shortest Job First) che da la priorità ai processi con meno tempo di CPU burst (tempo di utilizzo del processore). Il tempo di utilizzo della CPU viene stimato basandosi sulle precedenti esecuzioni e calcolato mediante la media esponenziale dando maggior peso alle esecuzioni più recenti.&lt;br /&gt;
Una variante preemptive del SJF è lo Shortest-Remeaning-Time First il quale fa passare dallo stato running a ready un processo se nel frattempo arriva nella coda ready un altro processo con un tempo di CPU burst più breve. Il problema principale però è che entrambi possono generare starvation.&lt;br /&gt;
&lt;br /&gt;
Lo scheduler Round-Robin, di tipo preemptive, fa uso della Time-Slice (ovvero un quanto di tempo). I processi vengono eseguiti in ordine di arrivo ma possono rilasciare il processore anticipatamente nel caso in cui finisca la time-slice a loro disposizione. Per implementare questo scheduler, tuttavia, è necessario un supporto hardware chiamato &amp;quot;interval time&amp;quot;, il quale non fa altro che generare un input ogni tot tempo scelto dal sistema operativo, questo input non ha nessuno scopo se non quello di generare un interrupt che possa interrompere l'esecuzione di un processo. La time-slice non piò essere troppo breve perché il context-switch ha un costo, non è istantaneo, ma nemmeno un troppo lunga altrimenti non si avrebbe l'idea di un sistema fluido ed istantaneo.&lt;br /&gt;
&lt;br /&gt;
Esistono anche gli scheduler che assegnano una priorità ai processi ed eseguono prima quelli con priorità più elevata. Questi scheduler esistono sia con priorità statica, che con priorità dinamica, ma potendo generare starvation, quelli con priorità dinamica possono utilizzare l'aging, ossia un processo con priorità bassa aumenta di volta in volta la sua priorità fino a diventare massima per poi, una volta eseguito, tornare alla priorità originaria.&lt;br /&gt;
Un po' più complessi sono gli scheduler con classi di priorità, in cui la coda ready è divisa in diverse sottocode, una per ogni classe di processi. Gli scheduler multilivello utilizzano sempre le classi di priorità ma assegnano ad ogni classe delle politiche diverse, adatte per i processi che contiene.&lt;br /&gt;
&lt;br /&gt;
Per applicazioni particolari, in cui la correttezza del programma dipende da tempo entro i quale viene data la risposta, esistono gli scheduler real-time.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 9 marzo 2018 ==&lt;br /&gt;
Esercizi su monitor e semafori degli esami di gennaio e febbraio 2018.&lt;br /&gt;
&lt;br /&gt;
Testo:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=c&amp;gt;&lt;br /&gt;
Monitor bridge&lt;br /&gt;
	boolean bridge_is_down&lt;br /&gt;
	int crossingCars&lt;br /&gt;
	int crossingBoat&lt;br /&gt;
	int waitingCars&lt;br /&gt;
	int waitingBoats&lt;br /&gt;
	condition ok2cars&lt;br /&gt;
	condition ok2boats&lt;br /&gt;
&lt;br /&gt;
procedure_entry car_enter(direction)&lt;br /&gt;
	if(crossingBoat || waitingBoats &amp;gt; 0){&lt;br /&gt;
		waitingCars++&lt;br /&gt;
		ok2cars.wait()&lt;br /&gt;
		waitingCars--&lt;br /&gt;
	}&lt;br /&gt;
	bridge_is_down = true&lt;br /&gt;
	crossingCars++&lt;br /&gt;
	ok2cars.signal()&lt;br /&gt;
&lt;br /&gt;
procedure_entry car_exit(direction)&lt;br /&gt;
	crossingCars--&lt;br /&gt;
	if(crossingCars == 0) ok2boat.signal()&lt;br /&gt;
&lt;br /&gt;
procedure_entry boat_enter(direction)&lt;br /&gt;
	if(crossingCars &amp;gt; 0 || crossingBoat &amp;gt; 0){&lt;br /&gt;
		waitingBoats++&lt;br /&gt;
		ok2boat.wait()&lt;br /&gt;
		waitingBoats--&lt;br /&gt;
	}&lt;br /&gt;
	bridge_is_down = false&lt;br /&gt;
	crossingBoat++&lt;br /&gt;
&lt;br /&gt;
procedure_entry boat_exit(direction)&lt;br /&gt;
	crossingBoat--&lt;br /&gt;
	ok2cars.signal()&lt;br /&gt;
	if(crossingCars == 0) ok2boat.signal()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Testo: implementare, utilizzando semafori ordinari, i semafori ternari&lt;br /&gt;
&amp;lt;syntaxhighlight lang=c&amp;gt;&lt;br /&gt;
class ternarySemaphore&lt;br /&gt;
&lt;br /&gt;
	semaphore pos&lt;br /&gt;
	semaphore neg&lt;br /&gt;
	&lt;br /&gt;
	void ternarySemaphore(int init)&lt;br /&gt;
	void P(void)&lt;br /&gt;
	void V(void)&lt;br /&gt;
&lt;br /&gt;
	void ternarySemaphore(void init)&lt;br /&gt;
		// init is -1 or 1&lt;br /&gt;
		pos = new semaphore(1 + init)&lt;br /&gt;
		neg = new semaphore(1 - init)&lt;br /&gt;
&lt;br /&gt;
	void P(void)&lt;br /&gt;
		pos.P()&lt;br /&gt;
		neg.V()&lt;br /&gt;
&lt;br /&gt;
	void V(void)&lt;br /&gt;
		neg.P()&lt;br /&gt;
		pos.V()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Soluzione alternativa, meno di stile ma ugualmente funzionante&lt;br /&gt;
&amp;lt;syntaxhighlight lang=c&amp;gt;&lt;br /&gt;
class ternarySemaphore&lt;br /&gt;
&lt;br /&gt;
	semaphore queue&lt;br /&gt;
	semaphore mutex&lt;br /&gt;
	int value&lt;br /&gt;
&lt;br /&gt;
	void ternarySemaphore(int init)&lt;br /&gt;
	void P(void)&lt;br /&gt;
	void V(void)&lt;br /&gt;
&lt;br /&gt;
	void ternarySemaphore(void init)&lt;br /&gt;
		mutex = new semaphore(1)&lt;br /&gt;
		value = init&lt;br /&gt;
		queue = new sem_queue&lt;br /&gt;
&lt;br /&gt;
	void P(void)&lt;br /&gt;
		mutex.P()&lt;br /&gt;
		if(value == 1 &amp;amp;&amp;amp; !queue.empty()){&lt;br /&gt;
			s = queue.dequeue()&lt;br /&gt;
			s.V()&lt;br /&gt;
		}else{&lt;br /&gt;
			if(value == -1){&lt;br /&gt;
				s = new semaphore(0)&lt;br /&gt;
				queue.enqueue(s)&lt;br /&gt;
				mutex.V()&lt;br /&gt;
				s.P()&lt;br /&gt;
			}else{&lt;br /&gt;
				value--&lt;br /&gt;
				mutex.V()&lt;br /&gt;
			}&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
	void V(void)&lt;br /&gt;
		mutex.P()&lt;br /&gt;
		if(value == -1 &amp;amp;&amp;amp; !queue.empty()){&lt;br /&gt;
			s = queue.dequeue()&lt;br /&gt;
			s.V()&lt;br /&gt;
		}else{&lt;br /&gt;
			if(value == -1){&lt;br /&gt;
				s = new semaphore(0)&lt;br /&gt;
				queue.enqueue(s)&lt;br /&gt;
				mutex.V()&lt;br /&gt;
				s.P()&lt;br /&gt;
				free(s)&lt;br /&gt;
			}else{&lt;br /&gt;
				value++&lt;br /&gt;
				mutex.V()&lt;br /&gt;
			}&lt;br /&gt;
		}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Lezione del 14 marzo 2018 ==&lt;br /&gt;
== Lezione del 15 marzo 2018 ==&lt;br /&gt;
Nessuna lezione.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 16 marzo 2018 ==&lt;br /&gt;
== Lezione del 21 marzo 2018 ==&lt;br /&gt;
== Lezione del 22 marzo 2018 ==&lt;br /&gt;
== Lezione del 23 marzo 2018 ==&lt;br /&gt;
== Lezione del 28 marzo 2018 ==&lt;br /&gt;
== Lezione del 29 marzo 2018 ==&lt;br /&gt;
== Lezione del 30 marzo 2018 ==&lt;/div&gt;</summary>
		<author><name>Matt</name></author>
	</entry>
	<entry>
		<id>https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_II_semestre&amp;diff=2176</id>
		<title>Lezioni Anno Accademico 2017/18 II semestre</title>
		<link rel="alternate" type="text/html" href="https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_II_semestre&amp;diff=2176"/>
		<updated>2018-03-20T21:25:53Z</updated>

		<summary type="html">&lt;p&gt;Matt: /* Lezione del 9 marzo 2018 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Lezione del 28 febbraio 2018 ==&lt;br /&gt;
Inizia oggi la seconda parte del corso di Sistemi Operativi che si terrà dal 28/02/18 al 25/05/18.&amp;lt;br&amp;gt;&lt;br /&gt;
Le lezioni si svolgeranno, il mercoledì e il giovedì, in aula E1. Il venerdì invece ci sposteremo in M1.&amp;lt;br&amp;gt;&lt;br /&gt;
La suddivisione degli argomenti trattati sarà: '''mercoledì''' teoria, '''giovedì''' tendenzialmente parte progettuale (potrebbe iniziare con un po' di teoria) e '''venerdì''', data la scarsa dotazione dell'aula assegnataci, verranno svolti esercizi scritti in preparazione all'esame.&lt;br /&gt;
&lt;br /&gt;
Nel corso della lezione ci siamo chiesti, riprendendo un po' il filo logico delle lezioni dello scorso semestre, quali siano i servizi che ci aspettiamo da un sistema operativo.&amp;lt;br&amp;gt;&lt;br /&gt;
Questa volta, tuttavia, non li analizzeremo dal punto di vista dell'utilizzatore, ma da un punto di vista interno al sistema stesso.&amp;lt;br&amp;gt;&lt;br /&gt;
I servizi elencati sono stati:&lt;br /&gt;
* Scheduling (Gestione CPU)&lt;br /&gt;
* Gestione dei processi&lt;br /&gt;
* Gestione della memoria primaria&lt;br /&gt;
* Gestione dei device di I/O&lt;br /&gt;
* Gestione della memoria secondaria&lt;br /&gt;
* Filesystem&lt;br /&gt;
* Networking&lt;br /&gt;
* Protezione&lt;br /&gt;
* Shell&lt;br /&gt;
&lt;br /&gt;
Come in tutta l'informatica spesso succede, la complessità di questi servizi viene gestita tramite la creazione di livelli di astrazione. &amp;lt;br&amp;gt;&lt;br /&gt;
Il livello zero di astrazione, quello più basso, sarà l''''hardware''' che noi dovremo considerare come un linguaggio (l'ISA del processore).&amp;lt;br&amp;gt;&lt;br /&gt;
Seguito poi da due blocchi:&lt;br /&gt;
* '''Kernel'''&lt;br /&gt;
* '''Utente'''&lt;br /&gt;
&lt;br /&gt;
Cosa va incluso nel kernel di un S.O.? Cosa nella parte utente? Meglio massimizzare o minimizzare le operazioni nello livello kernel?&amp;lt;br&amp;gt; '''KERNEL MONOLITICO''' O '''MICRO-KERNEL'''?&lt;br /&gt;
&lt;br /&gt;
I kernel monolitici (tipo Linux) hanno il vantaggio di essere leggermente più performanti, mentre i microkernel (che presentano più livelli di astrazione) sono molto più flessibili e facili da manutenere.&lt;br /&gt;
Ad esempio, nei microkernel un bug non obbliga necessariamente a riavviare la macchina perché è possibile riavviare il singolo &amp;quot;modulo&amp;quot; che presenta l'errore.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 1 marzo 2018 ==&lt;br /&gt;
La lezione tace come concordato con gli studenti.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 2 marzo 2018 ==&lt;br /&gt;
La lezione tace causa chiusura dell'intero Ateneo per condizioni climatiche avverse.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 7 marzo 2018 ==&lt;br /&gt;
Tutto incomincia dal boot, la fase che carica il kernel, la quale termina subito dopo la creazione del primo processo (init) tramite la compilazione della tabella (come si vede sotto) che verrà passata allo scheduler. Una volta caricato, il kernel ha davanti una gradissima diversità di modelli hardware, ma come utilizzare tutti questi dispositivi hardware? Una prima soluzione sarebbe quella di includere nel kernel tutti i relativi device driver, tuttavia il kernel diventerbbe davvero corposo, a meno che non si crei un kernel ad hoc per una specifica macchina. Un'altra soluzione (quella comunemente adottata) utilizza i moduli. I moduli vengono caricati dal kernel all'occorrenza senza essere inseriti direttamente in esso (non è quindi necessaria la ricompilazione).&lt;br /&gt;
Tuttavia con quest'ultima soluzione si viene a creare un dilemma, ovvero:&amp;lt;br&amp;gt;&lt;br /&gt;
Chi carica i moduli? Il kernel.&amp;lt;br&amp;gt;&lt;br /&gt;
Chi carica il kernel? Il boot tramite moduli.&lt;br /&gt;
&lt;br /&gt;
In verità non esiste solo il kernel ma anche un filesystem che contiene i moduli chiamato &amp;quot;init.rd&amp;quot; (inutile se il kernel è configurato per una specifica macchina).&lt;br /&gt;
Arrivati a questo punto ci si chiede: come si compila un kernel? Prima di tutto occorrono i sorgenti (quelli di linux sono reperibili su https://www.kernel.org). Una volta scaricati, si deve configurare il file “.config” che sostanzialmente descrive le specifiche del kernel. Questo file lo si può configurare a mano o tramite il “menuconfig” che dovrà essere prima di tutto opportunatamente compilato. All'interno del menuconfig tra le altre cose c'è una lista di tutti i device driver supportati e possono essere spuntati con:  * (driver inserito nel kernel) o M (creazione del relativo modulo). Una volta configurato il “.config” non resta che compilare il kernel per la macchina ospite o per una specifica architettura. Ad esempio:&lt;br /&gt;
&lt;br /&gt;
wget link_of_kernel_source #scarica i sorgenti&amp;lt;br&amp;gt;&lt;br /&gt;
tar xf source_compressed_file # decomprime i relativi sorgenti, in questo caso dal file.tar.xz&amp;lt;br&amp;gt;&lt;br /&gt;
cd linux #entra nella directory dei sorgenti&amp;lt;br&amp;gt;&lt;br /&gt;
(Di regola esiste già un .config standard ma per configurarlo)&amp;lt;br&amp;gt;&lt;br /&gt;
make menuconfig&amp;lt;br&amp;gt;&lt;br /&gt;
make #compila il kernel per la macchina ospite&amp;lt;br&amp;gt;&lt;br /&gt;
(make -arch=armhf, compila secondo l'architettura dei processori armhf)&amp;lt;br&amp;gt;&lt;br /&gt;
( make -j n, compila il kernel per la macchina ospite utilizzando i suoi n-1 core)&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Una volta compilato l'immagine del kernel si troverà in arch/nome_architettura/boot/bzImage.&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ora si ricordi la differenza tra processo e thread: Il processo è l'entità titolare delle risorse, mentre il thread è la componente sequenziale dell'elaborazione.&lt;br /&gt;
Nei sistemi monothread il concetto di thread si sovrappone al concetto di processo ma in linea generale un processo può avere anche più di un thread. Ma quali sono le caratteristiche principali dei processi e dei thread?&lt;br /&gt;
&lt;br /&gt;
&amp;lt;table&amp;gt;&lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;Processo (PCB)&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Thread (TCB)&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;ID (self, parent,child etc)&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;ID&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;Info sulla memoria (puntatore alla tabella dei segmenti)&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Stato del processore&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;File aperti&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Stato: Ready, Running,Block&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;Ownership&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Puntatori&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;Interprocess comunication&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;     &lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;Informazioni sull'accounting &amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;/table&amp;gt;                          &lt;br /&gt;
&lt;br /&gt;
Modo Kernel e Modo User&amp;lt;br&amp;gt;&lt;br /&gt;
Si definisce Mode Switch il passaggio da Kernel Mode a User Mode e viceversa (esiste tale differenza per motivi di sicurrenzza). Attenzione però a non confondere la kernel mode con i permessi di root (root è l'utente con più permessi in assoluto ma è comunque gestito in user mode).&lt;br /&gt;
Si definisce Context Switch il passaggio di processi. Ad esempio un processo A in user mode si ferma e passa il controllo al trap che tramite lo scheduler chiama un altro processo.&lt;br /&gt;
Da qui si capische che è lo scheduler a decidere il prossimo processo da mandare avanti (tramite la Ready Queue). Un tipico scheduler è lo scheduler FIFO non preemptive (ossia il processo in esecuzione utilizzerà il processore finché non passerà allo stato di wait o termina l'esecuzione) per sistemi batch.&lt;br /&gt;
I thread posso essere creati sia in modo kernel che in modo user.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 8 marzo 2018 ==&lt;br /&gt;
Lo scheduler è quella parte del sistema operativo che si occupa di assegnare le risorse (specialmente il processore) ai vari processi, decidendone l'ordine e il tempo di esecuzione. Di scheduler ne esistono di 2 tipi; quelli non-preemptive (o cooperative) dove il context switch avviene solo se il processo in esecuzione fa I/O o una system call bloccante passando allo stato di wait o termina e quelli preemptive quando il processo in esecuzione può passare allo stato ready mediante un interrupt.&lt;br /&gt;
&lt;br /&gt;
I criteri con cui vengono scelti gli scheduler sono:&lt;br /&gt;
&lt;br /&gt;
* Throughput: la quantità di processi completati per unità di tempo&amp;lt;br&amp;gt;&lt;br /&gt;
* Turnaround: il tempo che intercorre da quando un processo entra nella coda ready fino alla fine della sua esecuzione&amp;lt;br&amp;gt;&lt;br /&gt;
* Percentuale di utilizzo del processore&amp;lt;br&amp;gt;&lt;br /&gt;
* Tempo di attesa: quanto tempo tempo un processo rimane nella coda ready prima di iniziare la sua esecuzione&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Partendo dal più semplice da realizzare, troviamo lo scheduler FCFS (First Come, First Served), il quale usa una politica FIFO quindi i processi vengono eseguiti in ordine di arrivo, è cooperative e non è per nulla efficiente.&lt;br /&gt;
&lt;br /&gt;
Uno scheduler più avanzato, ma sempre cooperative, è lo SJF (Shortest Job First) che da la priorità ai processi con meno tempo di CPU burst (tempo di utilizzo del processore). Il tempo di utilizzo della CPU viene stimato basandosi sulle precedenti esecuzioni e calcolato mediante la media esponenziale dando maggior peso alle esecuzioni più recenti.&lt;br /&gt;
Una variante preemptive del SJF è lo Shortest-Remeaning-Time First il quale fa passare dallo stato running a ready un processo se nel frattempo arriva nella coda ready un altro processo con un tempo di CPU burst più breve. Il problema principale però è che entrambi possono generare starvation.&lt;br /&gt;
&lt;br /&gt;
Lo scheduler Round-Robin, di tipo preemptive, fa uso della Time-Slice (ovvero un quanto di tempo). I processi vengono eseguiti in ordine di arrivo ma possono rilasciare il processore anticipatamente nel caso in cui finisca la time-slice a loro disposizione. Per implementare questo scheduler, tuttavia, è necessario un supporto hardware chiamato &amp;quot;interval time&amp;quot;, il quale non fa altro che generare un input ogni tot tempo scelto dal sistema operativo, questo input non ha nessuno scopo se non quello di generare un interrupt che possa interrompere l'esecuzione di un processo. La time-slice non piò essere troppo breve perché il context-switch ha un costo, non è istantaneo, ma nemmeno un troppo lunga altrimenti non si avrebbe l'idea di un sistema fluido ed istantaneo.&lt;br /&gt;
&lt;br /&gt;
Esistono anche gli scheduler che assegnano una priorità ai processi ed eseguono prima quelli con priorità più elevata. Questi scheduler esistono sia con priorità statica, che con priorità dinamica, ma potendo generare starvation, quelli con priorità dinamica possono utilizzare l'aging, ossia un processo con priorità bassa aumenta di volta in volta la sua priorità fino a diventare massima per poi, una volta eseguito, tornare alla priorità originaria.&lt;br /&gt;
Un po' più complessi sono gli scheduler con classi di priorità, in cui la coda ready è divisa in diverse sottocode, una per ogni classe di processi. Gli scheduler multilivello utilizzano sempre le classi di priorità ma assegnano ad ogni classe delle politiche diverse, adatte per i processi che contiene.&lt;br /&gt;
&lt;br /&gt;
Per applicazioni particolari, in cui la correttezza del programma dipende da tempo entro i quale viene data la risposta, esistono gli scheduler real-time.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 9 marzo 2018 ==&lt;br /&gt;
Esercizi su monitor e semafori degli esami di gennaio e febbraio 2018.&lt;br /&gt;
&lt;br /&gt;
Testo:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=c&amp;gt;&lt;br /&gt;
Monitor bridge&lt;br /&gt;
	boolean bridge_is_down&lt;br /&gt;
	int crossingCars&lt;br /&gt;
	int crossingBoat&lt;br /&gt;
	int waitingCars&lt;br /&gt;
	int waitingBoats&lt;br /&gt;
	condition ok2cars&lt;br /&gt;
	condition ok2boats&lt;br /&gt;
&lt;br /&gt;
procedure_entry car_enter(direction)&lt;br /&gt;
	if(crossingBoat || waitingBoats &amp;gt; 0){&lt;br /&gt;
		waitingCars++&lt;br /&gt;
		ok2cars.wait()&lt;br /&gt;
		waitingCars--&lt;br /&gt;
	}&lt;br /&gt;
	bridge_is_down = true&lt;br /&gt;
	crossingCars++&lt;br /&gt;
	ok2cars.signal()&lt;br /&gt;
&lt;br /&gt;
procedure_entry car_exit(direction)&lt;br /&gt;
	crossingCars--&lt;br /&gt;
	if(crossingCars == 0) ok2boat.signal()&lt;br /&gt;
&lt;br /&gt;
procedure_entry boat_enter(direction)&lt;br /&gt;
	if(crossingCars &amp;gt; 0 || crossingBoat &amp;gt; 0){&lt;br /&gt;
		waitingBoats++&lt;br /&gt;
		ok2boat.wait()&lt;br /&gt;
		waitingBoats--&lt;br /&gt;
	}&lt;br /&gt;
	bridge_is_down = false&lt;br /&gt;
	crossingBoat++&lt;br /&gt;
&lt;br /&gt;
procedure_entry boat_exit(direction)&lt;br /&gt;
	crossingBoat--&lt;br /&gt;
	ok2cars.signal()&lt;br /&gt;
	if(crossingCars == 0) ok2boat.signal()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
Testo: implementare, utilizzando semafori ordinari, i semafori ternari&lt;br /&gt;
&amp;lt;syntaxhighlight lang=c&amp;gt;&lt;br /&gt;
class ternarySemaphore&lt;br /&gt;
&lt;br /&gt;
	semaphore pos&lt;br /&gt;
	semaphore neg&lt;br /&gt;
	&lt;br /&gt;
	void ternarySemaphore(int init)&lt;br /&gt;
	void P(void)&lt;br /&gt;
	void V(void)&lt;br /&gt;
&lt;br /&gt;
	void ternarySemaphore(void init)&lt;br /&gt;
		// init is -1 or 1&lt;br /&gt;
		pos = new semaphore(1 + init)&lt;br /&gt;
		neg = new semaphore(1 - init)&lt;br /&gt;
&lt;br /&gt;
	void P(void)&lt;br /&gt;
		pos.P()&lt;br /&gt;
		neg.V()&lt;br /&gt;
&lt;br /&gt;
	void V(void)&lt;br /&gt;
		neg.P()&lt;br /&gt;
		pos.V()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Lezione del 14 marzo 2018 ==&lt;br /&gt;
== Lezione del 15 marzo 2018 ==&lt;br /&gt;
Nessuna lezione.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 16 marzo 2018 ==&lt;br /&gt;
== Lezione del 21 marzo 2018 ==&lt;br /&gt;
== Lezione del 22 marzo 2018 ==&lt;br /&gt;
== Lezione del 23 marzo 2018 ==&lt;br /&gt;
== Lezione del 28 marzo 2018 ==&lt;br /&gt;
== Lezione del 29 marzo 2018 ==&lt;br /&gt;
== Lezione del 30 marzo 2018 ==&lt;/div&gt;</summary>
		<author><name>Matt</name></author>
	</entry>
	<entry>
		<id>https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_II_semestre&amp;diff=2175</id>
		<title>Lezioni Anno Accademico 2017/18 II semestre</title>
		<link rel="alternate" type="text/html" href="https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_II_semestre&amp;diff=2175"/>
		<updated>2018-03-20T21:16:39Z</updated>

		<summary type="html">&lt;p&gt;Matt: /* Lezione del 9 marzo 2018 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Lezione del 28 febbraio 2018 ==&lt;br /&gt;
Inizia oggi la seconda parte del corso di Sistemi Operativi che si terrà dal 28/02/18 al 25/05/18.&amp;lt;br&amp;gt;&lt;br /&gt;
Le lezioni si svolgeranno, il mercoledì e il giovedì, in aula E1. Il venerdì invece ci sposteremo in M1.&amp;lt;br&amp;gt;&lt;br /&gt;
La suddivisione degli argomenti trattati sarà: '''mercoledì''' teoria, '''giovedì''' tendenzialmente parte progettuale (potrebbe iniziare con un po' di teoria) e '''venerdì''', data la scarsa dotazione dell'aula assegnataci, verranno svolti esercizi scritti in preparazione all'esame.&lt;br /&gt;
&lt;br /&gt;
Nel corso della lezione ci siamo chiesti, riprendendo un po' il filo logico delle lezioni dello scorso semestre, quali siano i servizi che ci aspettiamo da un sistema operativo.&amp;lt;br&amp;gt;&lt;br /&gt;
Questa volta, tuttavia, non li analizzeremo dal punto di vista dell'utilizzatore, ma da un punto di vista interno al sistema stesso.&amp;lt;br&amp;gt;&lt;br /&gt;
I servizi elencati sono stati:&lt;br /&gt;
* Scheduling (Gestione CPU)&lt;br /&gt;
* Gestione dei processi&lt;br /&gt;
* Gestione della memoria primaria&lt;br /&gt;
* Gestione dei device di I/O&lt;br /&gt;
* Gestione della memoria secondaria&lt;br /&gt;
* Filesystem&lt;br /&gt;
* Networking&lt;br /&gt;
* Protezione&lt;br /&gt;
* Shell&lt;br /&gt;
&lt;br /&gt;
Come in tutta l'informatica spesso succede, la complessità di questi servizi viene gestita tramite la creazione di livelli di astrazione. &amp;lt;br&amp;gt;&lt;br /&gt;
Il livello zero di astrazione, quello più basso, sarà l''''hardware''' che noi dovremo considerare come un linguaggio (l'ISA del processore).&amp;lt;br&amp;gt;&lt;br /&gt;
Seguito poi da due blocchi:&lt;br /&gt;
* '''Kernel'''&lt;br /&gt;
* '''Utente'''&lt;br /&gt;
&lt;br /&gt;
Cosa va incluso nel kernel di un S.O.? Cosa nella parte utente? Meglio massimizzare o minimizzare le operazioni nello livello kernel?&amp;lt;br&amp;gt; '''KERNEL MONOLITICO''' O '''MICRO-KERNEL'''?&lt;br /&gt;
&lt;br /&gt;
I kernel monolitici (tipo Linux) hanno il vantaggio di essere leggermente più performanti, mentre i microkernel (che presentano più livelli di astrazione) sono molto più flessibili e facili da manutenere.&lt;br /&gt;
Ad esempio, nei microkernel un bug non obbliga necessariamente a riavviare la macchina perché è possibile riavviare il singolo &amp;quot;modulo&amp;quot; che presenta l'errore.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 1 marzo 2018 ==&lt;br /&gt;
La lezione tace come concordato con gli studenti.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 2 marzo 2018 ==&lt;br /&gt;
La lezione tace causa chiusura dell'intero Ateneo per condizioni climatiche avverse.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 7 marzo 2018 ==&lt;br /&gt;
Tutto incomincia dal boot, la fase che carica il kernel, la quale termina subito dopo la creazione del primo processo (init) tramite la compilazione della tabella (come si vede sotto) che verrà passata allo scheduler. Una volta caricato, il kernel ha davanti una gradissima diversità di modelli hardware, ma come utilizzare tutti questi dispositivi hardware? Una prima soluzione sarebbe quella di includere nel kernel tutti i relativi device driver, tuttavia il kernel diventerbbe davvero corposo, a meno che non si crei un kernel ad hoc per una specifica macchina. Un'altra soluzione (quella comunemente adottata) utilizza i moduli. I moduli vengono caricati dal kernel all'occorrenza senza essere inseriti direttamente in esso (non è quindi necessaria la ricompilazione).&lt;br /&gt;
Tuttavia con quest'ultima soluzione si viene a creare un dilemma, ovvero:&amp;lt;br&amp;gt;&lt;br /&gt;
Chi carica i moduli? Il kernel.&amp;lt;br&amp;gt;&lt;br /&gt;
Chi carica il kernel? Il boot tramite moduli.&lt;br /&gt;
&lt;br /&gt;
In verità non esiste solo il kernel ma anche un filesystem che contiene i moduli chiamato &amp;quot;init.rd&amp;quot; (inutile se il kernel è configurato per una specifica macchina).&lt;br /&gt;
Arrivati a questo punto ci si chiede: come si compila un kernel? Prima di tutto occorrono i sorgenti (quelli di linux sono reperibili su https://www.kernel.org). Una volta scaricati, si deve configurare il file “.config” che sostanzialmente descrive le specifiche del kernel. Questo file lo si può configurare a mano o tramite il “menuconfig” che dovrà essere prima di tutto opportunatamente compilato. All'interno del menuconfig tra le altre cose c'è una lista di tutti i device driver supportati e possono essere spuntati con:  * (driver inserito nel kernel) o M (creazione del relativo modulo). Una volta configurato il “.config” non resta che compilare il kernel per la macchina ospite o per una specifica architettura. Ad esempio:&lt;br /&gt;
&lt;br /&gt;
wget link_of_kernel_source #scarica i sorgenti&amp;lt;br&amp;gt;&lt;br /&gt;
tar xf source_compressed_file # decomprime i relativi sorgenti, in questo caso dal file.tar.xz&amp;lt;br&amp;gt;&lt;br /&gt;
cd linux #entra nella directory dei sorgenti&amp;lt;br&amp;gt;&lt;br /&gt;
(Di regola esiste già un .config standard ma per configurarlo)&amp;lt;br&amp;gt;&lt;br /&gt;
make menuconfig&amp;lt;br&amp;gt;&lt;br /&gt;
make #compila il kernel per la macchina ospite&amp;lt;br&amp;gt;&lt;br /&gt;
(make -arch=armhf, compila secondo l'architettura dei processori armhf)&amp;lt;br&amp;gt;&lt;br /&gt;
( make -j n, compila il kernel per la macchina ospite utilizzando i suoi n-1 core)&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Una volta compilato l'immagine del kernel si troverà in arch/nome_architettura/boot/bzImage.&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ora si ricordi la differenza tra processo e thread: Il processo è l'entità titolare delle risorse, mentre il thread è la componente sequenziale dell'elaborazione.&lt;br /&gt;
Nei sistemi monothread il concetto di thread si sovrappone al concetto di processo ma in linea generale un processo può avere anche più di un thread. Ma quali sono le caratteristiche principali dei processi e dei thread?&lt;br /&gt;
&lt;br /&gt;
&amp;lt;table&amp;gt;&lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;Processo (PCB)&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Thread (TCB)&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;ID (self, parent,child etc)&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;ID&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;Info sulla memoria (puntatore alla tabella dei segmenti)&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Stato del processore&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;File aperti&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Stato: Ready, Running,Block&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;Ownership&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Puntatori&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;Interprocess comunication&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;     &lt;br /&gt;
&amp;lt;tr&amp;gt;&lt;br /&gt;
       &amp;lt;th&amp;gt;Informazioni sull'accounting &amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;&amp;lt;/th&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;/table&amp;gt;                          &lt;br /&gt;
&lt;br /&gt;
Modo Kernel e Modo User&amp;lt;br&amp;gt;&lt;br /&gt;
Si definisce Mode Switch il passaggio da Kernel Mode a User Mode e viceversa (esiste tale differenza per motivi di sicurrenzza). Attenzione però a non confondere la kernel mode con i permessi di root (root è l'utente con più permessi in assoluto ma è comunque gestito in user mode).&lt;br /&gt;
Si definisce Context Switch il passaggio di processi. Ad esempio un processo A in user mode si ferma e passa il controllo al trap che tramite lo scheduler chiama un altro processo.&lt;br /&gt;
Da qui si capische che è lo scheduler a decidere il prossimo processo da mandare avanti (tramite la Ready Queue). Un tipico scheduler è lo scheduler FIFO non preemptive (ossia il processo in esecuzione utilizzerà il processore finché non passerà allo stato di wait o termina l'esecuzione) per sistemi batch.&lt;br /&gt;
I thread posso essere creati sia in modo kernel che in modo user.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 8 marzo 2018 ==&lt;br /&gt;
Lo scheduler è quella parte del sistema operativo che si occupa di assegnare le risorse (specialmente il processore) ai vari processi, decidendone l'ordine e il tempo di esecuzione. Di scheduler ne esistono di 2 tipi; quelli non-preemptive (o cooperative) dove il context switch avviene solo se il processo in esecuzione fa I/O o una system call bloccante passando allo stato di wait o termina e quelli preemptive quando il processo in esecuzione può passare allo stato ready mediante un interrupt.&lt;br /&gt;
&lt;br /&gt;
I criteri con cui vengono scelti gli scheduler sono:&lt;br /&gt;
&lt;br /&gt;
* Throughput: la quantità di processi completati per unità di tempo&amp;lt;br&amp;gt;&lt;br /&gt;
* Turnaround: il tempo che intercorre da quando un processo entra nella coda ready fino alla fine della sua esecuzione&amp;lt;br&amp;gt;&lt;br /&gt;
* Percentuale di utilizzo del processore&amp;lt;br&amp;gt;&lt;br /&gt;
* Tempo di attesa: quanto tempo tempo un processo rimane nella coda ready prima di iniziare la sua esecuzione&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Partendo dal più semplice da realizzare, troviamo lo scheduler FCFS (First Come, First Served), il quale usa una politica FIFO quindi i processi vengono eseguiti in ordine di arrivo, è cooperative e non è per nulla efficiente.&lt;br /&gt;
&lt;br /&gt;
Uno scheduler più avanzato, ma sempre cooperative, è lo SJF (Shortest Job First) che da la priorità ai processi con meno tempo di CPU burst (tempo di utilizzo del processore). Il tempo di utilizzo della CPU viene stimato basandosi sulle precedenti esecuzioni e calcolato mediante la media esponenziale dando maggior peso alle esecuzioni più recenti.&lt;br /&gt;
Una variante preemptive del SJF è lo Shortest-Remeaning-Time First il quale fa passare dallo stato running a ready un processo se nel frattempo arriva nella coda ready un altro processo con un tempo di CPU burst più breve. Il problema principale però è che entrambi possono generare starvation.&lt;br /&gt;
&lt;br /&gt;
Lo scheduler Round-Robin, di tipo preemptive, fa uso della Time-Slice (ovvero un quanto di tempo). I processi vengono eseguiti in ordine di arrivo ma possono rilasciare il processore anticipatamente nel caso in cui finisca la time-slice a loro disposizione. Per implementare questo scheduler, tuttavia, è necessario un supporto hardware chiamato &amp;quot;interval time&amp;quot;, il quale non fa altro che generare un input ogni tot tempo scelto dal sistema operativo, questo input non ha nessuno scopo se non quello di generare un interrupt che possa interrompere l'esecuzione di un processo. La time-slice non piò essere troppo breve perché il context-switch ha un costo, non è istantaneo, ma nemmeno un troppo lunga altrimenti non si avrebbe l'idea di un sistema fluido ed istantaneo.&lt;br /&gt;
&lt;br /&gt;
Esistono anche gli scheduler che assegnano una priorità ai processi ed eseguono prima quelli con priorità più elevata. Questi scheduler esistono sia con priorità statica, che con priorità dinamica, ma potendo generare starvation, quelli con priorità dinamica possono utilizzare l'aging, ossia un processo con priorità bassa aumenta di volta in volta la sua priorità fino a diventare massima per poi, una volta eseguito, tornare alla priorità originaria.&lt;br /&gt;
Un po' più complessi sono gli scheduler con classi di priorità, in cui la coda ready è divisa in diverse sottocode, una per ogni classe di processi. Gli scheduler multilivello utilizzano sempre le classi di priorità ma assegnano ad ogni classe delle politiche diverse, adatte per i processi che contiene.&lt;br /&gt;
&lt;br /&gt;
Per applicazioni particolari, in cui la correttezza del programma dipende da tempo entro i quale viene data la risposta, esistono gli scheduler real-time.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 9 marzo 2018 ==&lt;br /&gt;
Esercizi su monitor e semafori degli esami di gennaio e febbraio 2018.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=c&amp;gt;&lt;br /&gt;
Monitor bridge&lt;br /&gt;
	boolean bridge_is_down&lt;br /&gt;
	int crossingCars&lt;br /&gt;
	int crossingBoat&lt;br /&gt;
	int waitingCars&lt;br /&gt;
	int waitingBoats&lt;br /&gt;
	condition ok2cars&lt;br /&gt;
	condition ok2boats&lt;br /&gt;
&lt;br /&gt;
procedure_entry car_enter(direction)&lt;br /&gt;
	if(crossingBoat || waitingBoats &amp;gt; 0){&lt;br /&gt;
		waitingCars++&lt;br /&gt;
		ok2cars.wait()&lt;br /&gt;
		waitingCars--&lt;br /&gt;
	}&lt;br /&gt;
	bridge_is_down = true&lt;br /&gt;
	crossingCars++&lt;br /&gt;
	ok2cars.signal()&lt;br /&gt;
&lt;br /&gt;
procedure_entry car_exit(direction)&lt;br /&gt;
	crossingCars--&lt;br /&gt;
	if(crossingCars == 0) ok2boat.signal()&lt;br /&gt;
&lt;br /&gt;
procedure_entry boat_enter(direction)&lt;br /&gt;
	if(crossingCars &amp;gt; 0 || crossingBoat &amp;gt; 0){&lt;br /&gt;
		waitingBoats++&lt;br /&gt;
		ok2boat.wait()&lt;br /&gt;
		waitingBoats--&lt;br /&gt;
	}&lt;br /&gt;
	bridge_is_down = false&lt;br /&gt;
	crossingBoat++&lt;br /&gt;
&lt;br /&gt;
procedure_entry boat_exit(direction)&lt;br /&gt;
	crossingBoat--&lt;br /&gt;
	ok2cars.signal()&lt;br /&gt;
	if(crossingCars == 0) ok2boat.signal()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Lezione del 14 marzo 2018 ==&lt;br /&gt;
== Lezione del 15 marzo 2018 ==&lt;br /&gt;
Nessuna lezione.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 16 marzo 2018 ==&lt;br /&gt;
== Lezione del 21 marzo 2018 ==&lt;br /&gt;
== Lezione del 22 marzo 2018 ==&lt;br /&gt;
== Lezione del 23 marzo 2018 ==&lt;br /&gt;
== Lezione del 28 marzo 2018 ==&lt;br /&gt;
== Lezione del 29 marzo 2018 ==&lt;br /&gt;
== Lezione del 30 marzo 2018 ==&lt;/div&gt;</summary>
		<author><name>Matt</name></author>
	</entry>
	<entry>
		<id>https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_II_semestre&amp;diff=2148</id>
		<title>Lezioni Anno Accademico 2017/18 II semestre</title>
		<link rel="alternate" type="text/html" href="https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_II_semestre&amp;diff=2148"/>
		<updated>2018-03-01T16:01:27Z</updated>

		<summary type="html">&lt;p&gt;Matt: /* Lezione del 28 febbraio 2018 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Lezione del 28 febbraio 2018 ==&lt;br /&gt;
Inizia oggi la seconda parte del corso di Sistemi Operativi che si terrà dal 28/02/18 al 25/05/18.&amp;lt;br&amp;gt;&lt;br /&gt;
Le lezioni si svolgeranno, il mercoledì e il giovedì, in aula E1. Il venerdì invece ci sposteremo in M1.&amp;lt;br&amp;gt;&lt;br /&gt;
La suddivisione degli argomenti trattati sarà: '''mercoledì''' teoria, '''giovedì''' tendenzialmente parte progettuale (potrebbe iniziare con un po' di teoria) e '''venerdì''', data la scarsa dotazione dell'aula assegnataci, verranno svolti esercizi scritti in preparazione all'esame.&lt;br /&gt;
&lt;br /&gt;
Nel corso della lezione ci siamo chiesti, riprendendo un po' il filo logico delle lezioni dello scorso semestre, quali siano i servizi che ci aspettiamo da un sistema operativo.&amp;lt;br&amp;gt;&lt;br /&gt;
Questa volta, tuttavia, non li analizzeremo dal punto di vista dell'utilizzatore, ma da un punto di vista interno al sistema stesso.&amp;lt;br&amp;gt;&lt;br /&gt;
I servizi elencati sono stati:&lt;br /&gt;
* Scheduling (Gestione CPU)&lt;br /&gt;
* Gestione dei processi&lt;br /&gt;
* Gestione della memoria primaria&lt;br /&gt;
* Gestione dei device di I/O&lt;br /&gt;
* Gestione della memoria secondaria&lt;br /&gt;
* Filesystem&lt;br /&gt;
* Networking&lt;br /&gt;
* Protezione&lt;br /&gt;
* Shell&lt;br /&gt;
&lt;br /&gt;
Come in tutta l'informatica spesso succede, la complessità di questi servizi viene gestita tramite la creazione di livelli di astrazione. &amp;lt;br&amp;gt;&lt;br /&gt;
Il livello zero di astrazione, quello più basso, sarà l''''hardware''' che noi dovremo considerare come un linguaggio (l'ISA del processore).&amp;lt;br&amp;gt;&lt;br /&gt;
Seguito poi da due blocchi:&lt;br /&gt;
* '''Kernel'''&lt;br /&gt;
* '''Utente'''&lt;br /&gt;
&lt;br /&gt;
Cosa va incluso nel kernel di un S.O.? Cosa nella parte utente? Meglio massimizzare o minimizzare le operazioni nello livello kernel?&amp;lt;br&amp;gt; '''KERNEL MONOLITICO''' O '''MICRO-KERNEL'''?&lt;br /&gt;
&lt;br /&gt;
== Lezione del 1 marzo 2018 ==&lt;br /&gt;
La lezione tace come concordato con gli studenti.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 2 marzo 2018 ==&lt;br /&gt;
La lezione tace causa chiusura dell'intero Ateneo per condizioni climatiche avverse.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 7 marzo 2018 ==&lt;br /&gt;
== Lezione del 8 marzo 2018 ==&lt;br /&gt;
== Lezione del 9 marzo 2018 ==&lt;br /&gt;
== Lezione del 14 marzo 2018 ==&lt;br /&gt;
== Lezione del 15 marzo 2018 ==&lt;br /&gt;
== Lezione del 16 marzo 2018 ==&lt;br /&gt;
== Lezione del 21 marzo 2018 ==&lt;br /&gt;
== Lezione del 22 marzo 2018 ==&lt;br /&gt;
== Lezione del 23 marzo 2018 ==&lt;br /&gt;
== Lezione del 28 marzo 2018 ==&lt;br /&gt;
== Lezione del 29 marzo 2018 ==&lt;br /&gt;
== Lezione del 30 marzo 2018 ==&lt;/div&gt;</summary>
		<author><name>Matt</name></author>
	</entry>
	<entry>
		<id>https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_II_semestre&amp;diff=2147</id>
		<title>Lezioni Anno Accademico 2017/18 II semestre</title>
		<link rel="alternate" type="text/html" href="https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_II_semestre&amp;diff=2147"/>
		<updated>2018-03-01T15:43:04Z</updated>

		<summary type="html">&lt;p&gt;Matt: /* Lezione del 2 marzo 2018 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Lezione del 28 febbraio 2018 ==&lt;br /&gt;
Inizia oggi la seconda parte del corso di Sistemi Operativi che si terrà dal 28/02/18 al 25/05/18.&amp;lt;br&amp;gt;&lt;br /&gt;
Le lezioni si svolgeranno, il mercoledì e il giovedì, in aula E1. Il venerdì invece ci sposteremo in M1.&amp;lt;br&amp;gt;&lt;br /&gt;
La suddivisione degli argomenti trattati sarà: '''mercoledì''' teoria, '''giovedì''' tendenzialmente parte progettuale (potrebbe iniziare con un po' di teoria) e '''venerdì''', data la scarsa dotazione dell'aula assegnataci, verranno svolti esercizi scritti in preparazione all'esame.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 1 marzo 2018 ==&lt;br /&gt;
La lezione tace come concordato con gli studenti.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 2 marzo 2018 ==&lt;br /&gt;
La lezione tace causa chiusura dell'intero Ateneo per condizioni climatiche avverse.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 7 marzo 2018 ==&lt;br /&gt;
== Lezione del 8 marzo 2018 ==&lt;br /&gt;
== Lezione del 9 marzo 2018 ==&lt;br /&gt;
== Lezione del 14 marzo 2018 ==&lt;br /&gt;
== Lezione del 15 marzo 2018 ==&lt;br /&gt;
== Lezione del 16 marzo 2018 ==&lt;br /&gt;
== Lezione del 21 marzo 2018 ==&lt;br /&gt;
== Lezione del 22 marzo 2018 ==&lt;br /&gt;
== Lezione del 23 marzo 2018 ==&lt;br /&gt;
== Lezione del 28 marzo 2018 ==&lt;br /&gt;
== Lezione del 29 marzo 2018 ==&lt;br /&gt;
== Lezione del 30 marzo 2018 ==&lt;/div&gt;</summary>
		<author><name>Matt</name></author>
	</entry>
	<entry>
		<id>https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_II_semestre&amp;diff=2146</id>
		<title>Lezioni Anno Accademico 2017/18 II semestre</title>
		<link rel="alternate" type="text/html" href="https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_II_semestre&amp;diff=2146"/>
		<updated>2018-03-01T15:41:36Z</updated>

		<summary type="html">&lt;p&gt;Matt: /* Lezione del 1 marzo 2018 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Lezione del 28 febbraio 2018 ==&lt;br /&gt;
Inizia oggi la seconda parte del corso di Sistemi Operativi che si terrà dal 28/02/18 al 25/05/18.&amp;lt;br&amp;gt;&lt;br /&gt;
Le lezioni si svolgeranno, il mercoledì e il giovedì, in aula E1. Il venerdì invece ci sposteremo in M1.&amp;lt;br&amp;gt;&lt;br /&gt;
La suddivisione degli argomenti trattati sarà: '''mercoledì''' teoria, '''giovedì''' tendenzialmente parte progettuale (potrebbe iniziare con un po' di teoria) e '''venerdì''', data la scarsa dotazione dell'aula assegnataci, verranno svolti esercizi scritti in preparazione all'esame.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 1 marzo 2018 ==&lt;br /&gt;
La lezione tace come concordato con gli studenti.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 2 marzo 2018 ==&lt;br /&gt;
== Lezione del 7 marzo 2018 ==&lt;br /&gt;
== Lezione del 8 marzo 2018 ==&lt;br /&gt;
== Lezione del 9 marzo 2018 ==&lt;br /&gt;
== Lezione del 14 marzo 2018 ==&lt;br /&gt;
== Lezione del 15 marzo 2018 ==&lt;br /&gt;
== Lezione del 16 marzo 2018 ==&lt;br /&gt;
== Lezione del 21 marzo 2018 ==&lt;br /&gt;
== Lezione del 22 marzo 2018 ==&lt;br /&gt;
== Lezione del 23 marzo 2018 ==&lt;br /&gt;
== Lezione del 28 marzo 2018 ==&lt;br /&gt;
== Lezione del 29 marzo 2018 ==&lt;br /&gt;
== Lezione del 30 marzo 2018 ==&lt;/div&gt;</summary>
		<author><name>Matt</name></author>
	</entry>
	<entry>
		<id>https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_II_semestre&amp;diff=2145</id>
		<title>Lezioni Anno Accademico 2017/18 II semestre</title>
		<link rel="alternate" type="text/html" href="https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_II_semestre&amp;diff=2145"/>
		<updated>2018-03-01T15:41:04Z</updated>

		<summary type="html">&lt;p&gt;Matt: /* Lezione del 28 febbraio 2018 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Lezione del 28 febbraio 2018 ==&lt;br /&gt;
Inizia oggi la seconda parte del corso di Sistemi Operativi che si terrà dal 28/02/18 al 25/05/18.&amp;lt;br&amp;gt;&lt;br /&gt;
Le lezioni si svolgeranno, il mercoledì e il giovedì, in aula E1. Il venerdì invece ci sposteremo in M1.&amp;lt;br&amp;gt;&lt;br /&gt;
La suddivisione degli argomenti trattati sarà: '''mercoledì''' teoria, '''giovedì''' tendenzialmente parte progettuale (potrebbe iniziare con un po' di teoria) e '''venerdì''', data la scarsa dotazione dell'aula assegnataci, verranno svolti esercizi scritti in preparazione all'esame.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 1 marzo 2018 ==&lt;br /&gt;
== Lezione del 2 marzo 2018 ==&lt;br /&gt;
== Lezione del 7 marzo 2018 ==&lt;br /&gt;
== Lezione del 8 marzo 2018 ==&lt;br /&gt;
== Lezione del 9 marzo 2018 ==&lt;br /&gt;
== Lezione del 14 marzo 2018 ==&lt;br /&gt;
== Lezione del 15 marzo 2018 ==&lt;br /&gt;
== Lezione del 16 marzo 2018 ==&lt;br /&gt;
== Lezione del 21 marzo 2018 ==&lt;br /&gt;
== Lezione del 22 marzo 2018 ==&lt;br /&gt;
== Lezione del 23 marzo 2018 ==&lt;br /&gt;
== Lezione del 28 marzo 2018 ==&lt;br /&gt;
== Lezione del 29 marzo 2018 ==&lt;br /&gt;
== Lezione del 30 marzo 2018 ==&lt;/div&gt;</summary>
		<author><name>Matt</name></author>
	</entry>
	<entry>
		<id>https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_II_semestre&amp;diff=2144</id>
		<title>Lezioni Anno Accademico 2017/18 II semestre</title>
		<link rel="alternate" type="text/html" href="https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_II_semestre&amp;diff=2144"/>
		<updated>2018-03-01T15:34:33Z</updated>

		<summary type="html">&lt;p&gt;Matt: Created page with &amp;quot;== Lezione del 28 febbraio 2018 == == Lezione del 1 marzo 2018 == == Lezione del 2 marzo 2018 == == Lezione del 7 marzo 2018 == == Lezione del 8 marzo 2018 == == Lezione del 9...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Lezione del 28 febbraio 2018 ==&lt;br /&gt;
== Lezione del 1 marzo 2018 ==&lt;br /&gt;
== Lezione del 2 marzo 2018 ==&lt;br /&gt;
== Lezione del 7 marzo 2018 ==&lt;br /&gt;
== Lezione del 8 marzo 2018 ==&lt;br /&gt;
== Lezione del 9 marzo 2018 ==&lt;br /&gt;
== Lezione del 14 marzo 2018 ==&lt;br /&gt;
== Lezione del 15 marzo 2018 ==&lt;br /&gt;
== Lezione del 16 marzo 2018 ==&lt;br /&gt;
== Lezione del 21 marzo 2018 ==&lt;br /&gt;
== Lezione del 22 marzo 2018 ==&lt;br /&gt;
== Lezione del 23 marzo 2018 ==&lt;br /&gt;
== Lezione del 28 marzo 2018 ==&lt;br /&gt;
== Lezione del 29 marzo 2018 ==&lt;br /&gt;
== Lezione del 30 marzo 2018 ==&lt;/div&gt;</summary>
		<author><name>Matt</name></author>
	</entry>
	<entry>
		<id>https://so.v2.cs.unibo.it/wiki/index.php?title=Main_Page&amp;diff=2143</id>
		<title>Main Page</title>
		<link rel="alternate" type="text/html" href="https://so.v2.cs.unibo.it/wiki/index.php?title=Main_Page&amp;diff=2143"/>
		<updated>2018-03-01T15:30:34Z</updated>

		<summary type="html">&lt;p&gt;Matt: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Questo &amp;amp;egrave; il Wiki del Corso di Sistemi Operativi&lt;br /&gt;
&lt;br /&gt;
== ANNO ACCADEMICO 2017/18 ==&lt;br /&gt;
&lt;br /&gt;
[[Lezioni Anno Accademico 2017/18 I semestre]]&lt;br /&gt;
&lt;br /&gt;
[[Lezioni Anno Accademico 2017/18 II semestre]]&lt;br /&gt;
&lt;br /&gt;
[[Esercizi ed Esperimenti 2017/18]]&lt;br /&gt;
&lt;br /&gt;
== ANNI PRECEDENTI ==&lt;br /&gt;
[[Materiale dell'AA 2016-17]]&lt;br /&gt;
&lt;br /&gt;
[[Materiale dell'AA 2015-16]]&lt;br /&gt;
&lt;br /&gt;
[[Materiale dell'AA 2014-15]]&lt;br /&gt;
&lt;br /&gt;
[[Materiale dell'AA 2013-14]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
Ricordate che per creare un account o quando viene richiesto di risolvere un semplice calcolo occorre ricordare quanto scritto [[qui]]&lt;/div&gt;</summary>
		<author><name>Matt</name></author>
	</entry>
	<entry>
		<id>https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_I_semestre&amp;diff=2044</id>
		<title>Lezioni Anno Accademico 2017/18 I semestre</title>
		<link rel="alternate" type="text/html" href="https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_I_semestre&amp;diff=2044"/>
		<updated>2017-10-19T22:44:17Z</updated>

		<summary type="html">&lt;p&gt;Matt: /* Lezione del 17 ottobre 2017 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;''scrivete qui idee, riassunti dei concetti espressi, commenti approfondimenti sulle lezioni.''&lt;br /&gt;
&lt;br /&gt;
== Lezione del 26 settembre 2017 ==&lt;br /&gt;
'''Titolo della lezione: W&amp;amp;#x2074; H Y'''&lt;br /&gt;
&lt;br /&gt;
* What:&lt;br /&gt;
* Who:&lt;br /&gt;
----&lt;br /&gt;
Noi studenti ed &amp;quot;entrambi&amp;quot; i professori.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* When:&lt;br /&gt;
----&lt;br /&gt;
Il corso ha durata annuale. Verrà svolto ogni martedì e venerdì dalle 15:30 alle 18:30.&lt;br /&gt;
* La lezione del martedì sarà dedicata alla programmazione concorrente;&lt;br /&gt;
* La lezione del venerdì sarà dedicata alla parte generale. Terminerà circa 15 minuti prima dell'orario stabilito.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* Where:&lt;br /&gt;
----&lt;br /&gt;
Sempre in Aula 1 Ercolani (E1): DISI, Scuole Ercolani, Mura Anteo Zamboni 2B.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* How:&lt;br /&gt;
----&lt;br /&gt;
Il corso si svolgerà tramite:&lt;br /&gt;
* Lezioni frontali in aula.&lt;br /&gt;
* Attività di laboratorio.&lt;br /&gt;
* Esercitazioni.&lt;br /&gt;
&lt;br /&gt;
Inoltre, durante l'ultimo periodo di lezioni del secondo semestre, vi sarà un periodo di ripasso del programma tramite esercizi, in vista dell'esame.&lt;br /&gt;
Durante la lezione del 3/10 sono state indicate alcune propedeuticità del corso. tra cui: programmazione, algoritmi e architettura degli elaboratori in primis.&lt;br /&gt;
Importanti sono anche i corsi di Analisi matematica e Algebra e geometria. Per farla breve, il primo anno è propedeutico al secondo.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* Why:&lt;br /&gt;
----&lt;br /&gt;
* '''Fonti e strumenti del corso:''' wiki creata di gruppo, lezioni frontali e esercitazioni.&lt;br /&gt;
**Non esiste un vero e proprio testo consigliato. Tutte le informazioni sono date DURANTE il corso.&lt;br /&gt;
** Per domande specifiche scrivere su mailing list (so@cs.unibo.it);&lt;br /&gt;
** Per esercizi, appunti e programma svolto rivolgersi al wiki (so.v2.cs.unibo.it);&lt;br /&gt;
** Per live streaming (www.cs.unibo.it/~renzo/live/).&lt;br /&gt;
* '''Modalit&amp;amp;agrave; di esame:''' esame scritto (diviso in due parti: una parte generale e una di programmazione concorrente) + progetto + orale (facoltativo), possibilità di dare solo lo scritto per ottenere un massimo di 18. La modalità per studenti in &amp;quot;difficoltà&amp;quot; è disponibile solo fino agli appelli autunnali.&lt;br /&gt;
** Si parlerà del progetto indicativamente a partire da Dicembre 2017.&lt;br /&gt;
** L'orale può essere sostenuto da chi vuole migliorare (peggiorare) il voto ottenuto, o da chi vuole ottenere la lode.&lt;br /&gt;
* '''Orario di ricevimento per il primo semestre (fino al 15/09/17):''' martedì alle 11:30.&lt;br /&gt;
* Universit&amp;amp;agrave; = docenti + studenti.&lt;br /&gt;
* Informatica = come generare informazione automatica.&lt;br /&gt;
* Hardware, Software, Elaborazione, Comunicazione, Memorizzazione, Digitale/Analogico.&lt;br /&gt;
La principale distinzione che facciamo tra '''dato''' e '''informazione''' è che il dato, da solo, è privo di significato. Se, tuttavia, viene interpretato in un particolare contesto allora può diventare informazione significativa per chi sta interpretando i dati.&lt;br /&gt;
&lt;br /&gt;
Un '''algoritmo''' è una o più sequenze non ambigue di passi che, dato un problema, ci permette di risolverlo. &lt;br /&gt;
&lt;br /&gt;
Il nostro algoritmo, scritto in un qualche linguaggio formale, diventa un '''programma''' (sostanzialmente un testo, un insieme di istruzioni).&lt;br /&gt;
&lt;br /&gt;
Un linguaggio è un entità software, ed è definito come una quadrupla (alfabeto, sintassi, lessico, semantica) dove:&lt;br /&gt;
*l'''alfabeto'' è l'insieme di simboli che compone il linguaggio.&lt;br /&gt;
*il ''lessico'' si può vedere come una funzione che va dai simboli a un booleano e ci dice quali sono le frasi ben formate.&lt;br /&gt;
*la ''sintassi'' determina quali delle sequenze scritte sono effettivamente corrette.&lt;br /&gt;
*la ''semantica'' associa un significato alle parole ben formate.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 29 settembre 2017 ==&lt;br /&gt;
'''Cos'è un sistema operativo e un po' di storia'''&lt;br /&gt;
&lt;br /&gt;
Un ''sistema operativo'' è un programma che gestisce i processi, le risorse e interfaccia le applicazioni con l'hardware dell'elaboratore.&lt;br /&gt;
&lt;br /&gt;
In particolare, il sistema operativo (laddove esiste) è il primo processo ad essere attivato e resta in vita fino allo spegnimento del calcolatore o al sopraggiungere di un errore fatale per il sistema.&lt;br /&gt;
&lt;br /&gt;
A cosa serve un sistema operativo?&lt;br /&gt;
Principalmente, ha i seguenti scopi:&lt;br /&gt;
# Facilitare l'utilizzo del sistema (senza S.O. sarebbe a carico del programmatore conoscere tutti i linguaggi con cui si comunica con l'architettura della macchina);&lt;br /&gt;
# Rendere affidabile, protetto e sicuro l'utilizzo del sistema (e.g. un processo potrebbe recar danno all'intero sistema se non controllato o gestito, potrebbe ignorare i permessi di visualizzazione di un file);&lt;br /&gt;
# Astrarre l'hardware (e.g. filesystem);&lt;br /&gt;
# Garantire l'efficienza (e.g. non tenere la CPU in idle adottando opportuni algoritmi di scheduling);&lt;br /&gt;
# Assicurare portabilità ;&lt;br /&gt;
&lt;br /&gt;
Un approccio molto usato è il cosiddetto approccio a strati (&amp;quot;a livelli&amp;quot;) in cui ogni strato utilizza i servizi forniti al livello inferiore e ne fornisce di nuovi a quello superiore.&lt;br /&gt;
Astraendo il nostro calcolatore possiamo piazzare al livello più basso l'hardware e, subito sopra, il sistema operativo. I due layer comunicano usando il linguaggio ISA (Instruction Set Architecture), nativo della CPU stessa, al quale si aggiungono quelli che permettono di comunicare con i vari controllori dei dispositivi come la scheda di rete, la stampante, etc. Sopra il livello del sistema operativo possiamo collocare quello delle librerie e, infine, quello degli applicativi.&lt;br /&gt;
&lt;br /&gt;
* '''Systemcall''' = meccanismo usato da un processo per richiedere al SO una qualche funzionalità a livello kernel. Un esempio è la funzione printf() del linguaggio C (stampa a video) che utilizza la systemcall write() (una delle systemcall per la gestione dei dispositivi).&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
----&lt;br /&gt;
'''Storia dei sistemi operativi'''&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''zero''' (1800 ca.) possiamo includere Babbage con la sua macchina analitica e lady Ada Lovelace. [[File:290px-AnalyticalMachine Babbage London.jpg |200px|thumb|right|Macchina analitica di Babbage]]&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''uno''' vediamo la comparsa delle valvole con tutti i problemi connessi. Non c'erano utilizzatori delle macchine, le stesse persone che le costruivano erano anche programmatori e fruitori delle stesse.&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''due''' (fine anni '60) arriva il transistor. Quest'ultimo è molto più veloce e piccolo della valvola e, soprattutto, meno incline a guasti. Le macchine iniziano ad essere più economiche grazie alla possibilità di avere un'economia di scala e quindi accessibili al grande pubblico. Questo fa si che, in queste prime fasi, i costruttori non siano più gli unici utilizzatori. [[File:Transistors.jpg |300px|thumb|right|Diversi tipi di Transistor]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
A questo punto poiché, pur essendo diventata più accessibile, una macchina costava ancora molto, nasce l'esigenza di non lasciare mai un processore senza lavoro (al fine di massimizzare i ricavi) e di condividere i dati.&lt;br /&gt;
In questa fase, infatti, abbiamo sistemi operativi di tipo ''batch'' (che collezionano tutto l'input all'inizio per calcolare e restituire l'output) dove l'inserimento di programmi e dati veniva fatto tramite schede perforate e non c'era interazione. Dai sistemi batch nasce lo ''SPOOL'' (Simultaneous Peripheral Operations On-Line).&lt;br /&gt;
&lt;br /&gt;
Si comincia a pensare di utilizzare i tempi di I/O per poter eseguire altri processi. Questo però solleva almeno due problematiche: da un lato serve un modo per sapere quando un input è davvero finito mentre, dall'altro, bisogna fare in modo che un processo che non richieda I/O non occupi la CPU per un tempo indefinito.&lt;br /&gt;
Per la prima problematica la soluzione è rappresentata dall'introduzione degli '''interrupt''', segnali elettrici inviati alla CPU alla fine di ogni input.&lt;br /&gt;
Per la seconda, invece, è stato introdotto il cosiddetto '''interval timer''' che non è altro che un dispositivo che invia interrupt dopo un determinato quanto di tempo assicurando che un processo non occupi per troppo tempo il processore.&lt;br /&gt;
Questo consente di realizzare sistemi time sharing (il cui più semplice algoritmo è il round-robin) dove un processo può essere in uno dei seguenti tre stati:&lt;br /&gt;
*'''READY''' pronto per essere eseguito ma, il processore è già occupato;&lt;br /&gt;
*'''RUNNING''' in esecuzione;&lt;br /&gt;
*'''WAIT''' in attesa di I/O.&lt;br /&gt;
&lt;br /&gt;
[[File:Dds.png]]&lt;br /&gt;
&lt;br /&gt;
Nella '''quarta''' generazione (anni '70 ca.) vediamo la nascita di molte innovazioni importanti. Si riesce a rimpicciolire di molto i processori facendoli divenire a tutti gli effetti micro-processori. Nei laboratori Bell nasce Unix, un sistema operativo rivoluzionario con time sharing e clonazione dei processi (un processo non viene mai creato dal nulla ma viene prima clonato da uno già esistente e poi, alla copia, viene fatto eseguire un programma). A causa di problemi di portabilità da PDP-9, macchina su cui Unix è nato, a PDP-11 nasce il linguaggio C e il suo compilatore (scritto anch'esso in C).&lt;br /&gt;
Mentre Apple I &amp;quot;porta&amp;quot; una nuova idea di pc per tutte le famiglie, nascono molte versioni di Unix.&lt;br /&gt;
Per impedire una deriva proprietaria in cui ogni produttore offriva tutto quello che offrivano gli altri ma con qualche feature in più, Richard Stallman fonda il progetto GNU (GNU's Not Unix) per il quale scrive tutta una serie di utilities come il famoso compilatore gcc.&lt;br /&gt;
A GNU, per essere operativo, manca però un kernel. Non volendo aspettare i tempi di sviluppo del kernel che Stallman aveva in mente, Linus Torlvalds scrive il kernel '''Linux''' da cui nasce il sistema operativo GNU/Linux.&lt;br /&gt;
&lt;br /&gt;
Nei tempi più recenti nascono i processori multi-core.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 3 ottobre 2017 ==&lt;br /&gt;
 &lt;br /&gt;
'''Introduzione alla programmazione concorrente: processi, thread e  librerie'''&lt;br /&gt;
&lt;br /&gt;
In programmazione concorrente le nozioni apprese di &amp;quot;algoritmo&amp;quot;, &amp;quot;programma&amp;quot; e &amp;quot;processo&amp;quot; devono essere aggiornate. Questo perché non vi è più un programma con un'unica sequenza esecutiva [o meglio, un unico filo (= thread) di esecuzione]. Appare ovvio come servino dei meccanismi per organizzare e sincronizzare tali fili esecutivi, in modo da evitare il verificarsi di eventi non desiderati.&lt;br /&gt;
&lt;br /&gt;
Quando parliamo di ''sistema concorrente'', ci riferiamo ad un sistema in cui due o più &amp;quot;attori&amp;quot; sono eseguiti parallelamente.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Questi attori possono essere '''processi''' o '''thread'''.&lt;br /&gt;
La differenza sostanziale tra queste due tipologie di attori riguarda la condivisione della memoria e dei dati su cui operano.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Più processi possono eseguire lo stesso programma ma, ognuno di essi, ha i propri dati su cui operare e il suo stato di avanzamento. In particolare:&lt;br /&gt;
* un processo è un'entità, un attore che ha delle proprietà e delle risorse associate;&lt;br /&gt;
* in ogni istante, il processo può essere interrotto.&amp;lt;br&amp;gt;&lt;br /&gt;
Più thread che vengono eseguiti parallelamente, invece, condividono gli stessi dati su cui operare.&amp;lt;br&amp;gt;&lt;br /&gt;
Codici di esempio:&lt;br /&gt;
&lt;br /&gt;
'''processi'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=C&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;unistd.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int main(int argc, char *argv[]){&lt;br /&gt;
   if(fork()) printf(&amp;quot;Yes&amp;quot;);&lt;br /&gt;
   else printf(&amp;quot;No&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
L'output generato sarà &amp;quot;YesNo&amp;quot; poiché, usando la systemcall ''fork()'', abbiamo creato una nuova copia del processo (un processo figlio). Il processo padre, che ha eseguito con successo la fork(), stamperà &amp;quot;Yes&amp;quot; mentre il figlio stamperà &amp;quot;No&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
'''thread'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=C&amp;gt;&lt;br /&gt;
#include &amp;lt;pthread.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
#include &amp;lt;assert.h&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
#define NUM_THREADS     5&lt;br /&gt;
#define MAX 1000000&lt;br /&gt;
&lt;br /&gt;
int sum;&lt;br /&gt;
 &lt;br /&gt;
void* perform_work(){&lt;br /&gt;
	int i;&lt;br /&gt;
	&lt;br /&gt;
	for(i = 0; i &amp;lt; MAX; i++) sum = sum + 1;&lt;br /&gt;
  	/* optionally: insert more useful stuff here */ &lt;br /&gt;
  	return NULL;&lt;br /&gt;
}&lt;br /&gt;
 &lt;br /&gt;
int main( int argc, char** argv ){&lt;br /&gt;
	pthread_t threads[NUM_THREADS];&lt;br /&gt;
  	int thread_args[NUM_THREADS];&lt;br /&gt;
  	int result_code;&lt;br /&gt;
  	unsigned index;&lt;br /&gt;
 &lt;br /&gt;
	sum = 0;&lt;br /&gt;
  	// create all threads one by one&lt;br /&gt;
  	for(index = 0; index &amp;lt; NUM_THREADS; ++index ){&lt;br /&gt;
    		thread_args[index] = index;&lt;br /&gt;
    		printf(&amp;quot;In main: creating thread %d\n&amp;quot;, index);&lt;br /&gt;
    		result_code = pthread_create(&amp;amp;threads[index], NULL, perform_work, NULL);&lt;br /&gt;
    		assert(!result_code);&lt;br /&gt;
  	} &lt;br /&gt;
  	// wait for each thread to complete&lt;br /&gt;
  	for(index = 0; index &amp;lt; NUM_THREADS; ++index ){&lt;br /&gt;
    		// block until thread 'index' completes&lt;br /&gt;
    		result_code = pthread_join(threads[index], NULL);&lt;br /&gt;
    		assert(!result_code);&lt;br /&gt;
    		printf(&amp;quot;In main: thread %d has completed\n&amp;quot;, index);&lt;br /&gt;
   	}&lt;br /&gt;
 &lt;br /&gt;
   	printf(&amp;quot;In main: All threads completed successfully\n&amp;quot; );&lt;br /&gt;
	printf(&amp;quot;Result: %d\n&amp;quot;, sum);&lt;br /&gt;
   	exit( EXIT_SUCCESS );&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Prima di discutere l'output generato dall'esecuzione di questo codice, è opportuno introdurre, brevemente, gli strumenti utilizzati.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
'''Pthread (POSIX thread)'''&lt;br /&gt;
&lt;br /&gt;
Citazione dal Silberschatz, Galvin, Gagne:&lt;br /&gt;
''Pthread si riferisce allo standard POSIX (IEEE 1003.1c) che definisce una API per la creazione e sincronizzazione dei thread.''&lt;br /&gt;
&lt;br /&gt;
Le istruzioni fondamentali sono:&lt;br /&gt;
* '''int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);''' che crea un thread;&lt;br /&gt;
* '''int pthread_join(pthread_t thread, void **retval);''' che aspetta che un thread sia completato prima di continuare l'esecuzione.&lt;br /&gt;
&lt;br /&gt;
Da ricordare che, per utilizzare pthread, va incluso l'header ''&amp;lt;pthread.h&amp;gt;'' e durante la compilazione va inclusa la libreria esterna tramite l'opzione ''-pthread''.&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
Torniamo all'output generato dall'esecuzione del secondo codice.&amp;lt;br&amp;gt;&lt;br /&gt;
Nel codice vengono generati cinque thread ed ognuno somma MAX (=1000000) volte 1 a sum. Ci aspettiamo un output di 5000000 ma, in realtà, viene stampato un valore molto minore.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Questo accade principalmente per due motivi:&lt;br /&gt;
*sommare una costante ad una variabile non è un'operazione atomica (richiede almeno tre istruzioni assembler: due mov ed una sum).&lt;br /&gt;
*l'interval timer può fermare l'esecuzione del thread mentre sta effettuando una delle tre operazioni richieste per aggiungere uno a sum e provocare un risultato errato.&lt;br /&gt;
&lt;br /&gt;
Questo è uno dei possibili problemi della programmazione concorrente conosciuto come ''race condition''.&amp;lt;br&amp;gt;&lt;br /&gt;
Un sistema di processi multipli presenta una '''race condition''' qualora il risultato finale dell'esecuzione dipenda dalla temporizzazione, o dall'ordine con cui i processi vengono eseguiti.&lt;br /&gt;
&lt;br /&gt;
Per risolvere questo problema possiamo adoperare ancora una volta pthread.&lt;br /&gt;
Nello specifico, ci venogono offerte istruzione per realizzare una '''mutua esclusione''' sulle risorse condivise in modo che l'operazione che prima non era atomiche,ora, lo sia e non porti più ad una computazione errata.&amp;lt;br&amp;gt;&lt;br /&gt;
Codice di esempio:&amp;lt;br&amp;gt;&lt;br /&gt;
'''Mutex'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=C&amp;gt;&lt;br /&gt;
#include &amp;lt;pthread.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
#include &amp;lt;assert.h&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
#define NUM_THREADS     5&lt;br /&gt;
#define MAX 1000000&lt;br /&gt;
&lt;br /&gt;
int sum;&lt;br /&gt;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;&lt;br /&gt;
 &lt;br /&gt;
void* perform_work(){&lt;br /&gt;
	int i;&lt;br /&gt;
	&lt;br /&gt;
	for(i = 0; i &amp;lt; MAX; i++){ &lt;br /&gt;
		pthread_mutex_lock(&amp;amp;mutex);		&lt;br /&gt;
		sum = sum + 1;&lt;br /&gt;
		pthread_mutex_unlock(&amp;amp;mutex);&lt;br /&gt;
	}&lt;br /&gt;
  	/* optionally: insert more useful stuff here */ &lt;br /&gt;
  	return NULL;&lt;br /&gt;
}&lt;br /&gt;
 &lt;br /&gt;
int main( int argc, char** argv ){&lt;br /&gt;
	pthread_t threads[NUM_THREADS];&lt;br /&gt;
  	int thread_args[NUM_THREADS];&lt;br /&gt;
  	int result_code;&lt;br /&gt;
  	unsigned index;&lt;br /&gt;
&lt;br /&gt;
	sum = 0;&lt;br /&gt;
  	// create all threads one by one&lt;br /&gt;
  	for(index = 0; index &amp;lt; NUM_THREADS; ++index ){&lt;br /&gt;
    		thread_args[index] = index;&lt;br /&gt;
    		printf(&amp;quot;In main: creating thread %d\n&amp;quot;, index);&lt;br /&gt;
    		result_code = pthread_create(&amp;amp;threads[index], NULL, perform_work, NULL);&lt;br /&gt;
    		assert(!result_code);&lt;br /&gt;
  	} &lt;br /&gt;
  	// wait for each thread to complete&lt;br /&gt;
  	for(index = 0; index &amp;lt; NUM_THREADS; ++index ){&lt;br /&gt;
    		// block until thread 'index' completes&lt;br /&gt;
    		result_code = pthread_join(threads[index], NULL);&lt;br /&gt;
    		assert(!result_code);&lt;br /&gt;
    		printf(&amp;quot;In main: thread %d has completed\n&amp;quot;, index);&lt;br /&gt;
   	}&lt;br /&gt;
 &lt;br /&gt;
   	printf(&amp;quot;In main: All threads completed successfully\n&amp;quot; );&lt;br /&gt;
	printf(&amp;quot;Result: %d\n&amp;quot;, sum);&lt;br /&gt;
   	exit( EXIT_SUCCESS );&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
'''MUTEX'''&lt;br /&gt;
&lt;br /&gt;
Un mutex è un dispositivo di MUTual EXclusion (mutua esclusione), ed è utile per proteggere le strutture dati condivise da modifiche concorrenti, implementare sezioni critiche e monitor.&lt;br /&gt;
&lt;br /&gt;
Un mutex ha due possibili stati: sbloccato (non posseduto da nessun thread) e bloccato (posseduto da un thread). Un mutex non può mai essere posseduto da due thread contemporaneamente.&amp;lt;br&amp;gt;Se un thread provasse ad acquisire il mutex, mentre quest'ultimo è già in possesso di un altro thread, dovrebbe aspettare finché non viene rilasciato.&lt;br /&gt;
La variabile che realizza il mutex, di tipo ''pthread_mutex_t'', viene inizializzata staticamente assegnandogli PTHREAD_MUTEX_INITIALIZER.&lt;br /&gt;
'''pthread_mutex_lock''' acquisisce immediatamente il mutex, se libero. Altrimenti aspetta fino a trovarla, prima o poi, libera.&amp;lt;br&amp;gt;&lt;br /&gt;
'''pthread_mutex_unlock''' rilascia il mutex.&lt;br /&gt;
&lt;br /&gt;
[da: https://sourceware.org/pthreads-win32/manual/pthread_mutex_init.html]&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
Con questa soluzione abbiamo raggiunto un risultato esatto pur avendo pagato in prestazioni: la computazione infatti è più lenta a causa del tempo richiesto dalla sincronizzazione tra i thread.&lt;br /&gt;
&lt;br /&gt;
Consideriamo ora due scenari leggermente più complicati:&lt;br /&gt;
* nel primo abbiamo due processi, A e B, che devono accedere a due risorse, X e Y, contemporaneamente prima di poter terminare. Cosa succede se, mentre A acquisisce X, B acquisisce Y?&lt;br /&gt;
* nel secondo abbiamo tre processi, A, B e C, che devono accedere alla risorsa X. Che succede se A e B, essendo più veloci, occupano insistentemente la risorsa e non permettono a C di utilizzarla?&lt;br /&gt;
&lt;br /&gt;
La prima condizione si chiama '''deadlock''', la seconda condizione si chiama '''starvation'''. Il deadlock (stallo) è un problema che non può scomparire autonomamente, mentre la starvation (&amp;quot;inedia&amp;quot;) si può risolvere da sola. A tal proposito ci viene incontro l'assioma di '''finite progress''': &amp;quot;ogni processo che può avanzare, prima o poi lo farà&amp;quot;.&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
In ogni caso, per parlare in maniera più approfondita di queste tematiche:&lt;br /&gt;
* avremo bisogno di un modello di riferimento&lt;br /&gt;
* avremo bisogno di paradigmi in grado di esprimere la concorrenza (alcuni paradigmi saranno rivolti alla concorrenza a memoria privata; altri al multithreading a risorse condivise)&lt;br /&gt;
* dovremo imparare a scrivere programmi&lt;br /&gt;
Nel nostro modello di riferimento l'assegnamento di costanti è un operazione atomica. Dagli esempi precedenti, infatti, si evince come ci sia bisogno di creare atomicità in sezioni critiche. Inoltre, il nostro modello dovrà rispettare le proprietà di safety e di liveness, ovvero:&lt;br /&gt;
* Tutti i processi danno la stessa risposta;&lt;br /&gt;
* Ogni processo corretto da come risposta una tra quelle proposte;&lt;br /&gt;
* Ogni processo prima o poi fornisce una risposta.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 6 ottobre 2017 ==&lt;br /&gt;
'''Il linguaggio C'''&lt;br /&gt;
&lt;br /&gt;
Il linguaggio C nasce nei primi anni 70 grazie al lavoro di Dennis Ritchie con lo scopo di scrivere il &lt;br /&gt;
sistema operativo Unix. Quali sono le caratteristiche di C e cosa lo rende un linguaggio adatto per scrivere un sistema&lt;br /&gt;
operativo? Un linguaggio per scrivere un sistema operativo deve:&lt;br /&gt;
* essere indipendente dalla macchina su cui viene scritto&lt;br /&gt;
* dare la possibilità di lavorare direttamente con la memoria&lt;br /&gt;
* permette di andare a modificare i singoli bit in memoria&lt;br /&gt;
* essere semplice da usare&lt;br /&gt;
* essere massimamente produttivo&lt;br /&gt;
* essere veloce, efficiente&lt;br /&gt;
C presenta le caratteristiche dette sopra e alcune altre:&lt;br /&gt;
* in C, dal punto di vista sintattico, tutto è funzione. Si può vedere un programma in C come un insieme di funzioni. La funzione principale è il '''main'''. L'unica cosa che la distingue è il nome, a parte quello il main è una funzione come le altre. Prende due argomenti: argcount e argvalue, che contengono rispettivamente il numero degli argomenti e un array contenente gli argomenti stessi. L'interfaccia del main è tipicamente: &amp;lt;syntaxhighlight lang=C&amp;gt; int main(int argc, char *argv[]) &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
* il costrutto fondamentale è l'espressione. ''expression'' ; = ''instruction''&lt;br /&gt;
* sono previste istruzioni di struttura &amp;lt;syntaxhighlight lang=C&amp;gt;if, while, switch, etc.&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
* le variabili, per poter essere utilizzate, devono essere sempre definite&lt;br /&gt;
* il C è un linguaggio fortemente tipato, però con un certo numero di eccezioni: ad esempio, è possibile fare il casting esplicito di un puntatore void * in qualsiasi tipo, senza che il compilatore dia errori o warnings, con conseguente perdita di type safety&lt;br /&gt;
* C è un linguaggio &amp;quot;per adulti&amp;quot;: non impone una struttura ordinata al codice, come ad esempio fa invece Python, ed è piena responsabilità del programmatore scrivere programmi ordinati/leggibili. Inoltre molte operazioni che in altri linguaggi non sarebbero permesse lo sono invece in C: anche in questo caso è responsabilità del programmatore fare corretto uso del linguaggio&lt;br /&gt;
* C è un linguaggio molto semplice. Come si sopperisce alle mancanze imposte da questa semplicità? Grazie alle librerie: in C tutte le funzionalità aggiuntive (non fornite dal linguaggio stesso, come l'allocazione di memoria, la stampa a schermo, la gestione di input e output) sono fornite dalle librerie&lt;br /&gt;
* C è anche sintatticamente semplice. Dove va a finire la complessità sintattica? Nel preprocessore: prima di essere compilato un programma in C passa attraverso un preprocessore, che produce codice in base alle direttive di preprocessione date nel codice stesso &amp;lt;syntaxhighlight lang=C&amp;gt;#ifdef VAR ... #endif, # e ## operator, __LINE__,etc&amp;lt;/syntaxhighlight&amp;gt; &lt;br /&gt;
* in C vi sono fondamentalmente due tipi di dato: int e float. Tutti gli altri &amp;quot;tipi&amp;quot; di dato sono derivati da essi. Si hanno diversi modificatori, che vanno a modificare la quantità di memoria allocata per la variabile. Alcuni di questi sono &amp;lt;syntaxhighlight lang=C&amp;gt;long, short, char, etc&amp;lt;/syntaxhighlight&amp;gt; Un dato di tipo long int ha, solitamente, la dimensione di una parola di memoria della macchina su cui è eseguito il programma. char alloca 8 bits. Se si prende una variabile di un certo tipo e lo si assegna ad una di tipo più ampio, il valore viene mantenuto. Viceversa quando si assegna una variabile di un tipo ad una di tipo meno ampio, una volta &amp;quot;superato il limite&amp;quot; si torna indietro ciclicamente al valore iniziale. Per queste situazioni il compilatore non dà né warnings né errors: sono perfettamente lecite in C&lt;br /&gt;
* in C si passa una varibile ad una funzione solo per valore; per passare &amp;quot;per riferimento&amp;quot; una variabile si passa per valore l'indirizzo della variabile&lt;br /&gt;
* in C si può fare uso di puntatori alla memoria. Dato un puntatore p, un'istruzione del tipo p+i va letta come: vai avanti di i posizioni della grandezza del tipo del puntatore ognuno. Un vettore in C non è altro che un puntatore che non può essere assegnato. Una scrittura del tipo array[i] è una abbreviazione di *(array + i). Non c'è controllo in C se vado oltre la memoria allocata per l'array. Perché? In Pascal, ad esempio, c'è un tale controllo. Il motivo per cui in C questo manca è che questo controllo genera ulteriore codice macchina. Dovendo C essere efficiente, non ci si può permettere un tale costo aggiuntivo. La responsabilità di non andare oltre è lasciata al programmatore&lt;br /&gt;
&lt;br /&gt;
Esercizio svolto che prende in input due valori tramite i parametri del metodo '''main''', li converte da sequenze di caratteri ad interi, per poi stamparli utilizzando delle '''System Call'''. Lo scopo dell’esercizio è dimostrare come sia possibile utilizzare il linguaggio C senza l’utilizzo di librerie particolari (ad eccezione di quelle per le System Call).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=C&amp;gt;&lt;br /&gt;
#define _GNU_SOURCE&lt;br /&gt;
#include &amp;lt;unistd.h&amp;gt;&lt;br /&gt;
#include &amp;lt;sys/syscall.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int _uatoi(char *s, int value){&lt;br /&gt;
  if (*s == 0)&lt;br /&gt;
    return value;&lt;br /&gt;
  else if (*s &amp;gt;= '0' &amp;amp;&amp;amp; *s &amp;lt;= '9'){&lt;br /&gt;
    value = value * 10 + (*s - '0');&lt;br /&gt;
    return _uatoi(s+1, value);&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int uatoi(char *s){&lt;br /&gt;
  return _uatoi(s, 0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
char *uitoa(int val, char *buf, int len){&lt;br /&gt;
  int i;&lt;br /&gt;
  for (len = len - 1; len &amp;gt;= 0 &amp;amp;&amp;amp; val &amp;gt; 0; len--, val /= 10){&lt;br /&gt;
    buf[len] = '0' + (val %10);&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  return buf + len + 1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#define MAXOUTLEN 10&lt;br /&gt;
int main (int argc, char *argv[]) {&lt;br /&gt;
    int tot = 0;&lt;br /&gt;
    int i;&lt;br /&gt;
    char buf[MAXOUTLEN];&lt;br /&gt;
    char *result;&lt;br /&gt;
    for (i = 1; i &amp;lt; argc; i++)&lt;br /&gt;
      tot += uatoi(argv[i]);&lt;br /&gt;
    result = uitoa(tot, buf, MAXOUTLEN);&lt;br /&gt;
    syscall(__NR_write, 1, result, MAXOUTLEN - (result - buf));&lt;br /&gt;
    syscall(__NR_write, 1, &amp;quot;\n&amp;quot;, 1);&lt;br /&gt;
    return tot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Lezione del 10 ottobre 2017 ==&lt;br /&gt;
'''Programmazione concorrente: notazione e un primo problema'''&lt;br /&gt;
&lt;br /&gt;
Esempio di possibile rappresentazione (che non useremo) di codice concorrente.&amp;lt;br&amp;gt;&lt;br /&gt;
'''''A'', ''B'', ''C'', ''D'', ''E''''' rappresentano porzioni di codice.&amp;lt;br&amp;gt;&lt;br /&gt;
'''''//''''' rappresenta esecuzione parallela&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
A&lt;br /&gt;
cobegin&lt;br /&gt;
  B&lt;br /&gt;
//&lt;br /&gt;
  C&lt;br /&gt;
//&lt;br /&gt;
  D&lt;br /&gt;
coend&lt;br /&gt;
E&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il codice sarà eseguito come: ''A'' -&amp;gt; ''BCD'' -&amp;gt; ''E''. ''E'' quindi verrà eseguito solo dopo la fine dell'esecuzione di ''BCD''.&lt;br /&gt;
&lt;br /&gt;
Un esempio che si presta molto bene ad illustrare la programmazione concorrente è quello del '''merge sort''' concorrente:&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
leggi vettore&lt;br /&gt;
cobegin&lt;br /&gt;
  ordina la prima metà del vettore&lt;br /&gt;
//&lt;br /&gt;
  ordina la seconda metà del vettore&lt;br /&gt;
coend&lt;br /&gt;
merge (miscela) i risultati&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Versione errata del merge sort concorrente:&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
leggi vettore&lt;br /&gt;
cobegin&lt;br /&gt;
  ordina la prima metà del vettore&lt;br /&gt;
//&lt;br /&gt;
  ordina la seconda metà del vettore&lt;br /&gt;
//  &lt;br /&gt;
  merge (miscela) i risultati&lt;br /&gt;
coend&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avendo un numero ''N'' di processi, vorremmo avere una notazione che ci permetta di definire una porzione di codice come atomica (la sezione critica). Come possiamo procedere?&amp;lt;br&amp;gt;&lt;br /&gt;
Teoricamente, vorremmo avere due funzioni, '''csenter()''' e '''csexit()''' che ci permettano di entrare e uscire dalla sezione critica del codice senza causare errori nella computazione.&amp;lt;br&amp;gt;&lt;br /&gt;
Quali sono le proprietà che queste funzioni debbono rispettare?&lt;br /&gt;
# Devono garantire la mutua esclusione (1 solo processo esegue la critical section alla volta). Contando ''n'' csenter() completate e ''m'' csexit() completate la differenza deve essere &amp;lt;=1&lt;br /&gt;
# Non devono fare deadlock tra di loro&lt;br /&gt;
# Non devono fare starvation tra loro&lt;br /&gt;
# ''(no unnecessary delay)'' un processo che non sta usando la critical section {csenter()..csexit()} non deve bloccare un altro processo dall'entrarvi.&lt;br /&gt;
&lt;br /&gt;
La soluzione a questo problema è nota come '''soluzione di Dekker''' (scritta e pubblicata da Dijkstra) che venne presentata in modo graduale attraverso l'utilizzo di quattro possibili, ma sbagliate, soluzioni.&lt;br /&gt;
&lt;br /&gt;
Vorremmo avere programmi che abbiano la seguente struttura:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
process[i], i=1,...,N&lt;br /&gt;
&lt;br /&gt;
while(1):&lt;br /&gt;
  csenter() //entrata nella sezione critica (critical section)&lt;br /&gt;
  critical code&lt;br /&gt;
  csexit() //uscita dalla sezione critica&lt;br /&gt;
  non-critical code&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ad esempio, tornando al problema dei thread che sommavano 1 a ''sum'' MAX volte, avremo:&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
process A:&lt;br /&gt;
&lt;br /&gt;
  for (i = 0; i &amp;lt; MAX; i++)&lt;br /&gt;
    csenter()&lt;br /&gt;
    sum += 1 //atomicità descritta da csenter(), csexit()&lt;br /&gt;
    // &amp;lt; sum += 1 &amp;gt; atomicità lasciata al processore&lt;br /&gt;
    csexit()&lt;br /&gt;
&lt;br /&gt;
process B:&lt;br /&gt;
&lt;br /&gt;
  for (i = 0; i &amp;lt; MAX; i++)&lt;br /&gt;
    csenter()&lt;br /&gt;
    sum += 1&lt;br /&gt;
    csexit()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
----&lt;br /&gt;
'''Soluzione 1: A turni'''&lt;br /&gt;
&lt;br /&gt;
Qual è il problema?&amp;lt;br&amp;gt;&lt;br /&gt;
*'''mutex'''                   OK&amp;lt;br&amp;gt;&lt;br /&gt;
*'''deadlock'''                OK&amp;lt;br&amp;gt;&lt;br /&gt;
*'''starvation'''              OK&amp;lt;br&amp;gt;&lt;br /&gt;
*'''no unnecessary delay'''    NO&amp;lt;br&amp;gt;&lt;br /&gt;
:: Perché se P ha bisogno spesso della critical section, P non deve aspettare che Q chieda la critical section e, all'uscita, lo autorizzi di nuovo ad usarla impostando ''turn = P''. Se Q una volta richiesta la sezione non la usa ma si ferma, P non può richiederla ancora perché appartiene ancora a Q.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
&lt;br /&gt;
turn=P&lt;br /&gt;
&lt;br /&gt;
process P:&lt;br /&gt;
  while(1)&lt;br /&gt;
    while (turn != P)&lt;br /&gt;
      ;&lt;br /&gt;
    critical code&lt;br /&gt;
    turn = Q&lt;br /&gt;
    non-critical code&lt;br /&gt;
&lt;br /&gt;
process Q:&lt;br /&gt;
  while(1)&lt;br /&gt;
    while (turn != Q)&lt;br /&gt;
    critical code&lt;br /&gt;
    turn = P&lt;br /&gt;
    non-critical code&lt;br /&gt;
 &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
'''Soluzione 2: con 2 variabili''', cerchiamo di risolvere il problema dei turni&lt;br /&gt;
&lt;br /&gt;
Finché Q usa la sezione P aspetta, quando Q ha finito P prende la sezione critica e non ci sono ritardi indesiderati.&lt;br /&gt;
&lt;br /&gt;
*'''starvation'''              OK&lt;br /&gt;
*'''deadlock'''                OK&lt;br /&gt;
*'''no unnecessary delay'''    OK&lt;br /&gt;
*'''mutex'''                   NO &lt;br /&gt;
:: Possono essere entrambi come false, escono insieme nel while, tutti e due mettono a true ed entrano assieme nel while.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
&lt;br /&gt;
inP = false;&lt;br /&gt;
inQ = false;&lt;br /&gt;
&lt;br /&gt;
process P:&lt;br /&gt;
  while(1)&lt;br /&gt;
    while (inQ)&lt;br /&gt;
      ;&lt;br /&gt;
    inP = true&lt;br /&gt;
    critical code&lt;br /&gt;
    inP = false&lt;br /&gt;
    non-critical code&lt;br /&gt;
&lt;br /&gt;
process Q:&lt;br /&gt;
  while(1)&lt;br /&gt;
    while (inP)&lt;br /&gt;
      ;&lt;br /&gt;
    inQ = true&lt;br /&gt;
    critical code&lt;br /&gt;
    inQ = false&lt;br /&gt;
    non-critical code&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
'''Soluzione 3: il problema di soluzione 2 è che i processi non sono al corrente dell'intenzione dell'altro di entrare''' nel while quindi si crea il problema.&lt;br /&gt;
*'''mutex'''                   OK&lt;br /&gt;
*'''deadlock'''                NO (potrebbero entrambi richiedere, contemporaneamente, l'ingresso alla critical section)&lt;br /&gt;
*'''starvation'''              OK&lt;br /&gt;
*'''non unnecessary delay'''   OK&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
needP = false;&lt;br /&gt;
needQ = false;&lt;br /&gt;
&lt;br /&gt;
process P:&lt;br /&gt;
  while(1)&lt;br /&gt;
    needP = true&lt;br /&gt;
    while (needQ)&lt;br /&gt;
      ;&lt;br /&gt;
    critical code&lt;br /&gt;
    needP = false&lt;br /&gt;
    non-critical code&lt;br /&gt;
&lt;br /&gt;
process Q:&lt;br /&gt;
  while(1)&lt;br /&gt;
    needQ = true&lt;br /&gt;
    while (needP)&lt;br /&gt;
      ;&lt;br /&gt;
    critical code&lt;br /&gt;
    needQ = false&lt;br /&gt;
    non-critical code&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
'''Soluzione 4''':&lt;br /&gt;
&lt;br /&gt;
*'''mutex'''                 OK&lt;br /&gt;
*'''deadlock'''              OK&lt;br /&gt;
*'''no unnecessary delay'''  OK&lt;br /&gt;
*'''starvation'''            NO &lt;br /&gt;
:: Perché può esistere il processo che prova sempre a chiedere nel momento sbagliato e non esegue mai.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
inP = false;&lt;br /&gt;
inQ = false;&lt;br /&gt;
&lt;br /&gt;
process P:&lt;br /&gt;
  while(1)&lt;br /&gt;
    inP = true&lt;br /&gt;
    while (inQ){&lt;br /&gt;
        inP = false;&lt;br /&gt;
        delay&lt;br /&gt;
        inP = true;&lt;br /&gt;
    }&lt;br /&gt;
    critical code&lt;br /&gt;
    inP = false&lt;br /&gt;
    non-critical code&lt;br /&gt;
&lt;br /&gt;
process Q:&lt;br /&gt;
  while(1)&lt;br /&gt;
    inQ = true&lt;br /&gt;
    while (inQ){&lt;br /&gt;
        inQ = false;&lt;br /&gt;
        delay&lt;br /&gt;
        inQ = true;&lt;br /&gt;
    }&lt;br /&gt;
    critical code&lt;br /&gt;
    inQ = false&lt;br /&gt;
    non-critical code&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
'''Soluzione finale''':&amp;lt;br&amp;gt;&lt;br /&gt;
Uniamo soluzione 1 (poco simmetrica) e soluzione 4 (troppo simmetrica) usando quello che ci serve (attraverso ''needP'' e ''needQ'') e, se dovessimo essere tutti e due contemporaneamente a voler entrare, usiamo i turni (''turn'') per entrare nella critical section (settando il need dell'altro processo a false).&lt;br /&gt;
&lt;br /&gt;
*'''mutex'''                         OK&lt;br /&gt;
*'''deadlock'''                      OK&lt;br /&gt;
*'''starvation'''                    OK&lt;br /&gt;
*'''non unnecessary delay'''         OK&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
needP = false;&lt;br /&gt;
needQ = false;&lt;br /&gt;
turn = P&lt;br /&gt;
&lt;br /&gt;
process P:&lt;br /&gt;
  while(1)&lt;br /&gt;
    needP = true&lt;br /&gt;
    while (needQ)&lt;br /&gt;
      if (turn != P) {&lt;br /&gt;
        needP = false;&lt;br /&gt;
        while ( turn != P)&lt;br /&gt;
           ; /* busy wait */&lt;br /&gt;
        needP = true;&lt;br /&gt;
    }&lt;br /&gt;
    /*critical code*/&lt;br /&gt;
    turn = Q&lt;br /&gt;
    needP = false&lt;br /&gt;
    /*non-critical code*/&lt;br /&gt;
&lt;br /&gt;
process P:&lt;br /&gt;
  while(1)&lt;br /&gt;
    needQ = true&lt;br /&gt;
    while (needP)&lt;br /&gt;
      if (turn != Q) {&lt;br /&gt;
        needQ = false;&lt;br /&gt;
        while ( turn != Q)&lt;br /&gt;
             ; /* busy wait */&lt;br /&gt;
        needQ = true;&lt;br /&gt;
    }&lt;br /&gt;
    /*critical code*/&lt;br /&gt;
    turn = P&lt;br /&gt;
    needQ = false&lt;br /&gt;
    /*non-critical code*/&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Lezione del 13 ottobre 2017 ==&lt;br /&gt;
'''Il linguaggio C: PREPROCESSORE'''&lt;br /&gt;
&lt;br /&gt;
Il '''preprocessore''' è uno strumento contenuto nella toolchain di '''gcc''' che esamina il codice sorgente prima che questo venga compilato, e &amp;quot;trasforma&amp;quot; il programma prima della compilazione; è propriamente definito come ''macro processor'', ovvero un esaminatore di '''macro'''.&lt;br /&gt;
Le '''macro''' sono delle abbreviazioni che in realtà definiscono delle strutture di codice più grandi, e tornano molto utili in diversi casi nello sviluppo di un programma in C (è buona norma scrivere l’identificatore completamente in '''MAIUSCOLO'''). Il preprocessore esamina le cosiddette '''direttive'''; per conoscere l’output del preprocessore è possibile digitare il comando &amp;lt;kbd&amp;gt;'''gcc -E''' ''{nomeDelSorgente}''&amp;lt;/kbd&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Un primo esempio è dato dalla direttiva '''&amp;lt;kbd&amp;gt;#define&amp;lt;/kbd&amp;gt;''', la quale crea una ''macro'', ovvero l’associazione di un identificatore (con o senza parametri) con una stringa di ''token''. Le macro così definite possono assumere quindi diverse funzioni:&lt;br /&gt;
# condizione per l’inclusione o meno di codice nel file che verrà passato al compilatore;&lt;br /&gt;
# inserimento di '''costanti''' all’interno del codice (notare che questa direttiva nasce molto prima della keyword '''const''' del linguaggio C);&lt;br /&gt;
# creazione di abbreviazioni a porzioni di codice più lunghe.&lt;br /&gt;
&lt;br /&gt;
* '''Caso 1''' &amp;lt;br&amp;gt; Il primo caso, che potrebbe essere definito come il più semplice, prevede la dichiarazione di un valore costante, su cui ci si baserà in seguito per includere o meno parti di codice. Per fare ciò occorre introdurre anche altre direttive del preprocessore: '''#ifdef''', '''ifndef''' ed '''endif''', le quali identificano un blocco di codice che deve essere incluso nel file da compilare solo se rispettivamente una macro è definita ''(#ifdef)'' o una macro non è definita ''(#ifndef)'', mentre la direttiva ''#endif'' indica la fine del blocco condizionale.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#define DEBUG&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
#ifdef DEBUG&lt;br /&gt;
    printf(&amp;quot;In main\n&amp;quot;);&lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
// la macro __linux__ è definita solamente nei sistemi Linux-based&lt;br /&gt;
#ifndef __linux__&lt;br /&gt;
    printf(&amp;quot;WARNING: not a Linux system\n&amp;quot;);&lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
:Da notare in questo caso l’utilizzo della macro '''__linux__''', una macro predefinita nei sistemi ''Linux-based'', che consente di discriminare il sistema operativo su cui il programma viene compilato, dando quindi la possibilità di compilare del codice diverso in base alla piattaforma utilizzata. &amp;lt;br&amp;gt;&lt;br /&gt;
*'''Caso 2''' &amp;lt;br&amp;gt;Il secondo caso prevede la presenza di un valore in seguito all’identificatore, che quindi verrà sostituito in ogni sua occorrenza all’interno del codice. È da far notare come questo meccanismo sia simile all’utilizzo della keyword '''const''' del linguaggio, ma quest’ultima nasce solo successivamente come alternativa ''type-safe'' (dal momento che le macro potrebbero essere utilizzate ovunque senza alcun tipo di controllo). Questo meccanismo torna ad esempio quando si vuole effettuare un’operazione un numero di volte prefissato; in questo modo, è sufficiente modificare il valore della macro nella sua definizione, per aggiornare tutto il programma che utilizzava quella macro al nuovo valore.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#define ITER_NUM 5&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
    int i;&lt;br /&gt;
    for (i = 0; i &amp;lt; ITER_NUM; i++)&lt;br /&gt;
        printf(&amp;quot;Iterazione numero %d\n&amp;quot;, i);&lt;br /&gt;
&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt; &amp;lt;br&amp;gt;&lt;br /&gt;
*'''Caso 3''' &amp;lt;br&amp;gt; L’ultimo caso analizzato è quello che può essere definito il più complesso tra i tre. L’identificatore della macro ora presenta anche dei parametri formali, all’interno di parentesi e separati da virgole. Tali parametri compaiono anche nella token string in cui vengono utilizzati (anche più volte) e su cui vengono eseguite delle operazioni. Questa particolarità è utile per permettere di ridurre porzioni di codice che vengono definite così una volta sola, e per essere utilizzate sarà sufficiente scrivere il nome dell’identificatore con i relativi parametri. Da notare come ci sia un numero considerevole di parentesi, che potrebbero quasi sembrare superflue. In realtà sono essenziali per assicurare il corretto ordine di esecuzione delle istruzioni una volta che la stringa di token sarà sostituita alle occorrenze dell’identificatore.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#define MAX(x, y) ((x) &amp;gt; (y) ? (x) : (y))&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
    printf(&amp;quot;%d\n&amp;quot;, MAX(10, 20));&lt;br /&gt;
&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
:Che produrrà, in seguito all’esecuzione del preprocessore:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
    printf(&amp;quot;%d\n&amp;quot;, ((10) &amp;gt; (20) ? (10) : (20)));&lt;br /&gt;
&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
:Occorre far notare che quella effettuata dal preprocessore non è altro che una '''string substitution''', cioè si limita a sostituire le macro utilizzate nel codice con la rispettiva definizione (ovviamente includendo eventuali parametri formali), ma non effettua alcun tipo di controllo. Ciò significa che occorre prestare molta attenzione quando si utilizzano le macro come nell’ultimo caso illustrato, perché potrebbero comparire dei '''side-effect''' inaspettati. Se si modifica l’esempio precedente in questo modo&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#define MAX(x, y) ((x) &amp;gt; (y) ? (x) : (y))&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
int i = 100;&lt;br /&gt;
    printf(&amp;quot;%d\n&amp;quot;, MAX(10, i++));&lt;br /&gt;
    printf(&amp;quot;%d\n&amp;quot;, MAX(10, i));&lt;br /&gt;
&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
:si otterrà un risultato che in un primo momento potrebbe essere inaspettato:&lt;br /&gt;
&amp;lt;kbd&amp;gt;&lt;br /&gt;
101&amp;lt;br&amp;gt;102&lt;br /&gt;
&amp;lt;/kbd&amp;gt;&lt;br /&gt;
:Ad un’analisi più approfondita tuttavia, è possibile accorgersi dell’errore: il preprocessore effettua una sostituzione testuale dei parametri al momento dell’utilizzo della macro; ciò significa che sostituisce ogni occorrenza del primo parametro nella ''token string'' con il primo valore fornito, e dualmente per il secondo parametro. Il codice che verrà sottoposto al compilatore conterrà quindi due incrementi della variabile &amp;lt;kbd&amp;gt;i&amp;lt;/kbd&amp;gt;. Un esempio a grandi linee delle operazioni eseguite dal compilatore è la seguente (per capirlo è necessario aver ben compreso la nozione di '''post-incremento''' della variabile i):&lt;br /&gt;
#assegno alla variabile i il valore 100&lt;br /&gt;
#10 è maggiore di i?&lt;br /&gt;
#NO, incremento i (ora i vale 101)&lt;br /&gt;
#scrivo i (101)&lt;br /&gt;
#incremento i (ora i vale 102)&lt;br /&gt;
#10 è maggiore di i?&lt;br /&gt;
#NO, scrivo i (102)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
Esistono molti altri operatori che si possono utilizzare all’interno delle macro, due esempi sono:&lt;br /&gt;
* l’operatore '''#''' che, se anteposto al nome di un parametro nella stringa di token, viene riconosciuto dal processore, che rimpiazza il parametro con il simbolo '''#''' anteposto con il proprio valore letterale convertito in una stringa costante;&lt;br /&gt;
* l’operatore '''##''' &amp;quot;combina&amp;quot; due token in uno: unisce il token alla sinistra e quello alla destra dell’operatore per farlo diventare un’unico token. Questo torna molto utile se uno dei due token proviene da un parametro della macro.&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
Per un esempio su questi ultimi due operatori: [http://so.v2.cs.unibo.it/wiki/index.php?title=Esercizi_di_%22lettura%22_di_in_linguaggio_C_2017/18#tables_and_preprocessor_tricks tables and preprocessors tricks]&lt;br /&gt;
----&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Per ulteriori approfondimenti sul preprocessore del C: [https://gcc.gnu.org/onlinedocs/cpp/ Approfondimenti sul preprocessore del C]&lt;br /&gt;
&lt;br /&gt;
== Lezione del 17 ottobre 2017 ==&lt;br /&gt;
&lt;br /&gt;
'''Oltre la soluzione di Dekker'''&lt;br /&gt;
&lt;br /&gt;
Nella scorsa lezione di teoria abbiamo visto la soluzione di Dekker dove due processi, per poter entrare ed eseguire la critical section, manifestano prima questo loro bisogno (&amp;lt;code&amp;gt;needP/needQ = true&amp;lt;/code&amp;gt;), controllano poi se anche l'altro processo ha comunicato questo bisogno e, in caso affermativo, verrà utilizzata una politica a turni per gestire l'entrata e l'uscita alla sezione critica.&lt;br /&gt;
&lt;br /&gt;
P.S. Nei sistemi reali (specialmente multi-core) la soluzione di Dekker necessita di istruzioni aggiuntive come &amp;lt;code&amp;gt;__syn_synchronize()&amp;lt;/code&amp;gt; e &amp;lt;code&amp;gt;sched_yeld()&amp;lt;/code&amp;gt;: la prima sincronizza il contenuto delle cache (assicurando la correttezza del risultato) mentre la seconda fa sì che il thread rinunci al suo tempo di CPU permettendo a qualche altro processo di avere la priorità.&lt;br /&gt;
&lt;br /&gt;
Questa soluzione ha però due limiti:&lt;br /&gt;
*non funziona con un numero di processi &amp;gt; 2&lt;br /&gt;
*è relativamente complicato da capire, si può fare di meglio!&lt;br /&gt;
&lt;br /&gt;
Un'evoluzione più lineare e facilmente adattabile a più di due processi è l''''algoritmo di Peterson''' che, nel caso base, ha la seguente struttura:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
needP = false;&lt;br /&gt;
needQ = false;&lt;br /&gt;
turn;&lt;br /&gt;
 &lt;br /&gt;
process P:&lt;br /&gt;
  while(1)&lt;br /&gt;
    /* entry protocol */&lt;br /&gt;
    needP = true&lt;br /&gt;
    turn = Q&lt;br /&gt;
    while(needQ &amp;amp;&amp;amp; turn != P)&lt;br /&gt;
      ; /* busy wait */&lt;br /&gt;
    /* critical section */&lt;br /&gt;
    needP = false&lt;br /&gt;
    /* non-critical section */&lt;br /&gt;
 &lt;br /&gt;
process Q:&lt;br /&gt;
  while(1)&lt;br /&gt;
    /* entry protocol */&lt;br /&gt;
    needQ = true&lt;br /&gt;
    turn = P&lt;br /&gt;
    while(needP &amp;amp;&amp;amp; turn != Q)&lt;br /&gt;
      ; /* busy wait */&lt;br /&gt;
    /* critical section */&lt;br /&gt;
    needQ = false&lt;br /&gt;
    /* non-critical section */&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Questa soluzione è più snella poiché i processi, invece di assegnare la variabile &amp;lt;code&amp;gt;turn&amp;lt;/code&amp;gt; all'altro processo come in Dekker, lo fanno prima.&amp;lt;br&amp;gt;&lt;br /&gt;
Possiamo vedere questa soluzione come se avesse una sorta di &amp;quot;anticamera&amp;quot; da cui bisogna necessariamente passare per poter accedere alla sezione critica.&amp;lt;br&amp;gt;&lt;br /&gt;
Entra nella critical section sempre l'ultimo processo a non essere entrato nell'anticamera.&amp;lt;br&amp;gt;&lt;br /&gt;
Questi concetti possono bessimo essere estesi nel caso in cui abbiamo più di due processi a patto di estendere l'anticamera con tanti &amp;quot;stage&amp;quot; quanti sono i processi.&lt;br /&gt;
&lt;br /&gt;
Queste soluzioni funzionano in pratica ma, ci sono ancora tutti i difetti di prima: '''busy wait''' che spreca cicli di CPU e una grande difficoltà nello scrivere programmi concorrenti in questo modo. Ci farebbero davvero comodo dei paradigmi di più alto livello.&lt;br /&gt;
&lt;br /&gt;
Davanti a noi si aprono principalmente due possibilità:&lt;br /&gt;
*l'hardware della macchina ci viene in aiuto mascherando gli interrupt (nei sistemi mono-core)&lt;br /&gt;
*l'hardware (e gli ingegneri) ci aiuta fornendoci un'apposita istruzione atomica: la '''TEST&amp;amp;SET'''&lt;br /&gt;
&lt;br /&gt;
L'idea che sta alla base della T&amp;amp;S è quella di avere una variabile globale che ci dica se la critical section è libera (0) o occupata (1).&amp;lt;br&amp;gt;&lt;br /&gt;
La T&amp;amp;S viene invocata con due parametri, una variabile locale che serve per verificare lo stato precedente e la variabile globale, e realizza la seguente funzionalità:&lt;br /&gt;
&lt;br /&gt;
'''&amp;lt;code&amp;gt;T&amp;amp;S(x,y) = &amp;lt;x = y; y = 1&amp;gt;&amp;lt;/code&amp;gt;'''&lt;br /&gt;
&lt;br /&gt;
Quindi, intuitivamente, possiamo avere due casi: o la variabile globale è 0, e la T&amp;amp;S assegna 0 a x e setta la variabile globale a 1, o la variabile globale è a 1 e la funzione semplicemente immagazzina 1 in x.&lt;br /&gt;
Questa semplice funzione è abbastanza per realizzare i protocolli csenter() e csexit() e realizzare le critical section:&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
G = 0; /* global variable */&lt;br /&gt;
csenter():&lt;br /&gt;
    L;&lt;br /&gt;
    do{&lt;br /&gt;
        T&amp;amp;S(L, G)&lt;br /&gt;
    }while(L == 1)&lt;br /&gt;
&lt;br /&gt;
csexit():&lt;br /&gt;
    G = 0&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Questo approccio ci fornisce diversi vantaggi: è nativamente adatto a gestire un numero arbitrario di processi, è semplice e ci garantisce le 4 proprietà fondamentali delle critical section.&amp;lt;br&amp;gt;&lt;br /&gt;
Però c'è ancora busy wait (chiamata anche spin-lock in questo contesto), potrebbe esserci starvation in casi speciali ed è ancora una gestione di basso livello.&amp;lt;br&amp;gt;&lt;br /&gt;
Vorremo un paradigma che permetta di scrivere ancor più facilmente programmi concorrenti e che siano allo stesso tempo semplicemente implementabili.&lt;br /&gt;
&lt;br /&gt;
Il primo paradigma che vediamo è quello dei '''semafori''', proposto da Dijksta nel 1965. (P.S. quasi tutti gli appunti di Dijkstra sono stati scansionati e sono scaricabili da qui: http://www.cs.utexas.edu/users/EWD).&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Lezione del 20 ottobre 2017 ==&lt;br /&gt;
== Lezione del 24 ottobre 2017 ==&lt;br /&gt;
== Lezione del 27 ottobre 2017 ==&lt;br /&gt;
== Lezione del 31 ottobre 2017 ==&lt;br /&gt;
== Lezione del 3 novembre 2017 ==&lt;br /&gt;
== Lezione del 7 novembre 2017 ==&lt;br /&gt;
== Lezione del 10 novembre 2017 ==&lt;br /&gt;
== Lezione del 14 novembre 2017 ==&lt;br /&gt;
== Lezione del 17 novembre 2017 ==&lt;br /&gt;
== Lezione del 21 novembre 2017 ==&lt;br /&gt;
== Lezione del 24 novembre 2017 ==&lt;br /&gt;
== Lezione del 28 novembre 2017 ==&lt;br /&gt;
== Lezione del 1 dicembre 2017 ==&lt;br /&gt;
== Lezione del 5 dicembre 2017 ==&lt;br /&gt;
== Lezione del 12 dicembre 2017 ==&lt;br /&gt;
== Lezione del 15 dicembre 2017 ==&lt;/div&gt;</summary>
		<author><name>Matt</name></author>
	</entry>
	<entry>
		<id>https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_I_semestre&amp;diff=2043</id>
		<title>Lezioni Anno Accademico 2017/18 I semestre</title>
		<link rel="alternate" type="text/html" href="https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_I_semestre&amp;diff=2043"/>
		<updated>2017-10-18T20:21:37Z</updated>

		<summary type="html">&lt;p&gt;Matt: /* Lezione del 17 ottobre 2017 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;''scrivete qui idee, riassunti dei concetti espressi, commenti approfondimenti sulle lezioni.''&lt;br /&gt;
&lt;br /&gt;
== Lezione del 26 settembre 2017 ==&lt;br /&gt;
'''Titolo della lezione: W&amp;amp;#x2074; H Y'''&lt;br /&gt;
&lt;br /&gt;
* What:&lt;br /&gt;
* Who:&lt;br /&gt;
----&lt;br /&gt;
Noi studenti ed &amp;quot;entrambi&amp;quot; i professori.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* When:&lt;br /&gt;
----&lt;br /&gt;
Il corso ha durata annuale. Verrà svolto ogni martedì e venerdì dalle 15:30 alle 18:30.&lt;br /&gt;
* La lezione del martedì sarà dedicata alla programmazione concorrente;&lt;br /&gt;
* La lezione del venerdì sarà dedicata alla parte generale. Terminerà circa 15 minuti prima dell'orario stabilito.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* Where:&lt;br /&gt;
----&lt;br /&gt;
Sempre in Aula 1 Ercolani (E1): DISI, Scuole Ercolani, Mura Anteo Zamboni 2B.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* How:&lt;br /&gt;
----&lt;br /&gt;
Il corso si svolgerà tramite:&lt;br /&gt;
* Lezioni frontali in aula.&lt;br /&gt;
* Attività di laboratorio.&lt;br /&gt;
* Esercitazioni.&lt;br /&gt;
&lt;br /&gt;
Inoltre, durante l'ultimo periodo di lezioni del secondo semestre, vi sarà un periodo di ripasso del programma tramite esercizi, in vista dell'esame.&lt;br /&gt;
Durante la lezione del 3/10 sono state indicate alcune propedeuticità del corso. tra cui: programmazione, algoritmi e architettura degli elaboratori in primis.&lt;br /&gt;
Importanti sono anche i corsi di Analisi matematica e Algebra e geometria. Per farla breve, il primo anno è propedeutico al secondo.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* Why:&lt;br /&gt;
----&lt;br /&gt;
* '''Fonti e strumenti del corso:''' wiki creata di gruppo, lezioni frontali e esercitazioni.&lt;br /&gt;
**Non esiste un vero e proprio testo consigliato. Tutte le informazioni sono date DURANTE il corso.&lt;br /&gt;
** Per domande specifiche scrivere su mailing list (so@cs.unibo.it);&lt;br /&gt;
** Per esercizi, appunti e programma svolto rivolgersi al wiki (so.v2.cs.unibo.it);&lt;br /&gt;
** Per live streaming (www.cs.unibo.it/~renzo/live/).&lt;br /&gt;
* '''Modalit&amp;amp;agrave; di esame:''' esame scritto (diviso in due parti: una parte generale e una di programmazione concorrente) + progetto + orale (facoltativo), possibilità di dare solo lo scritto per ottenere un massimo di 18. La modalità per studenti in &amp;quot;difficoltà&amp;quot; è disponibile solo fino agli appelli autunnali.&lt;br /&gt;
** Si parlerà del progetto indicativamente a partire da Dicembre 2017.&lt;br /&gt;
** L'orale può essere sostenuto da chi vuole migliorare (peggiorare) il voto ottenuto, o da chi vuole ottenere la lode.&lt;br /&gt;
* '''Orario di ricevimento per il primo semestre (fino al 15/09/17):''' martedì alle 11:30.&lt;br /&gt;
* Universit&amp;amp;agrave; = docenti + studenti.&lt;br /&gt;
* Informatica = come generare informazione automatica.&lt;br /&gt;
* Hardware, Software, Elaborazione, Comunicazione, Memorizzazione, Digitale/Analogico.&lt;br /&gt;
La principale distinzione che facciamo tra '''dato''' e '''informazione''' è che il dato, da solo, è privo di significato. Se, tuttavia, viene interpretato in un particolare contesto allora può diventare informazione significativa per chi sta interpretando i dati.&lt;br /&gt;
&lt;br /&gt;
Un '''algoritmo''' è una o più sequenze non ambigue di passi che, dato un problema, ci permette di risolverlo. &lt;br /&gt;
&lt;br /&gt;
Il nostro algoritmo, scritto in un qualche linguaggio formale, diventa un '''programma''' (sostanzialmente un testo, un insieme di istruzioni).&lt;br /&gt;
&lt;br /&gt;
Un linguaggio è un entità software, ed è definito come una quadrupla (alfabeto, sintassi, lessico, semantica) dove:&lt;br /&gt;
*l'''alfabeto'' è l'insieme di simboli che compone il linguaggio.&lt;br /&gt;
*il ''lessico'' si può vedere come una funzione che va dai simboli a un booleano e ci dice quali sono le frasi ben formate.&lt;br /&gt;
*la ''sintassi'' determina quali delle sequenze scritte sono effettivamente corrette.&lt;br /&gt;
*la ''semantica'' associa un significato alle parole ben formate.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 29 settembre 2017 ==&lt;br /&gt;
'''Cos'è un sistema operativo e un po' di storia'''&lt;br /&gt;
&lt;br /&gt;
Un ''sistema operativo'' è un programma che gestisce i processi, le risorse e interfaccia le applicazioni con l'hardware dell'elaboratore.&lt;br /&gt;
&lt;br /&gt;
In particolare, il sistema operativo (laddove esiste) è il primo processo ad essere attivato e resta in vita fino allo spegnimento del calcolatore o al sopraggiungere di un errore fatale per il sistema.&lt;br /&gt;
&lt;br /&gt;
A cosa serve un sistema operativo?&lt;br /&gt;
Principalmente, ha i seguenti scopi:&lt;br /&gt;
# Facilitare l'utilizzo del sistema (senza S.O. sarebbe a carico del programmatore conoscere tutti i linguaggi con cui si comunica con l'architettura della macchina);&lt;br /&gt;
# Rendere affidabile, protetto e sicuro l'utilizzo del sistema (e.g. un processo potrebbe recar danno all'intero sistema se non controllato o gestito, potrebbe ignorare i permessi di visualizzazione di un file);&lt;br /&gt;
# Astrarre l'hardware (e.g. filesystem);&lt;br /&gt;
# Garantire l'efficienza (e.g. non tenere la CPU in idle adottando opportuni algoritmi di scheduling);&lt;br /&gt;
# Assicurare portabilità ;&lt;br /&gt;
&lt;br /&gt;
Un approccio molto usato è il cosiddetto approccio a strati (&amp;quot;a livelli&amp;quot;) in cui ogni strato utilizza i servizi forniti al livello inferiore e ne fornisce di nuovi a quello superiore.&lt;br /&gt;
Astraendo il nostro calcolatore possiamo piazzare al livello più basso l'hardware e, subito sopra, il sistema operativo. I due layer comunicano usando il linguaggio ISA (Instruction Set Architecture), nativo della CPU stessa, al quale si aggiungono quelli che permettono di comunicare con i vari controllori dei dispositivi come la scheda di rete, la stampante, etc. Sopra il livello del sistema operativo possiamo collocare quello delle librerie e, infine, quello degli applicativi.&lt;br /&gt;
&lt;br /&gt;
* '''Systemcall''' = meccanismo usato da un processo per richiedere al SO una qualche funzionalità a livello kernel. Un esempio è la funzione printf() del linguaggio C (stampa a video) che utilizza la systemcall write() (una delle systemcall per la gestione dei dispositivi).&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
----&lt;br /&gt;
'''Storia dei sistemi operativi'''&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''zero''' (1800 ca.) possiamo includere Babbage con la sua macchina analitica e lady Ada Lovelace. [[File:290px-AnalyticalMachine Babbage London.jpg |200px|thumb|right|Macchina analitica di Babbage]]&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''uno''' vediamo la comparsa delle valvole con tutti i problemi connessi. Non c'erano utilizzatori delle macchine, le stesse persone che le costruivano erano anche programmatori e fruitori delle stesse.&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''due''' (fine anni '60) arriva il transistor. Quest'ultimo è molto più veloce e piccolo della valvola e, soprattutto, meno incline a guasti. Le macchine iniziano ad essere più economiche grazie alla possibilità di avere un'economia di scala e quindi accessibili al grande pubblico. Questo fa si che, in queste prime fasi, i costruttori non siano più gli unici utilizzatori. [[File:Transistors.jpg |300px|thumb|right|Diversi tipi di Transistor]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
A questo punto poiché, pur essendo diventata più accessibile, una macchina costava ancora molto, nasce l'esigenza di non lasciare mai un processore senza lavoro (al fine di massimizzare i ricavi) e di condividere i dati.&lt;br /&gt;
In questa fase, infatti, abbiamo sistemi operativi di tipo ''batch'' (che collezionano tutto l'input all'inizio per calcolare e restituire l'output) dove l'inserimento di programmi e dati veniva fatto tramite schede perforate e non c'era interazione. Dai sistemi batch nasce lo ''SPOOL'' (Simultaneous Peripheral Operations On-Line).&lt;br /&gt;
&lt;br /&gt;
Si comincia a pensare di utilizzare i tempi di I/O per poter eseguire altri processi. Questo però solleva almeno due problematiche: da un lato serve un modo per sapere quando un input è davvero finito mentre, dall'altro, bisogna fare in modo che un processo che non richieda I/O non occupi la CPU per un tempo indefinito.&lt;br /&gt;
Per la prima problematica la soluzione è rappresentata dall'introduzione degli '''interrupt''', segnali elettrici inviati alla CPU alla fine di ogni input.&lt;br /&gt;
Per la seconda, invece, è stato introdotto il cosiddetto '''interval timer''' che non è altro che un dispositivo che invia interrupt dopo un determinato quanto di tempo assicurando che un processo non occupi per troppo tempo il processore.&lt;br /&gt;
Questo consente di realizzare sistemi time sharing (il cui più semplice algoritmo è il round-robin) dove un processo può essere in uno dei seguenti tre stati:&lt;br /&gt;
*'''READY''' pronto per essere eseguito ma, il processore è già occupato;&lt;br /&gt;
*'''RUNNING''' in esecuzione;&lt;br /&gt;
*'''WAIT''' in attesa di I/O.&lt;br /&gt;
&lt;br /&gt;
[[File:Dds.png]]&lt;br /&gt;
&lt;br /&gt;
Nella '''quarta''' generazione (anni '70 ca.) vediamo la nascita di molte innovazioni importanti. Si riesce a rimpicciolire di molto i processori facendoli divenire a tutti gli effetti micro-processori. Nei laboratori Bell nasce Unix, un sistema operativo rivoluzionario con time sharing e clonazione dei processi (un processo non viene mai creato dal nulla ma viene prima clonato da uno già esistente e poi, alla copia, viene fatto eseguire un programma). A causa di problemi di portabilità da PDP-9, macchina su cui Unix è nato, a PDP-11 nasce il linguaggio C e il suo compilatore (scritto anch'esso in C).&lt;br /&gt;
Mentre Apple I &amp;quot;porta&amp;quot; una nuova idea di pc per tutte le famiglie, nascono molte versioni di Unix.&lt;br /&gt;
Per impedire una deriva proprietaria in cui ogni produttore offriva tutto quello che offrivano gli altri ma con qualche feature in più, Richard Stallman fonda il progetto GNU (GNU's Not Unix) per il quale scrive tutta una serie di utilities come il famoso compilatore gcc.&lt;br /&gt;
A GNU, per essere operativo, manca però un kernel. Non volendo aspettare i tempi di sviluppo del kernel che Stallman aveva in mente, Linus Torlvalds scrive il kernel '''Linux''' da cui nasce il sistema operativo GNU/Linux.&lt;br /&gt;
&lt;br /&gt;
Nei tempi più recenti nascono i processori multi-core.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 3 ottobre 2017 ==&lt;br /&gt;
 &lt;br /&gt;
'''Introduzione alla programmazione concorrente: processi, thread e  librerie'''&lt;br /&gt;
&lt;br /&gt;
In programmazione concorrente le nozioni apprese di &amp;quot;algoritmo&amp;quot;, &amp;quot;programma&amp;quot; e &amp;quot;processo&amp;quot; devono essere aggiornate. Questo perché non vi è più un programma con un'unica sequenza esecutiva [o meglio, un unico filo (= thread) di esecuzione]. Appare ovvio come servino dei meccanismi per organizzare e sincronizzare tali fili esecutivi, in modo da evitare il verificarsi di eventi non desiderati.&lt;br /&gt;
&lt;br /&gt;
Quando parliamo di ''sistema concorrente'', ci riferiamo ad un sistema in cui due o più &amp;quot;attori&amp;quot; sono eseguiti parallelamente.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Questi attori possono essere '''processi''' o '''thread'''.&lt;br /&gt;
La differenza sostanziale tra queste due tipologie di attori riguarda la condivisione della memoria e dei dati su cui operano.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Più processi possono eseguire lo stesso programma ma, ognuno di essi, ha i propri dati su cui operare e il suo stato di avanzamento. In particolare:&lt;br /&gt;
* un processo è un'entità, un attore che ha delle proprietà e delle risorse associate;&lt;br /&gt;
* in ogni istante, il processo può essere interrotto.&amp;lt;br&amp;gt;&lt;br /&gt;
Più thread che vengono eseguiti parallelamente, invece, condividono gli stessi dati su cui operare.&amp;lt;br&amp;gt;&lt;br /&gt;
Codici di esempio:&lt;br /&gt;
&lt;br /&gt;
'''processi'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=C&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;unistd.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int main(int argc, char *argv[]){&lt;br /&gt;
   if(fork()) printf(&amp;quot;Yes&amp;quot;);&lt;br /&gt;
   else printf(&amp;quot;No&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
L'output generato sarà &amp;quot;YesNo&amp;quot; poiché, usando la systemcall ''fork()'', abbiamo creato una nuova copia del processo (un processo figlio). Il processo padre, che ha eseguito con successo la fork(), stamperà &amp;quot;Yes&amp;quot; mentre il figlio stamperà &amp;quot;No&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
'''thread'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=C&amp;gt;&lt;br /&gt;
#include &amp;lt;pthread.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
#include &amp;lt;assert.h&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
#define NUM_THREADS     5&lt;br /&gt;
#define MAX 1000000&lt;br /&gt;
&lt;br /&gt;
int sum;&lt;br /&gt;
 &lt;br /&gt;
void* perform_work(){&lt;br /&gt;
	int i;&lt;br /&gt;
	&lt;br /&gt;
	for(i = 0; i &amp;lt; MAX; i++) sum = sum + 1;&lt;br /&gt;
  	/* optionally: insert more useful stuff here */ &lt;br /&gt;
  	return NULL;&lt;br /&gt;
}&lt;br /&gt;
 &lt;br /&gt;
int main( int argc, char** argv ){&lt;br /&gt;
	pthread_t threads[NUM_THREADS];&lt;br /&gt;
  	int thread_args[NUM_THREADS];&lt;br /&gt;
  	int result_code;&lt;br /&gt;
  	unsigned index;&lt;br /&gt;
 &lt;br /&gt;
	sum = 0;&lt;br /&gt;
  	// create all threads one by one&lt;br /&gt;
  	for(index = 0; index &amp;lt; NUM_THREADS; ++index ){&lt;br /&gt;
    		thread_args[index] = index;&lt;br /&gt;
    		printf(&amp;quot;In main: creating thread %d\n&amp;quot;, index);&lt;br /&gt;
    		result_code = pthread_create(&amp;amp;threads[index], NULL, perform_work, NULL);&lt;br /&gt;
    		assert(!result_code);&lt;br /&gt;
  	} &lt;br /&gt;
  	// wait for each thread to complete&lt;br /&gt;
  	for(index = 0; index &amp;lt; NUM_THREADS; ++index ){&lt;br /&gt;
    		// block until thread 'index' completes&lt;br /&gt;
    		result_code = pthread_join(threads[index], NULL);&lt;br /&gt;
    		assert(!result_code);&lt;br /&gt;
    		printf(&amp;quot;In main: thread %d has completed\n&amp;quot;, index);&lt;br /&gt;
   	}&lt;br /&gt;
 &lt;br /&gt;
   	printf(&amp;quot;In main: All threads completed successfully\n&amp;quot; );&lt;br /&gt;
	printf(&amp;quot;Result: %d\n&amp;quot;, sum);&lt;br /&gt;
   	exit( EXIT_SUCCESS );&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Prima di discutere l'output generato dall'esecuzione di questo codice, è opportuno introdurre, brevemente, gli strumenti utilizzati.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
'''Pthread (POSIX thread)'''&lt;br /&gt;
&lt;br /&gt;
Citazione dal Silberschatz, Galvin, Gagne:&lt;br /&gt;
''Pthread si riferisce allo standard POSIX (IEEE 1003.1c) che definisce una API per la creazione e sincronizzazione dei thread.''&lt;br /&gt;
&lt;br /&gt;
Le istruzioni fondamentali sono:&lt;br /&gt;
* '''int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);''' che crea un thread;&lt;br /&gt;
* '''int pthread_join(pthread_t thread, void **retval);''' che aspetta che un thread sia completato prima di continuare l'esecuzione.&lt;br /&gt;
&lt;br /&gt;
Da ricordare che, per utilizzare pthread, va incluso l'header ''&amp;lt;pthread.h&amp;gt;'' e durante la compilazione va inclusa la libreria esterna tramite l'opzione ''-pthread''.&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
Torniamo all'output generato dall'esecuzione del secondo codice.&amp;lt;br&amp;gt;&lt;br /&gt;
Nel codice vengono generati cinque thread ed ognuno somma MAX (=1000000) volte 1 a sum. Ci aspettiamo un output di 5000000 ma, in realtà, viene stampato un valore molto minore.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Questo accade principalmente per due motivi:&lt;br /&gt;
*sommare una costante ad una variabile non è un'operazione atomica (richiede almeno tre istruzioni assembler: due mov ed una sum).&lt;br /&gt;
*l'interval timer può fermare l'esecuzione del thread mentre sta effettuando una delle tre operazioni richieste per aggiungere uno a sum e provocare un risultato errato.&lt;br /&gt;
&lt;br /&gt;
Questo è uno dei possibili problemi della programmazione concorrente conosciuto come ''race condition''.&amp;lt;br&amp;gt;&lt;br /&gt;
Un sistema di processi multipli presenta una '''race condition''' qualora il risultato finale dell'esecuzione dipenda dalla temporizzazione, o dall'ordine con cui i processi vengono eseguiti.&lt;br /&gt;
&lt;br /&gt;
Per risolvere questo problema possiamo adoperare ancora una volta pthread.&lt;br /&gt;
Nello specifico, ci venogono offerte istruzione per realizzare una '''mutua esclusione''' sulle risorse condivise in modo che l'operazione che prima non era atomiche,ora, lo sia e non porti più ad una computazione errata.&amp;lt;br&amp;gt;&lt;br /&gt;
Codice di esempio:&amp;lt;br&amp;gt;&lt;br /&gt;
'''Mutex'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=C&amp;gt;&lt;br /&gt;
#include &amp;lt;pthread.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
#include &amp;lt;assert.h&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
#define NUM_THREADS     5&lt;br /&gt;
#define MAX 1000000&lt;br /&gt;
&lt;br /&gt;
int sum;&lt;br /&gt;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;&lt;br /&gt;
 &lt;br /&gt;
void* perform_work(){&lt;br /&gt;
	int i;&lt;br /&gt;
	&lt;br /&gt;
	for(i = 0; i &amp;lt; MAX; i++){ &lt;br /&gt;
		pthread_mutex_lock(&amp;amp;mutex);		&lt;br /&gt;
		sum = sum + 1;&lt;br /&gt;
		pthread_mutex_unlock(&amp;amp;mutex);&lt;br /&gt;
	}&lt;br /&gt;
  	/* optionally: insert more useful stuff here */ &lt;br /&gt;
  	return NULL;&lt;br /&gt;
}&lt;br /&gt;
 &lt;br /&gt;
int main( int argc, char** argv ){&lt;br /&gt;
	pthread_t threads[NUM_THREADS];&lt;br /&gt;
  	int thread_args[NUM_THREADS];&lt;br /&gt;
  	int result_code;&lt;br /&gt;
  	unsigned index;&lt;br /&gt;
&lt;br /&gt;
	sum = 0;&lt;br /&gt;
  	// create all threads one by one&lt;br /&gt;
  	for(index = 0; index &amp;lt; NUM_THREADS; ++index ){&lt;br /&gt;
    		thread_args[index] = index;&lt;br /&gt;
    		printf(&amp;quot;In main: creating thread %d\n&amp;quot;, index);&lt;br /&gt;
    		result_code = pthread_create(&amp;amp;threads[index], NULL, perform_work, NULL);&lt;br /&gt;
    		assert(!result_code);&lt;br /&gt;
  	} &lt;br /&gt;
  	// wait for each thread to complete&lt;br /&gt;
  	for(index = 0; index &amp;lt; NUM_THREADS; ++index ){&lt;br /&gt;
    		// block until thread 'index' completes&lt;br /&gt;
    		result_code = pthread_join(threads[index], NULL);&lt;br /&gt;
    		assert(!result_code);&lt;br /&gt;
    		printf(&amp;quot;In main: thread %d has completed\n&amp;quot;, index);&lt;br /&gt;
   	}&lt;br /&gt;
 &lt;br /&gt;
   	printf(&amp;quot;In main: All threads completed successfully\n&amp;quot; );&lt;br /&gt;
	printf(&amp;quot;Result: %d\n&amp;quot;, sum);&lt;br /&gt;
   	exit( EXIT_SUCCESS );&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
'''MUTEX'''&lt;br /&gt;
&lt;br /&gt;
Un mutex è un dispositivo di MUTual EXclusion (mutua esclusione), ed è utile per proteggere le strutture dati condivise da modifiche concorrenti, implementare sezioni critiche e monitor.&lt;br /&gt;
&lt;br /&gt;
Un mutex ha due possibili stati: sbloccato (non posseduto da nessun thread) e bloccato (posseduto da un thread). Un mutex non può mai essere posseduto da due thread contemporaneamente.&amp;lt;br&amp;gt;Se un thread provasse ad acquisire il mutex, mentre quest'ultimo è già in possesso di un altro thread, dovrebbe aspettare finché non viene rilasciato.&lt;br /&gt;
La variabile che realizza il mutex, di tipo ''pthread_mutex_t'', viene inizializzata staticamente assegnandogli PTHREAD_MUTEX_INITIALIZER.&lt;br /&gt;
'''pthread_mutex_lock''' acquisisce immediatamente il mutex, se libero. Altrimenti aspetta fino a trovarla, prima o poi, libera.&amp;lt;br&amp;gt;&lt;br /&gt;
'''pthread_mutex_unlock''' rilascia il mutex.&lt;br /&gt;
&lt;br /&gt;
[da: https://sourceware.org/pthreads-win32/manual/pthread_mutex_init.html]&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
Con questa soluzione abbiamo raggiunto un risultato esatto pur avendo pagato in prestazioni: la computazione infatti è più lenta a causa del tempo richiesto dalla sincronizzazione tra i thread.&lt;br /&gt;
&lt;br /&gt;
Consideriamo ora due scenari leggermente più complicati:&lt;br /&gt;
* nel primo abbiamo due processi, A e B, che devono accedere a due risorse, X e Y, contemporaneamente prima di poter terminare. Cosa succede se, mentre A acquisisce X, B acquisisce Y?&lt;br /&gt;
* nel secondo abbiamo tre processi, A, B e C, che devono accedere alla risorsa X. Che succede se A e B, essendo più veloci, occupano insistentemente la risorsa e non permettono a C di utilizzarla?&lt;br /&gt;
&lt;br /&gt;
La prima condizione si chiama '''deadlock''', la seconda condizione si chiama '''starvation'''. Il deadlock (stallo) è un problema che non può scomparire autonomamente, mentre la starvation (&amp;quot;inedia&amp;quot;) si può risolvere da sola. A tal proposito ci viene incontro l'assioma di '''finite progress''': &amp;quot;ogni processo che può avanzare, prima o poi lo farà&amp;quot;.&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
In ogni caso, per parlare in maniera più approfondita di queste tematiche:&lt;br /&gt;
* avremo bisogno di un modello di riferimento&lt;br /&gt;
* avremo bisogno di paradigmi in grado di esprimere la concorrenza (alcuni paradigmi saranno rivolti alla concorrenza a memoria privata; altri al multithreading a risorse condivise)&lt;br /&gt;
* dovremo imparare a scrivere programmi&lt;br /&gt;
Nel nostro modello di riferimento l'assegnamento di costanti è un operazione atomica. Dagli esempi precedenti, infatti, si evince come ci sia bisogno di creare atomicità in sezioni critiche. Inoltre, il nostro modello dovrà rispettare le proprietà di safety e di liveness, ovvero:&lt;br /&gt;
* Tutti i processi danno la stessa risposta;&lt;br /&gt;
* Ogni processo corretto da come risposta una tra quelle proposte;&lt;br /&gt;
* Ogni processo prima o poi fornisce una risposta.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 6 ottobre 2017 ==&lt;br /&gt;
'''Il linguaggio C'''&lt;br /&gt;
&lt;br /&gt;
Il linguaggio C nasce nei primi anni 70 grazie al lavoro di Dennis Ritchie con lo scopo di scrivere il &lt;br /&gt;
sistema operativo Unix. Quali sono le caratteristiche di C e cosa lo rende un linguaggio adatto per scrivere un sistema&lt;br /&gt;
operativo? Un linguaggio per scrivere un sistema operativo deve:&lt;br /&gt;
* essere indipendente dalla macchina su cui viene scritto&lt;br /&gt;
* dare la possibilità di lavorare direttamente con la memoria&lt;br /&gt;
* permette di andare a modificare i singoli bit in memoria&lt;br /&gt;
* essere semplice da usare&lt;br /&gt;
* essere massimamente produttivo&lt;br /&gt;
* essere veloce, efficiente&lt;br /&gt;
C presenta le caratteristiche dette sopra e alcune altre:&lt;br /&gt;
* in C, dal punto di vista sintattico, tutto è funzione. Si può vedere un programma in C come un insieme di funzioni. La funzione principale è il '''main'''. L'unica cosa che la distingue è il nome, a parte quello il main è una funzione come le altre. Prende due argomenti: argcount e argvalue, che contengono rispettivamente il numero degli argomenti e un array contenente gli argomenti stessi. L'interfaccia del main è tipicamente: &amp;lt;syntaxhighlight lang=C&amp;gt; int main(int argc, char *argv[]) &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
* il costrutto fondamentale è l'espressione. ''expression'' ; = ''instruction''&lt;br /&gt;
* sono previste istruzioni di struttura &amp;lt;syntaxhighlight lang=C&amp;gt;if, while, switch, etc.&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
* le variabili, per poter essere utilizzate, devono essere sempre definite&lt;br /&gt;
* il C è un linguaggio fortemente tipato, però con un certo numero di eccezioni: ad esempio, è possibile fare il casting esplicito di un puntatore void * in qualsiasi tipo, senza che il compilatore dia errori o warnings, con conseguente perdita di type safety&lt;br /&gt;
* C è un linguaggio &amp;quot;per adulti&amp;quot;: non impone una struttura ordinata al codice, come ad esempio fa invece Python, ed è piena responsabilità del programmatore scrivere programmi ordinati/leggibili. Inoltre molte operazioni che in altri linguaggi non sarebbero permesse lo sono invece in C: anche in questo caso è responsabilità del programmatore fare corretto uso del linguaggio&lt;br /&gt;
* C è un linguaggio molto semplice. Come si sopperisce alle mancanze imposte da questa semplicità? Grazie alle librerie: in C tutte le funzionalità aggiuntive (non fornite dal linguaggio stesso, come l'allocazione di memoria, la stampa a schermo, la gestione di input e output) sono fornite dalle librerie&lt;br /&gt;
* C è anche sintatticamente semplice. Dove va a finire la complessità sintattica? Nel preprocessore: prima di essere compilato un programma in C passa attraverso un preprocessore, che produce codice in base alle direttive di preprocessione date nel codice stesso &amp;lt;syntaxhighlight lang=C&amp;gt;#ifdef VAR ... #endif, # e ## operator, __LINE__,etc&amp;lt;/syntaxhighlight&amp;gt; &lt;br /&gt;
* in C vi sono fondamentalmente due tipi di dato: int e float. Tutti gli altri &amp;quot;tipi&amp;quot; di dato sono derivati da essi. Si hanno diversi modificatori, che vanno a modificare la quantità di memoria allocata per la variabile. Alcuni di questi sono &amp;lt;syntaxhighlight lang=C&amp;gt;long, short, char, etc&amp;lt;/syntaxhighlight&amp;gt; Un dato di tipo long int ha, solitamente, la dimensione di una parola di memoria della macchina su cui è eseguito il programma. char alloca 8 bits. Se si prende una variabile di un certo tipo e lo si assegna ad una di tipo più ampio, il valore viene mantenuto. Viceversa quando si assegna una variabile di un tipo ad una di tipo meno ampio, una volta &amp;quot;superato il limite&amp;quot; si torna indietro ciclicamente al valore iniziale. Per queste situazioni il compilatore non dà né warnings né errors: sono perfettamente lecite in C&lt;br /&gt;
* in C si passa una varibile ad una funzione solo per valore; per passare &amp;quot;per riferimento&amp;quot; una variabile si passa per valore l'indirizzo della variabile&lt;br /&gt;
* in C si può fare uso di puntatori alla memoria. Dato un puntatore p, un'istruzione del tipo p+i va letta come: vai avanti di i posizioni della grandezza del tipo del puntatore ognuno. Un vettore in C non è altro che un puntatore che non può essere assegnato. Una scrittura del tipo array[i] è una abbreviazione di *(array + i). Non c'è controllo in C se vado oltre la memoria allocata per l'array. Perché? In Pascal, ad esempio, c'è un tale controllo. Il motivo per cui in C questo manca è che questo controllo genera ulteriore codice macchina. Dovendo C essere efficiente, non ci si può permettere un tale costo aggiuntivo. La responsabilità di non andare oltre è lasciata al programmatore&lt;br /&gt;
&lt;br /&gt;
Esercizio svolto che prende in input due valori tramite i parametri del metodo '''main''', li converte da sequenze di caratteri ad interi, per poi stamparli utilizzando delle '''System Call'''. Lo scopo dell’esercizio è dimostrare come sia possibile utilizzare il linguaggio C senza l’utilizzo di librerie particolari (ad eccezione di quelle per le System Call).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=C&amp;gt;&lt;br /&gt;
#define _GNU_SOURCE&lt;br /&gt;
#include &amp;lt;unistd.h&amp;gt;&lt;br /&gt;
#include &amp;lt;sys/syscall.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int _uatoi(char *s, int value){&lt;br /&gt;
  if (*s == 0)&lt;br /&gt;
    return value;&lt;br /&gt;
  else if (*s &amp;gt;= '0' &amp;amp;&amp;amp; *s &amp;lt;= '9'){&lt;br /&gt;
    value = value * 10 + (*s - '0');&lt;br /&gt;
    return _uatoi(s+1, value);&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int uatoi(char *s){&lt;br /&gt;
  return _uatoi(s, 0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
char *uitoa(int val, char *buf, int len){&lt;br /&gt;
  int i;&lt;br /&gt;
  for (len = len - 1; len &amp;gt;= 0 &amp;amp;&amp;amp; val &amp;gt; 0; len--, val /= 10){&lt;br /&gt;
    buf[len] = '0' + (val %10);&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  return buf + len + 1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#define MAXOUTLEN 10&lt;br /&gt;
int main (int argc, char *argv[]) {&lt;br /&gt;
    int tot = 0;&lt;br /&gt;
    int i;&lt;br /&gt;
    char buf[MAXOUTLEN];&lt;br /&gt;
    char *result;&lt;br /&gt;
    for (i = 1; i &amp;lt; argc; i++)&lt;br /&gt;
      tot += uatoi(argv[i]);&lt;br /&gt;
    result = uitoa(tot, buf, MAXOUTLEN);&lt;br /&gt;
    syscall(__NR_write, 1, result, MAXOUTLEN - (result - buf));&lt;br /&gt;
    syscall(__NR_write, 1, &amp;quot;\n&amp;quot;, 1);&lt;br /&gt;
    return tot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Lezione del 10 ottobre 2017 ==&lt;br /&gt;
'''Programmazione concorrente: notazione e un primo problema'''&lt;br /&gt;
&lt;br /&gt;
Esempio di possibile rappresentazione (che non useremo) di codice concorrente.&amp;lt;br&amp;gt;&lt;br /&gt;
'''''A'', ''B'', ''C'', ''D'', ''E''''' rappresentano porzioni di codice.&amp;lt;br&amp;gt;&lt;br /&gt;
'''''//''''' rappresenta esecuzione parallela&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
A&lt;br /&gt;
cobegin&lt;br /&gt;
  B&lt;br /&gt;
//&lt;br /&gt;
  C&lt;br /&gt;
//&lt;br /&gt;
  D&lt;br /&gt;
coend&lt;br /&gt;
E&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il codice sarà eseguito come: ''A'' -&amp;gt; ''BCD'' -&amp;gt; ''E''. ''E'' quindi verrà eseguito solo dopo la fine dell'esecuzione di ''BCD''.&lt;br /&gt;
&lt;br /&gt;
Un esempio che si presta molto bene ad illustrare la programmazione concorrente è quello del '''merge sort''' concorrente:&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
leggi vettore&lt;br /&gt;
cobegin&lt;br /&gt;
  ordina la prima metà del vettore&lt;br /&gt;
//&lt;br /&gt;
  ordina la seconda metà del vettore&lt;br /&gt;
coend&lt;br /&gt;
merge (miscela) i risultati&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Versione errata del merge sort concorrente:&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
leggi vettore&lt;br /&gt;
cobegin&lt;br /&gt;
  ordina la prima metà del vettore&lt;br /&gt;
//&lt;br /&gt;
  ordina la seconda metà del vettore&lt;br /&gt;
//  &lt;br /&gt;
  merge (miscela) i risultati&lt;br /&gt;
coend&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avendo un numero ''N'' di processi, vorremmo avere una notazione che ci permetta di definire una porzione di codice come atomica (la sezione critica). Come possiamo procedere?&amp;lt;br&amp;gt;&lt;br /&gt;
Teoricamente, vorremmo avere due funzioni, '''csenter()''' e '''csexit()''' che ci permettano di entrare e uscire dalla sezione critica del codice senza causare errori nella computazione.&amp;lt;br&amp;gt;&lt;br /&gt;
Quali sono le proprietà che queste funzioni debbono rispettare?&lt;br /&gt;
# Devono garantire la mutua esclusione (1 solo processo esegue la critical section alla volta). Contando ''n'' csenter() completate e ''m'' csexit() completate la differenza deve essere &amp;lt;=1&lt;br /&gt;
# Non devono fare deadlock tra di loro&lt;br /&gt;
# Non devono fare starvation tra loro&lt;br /&gt;
# ''(no unnecessary delay)'' un processo che non sta usando la critical section {csenter()..csexit()} non deve bloccare un altro processo dall'entrarvi.&lt;br /&gt;
&lt;br /&gt;
La soluzione a questo problema è nota come '''soluzione di Dekker''' (scritta e pubblicata da Dijkstra) che venne presentata in modo graduale attraverso l'utilizzo di quattro possibili, ma sbagliate, soluzioni.&lt;br /&gt;
&lt;br /&gt;
Vorremmo avere programmi che abbiano la seguente struttura:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
process[i], i=1,...,N&lt;br /&gt;
&lt;br /&gt;
while(1):&lt;br /&gt;
  csenter() //entrata nella sezione critica (critical section)&lt;br /&gt;
  critical code&lt;br /&gt;
  csexit() //uscita dalla sezione critica&lt;br /&gt;
  non-critical code&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ad esempio, tornando al problema dei thread che sommavano 1 a ''sum'' MAX volte, avremo:&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
process A:&lt;br /&gt;
&lt;br /&gt;
  for (i = 0; i &amp;lt; MAX; i++)&lt;br /&gt;
    csenter()&lt;br /&gt;
    sum += 1 //atomicità descritta da csenter(), csexit()&lt;br /&gt;
    // &amp;lt; sum += 1 &amp;gt; atomicità lasciata al processore&lt;br /&gt;
    csexit()&lt;br /&gt;
&lt;br /&gt;
process B:&lt;br /&gt;
&lt;br /&gt;
  for (i = 0; i &amp;lt; MAX; i++)&lt;br /&gt;
    csenter()&lt;br /&gt;
    sum += 1&lt;br /&gt;
    csexit()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
----&lt;br /&gt;
'''Soluzione 1: A turni'''&lt;br /&gt;
&lt;br /&gt;
Qual è il problema?&amp;lt;br&amp;gt;&lt;br /&gt;
*'''mutex'''                   OK&amp;lt;br&amp;gt;&lt;br /&gt;
*'''deadlock'''                OK&amp;lt;br&amp;gt;&lt;br /&gt;
*'''starvation'''              OK&amp;lt;br&amp;gt;&lt;br /&gt;
*'''no unnecessary delay'''    NO&amp;lt;br&amp;gt;&lt;br /&gt;
:: Perché se P ha bisogno spesso della critical section, P non deve aspettare che Q chieda la critical section e, all'uscita, lo autorizzi di nuovo ad usarla impostando ''turn = P''. Se Q una volta richiesta la sezione non la usa ma si ferma, P non può richiederla ancora perché appartiene ancora a Q.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
&lt;br /&gt;
turn=P&lt;br /&gt;
&lt;br /&gt;
process P:&lt;br /&gt;
  while(1)&lt;br /&gt;
    while (turn != P)&lt;br /&gt;
      ;&lt;br /&gt;
    critical code&lt;br /&gt;
    turn = Q&lt;br /&gt;
    non-critical code&lt;br /&gt;
&lt;br /&gt;
process Q:&lt;br /&gt;
  while(1)&lt;br /&gt;
    while (turn != Q)&lt;br /&gt;
    critical code&lt;br /&gt;
    turn = P&lt;br /&gt;
    non-critical code&lt;br /&gt;
 &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
'''Soluzione 2: con 2 variabili''', cerchiamo di risolvere il problema dei turni&lt;br /&gt;
&lt;br /&gt;
Finché Q usa la sezione P aspetta, quando Q ha finito P prende la sezione critica e non ci sono ritardi indesiderati.&lt;br /&gt;
&lt;br /&gt;
*'''starvation'''              OK&lt;br /&gt;
*'''deadlock'''                OK&lt;br /&gt;
*'''no unnecessary delay'''    OK&lt;br /&gt;
*'''mutex'''                   NO &lt;br /&gt;
:: Possono essere entrambi come false, escono insieme nel while, tutti e due mettono a true ed entrano assieme nel while.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
&lt;br /&gt;
inP = false;&lt;br /&gt;
inQ = false;&lt;br /&gt;
&lt;br /&gt;
process P:&lt;br /&gt;
  while(1)&lt;br /&gt;
    while (inQ)&lt;br /&gt;
      ;&lt;br /&gt;
    inP = true&lt;br /&gt;
    critical code&lt;br /&gt;
    inP = false&lt;br /&gt;
    non-critical code&lt;br /&gt;
&lt;br /&gt;
process Q:&lt;br /&gt;
  while(1)&lt;br /&gt;
    while (inP)&lt;br /&gt;
      ;&lt;br /&gt;
    inQ = true&lt;br /&gt;
    critical code&lt;br /&gt;
    inQ = false&lt;br /&gt;
    non-critical code&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
'''Soluzione 3: il problema di soluzione 2 è che i processi non sono al corrente dell'intenzione dell'altro di entrare''' nel while quindi si crea il problema.&lt;br /&gt;
*'''mutex'''                   OK&lt;br /&gt;
*'''deadlock'''                NO (potrebbero entrambi richiedere, contemporaneamente, l'ingresso alla critical section)&lt;br /&gt;
*'''starvation'''              OK&lt;br /&gt;
*'''non unnecessary delay'''   OK&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
needP = false;&lt;br /&gt;
needQ = false;&lt;br /&gt;
&lt;br /&gt;
process P:&lt;br /&gt;
  while(1)&lt;br /&gt;
    needP = true&lt;br /&gt;
    while (needQ)&lt;br /&gt;
      ;&lt;br /&gt;
    critical code&lt;br /&gt;
    needP = false&lt;br /&gt;
    non-critical code&lt;br /&gt;
&lt;br /&gt;
process Q:&lt;br /&gt;
  while(1)&lt;br /&gt;
    needQ = true&lt;br /&gt;
    while (needP)&lt;br /&gt;
      ;&lt;br /&gt;
    critical code&lt;br /&gt;
    needQ = false&lt;br /&gt;
    non-critical code&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
'''Soluzione 4''':&lt;br /&gt;
&lt;br /&gt;
*'''mutex'''                 OK&lt;br /&gt;
*'''deadlock'''              OK&lt;br /&gt;
*'''no unnecessary delay'''  OK&lt;br /&gt;
*'''starvation'''            NO &lt;br /&gt;
:: Perché può esistere il processo che prova sempre a chiedere nel momento sbagliato e non esegue mai.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
inP = false;&lt;br /&gt;
inQ = false;&lt;br /&gt;
&lt;br /&gt;
process P:&lt;br /&gt;
  while(1)&lt;br /&gt;
    inP = true&lt;br /&gt;
    while (inQ){&lt;br /&gt;
        inP = false;&lt;br /&gt;
        delay&lt;br /&gt;
        inP = true;&lt;br /&gt;
    }&lt;br /&gt;
    critical code&lt;br /&gt;
    inP = false&lt;br /&gt;
    non-critical code&lt;br /&gt;
&lt;br /&gt;
process Q:&lt;br /&gt;
  while(1)&lt;br /&gt;
    inQ = true&lt;br /&gt;
    while (inQ){&lt;br /&gt;
        inQ = false;&lt;br /&gt;
        delay&lt;br /&gt;
        inQ = true;&lt;br /&gt;
    }&lt;br /&gt;
    critical code&lt;br /&gt;
    inQ = false&lt;br /&gt;
    non-critical code&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
'''Soluzione finale''':&amp;lt;br&amp;gt;&lt;br /&gt;
Uniamo soluzione 1 (poco simmetrica) e soluzione 4 (troppo simmetrica) usando quello che ci serve (attraverso ''needP'' e ''needQ'') e, se dovessimo essere tutti e due contemporaneamente a voler entrare, usiamo i turni (''turn'') per entrare nella critical section (settando il need dell'altro processo a false).&lt;br /&gt;
&lt;br /&gt;
*'''mutex'''                         OK&lt;br /&gt;
*'''deadlock'''                      OK&lt;br /&gt;
*'''starvation'''                    OK&lt;br /&gt;
*'''non unnecessary delay'''         OK&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
needP = false;&lt;br /&gt;
needQ = false;&lt;br /&gt;
turn = P&lt;br /&gt;
&lt;br /&gt;
process P:&lt;br /&gt;
  while(1)&lt;br /&gt;
    needP = true&lt;br /&gt;
    while (needQ)&lt;br /&gt;
      if (turn != P) {&lt;br /&gt;
        needP = false;&lt;br /&gt;
        while ( turn != P)&lt;br /&gt;
           ; /* busy wait */&lt;br /&gt;
        needP = true;&lt;br /&gt;
    }&lt;br /&gt;
    /*critical code*/&lt;br /&gt;
    turn = Q&lt;br /&gt;
    needP = false&lt;br /&gt;
    /*non-critical code*/&lt;br /&gt;
&lt;br /&gt;
process P:&lt;br /&gt;
  while(1)&lt;br /&gt;
    needQ = true&lt;br /&gt;
    while (needP)&lt;br /&gt;
      if (turn != Q) {&lt;br /&gt;
        needQ = false;&lt;br /&gt;
        while ( turn != Q)&lt;br /&gt;
             ; /* busy wait */&lt;br /&gt;
        needQ = true;&lt;br /&gt;
    }&lt;br /&gt;
    /*critical code*/&lt;br /&gt;
    turn = P&lt;br /&gt;
    needQ = false&lt;br /&gt;
    /*non-critical code*/&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Lezione del 13 ottobre 2017 ==&lt;br /&gt;
'''Il linguaggio C: PREPROCESSORE'''&lt;br /&gt;
&lt;br /&gt;
Il '''preprocessore''' è uno strumento contenuto nella toolchain di '''gcc''' che esamina il codice sorgente prima che questo venga compilato, e &amp;quot;trasforma&amp;quot; il programma prima della compilazione; è propriamente definito come ''macro processor'', ovvero un esaminatore di '''macro'''.&lt;br /&gt;
Le '''macro''' sono delle abbreviazioni che in realtà definiscono delle strutture di codice più grandi, e tornano molto utili in diversi casi nello sviluppo di un programma in C (è buona norma scrivere l’identificatore completamente in '''MAIUSCOLO'''). Il preprocessore esamina le cosiddette '''direttive'''; per conoscere l’output del preprocessore è possibile digitare il comando &amp;lt;kbd&amp;gt;'''gcc -E''' ''{nomeDelSorgente}''&amp;lt;/kbd&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Un primo esempio è dato dalla direttiva '''&amp;lt;kbd&amp;gt;#define&amp;lt;/kbd&amp;gt;''', la quale crea una ''macro'', ovvero l’associazione di un identificatore (con o senza parametri) con una stringa di ''token''. Le macro così definite possono assumere quindi diverse funzioni:&lt;br /&gt;
# condizione per l’inclusione o meno di codice nel file che verrà passato al compilatore;&lt;br /&gt;
# inserimento di '''costanti''' all’interno del codice (notare che questa direttiva nasce molto prima della keyword '''const''' del linguaggio C);&lt;br /&gt;
# creazione di abbreviazioni a porzioni di codice più lunghe.&lt;br /&gt;
&lt;br /&gt;
* '''Caso 1''' &amp;lt;br&amp;gt; Il primo caso, che potrebbe essere definito come il più semplice, prevede la dichiarazione di un valore costante, su cui ci si baserà in seguito per includere o meno parti di codice. Per fare ciò occorre introdurre anche altre direttive del preprocessore: '''#ifdef''', '''ifndef''' ed '''endif''', le quali identificano un blocco di codice che deve essere incluso nel file da compilare solo se rispettivamente una macro è definita ''(#ifdef)'' o una macro non è definita ''(#ifndef)'', mentre la direttiva ''#endif'' indica la fine del blocco condizionale.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#define DEBUG&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
#ifdef DEBUG&lt;br /&gt;
    printf(&amp;quot;In main\n&amp;quot;);&lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
// la macro __linux__ è definita solamente nei sistemi Linux-based&lt;br /&gt;
#ifndef __linux__&lt;br /&gt;
    printf(&amp;quot;WARNING: not a Linux system\n&amp;quot;);&lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
:Da notare in questo caso l’utilizzo della macro '''__linux__''', una macro predefinita nei sistemi ''Linux-based'', che consente di discriminare il sistema operativo su cui il programma viene compilato, dando quindi la possibilità di compilare del codice diverso in base alla piattaforma utilizzata. &amp;lt;br&amp;gt;&lt;br /&gt;
*'''Caso 2''' &amp;lt;br&amp;gt;Il secondo caso prevede la presenza di un valore in seguito all’identificatore, che quindi verrà sostituito in ogni sua occorrenza all’interno del codice. È da far notare come questo meccanismo sia simile all’utilizzo della keyword '''const''' del linguaggio, ma quest’ultima nasce solo successivamente come alternativa ''type-safe'' (dal momento che le macro potrebbero essere utilizzate ovunque senza alcun tipo di controllo). Questo meccanismo torna ad esempio quando si vuole effettuare un’operazione un numero di volte prefissato; in questo modo, è sufficiente modificare il valore della macro nella sua definizione, per aggiornare tutto il programma che utilizzava quella macro al nuovo valore.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#define ITER_NUM 5&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
    int i;&lt;br /&gt;
    for (i = 0; i &amp;lt; ITER_NUM; i++)&lt;br /&gt;
        printf(&amp;quot;Iterazione numero %d\n&amp;quot;, i);&lt;br /&gt;
&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt; &amp;lt;br&amp;gt;&lt;br /&gt;
*'''Caso 3''' &amp;lt;br&amp;gt; L’ultimo caso analizzato è quello che può essere definito il più complesso tra i tre. L’identificatore della macro ora presenta anche dei parametri formali, all’interno di parentesi e separati da virgole. Tali parametri compaiono anche nella token string in cui vengono utilizzati (anche più volte) e su cui vengono eseguite delle operazioni. Questa particolarità è utile per permettere di ridurre porzioni di codice che vengono definite così una volta sola, e per essere utilizzate sarà sufficiente scrivere il nome dell’identificatore con i relativi parametri. Da notare come ci sia un numero considerevole di parentesi, che potrebbero quasi sembrare superflue. In realtà sono essenziali per assicurare il corretto ordine di esecuzione delle istruzioni una volta che la stringa di token sarà sostituita alle occorrenze dell’identificatore.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#define MAX(x, y) ((x) &amp;gt; (y) ? (x) : (y))&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
    printf(&amp;quot;%d\n&amp;quot;, MAX(10, 20));&lt;br /&gt;
&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
:Che produrrà, in seguito all’esecuzione del preprocessore:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
    printf(&amp;quot;%d\n&amp;quot;, ((10) &amp;gt; (20) ? (10) : (20)));&lt;br /&gt;
&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
:Occorre far notare che quella effettuata dal preprocessore non è altro che una '''string substitution''', cioè si limita a sostituire le macro utilizzate nel codice con la rispettiva definizione (ovviamente includendo eventuali parametri formali), ma non effettua alcun tipo di controllo. Ciò significa che occorre prestare molta attenzione quando si utilizzano le macro come nell’ultimo caso illustrato, perché potrebbero comparire dei '''side-effect''' inaspettati. Se si modifica l’esempio precedente in questo modo&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#define MAX(x, y) ((x) &amp;gt; (y) ? (x) : (y))&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
int i = 100;&lt;br /&gt;
    printf(&amp;quot;%d\n&amp;quot;, MAX(10, i++));&lt;br /&gt;
    printf(&amp;quot;%d\n&amp;quot;, MAX(10, i));&lt;br /&gt;
&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
:si otterrà un risultato che in un primo momento potrebbe essere inaspettato:&lt;br /&gt;
&amp;lt;kbd&amp;gt;&lt;br /&gt;
101&amp;lt;br&amp;gt;102&lt;br /&gt;
&amp;lt;/kbd&amp;gt;&lt;br /&gt;
:Ad un’analisi più approfondita tuttavia, è possibile accorgersi dell’errore: il preprocessore effettua una sostituzione testuale dei parametri al momento dell’utilizzo della macro; ciò significa che sostituisce ogni occorrenza del primo parametro nella ''token string'' con il primo valore fornito, e dualmente per il secondo parametro. Il codice che verrà sottoposto al compilatore conterrà quindi due incrementi della variabile &amp;lt;kbd&amp;gt;i&amp;lt;/kbd&amp;gt;. Un esempio a grandi linee delle operazioni eseguite dal compilatore è la seguente (per capirlo è necessario aver ben compreso la nozione di '''post-incremento''' della variabile i):&lt;br /&gt;
#assegno alla variabile i il valore 100&lt;br /&gt;
#10 è maggiore di i?&lt;br /&gt;
#NO, incremento i (ora i vale 101)&lt;br /&gt;
#scrivo i (101)&lt;br /&gt;
#incremento i (ora i vale 102)&lt;br /&gt;
#10 è maggiore di i?&lt;br /&gt;
#NO, scrivo i (102)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
Esistono molti altri operatori che si possono utilizzare all’interno delle macro, due esempi sono:&lt;br /&gt;
* l’operatore '''#''' che, se anteposto al nome di un parametro nella stringa di token, viene riconosciuto dal processore, che rimpiazza il parametro con il simbolo '''#''' anteposto con il proprio valore letterale convertito in una stringa costante;&lt;br /&gt;
* l’operatore '''##''' &amp;quot;combina&amp;quot; due token in uno: unisce il token alla sinistra e quello alla destra dell’operatore per farlo diventare un’unico token. Questo torna molto utile se uno dei due token proviene da un parametro della macro.&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
Per un esempio su questi ultimi due operatori: [http://so.v2.cs.unibo.it/wiki/index.php?title=Esercizi_di_%22lettura%22_di_in_linguaggio_C_2017/18#tables_and_preprocessor_tricks tables and preprocessors tricks]&lt;br /&gt;
----&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Per ulteriori approfondimenti sul preprocessore del C: [https://gcc.gnu.org/onlinedocs/cpp/ Approfondimenti sul preprocessore del C]&lt;br /&gt;
&lt;br /&gt;
== Lezione del 17 ottobre 2017 ==&lt;br /&gt;
&lt;br /&gt;
'''Oltre la soluzione di Dekker'''&lt;br /&gt;
&lt;br /&gt;
Nella scorsa lezione di teoria abbiamo visto la soluzione di Dekker dove due processi, per poter entrare ed eseguire la critical section, manifestano prima questo loro bisogno (&amp;lt;code&amp;gt;needP/needQ = true&amp;lt;/code&amp;gt;), controllano poi se anche l'altro processo ha comunicato questo bisogno e, in caso affermativo, verrà utilizzata una politica a turni per gestire l'entrata e l'uscita alla sezione critica.&lt;br /&gt;
&lt;br /&gt;
P.S. Nei sistemi reali (specialmente multi-core) la soluzione di Dekker necessita di istruzioni aggiuntive come &amp;lt;code&amp;gt;__syn_synchronize()&amp;lt;/code&amp;gt; e &amp;lt;code&amp;gt;sched_yeld()&amp;lt;/code&amp;gt;: la prima sincronizza il contenuto delle cache (assicurando la correttezza del risultato) mentre la seconda fa sì che il thread rinunci al suo tempo di CPU permettendo a qualche altro processo di avere la priorità.&lt;br /&gt;
&lt;br /&gt;
Questa soluzione ha però due limiti:&lt;br /&gt;
*non funziona con un numero di processi &amp;gt; 2&lt;br /&gt;
*è relativamente complicato da capire, si può fare di meglio!&lt;br /&gt;
&lt;br /&gt;
Un'evoluzione più lineare e facilmente adattabile a più di due processi è l''''algoritmo di Peterson''' che, nel caso base, ha la seguente struttura:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
needP = false;&lt;br /&gt;
needQ = false;&lt;br /&gt;
turn;&lt;br /&gt;
 &lt;br /&gt;
process P:&lt;br /&gt;
  while(1)&lt;br /&gt;
    /* entry protocol */&lt;br /&gt;
    needP = true&lt;br /&gt;
    turn = Q&lt;br /&gt;
    while(needQ &amp;amp;&amp;amp; turn != P)&lt;br /&gt;
      ; /* busy wait */&lt;br /&gt;
    /* critical section */&lt;br /&gt;
    needP = false&lt;br /&gt;
    /* non-critical section */&lt;br /&gt;
 &lt;br /&gt;
process Q:&lt;br /&gt;
  while(1)&lt;br /&gt;
    /* entry protocol */&lt;br /&gt;
    needQ = true&lt;br /&gt;
    turn = P&lt;br /&gt;
    while(needP &amp;amp;&amp;amp; turn != Q)&lt;br /&gt;
      ; /* busy wait */&lt;br /&gt;
    /* critical section */&lt;br /&gt;
    needQ = false&lt;br /&gt;
    /* non-critical section */&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Questa soluzione è più snella poiché i processi, invece di assegnare la variabile &amp;lt;code&amp;gt;turn&amp;lt;/code&amp;gt; all'altro processo come in Dekker, lo fanno prima.&amp;lt;br&amp;gt;&lt;br /&gt;
Possiamo vedere questa soluzione come se avesse una sorta di &amp;quot;anticamera&amp;quot; da cui bisogna necessariamente passare per poter accedere alla sezione critica.&amp;lt;br&amp;gt;&lt;br /&gt;
Entra nella critical section sempre l'ultimo processo a non essere entrato nell'anticamera.&amp;lt;br&amp;gt;&lt;br /&gt;
Questi concetti possono bessimo essere estesi nel caso in cui abbiamo più di due processi a patto di estendere l'anticamera con tanti &amp;quot;stage&amp;quot; quanti sono i processi.&lt;br /&gt;
&lt;br /&gt;
Queste soluzioni funzionano in pratica ma, ci sono ancora tutti i difetti di prima: '''busy wait''' che spreca cicli di CPU e una grande difficoltà nello scrivere programmi concorrenti in questo modo. Ci farebbero davvero comodo dei paradigmi di più alto livello.&lt;br /&gt;
&lt;br /&gt;
Davanti a noi si aprono principalmente due possibilità:&lt;br /&gt;
*l'hardware della macchina ci viene in aiuto mascherando gli interrupt (nei sistemi mono-core)&lt;br /&gt;
*l'hardware (e gli ingegneri) ci aiuta fornendoci un'apposita istruzione atomica: la '''TEST&amp;amp;SET'''&lt;br /&gt;
&lt;br /&gt;
== Lezione del 20 ottobre 2017 ==&lt;br /&gt;
== Lezione del 24 ottobre 2017 ==&lt;br /&gt;
== Lezione del 27 ottobre 2017 ==&lt;br /&gt;
== Lezione del 31 ottobre 2017 ==&lt;br /&gt;
== Lezione del 3 novembre 2017 ==&lt;br /&gt;
== Lezione del 7 novembre 2017 ==&lt;br /&gt;
== Lezione del 10 novembre 2017 ==&lt;br /&gt;
== Lezione del 14 novembre 2017 ==&lt;br /&gt;
== Lezione del 17 novembre 2017 ==&lt;br /&gt;
== Lezione del 21 novembre 2017 ==&lt;br /&gt;
== Lezione del 24 novembre 2017 ==&lt;br /&gt;
== Lezione del 28 novembre 2017 ==&lt;br /&gt;
== Lezione del 1 dicembre 2017 ==&lt;br /&gt;
== Lezione del 5 dicembre 2017 ==&lt;br /&gt;
== Lezione del 12 dicembre 2017 ==&lt;br /&gt;
== Lezione del 15 dicembre 2017 ==&lt;/div&gt;</summary>
		<author><name>Matt</name></author>
	</entry>
	<entry>
		<id>https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_I_semestre&amp;diff=2042</id>
		<title>Lezioni Anno Accademico 2017/18 I semestre</title>
		<link rel="alternate" type="text/html" href="https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_I_semestre&amp;diff=2042"/>
		<updated>2017-10-18T19:28:12Z</updated>

		<summary type="html">&lt;p&gt;Matt: /* Lezione del 17 ottobre 2017 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;''scrivete qui idee, riassunti dei concetti espressi, commenti approfondimenti sulle lezioni.''&lt;br /&gt;
&lt;br /&gt;
== Lezione del 26 settembre 2017 ==&lt;br /&gt;
'''Titolo della lezione: W&amp;amp;#x2074; H Y'''&lt;br /&gt;
&lt;br /&gt;
* What:&lt;br /&gt;
* Who:&lt;br /&gt;
----&lt;br /&gt;
Noi studenti ed &amp;quot;entrambi&amp;quot; i professori.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* When:&lt;br /&gt;
----&lt;br /&gt;
Il corso ha durata annuale. Verrà svolto ogni martedì e venerdì dalle 15:30 alle 18:30.&lt;br /&gt;
* La lezione del martedì sarà dedicata alla programmazione concorrente;&lt;br /&gt;
* La lezione del venerdì sarà dedicata alla parte generale. Terminerà circa 15 minuti prima dell'orario stabilito.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* Where:&lt;br /&gt;
----&lt;br /&gt;
Sempre in Aula 1 Ercolani (E1): DISI, Scuole Ercolani, Mura Anteo Zamboni 2B.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* How:&lt;br /&gt;
----&lt;br /&gt;
Il corso si svolgerà tramite:&lt;br /&gt;
* Lezioni frontali in aula.&lt;br /&gt;
* Attività di laboratorio.&lt;br /&gt;
* Esercitazioni.&lt;br /&gt;
&lt;br /&gt;
Inoltre, durante l'ultimo periodo di lezioni del secondo semestre, vi sarà un periodo di ripasso del programma tramite esercizi, in vista dell'esame.&lt;br /&gt;
Durante la lezione del 3/10 sono state indicate alcune propedeuticità del corso. tra cui: programmazione, algoritmi e architettura degli elaboratori in primis.&lt;br /&gt;
Importanti sono anche i corsi di Analisi matematica e Algebra e geometria. Per farla breve, il primo anno è propedeutico al secondo.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* Why:&lt;br /&gt;
----&lt;br /&gt;
* '''Fonti e strumenti del corso:''' wiki creata di gruppo, lezioni frontali e esercitazioni.&lt;br /&gt;
**Non esiste un vero e proprio testo consigliato. Tutte le informazioni sono date DURANTE il corso.&lt;br /&gt;
** Per domande specifiche scrivere su mailing list (so@cs.unibo.it);&lt;br /&gt;
** Per esercizi, appunti e programma svolto rivolgersi al wiki (so.v2.cs.unibo.it);&lt;br /&gt;
** Per live streaming (www.cs.unibo.it/~renzo/live/).&lt;br /&gt;
* '''Modalit&amp;amp;agrave; di esame:''' esame scritto (diviso in due parti: una parte generale e una di programmazione concorrente) + progetto + orale (facoltativo), possibilità di dare solo lo scritto per ottenere un massimo di 18. La modalità per studenti in &amp;quot;difficoltà&amp;quot; è disponibile solo fino agli appelli autunnali.&lt;br /&gt;
** Si parlerà del progetto indicativamente a partire da Dicembre 2017.&lt;br /&gt;
** L'orale può essere sostenuto da chi vuole migliorare (peggiorare) il voto ottenuto, o da chi vuole ottenere la lode.&lt;br /&gt;
* '''Orario di ricevimento per il primo semestre (fino al 15/09/17):''' martedì alle 11:30.&lt;br /&gt;
* Universit&amp;amp;agrave; = docenti + studenti.&lt;br /&gt;
* Informatica = come generare informazione automatica.&lt;br /&gt;
* Hardware, Software, Elaborazione, Comunicazione, Memorizzazione, Digitale/Analogico.&lt;br /&gt;
La principale distinzione che facciamo tra '''dato''' e '''informazione''' è che il dato, da solo, è privo di significato. Se, tuttavia, viene interpretato in un particolare contesto allora può diventare informazione significativa per chi sta interpretando i dati.&lt;br /&gt;
&lt;br /&gt;
Un '''algoritmo''' è una o più sequenze non ambigue di passi che, dato un problema, ci permette di risolverlo. &lt;br /&gt;
&lt;br /&gt;
Il nostro algoritmo, scritto in un qualche linguaggio formale, diventa un '''programma''' (sostanzialmente un testo, un insieme di istruzioni).&lt;br /&gt;
&lt;br /&gt;
Un linguaggio è un entità software, ed è definito come una quadrupla (alfabeto, sintassi, lessico, semantica) dove:&lt;br /&gt;
*l'''alfabeto'' è l'insieme di simboli che compone il linguaggio.&lt;br /&gt;
*il ''lessico'' si può vedere come una funzione che va dai simboli a un booleano e ci dice quali sono le frasi ben formate.&lt;br /&gt;
*la ''sintassi'' determina quali delle sequenze scritte sono effettivamente corrette.&lt;br /&gt;
*la ''semantica'' associa un significato alle parole ben formate.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 29 settembre 2017 ==&lt;br /&gt;
'''Cos'è un sistema operativo e un po' di storia'''&lt;br /&gt;
&lt;br /&gt;
Un ''sistema operativo'' è un programma che gestisce i processi, le risorse e interfaccia le applicazioni con l'hardware dell'elaboratore.&lt;br /&gt;
&lt;br /&gt;
In particolare, il sistema operativo (laddove esiste) è il primo processo ad essere attivato e resta in vita fino allo spegnimento del calcolatore o al sopraggiungere di un errore fatale per il sistema.&lt;br /&gt;
&lt;br /&gt;
A cosa serve un sistema operativo?&lt;br /&gt;
Principalmente, ha i seguenti scopi:&lt;br /&gt;
# Facilitare l'utilizzo del sistema (senza S.O. sarebbe a carico del programmatore conoscere tutti i linguaggi con cui si comunica con l'architettura della macchina);&lt;br /&gt;
# Rendere affidabile, protetto e sicuro l'utilizzo del sistema (e.g. un processo potrebbe recar danno all'intero sistema se non controllato o gestito, potrebbe ignorare i permessi di visualizzazione di un file);&lt;br /&gt;
# Astrarre l'hardware (e.g. filesystem);&lt;br /&gt;
# Garantire l'efficienza (e.g. non tenere la CPU in idle adottando opportuni algoritmi di scheduling);&lt;br /&gt;
# Assicurare portabilità ;&lt;br /&gt;
&lt;br /&gt;
Un approccio molto usato è il cosiddetto approccio a strati (&amp;quot;a livelli&amp;quot;) in cui ogni strato utilizza i servizi forniti al livello inferiore e ne fornisce di nuovi a quello superiore.&lt;br /&gt;
Astraendo il nostro calcolatore possiamo piazzare al livello più basso l'hardware e, subito sopra, il sistema operativo. I due layer comunicano usando il linguaggio ISA (Instruction Set Architecture), nativo della CPU stessa, al quale si aggiungono quelli che permettono di comunicare con i vari controllori dei dispositivi come la scheda di rete, la stampante, etc. Sopra il livello del sistema operativo possiamo collocare quello delle librerie e, infine, quello degli applicativi.&lt;br /&gt;
&lt;br /&gt;
* '''Systemcall''' = meccanismo usato da un processo per richiedere al SO una qualche funzionalità a livello kernel. Un esempio è la funzione printf() del linguaggio C (stampa a video) che utilizza la systemcall write() (una delle systemcall per la gestione dei dispositivi).&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
----&lt;br /&gt;
'''Storia dei sistemi operativi'''&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''zero''' (1800 ca.) possiamo includere Babbage con la sua macchina analitica e lady Ada Lovelace. [[File:290px-AnalyticalMachine Babbage London.jpg |200px|thumb|right|Macchina analitica di Babbage]]&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''uno''' vediamo la comparsa delle valvole con tutti i problemi connessi. Non c'erano utilizzatori delle macchine, le stesse persone che le costruivano erano anche programmatori e fruitori delle stesse.&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''due''' (fine anni '60) arriva il transistor. Quest'ultimo è molto più veloce e piccolo della valvola e, soprattutto, meno incline a guasti. Le macchine iniziano ad essere più economiche grazie alla possibilità di avere un'economia di scala e quindi accessibili al grande pubblico. Questo fa si che, in queste prime fasi, i costruttori non siano più gli unici utilizzatori. [[File:Transistors.jpg |300px|thumb|right|Diversi tipi di Transistor]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
A questo punto poiché, pur essendo diventata più accessibile, una macchina costava ancora molto, nasce l'esigenza di non lasciare mai un processore senza lavoro (al fine di massimizzare i ricavi) e di condividere i dati.&lt;br /&gt;
In questa fase, infatti, abbiamo sistemi operativi di tipo ''batch'' (che collezionano tutto l'input all'inizio per calcolare e restituire l'output) dove l'inserimento di programmi e dati veniva fatto tramite schede perforate e non c'era interazione. Dai sistemi batch nasce lo ''SPOOL'' (Simultaneous Peripheral Operations On-Line).&lt;br /&gt;
&lt;br /&gt;
Si comincia a pensare di utilizzare i tempi di I/O per poter eseguire altri processi. Questo però solleva almeno due problematiche: da un lato serve un modo per sapere quando un input è davvero finito mentre, dall'altro, bisogna fare in modo che un processo che non richieda I/O non occupi la CPU per un tempo indefinito.&lt;br /&gt;
Per la prima problematica la soluzione è rappresentata dall'introduzione degli '''interrupt''', segnali elettrici inviati alla CPU alla fine di ogni input.&lt;br /&gt;
Per la seconda, invece, è stato introdotto il cosiddetto '''interval timer''' che non è altro che un dispositivo che invia interrupt dopo un determinato quanto di tempo assicurando che un processo non occupi per troppo tempo il processore.&lt;br /&gt;
Questo consente di realizzare sistemi time sharing (il cui più semplice algoritmo è il round-robin) dove un processo può essere in uno dei seguenti tre stati:&lt;br /&gt;
*'''READY''' pronto per essere eseguito ma, il processore è già occupato;&lt;br /&gt;
*'''RUNNING''' in esecuzione;&lt;br /&gt;
*'''WAIT''' in attesa di I/O.&lt;br /&gt;
&lt;br /&gt;
[[File:Dds.png]]&lt;br /&gt;
&lt;br /&gt;
Nella '''quarta''' generazione (anni '70 ca.) vediamo la nascita di molte innovazioni importanti. Si riesce a rimpicciolire di molto i processori facendoli divenire a tutti gli effetti micro-processori. Nei laboratori Bell nasce Unix, un sistema operativo rivoluzionario con time sharing e clonazione dei processi (un processo non viene mai creato dal nulla ma viene prima clonato da uno già esistente e poi, alla copia, viene fatto eseguire un programma). A causa di problemi di portabilità da PDP-9, macchina su cui Unix è nato, a PDP-11 nasce il linguaggio C e il suo compilatore (scritto anch'esso in C).&lt;br /&gt;
Mentre Apple I &amp;quot;porta&amp;quot; una nuova idea di pc per tutte le famiglie, nascono molte versioni di Unix.&lt;br /&gt;
Per impedire una deriva proprietaria in cui ogni produttore offriva tutto quello che offrivano gli altri ma con qualche feature in più, Richard Stallman fonda il progetto GNU (GNU's Not Unix) per il quale scrive tutta una serie di utilities come il famoso compilatore gcc.&lt;br /&gt;
A GNU, per essere operativo, manca però un kernel. Non volendo aspettare i tempi di sviluppo del kernel che Stallman aveva in mente, Linus Torlvalds scrive il kernel '''Linux''' da cui nasce il sistema operativo GNU/Linux.&lt;br /&gt;
&lt;br /&gt;
Nei tempi più recenti nascono i processori multi-core.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 3 ottobre 2017 ==&lt;br /&gt;
 &lt;br /&gt;
'''Introduzione alla programmazione concorrente: processi, thread e  librerie'''&lt;br /&gt;
&lt;br /&gt;
In programmazione concorrente le nozioni apprese di &amp;quot;algoritmo&amp;quot;, &amp;quot;programma&amp;quot; e &amp;quot;processo&amp;quot; devono essere aggiornate. Questo perché non vi è più un programma con un'unica sequenza esecutiva [o meglio, un unico filo (= thread) di esecuzione]. Appare ovvio come servino dei meccanismi per organizzare e sincronizzare tali fili esecutivi, in modo da evitare il verificarsi di eventi non desiderati.&lt;br /&gt;
&lt;br /&gt;
Quando parliamo di ''sistema concorrente'', ci riferiamo ad un sistema in cui due o più &amp;quot;attori&amp;quot; sono eseguiti parallelamente.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Questi attori possono essere '''processi''' o '''thread'''.&lt;br /&gt;
La differenza sostanziale tra queste due tipologie di attori riguarda la condivisione della memoria e dei dati su cui operano.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Più processi possono eseguire lo stesso programma ma, ognuno di essi, ha i propri dati su cui operare e il suo stato di avanzamento. In particolare:&lt;br /&gt;
* un processo è un'entità, un attore che ha delle proprietà e delle risorse associate;&lt;br /&gt;
* in ogni istante, il processo può essere interrotto.&amp;lt;br&amp;gt;&lt;br /&gt;
Più thread che vengono eseguiti parallelamente, invece, condividono gli stessi dati su cui operare.&amp;lt;br&amp;gt;&lt;br /&gt;
Codici di esempio:&lt;br /&gt;
&lt;br /&gt;
'''processi'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=C&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;unistd.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int main(int argc, char *argv[]){&lt;br /&gt;
   if(fork()) printf(&amp;quot;Yes&amp;quot;);&lt;br /&gt;
   else printf(&amp;quot;No&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
L'output generato sarà &amp;quot;YesNo&amp;quot; poiché, usando la systemcall ''fork()'', abbiamo creato una nuova copia del processo (un processo figlio). Il processo padre, che ha eseguito con successo la fork(), stamperà &amp;quot;Yes&amp;quot; mentre il figlio stamperà &amp;quot;No&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
'''thread'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=C&amp;gt;&lt;br /&gt;
#include &amp;lt;pthread.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
#include &amp;lt;assert.h&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
#define NUM_THREADS     5&lt;br /&gt;
#define MAX 1000000&lt;br /&gt;
&lt;br /&gt;
int sum;&lt;br /&gt;
 &lt;br /&gt;
void* perform_work(){&lt;br /&gt;
	int i;&lt;br /&gt;
	&lt;br /&gt;
	for(i = 0; i &amp;lt; MAX; i++) sum = sum + 1;&lt;br /&gt;
  	/* optionally: insert more useful stuff here */ &lt;br /&gt;
  	return NULL;&lt;br /&gt;
}&lt;br /&gt;
 &lt;br /&gt;
int main( int argc, char** argv ){&lt;br /&gt;
	pthread_t threads[NUM_THREADS];&lt;br /&gt;
  	int thread_args[NUM_THREADS];&lt;br /&gt;
  	int result_code;&lt;br /&gt;
  	unsigned index;&lt;br /&gt;
 &lt;br /&gt;
	sum = 0;&lt;br /&gt;
  	// create all threads one by one&lt;br /&gt;
  	for(index = 0; index &amp;lt; NUM_THREADS; ++index ){&lt;br /&gt;
    		thread_args[index] = index;&lt;br /&gt;
    		printf(&amp;quot;In main: creating thread %d\n&amp;quot;, index);&lt;br /&gt;
    		result_code = pthread_create(&amp;amp;threads[index], NULL, perform_work, NULL);&lt;br /&gt;
    		assert(!result_code);&lt;br /&gt;
  	} &lt;br /&gt;
  	// wait for each thread to complete&lt;br /&gt;
  	for(index = 0; index &amp;lt; NUM_THREADS; ++index ){&lt;br /&gt;
    		// block until thread 'index' completes&lt;br /&gt;
    		result_code = pthread_join(threads[index], NULL);&lt;br /&gt;
    		assert(!result_code);&lt;br /&gt;
    		printf(&amp;quot;In main: thread %d has completed\n&amp;quot;, index);&lt;br /&gt;
   	}&lt;br /&gt;
 &lt;br /&gt;
   	printf(&amp;quot;In main: All threads completed successfully\n&amp;quot; );&lt;br /&gt;
	printf(&amp;quot;Result: %d\n&amp;quot;, sum);&lt;br /&gt;
   	exit( EXIT_SUCCESS );&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Prima di discutere l'output generato dall'esecuzione di questo codice, è opportuno introdurre, brevemente, gli strumenti utilizzati.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
'''Pthread (POSIX thread)'''&lt;br /&gt;
&lt;br /&gt;
Citazione dal Silberschatz, Galvin, Gagne:&lt;br /&gt;
''Pthread si riferisce allo standard POSIX (IEEE 1003.1c) che definisce una API per la creazione e sincronizzazione dei thread.''&lt;br /&gt;
&lt;br /&gt;
Le istruzioni fondamentali sono:&lt;br /&gt;
* '''int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);''' che crea un thread;&lt;br /&gt;
* '''int pthread_join(pthread_t thread, void **retval);''' che aspetta che un thread sia completato prima di continuare l'esecuzione.&lt;br /&gt;
&lt;br /&gt;
Da ricordare che, per utilizzare pthread, va incluso l'header ''&amp;lt;pthread.h&amp;gt;'' e durante la compilazione va inclusa la libreria esterna tramite l'opzione ''-pthread''.&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
Torniamo all'output generato dall'esecuzione del secondo codice.&amp;lt;br&amp;gt;&lt;br /&gt;
Nel codice vengono generati cinque thread ed ognuno somma MAX (=1000000) volte 1 a sum. Ci aspettiamo un output di 5000000 ma, in realtà, viene stampato un valore molto minore.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Questo accade principalmente per due motivi:&lt;br /&gt;
*sommare una costante ad una variabile non è un'operazione atomica (richiede almeno tre istruzioni assembler: due mov ed una sum).&lt;br /&gt;
*l'interval timer può fermare l'esecuzione del thread mentre sta effettuando una delle tre operazioni richieste per aggiungere uno a sum e provocare un risultato errato.&lt;br /&gt;
&lt;br /&gt;
Questo è uno dei possibili problemi della programmazione concorrente conosciuto come ''race condition''.&amp;lt;br&amp;gt;&lt;br /&gt;
Un sistema di processi multipli presenta una '''race condition''' qualora il risultato finale dell'esecuzione dipenda dalla temporizzazione, o dall'ordine con cui i processi vengono eseguiti.&lt;br /&gt;
&lt;br /&gt;
Per risolvere questo problema possiamo adoperare ancora una volta pthread.&lt;br /&gt;
Nello specifico, ci venogono offerte istruzione per realizzare una '''mutua esclusione''' sulle risorse condivise in modo che l'operazione che prima non era atomiche,ora, lo sia e non porti più ad una computazione errata.&amp;lt;br&amp;gt;&lt;br /&gt;
Codice di esempio:&amp;lt;br&amp;gt;&lt;br /&gt;
'''Mutex'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=C&amp;gt;&lt;br /&gt;
#include &amp;lt;pthread.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
#include &amp;lt;assert.h&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
#define NUM_THREADS     5&lt;br /&gt;
#define MAX 1000000&lt;br /&gt;
&lt;br /&gt;
int sum;&lt;br /&gt;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;&lt;br /&gt;
 &lt;br /&gt;
void* perform_work(){&lt;br /&gt;
	int i;&lt;br /&gt;
	&lt;br /&gt;
	for(i = 0; i &amp;lt; MAX; i++){ &lt;br /&gt;
		pthread_mutex_lock(&amp;amp;mutex);		&lt;br /&gt;
		sum = sum + 1;&lt;br /&gt;
		pthread_mutex_unlock(&amp;amp;mutex);&lt;br /&gt;
	}&lt;br /&gt;
  	/* optionally: insert more useful stuff here */ &lt;br /&gt;
  	return NULL;&lt;br /&gt;
}&lt;br /&gt;
 &lt;br /&gt;
int main( int argc, char** argv ){&lt;br /&gt;
	pthread_t threads[NUM_THREADS];&lt;br /&gt;
  	int thread_args[NUM_THREADS];&lt;br /&gt;
  	int result_code;&lt;br /&gt;
  	unsigned index;&lt;br /&gt;
&lt;br /&gt;
	sum = 0;&lt;br /&gt;
  	// create all threads one by one&lt;br /&gt;
  	for(index = 0; index &amp;lt; NUM_THREADS; ++index ){&lt;br /&gt;
    		thread_args[index] = index;&lt;br /&gt;
    		printf(&amp;quot;In main: creating thread %d\n&amp;quot;, index);&lt;br /&gt;
    		result_code = pthread_create(&amp;amp;threads[index], NULL, perform_work, NULL);&lt;br /&gt;
    		assert(!result_code);&lt;br /&gt;
  	} &lt;br /&gt;
  	// wait for each thread to complete&lt;br /&gt;
  	for(index = 0; index &amp;lt; NUM_THREADS; ++index ){&lt;br /&gt;
    		// block until thread 'index' completes&lt;br /&gt;
    		result_code = pthread_join(threads[index], NULL);&lt;br /&gt;
    		assert(!result_code);&lt;br /&gt;
    		printf(&amp;quot;In main: thread %d has completed\n&amp;quot;, index);&lt;br /&gt;
   	}&lt;br /&gt;
 &lt;br /&gt;
   	printf(&amp;quot;In main: All threads completed successfully\n&amp;quot; );&lt;br /&gt;
	printf(&amp;quot;Result: %d\n&amp;quot;, sum);&lt;br /&gt;
   	exit( EXIT_SUCCESS );&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
'''MUTEX'''&lt;br /&gt;
&lt;br /&gt;
Un mutex è un dispositivo di MUTual EXclusion (mutua esclusione), ed è utile per proteggere le strutture dati condivise da modifiche concorrenti, implementare sezioni critiche e monitor.&lt;br /&gt;
&lt;br /&gt;
Un mutex ha due possibili stati: sbloccato (non posseduto da nessun thread) e bloccato (posseduto da un thread). Un mutex non può mai essere posseduto da due thread contemporaneamente.&amp;lt;br&amp;gt;Se un thread provasse ad acquisire il mutex, mentre quest'ultimo è già in possesso di un altro thread, dovrebbe aspettare finché non viene rilasciato.&lt;br /&gt;
La variabile che realizza il mutex, di tipo ''pthread_mutex_t'', viene inizializzata staticamente assegnandogli PTHREAD_MUTEX_INITIALIZER.&lt;br /&gt;
'''pthread_mutex_lock''' acquisisce immediatamente il mutex, se libero. Altrimenti aspetta fino a trovarla, prima o poi, libera.&amp;lt;br&amp;gt;&lt;br /&gt;
'''pthread_mutex_unlock''' rilascia il mutex.&lt;br /&gt;
&lt;br /&gt;
[da: https://sourceware.org/pthreads-win32/manual/pthread_mutex_init.html]&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
Con questa soluzione abbiamo raggiunto un risultato esatto pur avendo pagato in prestazioni: la computazione infatti è più lenta a causa del tempo richiesto dalla sincronizzazione tra i thread.&lt;br /&gt;
&lt;br /&gt;
Consideriamo ora due scenari leggermente più complicati:&lt;br /&gt;
* nel primo abbiamo due processi, A e B, che devono accedere a due risorse, X e Y, contemporaneamente prima di poter terminare. Cosa succede se, mentre A acquisisce X, B acquisisce Y?&lt;br /&gt;
* nel secondo abbiamo tre processi, A, B e C, che devono accedere alla risorsa X. Che succede se A e B, essendo più veloci, occupano insistentemente la risorsa e non permettono a C di utilizzarla?&lt;br /&gt;
&lt;br /&gt;
La prima condizione si chiama '''deadlock''', la seconda condizione si chiama '''starvation'''. Il deadlock (stallo) è un problema che non può scomparire autonomamente, mentre la starvation (&amp;quot;inedia&amp;quot;) si può risolvere da sola. A tal proposito ci viene incontro l'assioma di '''finite progress''': &amp;quot;ogni processo che può avanzare, prima o poi lo farà&amp;quot;.&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
In ogni caso, per parlare in maniera più approfondita di queste tematiche:&lt;br /&gt;
* avremo bisogno di un modello di riferimento&lt;br /&gt;
* avremo bisogno di paradigmi in grado di esprimere la concorrenza (alcuni paradigmi saranno rivolti alla concorrenza a memoria privata; altri al multithreading a risorse condivise)&lt;br /&gt;
* dovremo imparare a scrivere programmi&lt;br /&gt;
Nel nostro modello di riferimento l'assegnamento di costanti è un operazione atomica. Dagli esempi precedenti, infatti, si evince come ci sia bisogno di creare atomicità in sezioni critiche. Inoltre, il nostro modello dovrà rispettare le proprietà di safety e di liveness, ovvero:&lt;br /&gt;
* Tutti i processi danno la stessa risposta;&lt;br /&gt;
* Ogni processo corretto da come risposta una tra quelle proposte;&lt;br /&gt;
* Ogni processo prima o poi fornisce una risposta.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 6 ottobre 2017 ==&lt;br /&gt;
'''Il linguaggio C'''&lt;br /&gt;
&lt;br /&gt;
Il linguaggio C nasce nei primi anni 70 grazie al lavoro di Dennis Ritchie con lo scopo di scrivere il &lt;br /&gt;
sistema operativo Unix. Quali sono le caratteristiche di C e cosa lo rende un linguaggio adatto per scrivere un sistema&lt;br /&gt;
operativo? Un linguaggio per scrivere un sistema operativo deve:&lt;br /&gt;
* essere indipendente dalla macchina su cui viene scritto&lt;br /&gt;
* dare la possibilità di lavorare direttamente con la memoria&lt;br /&gt;
* permette di andare a modificare i singoli bit in memoria&lt;br /&gt;
* essere semplice da usare&lt;br /&gt;
* essere massimamente produttivo&lt;br /&gt;
* essere veloce, efficiente&lt;br /&gt;
C presenta le caratteristiche dette sopra e alcune altre:&lt;br /&gt;
* in C, dal punto di vista sintattico, tutto è funzione. Si può vedere un programma in C come un insieme di funzioni. La funzione principale è il '''main'''. L'unica cosa che la distingue è il nome, a parte quello il main è una funzione come le altre. Prende due argomenti: argcount e argvalue, che contengono rispettivamente il numero degli argomenti e un array contenente gli argomenti stessi. L'interfaccia del main è tipicamente: &amp;lt;syntaxhighlight lang=C&amp;gt; int main(int argc, char *argv[]) &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
* il costrutto fondamentale è l'espressione. ''expression'' ; = ''instruction''&lt;br /&gt;
* sono previste istruzioni di struttura &amp;lt;syntaxhighlight lang=C&amp;gt;if, while, switch, etc.&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
* le variabili, per poter essere utilizzate, devono essere sempre definite&lt;br /&gt;
* il C è un linguaggio fortemente tipato, però con un certo numero di eccezioni: ad esempio, è possibile fare il casting esplicito di un puntatore void * in qualsiasi tipo, senza che il compilatore dia errori o warnings, con conseguente perdita di type safety&lt;br /&gt;
* C è un linguaggio &amp;quot;per adulti&amp;quot;: non impone una struttura ordinata al codice, come ad esempio fa invece Python, ed è piena responsabilità del programmatore scrivere programmi ordinati/leggibili. Inoltre molte operazioni che in altri linguaggi non sarebbero permesse lo sono invece in C: anche in questo caso è responsabilità del programmatore fare corretto uso del linguaggio&lt;br /&gt;
* C è un linguaggio molto semplice. Come si sopperisce alle mancanze imposte da questa semplicità? Grazie alle librerie: in C tutte le funzionalità aggiuntive (non fornite dal linguaggio stesso, come l'allocazione di memoria, la stampa a schermo, la gestione di input e output) sono fornite dalle librerie&lt;br /&gt;
* C è anche sintatticamente semplice. Dove va a finire la complessità sintattica? Nel preprocessore: prima di essere compilato un programma in C passa attraverso un preprocessore, che produce codice in base alle direttive di preprocessione date nel codice stesso &amp;lt;syntaxhighlight lang=C&amp;gt;#ifdef VAR ... #endif, # e ## operator, __LINE__,etc&amp;lt;/syntaxhighlight&amp;gt; &lt;br /&gt;
* in C vi sono fondamentalmente due tipi di dato: int e float. Tutti gli altri &amp;quot;tipi&amp;quot; di dato sono derivati da essi. Si hanno diversi modificatori, che vanno a modificare la quantità di memoria allocata per la variabile. Alcuni di questi sono &amp;lt;syntaxhighlight lang=C&amp;gt;long, short, char, etc&amp;lt;/syntaxhighlight&amp;gt; Un dato di tipo long int ha, solitamente, la dimensione di una parola di memoria della macchina su cui è eseguito il programma. char alloca 8 bits. Se si prende una variabile di un certo tipo e lo si assegna ad una di tipo più ampio, il valore viene mantenuto. Viceversa quando si assegna una variabile di un tipo ad una di tipo meno ampio, una volta &amp;quot;superato il limite&amp;quot; si torna indietro ciclicamente al valore iniziale. Per queste situazioni il compilatore non dà né warnings né errors: sono perfettamente lecite in C&lt;br /&gt;
* in C si passa una varibile ad una funzione solo per valore; per passare &amp;quot;per riferimento&amp;quot; una variabile si passa per valore l'indirizzo della variabile&lt;br /&gt;
* in C si può fare uso di puntatori alla memoria. Dato un puntatore p, un'istruzione del tipo p+i va letta come: vai avanti di i posizioni della grandezza del tipo del puntatore ognuno. Un vettore in C non è altro che un puntatore che non può essere assegnato. Una scrittura del tipo array[i] è una abbreviazione di *(array + i). Non c'è controllo in C se vado oltre la memoria allocata per l'array. Perché? In Pascal, ad esempio, c'è un tale controllo. Il motivo per cui in C questo manca è che questo controllo genera ulteriore codice macchina. Dovendo C essere efficiente, non ci si può permettere un tale costo aggiuntivo. La responsabilità di non andare oltre è lasciata al programmatore&lt;br /&gt;
&lt;br /&gt;
Esercizio svolto che prende in input due valori tramite i parametri del metodo '''main''', li converte da sequenze di caratteri ad interi, per poi stamparli utilizzando delle '''System Call'''. Lo scopo dell’esercizio è dimostrare come sia possibile utilizzare il linguaggio C senza l’utilizzo di librerie particolari (ad eccezione di quelle per le System Call).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=C&amp;gt;&lt;br /&gt;
#define _GNU_SOURCE&lt;br /&gt;
#include &amp;lt;unistd.h&amp;gt;&lt;br /&gt;
#include &amp;lt;sys/syscall.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int _uatoi(char *s, int value){&lt;br /&gt;
  if (*s == 0)&lt;br /&gt;
    return value;&lt;br /&gt;
  else if (*s &amp;gt;= '0' &amp;amp;&amp;amp; *s &amp;lt;= '9'){&lt;br /&gt;
    value = value * 10 + (*s - '0');&lt;br /&gt;
    return _uatoi(s+1, value);&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int uatoi(char *s){&lt;br /&gt;
  return _uatoi(s, 0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
char *uitoa(int val, char *buf, int len){&lt;br /&gt;
  int i;&lt;br /&gt;
  for (len = len - 1; len &amp;gt;= 0 &amp;amp;&amp;amp; val &amp;gt; 0; len--, val /= 10){&lt;br /&gt;
    buf[len] = '0' + (val %10);&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  return buf + len + 1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#define MAXOUTLEN 10&lt;br /&gt;
int main (int argc, char *argv[]) {&lt;br /&gt;
    int tot = 0;&lt;br /&gt;
    int i;&lt;br /&gt;
    char buf[MAXOUTLEN];&lt;br /&gt;
    char *result;&lt;br /&gt;
    for (i = 1; i &amp;lt; argc; i++)&lt;br /&gt;
      tot += uatoi(argv[i]);&lt;br /&gt;
    result = uitoa(tot, buf, MAXOUTLEN);&lt;br /&gt;
    syscall(__NR_write, 1, result, MAXOUTLEN - (result - buf));&lt;br /&gt;
    syscall(__NR_write, 1, &amp;quot;\n&amp;quot;, 1);&lt;br /&gt;
    return tot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Lezione del 10 ottobre 2017 ==&lt;br /&gt;
'''Programmazione concorrente: notazione e un primo problema'''&lt;br /&gt;
&lt;br /&gt;
Esempio di possibile rappresentazione (che non useremo) di codice concorrente.&amp;lt;br&amp;gt;&lt;br /&gt;
'''''A'', ''B'', ''C'', ''D'', ''E''''' rappresentano porzioni di codice.&amp;lt;br&amp;gt;&lt;br /&gt;
'''''//''''' rappresenta esecuzione parallela&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
A&lt;br /&gt;
cobegin&lt;br /&gt;
  B&lt;br /&gt;
//&lt;br /&gt;
  C&lt;br /&gt;
//&lt;br /&gt;
  D&lt;br /&gt;
coend&lt;br /&gt;
E&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il codice sarà eseguito come: ''A'' -&amp;gt; ''BCD'' -&amp;gt; ''E''. ''E'' quindi verrà eseguito solo dopo la fine dell'esecuzione di ''BCD''.&lt;br /&gt;
&lt;br /&gt;
Un esempio che si presta molto bene ad illustrare la programmazione concorrente è quello del '''merge sort''' concorrente:&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
leggi vettore&lt;br /&gt;
cobegin&lt;br /&gt;
  ordina la prima metà del vettore&lt;br /&gt;
//&lt;br /&gt;
  ordina la seconda metà del vettore&lt;br /&gt;
coend&lt;br /&gt;
merge (miscela) i risultati&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Versione errata del merge sort concorrente:&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
leggi vettore&lt;br /&gt;
cobegin&lt;br /&gt;
  ordina la prima metà del vettore&lt;br /&gt;
//&lt;br /&gt;
  ordina la seconda metà del vettore&lt;br /&gt;
//  &lt;br /&gt;
  merge (miscela) i risultati&lt;br /&gt;
coend&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avendo un numero ''N'' di processi, vorremmo avere una notazione che ci permetta di definire una porzione di codice come atomica (la sezione critica). Come possiamo procedere?&amp;lt;br&amp;gt;&lt;br /&gt;
Teoricamente, vorremmo avere due funzioni, '''csenter()''' e '''csexit()''' che ci permettano di entrare e uscire dalla sezione critica del codice senza causare errori nella computazione.&amp;lt;br&amp;gt;&lt;br /&gt;
Quali sono le proprietà che queste funzioni debbono rispettare?&lt;br /&gt;
# Devono garantire la mutua esclusione (1 solo processo esegue la critical section alla volta). Contando ''n'' csenter() completate e ''m'' csexit() completate la differenza deve essere &amp;lt;=1&lt;br /&gt;
# Non devono fare deadlock tra di loro&lt;br /&gt;
# Non devono fare starvation tra loro&lt;br /&gt;
# ''(no unnecessary delay)'' un processo che non sta usando la critical section {csenter()..csexit()} non deve bloccare un altro processo dall'entrarvi.&lt;br /&gt;
&lt;br /&gt;
La soluzione a questo problema è nota come '''soluzione di Dekker''' (scritta e pubblicata da Dijkstra) che venne presentata in modo graduale attraverso l'utilizzo di quattro possibili, ma sbagliate, soluzioni.&lt;br /&gt;
&lt;br /&gt;
Vorremmo avere programmi che abbiano la seguente struttura:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
process[i], i=1,...,N&lt;br /&gt;
&lt;br /&gt;
while(1):&lt;br /&gt;
  csenter() //entrata nella sezione critica (critical section)&lt;br /&gt;
  critical code&lt;br /&gt;
  csexit() //uscita dalla sezione critica&lt;br /&gt;
  non-critical code&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ad esempio, tornando al problema dei thread che sommavano 1 a ''sum'' MAX volte, avremo:&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
process A:&lt;br /&gt;
&lt;br /&gt;
  for (i = 0; i &amp;lt; MAX; i++)&lt;br /&gt;
    csenter()&lt;br /&gt;
    sum += 1 //atomicità descritta da csenter(), csexit()&lt;br /&gt;
    // &amp;lt; sum += 1 &amp;gt; atomicità lasciata al processore&lt;br /&gt;
    csexit()&lt;br /&gt;
&lt;br /&gt;
process B:&lt;br /&gt;
&lt;br /&gt;
  for (i = 0; i &amp;lt; MAX; i++)&lt;br /&gt;
    csenter()&lt;br /&gt;
    sum += 1&lt;br /&gt;
    csexit()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
----&lt;br /&gt;
'''Soluzione 1: A turni'''&lt;br /&gt;
&lt;br /&gt;
Qual è il problema?&amp;lt;br&amp;gt;&lt;br /&gt;
*'''mutex'''                   OK&amp;lt;br&amp;gt;&lt;br /&gt;
*'''deadlock'''                OK&amp;lt;br&amp;gt;&lt;br /&gt;
*'''starvation'''              OK&amp;lt;br&amp;gt;&lt;br /&gt;
*'''no unnecessary delay'''    NO&amp;lt;br&amp;gt;&lt;br /&gt;
:: Perché se P ha bisogno spesso della critical section, P non deve aspettare che Q chieda la critical section e, all'uscita, lo autorizzi di nuovo ad usarla impostando ''turn = P''. Se Q una volta richiesta la sezione non la usa ma si ferma, P non può richiederla ancora perché appartiene ancora a Q.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
&lt;br /&gt;
turn=P&lt;br /&gt;
&lt;br /&gt;
process P:&lt;br /&gt;
  while(1)&lt;br /&gt;
    while (turn != P)&lt;br /&gt;
      ;&lt;br /&gt;
    critical code&lt;br /&gt;
    turn = Q&lt;br /&gt;
    non-critical code&lt;br /&gt;
&lt;br /&gt;
process Q:&lt;br /&gt;
  while(1)&lt;br /&gt;
    while (turn != Q)&lt;br /&gt;
    critical code&lt;br /&gt;
    turn = P&lt;br /&gt;
    non-critical code&lt;br /&gt;
 &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
'''Soluzione 2: con 2 variabili''', cerchiamo di risolvere il problema dei turni&lt;br /&gt;
&lt;br /&gt;
Finché Q usa la sezione P aspetta, quando Q ha finito P prende la sezione critica e non ci sono ritardi indesiderati.&lt;br /&gt;
&lt;br /&gt;
*'''starvation'''              OK&lt;br /&gt;
*'''deadlock'''                OK&lt;br /&gt;
*'''no unnecessary delay'''    OK&lt;br /&gt;
*'''mutex'''                   NO &lt;br /&gt;
:: Possono essere entrambi come false, escono insieme nel while, tutti e due mettono a true ed entrano assieme nel while.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
&lt;br /&gt;
inP = false;&lt;br /&gt;
inQ = false;&lt;br /&gt;
&lt;br /&gt;
process P:&lt;br /&gt;
  while(1)&lt;br /&gt;
    while (inQ)&lt;br /&gt;
      ;&lt;br /&gt;
    inP = true&lt;br /&gt;
    critical code&lt;br /&gt;
    inP = false&lt;br /&gt;
    non-critical code&lt;br /&gt;
&lt;br /&gt;
process Q:&lt;br /&gt;
  while(1)&lt;br /&gt;
    while (inP)&lt;br /&gt;
      ;&lt;br /&gt;
    inQ = true&lt;br /&gt;
    critical code&lt;br /&gt;
    inQ = false&lt;br /&gt;
    non-critical code&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
'''Soluzione 3: il problema di soluzione 2 è che i processi non sono al corrente dell'intenzione dell'altro di entrare''' nel while quindi si crea il problema.&lt;br /&gt;
*'''mutex'''                   OK&lt;br /&gt;
*'''deadlock'''                NO (potrebbero entrambi richiedere, contemporaneamente, l'ingresso alla critical section)&lt;br /&gt;
*'''starvation'''              OK&lt;br /&gt;
*'''non unnecessary delay'''   OK&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
needP = false;&lt;br /&gt;
needQ = false;&lt;br /&gt;
&lt;br /&gt;
process P:&lt;br /&gt;
  while(1)&lt;br /&gt;
    needP = true&lt;br /&gt;
    while (needQ)&lt;br /&gt;
      ;&lt;br /&gt;
    critical code&lt;br /&gt;
    needP = false&lt;br /&gt;
    non-critical code&lt;br /&gt;
&lt;br /&gt;
process Q:&lt;br /&gt;
  while(1)&lt;br /&gt;
    needQ = true&lt;br /&gt;
    while (needP)&lt;br /&gt;
      ;&lt;br /&gt;
    critical code&lt;br /&gt;
    needQ = false&lt;br /&gt;
    non-critical code&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
'''Soluzione 4''':&lt;br /&gt;
&lt;br /&gt;
*'''mutex'''                 OK&lt;br /&gt;
*'''deadlock'''              OK&lt;br /&gt;
*'''no unnecessary delay'''  OK&lt;br /&gt;
*'''starvation'''            NO &lt;br /&gt;
:: Perché può esistere il processo che prova sempre a chiedere nel momento sbagliato e non esegue mai.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
inP = false;&lt;br /&gt;
inQ = false;&lt;br /&gt;
&lt;br /&gt;
process P:&lt;br /&gt;
  while(1)&lt;br /&gt;
    inP = true&lt;br /&gt;
    while (inQ){&lt;br /&gt;
        inP = false;&lt;br /&gt;
        delay&lt;br /&gt;
        inP = true;&lt;br /&gt;
    }&lt;br /&gt;
    critical code&lt;br /&gt;
    inP = false&lt;br /&gt;
    non-critical code&lt;br /&gt;
&lt;br /&gt;
process Q:&lt;br /&gt;
  while(1)&lt;br /&gt;
    inQ = true&lt;br /&gt;
    while (inQ){&lt;br /&gt;
        inQ = false;&lt;br /&gt;
        delay&lt;br /&gt;
        inQ = true;&lt;br /&gt;
    }&lt;br /&gt;
    critical code&lt;br /&gt;
    inQ = false&lt;br /&gt;
    non-critical code&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
'''Soluzione finale''':&amp;lt;br&amp;gt;&lt;br /&gt;
Uniamo soluzione 1 (poco simmetrica) e soluzione 4 (troppo simmetrica) usando quello che ci serve (attraverso ''needP'' e ''needQ'') e, se dovessimo essere tutti e due contemporaneamente a voler entrare, usiamo i turni (''turn'') per entrare nella critical section (settando il need dell'altro processo a false).&lt;br /&gt;
&lt;br /&gt;
*'''mutex'''                         OK&lt;br /&gt;
*'''deadlock'''                      OK&lt;br /&gt;
*'''starvation'''                    OK&lt;br /&gt;
*'''non unnecessary delay'''         OK&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
needP = false;&lt;br /&gt;
needQ = false;&lt;br /&gt;
turn = P&lt;br /&gt;
&lt;br /&gt;
process P:&lt;br /&gt;
  while(1)&lt;br /&gt;
    needP = true&lt;br /&gt;
    while (needQ)&lt;br /&gt;
      if (turn != P) {&lt;br /&gt;
        needP = false;&lt;br /&gt;
        while ( turn != P)&lt;br /&gt;
           ; /* busy wait */&lt;br /&gt;
        needP = true;&lt;br /&gt;
    }&lt;br /&gt;
    /*critical code*/&lt;br /&gt;
    turn = Q&lt;br /&gt;
    needP = false&lt;br /&gt;
    /*non-critical code*/&lt;br /&gt;
&lt;br /&gt;
process P:&lt;br /&gt;
  while(1)&lt;br /&gt;
    needQ = true&lt;br /&gt;
    while (needP)&lt;br /&gt;
      if (turn != Q) {&lt;br /&gt;
        needQ = false;&lt;br /&gt;
        while ( turn != Q)&lt;br /&gt;
             ; /* busy wait */&lt;br /&gt;
        needQ = true;&lt;br /&gt;
    }&lt;br /&gt;
    /*critical code*/&lt;br /&gt;
    turn = P&lt;br /&gt;
    needQ = false&lt;br /&gt;
    /*non-critical code*/&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Lezione del 13 ottobre 2017 ==&lt;br /&gt;
'''Il linguaggio C: PREPROCESSORE'''&lt;br /&gt;
&lt;br /&gt;
Il '''preprocessore''' è uno strumento contenuto nella toolchain di '''gcc''' che esamina il codice sorgente prima che questo venga compilato, e &amp;quot;trasforma&amp;quot; il programma prima della compilazione; è propriamente definito come ''macro processor'', ovvero un esaminatore di '''macro'''.&lt;br /&gt;
Le '''macro''' sono delle abbreviazioni che in realtà definiscono delle strutture di codice più grandi, e tornano molto utili in diversi casi nello sviluppo di un programma in C (è buona norma scrivere l’identificatore completamente in '''MAIUSCOLO'''). Il preprocessore esamina le cosiddette '''direttive'''; per conoscere l’output del preprocessore è possibile digitare il comando &amp;lt;kbd&amp;gt;'''gcc -E''' ''{nomeDelSorgente}''&amp;lt;/kbd&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Un primo esempio è dato dalla direttiva '''&amp;lt;kbd&amp;gt;#define&amp;lt;/kbd&amp;gt;''', la quale crea una ''macro'', ovvero l’associazione di un identificatore (con o senza parametri) con una stringa di ''token''. Le macro così definite possono assumere quindi diverse funzioni:&lt;br /&gt;
# condizione per l’inclusione o meno di codice nel file che verrà passato al compilatore;&lt;br /&gt;
# inserimento di '''costanti''' all’interno del codice (notare che questa direttiva nasce molto prima della keyword '''const''' del linguaggio C);&lt;br /&gt;
# creazione di abbreviazioni a porzioni di codice più lunghe.&lt;br /&gt;
&lt;br /&gt;
* '''Caso 1''' &amp;lt;br&amp;gt; Il primo caso, che potrebbe essere definito come il più semplice, prevede la dichiarazione di un valore costante, su cui ci si baserà in seguito per includere o meno parti di codice. Per fare ciò occorre introdurre anche altre direttive del preprocessore: '''#ifdef''', '''ifndef''' ed '''endif''', le quali identificano un blocco di codice che deve essere incluso nel file da compilare solo se rispettivamente una macro è definita ''(#ifdef)'' o una macro non è definita ''(#ifndef)'', mentre la direttiva ''#endif'' indica la fine del blocco condizionale.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#define DEBUG&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
#ifdef DEBUG&lt;br /&gt;
    printf(&amp;quot;In main\n&amp;quot;);&lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
// la macro __linux__ è definita solamente nei sistemi Linux-based&lt;br /&gt;
#ifndef __linux__&lt;br /&gt;
    printf(&amp;quot;WARNING: not a Linux system\n&amp;quot;);&lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
:Da notare in questo caso l’utilizzo della macro '''__linux__''', una macro predefinita nei sistemi ''Linux-based'', che consente di discriminare il sistema operativo su cui il programma viene compilato, dando quindi la possibilità di compilare del codice diverso in base alla piattaforma utilizzata. &amp;lt;br&amp;gt;&lt;br /&gt;
*'''Caso 2''' &amp;lt;br&amp;gt;Il secondo caso prevede la presenza di un valore in seguito all’identificatore, che quindi verrà sostituito in ogni sua occorrenza all’interno del codice. È da far notare come questo meccanismo sia simile all’utilizzo della keyword '''const''' del linguaggio, ma quest’ultima nasce solo successivamente come alternativa ''type-safe'' (dal momento che le macro potrebbero essere utilizzate ovunque senza alcun tipo di controllo). Questo meccanismo torna ad esempio quando si vuole effettuare un’operazione un numero di volte prefissato; in questo modo, è sufficiente modificare il valore della macro nella sua definizione, per aggiornare tutto il programma che utilizzava quella macro al nuovo valore.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#define ITER_NUM 5&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
    int i;&lt;br /&gt;
    for (i = 0; i &amp;lt; ITER_NUM; i++)&lt;br /&gt;
        printf(&amp;quot;Iterazione numero %d\n&amp;quot;, i);&lt;br /&gt;
&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt; &amp;lt;br&amp;gt;&lt;br /&gt;
*'''Caso 3''' &amp;lt;br&amp;gt; L’ultimo caso analizzato è quello che può essere definito il più complesso tra i tre. L’identificatore della macro ora presenta anche dei parametri formali, all’interno di parentesi e separati da virgole. Tali parametri compaiono anche nella token string in cui vengono utilizzati (anche più volte) e su cui vengono eseguite delle operazioni. Questa particolarità è utile per permettere di ridurre porzioni di codice che vengono definite così una volta sola, e per essere utilizzate sarà sufficiente scrivere il nome dell’identificatore con i relativi parametri. Da notare come ci sia un numero considerevole di parentesi, che potrebbero quasi sembrare superflue. In realtà sono essenziali per assicurare il corretto ordine di esecuzione delle istruzioni una volta che la stringa di token sarà sostituita alle occorrenze dell’identificatore.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#define MAX(x, y) ((x) &amp;gt; (y) ? (x) : (y))&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
    printf(&amp;quot;%d\n&amp;quot;, MAX(10, 20));&lt;br /&gt;
&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
:Che produrrà, in seguito all’esecuzione del preprocessore:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
    printf(&amp;quot;%d\n&amp;quot;, ((10) &amp;gt; (20) ? (10) : (20)));&lt;br /&gt;
&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
:Occorre far notare che quella effettuata dal preprocessore non è altro che una '''string substitution''', cioè si limita a sostituire le macro utilizzate nel codice con la rispettiva definizione (ovviamente includendo eventuali parametri formali), ma non effettua alcun tipo di controllo. Ciò significa che occorre prestare molta attenzione quando si utilizzano le macro come nell’ultimo caso illustrato, perché potrebbero comparire dei '''side-effect''' inaspettati. Se si modifica l’esempio precedente in questo modo&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#define MAX(x, y) ((x) &amp;gt; (y) ? (x) : (y))&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
int i = 100;&lt;br /&gt;
    printf(&amp;quot;%d\n&amp;quot;, MAX(10, i++));&lt;br /&gt;
    printf(&amp;quot;%d\n&amp;quot;, MAX(10, i));&lt;br /&gt;
&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
:si otterrà un risultato che in un primo momento potrebbe essere inaspettato:&lt;br /&gt;
&amp;lt;kbd&amp;gt;&lt;br /&gt;
101&amp;lt;br&amp;gt;102&lt;br /&gt;
&amp;lt;/kbd&amp;gt;&lt;br /&gt;
:Ad un’analisi più approfondita tuttavia, è possibile accorgersi dell’errore: il preprocessore effettua una sostituzione testuale dei parametri al momento dell’utilizzo della macro; ciò significa che sostituisce ogni occorrenza del primo parametro nella ''token string'' con il primo valore fornito, e dualmente per il secondo parametro. Il codice che verrà sottoposto al compilatore conterrà quindi due incrementi della variabile &amp;lt;kbd&amp;gt;i&amp;lt;/kbd&amp;gt;. Un esempio a grandi linee delle operazioni eseguite dal compilatore è la seguente (per capirlo è necessario aver ben compreso la nozione di '''post-incremento''' della variabile i):&lt;br /&gt;
#assegno alla variabile i il valore 100&lt;br /&gt;
#10 è maggiore di i?&lt;br /&gt;
#NO, incremento i (ora i vale 101)&lt;br /&gt;
#scrivo i (101)&lt;br /&gt;
#incremento i (ora i vale 102)&lt;br /&gt;
#10 è maggiore di i?&lt;br /&gt;
#NO, scrivo i (102)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
Esistono molti altri operatori che si possono utilizzare all’interno delle macro, due esempi sono:&lt;br /&gt;
* l’operatore '''#''' che, se anteposto al nome di un parametro nella stringa di token, viene riconosciuto dal processore, che rimpiazza il parametro con il simbolo '''#''' anteposto con il proprio valore letterale convertito in una stringa costante;&lt;br /&gt;
* l’operatore '''##''' &amp;quot;combina&amp;quot; due token in uno: unisce il token alla sinistra e quello alla destra dell’operatore per farlo diventare un’unico token. Questo torna molto utile se uno dei due token proviene da un parametro della macro.&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
Per un esempio su questi ultimi due operatori: [http://so.v2.cs.unibo.it/wiki/index.php?title=Esercizi_di_%22lettura%22_di_in_linguaggio_C_2017/18#tables_and_preprocessor_tricks tables and preprocessors tricks]&lt;br /&gt;
----&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Per ulteriori approfondimenti sul preprocessore del C: [https://gcc.gnu.org/onlinedocs/cpp/ Approfondimenti sul preprocessore del C]&lt;br /&gt;
&lt;br /&gt;
== Lezione del 17 ottobre 2017 ==&lt;br /&gt;
&lt;br /&gt;
'''Oltre la soluzione di Dekker'''&lt;br /&gt;
&lt;br /&gt;
Nella scorsa lezione di teoria abbiamo visto la soluzione di Dekker dove due processi, per poter entrare ed eseguire la critical section, manifestano prima questo loro bisogno (&amp;lt;code&amp;gt;needP/needQ = true&amp;lt;/code&amp;gt;), controllano poi se anche l'altro processo ha comunicato questo bisogno e, in caso affermativo, verrà utilizzata una politica a turni per gestire l'entrata e l'uscita alla sezione critica.&lt;br /&gt;
&lt;br /&gt;
Questa soluzione ha però due limiti:&lt;br /&gt;
*non funziona con un numero di processi &amp;gt; 2&lt;br /&gt;
*è relativamente complicato da capire, si può fare di meglio!&lt;br /&gt;
&lt;br /&gt;
Un'evoluzione più lineare e facilmente adattabile a più di due processi è l''''algoritmo di Peterson''' che, nel caso base, ha la seguente struttura:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
needP = false;&lt;br /&gt;
needQ = false;&lt;br /&gt;
turn;&lt;br /&gt;
 &lt;br /&gt;
process P:&lt;br /&gt;
  while(1)&lt;br /&gt;
    /* entry protocol */&lt;br /&gt;
    needP = true&lt;br /&gt;
    turn = Q&lt;br /&gt;
    while(needQ &amp;amp;&amp;amp; turn != P)&lt;br /&gt;
      ; /* busy wait */&lt;br /&gt;
    /* critical section */&lt;br /&gt;
    needP = false&lt;br /&gt;
    /* non-critical section */&lt;br /&gt;
 &lt;br /&gt;
process Q:&lt;br /&gt;
  while(1)&lt;br /&gt;
    /* entry protocol */&lt;br /&gt;
    needQ = true&lt;br /&gt;
    turn = P&lt;br /&gt;
    while(needP &amp;amp;&amp;amp; turn != Q)&lt;br /&gt;
      ; /* busy wait */&lt;br /&gt;
    /* critical section */&lt;br /&gt;
    needQ = false&lt;br /&gt;
    /* non-critical section */&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Lezione del 20 ottobre 2017 ==&lt;br /&gt;
== Lezione del 24 ottobre 2017 ==&lt;br /&gt;
== Lezione del 27 ottobre 2017 ==&lt;br /&gt;
== Lezione del 31 ottobre 2017 ==&lt;br /&gt;
== Lezione del 3 novembre 2017 ==&lt;br /&gt;
== Lezione del 7 novembre 2017 ==&lt;br /&gt;
== Lezione del 10 novembre 2017 ==&lt;br /&gt;
== Lezione del 14 novembre 2017 ==&lt;br /&gt;
== Lezione del 17 novembre 2017 ==&lt;br /&gt;
== Lezione del 21 novembre 2017 ==&lt;br /&gt;
== Lezione del 24 novembre 2017 ==&lt;br /&gt;
== Lezione del 28 novembre 2017 ==&lt;br /&gt;
== Lezione del 1 dicembre 2017 ==&lt;br /&gt;
== Lezione del 5 dicembre 2017 ==&lt;br /&gt;
== Lezione del 12 dicembre 2017 ==&lt;br /&gt;
== Lezione del 15 dicembre 2017 ==&lt;/div&gt;</summary>
		<author><name>Matt</name></author>
	</entry>
	<entry>
		<id>https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_I_semestre&amp;diff=2027</id>
		<title>Lezioni Anno Accademico 2017/18 I semestre</title>
		<link rel="alternate" type="text/html" href="https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_I_semestre&amp;diff=2027"/>
		<updated>2017-10-10T20:27:11Z</updated>

		<summary type="html">&lt;p&gt;Matt: /* Lezione del 10 ottobre 2017 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;''scrivete qui idee, riassunti dei concetti espressi, commenti approfondimenti sulle lezioni.''&lt;br /&gt;
&lt;br /&gt;
== Lezione del 26 settembre 2017 ==&lt;br /&gt;
'''Titolo della lezione: W&amp;amp;#x2074; H Y'''&lt;br /&gt;
&lt;br /&gt;
* What:&lt;br /&gt;
* Who:&lt;br /&gt;
----&lt;br /&gt;
Noi studenti ed &amp;quot;entrambi&amp;quot; i professori.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* When:&lt;br /&gt;
----&lt;br /&gt;
Il corso ha durata annuale. Verrà svolto ogni martedì e venerdì dalle 15:30 alle 18:30.&lt;br /&gt;
* La lezione del martedì sarà dedicata alla programmazione concorrente;&lt;br /&gt;
* La lezione del venerdì sarà dedicata alla parte generale. Terminerà circa 15 minuti prima dell'orario stabilito.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* Where:&lt;br /&gt;
----&lt;br /&gt;
Sempre in Aula 1 Ercolani (E1): DISI, Scuole Ercolani, Mura Anteo Zamboni 2B.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* How:&lt;br /&gt;
----&lt;br /&gt;
Il corso si svolgerà tramite:&lt;br /&gt;
* Lezioni frontali in aula.&lt;br /&gt;
* Attività di laboratorio.&lt;br /&gt;
* Esercitazioni.&lt;br /&gt;
&lt;br /&gt;
Inoltre, durante l'ultimo periodo di lezioni del secondo semestre, vi sarà un periodo di ripasso del programma tramite esercizi, in vista dell'esame.&lt;br /&gt;
Durante la lezione del 3/10 sono state indicate alcune propedeuticità del corso. tra cui: programmazione, algoritmi e architettura degli elaboratori in primis.&lt;br /&gt;
Importanti sono anche i corsi di Analisi matematica e Algebra e geometria. Per farla breve, il primo anno è propedeutico al secondo.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* Why:&lt;br /&gt;
----&lt;br /&gt;
* '''Fonti e strumenti del corso:''' wiki creata di gruppo, lezioni frontali e esercitazioni.&lt;br /&gt;
**Non esiste un vero e proprio testo consigliato. Tutte le informazioni sono date DURANTE il corso.&lt;br /&gt;
** Per domande specifiche scrivere su mailing list (so@cs.unibo.it);&lt;br /&gt;
** Per esercizi, appunti e programma svolto rivolgersi al wiki (so.v2.cs.unibo.it);&lt;br /&gt;
** Per live streaming (www.cs.unibo.it/~renzo/live/).&lt;br /&gt;
* '''Modalit&amp;amp;agrave; di esame:''' esame scritto (diviso in due parti: una parte generale e una di programmazione concorrente) + progetto + orale (facoltativo), possibilità di dare solo lo scritto per ottenere un massimo di 18. La modalità per studenti in &amp;quot;difficoltà&amp;quot; è disponibile solo fino agli appelli autunnali.&lt;br /&gt;
** Si parlerà del progetto indicativamente a partire da Dicembre 2017.&lt;br /&gt;
** L'orale può essere sostenuto da chi vuole migliorare (peggiorare) il voto ottenuto, o da chi vuole ottenere la lode.&lt;br /&gt;
* '''Orario di ricevimento per il primo semestre (fino al 15/09/17):''' martedì alle 11:30.&lt;br /&gt;
* Universit&amp;amp;agrave; = docenti + studenti.&lt;br /&gt;
* Informatica = come generare informazione automatica.&lt;br /&gt;
* Hardware, Software, Elaborazione, Comunicazione, Memorizzazione, Digitale/Analogico.&lt;br /&gt;
La principale distinzione che facciamo tra '''dato''' e '''informazione''' è che il dato, da solo, è privo di significato. Se, tuttavia, viene interpretato in un particolare contesto allora può diventare informazione significativa per chi sta interpretando i dati.&lt;br /&gt;
&lt;br /&gt;
Un '''algoritmo''' è una o più sequenze non ambigue di passi che, dato un problema, ci permette di risolverlo. &lt;br /&gt;
&lt;br /&gt;
Il nostro algoritmo, scritto in un qualche linguaggio formale, diventa un '''programma''' (sostanzialmente un testo, un insieme di istruzioni).&lt;br /&gt;
&lt;br /&gt;
Un linguaggio è un entità software, ed è definito come una quadrupla (alfabeto, sintassi, lessico, semantica) dove:&lt;br /&gt;
*l'''alfabeto'' è l'insieme di simboli che compone il linguaggio.&lt;br /&gt;
*il ''lessico'' si può vedere come una funzione che va dai simboli a un booleano e ci dice quali sono le frasi ben formate.&lt;br /&gt;
*la ''sintassi'' determina quali delle sequenze scritte sono effettivamente corrette.&lt;br /&gt;
*la ''semantica'' associa un significato alle parole ben formate.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 29 settembre 2017 ==&lt;br /&gt;
'''Cos'è un sistema operativo e un po' di storia'''&lt;br /&gt;
&lt;br /&gt;
Un ''sistema operativo'' è un programma che gestisce i processi, le risorse e interfaccia le applicazioni con l'hardware dell'elaboratore.&lt;br /&gt;
&lt;br /&gt;
In particolare, il sistema operativo (laddove esiste) è il primo processo ad essere attivato e resta in vita fino allo spegnimento del calcolatore o al sopraggiungere di un errore fatale per il sistema.&lt;br /&gt;
&lt;br /&gt;
A cosa serve un sistema operativo?&lt;br /&gt;
Principalmente, ha i seguenti scopi:&lt;br /&gt;
# Facilitare l'utilizzo del sistema (senza S.O. sarebbe a carico del programmatore conoscere tutti i linguaggi con cui si comunica con l'architettura della macchina);&lt;br /&gt;
# Rendere affidabile, protetto e sicuro l'utilizzo del sistema (e.g. un processo potrebbe recar danno all'intero sistema se non controllato o gestito, potrebbe ignorare i permessi di visualizzazione di un file);&lt;br /&gt;
# Astrarre l'hardware (e.g. filesystem);&lt;br /&gt;
# Garantire l'efficienza (e.g. non tenere la CPU in idle adottando opportuni algoritmi di scheduling);&lt;br /&gt;
# Assicurare portabilità ;&lt;br /&gt;
&lt;br /&gt;
Un approccio molto usato è il cosiddetto approccio a strati (&amp;quot;a livelli&amp;quot;) in cui ogni strato utilizza i servizi forniti al livello inferiore e ne fornisce di nuovi a quello superiore.&lt;br /&gt;
Astraendo il nostro calcolatore possiamo piazzare al livello più basso l'hardware e, subito sopra, il sistema operativo. I due layer comunicano usando il linguaggio ISA (Instruction Set Architecture), nativo della CPU stessa, al quale si aggiungono quelli che permettono di comunicare con i vari controllori dei dispositivi come la scheda di rete, la stampante, etc. Sopra il livello del sistema operativo possiamo collocare quello delle librerie e, infine, quello degli applicativi.&lt;br /&gt;
&lt;br /&gt;
* '''Systemcall''' = meccanismo usato da un processo per richiedere al SO una qualche funzionalità a livello kernel. Un esempio è la funzione printf() del linguaggio C (stampa a video) che utilizza la systemcall write() (una delle systemcall per la gestione dei dispositivi).&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
----&lt;br /&gt;
'''Storia dei sistemi operativi'''&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''zero''' (1800 ca.) possiamo includere Babbage con la sua macchina analitica e lady Ada Lovelace. [[File:290px-AnalyticalMachine Babbage London.jpg |200px|thumb|right|Macchina analitica di Babbage]]&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''uno''' vediamo la comparsa delle valvole con tutti i problemi connessi. Non c'erano utilizzatori delle macchine, le stesse persone che le costruivano erano anche programmatori e fruitori delle stesse.&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''due''' (fine anni '60) arriva il transistor. Quest'ultimo è molto più veloce e piccolo della valvola e, soprattutto, meno incline a guasti. Le macchine iniziano ad essere più economiche grazie alla possibilità di avere un'economia di scala e quindi accessibili al grande pubblico. Questo fa si che, in queste prime fasi, i costruttori non siano più gli unici utilizzatori. [[File:Transistors.jpg |300px|thumb|right|Diversi tipi di Transistor]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
A questo punto poiché, pur essendo diventata più accessibile, una macchina costava ancora molto, nasce l'esigenza di non lasciare mai un processore senza lavoro (al fine di massimizzare i ricavi) e di condividere i dati.&lt;br /&gt;
In questa fase, infatti, abbiamo sistemi operativi di tipo ''batch'' (che collezionano tutto l'input all'inizio per calcolare e restituire l'output) dove l'inserimento di programmi e dati veniva fatto tramite schede perforate e non c'era interazione. Dai sistemi batch nasce lo ''SPOOL'' (Simultaneous Peripheral Operations On-Line).&lt;br /&gt;
&lt;br /&gt;
Si comincia a pensare di utilizzare i tempi di I/O per poter eseguire altri processi. Questo però solleva almeno due problematiche: da un lato serve un modo per sapere quando un input è davvero finito mentre, dall'altro, bisogna fare in modo che un processo che non richieda I/O non occupi la CPU per un tempo indefinito.&lt;br /&gt;
Per la prima problematica la soluzione è rappresentata dall'introduzione degli '''interrupt''', segnali elettrici inviati alla CPU alla fine di ogni input.&lt;br /&gt;
Per la seconda, invece, è stato introdotto il cosiddetto '''interval timer''' che non è altro che un dispositivo che invia interrupt dopo un determinato quanto di tempo assicurando che un processo non occupi per troppo tempo il processore.&lt;br /&gt;
Questo consente di realizzare sistemi time sharing (il cui più semplice algoritmo è il round-robin) dove un processo può essere in uno dei seguenti tre stati:&lt;br /&gt;
*'''READY''' pronto per essere eseguito ma, il processore è già occupato;&lt;br /&gt;
*'''RUNNING''' in esecuzione;&lt;br /&gt;
*'''WAIT''' in attesa di I/O.&lt;br /&gt;
&lt;br /&gt;
[[File:Dds.png]]&lt;br /&gt;
&lt;br /&gt;
Nella '''quarta''' generazione (anni '70 ca.) vediamo la nascita di molte innovazioni importanti. Si riesce a rimpicciolire di molto i processori facendoli divenire a tutti gli effetti micro-processori. Nei laboratori Bell nasce Unix, un sistema operativo rivoluzionario con time sharing e clonazione dei processi (un processo non viene mai creato dal nulla ma viene prima clonato da uno già esistente e poi, alla copia, viene fatto eseguire un programma). A causa di problemi di portabilità da PDP-9, macchina su cui Unix è nato, a PDP-11 nasce il linguaggio C e il suo compilatore (scritto anch'esso in C).&lt;br /&gt;
Mentre Apple I &amp;quot;porta&amp;quot; una nuova idea di pc per tutte le famiglie, nascono molte versioni di Unix.&lt;br /&gt;
Per impedire una deriva proprietaria in cui ogni produttore offriva tutto quello che offrivano gli altri ma con qualche feature in più, Richard Stallman fonda il progetto GNU (GNU's Not Unix) per il quale scrive tutta una serie di utilities come il famoso compilatore gcc.&lt;br /&gt;
A GNU, per essere operativo, manca però un kernel. Non volendo aspettare i tempi di sviluppo del kernel che Stallman aveva in mente, Linus Torlvalds scrive il kernel '''Linux''' da cui nasce il sistema operativo GNU/Linux.&lt;br /&gt;
&lt;br /&gt;
Nei tempi più recenti nascono i processori multi-core.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 3 ottobre 2017 ==&lt;br /&gt;
 &lt;br /&gt;
'''Introduzione alla programmazione concorrente: processi, thread e  librerie'''&lt;br /&gt;
&lt;br /&gt;
In programmazione concorrente le nozioni apprese di &amp;quot;algoritmo&amp;quot;, &amp;quot;programma&amp;quot; e &amp;quot;processo&amp;quot; devono essere aggiornate. Questo perché non vi è più un programma con un'unica sequenza esecutiva [o meglio, un unico filo (= thread) di esecuzione]. Appare ovvio come servino dei meccanismi per organizzare e sincronizzare tali fili esecutivi, in modo da evitare il verificarsi di eventi non desiderati.&lt;br /&gt;
&lt;br /&gt;
Quando parliamo di ''sistema concorrente'', ci riferiamo ad un sistema in cui due o più &amp;quot;attori&amp;quot; sono eseguiti parallelamente.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Questi attori possono essere '''processi''' o '''thread'''.&lt;br /&gt;
La differenza sostanziale tra queste due tipologie di attori riguarda la condivisione della memoria e dei dati su cui operano.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Più processi possono eseguire lo stesso programma ma, ognuno di essi, ha i propri dati su cui operare e il suo stato di avanzamento. In particolare:&lt;br /&gt;
* un processo è un'entità, un attore che ha delle proprietà e delle risorse associate;&lt;br /&gt;
* in ogni istante, il processo può essere interrotto.&amp;lt;br&amp;gt;&lt;br /&gt;
Più thread che vengono eseguiti parallelamente, invece, condividono gli stessi dati su cui operare.&amp;lt;br&amp;gt;&lt;br /&gt;
Codici di esempio:&lt;br /&gt;
&lt;br /&gt;
'''processi'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=C&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;unistd.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int main(int argc, char *argv[]){&lt;br /&gt;
   if(fork()) printf(&amp;quot;Yes&amp;quot;);&lt;br /&gt;
   else printf(&amp;quot;No&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
L'output generato sarà &amp;quot;YesNo&amp;quot; poiché, usando la systemcall ''fork()'', abbiamo creato una nuova copia del processo (un processo figlio). Il processo padre, che ha eseguito con successo la fork(), stamperà &amp;quot;Yes&amp;quot; mentre il figlio stamperà &amp;quot;No&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
'''thread'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=C&amp;gt;&lt;br /&gt;
#include &amp;lt;pthread.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
#include &amp;lt;assert.h&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
#define NUM_THREADS     5&lt;br /&gt;
#define MAX 1000000&lt;br /&gt;
&lt;br /&gt;
int sum;&lt;br /&gt;
 &lt;br /&gt;
void* perform_work(){&lt;br /&gt;
	int i;&lt;br /&gt;
	&lt;br /&gt;
	for(i = 0; i &amp;lt; MAX; i++) sum = sum + 1;&lt;br /&gt;
  	/* optionally: insert more useful stuff here */ &lt;br /&gt;
  	return NULL;&lt;br /&gt;
}&lt;br /&gt;
 &lt;br /&gt;
int main( int argc, char** argv ){&lt;br /&gt;
	pthread_t threads[NUM_THREADS];&lt;br /&gt;
  	int thread_args[NUM_THREADS];&lt;br /&gt;
  	int result_code;&lt;br /&gt;
  	unsigned index;&lt;br /&gt;
 &lt;br /&gt;
	sum = 0;&lt;br /&gt;
  	// create all threads one by one&lt;br /&gt;
  	for(index = 0; index &amp;lt; NUM_THREADS; ++index ){&lt;br /&gt;
    		thread_args[index] = index;&lt;br /&gt;
    		printf(&amp;quot;In main: creating thread %d\n&amp;quot;, index);&lt;br /&gt;
    		result_code = pthread_create(&amp;amp;threads[index], NULL, perform_work, NULL);&lt;br /&gt;
    		assert(!result_code);&lt;br /&gt;
  	} &lt;br /&gt;
  	// wait for each thread to complete&lt;br /&gt;
  	for(index = 0; index &amp;lt; NUM_THREADS; ++index ){&lt;br /&gt;
    		// block until thread 'index' completes&lt;br /&gt;
    		result_code = pthread_join(threads[index], NULL);&lt;br /&gt;
    		assert(!result_code);&lt;br /&gt;
    		printf(&amp;quot;In main: thread %d has completed\n&amp;quot;, index);&lt;br /&gt;
   	}&lt;br /&gt;
 &lt;br /&gt;
   	printf(&amp;quot;In main: All threads completed successfully\n&amp;quot; );&lt;br /&gt;
	printf(&amp;quot;Result: %d\n&amp;quot;, sum);&lt;br /&gt;
   	exit( EXIT_SUCCESS );&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Prima di discutere l'output generato dall'esecuzione di questo codice, è opportuno introdurre, brevemente, gli strumenti utilizzati.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
'''Pthread (POSIX thread)'''&lt;br /&gt;
&lt;br /&gt;
Citazione dal Silberschatz, Galvin, Gagne:&lt;br /&gt;
''Pthread si riferisce allo standard POSIX (IEEE 1003.1c) che definisce una API per la creazione e sincronizzazione dei thread.''&lt;br /&gt;
&lt;br /&gt;
Le istruzioni fondamentali sono:&lt;br /&gt;
* '''int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);''' che crea un thread;&lt;br /&gt;
* '''int pthread_join(pthread_t thread, void **retval);''' che aspetta che un thread sia completato prima di continuare l'esecuzione.&lt;br /&gt;
&lt;br /&gt;
Da ricordare che, per utilizzare pthread, va incluso l'header ''&amp;lt;pthread.h&amp;gt;'' e durante la compilazione va inclusa la libreria esterna tramite l'opzione ''-pthread''.&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
Torniamo all'output generato dall'esecuzione del secondo codice.&amp;lt;br&amp;gt;&lt;br /&gt;
Nel codice vengono generati cinque thread ed ognuno somma MAX (=1000000) volte 1 a sum. Ci aspettiamo un output di 5000000 ma, in realtà, viene stampato un valore molto minore.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Questo accade principalmente per due motivi:&lt;br /&gt;
*sommare una costante ad una variabile non è un'operazione atomica (richiede almeno tre istruzioni assembler: due mov ed una sum).&lt;br /&gt;
*l'interval timer può fermare l'esecuzione del thread mentre sta effettuando una delle tre operazioni richieste per aggiungere uno a sum e provocare un risultato errato.&lt;br /&gt;
&lt;br /&gt;
Questo è uno dei possibili problemi della programmazione concorrente conosciuto come ''race condition''.&amp;lt;br&amp;gt;&lt;br /&gt;
Un sistema di processi multipli presenta una '''race condition''' qualora il risultato finale dell'esecuzione dipenda dalla temporizzazione, o dall'ordine con cui i processi vengono eseguiti.&lt;br /&gt;
&lt;br /&gt;
Per risolvere questo problema possiamo adoperare ancora una volta pthread.&lt;br /&gt;
Nello specifico, ci venogono offerte istruzione per realizzare una '''mutua esclusione''' sulle risorse condivise in modo che l'operazione che prima non era atomiche,ora, lo sia e non porti più ad una computazione errata.&amp;lt;br&amp;gt;&lt;br /&gt;
Codice di esempio:&amp;lt;br&amp;gt;&lt;br /&gt;
'''Mutex'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=C&amp;gt;&lt;br /&gt;
#include &amp;lt;pthread.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
#include &amp;lt;assert.h&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
#define NUM_THREADS     5&lt;br /&gt;
#define MAX 1000000&lt;br /&gt;
&lt;br /&gt;
int sum;&lt;br /&gt;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;&lt;br /&gt;
 &lt;br /&gt;
void* perform_work(){&lt;br /&gt;
	int i;&lt;br /&gt;
	&lt;br /&gt;
	for(i = 0; i &amp;lt; MAX; i++){ &lt;br /&gt;
		pthread_mutex_lock(&amp;amp;mutex);		&lt;br /&gt;
		sum = sum + 1;&lt;br /&gt;
		pthread_mutex_unlock(&amp;amp;mutex);&lt;br /&gt;
	}&lt;br /&gt;
  	/* optionally: insert more useful stuff here */ &lt;br /&gt;
  	return NULL;&lt;br /&gt;
}&lt;br /&gt;
 &lt;br /&gt;
int main( int argc, char** argv ){&lt;br /&gt;
	pthread_t threads[NUM_THREADS];&lt;br /&gt;
  	int thread_args[NUM_THREADS];&lt;br /&gt;
  	int result_code;&lt;br /&gt;
  	unsigned index;&lt;br /&gt;
&lt;br /&gt;
	sum = 0;&lt;br /&gt;
  	// create all threads one by one&lt;br /&gt;
  	for(index = 0; index &amp;lt; NUM_THREADS; ++index ){&lt;br /&gt;
    		thread_args[index] = index;&lt;br /&gt;
    		printf(&amp;quot;In main: creating thread %d\n&amp;quot;, index);&lt;br /&gt;
    		result_code = pthread_create(&amp;amp;threads[index], NULL, perform_work, NULL);&lt;br /&gt;
    		assert(!result_code);&lt;br /&gt;
  	} &lt;br /&gt;
  	// wait for each thread to complete&lt;br /&gt;
  	for(index = 0; index &amp;lt; NUM_THREADS; ++index ){&lt;br /&gt;
    		// block until thread 'index' completes&lt;br /&gt;
    		result_code = pthread_join(threads[index], NULL);&lt;br /&gt;
    		assert(!result_code);&lt;br /&gt;
    		printf(&amp;quot;In main: thread %d has completed\n&amp;quot;, index);&lt;br /&gt;
   	}&lt;br /&gt;
 &lt;br /&gt;
   	printf(&amp;quot;In main: All threads completed successfully\n&amp;quot; );&lt;br /&gt;
	printf(&amp;quot;Result: %d\n&amp;quot;, sum);&lt;br /&gt;
   	exit( EXIT_SUCCESS );&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
'''MUTEX'''&lt;br /&gt;
&lt;br /&gt;
Un mutex è un dispositivo di MUTual EXclusion (mutua esclusione), ed è utile per proteggere le strutture dati condivise da modifiche concorrenti, implementare sezioni critiche e monitor.&lt;br /&gt;
&lt;br /&gt;
Un mutex ha due possibili stati: sbloccato (non posseduto da nessun thread) e bloccato (posseduto da un thread). Un mutex non può mai essere posseduto da due thread contemporaneamente.&amp;lt;br&amp;gt;Se un thread provasse ad acquisire il mutex, mentre quest'ultimo è già in possesso di un altro thread, dovrebbe aspettare finché non viene rilasciato.&lt;br /&gt;
La variabile che realizza il mutex, di tipo ''pthread_mutex_t'', viene inizializzata staticamente assegnandogli PTHREAD_MUTEX_INITIALIZER.&lt;br /&gt;
'''pthread_mutex_lock''' acquisisce immediatamente il mutex, se libero. Altrimenti aspetta fino a trovarla, prima o poi, libera.&amp;lt;br&amp;gt;&lt;br /&gt;
'''pthread_mutex_unlock''' rilascia il mutex.&lt;br /&gt;
&lt;br /&gt;
[da: https://sourceware.org/pthreads-win32/manual/pthread_mutex_init.html]&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
Con questa soluzione abbiamo raggiunto un risultato esatto pur avendo pagato in prestazioni: la computazione infatti è più lenta a causa del tempo richiesto dalla sincronizzazione tra i thread.&lt;br /&gt;
&lt;br /&gt;
Consideriamo ora due scenari leggermente più complicati:&lt;br /&gt;
* nel primo abbiamo due processi, A e B, che devono accedere a due risorse, X e Y, contemporaneamente prima di poter terminare. Cosa succede se, mentre A acquisisce X, B acquisisce Y?&lt;br /&gt;
* nel secondo abbiamo tre processi, A, B e C, che devono accedere alla risorsa X. Che succede se A e B, essendo più veloci, occupano insistentemente la risorsa e non permettono a C di utilizzarla?&lt;br /&gt;
&lt;br /&gt;
La prima condizione si chiama '''deadlock''', la seconda condizione si chiama '''starvation'''. Il deadlock (stallo) è un problema che non può scomparire autonomamente, mentre la starvation (&amp;quot;inedia&amp;quot;) si può risolvere da sola. A tal proposito ci viene incontro l'assioma di '''finite progress''': &amp;quot;ogni processo che può avanzare, prima o poi lo farà&amp;quot;.&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
In ogni caso, per parlare in maniera più approfondita di queste tematiche:&lt;br /&gt;
* avremo bisogno di un modello di riferimento&lt;br /&gt;
* avremo bisogno di paradigmi in grado di esprimere la concorrenza (alcuni paradigmi saranno rivolti alla concorrenza a memoria privata; altri al multithreading a risorse condivise)&lt;br /&gt;
* dovremo imparare a scrivere programmi&lt;br /&gt;
Nel nostro modello di riferimento l'assegnamento di costanti è un operazione atomica. Dagli esempi precedenti, infatti, si evince come ci sia bisogno di creare atomicità in sezioni critiche. Inoltre, il nostro modello dovrà rispettare le proprietà di safety e di liveness, ovvero:&lt;br /&gt;
* Tutti i processi danno la stessa risposta;&lt;br /&gt;
* Ogni processo corretto da come risposta una tra quelle proposte;&lt;br /&gt;
* Ogni processo prima o poi fornisce una risposta.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 6 ottobre 2017 ==&lt;br /&gt;
'''Il linguaggio C'''&lt;br /&gt;
&lt;br /&gt;
Il linguaggio C nasce nei primi anni 70 grazie al lavoro di Dennis Ritchie con lo scopo di scrivere il &lt;br /&gt;
sistema operativo Unix. Quali sono le caratteristiche di C e cosa lo rende un linguaggio adatto per scrivere un sistema&lt;br /&gt;
operativo? Un linguaggio per scrivere un sistema operativo deve:&lt;br /&gt;
* essere indipendente dalla macchina su cui viene scritto&lt;br /&gt;
* dare la possibilità di lavorare direttamente con la memoria&lt;br /&gt;
* permette di andare a modificare i singoli bit in memoria&lt;br /&gt;
* essere semplice da usare&lt;br /&gt;
* essere massimamente produttivo&lt;br /&gt;
* essere veloce, efficiente&lt;br /&gt;
C presenta le caratteristiche dette sopra e alcune altre:&lt;br /&gt;
* in C, dal punto di vista sintattico, tutto è funzione. Si può vedere un programma in C come un insieme di funzioni. La funzione principale è il '''main'''. L'unica cosa che la distingue è il nome, a parte quello il main è una funzione come le altre. Prende due argomenti: argcount e argvalue, che contengono rispettivamente il numero degli argomenti e un array contenente gli argomenti stessi. L'interfaccia del main è tipicamente: &amp;lt;syntaxhighlight lang=C&amp;gt; int main(int argc, char *argv[]) &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
* il costrutto fondamentale è l'espressione. ''expression'' ; = ''instruction''&lt;br /&gt;
* sono previste istruzioni di struttura &amp;lt;syntaxhighlight lang=C&amp;gt;if, while, switch, etc.&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
* le variabili, per poter essere utilizzate, devono essere sempre definite&lt;br /&gt;
* il C è un linguaggio fortemente tipato, però con un certo numero di eccezioni: ad esempio, è possibile fare il casting esplicito di un puntatore void * in qualsiasi tipo, senza che il compilatore dia errori o warnings, con conseguente perdita di type safety&lt;br /&gt;
* C è un linguaggio &amp;quot;per adulti&amp;quot;: non impone una struttura ordinata al codice, come ad esempio fa invece Python, ed è piena responsabilità del programmatore scrivere programmi ordinati/leggibili. Inoltre molte operazioni che in altri linguaggi non sarebbero permesse lo sono invece in C: anche in questo caso è responsabilità del programmatore fare corretto uso del linguaggio&lt;br /&gt;
* C è un linguaggio molto semplice. Come si sopperisce alle mancanze imposte da questa semplicità? Grazie alle librerie: in C tutte le funzionalità aggiuntive (non fornite dal linguaggio stesso, come l'allocazione di memoria, la stampa a schermo, la gestione di input e output) sono fornite dalle librerie&lt;br /&gt;
* C è anche sintatticamente semplice. Dove va a finire la complessità sintattica? Nel preprocessore: prima di essere compilato un programma in C passa attraverso un preprocessore, che produce codice in base alle direttive di preprocessione date nel codice stesso &amp;lt;syntaxhighlight lang=C&amp;gt;#ifdef VAR ... #endif, # e ## operator, __LINE__,etc&amp;lt;/syntaxhighlight&amp;gt; &lt;br /&gt;
* in C vi sono fondamentalmente due tipi di dato: int e float. Tutti gli altri &amp;quot;tipi&amp;quot; di dato sono derivati da essi. Si hanno diversi modificatori, che vanno a modificare la quantità di memoria allocata per la variabile. Alcuni di questi sono &amp;lt;syntaxhighlight lang=C&amp;gt;long, short, char, etc&amp;lt;/syntaxhighlight&amp;gt; Un dato di tipo long int ha, solitamente, la dimensione di una parola di memoria della macchina su cui è eseguito il programma. char alloca 8 bits. Se si prende una variabile di un certo tipo e lo si assegna ad una di tipo più ampio, il valore viene mantenuto. Viceversa quando si assegna una variabile di un tipo ad una di tipo meno ampio, una volta &amp;quot;superato il limite&amp;quot; si torna indietro ciclicamente al valore iniziale. Per queste situazioni il compilatore non dà né warnings né errors: sono perfettamente lecite in C&lt;br /&gt;
* in C si passa una varibile ad una funzione solo per valore; per passare &amp;quot;per riferimento&amp;quot; una variabile si passa per valore l'indirizzo della variabile&lt;br /&gt;
* in C si può fare uso di puntatori alla memoria. Dato un puntatore p, un'istruzione del tipo p+i va letta come: vai avanti di i posizioni della grandezza del tipo del puntatore ognuno. Un vettore in C non è altro che un puntatore che non può essere assegnato. Una scrittura del tipo array[i] è una abbreviazione di *(array + i). Non c'è controllo in C se vado oltre la memoria allocata per l'array. Perché? In Pascal, ad esempio, c'è un tale controllo. Il motivo per cui in C questo manca è che questo controllo genera ulteriore codice macchina. Dovendo C essere efficiente, non ci si può permettere un tale costo aggiuntivo. La responsabilità di non andare oltre è lasciata al programmatore&lt;br /&gt;
&lt;br /&gt;
Esercizio svolto che prende in input due valori tramite i parametri del metodo '''main''', li converte da sequenze di caratteri ad interi, per poi stamparli utilizzando delle '''System Call'''. Lo scopo dell’esercizio è dimostrare come sia possibile utilizzare il linguaggio C senza l’utilizzo di librerie particolari (ad eccezione di quelle per le System Call).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=C&amp;gt;&lt;br /&gt;
#define _GNU_SOURCE&lt;br /&gt;
#include &amp;lt;unistd.h&amp;gt;&lt;br /&gt;
#include &amp;lt;sys/syscall.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int _uatoi(char *s, int value){&lt;br /&gt;
  if (*s == 0)&lt;br /&gt;
    return value;&lt;br /&gt;
  else if (*s &amp;gt;= '0' &amp;amp;&amp;amp; *s &amp;lt;= '9'){&lt;br /&gt;
    value = value * 10 + (*s - '0');&lt;br /&gt;
    return _uatoi(s+1, value);&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int uatoi(char *s){&lt;br /&gt;
  return _uatoi(s, 0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
char *uitoa(int val, char *buf, int len){&lt;br /&gt;
  int i;&lt;br /&gt;
  for (len = len - 1; len &amp;gt;= 0 &amp;amp;&amp;amp; val &amp;gt; 0; len--, val /= 10){&lt;br /&gt;
    buf[len] = '0' + (val %10);&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  return buf + len + 1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#define MAXOUTLEN 10&lt;br /&gt;
int main (int argc, char *argv[]) {&lt;br /&gt;
    int tot = 0;&lt;br /&gt;
    int i;&lt;br /&gt;
    char buf[MAXOUTLEN];&lt;br /&gt;
    char *result;&lt;br /&gt;
    for (i = 1; i &amp;lt; argc; i++)&lt;br /&gt;
      tot += uatoi(argv[i]);&lt;br /&gt;
    result = uitoa(tot, buf, MAXOUTLEN);&lt;br /&gt;
    syscall(__NR_write, 1, result, MAXOUTLEN - (result - buf));&lt;br /&gt;
    syscall(__NR_write, 1, &amp;quot;\n&amp;quot;, 1);&lt;br /&gt;
    return tot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Lezione del 10 ottobre 2017 ==&lt;br /&gt;
'''Programmazione concorrente: notazione e un primo problema'''&lt;br /&gt;
&lt;br /&gt;
Esempio di possibile rappresentazione (che non useremo) di codice concorrente.&amp;lt;br&amp;gt;&lt;br /&gt;
'''''A'', ''B'', ''C'', ''D'', ''E''''' rappresentano porzioni di codice.&amp;lt;br&amp;gt;&lt;br /&gt;
'''''//''''' rappresenta esecuzione parallela&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
A&lt;br /&gt;
cobegin&lt;br /&gt;
  B&lt;br /&gt;
//&lt;br /&gt;
  C&lt;br /&gt;
//&lt;br /&gt;
  D&lt;br /&gt;
coend&lt;br /&gt;
E&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il codice sarà eseguito come: ''A'' -&amp;gt; ''BCD'' -&amp;gt; ''E''. ''E'' quindi verrà eseguito solo dopo la fine dell'esecuzione di ''BCD''.&lt;br /&gt;
&lt;br /&gt;
Un esempio che si presta molto bene ad illustrare la programmazione concorrente è quello del '''merge sort''' concorrente:&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
leggi vettore&lt;br /&gt;
cobegin&lt;br /&gt;
  ordina la prima metà del vettore&lt;br /&gt;
//&lt;br /&gt;
  ordina la seconda metà del vettore&lt;br /&gt;
coend&lt;br /&gt;
merge (miscela) i risultati&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Versione errata del merge sort concorrente:&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
leggi vettore&lt;br /&gt;
cobegin&lt;br /&gt;
  ordina la prima metà del vettore&lt;br /&gt;
//&lt;br /&gt;
  ordina la seconda metà del vettore&lt;br /&gt;
//  &lt;br /&gt;
  merge (miscela) i risultati&lt;br /&gt;
coend&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avendo un numero ''N'' di processi, vorremmo avere una notazione che ci permetta di definire una porzione di codice come atomica (la sezione critica). Come possiamo procedere?&amp;lt;br&amp;gt;&lt;br /&gt;
Teoricamente, vorremmo avere due funzioni, '''csenter()''' e '''csexit()''' che ci permettano di entrare e uscire dalla sezione critica del codice senza causare errori nella computazione.&amp;lt;br&amp;gt;&lt;br /&gt;
Quali sono le proprietà che queste funzioni debbono rispettare?&lt;br /&gt;
# Devono garantire la mutua esclusione (1 solo processo esegue la critical section alla volta). Contando ''n'' csenter() completate e ''m'' csexit() completate la differenza deve essere &amp;lt;=1&lt;br /&gt;
# Non devono fare deadlock tra di loro&lt;br /&gt;
# Non devono fare starvation tra loro&lt;br /&gt;
# ''(no unnecessary delay)'' un processo che non sta usando la critical section {csenter()..csexit()} non deve bloccare un altro processo dall'entrarvi.&lt;br /&gt;
&lt;br /&gt;
La soluzione a questo problema è nota come '''soluzione di Dekker''' (scritta e pubblicata da Dijkstra) che venne presentata in modo graduale attraverso l'utilizzo di quattro possibili, ma sbagliate, soluzioni.&lt;br /&gt;
&lt;br /&gt;
Vorremmo avere programmi che abbiano la seguente struttura:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
process[i], i=1,...,N&lt;br /&gt;
&lt;br /&gt;
while(1):&lt;br /&gt;
  csenter() //entrata nella sezione critica (critical section)&lt;br /&gt;
  critical code&lt;br /&gt;
  csexit() //uscita dalla sezione critica&lt;br /&gt;
  non-critical code&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ad esempio, tornando al problema dei thread che sommavano 1 a ''sum'' MAX volte, avremo:&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
process A:&lt;br /&gt;
&lt;br /&gt;
  for (i = 0; i &amp;lt; MAX; i++)&lt;br /&gt;
    csenter()&lt;br /&gt;
    sum += 1 //atomicità descritta da csenter(), csexit()&lt;br /&gt;
    // &amp;lt; sum += 1 &amp;gt; atomicità lasciata al processore&lt;br /&gt;
    csexit()&lt;br /&gt;
&lt;br /&gt;
process B:&lt;br /&gt;
&lt;br /&gt;
  for (i = 0; i &amp;lt; MAX; i++)&lt;br /&gt;
    csenter()&lt;br /&gt;
    sum += 1&lt;br /&gt;
    csexit()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
----&lt;br /&gt;
'''Soluzione 1: A turni'''&lt;br /&gt;
&lt;br /&gt;
Qual è il problema?&amp;lt;br&amp;gt;&lt;br /&gt;
*'''mutex'''                   OK&amp;lt;br&amp;gt;&lt;br /&gt;
*'''deadlock'''                OK&amp;lt;br&amp;gt;&lt;br /&gt;
*'''starvation'''              OK&amp;lt;br&amp;gt;&lt;br /&gt;
*'''no unnecessary delay'''    NO&amp;lt;br&amp;gt;&lt;br /&gt;
:: Perché se P ha bisogno spesso della critical section, P non deve aspettare che Q chieda la critical section e, all'uscita, lo autorizzi di nuovo ad usarla impostando ''turn = P''. Se Q una volta richiesta la sezione non la usa ma si ferma, P non può richiederla ancora perché appartiene ancora a Q.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
&lt;br /&gt;
turn=P&lt;br /&gt;
&lt;br /&gt;
process P:&lt;br /&gt;
  while(1)&lt;br /&gt;
    while (turn != P)&lt;br /&gt;
      ;&lt;br /&gt;
    critical code&lt;br /&gt;
    turn = Q&lt;br /&gt;
    non-critical code&lt;br /&gt;
&lt;br /&gt;
process Q:&lt;br /&gt;
  while(1)&lt;br /&gt;
    while (turn != Q)&lt;br /&gt;
    critical code&lt;br /&gt;
    turn = P&lt;br /&gt;
    non-critical code&lt;br /&gt;
 &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
'''Soluzione 2: con 2 variabili''', cerchiamo di risolvere il problema dei turni&lt;br /&gt;
&lt;br /&gt;
Finché Q usa la sezione P aspetta, quando Q ha finito P prende la sezione critica e non ci sono ritardi indesiderati.&lt;br /&gt;
&lt;br /&gt;
*'''starvation'''              OK&lt;br /&gt;
*'''deadlock'''                OK&lt;br /&gt;
*'''no unnecessary delay'''    OK&lt;br /&gt;
*'''mutex'''                   NO &lt;br /&gt;
:: Possono essere entrambi come false, escono insieme nel while, tutti e due mettono a true ed entrano assieme nel while.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
&lt;br /&gt;
inP = false;&lt;br /&gt;
inQ = false;&lt;br /&gt;
&lt;br /&gt;
process P:&lt;br /&gt;
  while(1)&lt;br /&gt;
    while (inQ)&lt;br /&gt;
      ;&lt;br /&gt;
    inP = true&lt;br /&gt;
    critical code&lt;br /&gt;
    inP = false&lt;br /&gt;
    non-critical code&lt;br /&gt;
&lt;br /&gt;
process Q:&lt;br /&gt;
  while(1)&lt;br /&gt;
    while (inP)&lt;br /&gt;
      ;&lt;br /&gt;
    inQ = true&lt;br /&gt;
    critical code&lt;br /&gt;
    inQ = false&lt;br /&gt;
    non-critical code&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
'''Soluzione 3: il problema di soluzione 2 è che i processi non sono al corrente dell'intenzione dell'altro di entrare''' nel while quindi si crea il problema.&lt;br /&gt;
*'''mutex'''                   OK&lt;br /&gt;
*'''deadlock'''                NO (potrebbero entrambi richiedere, contemporaneamente, l'ingresso alla critical section)&lt;br /&gt;
*'''starvation'''              OK&lt;br /&gt;
*'''non unnecessary delay'''   OK&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
needP = false;&lt;br /&gt;
needQ = false;&lt;br /&gt;
&lt;br /&gt;
process P:&lt;br /&gt;
  while(1)&lt;br /&gt;
    needP = true&lt;br /&gt;
    while (needQ)&lt;br /&gt;
      ;&lt;br /&gt;
    critical code&lt;br /&gt;
    needP = false&lt;br /&gt;
    non-critical code&lt;br /&gt;
&lt;br /&gt;
process Q:&lt;br /&gt;
  while(1)&lt;br /&gt;
    needQ = true&lt;br /&gt;
    while (needP)&lt;br /&gt;
      ;&lt;br /&gt;
    critical code&lt;br /&gt;
    needQ = false&lt;br /&gt;
    non-critical code&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
'''Soluzione 4''':&lt;br /&gt;
&lt;br /&gt;
*'''mutex'''                 OK&lt;br /&gt;
*'''deadlock'''              OK&lt;br /&gt;
*'''no unnecessary delay'''  OK&lt;br /&gt;
*'''starvation'''            NO &lt;br /&gt;
:: Perché può esistere il processo che prova sempre a chiedere nel momento sbagliato e non esegue mai.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
inP = false;&lt;br /&gt;
inQ = false;&lt;br /&gt;
&lt;br /&gt;
process P:&lt;br /&gt;
  while(1)&lt;br /&gt;
    inP = true&lt;br /&gt;
    while (inQ){&lt;br /&gt;
        inP = false;&lt;br /&gt;
        delay&lt;br /&gt;
        inP = true;&lt;br /&gt;
    }&lt;br /&gt;
    critical code&lt;br /&gt;
    inP = false&lt;br /&gt;
    non-critical code&lt;br /&gt;
&lt;br /&gt;
process Q:&lt;br /&gt;
  while(1)&lt;br /&gt;
    inQ = true&lt;br /&gt;
    while (inQ){&lt;br /&gt;
        inQ = false;&lt;br /&gt;
        delay&lt;br /&gt;
        inQ = true;&lt;br /&gt;
    }&lt;br /&gt;
    critical code&lt;br /&gt;
    inQ = false&lt;br /&gt;
    non-critical code&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
'''Soluzione finale''':&amp;lt;br&amp;gt;&lt;br /&gt;
Uniamo soluzione 1 (poco simmetrica) e soluzione 4 (troppo simmetrica) usando quello che ci serve (attraverso ''needP'' e ''needQ'') e, se dovessimo essere tutti e due contemporaneamente a voler entrare, usiamo i turni (''turn'') per entrare nella critical section (settando il need dell'altro processo a false).&lt;br /&gt;
&lt;br /&gt;
*'''mutex'''                         OK&lt;br /&gt;
*'''deadlock'''                      OK&lt;br /&gt;
*'''starvation'''                    OK&lt;br /&gt;
*'''non unnecessary delay'''         OK&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
needP = false;&lt;br /&gt;
needQ = false;&lt;br /&gt;
turn = P&lt;br /&gt;
&lt;br /&gt;
process P:&lt;br /&gt;
  while(1)&lt;br /&gt;
    needP = true&lt;br /&gt;
    while (needQ)&lt;br /&gt;
      if (turn != P) {&lt;br /&gt;
        needP = false;&lt;br /&gt;
        while ( turn != P)&lt;br /&gt;
        needP = true;&lt;br /&gt;
    }&lt;br /&gt;
    /*critical code*/&lt;br /&gt;
    turn = Q&lt;br /&gt;
    needP = false&lt;br /&gt;
    /*non-critical code*/&lt;br /&gt;
&lt;br /&gt;
process P:&lt;br /&gt;
  while(1)&lt;br /&gt;
    needQ = true&lt;br /&gt;
    while (needP)&lt;br /&gt;
      if (turn != Q) {&lt;br /&gt;
        needQ = false;&lt;br /&gt;
        while ( turn != Q)&lt;br /&gt;
        needQ = true;&lt;br /&gt;
    }&lt;br /&gt;
    /*critical code*/&lt;br /&gt;
    turn = P&lt;br /&gt;
    needQ = false&lt;br /&gt;
    /*non-critical code*/&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Lezione del 13 ottobre 2017 ==&lt;br /&gt;
== Lezione del 17 ottobre 2017 ==&lt;br /&gt;
== Lezione del 20 ottobre 2017 ==&lt;br /&gt;
== Lezione del 24 ottobre 2017 ==&lt;br /&gt;
== Lezione del 27 ottobre 2017 ==&lt;br /&gt;
== Lezione del 31 ottobre 2017 ==&lt;br /&gt;
== Lezione del 3 novembre 2017 ==&lt;br /&gt;
== Lezione del 7 novembre 2017 ==&lt;br /&gt;
== Lezione del 10 novembre 2017 ==&lt;br /&gt;
== Lezione del 14 novembre 2017 ==&lt;br /&gt;
== Lezione del 17 novembre 2017 ==&lt;br /&gt;
== Lezione del 21 novembre 2017 ==&lt;br /&gt;
== Lezione del 24 novembre 2017 ==&lt;br /&gt;
== Lezione del 28 novembre 2017 ==&lt;br /&gt;
== Lezione del 1 dicembre 2017 ==&lt;br /&gt;
== Lezione del 5 dicembre 2017 ==&lt;br /&gt;
== Lezione del 12 dicembre 2017 ==&lt;br /&gt;
== Lezione del 15 dicembre 2017 ==&lt;/div&gt;</summary>
		<author><name>Matt</name></author>
	</entry>
	<entry>
		<id>https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_I_semestre&amp;diff=2026</id>
		<title>Lezioni Anno Accademico 2017/18 I semestre</title>
		<link rel="alternate" type="text/html" href="https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_I_semestre&amp;diff=2026"/>
		<updated>2017-10-10T20:12:39Z</updated>

		<summary type="html">&lt;p&gt;Matt: /* Lezione del 10 ottobre 2017 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;''scrivete qui idee, riassunti dei concetti espressi, commenti approfondimenti sulle lezioni.''&lt;br /&gt;
&lt;br /&gt;
== Lezione del 26 settembre 2017 ==&lt;br /&gt;
'''Titolo della lezione: W&amp;amp;#x2074; H Y'''&lt;br /&gt;
&lt;br /&gt;
* What:&lt;br /&gt;
* Who:&lt;br /&gt;
----&lt;br /&gt;
Noi studenti ed &amp;quot;entrambi&amp;quot; i professori.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* When:&lt;br /&gt;
----&lt;br /&gt;
Il corso ha durata annuale. Verrà svolto ogni martedì e venerdì dalle 15:30 alle 18:30.&lt;br /&gt;
* La lezione del martedì sarà dedicata alla programmazione concorrente;&lt;br /&gt;
* La lezione del venerdì sarà dedicata alla parte generale. Terminerà circa 15 minuti prima dell'orario stabilito.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* Where:&lt;br /&gt;
----&lt;br /&gt;
Sempre in Aula 1 Ercolani (E1): DISI, Scuole Ercolani, Mura Anteo Zamboni 2B.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* How:&lt;br /&gt;
----&lt;br /&gt;
Il corso si svolgerà tramite:&lt;br /&gt;
* Lezioni frontali in aula.&lt;br /&gt;
* Attività di laboratorio.&lt;br /&gt;
* Esercitazioni.&lt;br /&gt;
&lt;br /&gt;
Inoltre, durante l'ultimo periodo di lezioni del secondo semestre, vi sarà un periodo di ripasso del programma tramite esercizi, in vista dell'esame.&lt;br /&gt;
Durante la lezione del 3/10 sono state indicate alcune propedeuticità del corso. tra cui: programmazione, algoritmi e architettura degli elaboratori in primis.&lt;br /&gt;
Importanti sono anche i corsi di Analisi matematica e Algebra e geometria. Per farla breve, il primo anno è propedeutico al secondo.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* Why:&lt;br /&gt;
----&lt;br /&gt;
* '''Fonti e strumenti del corso:''' wiki creata di gruppo, lezioni frontali e esercitazioni.&lt;br /&gt;
**Non esiste un vero e proprio testo consigliato. Tutte le informazioni sono date DURANTE il corso.&lt;br /&gt;
** Per domande specifiche scrivere su mailing list (so@cs.unibo.it);&lt;br /&gt;
** Per esercizi, appunti e programma svolto rivolgersi al wiki (so.v2.cs.unibo.it);&lt;br /&gt;
** Per live streaming (www.cs.unibo.it/~renzo/live/).&lt;br /&gt;
* '''Modalit&amp;amp;agrave; di esame:''' esame scritto (diviso in due parti: una parte generale e una di programmazione concorrente) + progetto + orale (facoltativo), possibilità di dare solo lo scritto per ottenere un massimo di 18. La modalità per studenti in &amp;quot;difficoltà&amp;quot; è disponibile solo fino agli appelli autunnali.&lt;br /&gt;
** Si parlerà del progetto indicativamente a partire da Dicembre 2017.&lt;br /&gt;
** L'orale può essere sostenuto da chi vuole migliorare (peggiorare) il voto ottenuto, o da chi vuole ottenere la lode.&lt;br /&gt;
* '''Orario di ricevimento per il primo semestre (fino al 15/09/17):''' martedì alle 11:30.&lt;br /&gt;
* Universit&amp;amp;agrave; = docenti + studenti.&lt;br /&gt;
* Informatica = come generare informazione automatica.&lt;br /&gt;
* Hardware, Software, Elaborazione, Comunicazione, Memorizzazione, Digitale/Analogico.&lt;br /&gt;
La principale distinzione che facciamo tra '''dato''' e '''informazione''' è che il dato, da solo, è privo di significato. Se, tuttavia, viene interpretato in un particolare contesto allora può diventare informazione significativa per chi sta interpretando i dati.&lt;br /&gt;
&lt;br /&gt;
Un '''algoritmo''' è una o più sequenze non ambigue di passi che, dato un problema, ci permette di risolverlo. &lt;br /&gt;
&lt;br /&gt;
Il nostro algoritmo, scritto in un qualche linguaggio formale, diventa un '''programma''' (sostanzialmente un testo, un insieme di istruzioni).&lt;br /&gt;
&lt;br /&gt;
Un linguaggio è un entità software, ed è definito come una quadrupla (alfabeto, sintassi, lessico, semantica) dove:&lt;br /&gt;
*l'''alfabeto'' è l'insieme di simboli che compone il linguaggio.&lt;br /&gt;
*il ''lessico'' si può vedere come una funzione che va dai simboli a un booleano e ci dice quali sono le frasi ben formate.&lt;br /&gt;
*la ''sintassi'' determina quali delle sequenze scritte sono effettivamente corrette.&lt;br /&gt;
*la ''semantica'' associa un significato alle parole ben formate.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 29 settembre 2017 ==&lt;br /&gt;
'''Cos'è un sistema operativo e un po' di storia'''&lt;br /&gt;
&lt;br /&gt;
Un ''sistema operativo'' è un programma che gestisce i processi, le risorse e interfaccia le applicazioni con l'hardware dell'elaboratore.&lt;br /&gt;
&lt;br /&gt;
In particolare, il sistema operativo (laddove esiste) è il primo processo ad essere attivato e resta in vita fino allo spegnimento del calcolatore o al sopraggiungere di un errore fatale per il sistema.&lt;br /&gt;
&lt;br /&gt;
A cosa serve un sistema operativo?&lt;br /&gt;
Principalmente, ha i seguenti scopi:&lt;br /&gt;
# Facilitare l'utilizzo del sistema (senza S.O. sarebbe a carico del programmatore conoscere tutti i linguaggi con cui si comunica con l'architettura della macchina);&lt;br /&gt;
# Rendere affidabile, protetto e sicuro l'utilizzo del sistema (e.g. un processo potrebbe recar danno all'intero sistema se non controllato o gestito, potrebbe ignorare i permessi di visualizzazione di un file);&lt;br /&gt;
# Astrarre l'hardware (e.g. filesystem);&lt;br /&gt;
# Garantire l'efficienza (e.g. non tenere la CPU in idle adottando opportuni algoritmi di scheduling);&lt;br /&gt;
# Assicurare portabilità ;&lt;br /&gt;
&lt;br /&gt;
Un approccio molto usato è il cosiddetto approccio a strati (&amp;quot;a livelli&amp;quot;) in cui ogni strato utilizza i servizi forniti al livello inferiore e ne fornisce di nuovi a quello superiore.&lt;br /&gt;
Astraendo il nostro calcolatore possiamo piazzare al livello più basso l'hardware e, subito sopra, il sistema operativo. I due layer comunicano usando il linguaggio ISA (Instruction Set Architecture), nativo della CPU stessa, al quale si aggiungono quelli che permettono di comunicare con i vari controllori dei dispositivi come la scheda di rete, la stampante, etc. Sopra il livello del sistema operativo possiamo collocare quello delle librerie e, infine, quello degli applicativi.&lt;br /&gt;
&lt;br /&gt;
* '''Systemcall''' = meccanismo usato da un processo per richiedere al SO una qualche funzionalità a livello kernel. Un esempio è la funzione printf() del linguaggio C (stampa a video) che utilizza la systemcall write() (una delle systemcall per la gestione dei dispositivi).&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
----&lt;br /&gt;
'''Storia dei sistemi operativi'''&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''zero''' (1800 ca.) possiamo includere Babbage con la sua macchina analitica e lady Ada Lovelace. [[File:290px-AnalyticalMachine Babbage London.jpg |200px|thumb|right|Macchina analitica di Babbage]]&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''uno''' vediamo la comparsa delle valvole con tutti i problemi connessi. Non c'erano utilizzatori delle macchine, le stesse persone che le costruivano erano anche programmatori e fruitori delle stesse.&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''due''' (fine anni '60) arriva il transistor. Quest'ultimo è molto più veloce e piccolo della valvola e, soprattutto, meno incline a guasti. Le macchine iniziano ad essere più economiche grazie alla possibilità di avere un'economia di scala e quindi accessibili al grande pubblico. Questo fa si che, in queste prime fasi, i costruttori non siano più gli unici utilizzatori. [[File:Transistors.jpg |300px|thumb|right|Diversi tipi di Transistor]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
A questo punto poiché, pur essendo diventata più accessibile, una macchina costava ancora molto, nasce l'esigenza di non lasciare mai un processore senza lavoro (al fine di massimizzare i ricavi) e di condividere i dati.&lt;br /&gt;
In questa fase, infatti, abbiamo sistemi operativi di tipo ''batch'' (che collezionano tutto l'input all'inizio per calcolare e restituire l'output) dove l'inserimento di programmi e dati veniva fatto tramite schede perforate e non c'era interazione. Dai sistemi batch nasce lo ''SPOOL'' (Simultaneous Peripheral Operations On-Line).&lt;br /&gt;
&lt;br /&gt;
Si comincia a pensare di utilizzare i tempi di I/O per poter eseguire altri processi. Questo però solleva almeno due problematiche: da un lato serve un modo per sapere quando un input è davvero finito mentre, dall'altro, bisogna fare in modo che un processo che non richieda I/O non occupi la CPU per un tempo indefinito.&lt;br /&gt;
Per la prima problematica la soluzione è rappresentata dall'introduzione degli '''interrupt''', segnali elettrici inviati alla CPU alla fine di ogni input.&lt;br /&gt;
Per la seconda, invece, è stato introdotto il cosiddetto '''interval timer''' che non è altro che un dispositivo che invia interrupt dopo un determinato quanto di tempo assicurando che un processo non occupi per troppo tempo il processore.&lt;br /&gt;
Questo consente di realizzare sistemi time sharing (il cui più semplice algoritmo è il round-robin) dove un processo può essere in uno dei seguenti tre stati:&lt;br /&gt;
*'''READY''' pronto per essere eseguito ma, il processore è già occupato;&lt;br /&gt;
*'''RUNNING''' in esecuzione;&lt;br /&gt;
*'''WAIT''' in attesa di I/O.&lt;br /&gt;
&lt;br /&gt;
[[File:Dds.png]]&lt;br /&gt;
&lt;br /&gt;
Nella '''quarta''' generazione (anni '70 ca.) vediamo la nascita di molte innovazioni importanti. Si riesce a rimpicciolire di molto i processori facendoli divenire a tutti gli effetti micro-processori. Nei laboratori Bell nasce Unix, un sistema operativo rivoluzionario con time sharing e clonazione dei processi (un processo non viene mai creato dal nulla ma viene prima clonato da uno già esistente e poi, alla copia, viene fatto eseguire un programma). A causa di problemi di portabilità da PDP-9, macchina su cui Unix è nato, a PDP-11 nasce il linguaggio C e il suo compilatore (scritto anch'esso in C).&lt;br /&gt;
Mentre Apple I &amp;quot;porta&amp;quot; una nuova idea di pc per tutte le famiglie, nascono molte versioni di Unix.&lt;br /&gt;
Per impedire una deriva proprietaria in cui ogni produttore offriva tutto quello che offrivano gli altri ma con qualche feature in più, Richard Stallman fonda il progetto GNU (GNU's Not Unix) per il quale scrive tutta una serie di utilities come il famoso compilatore gcc.&lt;br /&gt;
A GNU, per essere operativo, manca però un kernel. Non volendo aspettare i tempi di sviluppo del kernel che Stallman aveva in mente, Linus Torlvalds scrive il kernel '''Linux''' da cui nasce il sistema operativo GNU/Linux.&lt;br /&gt;
&lt;br /&gt;
Nei tempi più recenti nascono i processori multi-core.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 3 ottobre 2017 ==&lt;br /&gt;
 &lt;br /&gt;
'''Introduzione alla programmazione concorrente: processi, thread e  librerie'''&lt;br /&gt;
&lt;br /&gt;
In programmazione concorrente le nozioni apprese di &amp;quot;algoritmo&amp;quot;, &amp;quot;programma&amp;quot; e &amp;quot;processo&amp;quot; devono essere aggiornate. Questo perché non vi è più un programma con un'unica sequenza esecutiva [o meglio, un unico filo (= thread) di esecuzione]. Appare ovvio come servino dei meccanismi per organizzare e sincronizzare tali fili esecutivi, in modo da evitare il verificarsi di eventi non desiderati.&lt;br /&gt;
&lt;br /&gt;
Quando parliamo di ''sistema concorrente'', ci riferiamo ad un sistema in cui due o più &amp;quot;attori&amp;quot; sono eseguiti parallelamente.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Questi attori possono essere '''processi''' o '''thread'''.&lt;br /&gt;
La differenza sostanziale tra queste due tipologie di attori riguarda la condivisione della memoria e dei dati su cui operano.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Più processi possono eseguire lo stesso programma ma, ognuno di essi, ha i propri dati su cui operare e il suo stato di avanzamento. In particolare:&lt;br /&gt;
* un processo è un'entità, un attore che ha delle proprietà e delle risorse associate;&lt;br /&gt;
* in ogni istante, il processo può essere interrotto.&amp;lt;br&amp;gt;&lt;br /&gt;
Più thread che vengono eseguiti parallelamente, invece, condividono gli stessi dati su cui operare.&amp;lt;br&amp;gt;&lt;br /&gt;
Codici di esempio:&lt;br /&gt;
&lt;br /&gt;
'''processi'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=C&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;unistd.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int main(int argc, char *argv[]){&lt;br /&gt;
   if(fork()) printf(&amp;quot;Yes&amp;quot;);&lt;br /&gt;
   else printf(&amp;quot;No&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
L'output generato sarà &amp;quot;YesNo&amp;quot; poiché, usando la systemcall ''fork()'', abbiamo creato una nuova copia del processo (un processo figlio). Il processo padre, che ha eseguito con successo la fork(), stamperà &amp;quot;Yes&amp;quot; mentre il figlio stamperà &amp;quot;No&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
'''thread'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=C&amp;gt;&lt;br /&gt;
#include &amp;lt;pthread.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
#include &amp;lt;assert.h&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
#define NUM_THREADS     5&lt;br /&gt;
#define MAX 1000000&lt;br /&gt;
&lt;br /&gt;
int sum;&lt;br /&gt;
 &lt;br /&gt;
void* perform_work(){&lt;br /&gt;
	int i;&lt;br /&gt;
	&lt;br /&gt;
	for(i = 0; i &amp;lt; MAX; i++) sum = sum + 1;&lt;br /&gt;
  	/* optionally: insert more useful stuff here */ &lt;br /&gt;
  	return NULL;&lt;br /&gt;
}&lt;br /&gt;
 &lt;br /&gt;
int main( int argc, char** argv ){&lt;br /&gt;
	pthread_t threads[NUM_THREADS];&lt;br /&gt;
  	int thread_args[NUM_THREADS];&lt;br /&gt;
  	int result_code;&lt;br /&gt;
  	unsigned index;&lt;br /&gt;
 &lt;br /&gt;
	sum = 0;&lt;br /&gt;
  	// create all threads one by one&lt;br /&gt;
  	for(index = 0; index &amp;lt; NUM_THREADS; ++index ){&lt;br /&gt;
    		thread_args[index] = index;&lt;br /&gt;
    		printf(&amp;quot;In main: creating thread %d\n&amp;quot;, index);&lt;br /&gt;
    		result_code = pthread_create(&amp;amp;threads[index], NULL, perform_work, NULL);&lt;br /&gt;
    		assert(!result_code);&lt;br /&gt;
  	} &lt;br /&gt;
  	// wait for each thread to complete&lt;br /&gt;
  	for(index = 0; index &amp;lt; NUM_THREADS; ++index ){&lt;br /&gt;
    		// block until thread 'index' completes&lt;br /&gt;
    		result_code = pthread_join(threads[index], NULL);&lt;br /&gt;
    		assert(!result_code);&lt;br /&gt;
    		printf(&amp;quot;In main: thread %d has completed\n&amp;quot;, index);&lt;br /&gt;
   	}&lt;br /&gt;
 &lt;br /&gt;
   	printf(&amp;quot;In main: All threads completed successfully\n&amp;quot; );&lt;br /&gt;
	printf(&amp;quot;Result: %d\n&amp;quot;, sum);&lt;br /&gt;
   	exit( EXIT_SUCCESS );&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Prima di discutere l'output generato dall'esecuzione di questo codice, è opportuno introdurre, brevemente, gli strumenti utilizzati.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
'''Pthread (POSIX thread)'''&lt;br /&gt;
&lt;br /&gt;
Citazione dal Silberschatz, Galvin, Gagne:&lt;br /&gt;
''Pthread si riferisce allo standard POSIX (IEEE 1003.1c) che definisce una API per la creazione e sincronizzazione dei thread.''&lt;br /&gt;
&lt;br /&gt;
Le istruzioni fondamentali sono:&lt;br /&gt;
* '''int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);''' che crea un thread;&lt;br /&gt;
* '''int pthread_join(pthread_t thread, void **retval);''' che aspetta che un thread sia completato prima di continuare l'esecuzione.&lt;br /&gt;
&lt;br /&gt;
Da ricordare che, per utilizzare pthread, va incluso l'header ''&amp;lt;pthread.h&amp;gt;'' e durante la compilazione va inclusa la libreria esterna tramite l'opzione ''-pthread''.&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
Torniamo all'output generato dall'esecuzione del secondo codice.&amp;lt;br&amp;gt;&lt;br /&gt;
Nel codice vengono generati cinque thread ed ognuno somma MAX (=1000000) volte 1 a sum. Ci aspettiamo un output di 5000000 ma, in realtà, viene stampato un valore molto minore.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Questo accade principalmente per due motivi:&lt;br /&gt;
*sommare una costante ad una variabile non è un'operazione atomica (richiede almeno tre istruzioni assembler: due mov ed una sum).&lt;br /&gt;
*l'interval timer può fermare l'esecuzione del thread mentre sta effettuando una delle tre operazioni richieste per aggiungere uno a sum e provocare un risultato errato.&lt;br /&gt;
&lt;br /&gt;
Questo è uno dei possibili problemi della programmazione concorrente conosciuto come ''race condition''.&amp;lt;br&amp;gt;&lt;br /&gt;
Un sistema di processi multipli presenta una '''race condition''' qualora il risultato finale dell'esecuzione dipenda dalla temporizzazione, o dall'ordine con cui i processi vengono eseguiti.&lt;br /&gt;
&lt;br /&gt;
Per risolvere questo problema possiamo adoperare ancora una volta pthread.&lt;br /&gt;
Nello specifico, ci venogono offerte istruzione per realizzare una '''mutua esclusione''' sulle risorse condivise in modo che l'operazione che prima non era atomiche,ora, lo sia e non porti più ad una computazione errata.&amp;lt;br&amp;gt;&lt;br /&gt;
Codice di esempio:&amp;lt;br&amp;gt;&lt;br /&gt;
'''Mutex'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=C&amp;gt;&lt;br /&gt;
#include &amp;lt;pthread.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
#include &amp;lt;assert.h&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
#define NUM_THREADS     5&lt;br /&gt;
#define MAX 1000000&lt;br /&gt;
&lt;br /&gt;
int sum;&lt;br /&gt;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;&lt;br /&gt;
 &lt;br /&gt;
void* perform_work(){&lt;br /&gt;
	int i;&lt;br /&gt;
	&lt;br /&gt;
	for(i = 0; i &amp;lt; MAX; i++){ &lt;br /&gt;
		pthread_mutex_lock(&amp;amp;mutex);		&lt;br /&gt;
		sum = sum + 1;&lt;br /&gt;
		pthread_mutex_unlock(&amp;amp;mutex);&lt;br /&gt;
	}&lt;br /&gt;
  	/* optionally: insert more useful stuff here */ &lt;br /&gt;
  	return NULL;&lt;br /&gt;
}&lt;br /&gt;
 &lt;br /&gt;
int main( int argc, char** argv ){&lt;br /&gt;
	pthread_t threads[NUM_THREADS];&lt;br /&gt;
  	int thread_args[NUM_THREADS];&lt;br /&gt;
  	int result_code;&lt;br /&gt;
  	unsigned index;&lt;br /&gt;
&lt;br /&gt;
	sum = 0;&lt;br /&gt;
  	// create all threads one by one&lt;br /&gt;
  	for(index = 0; index &amp;lt; NUM_THREADS; ++index ){&lt;br /&gt;
    		thread_args[index] = index;&lt;br /&gt;
    		printf(&amp;quot;In main: creating thread %d\n&amp;quot;, index);&lt;br /&gt;
    		result_code = pthread_create(&amp;amp;threads[index], NULL, perform_work, NULL);&lt;br /&gt;
    		assert(!result_code);&lt;br /&gt;
  	} &lt;br /&gt;
  	// wait for each thread to complete&lt;br /&gt;
  	for(index = 0; index &amp;lt; NUM_THREADS; ++index ){&lt;br /&gt;
    		// block until thread 'index' completes&lt;br /&gt;
    		result_code = pthread_join(threads[index], NULL);&lt;br /&gt;
    		assert(!result_code);&lt;br /&gt;
    		printf(&amp;quot;In main: thread %d has completed\n&amp;quot;, index);&lt;br /&gt;
   	}&lt;br /&gt;
 &lt;br /&gt;
   	printf(&amp;quot;In main: All threads completed successfully\n&amp;quot; );&lt;br /&gt;
	printf(&amp;quot;Result: %d\n&amp;quot;, sum);&lt;br /&gt;
   	exit( EXIT_SUCCESS );&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
'''MUTEX'''&lt;br /&gt;
&lt;br /&gt;
Un mutex è un dispositivo di MUTual EXclusion (mutua esclusione), ed è utile per proteggere le strutture dati condivise da modifiche concorrenti, implementare sezioni critiche e monitor.&lt;br /&gt;
&lt;br /&gt;
Un mutex ha due possibili stati: sbloccato (non posseduto da nessun thread) e bloccato (posseduto da un thread). Un mutex non può mai essere posseduto da due thread contemporaneamente.&amp;lt;br&amp;gt;Se un thread provasse ad acquisire il mutex, mentre quest'ultimo è già in possesso di un altro thread, dovrebbe aspettare finché non viene rilasciato.&lt;br /&gt;
La variabile che realizza il mutex, di tipo ''pthread_mutex_t'', viene inizializzata staticamente assegnandogli PTHREAD_MUTEX_INITIALIZER.&lt;br /&gt;
'''pthread_mutex_lock''' acquisisce immediatamente il mutex, se libero. Altrimenti aspetta fino a trovarla, prima o poi, libera.&amp;lt;br&amp;gt;&lt;br /&gt;
'''pthread_mutex_unlock''' rilascia il mutex.&lt;br /&gt;
&lt;br /&gt;
[da: https://sourceware.org/pthreads-win32/manual/pthread_mutex_init.html]&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
Con questa soluzione abbiamo raggiunto un risultato esatto pur avendo pagato in prestazioni: la computazione infatti è più lenta a causa del tempo richiesto dalla sincronizzazione tra i thread.&lt;br /&gt;
&lt;br /&gt;
Consideriamo ora due scenari leggermente più complicati:&lt;br /&gt;
* nel primo abbiamo due processi, A e B, che devono accedere a due risorse, X e Y, contemporaneamente prima di poter terminare. Cosa succede se, mentre A acquisisce X, B acquisisce Y?&lt;br /&gt;
* nel secondo abbiamo tre processi, A, B e C, che devono accedere alla risorsa X. Che succede se A e B, essendo più veloci, occupano insistentemente la risorsa e non permettono a C di utilizzarla?&lt;br /&gt;
&lt;br /&gt;
La prima condizione si chiama '''deadlock''', la seconda condizione si chiama '''starvation'''. Il deadlock (stallo) è un problema che non può scomparire autonomamente, mentre la starvation (&amp;quot;inedia&amp;quot;) si può risolvere da sola. A tal proposito ci viene incontro l'assioma di '''finite progress''': &amp;quot;ogni processo che può avanzare, prima o poi lo farà&amp;quot;.&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
In ogni caso, per parlare in maniera più approfondita di queste tematiche:&lt;br /&gt;
* avremo bisogno di un modello di riferimento&lt;br /&gt;
* avremo bisogno di paradigmi in grado di esprimere la concorrenza (alcuni paradigmi saranno rivolti alla concorrenza a memoria privata; altri al multithreading a risorse condivise)&lt;br /&gt;
* dovremo imparare a scrivere programmi&lt;br /&gt;
Nel nostro modello di riferimento l'assegnamento di costanti è un operazione atomica. Dagli esempi precedenti, infatti, si evince come ci sia bisogno di creare atomicità in sezioni critiche. Inoltre, il nostro modello dovrà rispettare le proprietà di safety e di liveness, ovvero:&lt;br /&gt;
* Tutti i processi danno la stessa risposta;&lt;br /&gt;
* Ogni processo corretto da come risposta una tra quelle proposte;&lt;br /&gt;
* Ogni processo prima o poi fornisce una risposta.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 6 ottobre 2017 ==&lt;br /&gt;
'''Il linguaggio C'''&lt;br /&gt;
&lt;br /&gt;
Il linguaggio C nasce nei primi anni 70 grazie al lavoro di Dennis Ritchie con lo scopo di scrivere il &lt;br /&gt;
sistema operativo Unix. Quali sono le caratteristiche di C e cosa lo rende un linguaggio adatto per scrivere un sistema&lt;br /&gt;
operativo? Un linguaggio per scrivere un sistema operativo deve:&lt;br /&gt;
* essere indipendente dalla macchina su cui viene scritto&lt;br /&gt;
* dare la possibilità di lavorare direttamente con la memoria&lt;br /&gt;
* permette di andare a modificare i singoli bit in memoria&lt;br /&gt;
* essere semplice da usare&lt;br /&gt;
* essere massimamente produttivo&lt;br /&gt;
* essere veloce, efficiente&lt;br /&gt;
C presenta le caratteristiche dette sopra e alcune altre:&lt;br /&gt;
* in C, dal punto di vista sintattico, tutto è funzione. Si può vedere un programma in C come un insieme di funzioni. La funzione principale è il '''main'''. L'unica cosa che la distingue è il nome, a parte quello il main è una funzione come le altre. Prende due argomenti: argcount e argvalue, che contengono rispettivamente il numero degli argomenti e un array contenente gli argomenti stessi. L'interfaccia del main è tipicamente: &amp;lt;syntaxhighlight lang=C&amp;gt; int main(int argc, char *argv[]) &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
* il costrutto fondamentale è l'espressione. ''expression'' ; = ''instruction''&lt;br /&gt;
* sono previste istruzioni di struttura &amp;lt;syntaxhighlight lang=C&amp;gt;if, while, switch, etc.&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
* le variabili, per poter essere utilizzate, devono essere sempre definite&lt;br /&gt;
* il C è un linguaggio fortemente tipato, però con un certo numero di eccezioni: ad esempio, è possibile fare il casting esplicito di un puntatore void * in qualsiasi tipo, senza che il compilatore dia errori o warnings, con conseguente perdita di type safety&lt;br /&gt;
* C è un linguaggio &amp;quot;per adulti&amp;quot;: non impone una struttura ordinata al codice, come ad esempio fa invece Python, ed è piena responsabilità del programmatore scrivere programmi ordinati/leggibili. Inoltre molte operazioni che in altri linguaggi non sarebbero permesse lo sono invece in C: anche in questo caso è responsabilità del programmatore fare corretto uso del linguaggio&lt;br /&gt;
* C è un linguaggio molto semplice. Come si sopperisce alle mancanze imposte da questa semplicità? Grazie alle librerie: in C tutte le funzionalità aggiuntive (non fornite dal linguaggio stesso, come l'allocazione di memoria, la stampa a schermo, la gestione di input e output) sono fornite dalle librerie&lt;br /&gt;
* C è anche sintatticamente semplice. Dove va a finire la complessità sintattica? Nel preprocessore: prima di essere compilato un programma in C passa attraverso un preprocessore, che produce codice in base alle direttive di preprocessione date nel codice stesso &amp;lt;syntaxhighlight lang=C&amp;gt;#ifdef VAR ... #endif, # e ## operator, __LINE__,etc&amp;lt;/syntaxhighlight&amp;gt; &lt;br /&gt;
* in C vi sono fondamentalmente due tipi di dato: int e float. Tutti gli altri &amp;quot;tipi&amp;quot; di dato sono derivati da essi. Si hanno diversi modificatori, che vanno a modificare la quantità di memoria allocata per la variabile. Alcuni di questi sono &amp;lt;syntaxhighlight lang=C&amp;gt;long, short, char, etc&amp;lt;/syntaxhighlight&amp;gt; Un dato di tipo long int ha, solitamente, la dimensione di una parola di memoria della macchina su cui è eseguito il programma. char alloca 8 bits. Se si prende una variabile di un certo tipo e lo si assegna ad una di tipo più ampio, il valore viene mantenuto. Viceversa quando si assegna una variabile di un tipo ad una di tipo meno ampio, una volta &amp;quot;superato il limite&amp;quot; si torna indietro ciclicamente al valore iniziale. Per queste situazioni il compilatore non dà né warnings né errors: sono perfettamente lecite in C&lt;br /&gt;
* in C si passa una varibile ad una funzione solo per valore; per passare &amp;quot;per riferimento&amp;quot; una variabile si passa per valore l'indirizzo della variabile&lt;br /&gt;
* in C si può fare uso di puntatori alla memoria. Dato un puntatore p, un'istruzione del tipo p+i va letta come: vai avanti di i posizioni della grandezza del tipo del puntatore ognuno. Un vettore in C non è altro che un puntatore che non può essere assegnato. Una scrittura del tipo array[i] è una abbreviazione di *(array + i). Non c'è controllo in C se vado oltre la memoria allocata per l'array. Perché? In Pascal, ad esempio, c'è un tale controllo. Il motivo per cui in C questo manca è che questo controllo genera ulteriore codice macchina. Dovendo C essere efficiente, non ci si può permettere un tale costo aggiuntivo. La responsabilità di non andare oltre è lasciata al programmatore&lt;br /&gt;
&lt;br /&gt;
Esercizio svolto che prende in input due valori tramite i parametri del metodo '''main''', li converte da sequenze di caratteri ad interi, per poi stamparli utilizzando delle '''System Call'''. Lo scopo dell’esercizio è dimostrare come sia possibile utilizzare il linguaggio C senza l’utilizzo di librerie particolari (ad eccezione di quelle per le System Call).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=C&amp;gt;&lt;br /&gt;
#define _GNU_SOURCE&lt;br /&gt;
#include &amp;lt;unistd.h&amp;gt;&lt;br /&gt;
#include &amp;lt;sys/syscall.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int _uatoi(char *s, int value){&lt;br /&gt;
  if (*s == 0)&lt;br /&gt;
    return value;&lt;br /&gt;
  else if (*s &amp;gt;= '0' &amp;amp;&amp;amp; *s &amp;lt;= '9'){&lt;br /&gt;
    value = value * 10 + (*s - '0');&lt;br /&gt;
    return _uatoi(s+1, value);&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int uatoi(char *s){&lt;br /&gt;
  return _uatoi(s, 0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
char *uitoa(int val, char *buf, int len){&lt;br /&gt;
  int i;&lt;br /&gt;
  for (len = len - 1; len &amp;gt;= 0 &amp;amp;&amp;amp; val &amp;gt; 0; len--, val /= 10){&lt;br /&gt;
    buf[len] = '0' + (val %10);&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  return buf + len + 1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#define MAXOUTLEN 10&lt;br /&gt;
int main (int argc, char *argv[]) {&lt;br /&gt;
    int tot = 0;&lt;br /&gt;
    int i;&lt;br /&gt;
    char buf[MAXOUTLEN];&lt;br /&gt;
    char *result;&lt;br /&gt;
    for (i = 1; i &amp;lt; argc; i++)&lt;br /&gt;
      tot += uatoi(argv[i]);&lt;br /&gt;
    result = uitoa(tot, buf, MAXOUTLEN);&lt;br /&gt;
    syscall(__NR_write, 1, result, MAXOUTLEN - (result - buf));&lt;br /&gt;
    syscall(__NR_write, 1, &amp;quot;\n&amp;quot;, 1);&lt;br /&gt;
    return tot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Lezione del 10 ottobre 2017 ==&lt;br /&gt;
'''Programmazione concorrente: notazione e un primo problema'''&lt;br /&gt;
&lt;br /&gt;
Esempio di possibile rappresentazione (che non useremo) di codice concorrente.&amp;lt;br&amp;gt;&lt;br /&gt;
'''''A'', ''B'', ''C'', ''D'', ''E''''' rappresentano porzioni di codice.&amp;lt;br&amp;gt;&lt;br /&gt;
'''''//''''' rappresenta esecuzione parallela&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
A&lt;br /&gt;
cobegin&lt;br /&gt;
  B&lt;br /&gt;
//&lt;br /&gt;
  C&lt;br /&gt;
//&lt;br /&gt;
  D&lt;br /&gt;
coend&lt;br /&gt;
E&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il codice sarà eseguito come: ''A'' -&amp;gt; ''BCD'' -&amp;gt; ''E''. ''E'' quindi verrà eseguito solo dopo la fine dell'esecuzione di ''BCD''.&lt;br /&gt;
&lt;br /&gt;
Un esempio che si presta molto bene ad illustrare la programmazione concorrente è quello del '''merge sort''' concorrente:&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
leggi vettore&lt;br /&gt;
cobegin&lt;br /&gt;
  ordina la prima metà del vettore&lt;br /&gt;
//&lt;br /&gt;
  ordina la seconda metà del vettore&lt;br /&gt;
coend&lt;br /&gt;
merge (miscela) i risultati&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Versione errata del merge sort concorrente:&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
leggi vettore&lt;br /&gt;
cobegin&lt;br /&gt;
  ordina la prima metà del vettore&lt;br /&gt;
//&lt;br /&gt;
  ordina la seconda metà del vettore&lt;br /&gt;
//  &lt;br /&gt;
  merge (miscela) i risultati&lt;br /&gt;
coend&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avendo un numero ''N'' di processi, vorremmo avere una notazione che ci permetta di definire una porzione di codice come atomica (la sezione critica). Come possiamo procedere?&amp;lt;br&amp;gt;&lt;br /&gt;
Teoricamente, vorremmo avere due funzioni, '''csenter()''' e '''csexit()''' che ci permettano di entrare e uscire dalla sezione critica del codice senza causare errori nella computazione.&amp;lt;br&amp;gt;&lt;br /&gt;
Quali sono le proprietà che queste funzioni debbono rispettare?&lt;br /&gt;
# Devono garantire la mutua esclusione (1 solo processo esegue la critical section alla volta). Contando ''n'' csenter() completate e ''m'' csexit() completate la differenza deve essere &amp;lt;=1&lt;br /&gt;
# Non devono fare deadlock tra di loro&lt;br /&gt;
# Non devono fare starvation tra loro&lt;br /&gt;
# ''(no unnecessary delay)'' un processo che non sta usando la critical section {csenter()..csexit()} non deve bloccare un altro processo dall'entrarvi.&lt;br /&gt;
&lt;br /&gt;
La soluzione a questo problema è nota come '''soluzione di Dekker''' (scritta e pubblicata da Dijkstra) che venne presentata in modo graduale attraverso l'utilizzo di quattro possibili, ma sbagliate, soluzioni.&lt;br /&gt;
&lt;br /&gt;
Vorremmo avere programmi che abbiano la seguente struttura:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
process[i], i=1,...,N&lt;br /&gt;
&lt;br /&gt;
while(1):&lt;br /&gt;
  csenter() //entrata nella sezione critica (critical section)&lt;br /&gt;
  critical code&lt;br /&gt;
  csexit() //uscita dalla sezione critica&lt;br /&gt;
  non-critical code&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ad esempio, tornando al problema dei thread che sommavano 1 a ''sum'' MAX volte, avremo:&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
process A:&lt;br /&gt;
&lt;br /&gt;
  for (i = 0; i &amp;lt; MAX; i++)&lt;br /&gt;
    csenter()&lt;br /&gt;
    sum += 1 //atomicità descritta da csenter(), csexit()&lt;br /&gt;
    // &amp;lt; sum += 1 &amp;gt; atomicità lasciata al processore&lt;br /&gt;
    csexit()&lt;br /&gt;
&lt;br /&gt;
process B:&lt;br /&gt;
&lt;br /&gt;
  for (i = 0; i &amp;lt; MAX; i++)&lt;br /&gt;
    csenter()&lt;br /&gt;
    sum += 1&lt;br /&gt;
    csexit()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
----&lt;br /&gt;
'''Soluzione 1: A turni'''&lt;br /&gt;
&lt;br /&gt;
Qual è il problema?&amp;lt;br&amp;gt;&lt;br /&gt;
*'''mutex'''                   OK&amp;lt;br&amp;gt;&lt;br /&gt;
*'''deadlock'''                OK&amp;lt;br&amp;gt;&lt;br /&gt;
*'''starvation'''              OK&amp;lt;br&amp;gt;&lt;br /&gt;
*'''no unnecessary delay'''    NO&amp;lt;br&amp;gt;&lt;br /&gt;
:: Perché se P ha bisogno spesso della critical section, P non deve aspettare che Q chieda la critical section e, all'uscita, lo autorizzi di nuovo ad usarla impostando ''turn = P''. Se Q una volta richiesta la sezione non la usa ma si ferma, P non può richiederla ancora perché appartiene ancora a Q.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
&lt;br /&gt;
turn=P&lt;br /&gt;
&lt;br /&gt;
process P:&lt;br /&gt;
  while(1)&lt;br /&gt;
    while (turn != P)&lt;br /&gt;
      ;&lt;br /&gt;
    critical code&lt;br /&gt;
    turn = Q&lt;br /&gt;
    non-critical code&lt;br /&gt;
&lt;br /&gt;
process Q:&lt;br /&gt;
  while(1)&lt;br /&gt;
    while (turn != Q)&lt;br /&gt;
    critical code&lt;br /&gt;
    turn = P&lt;br /&gt;
    non-critical code&lt;br /&gt;
 &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
/*Soluzione 2: Con 2 variabili, cerchiamo di risolvere il problema dei turni&lt;br /&gt;
&lt;br /&gt;
  finché Q usa la sezione P aspetta, quando Q ha finito P prende la sezione critica&lt;br /&gt;
  non ci sono ritardi indesiderati&lt;br /&gt;
&lt;br /&gt;
  starvation              OK&lt;br /&gt;
  deadlock                OK&lt;br /&gt;
  no unnecessary delay    OK&lt;br /&gt;
  mutex                   NO possono essere entrambi come false, escono insieme nel while&lt;br /&gt;
                          tutti e due mettono a true ed entrano assieme nel while.&lt;br /&gt;
*/&lt;br /&gt;
inP = false;&lt;br /&gt;
inQ = false;&lt;br /&gt;
&lt;br /&gt;
process P:&lt;br /&gt;
  while(1)&lt;br /&gt;
    while (inQ)&lt;br /&gt;
      ;&lt;br /&gt;
    inP = true&lt;br /&gt;
    critical code&lt;br /&gt;
    inP = false&lt;br /&gt;
    non-critical code&lt;br /&gt;
&lt;br /&gt;
process Q:&lt;br /&gt;
  while(1)&lt;br /&gt;
    while (inP)&lt;br /&gt;
      ;&lt;br /&gt;
    inQ = true&lt;br /&gt;
    critical code&lt;br /&gt;
    inQ = false&lt;br /&gt;
    non-critical code&lt;br /&gt;
&lt;br /&gt;
/*Soluzione 3: il problema di soluzione 2 è che i processi non sono al corrente&lt;br /&gt;
dell'intenzione altrui di entrare nel while quindi crea un problema.&lt;br /&gt;
  mutex                   OK&lt;br /&gt;
  deadlock                NO (unisono entrambi richiedono &amp;quot;mia&amp;quot; loop)&lt;br /&gt;
  starvation              OK&lt;br /&gt;
  non unnecessary delay   OK&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
needP = false;&lt;br /&gt;
needQ = false;&lt;br /&gt;
&lt;br /&gt;
process P:&lt;br /&gt;
  while(1)&lt;br /&gt;
    needP = true&lt;br /&gt;
    while (needQ)&lt;br /&gt;
      ;&lt;br /&gt;
    critical code&lt;br /&gt;
    needP = false&lt;br /&gt;
    non-critical code&lt;br /&gt;
&lt;br /&gt;
process Q:&lt;br /&gt;
  while(1)&lt;br /&gt;
    needQ = true&lt;br /&gt;
    while (needP)&lt;br /&gt;
      ;&lt;br /&gt;
    critical code&lt;br /&gt;
    needQ = false&lt;br /&gt;
    non-critical code&lt;br /&gt;
&lt;br /&gt;
/*Soluzione 4:&lt;br /&gt;
&lt;br /&gt;
    mutex                 OK&lt;br /&gt;
    deadlock              OK&lt;br /&gt;
    non unnecessary delay OK&lt;br /&gt;
    starvation            NO perché può esistere il processo che prova sempre a&lt;br /&gt;
                          chiedere nel momento sbagliato e non esegue mai.&lt;br /&gt;
*/&lt;br /&gt;
inP = false;&lt;br /&gt;
inQ = false;&lt;br /&gt;
&lt;br /&gt;
process P:&lt;br /&gt;
  while(1)&lt;br /&gt;
    inP = true&lt;br /&gt;
    while (inQ){&lt;br /&gt;
        inP = false;&lt;br /&gt;
        delay&lt;br /&gt;
        inP = true;&lt;br /&gt;
    }&lt;br /&gt;
    critical code&lt;br /&gt;
    inP = false&lt;br /&gt;
    non-critical code&lt;br /&gt;
&lt;br /&gt;
process Q:&lt;br /&gt;
  while(1)&lt;br /&gt;
    inQ = true&lt;br /&gt;
    while (inQ){&lt;br /&gt;
        inQ = false;&lt;br /&gt;
        delay&lt;br /&gt;
        inQ = true;&lt;br /&gt;
    }&lt;br /&gt;
    critical code&lt;br /&gt;
    inQ = false&lt;br /&gt;
    non-critical code&lt;br /&gt;
&lt;br /&gt;
/*Soluzione finale:&lt;br /&gt;
  uniamo soluzione 1 (poco simmetrica) e soluzione 4 (troppo simmetrica)&lt;br /&gt;
  usiamo quello che ci serve e se siamo troppi a voler entrare usiamo i turni&lt;br /&gt;
  per entrare nella critical section il need opposto deve essere diventato falso&lt;br /&gt;
&lt;br /&gt;
  mutex                         OK&lt;br /&gt;
  deadlock                      OK&lt;br /&gt;
  starvation                    OK&lt;br /&gt;
  non unnecessary delay         OK&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
needP = false;&lt;br /&gt;
needQ = false;&lt;br /&gt;
turn = P&lt;br /&gt;
&lt;br /&gt;
process P:&lt;br /&gt;
  while(1)&lt;br /&gt;
    needP = true&lt;br /&gt;
    while (needQ)&lt;br /&gt;
      if (turn != P) {&lt;br /&gt;
        needP = false;&lt;br /&gt;
        while ( turn != P)&lt;br /&gt;
        needP = true;&lt;br /&gt;
    }&lt;br /&gt;
    /*critical code*/&lt;br /&gt;
    turn = Q&lt;br /&gt;
    needP = false&lt;br /&gt;
    /*non-critical code*/&lt;br /&gt;
&lt;br /&gt;
process P:&lt;br /&gt;
  while(1)&lt;br /&gt;
    needQ = true&lt;br /&gt;
    while (needP)&lt;br /&gt;
      if (turn != Q) {&lt;br /&gt;
        needQ = false;&lt;br /&gt;
        while ( turn != Q)&lt;br /&gt;
        needQ = true;&lt;br /&gt;
    }&lt;br /&gt;
    /*critical code*/&lt;br /&gt;
    turn = P&lt;br /&gt;
    needQ = false&lt;br /&gt;
    /*non-critical code*/&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Lezione del 13 ottobre 2017 ==&lt;br /&gt;
== Lezione del 17 ottobre 2017 ==&lt;br /&gt;
== Lezione del 20 ottobre 2017 ==&lt;br /&gt;
== Lezione del 24 ottobre 2017 ==&lt;br /&gt;
== Lezione del 27 ottobre 2017 ==&lt;br /&gt;
== Lezione del 31 ottobre 2017 ==&lt;br /&gt;
== Lezione del 3 novembre 2017 ==&lt;br /&gt;
== Lezione del 7 novembre 2017 ==&lt;br /&gt;
== Lezione del 10 novembre 2017 ==&lt;br /&gt;
== Lezione del 14 novembre 2017 ==&lt;br /&gt;
== Lezione del 17 novembre 2017 ==&lt;br /&gt;
== Lezione del 21 novembre 2017 ==&lt;br /&gt;
== Lezione del 24 novembre 2017 ==&lt;br /&gt;
== Lezione del 28 novembre 2017 ==&lt;br /&gt;
== Lezione del 1 dicembre 2017 ==&lt;br /&gt;
== Lezione del 5 dicembre 2017 ==&lt;br /&gt;
== Lezione del 12 dicembre 2017 ==&lt;br /&gt;
== Lezione del 15 dicembre 2017 ==&lt;/div&gt;</summary>
		<author><name>Matt</name></author>
	</entry>
	<entry>
		<id>https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_I_semestre&amp;diff=2025</id>
		<title>Lezioni Anno Accademico 2017/18 I semestre</title>
		<link rel="alternate" type="text/html" href="https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_I_semestre&amp;diff=2025"/>
		<updated>2017-10-10T20:00:12Z</updated>

		<summary type="html">&lt;p&gt;Matt: /* Lezione del 10 ottobre 2017 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;''scrivete qui idee, riassunti dei concetti espressi, commenti approfondimenti sulle lezioni.''&lt;br /&gt;
&lt;br /&gt;
== Lezione del 26 settembre 2017 ==&lt;br /&gt;
'''Titolo della lezione: W&amp;amp;#x2074; H Y'''&lt;br /&gt;
&lt;br /&gt;
* What:&lt;br /&gt;
* Who:&lt;br /&gt;
----&lt;br /&gt;
Noi studenti ed &amp;quot;entrambi&amp;quot; i professori.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* When:&lt;br /&gt;
----&lt;br /&gt;
Il corso ha durata annuale. Verrà svolto ogni martedì e venerdì dalle 15:30 alle 18:30.&lt;br /&gt;
* La lezione del martedì sarà dedicata alla programmazione concorrente;&lt;br /&gt;
* La lezione del venerdì sarà dedicata alla parte generale. Terminerà circa 15 minuti prima dell'orario stabilito.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* Where:&lt;br /&gt;
----&lt;br /&gt;
Sempre in Aula 1 Ercolani (E1): DISI, Scuole Ercolani, Mura Anteo Zamboni 2B.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* How:&lt;br /&gt;
----&lt;br /&gt;
Il corso si svolgerà tramite:&lt;br /&gt;
* Lezioni frontali in aula.&lt;br /&gt;
* Attività di laboratorio.&lt;br /&gt;
* Esercitazioni.&lt;br /&gt;
&lt;br /&gt;
Inoltre, durante l'ultimo periodo di lezioni del secondo semestre, vi sarà un periodo di ripasso del programma tramite esercizi, in vista dell'esame.&lt;br /&gt;
Durante la lezione del 3/10 sono state indicate alcune propedeuticità del corso. tra cui: programmazione, algoritmi e architettura degli elaboratori in primis.&lt;br /&gt;
Importanti sono anche i corsi di Analisi matematica e Algebra e geometria. Per farla breve, il primo anno è propedeutico al secondo.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* Why:&lt;br /&gt;
----&lt;br /&gt;
* '''Fonti e strumenti del corso:''' wiki creata di gruppo, lezioni frontali e esercitazioni.&lt;br /&gt;
**Non esiste un vero e proprio testo consigliato. Tutte le informazioni sono date DURANTE il corso.&lt;br /&gt;
** Per domande specifiche scrivere su mailing list (so@cs.unibo.it);&lt;br /&gt;
** Per esercizi, appunti e programma svolto rivolgersi al wiki (so.v2.cs.unibo.it);&lt;br /&gt;
** Per live streaming (www.cs.unibo.it/~renzo/live/).&lt;br /&gt;
* '''Modalit&amp;amp;agrave; di esame:''' esame scritto (diviso in due parti: una parte generale e una di programmazione concorrente) + progetto + orale (facoltativo), possibilità di dare solo lo scritto per ottenere un massimo di 18. La modalità per studenti in &amp;quot;difficoltà&amp;quot; è disponibile solo fino agli appelli autunnali.&lt;br /&gt;
** Si parlerà del progetto indicativamente a partire da Dicembre 2017.&lt;br /&gt;
** L'orale può essere sostenuto da chi vuole migliorare (peggiorare) il voto ottenuto, o da chi vuole ottenere la lode.&lt;br /&gt;
* '''Orario di ricevimento per il primo semestre (fino al 15/09/17):''' martedì alle 11:30.&lt;br /&gt;
* Universit&amp;amp;agrave; = docenti + studenti.&lt;br /&gt;
* Informatica = come generare informazione automatica.&lt;br /&gt;
* Hardware, Software, Elaborazione, Comunicazione, Memorizzazione, Digitale/Analogico.&lt;br /&gt;
La principale distinzione che facciamo tra '''dato''' e '''informazione''' è che il dato, da solo, è privo di significato. Se, tuttavia, viene interpretato in un particolare contesto allora può diventare informazione significativa per chi sta interpretando i dati.&lt;br /&gt;
&lt;br /&gt;
Un '''algoritmo''' è una o più sequenze non ambigue di passi che, dato un problema, ci permette di risolverlo. &lt;br /&gt;
&lt;br /&gt;
Il nostro algoritmo, scritto in un qualche linguaggio formale, diventa un '''programma''' (sostanzialmente un testo, un insieme di istruzioni).&lt;br /&gt;
&lt;br /&gt;
Un linguaggio è un entità software, ed è definito come una quadrupla (alfabeto, sintassi, lessico, semantica) dove:&lt;br /&gt;
*l'''alfabeto'' è l'insieme di simboli che compone il linguaggio.&lt;br /&gt;
*il ''lessico'' si può vedere come una funzione che va dai simboli a un booleano e ci dice quali sono le frasi ben formate.&lt;br /&gt;
*la ''sintassi'' determina quali delle sequenze scritte sono effettivamente corrette.&lt;br /&gt;
*la ''semantica'' associa un significato alle parole ben formate.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 29 settembre 2017 ==&lt;br /&gt;
'''Cos'è un sistema operativo e un po' di storia'''&lt;br /&gt;
&lt;br /&gt;
Un ''sistema operativo'' è un programma che gestisce i processi, le risorse e interfaccia le applicazioni con l'hardware dell'elaboratore.&lt;br /&gt;
&lt;br /&gt;
In particolare, il sistema operativo (laddove esiste) è il primo processo ad essere attivato e resta in vita fino allo spegnimento del calcolatore o al sopraggiungere di un errore fatale per il sistema.&lt;br /&gt;
&lt;br /&gt;
A cosa serve un sistema operativo?&lt;br /&gt;
Principalmente, ha i seguenti scopi:&lt;br /&gt;
# Facilitare l'utilizzo del sistema (senza S.O. sarebbe a carico del programmatore conoscere tutti i linguaggi con cui si comunica con l'architettura della macchina);&lt;br /&gt;
# Rendere affidabile, protetto e sicuro l'utilizzo del sistema (e.g. un processo potrebbe recar danno all'intero sistema se non controllato o gestito, potrebbe ignorare i permessi di visualizzazione di un file);&lt;br /&gt;
# Astrarre l'hardware (e.g. filesystem);&lt;br /&gt;
# Garantire l'efficienza (e.g. non tenere la CPU in idle adottando opportuni algoritmi di scheduling);&lt;br /&gt;
# Assicurare portabilità ;&lt;br /&gt;
&lt;br /&gt;
Un approccio molto usato è il cosiddetto approccio a strati (&amp;quot;a livelli&amp;quot;) in cui ogni strato utilizza i servizi forniti al livello inferiore e ne fornisce di nuovi a quello superiore.&lt;br /&gt;
Astraendo il nostro calcolatore possiamo piazzare al livello più basso l'hardware e, subito sopra, il sistema operativo. I due layer comunicano usando il linguaggio ISA (Instruction Set Architecture), nativo della CPU stessa, al quale si aggiungono quelli che permettono di comunicare con i vari controllori dei dispositivi come la scheda di rete, la stampante, etc. Sopra il livello del sistema operativo possiamo collocare quello delle librerie e, infine, quello degli applicativi.&lt;br /&gt;
&lt;br /&gt;
* '''Systemcall''' = meccanismo usato da un processo per richiedere al SO una qualche funzionalità a livello kernel. Un esempio è la funzione printf() del linguaggio C (stampa a video) che utilizza la systemcall write() (una delle systemcall per la gestione dei dispositivi).&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
----&lt;br /&gt;
'''Storia dei sistemi operativi'''&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''zero''' (1800 ca.) possiamo includere Babbage con la sua macchina analitica e lady Ada Lovelace. [[File:290px-AnalyticalMachine Babbage London.jpg |200px|thumb|right|Macchina analitica di Babbage]]&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''uno''' vediamo la comparsa delle valvole con tutti i problemi connessi. Non c'erano utilizzatori delle macchine, le stesse persone che le costruivano erano anche programmatori e fruitori delle stesse.&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''due''' (fine anni '60) arriva il transistor. Quest'ultimo è molto più veloce e piccolo della valvola e, soprattutto, meno incline a guasti. Le macchine iniziano ad essere più economiche grazie alla possibilità di avere un'economia di scala e quindi accessibili al grande pubblico. Questo fa si che, in queste prime fasi, i costruttori non siano più gli unici utilizzatori. [[File:Transistors.jpg |300px|thumb|right|Diversi tipi di Transistor]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
A questo punto poiché, pur essendo diventata più accessibile, una macchina costava ancora molto, nasce l'esigenza di non lasciare mai un processore senza lavoro (al fine di massimizzare i ricavi) e di condividere i dati.&lt;br /&gt;
In questa fase, infatti, abbiamo sistemi operativi di tipo ''batch'' (che collezionano tutto l'input all'inizio per calcolare e restituire l'output) dove l'inserimento di programmi e dati veniva fatto tramite schede perforate e non c'era interazione. Dai sistemi batch nasce lo ''SPOOL'' (Simultaneous Peripheral Operations On-Line).&lt;br /&gt;
&lt;br /&gt;
Si comincia a pensare di utilizzare i tempi di I/O per poter eseguire altri processi. Questo però solleva almeno due problematiche: da un lato serve un modo per sapere quando un input è davvero finito mentre, dall'altro, bisogna fare in modo che un processo che non richieda I/O non occupi la CPU per un tempo indefinito.&lt;br /&gt;
Per la prima problematica la soluzione è rappresentata dall'introduzione degli '''interrupt''', segnali elettrici inviati alla CPU alla fine di ogni input.&lt;br /&gt;
Per la seconda, invece, è stato introdotto il cosiddetto '''interval timer''' che non è altro che un dispositivo che invia interrupt dopo un determinato quanto di tempo assicurando che un processo non occupi per troppo tempo il processore.&lt;br /&gt;
Questo consente di realizzare sistemi time sharing (il cui più semplice algoritmo è il round-robin) dove un processo può essere in uno dei seguenti tre stati:&lt;br /&gt;
*'''READY''' pronto per essere eseguito ma, il processore è già occupato;&lt;br /&gt;
*'''RUNNING''' in esecuzione;&lt;br /&gt;
*'''WAIT''' in attesa di I/O.&lt;br /&gt;
&lt;br /&gt;
[[File:Dds.png]]&lt;br /&gt;
&lt;br /&gt;
Nella '''quarta''' generazione (anni '70 ca.) vediamo la nascita di molte innovazioni importanti. Si riesce a rimpicciolire di molto i processori facendoli divenire a tutti gli effetti micro-processori. Nei laboratori Bell nasce Unix, un sistema operativo rivoluzionario con time sharing e clonazione dei processi (un processo non viene mai creato dal nulla ma viene prima clonato da uno già esistente e poi, alla copia, viene fatto eseguire un programma). A causa di problemi di portabilità da PDP-9, macchina su cui Unix è nato, a PDP-11 nasce il linguaggio C e il suo compilatore (scritto anch'esso in C).&lt;br /&gt;
Mentre Apple I &amp;quot;porta&amp;quot; una nuova idea di pc per tutte le famiglie, nascono molte versioni di Unix.&lt;br /&gt;
Per impedire una deriva proprietaria in cui ogni produttore offriva tutto quello che offrivano gli altri ma con qualche feature in più, Richard Stallman fonda il progetto GNU (GNU's Not Unix) per il quale scrive tutta una serie di utilities come il famoso compilatore gcc.&lt;br /&gt;
A GNU, per essere operativo, manca però un kernel. Non volendo aspettare i tempi di sviluppo del kernel che Stallman aveva in mente, Linus Torlvalds scrive il kernel '''Linux''' da cui nasce il sistema operativo GNU/Linux.&lt;br /&gt;
&lt;br /&gt;
Nei tempi più recenti nascono i processori multi-core.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 3 ottobre 2017 ==&lt;br /&gt;
 &lt;br /&gt;
'''Introduzione alla programmazione concorrente: processi, thread e  librerie'''&lt;br /&gt;
&lt;br /&gt;
In programmazione concorrente le nozioni apprese di &amp;quot;algoritmo&amp;quot;, &amp;quot;programma&amp;quot; e &amp;quot;processo&amp;quot; devono essere aggiornate. Questo perché non vi è più un programma con un'unica sequenza esecutiva [o meglio, un unico filo (= thread) di esecuzione]. Appare ovvio come servino dei meccanismi per organizzare e sincronizzare tali fili esecutivi, in modo da evitare il verificarsi di eventi non desiderati.&lt;br /&gt;
&lt;br /&gt;
Quando parliamo di ''sistema concorrente'', ci riferiamo ad un sistema in cui due o più &amp;quot;attori&amp;quot; sono eseguiti parallelamente.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Questi attori possono essere '''processi''' o '''thread'''.&lt;br /&gt;
La differenza sostanziale tra queste due tipologie di attori riguarda la condivisione della memoria e dei dati su cui operano.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Più processi possono eseguire lo stesso programma ma, ognuno di essi, ha i propri dati su cui operare e il suo stato di avanzamento. In particolare:&lt;br /&gt;
* un processo è un'entità, un attore che ha delle proprietà e delle risorse associate;&lt;br /&gt;
* in ogni istante, il processo può essere interrotto.&amp;lt;br&amp;gt;&lt;br /&gt;
Più thread che vengono eseguiti parallelamente, invece, condividono gli stessi dati su cui operare.&amp;lt;br&amp;gt;&lt;br /&gt;
Codici di esempio:&lt;br /&gt;
&lt;br /&gt;
'''processi'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=C&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;unistd.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int main(int argc, char *argv[]){&lt;br /&gt;
   if(fork()) printf(&amp;quot;Yes&amp;quot;);&lt;br /&gt;
   else printf(&amp;quot;No&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
L'output generato sarà &amp;quot;YesNo&amp;quot; poiché, usando la systemcall ''fork()'', abbiamo creato una nuova copia del processo (un processo figlio). Il processo padre, che ha eseguito con successo la fork(), stamperà &amp;quot;Yes&amp;quot; mentre il figlio stamperà &amp;quot;No&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
'''thread'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=C&amp;gt;&lt;br /&gt;
#include &amp;lt;pthread.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
#include &amp;lt;assert.h&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
#define NUM_THREADS     5&lt;br /&gt;
#define MAX 1000000&lt;br /&gt;
&lt;br /&gt;
int sum;&lt;br /&gt;
 &lt;br /&gt;
void* perform_work(){&lt;br /&gt;
	int i;&lt;br /&gt;
	&lt;br /&gt;
	for(i = 0; i &amp;lt; MAX; i++) sum = sum + 1;&lt;br /&gt;
  	/* optionally: insert more useful stuff here */ &lt;br /&gt;
  	return NULL;&lt;br /&gt;
}&lt;br /&gt;
 &lt;br /&gt;
int main( int argc, char** argv ){&lt;br /&gt;
	pthread_t threads[NUM_THREADS];&lt;br /&gt;
  	int thread_args[NUM_THREADS];&lt;br /&gt;
  	int result_code;&lt;br /&gt;
  	unsigned index;&lt;br /&gt;
 &lt;br /&gt;
	sum = 0;&lt;br /&gt;
  	// create all threads one by one&lt;br /&gt;
  	for(index = 0; index &amp;lt; NUM_THREADS; ++index ){&lt;br /&gt;
    		thread_args[index] = index;&lt;br /&gt;
    		printf(&amp;quot;In main: creating thread %d\n&amp;quot;, index);&lt;br /&gt;
    		result_code = pthread_create(&amp;amp;threads[index], NULL, perform_work, NULL);&lt;br /&gt;
    		assert(!result_code);&lt;br /&gt;
  	} &lt;br /&gt;
  	// wait for each thread to complete&lt;br /&gt;
  	for(index = 0; index &amp;lt; NUM_THREADS; ++index ){&lt;br /&gt;
    		// block until thread 'index' completes&lt;br /&gt;
    		result_code = pthread_join(threads[index], NULL);&lt;br /&gt;
    		assert(!result_code);&lt;br /&gt;
    		printf(&amp;quot;In main: thread %d has completed\n&amp;quot;, index);&lt;br /&gt;
   	}&lt;br /&gt;
 &lt;br /&gt;
   	printf(&amp;quot;In main: All threads completed successfully\n&amp;quot; );&lt;br /&gt;
	printf(&amp;quot;Result: %d\n&amp;quot;, sum);&lt;br /&gt;
   	exit( EXIT_SUCCESS );&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Prima di discutere l'output generato dall'esecuzione di questo codice, è opportuno introdurre, brevemente, gli strumenti utilizzati.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
'''Pthread (POSIX thread)'''&lt;br /&gt;
&lt;br /&gt;
Citazione dal Silberschatz, Galvin, Gagne:&lt;br /&gt;
''Pthread si riferisce allo standard POSIX (IEEE 1003.1c) che definisce una API per la creazione e sincronizzazione dei thread.''&lt;br /&gt;
&lt;br /&gt;
Le istruzioni fondamentali sono:&lt;br /&gt;
* '''int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);''' che crea un thread;&lt;br /&gt;
* '''int pthread_join(pthread_t thread, void **retval);''' che aspetta che un thread sia completato prima di continuare l'esecuzione.&lt;br /&gt;
&lt;br /&gt;
Da ricordare che, per utilizzare pthread, va incluso l'header ''&amp;lt;pthread.h&amp;gt;'' e durante la compilazione va inclusa la libreria esterna tramite l'opzione ''-pthread''.&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
Torniamo all'output generato dall'esecuzione del secondo codice.&amp;lt;br&amp;gt;&lt;br /&gt;
Nel codice vengono generati cinque thread ed ognuno somma MAX (=1000000) volte 1 a sum. Ci aspettiamo un output di 5000000 ma, in realtà, viene stampato un valore molto minore.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Questo accade principalmente per due motivi:&lt;br /&gt;
*sommare una costante ad una variabile non è un'operazione atomica (richiede almeno tre istruzioni assembler: due mov ed una sum).&lt;br /&gt;
*l'interval timer può fermare l'esecuzione del thread mentre sta effettuando una delle tre operazioni richieste per aggiungere uno a sum e provocare un risultato errato.&lt;br /&gt;
&lt;br /&gt;
Questo è uno dei possibili problemi della programmazione concorrente conosciuto come ''race condition''.&amp;lt;br&amp;gt;&lt;br /&gt;
Un sistema di processi multipli presenta una '''race condition''' qualora il risultato finale dell'esecuzione dipenda dalla temporizzazione, o dall'ordine con cui i processi vengono eseguiti.&lt;br /&gt;
&lt;br /&gt;
Per risolvere questo problema possiamo adoperare ancora una volta pthread.&lt;br /&gt;
Nello specifico, ci venogono offerte istruzione per realizzare una '''mutua esclusione''' sulle risorse condivise in modo che l'operazione che prima non era atomiche,ora, lo sia e non porti più ad una computazione errata.&amp;lt;br&amp;gt;&lt;br /&gt;
Codice di esempio:&amp;lt;br&amp;gt;&lt;br /&gt;
'''Mutex'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=C&amp;gt;&lt;br /&gt;
#include &amp;lt;pthread.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
#include &amp;lt;assert.h&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
#define NUM_THREADS     5&lt;br /&gt;
#define MAX 1000000&lt;br /&gt;
&lt;br /&gt;
int sum;&lt;br /&gt;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;&lt;br /&gt;
 &lt;br /&gt;
void* perform_work(){&lt;br /&gt;
	int i;&lt;br /&gt;
	&lt;br /&gt;
	for(i = 0; i &amp;lt; MAX; i++){ &lt;br /&gt;
		pthread_mutex_lock(&amp;amp;mutex);		&lt;br /&gt;
		sum = sum + 1;&lt;br /&gt;
		pthread_mutex_unlock(&amp;amp;mutex);&lt;br /&gt;
	}&lt;br /&gt;
  	/* optionally: insert more useful stuff here */ &lt;br /&gt;
  	return NULL;&lt;br /&gt;
}&lt;br /&gt;
 &lt;br /&gt;
int main( int argc, char** argv ){&lt;br /&gt;
	pthread_t threads[NUM_THREADS];&lt;br /&gt;
  	int thread_args[NUM_THREADS];&lt;br /&gt;
  	int result_code;&lt;br /&gt;
  	unsigned index;&lt;br /&gt;
&lt;br /&gt;
	sum = 0;&lt;br /&gt;
  	// create all threads one by one&lt;br /&gt;
  	for(index = 0; index &amp;lt; NUM_THREADS; ++index ){&lt;br /&gt;
    		thread_args[index] = index;&lt;br /&gt;
    		printf(&amp;quot;In main: creating thread %d\n&amp;quot;, index);&lt;br /&gt;
    		result_code = pthread_create(&amp;amp;threads[index], NULL, perform_work, NULL);&lt;br /&gt;
    		assert(!result_code);&lt;br /&gt;
  	} &lt;br /&gt;
  	// wait for each thread to complete&lt;br /&gt;
  	for(index = 0; index &amp;lt; NUM_THREADS; ++index ){&lt;br /&gt;
    		// block until thread 'index' completes&lt;br /&gt;
    		result_code = pthread_join(threads[index], NULL);&lt;br /&gt;
    		assert(!result_code);&lt;br /&gt;
    		printf(&amp;quot;In main: thread %d has completed\n&amp;quot;, index);&lt;br /&gt;
   	}&lt;br /&gt;
 &lt;br /&gt;
   	printf(&amp;quot;In main: All threads completed successfully\n&amp;quot; );&lt;br /&gt;
	printf(&amp;quot;Result: %d\n&amp;quot;, sum);&lt;br /&gt;
   	exit( EXIT_SUCCESS );&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
'''MUTEX'''&lt;br /&gt;
&lt;br /&gt;
Un mutex è un dispositivo di MUTual EXclusion (mutua esclusione), ed è utile per proteggere le strutture dati condivise da modifiche concorrenti, implementare sezioni critiche e monitor.&lt;br /&gt;
&lt;br /&gt;
Un mutex ha due possibili stati: sbloccato (non posseduto da nessun thread) e bloccato (posseduto da un thread). Un mutex non può mai essere posseduto da due thread contemporaneamente.&amp;lt;br&amp;gt;Se un thread provasse ad acquisire il mutex, mentre quest'ultimo è già in possesso di un altro thread, dovrebbe aspettare finché non viene rilasciato.&lt;br /&gt;
La variabile che realizza il mutex, di tipo ''pthread_mutex_t'', viene inizializzata staticamente assegnandogli PTHREAD_MUTEX_INITIALIZER.&lt;br /&gt;
'''pthread_mutex_lock''' acquisisce immediatamente il mutex, se libero. Altrimenti aspetta fino a trovarla, prima o poi, libera.&amp;lt;br&amp;gt;&lt;br /&gt;
'''pthread_mutex_unlock''' rilascia il mutex.&lt;br /&gt;
&lt;br /&gt;
[da: https://sourceware.org/pthreads-win32/manual/pthread_mutex_init.html]&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
Con questa soluzione abbiamo raggiunto un risultato esatto pur avendo pagato in prestazioni: la computazione infatti è più lenta a causa del tempo richiesto dalla sincronizzazione tra i thread.&lt;br /&gt;
&lt;br /&gt;
Consideriamo ora due scenari leggermente più complicati:&lt;br /&gt;
* nel primo abbiamo due processi, A e B, che devono accedere a due risorse, X e Y, contemporaneamente prima di poter terminare. Cosa succede se, mentre A acquisisce X, B acquisisce Y?&lt;br /&gt;
* nel secondo abbiamo tre processi, A, B e C, che devono accedere alla risorsa X. Che succede se A e B, essendo più veloci, occupano insistentemente la risorsa e non permettono a C di utilizzarla?&lt;br /&gt;
&lt;br /&gt;
La prima condizione si chiama '''deadlock''', la seconda condizione si chiama '''starvation'''. Il deadlock (stallo) è un problema che non può scomparire autonomamente, mentre la starvation (&amp;quot;inedia&amp;quot;) si può risolvere da sola. A tal proposito ci viene incontro l'assioma di '''finite progress''': &amp;quot;ogni processo che può avanzare, prima o poi lo farà&amp;quot;.&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
In ogni caso, per parlare in maniera più approfondita di queste tematiche:&lt;br /&gt;
* avremo bisogno di un modello di riferimento&lt;br /&gt;
* avremo bisogno di paradigmi in grado di esprimere la concorrenza (alcuni paradigmi saranno rivolti alla concorrenza a memoria privata; altri al multithreading a risorse condivise)&lt;br /&gt;
* dovremo imparare a scrivere programmi&lt;br /&gt;
Nel nostro modello di riferimento l'assegnamento di costanti è un operazione atomica. Dagli esempi precedenti, infatti, si evince come ci sia bisogno di creare atomicità in sezioni critiche. Inoltre, il nostro modello dovrà rispettare le proprietà di safety e di liveness, ovvero:&lt;br /&gt;
* Tutti i processi danno la stessa risposta;&lt;br /&gt;
* Ogni processo corretto da come risposta una tra quelle proposte;&lt;br /&gt;
* Ogni processo prima o poi fornisce una risposta.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 6 ottobre 2017 ==&lt;br /&gt;
'''Il linguaggio C'''&lt;br /&gt;
&lt;br /&gt;
Il linguaggio C nasce nei primi anni 70 grazie al lavoro di Dennis Ritchie con lo scopo di scrivere il &lt;br /&gt;
sistema operativo Unix. Quali sono le caratteristiche di C e cosa lo rende un linguaggio adatto per scrivere un sistema&lt;br /&gt;
operativo? Un linguaggio per scrivere un sistema operativo deve:&lt;br /&gt;
* essere indipendente dalla macchina su cui viene scritto&lt;br /&gt;
* dare la possibilità di lavorare direttamente con la memoria&lt;br /&gt;
* permette di andare a modificare i singoli bit in memoria&lt;br /&gt;
* essere semplice da usare&lt;br /&gt;
* essere massimamente produttivo&lt;br /&gt;
* essere veloce, efficiente&lt;br /&gt;
C presenta le caratteristiche dette sopra e alcune altre:&lt;br /&gt;
* in C, dal punto di vista sintattico, tutto è funzione. Si può vedere un programma in C come un insieme di funzioni. La funzione principale è il '''main'''. L'unica cosa che la distingue è il nome, a parte quello il main è una funzione come le altre. Prende due argomenti: argcount e argvalue, che contengono rispettivamente il numero degli argomenti e un array contenente gli argomenti stessi. L'interfaccia del main è tipicamente: &amp;lt;syntaxhighlight lang=C&amp;gt; int main(int argc, char *argv[]) &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
* il costrutto fondamentale è l'espressione. ''expression'' ; = ''instruction''&lt;br /&gt;
* sono previste istruzioni di struttura &amp;lt;syntaxhighlight lang=C&amp;gt;if, while, switch, etc.&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
* le variabili, per poter essere utilizzate, devono essere sempre definite&lt;br /&gt;
* il C è un linguaggio fortemente tipato, però con un certo numero di eccezioni: ad esempio, è possibile fare il casting esplicito di un puntatore void * in qualsiasi tipo, senza che il compilatore dia errori o warnings, con conseguente perdita di type safety&lt;br /&gt;
* C è un linguaggio &amp;quot;per adulti&amp;quot;: non impone una struttura ordinata al codice, come ad esempio fa invece Python, ed è piena responsabilità del programmatore scrivere programmi ordinati/leggibili. Inoltre molte operazioni che in altri linguaggi non sarebbero permesse lo sono invece in C: anche in questo caso è responsabilità del programmatore fare corretto uso del linguaggio&lt;br /&gt;
* C è un linguaggio molto semplice. Come si sopperisce alle mancanze imposte da questa semplicità? Grazie alle librerie: in C tutte le funzionalità aggiuntive (non fornite dal linguaggio stesso, come l'allocazione di memoria, la stampa a schermo, la gestione di input e output) sono fornite dalle librerie&lt;br /&gt;
* C è anche sintatticamente semplice. Dove va a finire la complessità sintattica? Nel preprocessore: prima di essere compilato un programma in C passa attraverso un preprocessore, che produce codice in base alle direttive di preprocessione date nel codice stesso &amp;lt;syntaxhighlight lang=C&amp;gt;#ifdef VAR ... #endif, # e ## operator, __LINE__,etc&amp;lt;/syntaxhighlight&amp;gt; &lt;br /&gt;
* in C vi sono fondamentalmente due tipi di dato: int e float. Tutti gli altri &amp;quot;tipi&amp;quot; di dato sono derivati da essi. Si hanno diversi modificatori, che vanno a modificare la quantità di memoria allocata per la variabile. Alcuni di questi sono &amp;lt;syntaxhighlight lang=C&amp;gt;long, short, char, etc&amp;lt;/syntaxhighlight&amp;gt; Un dato di tipo long int ha, solitamente, la dimensione di una parola di memoria della macchina su cui è eseguito il programma. char alloca 8 bits. Se si prende una variabile di un certo tipo e lo si assegna ad una di tipo più ampio, il valore viene mantenuto. Viceversa quando si assegna una variabile di un tipo ad una di tipo meno ampio, una volta &amp;quot;superato il limite&amp;quot; si torna indietro ciclicamente al valore iniziale. Per queste situazioni il compilatore non dà né warnings né errors: sono perfettamente lecite in C&lt;br /&gt;
* in C si passa una varibile ad una funzione solo per valore; per passare &amp;quot;per riferimento&amp;quot; una variabile si passa per valore l'indirizzo della variabile&lt;br /&gt;
* in C si può fare uso di puntatori alla memoria. Dato un puntatore p, un'istruzione del tipo p+i va letta come: vai avanti di i posizioni della grandezza del tipo del puntatore ognuno. Un vettore in C non è altro che un puntatore che non può essere assegnato. Una scrittura del tipo array[i] è una abbreviazione di *(array + i). Non c'è controllo in C se vado oltre la memoria allocata per l'array. Perché? In Pascal, ad esempio, c'è un tale controllo. Il motivo per cui in C questo manca è che questo controllo genera ulteriore codice macchina. Dovendo C essere efficiente, non ci si può permettere un tale costo aggiuntivo. La responsabilità di non andare oltre è lasciata al programmatore&lt;br /&gt;
&lt;br /&gt;
Esercizio svolto che prende in input due valori tramite i parametri del metodo '''main''', li converte da sequenze di caratteri ad interi, per poi stamparli utilizzando delle '''System Call'''. Lo scopo dell’esercizio è dimostrare come sia possibile utilizzare il linguaggio C senza l’utilizzo di librerie particolari (ad eccezione di quelle per le System Call).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=C&amp;gt;&lt;br /&gt;
#define _GNU_SOURCE&lt;br /&gt;
#include &amp;lt;unistd.h&amp;gt;&lt;br /&gt;
#include &amp;lt;sys/syscall.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int _uatoi(char *s, int value){&lt;br /&gt;
  if (*s == 0)&lt;br /&gt;
    return value;&lt;br /&gt;
  else if (*s &amp;gt;= '0' &amp;amp;&amp;amp; *s &amp;lt;= '9'){&lt;br /&gt;
    value = value * 10 + (*s - '0');&lt;br /&gt;
    return _uatoi(s+1, value);&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int uatoi(char *s){&lt;br /&gt;
  return _uatoi(s, 0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
char *uitoa(int val, char *buf, int len){&lt;br /&gt;
  int i;&lt;br /&gt;
  for (len = len - 1; len &amp;gt;= 0 &amp;amp;&amp;amp; val &amp;gt; 0; len--, val /= 10){&lt;br /&gt;
    buf[len] = '0' + (val %10);&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  return buf + len + 1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#define MAXOUTLEN 10&lt;br /&gt;
int main (int argc, char *argv[]) {&lt;br /&gt;
    int tot = 0;&lt;br /&gt;
    int i;&lt;br /&gt;
    char buf[MAXOUTLEN];&lt;br /&gt;
    char *result;&lt;br /&gt;
    for (i = 1; i &amp;lt; argc; i++)&lt;br /&gt;
      tot += uatoi(argv[i]);&lt;br /&gt;
    result = uitoa(tot, buf, MAXOUTLEN);&lt;br /&gt;
    syscall(__NR_write, 1, result, MAXOUTLEN - (result - buf));&lt;br /&gt;
    syscall(__NR_write, 1, &amp;quot;\n&amp;quot;, 1);&lt;br /&gt;
    return tot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Lezione del 10 ottobre 2017 ==&lt;br /&gt;
'''Programmazione concorrente: notazione e un primo problema'''&lt;br /&gt;
&lt;br /&gt;
Esempio di possibile rappresentazione (che non useremo) di codice concorrente.&amp;lt;br&amp;gt;&lt;br /&gt;
'''''A'', ''B'', ''C'', ''D'', ''E''''' rappresentano porzioni di codice.&amp;lt;br&amp;gt;&lt;br /&gt;
'''''//''''' rappresenta esecuzione parallela&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
A&lt;br /&gt;
cobegin&lt;br /&gt;
  B&lt;br /&gt;
//&lt;br /&gt;
  C&lt;br /&gt;
//&lt;br /&gt;
  D&lt;br /&gt;
coend&lt;br /&gt;
E&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il codice sarà eseguito come: ''A'' -&amp;gt; ''BCD'' -&amp;gt; ''E''. ''E'' quindi verrà eseguito solo dopo la fine dell'esecuzione di ''BCD''.&lt;br /&gt;
&lt;br /&gt;
Un esempio che si presta molto bene ad illustrare la programmazione concorrente è quello del '''merge sort''' concorrente:&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
leggi vettore&lt;br /&gt;
cobegin&lt;br /&gt;
  ordina la prima metà del vettore&lt;br /&gt;
//&lt;br /&gt;
  ordina la seconda metà del vettore&lt;br /&gt;
coend&lt;br /&gt;
merge (miscela) i risultati&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Versione errata del merge sort concorrente:&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
leggi vettore&lt;br /&gt;
cobegin&lt;br /&gt;
  ordina la prima metà del vettore&lt;br /&gt;
//&lt;br /&gt;
  ordina la seconda metà del vettore&lt;br /&gt;
//  &lt;br /&gt;
  merge (miscela) i risultati&lt;br /&gt;
coend&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avendo un numero ''N'' di processi, vorremmo avere una notazione che ci permetta di definire una porzione di codice come atomica (la sezione critica). Come possiamo procedere?&amp;lt;br&amp;gt;&lt;br /&gt;
Teoricamente, vorremmo avere due funzioni, '''csenter()''' e '''csexit()''' che ci permettano di entrare e uscire dalla sezione critica del codice senza causare errori nella computazione.&amp;lt;br&amp;gt;&lt;br /&gt;
Quali sono le proprietà che queste funzioni debbono rispettare?&lt;br /&gt;
# Devono garantire la mutua esclusione (1 solo processo esegue la critical section alla volta). Contando ''n'' csenter() completate e ''m'' csexit() completate la differenza deve essere &amp;lt;=1&lt;br /&gt;
# Non devono fare deadlock tra di loro&lt;br /&gt;
# Non devono fare starvation tra loro&lt;br /&gt;
# ''(no unnecessary delay)'' un processo che non sta usando la critical section {csenter()..csexit()} non deve bloccare un altro processo dall'entrarvi.&lt;br /&gt;
&lt;br /&gt;
La soluzione a questo problema è nota come '''soluzione di Dekker''' (scritta e pubblicata da Dijkstra) che venne presentata in modo graduale attraverso l'utilizzo di quattro possibili, ma sbagliate, soluzioni.&lt;br /&gt;
&lt;br /&gt;
Vorremmo avere programmi che abbiano la seguente struttura:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
process[i], i=1,...,N&lt;br /&gt;
&lt;br /&gt;
while(1):&lt;br /&gt;
  csenter() //entrata nella sezione critica (critical section)&lt;br /&gt;
  critical code&lt;br /&gt;
  csexit() //uscita dalla sezione critica&lt;br /&gt;
  non-critical code&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ad esempio, tornando al problema dei thread che sommavano 1 a ''sum'' MAX volte, avremo:&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
process A:&lt;br /&gt;
&lt;br /&gt;
  for (i = 0; i &amp;lt; MAX; i++)&lt;br /&gt;
    csenter()&lt;br /&gt;
    sum += 1 //atomicità descritta da csenter(), csexit()&lt;br /&gt;
    // &amp;lt; sum += 1 &amp;gt; atomicità lasciata al processore&lt;br /&gt;
    csexit()&lt;br /&gt;
&lt;br /&gt;
process B:&lt;br /&gt;
&lt;br /&gt;
  for (i = 0; i &amp;lt; MAX; i++)&lt;br /&gt;
    csenter()&lt;br /&gt;
    sum += 1&lt;br /&gt;
    csexit()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
/*Soluzione 1: A turni&lt;br /&gt;
Qual è il problema?&lt;br /&gt;
  mutex                   OK&lt;br /&gt;
  deadlock                OK&lt;br /&gt;
  starvation              OK&lt;br /&gt;
  no unnecessary delay    NO perché se P ha bisogno di molte volte la critical&lt;br /&gt;
  section, P non deve aspettare che Q chieda la critical section. Qui se Q una&lt;br /&gt;
  volta richiesta la sezione non la usa ma si ferma, P non può richiederla ancora&lt;br /&gt;
  perché appartiene a Q&lt;br /&gt;
*/&lt;br /&gt;
turn=P&lt;br /&gt;
&lt;br /&gt;
process P:&lt;br /&gt;
  while(1)&lt;br /&gt;
    while (turn != P)&lt;br /&gt;
      ;&lt;br /&gt;
    critical code&lt;br /&gt;
    turn = Q&lt;br /&gt;
    non-critical code&lt;br /&gt;
&lt;br /&gt;
process Q:&lt;br /&gt;
  while(1)&lt;br /&gt;
    while (turn != Q)&lt;br /&gt;
    critical code&lt;br /&gt;
    turn = P&lt;br /&gt;
    non-critical code&lt;br /&gt;
&lt;br /&gt;
/*Soluzione 2: Con 2 variabili, cerchiamo di risolvere il problema dei turni&lt;br /&gt;
&lt;br /&gt;
  finché Q usa la sezione P aspetta, quando Q ha finito P prende la sezione critica&lt;br /&gt;
  non ci sono ritardi indesiderati&lt;br /&gt;
&lt;br /&gt;
  starvation              OK&lt;br /&gt;
  deadlock                OK&lt;br /&gt;
  no unnecessary delay    OK&lt;br /&gt;
  mutex                   NO possono essere entrambi come false, escono insieme nel while&lt;br /&gt;
                          tutti e due mettono a true ed entrano assieme nel while.&lt;br /&gt;
*/&lt;br /&gt;
inP = false;&lt;br /&gt;
inQ = false;&lt;br /&gt;
&lt;br /&gt;
process P:&lt;br /&gt;
  while(1)&lt;br /&gt;
    while (inQ)&lt;br /&gt;
      ;&lt;br /&gt;
    inP = true&lt;br /&gt;
    critical code&lt;br /&gt;
    inP = false&lt;br /&gt;
    non-critical code&lt;br /&gt;
&lt;br /&gt;
process Q:&lt;br /&gt;
  while(1)&lt;br /&gt;
    while (inP)&lt;br /&gt;
      ;&lt;br /&gt;
    inQ = true&lt;br /&gt;
    critical code&lt;br /&gt;
    inQ = false&lt;br /&gt;
    non-critical code&lt;br /&gt;
&lt;br /&gt;
/*Soluzione 3: il problema di soluzione 2 è che i processi non sono al corrente&lt;br /&gt;
dell'intenzione altrui di entrare nel while quindi crea un problema.&lt;br /&gt;
  mutex                   OK&lt;br /&gt;
  deadlock                NO (unisono entrambi richiedono &amp;quot;mia&amp;quot; loop)&lt;br /&gt;
  starvation              OK&lt;br /&gt;
  non unnecessary delay   OK&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
needP = false;&lt;br /&gt;
needQ = false;&lt;br /&gt;
&lt;br /&gt;
process P:&lt;br /&gt;
  while(1)&lt;br /&gt;
    needP = true&lt;br /&gt;
    while (needQ)&lt;br /&gt;
      ;&lt;br /&gt;
    critical code&lt;br /&gt;
    needP = false&lt;br /&gt;
    non-critical code&lt;br /&gt;
&lt;br /&gt;
process Q:&lt;br /&gt;
  while(1)&lt;br /&gt;
    needQ = true&lt;br /&gt;
    while (needP)&lt;br /&gt;
      ;&lt;br /&gt;
    critical code&lt;br /&gt;
    needQ = false&lt;br /&gt;
    non-critical code&lt;br /&gt;
&lt;br /&gt;
/*Soluzione 4:&lt;br /&gt;
&lt;br /&gt;
    mutex                 OK&lt;br /&gt;
    deadlock              OK&lt;br /&gt;
    non unnecessary delay OK&lt;br /&gt;
    starvation            NO perché può esistere il processo che prova sempre a&lt;br /&gt;
                          chiedere nel momento sbagliato e non esegue mai.&lt;br /&gt;
*/&lt;br /&gt;
inP = false;&lt;br /&gt;
inQ = false;&lt;br /&gt;
&lt;br /&gt;
process P:&lt;br /&gt;
  while(1)&lt;br /&gt;
    inP = true&lt;br /&gt;
    while (inQ){&lt;br /&gt;
        inP = false;&lt;br /&gt;
        delay&lt;br /&gt;
        inP = true;&lt;br /&gt;
    }&lt;br /&gt;
    critical code&lt;br /&gt;
    inP = false&lt;br /&gt;
    non-critical code&lt;br /&gt;
&lt;br /&gt;
process Q:&lt;br /&gt;
  while(1)&lt;br /&gt;
    inQ = true&lt;br /&gt;
    while (inQ){&lt;br /&gt;
        inQ = false;&lt;br /&gt;
        delay&lt;br /&gt;
        inQ = true;&lt;br /&gt;
    }&lt;br /&gt;
    critical code&lt;br /&gt;
    inQ = false&lt;br /&gt;
    non-critical code&lt;br /&gt;
&lt;br /&gt;
/*Soluzione finale:&lt;br /&gt;
  uniamo soluzione 1 (poco simmetrica) e soluzione 4 (troppo simmetrica)&lt;br /&gt;
  usiamo quello che ci serve e se siamo troppi a voler entrare usiamo i turni&lt;br /&gt;
  per entrare nella critical section il need opposto deve essere diventato falso&lt;br /&gt;
&lt;br /&gt;
  mutex                         OK&lt;br /&gt;
  deadlock                      OK&lt;br /&gt;
  starvation                    OK&lt;br /&gt;
  non unnecessary delay         OK&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
needP = false;&lt;br /&gt;
needQ = false;&lt;br /&gt;
turn = P&lt;br /&gt;
&lt;br /&gt;
process P:&lt;br /&gt;
  while(1)&lt;br /&gt;
    needP = true&lt;br /&gt;
    while (needQ)&lt;br /&gt;
      if (turn != P) {&lt;br /&gt;
        needP = false;&lt;br /&gt;
        while ( turn != P)&lt;br /&gt;
        needP = true;&lt;br /&gt;
    }&lt;br /&gt;
    /*critical code*/&lt;br /&gt;
    turn = Q&lt;br /&gt;
    needP = false&lt;br /&gt;
    /*non-critical code*/&lt;br /&gt;
&lt;br /&gt;
process P:&lt;br /&gt;
  while(1)&lt;br /&gt;
    needQ = true&lt;br /&gt;
    while (needP)&lt;br /&gt;
      if (turn != Q) {&lt;br /&gt;
        needQ = false;&lt;br /&gt;
        while ( turn != Q)&lt;br /&gt;
        needQ = true;&lt;br /&gt;
    }&lt;br /&gt;
    /*critical code*/&lt;br /&gt;
    turn = P&lt;br /&gt;
    needQ = false&lt;br /&gt;
    /*non-critical code*/&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Lezione del 13 ottobre 2017 ==&lt;br /&gt;
== Lezione del 17 ottobre 2017 ==&lt;br /&gt;
== Lezione del 20 ottobre 2017 ==&lt;br /&gt;
== Lezione del 24 ottobre 2017 ==&lt;br /&gt;
== Lezione del 27 ottobre 2017 ==&lt;br /&gt;
== Lezione del 31 ottobre 2017 ==&lt;br /&gt;
== Lezione del 3 novembre 2017 ==&lt;br /&gt;
== Lezione del 7 novembre 2017 ==&lt;br /&gt;
== Lezione del 10 novembre 2017 ==&lt;br /&gt;
== Lezione del 14 novembre 2017 ==&lt;br /&gt;
== Lezione del 17 novembre 2017 ==&lt;br /&gt;
== Lezione del 21 novembre 2017 ==&lt;br /&gt;
== Lezione del 24 novembre 2017 ==&lt;br /&gt;
== Lezione del 28 novembre 2017 ==&lt;br /&gt;
== Lezione del 1 dicembre 2017 ==&lt;br /&gt;
== Lezione del 5 dicembre 2017 ==&lt;br /&gt;
== Lezione del 12 dicembre 2017 ==&lt;br /&gt;
== Lezione del 15 dicembre 2017 ==&lt;/div&gt;</summary>
		<author><name>Matt</name></author>
	</entry>
	<entry>
		<id>https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_I_semestre&amp;diff=2024</id>
		<title>Lezioni Anno Accademico 2017/18 I semestre</title>
		<link rel="alternate" type="text/html" href="https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_I_semestre&amp;diff=2024"/>
		<updated>2017-10-10T19:36:15Z</updated>

		<summary type="html">&lt;p&gt;Matt: /* Lezione del 10 ottobre 2017 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;''scrivete qui idee, riassunti dei concetti espressi, commenti approfondimenti sulle lezioni.''&lt;br /&gt;
&lt;br /&gt;
== Lezione del 26 settembre 2017 ==&lt;br /&gt;
'''Titolo della lezione: W&amp;amp;#x2074; H Y'''&lt;br /&gt;
&lt;br /&gt;
* What:&lt;br /&gt;
* Who:&lt;br /&gt;
----&lt;br /&gt;
Noi studenti ed &amp;quot;entrambi&amp;quot; i professori.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* When:&lt;br /&gt;
----&lt;br /&gt;
Il corso ha durata annuale. Verrà svolto ogni martedì e venerdì dalle 15:30 alle 18:30.&lt;br /&gt;
* La lezione del martedì sarà dedicata alla programmazione concorrente;&lt;br /&gt;
* La lezione del venerdì sarà dedicata alla parte generale. Terminerà circa 15 minuti prima dell'orario stabilito.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* Where:&lt;br /&gt;
----&lt;br /&gt;
Sempre in Aula 1 Ercolani (E1): DISI, Scuole Ercolani, Mura Anteo Zamboni 2B.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* How:&lt;br /&gt;
----&lt;br /&gt;
Il corso si svolgerà tramite:&lt;br /&gt;
* Lezioni frontali in aula.&lt;br /&gt;
* Attività di laboratorio.&lt;br /&gt;
* Esercitazioni.&lt;br /&gt;
&lt;br /&gt;
Inoltre, durante l'ultimo periodo di lezioni del secondo semestre, vi sarà un periodo di ripasso del programma tramite esercizi, in vista dell'esame.&lt;br /&gt;
Durante la lezione del 3/10 sono state indicate alcune propedeuticità del corso. tra cui: programmazione, algoritmi e architettura degli elaboratori in primis.&lt;br /&gt;
Importanti sono anche i corsi di Analisi matematica e Algebra e geometria. Per farla breve, il primo anno è propedeutico al secondo.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* Why:&lt;br /&gt;
----&lt;br /&gt;
* '''Fonti e strumenti del corso:''' wiki creata di gruppo, lezioni frontali e esercitazioni.&lt;br /&gt;
**Non esiste un vero e proprio testo consigliato. Tutte le informazioni sono date DURANTE il corso.&lt;br /&gt;
** Per domande specifiche scrivere su mailing list (so@cs.unibo.it);&lt;br /&gt;
** Per esercizi, appunti e programma svolto rivolgersi al wiki (so.v2.cs.unibo.it);&lt;br /&gt;
** Per live streaming (www.cs.unibo.it/~renzo/live/).&lt;br /&gt;
* '''Modalit&amp;amp;agrave; di esame:''' esame scritto (diviso in due parti: una parte generale e una di programmazione concorrente) + progetto + orale (facoltativo), possibilità di dare solo lo scritto per ottenere un massimo di 18. La modalità per studenti in &amp;quot;difficoltà&amp;quot; è disponibile solo fino agli appelli autunnali.&lt;br /&gt;
** Si parlerà del progetto indicativamente a partire da Dicembre 2017.&lt;br /&gt;
** L'orale può essere sostenuto da chi vuole migliorare (peggiorare) il voto ottenuto, o da chi vuole ottenere la lode.&lt;br /&gt;
* '''Orario di ricevimento per il primo semestre (fino al 15/09/17):''' martedì alle 11:30.&lt;br /&gt;
* Universit&amp;amp;agrave; = docenti + studenti.&lt;br /&gt;
* Informatica = come generare informazione automatica.&lt;br /&gt;
* Hardware, Software, Elaborazione, Comunicazione, Memorizzazione, Digitale/Analogico.&lt;br /&gt;
La principale distinzione che facciamo tra '''dato''' e '''informazione''' è che il dato, da solo, è privo di significato. Se, tuttavia, viene interpretato in un particolare contesto allora può diventare informazione significativa per chi sta interpretando i dati.&lt;br /&gt;
&lt;br /&gt;
Un '''algoritmo''' è una o più sequenze non ambigue di passi che, dato un problema, ci permette di risolverlo. &lt;br /&gt;
&lt;br /&gt;
Il nostro algoritmo, scritto in un qualche linguaggio formale, diventa un '''programma''' (sostanzialmente un testo, un insieme di istruzioni).&lt;br /&gt;
&lt;br /&gt;
Un linguaggio è un entità software, ed è definito come una quadrupla (alfabeto, sintassi, lessico, semantica) dove:&lt;br /&gt;
*l'''alfabeto'' è l'insieme di simboli che compone il linguaggio.&lt;br /&gt;
*il ''lessico'' si può vedere come una funzione che va dai simboli a un booleano e ci dice quali sono le frasi ben formate.&lt;br /&gt;
*la ''sintassi'' determina quali delle sequenze scritte sono effettivamente corrette.&lt;br /&gt;
*la ''semantica'' associa un significato alle parole ben formate.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 29 settembre 2017 ==&lt;br /&gt;
'''Cos'è un sistema operativo e un po' di storia'''&lt;br /&gt;
&lt;br /&gt;
Un ''sistema operativo'' è un programma che gestisce i processi, le risorse e interfaccia le applicazioni con l'hardware dell'elaboratore.&lt;br /&gt;
&lt;br /&gt;
In particolare, il sistema operativo (laddove esiste) è il primo processo ad essere attivato e resta in vita fino allo spegnimento del calcolatore o al sopraggiungere di un errore fatale per il sistema.&lt;br /&gt;
&lt;br /&gt;
A cosa serve un sistema operativo?&lt;br /&gt;
Principalmente, ha i seguenti scopi:&lt;br /&gt;
# Facilitare l'utilizzo del sistema (senza S.O. sarebbe a carico del programmatore conoscere tutti i linguaggi con cui si comunica con l'architettura della macchina);&lt;br /&gt;
# Rendere affidabile, protetto e sicuro l'utilizzo del sistema (e.g. un processo potrebbe recar danno all'intero sistema se non controllato o gestito, potrebbe ignorare i permessi di visualizzazione di un file);&lt;br /&gt;
# Astrarre l'hardware (e.g. filesystem);&lt;br /&gt;
# Garantire l'efficienza (e.g. non tenere la CPU in idle adottando opportuni algoritmi di scheduling);&lt;br /&gt;
# Assicurare portabilità ;&lt;br /&gt;
&lt;br /&gt;
Un approccio molto usato è il cosiddetto approccio a strati (&amp;quot;a livelli&amp;quot;) in cui ogni strato utilizza i servizi forniti al livello inferiore e ne fornisce di nuovi a quello superiore.&lt;br /&gt;
Astraendo il nostro calcolatore possiamo piazzare al livello più basso l'hardware e, subito sopra, il sistema operativo. I due layer comunicano usando il linguaggio ISA (Instruction Set Architecture), nativo della CPU stessa, al quale si aggiungono quelli che permettono di comunicare con i vari controllori dei dispositivi come la scheda di rete, la stampante, etc. Sopra il livello del sistema operativo possiamo collocare quello delle librerie e, infine, quello degli applicativi.&lt;br /&gt;
&lt;br /&gt;
* '''Systemcall''' = meccanismo usato da un processo per richiedere al SO una qualche funzionalità a livello kernel. Un esempio è la funzione printf() del linguaggio C (stampa a video) che utilizza la systemcall write() (una delle systemcall per la gestione dei dispositivi).&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
----&lt;br /&gt;
'''Storia dei sistemi operativi'''&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''zero''' (1800 ca.) possiamo includere Babbage con la sua macchina analitica e lady Ada Lovelace. [[File:290px-AnalyticalMachine Babbage London.jpg |200px|thumb|right|Macchina analitica di Babbage]]&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''uno''' vediamo la comparsa delle valvole con tutti i problemi connessi. Non c'erano utilizzatori delle macchine, le stesse persone che le costruivano erano anche programmatori e fruitori delle stesse.&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''due''' (fine anni '60) arriva il transistor. Quest'ultimo è molto più veloce e piccolo della valvola e, soprattutto, meno incline a guasti. Le macchine iniziano ad essere più economiche grazie alla possibilità di avere un'economia di scala e quindi accessibili al grande pubblico. Questo fa si che, in queste prime fasi, i costruttori non siano più gli unici utilizzatori. [[File:Transistors.jpg |300px|thumb|right|Diversi tipi di Transistor]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
A questo punto poiché, pur essendo diventata più accessibile, una macchina costava ancora molto, nasce l'esigenza di non lasciare mai un processore senza lavoro (al fine di massimizzare i ricavi) e di condividere i dati.&lt;br /&gt;
In questa fase, infatti, abbiamo sistemi operativi di tipo ''batch'' (che collezionano tutto l'input all'inizio per calcolare e restituire l'output) dove l'inserimento di programmi e dati veniva fatto tramite schede perforate e non c'era interazione. Dai sistemi batch nasce lo ''SPOOL'' (Simultaneous Peripheral Operations On-Line).&lt;br /&gt;
&lt;br /&gt;
Si comincia a pensare di utilizzare i tempi di I/O per poter eseguire altri processi. Questo però solleva almeno due problematiche: da un lato serve un modo per sapere quando un input è davvero finito mentre, dall'altro, bisogna fare in modo che un processo che non richieda I/O non occupi la CPU per un tempo indefinito.&lt;br /&gt;
Per la prima problematica la soluzione è rappresentata dall'introduzione degli '''interrupt''', segnali elettrici inviati alla CPU alla fine di ogni input.&lt;br /&gt;
Per la seconda, invece, è stato introdotto il cosiddetto '''interval timer''' che non è altro che un dispositivo che invia interrupt dopo un determinato quanto di tempo assicurando che un processo non occupi per troppo tempo il processore.&lt;br /&gt;
Questo consente di realizzare sistemi time sharing (il cui più semplice algoritmo è il round-robin) dove un processo può essere in uno dei seguenti tre stati:&lt;br /&gt;
*'''READY''' pronto per essere eseguito ma, il processore è già occupato;&lt;br /&gt;
*'''RUNNING''' in esecuzione;&lt;br /&gt;
*'''WAIT''' in attesa di I/O.&lt;br /&gt;
&lt;br /&gt;
[[File:Dds.png]]&lt;br /&gt;
&lt;br /&gt;
Nella '''quarta''' generazione (anni '70 ca.) vediamo la nascita di molte innovazioni importanti. Si riesce a rimpicciolire di molto i processori facendoli divenire a tutti gli effetti micro-processori. Nei laboratori Bell nasce Unix, un sistema operativo rivoluzionario con time sharing e clonazione dei processi (un processo non viene mai creato dal nulla ma viene prima clonato da uno già esistente e poi, alla copia, viene fatto eseguire un programma). A causa di problemi di portabilità da PDP-9, macchina su cui Unix è nato, a PDP-11 nasce il linguaggio C e il suo compilatore (scritto anch'esso in C).&lt;br /&gt;
Mentre Apple I &amp;quot;porta&amp;quot; una nuova idea di pc per tutte le famiglie, nascono molte versioni di Unix.&lt;br /&gt;
Per impedire una deriva proprietaria in cui ogni produttore offriva tutto quello che offrivano gli altri ma con qualche feature in più, Richard Stallman fonda il progetto GNU (GNU's Not Unix) per il quale scrive tutta una serie di utilities come il famoso compilatore gcc.&lt;br /&gt;
A GNU, per essere operativo, manca però un kernel. Non volendo aspettare i tempi di sviluppo del kernel che Stallman aveva in mente, Linus Torlvalds scrive il kernel '''Linux''' da cui nasce il sistema operativo GNU/Linux.&lt;br /&gt;
&lt;br /&gt;
Nei tempi più recenti nascono i processori multi-core.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 3 ottobre 2017 ==&lt;br /&gt;
 &lt;br /&gt;
'''Introduzione alla programmazione concorrente: processi, thread e  librerie'''&lt;br /&gt;
&lt;br /&gt;
In programmazione concorrente le nozioni apprese di &amp;quot;algoritmo&amp;quot;, &amp;quot;programma&amp;quot; e &amp;quot;processo&amp;quot; devono essere aggiornate. Questo perché non vi è più un programma con un'unica sequenza esecutiva [o meglio, un unico filo (= thread) di esecuzione]. Appare ovvio come servino dei meccanismi per organizzare e sincronizzare tali fili esecutivi, in modo da evitare il verificarsi di eventi non desiderati.&lt;br /&gt;
&lt;br /&gt;
Quando parliamo di ''sistema concorrente'', ci riferiamo ad un sistema in cui due o più &amp;quot;attori&amp;quot; sono eseguiti parallelamente.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Questi attori possono essere '''processi''' o '''thread'''.&lt;br /&gt;
La differenza sostanziale tra queste due tipologie di attori riguarda la condivisione della memoria e dei dati su cui operano.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Più processi possono eseguire lo stesso programma ma, ognuno di essi, ha i propri dati su cui operare e il suo stato di avanzamento. In particolare:&lt;br /&gt;
* un processo è un'entità, un attore che ha delle proprietà e delle risorse associate;&lt;br /&gt;
* in ogni istante, il processo può essere interrotto.&amp;lt;br&amp;gt;&lt;br /&gt;
Più thread che vengono eseguiti parallelamente, invece, condividono gli stessi dati su cui operare.&amp;lt;br&amp;gt;&lt;br /&gt;
Codici di esempio:&lt;br /&gt;
&lt;br /&gt;
'''processi'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=C&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;unistd.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int main(int argc, char *argv[]){&lt;br /&gt;
   if(fork()) printf(&amp;quot;Yes&amp;quot;);&lt;br /&gt;
   else printf(&amp;quot;No&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
L'output generato sarà &amp;quot;YesNo&amp;quot; poiché, usando la systemcall ''fork()'', abbiamo creato una nuova copia del processo (un processo figlio). Il processo padre, che ha eseguito con successo la fork(), stamperà &amp;quot;Yes&amp;quot; mentre il figlio stamperà &amp;quot;No&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
'''thread'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=C&amp;gt;&lt;br /&gt;
#include &amp;lt;pthread.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
#include &amp;lt;assert.h&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
#define NUM_THREADS     5&lt;br /&gt;
#define MAX 1000000&lt;br /&gt;
&lt;br /&gt;
int sum;&lt;br /&gt;
 &lt;br /&gt;
void* perform_work(){&lt;br /&gt;
	int i;&lt;br /&gt;
	&lt;br /&gt;
	for(i = 0; i &amp;lt; MAX; i++) sum = sum + 1;&lt;br /&gt;
  	/* optionally: insert more useful stuff here */ &lt;br /&gt;
  	return NULL;&lt;br /&gt;
}&lt;br /&gt;
 &lt;br /&gt;
int main( int argc, char** argv ){&lt;br /&gt;
	pthread_t threads[NUM_THREADS];&lt;br /&gt;
  	int thread_args[NUM_THREADS];&lt;br /&gt;
  	int result_code;&lt;br /&gt;
  	unsigned index;&lt;br /&gt;
 &lt;br /&gt;
	sum = 0;&lt;br /&gt;
  	// create all threads one by one&lt;br /&gt;
  	for(index = 0; index &amp;lt; NUM_THREADS; ++index ){&lt;br /&gt;
    		thread_args[index] = index;&lt;br /&gt;
    		printf(&amp;quot;In main: creating thread %d\n&amp;quot;, index);&lt;br /&gt;
    		result_code = pthread_create(&amp;amp;threads[index], NULL, perform_work, NULL);&lt;br /&gt;
    		assert(!result_code);&lt;br /&gt;
  	} &lt;br /&gt;
  	// wait for each thread to complete&lt;br /&gt;
  	for(index = 0; index &amp;lt; NUM_THREADS; ++index ){&lt;br /&gt;
    		// block until thread 'index' completes&lt;br /&gt;
    		result_code = pthread_join(threads[index], NULL);&lt;br /&gt;
    		assert(!result_code);&lt;br /&gt;
    		printf(&amp;quot;In main: thread %d has completed\n&amp;quot;, index);&lt;br /&gt;
   	}&lt;br /&gt;
 &lt;br /&gt;
   	printf(&amp;quot;In main: All threads completed successfully\n&amp;quot; );&lt;br /&gt;
	printf(&amp;quot;Result: %d\n&amp;quot;, sum);&lt;br /&gt;
   	exit( EXIT_SUCCESS );&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Prima di discutere l'output generato dall'esecuzione di questo codice, è opportuno introdurre, brevemente, gli strumenti utilizzati.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
'''Pthread (POSIX thread)'''&lt;br /&gt;
&lt;br /&gt;
Citazione dal Silberschatz, Galvin, Gagne:&lt;br /&gt;
''Pthread si riferisce allo standard POSIX (IEEE 1003.1c) che definisce una API per la creazione e sincronizzazione dei thread.''&lt;br /&gt;
&lt;br /&gt;
Le istruzioni fondamentali sono:&lt;br /&gt;
* '''int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);''' che crea un thread;&lt;br /&gt;
* '''int pthread_join(pthread_t thread, void **retval);''' che aspetta che un thread sia completato prima di continuare l'esecuzione.&lt;br /&gt;
&lt;br /&gt;
Da ricordare che, per utilizzare pthread, va incluso l'header ''&amp;lt;pthread.h&amp;gt;'' e durante la compilazione va inclusa la libreria esterna tramite l'opzione ''-pthread''.&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
Torniamo all'output generato dall'esecuzione del secondo codice.&amp;lt;br&amp;gt;&lt;br /&gt;
Nel codice vengono generati cinque thread ed ognuno somma MAX (=1000000) volte 1 a sum. Ci aspettiamo un output di 5000000 ma, in realtà, viene stampato un valore molto minore.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Questo accade principalmente per due motivi:&lt;br /&gt;
*sommare una costante ad una variabile non è un'operazione atomica (richiede almeno tre istruzioni assembler: due mov ed una sum).&lt;br /&gt;
*l'interval timer può fermare l'esecuzione del thread mentre sta effettuando una delle tre operazioni richieste per aggiungere uno a sum e provocare un risultato errato.&lt;br /&gt;
&lt;br /&gt;
Questo è uno dei possibili problemi della programmazione concorrente conosciuto come ''race condition''.&amp;lt;br&amp;gt;&lt;br /&gt;
Un sistema di processi multipli presenta una '''race condition''' qualora il risultato finale dell'esecuzione dipenda dalla temporizzazione, o dall'ordine con cui i processi vengono eseguiti.&lt;br /&gt;
&lt;br /&gt;
Per risolvere questo problema possiamo adoperare ancora una volta pthread.&lt;br /&gt;
Nello specifico, ci venogono offerte istruzione per realizzare una '''mutua esclusione''' sulle risorse condivise in modo che l'operazione che prima non era atomiche,ora, lo sia e non porti più ad una computazione errata.&amp;lt;br&amp;gt;&lt;br /&gt;
Codice di esempio:&amp;lt;br&amp;gt;&lt;br /&gt;
'''Mutex'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=C&amp;gt;&lt;br /&gt;
#include &amp;lt;pthread.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
#include &amp;lt;assert.h&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
#define NUM_THREADS     5&lt;br /&gt;
#define MAX 1000000&lt;br /&gt;
&lt;br /&gt;
int sum;&lt;br /&gt;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;&lt;br /&gt;
 &lt;br /&gt;
void* perform_work(){&lt;br /&gt;
	int i;&lt;br /&gt;
	&lt;br /&gt;
	for(i = 0; i &amp;lt; MAX; i++){ &lt;br /&gt;
		pthread_mutex_lock(&amp;amp;mutex);		&lt;br /&gt;
		sum = sum + 1;&lt;br /&gt;
		pthread_mutex_unlock(&amp;amp;mutex);&lt;br /&gt;
	}&lt;br /&gt;
  	/* optionally: insert more useful stuff here */ &lt;br /&gt;
  	return NULL;&lt;br /&gt;
}&lt;br /&gt;
 &lt;br /&gt;
int main( int argc, char** argv ){&lt;br /&gt;
	pthread_t threads[NUM_THREADS];&lt;br /&gt;
  	int thread_args[NUM_THREADS];&lt;br /&gt;
  	int result_code;&lt;br /&gt;
  	unsigned index;&lt;br /&gt;
&lt;br /&gt;
	sum = 0;&lt;br /&gt;
  	// create all threads one by one&lt;br /&gt;
  	for(index = 0; index &amp;lt; NUM_THREADS; ++index ){&lt;br /&gt;
    		thread_args[index] = index;&lt;br /&gt;
    		printf(&amp;quot;In main: creating thread %d\n&amp;quot;, index);&lt;br /&gt;
    		result_code = pthread_create(&amp;amp;threads[index], NULL, perform_work, NULL);&lt;br /&gt;
    		assert(!result_code);&lt;br /&gt;
  	} &lt;br /&gt;
  	// wait for each thread to complete&lt;br /&gt;
  	for(index = 0; index &amp;lt; NUM_THREADS; ++index ){&lt;br /&gt;
    		// block until thread 'index' completes&lt;br /&gt;
    		result_code = pthread_join(threads[index], NULL);&lt;br /&gt;
    		assert(!result_code);&lt;br /&gt;
    		printf(&amp;quot;In main: thread %d has completed\n&amp;quot;, index);&lt;br /&gt;
   	}&lt;br /&gt;
 &lt;br /&gt;
   	printf(&amp;quot;In main: All threads completed successfully\n&amp;quot; );&lt;br /&gt;
	printf(&amp;quot;Result: %d\n&amp;quot;, sum);&lt;br /&gt;
   	exit( EXIT_SUCCESS );&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
'''MUTEX'''&lt;br /&gt;
&lt;br /&gt;
Un mutex è un dispositivo di MUTual EXclusion (mutua esclusione), ed è utile per proteggere le strutture dati condivise da modifiche concorrenti, implementare sezioni critiche e monitor.&lt;br /&gt;
&lt;br /&gt;
Un mutex ha due possibili stati: sbloccato (non posseduto da nessun thread) e bloccato (posseduto da un thread). Un mutex non può mai essere posseduto da due thread contemporaneamente.&amp;lt;br&amp;gt;Se un thread provasse ad acquisire il mutex, mentre quest'ultimo è già in possesso di un altro thread, dovrebbe aspettare finché non viene rilasciato.&lt;br /&gt;
La variabile che realizza il mutex, di tipo ''pthread_mutex_t'', viene inizializzata staticamente assegnandogli PTHREAD_MUTEX_INITIALIZER.&lt;br /&gt;
'''pthread_mutex_lock''' acquisisce immediatamente il mutex, se libero. Altrimenti aspetta fino a trovarla, prima o poi, libera.&amp;lt;br&amp;gt;&lt;br /&gt;
'''pthread_mutex_unlock''' rilascia il mutex.&lt;br /&gt;
&lt;br /&gt;
[da: https://sourceware.org/pthreads-win32/manual/pthread_mutex_init.html]&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
Con questa soluzione abbiamo raggiunto un risultato esatto pur avendo pagato in prestazioni: la computazione infatti è più lenta a causa del tempo richiesto dalla sincronizzazione tra i thread.&lt;br /&gt;
&lt;br /&gt;
Consideriamo ora due scenari leggermente più complicati:&lt;br /&gt;
* nel primo abbiamo due processi, A e B, che devono accedere a due risorse, X e Y, contemporaneamente prima di poter terminare. Cosa succede se, mentre A acquisisce X, B acquisisce Y?&lt;br /&gt;
* nel secondo abbiamo tre processi, A, B e C, che devono accedere alla risorsa X. Che succede se A e B, essendo più veloci, occupano insistentemente la risorsa e non permettono a C di utilizzarla?&lt;br /&gt;
&lt;br /&gt;
La prima condizione si chiama '''deadlock''', la seconda condizione si chiama '''starvation'''. Il deadlock (stallo) è un problema che non può scomparire autonomamente, mentre la starvation (&amp;quot;inedia&amp;quot;) si può risolvere da sola. A tal proposito ci viene incontro l'assioma di '''finite progress''': &amp;quot;ogni processo che può avanzare, prima o poi lo farà&amp;quot;.&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
In ogni caso, per parlare in maniera più approfondita di queste tematiche:&lt;br /&gt;
* avremo bisogno di un modello di riferimento&lt;br /&gt;
* avremo bisogno di paradigmi in grado di esprimere la concorrenza (alcuni paradigmi saranno rivolti alla concorrenza a memoria privata; altri al multithreading a risorse condivise)&lt;br /&gt;
* dovremo imparare a scrivere programmi&lt;br /&gt;
Nel nostro modello di riferimento l'assegnamento di costanti è un operazione atomica. Dagli esempi precedenti, infatti, si evince come ci sia bisogno di creare atomicità in sezioni critiche. Inoltre, il nostro modello dovrà rispettare le proprietà di safety e di liveness, ovvero:&lt;br /&gt;
* Tutti i processi danno la stessa risposta;&lt;br /&gt;
* Ogni processo corretto da come risposta una tra quelle proposte;&lt;br /&gt;
* Ogni processo prima o poi fornisce una risposta.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 6 ottobre 2017 ==&lt;br /&gt;
'''Il linguaggio C'''&lt;br /&gt;
&lt;br /&gt;
Il linguaggio C nasce nei primi anni 70 grazie al lavoro di Dennis Ritchie con lo scopo di scrivere il &lt;br /&gt;
sistema operativo Unix. Quali sono le caratteristiche di C e cosa lo rende un linguaggio adatto per scrivere un sistema&lt;br /&gt;
operativo? Un linguaggio per scrivere un sistema operativo deve:&lt;br /&gt;
* essere indipendente dalla macchina su cui viene scritto&lt;br /&gt;
* dare la possibilità di lavorare direttamente con la memoria&lt;br /&gt;
* permette di andare a modificare i singoli bit in memoria&lt;br /&gt;
* essere semplice da usare&lt;br /&gt;
* essere massimamente produttivo&lt;br /&gt;
* essere veloce, efficiente&lt;br /&gt;
C presenta le caratteristiche dette sopra e alcune altre:&lt;br /&gt;
* in C, dal punto di vista sintattico, tutto è funzione. Si può vedere un programma in C come un insieme di funzioni. La funzione principale è il '''main'''. L'unica cosa che la distingue è il nome, a parte quello il main è una funzione come le altre. Prende due argomenti: argcount e argvalue, che contengono rispettivamente il numero degli argomenti e un array contenente gli argomenti stessi. L'interfaccia del main è tipicamente: &amp;lt;syntaxhighlight lang=C&amp;gt; int main(int argc, char *argv[]) &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
* il costrutto fondamentale è l'espressione. ''expression'' ; = ''instruction''&lt;br /&gt;
* sono previste istruzioni di struttura &amp;lt;syntaxhighlight lang=C&amp;gt;if, while, switch, etc.&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
* le variabili, per poter essere utilizzate, devono essere sempre definite&lt;br /&gt;
* il C è un linguaggio fortemente tipato, però con un certo numero di eccezioni: ad esempio, è possibile fare il casting esplicito di un puntatore void * in qualsiasi tipo, senza che il compilatore dia errori o warnings, con conseguente perdita di type safety&lt;br /&gt;
* C è un linguaggio &amp;quot;per adulti&amp;quot;: non impone una struttura ordinata al codice, come ad esempio fa invece Python, ed è piena responsabilità del programmatore scrivere programmi ordinati/leggibili. Inoltre molte operazioni che in altri linguaggi non sarebbero permesse lo sono invece in C: anche in questo caso è responsabilità del programmatore fare corretto uso del linguaggio&lt;br /&gt;
* C è un linguaggio molto semplice. Come si sopperisce alle mancanze imposte da questa semplicità? Grazie alle librerie: in C tutte le funzionalità aggiuntive (non fornite dal linguaggio stesso, come l'allocazione di memoria, la stampa a schermo, la gestione di input e output) sono fornite dalle librerie&lt;br /&gt;
* C è anche sintatticamente semplice. Dove va a finire la complessità sintattica? Nel preprocessore: prima di essere compilato un programma in C passa attraverso un preprocessore, che produce codice in base alle direttive di preprocessione date nel codice stesso &amp;lt;syntaxhighlight lang=C&amp;gt;#ifdef VAR ... #endif, # e ## operator, __LINE__,etc&amp;lt;/syntaxhighlight&amp;gt; &lt;br /&gt;
* in C vi sono fondamentalmente due tipi di dato: int e float. Tutti gli altri &amp;quot;tipi&amp;quot; di dato sono derivati da essi. Si hanno diversi modificatori, che vanno a modificare la quantità di memoria allocata per la variabile. Alcuni di questi sono &amp;lt;syntaxhighlight lang=C&amp;gt;long, short, char, etc&amp;lt;/syntaxhighlight&amp;gt; Un dato di tipo long int ha, solitamente, la dimensione di una parola di memoria della macchina su cui è eseguito il programma. char alloca 8 bits. Se si prende una variabile di un certo tipo e lo si assegna ad una di tipo più ampio, il valore viene mantenuto. Viceversa quando si assegna una variabile di un tipo ad una di tipo meno ampio, una volta &amp;quot;superato il limite&amp;quot; si torna indietro ciclicamente al valore iniziale. Per queste situazioni il compilatore non dà né warnings né errors: sono perfettamente lecite in C&lt;br /&gt;
* in C si passa una varibile ad una funzione solo per valore; per passare &amp;quot;per riferimento&amp;quot; una variabile si passa per valore l'indirizzo della variabile&lt;br /&gt;
* in C si può fare uso di puntatori alla memoria. Dato un puntatore p, un'istruzione del tipo p+i va letta come: vai avanti di i posizioni della grandezza del tipo del puntatore ognuno. Un vettore in C non è altro che un puntatore che non può essere assegnato. Una scrittura del tipo array[i] è una abbreviazione di *(array + i). Non c'è controllo in C se vado oltre la memoria allocata per l'array. Perché? In Pascal, ad esempio, c'è un tale controllo. Il motivo per cui in C questo manca è che questo controllo genera ulteriore codice macchina. Dovendo C essere efficiente, non ci si può permettere un tale costo aggiuntivo. La responsabilità di non andare oltre è lasciata al programmatore&lt;br /&gt;
&lt;br /&gt;
Esercizio svolto che prende in input due valori tramite i parametri del metodo '''main''', li converte da sequenze di caratteri ad interi, per poi stamparli utilizzando delle '''System Call'''. Lo scopo dell’esercizio è dimostrare come sia possibile utilizzare il linguaggio C senza l’utilizzo di librerie particolari (ad eccezione di quelle per le System Call).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=C&amp;gt;&lt;br /&gt;
#define _GNU_SOURCE&lt;br /&gt;
#include &amp;lt;unistd.h&amp;gt;&lt;br /&gt;
#include &amp;lt;sys/syscall.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int _uatoi(char *s, int value){&lt;br /&gt;
  if (*s == 0)&lt;br /&gt;
    return value;&lt;br /&gt;
  else if (*s &amp;gt;= '0' &amp;amp;&amp;amp; *s &amp;lt;= '9'){&lt;br /&gt;
    value = value * 10 + (*s - '0');&lt;br /&gt;
    return _uatoi(s+1, value);&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int uatoi(char *s){&lt;br /&gt;
  return _uatoi(s, 0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
char *uitoa(int val, char *buf, int len){&lt;br /&gt;
  int i;&lt;br /&gt;
  for (len = len - 1; len &amp;gt;= 0 &amp;amp;&amp;amp; val &amp;gt; 0; len--, val /= 10){&lt;br /&gt;
    buf[len] = '0' + (val %10);&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  return buf + len + 1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#define MAXOUTLEN 10&lt;br /&gt;
int main (int argc, char *argv[]) {&lt;br /&gt;
    int tot = 0;&lt;br /&gt;
    int i;&lt;br /&gt;
    char buf[MAXOUTLEN];&lt;br /&gt;
    char *result;&lt;br /&gt;
    for (i = 1; i &amp;lt; argc; i++)&lt;br /&gt;
      tot += uatoi(argv[i]);&lt;br /&gt;
    result = uitoa(tot, buf, MAXOUTLEN);&lt;br /&gt;
    syscall(__NR_write, 1, result, MAXOUTLEN - (result - buf));&lt;br /&gt;
    syscall(__NR_write, 1, &amp;quot;\n&amp;quot;, 1);&lt;br /&gt;
    return tot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Lezione del 10 ottobre 2017 ==&lt;br /&gt;
'''Programmazione concorrente: notazione e un primo problema'''&lt;br /&gt;
&lt;br /&gt;
Esempio di possibile rappresentazione di codice concorrente.&amp;lt;br&amp;gt;&lt;br /&gt;
'''''A'', ''B'', ''C'', ''D'', ''E''''' rappresentano porzioni di codice.&amp;lt;br&amp;gt;&lt;br /&gt;
'''''//''''' rappresenta esecuzione parallela&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
A&lt;br /&gt;
cobegin&lt;br /&gt;
  B&lt;br /&gt;
//&lt;br /&gt;
  C&lt;br /&gt;
//&lt;br /&gt;
  D&lt;br /&gt;
coend&lt;br /&gt;
E&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il codice sarà eseguito come: ''A'' -&amp;gt; ''BCD'' -&amp;gt; ''E''. ''E'' quindi verrà eseguito solo dopo la fine dell'esecuzione di ''BCD''.&lt;br /&gt;
&lt;br /&gt;
Un esempio che si presta molto bene ad illustrare la programmazione concorrente è quello del '''merge sort''' concorrente:&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
leggi vettore&lt;br /&gt;
cobegin&lt;br /&gt;
  ordina la prima metà del vettore&lt;br /&gt;
//&lt;br /&gt;
  ordina la seconda metà del vettore&lt;br /&gt;
coend&lt;br /&gt;
merge (miscela) i risultati&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Versione errata del merge sort concorrente:&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
leggi vettore&lt;br /&gt;
cobegin&lt;br /&gt;
  ordina la prima metà del vettore&lt;br /&gt;
//&lt;br /&gt;
  ordina la seconda metà del vettore&lt;br /&gt;
//  &lt;br /&gt;
  merge (miscela) i risultati&lt;br /&gt;
coend&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
/* L'atomicità della funzione somma non è scontata quindi la dobbiamo&lt;br /&gt;
definire, altrimenti le parti potrebbero sovrapporsi*/&lt;br /&gt;
&lt;br /&gt;
/*Quali sono le proprietà che devono avere csenter() e cexit()?&lt;br /&gt;
&lt;br /&gt;
  1) devono garantire la mutua esclusione (1 solo processo alla volta)&lt;br /&gt;
  contando n csenter() completate e m cexit() completate la differenza&lt;br /&gt;
  deve essere &amp;lt;=1&lt;br /&gt;
&lt;br /&gt;
  2) non devono fare deadlock tra di loro&lt;br /&gt;
&lt;br /&gt;
  3) non devono fare starvation tra loro&lt;br /&gt;
&lt;br /&gt;
  4) (no unnecessary delay)&lt;br /&gt;
  un processo che non sta usando la critical section {csenter()..cexit()}&lt;br /&gt;
  non deve bloccare un altro processo dall'entrarvi.&lt;br /&gt;
&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
process A:&lt;br /&gt;
&lt;br /&gt;
  for (i = 0; i &amp;lt; MAX; i++)&lt;br /&gt;
    csenter()&lt;br /&gt;
    sum += 1 //atomicità descritta da csenter(), csexit()&lt;br /&gt;
    // &amp;lt; sum += 1 &amp;gt; atomicità lasciata al processore&lt;br /&gt;
    csexit()&lt;br /&gt;
&lt;br /&gt;
process B:&lt;br /&gt;
&lt;br /&gt;
  for (i = 0; i &amp;lt; MAX; i++)&lt;br /&gt;
    csenter()&lt;br /&gt;
    sum += 1&lt;br /&gt;
    csexit()&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
Problema la cui soluzione è nota come '''Soluzione di Dekker''' (scritta da Dijkstra che )&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
process[i], i=1,...,N&lt;br /&gt;
&lt;br /&gt;
while(1):&lt;br /&gt;
  csenter() //inizio della sezione critica (critical section)&lt;br /&gt;
  critical code&lt;br /&gt;
  csexit() //fine della sezione critica&lt;br /&gt;
  non-critical code&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang='c'&amp;gt;&lt;br /&gt;
/*Soluzione 1: A turni&lt;br /&gt;
Qual è il problema?&lt;br /&gt;
  mutex                   OK&lt;br /&gt;
  deadlock                OK&lt;br /&gt;
  starvation              OK&lt;br /&gt;
  no unnecessary delay    NO perché se P ha bisogno di molte volte la critical&lt;br /&gt;
  section, P non deve aspettare che Q chieda la critical section. Qui se Q una&lt;br /&gt;
  volta richiesta la sezione non la usa ma si ferma, P non può richiederla ancora&lt;br /&gt;
  perché appartiene a Q&lt;br /&gt;
*/&lt;br /&gt;
turn=P&lt;br /&gt;
&lt;br /&gt;
process P:&lt;br /&gt;
  while(1)&lt;br /&gt;
    while (turn != P)&lt;br /&gt;
      ;&lt;br /&gt;
    critical code&lt;br /&gt;
    turn = Q&lt;br /&gt;
    non-critical code&lt;br /&gt;
&lt;br /&gt;
process Q:&lt;br /&gt;
  while(1)&lt;br /&gt;
    while (turn != Q)&lt;br /&gt;
    critical code&lt;br /&gt;
    turn = P&lt;br /&gt;
    non-critical code&lt;br /&gt;
&lt;br /&gt;
/*Soluzione 2: Con 2 variabili, cerchiamo di risolvere il problema dei turni&lt;br /&gt;
&lt;br /&gt;
  finché Q usa la sezione P aspetta, quando Q ha finito P prende la sezione critica&lt;br /&gt;
  non ci sono ritardi indesiderati&lt;br /&gt;
&lt;br /&gt;
  starvation              OK&lt;br /&gt;
  deadlock                OK&lt;br /&gt;
  no unnecessary delay    OK&lt;br /&gt;
  mutex                   NO possono essere entrambi come false, escono insieme nel while&lt;br /&gt;
                          tutti e due mettono a true ed entrano assieme nel while.&lt;br /&gt;
*/&lt;br /&gt;
inP = false;&lt;br /&gt;
inQ = false;&lt;br /&gt;
&lt;br /&gt;
process P:&lt;br /&gt;
  while(1)&lt;br /&gt;
    while (inQ)&lt;br /&gt;
      ;&lt;br /&gt;
    inP = true&lt;br /&gt;
    critical code&lt;br /&gt;
    inP = false&lt;br /&gt;
    non-critical code&lt;br /&gt;
&lt;br /&gt;
process Q:&lt;br /&gt;
  while(1)&lt;br /&gt;
    while (inP)&lt;br /&gt;
      ;&lt;br /&gt;
    inQ = true&lt;br /&gt;
    critical code&lt;br /&gt;
    inQ = false&lt;br /&gt;
    non-critical code&lt;br /&gt;
&lt;br /&gt;
/*Soluzione 3: il problema di soluzione 2 è che i processi non sono al corrente&lt;br /&gt;
dell'intenzione altrui di entrare nel while quindi crea un problema.&lt;br /&gt;
  mutex                   OK&lt;br /&gt;
  deadlock                NO (unisono entrambi richiedono &amp;quot;mia&amp;quot; loop)&lt;br /&gt;
  starvation              OK&lt;br /&gt;
  non unnecessary delay   OK&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
needP = false;&lt;br /&gt;
needQ = false;&lt;br /&gt;
&lt;br /&gt;
process P:&lt;br /&gt;
  while(1)&lt;br /&gt;
    needP = true&lt;br /&gt;
    while (needQ)&lt;br /&gt;
      ;&lt;br /&gt;
    critical code&lt;br /&gt;
    needP = false&lt;br /&gt;
    non-critical code&lt;br /&gt;
&lt;br /&gt;
process Q:&lt;br /&gt;
  while(1)&lt;br /&gt;
    needQ = true&lt;br /&gt;
    while (needP)&lt;br /&gt;
      ;&lt;br /&gt;
    critical code&lt;br /&gt;
    needQ = false&lt;br /&gt;
    non-critical code&lt;br /&gt;
&lt;br /&gt;
/*Soluzione 4:&lt;br /&gt;
&lt;br /&gt;
    mutex                 OK&lt;br /&gt;
    deadlock              OK&lt;br /&gt;
    non unnecessary delay OK&lt;br /&gt;
    starvation            NO perché può esistere il processo che prova sempre a&lt;br /&gt;
                          chiedere nel momento sbagliato e non esegue mai.&lt;br /&gt;
*/&lt;br /&gt;
inP = false;&lt;br /&gt;
inQ = false;&lt;br /&gt;
&lt;br /&gt;
process P:&lt;br /&gt;
  while(1)&lt;br /&gt;
    inP = true&lt;br /&gt;
    while (inQ){&lt;br /&gt;
        inP = false;&lt;br /&gt;
        delay&lt;br /&gt;
        inP = true;&lt;br /&gt;
    }&lt;br /&gt;
    critical code&lt;br /&gt;
    inP = false&lt;br /&gt;
    non-critical code&lt;br /&gt;
&lt;br /&gt;
process Q:&lt;br /&gt;
  while(1)&lt;br /&gt;
    inQ = true&lt;br /&gt;
    while (inQ){&lt;br /&gt;
        inQ = false;&lt;br /&gt;
        delay&lt;br /&gt;
        inQ = true;&lt;br /&gt;
    }&lt;br /&gt;
    critical code&lt;br /&gt;
    inQ = false&lt;br /&gt;
    non-critical code&lt;br /&gt;
&lt;br /&gt;
/*Soluzione finale:&lt;br /&gt;
  uniamo soluzione 1 (poco simmetrica) e soluzione 4 (troppo simmetrica)&lt;br /&gt;
  usiamo quello che ci serve e se siamo troppi a voler entrare usiamo i turni&lt;br /&gt;
  per entrare nella critical section il need opposto deve essere diventato falso&lt;br /&gt;
&lt;br /&gt;
  mutex                         OK&lt;br /&gt;
  deadlock                      OK&lt;br /&gt;
  starvation                    OK&lt;br /&gt;
  non unnecessary delay         OK&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
needP = false;&lt;br /&gt;
needQ = false;&lt;br /&gt;
turn = P&lt;br /&gt;
&lt;br /&gt;
process P:&lt;br /&gt;
  while(1)&lt;br /&gt;
    needP = true&lt;br /&gt;
    while (needQ)&lt;br /&gt;
      if (turn != P) {&lt;br /&gt;
        needP = false;&lt;br /&gt;
        while ( turn != P)&lt;br /&gt;
        needP = true;&lt;br /&gt;
    }&lt;br /&gt;
    /*critical code*/&lt;br /&gt;
    turn = Q&lt;br /&gt;
    needP = false&lt;br /&gt;
    /*non-critical code*/&lt;br /&gt;
&lt;br /&gt;
process P:&lt;br /&gt;
  while(1)&lt;br /&gt;
    needQ = true&lt;br /&gt;
    while (needP)&lt;br /&gt;
      if (turn != Q) {&lt;br /&gt;
        needQ = false;&lt;br /&gt;
        while ( turn != Q)&lt;br /&gt;
        needQ = true;&lt;br /&gt;
    }&lt;br /&gt;
    /*critical code*/&lt;br /&gt;
    turn = P&lt;br /&gt;
    needQ = false&lt;br /&gt;
    /*non-critical code*/&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Lezione del 13 ottobre 2017 ==&lt;br /&gt;
== Lezione del 17 ottobre 2017 ==&lt;br /&gt;
== Lezione del 20 ottobre 2017 ==&lt;br /&gt;
== Lezione del 24 ottobre 2017 ==&lt;br /&gt;
== Lezione del 27 ottobre 2017 ==&lt;br /&gt;
== Lezione del 31 ottobre 2017 ==&lt;br /&gt;
== Lezione del 3 novembre 2017 ==&lt;br /&gt;
== Lezione del 7 novembre 2017 ==&lt;br /&gt;
== Lezione del 10 novembre 2017 ==&lt;br /&gt;
== Lezione del 14 novembre 2017 ==&lt;br /&gt;
== Lezione del 17 novembre 2017 ==&lt;br /&gt;
== Lezione del 21 novembre 2017 ==&lt;br /&gt;
== Lezione del 24 novembre 2017 ==&lt;br /&gt;
== Lezione del 28 novembre 2017 ==&lt;br /&gt;
== Lezione del 1 dicembre 2017 ==&lt;br /&gt;
== Lezione del 5 dicembre 2017 ==&lt;br /&gt;
== Lezione del 12 dicembre 2017 ==&lt;br /&gt;
== Lezione del 15 dicembre 2017 ==&lt;/div&gt;</summary>
		<author><name>Matt</name></author>
	</entry>
	<entry>
		<id>https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_I_semestre&amp;diff=2005</id>
		<title>Lezioni Anno Accademico 2017/18 I semestre</title>
		<link rel="alternate" type="text/html" href="https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_I_semestre&amp;diff=2005"/>
		<updated>2017-10-04T15:54:11Z</updated>

		<summary type="html">&lt;p&gt;Matt: /* Lezione del 3 ottobre 2017 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;''scrivete qui idee, riassunti dei concetti espressi, commenti approfondimenti sulle lezioni.''&lt;br /&gt;
&lt;br /&gt;
== Lezione del 26 settembre 2017 ==&lt;br /&gt;
'''Titolo della lezione: W&amp;amp;#x2074; H Y'''&lt;br /&gt;
&lt;br /&gt;
* What:&lt;br /&gt;
* Who:&lt;br /&gt;
----&lt;br /&gt;
Noi studenti ed &amp;quot;entrambi&amp;quot; i professori.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* When:&lt;br /&gt;
----&lt;br /&gt;
Il corso ha durata annuale. Verrà svolto ogni martedì e venerdì dalle 15:30 alle 18:30.&lt;br /&gt;
* La lezione del martedì sarà dedicata alla programmazione concorrente;&lt;br /&gt;
* La lezione del venerdì sarà dedicata alla parte generale. Terminerà circa 15 minuti prima dell'orario stabilito.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* Where:&lt;br /&gt;
----&lt;br /&gt;
Sempre in Aula 1 Ercolani (E1): DISI, Scuole Ercolani, Mura Anteo Zamboni 2B.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* How:&lt;br /&gt;
----&lt;br /&gt;
Il corso si svolgerà tramite:&lt;br /&gt;
* Lezioni frontali in aula.&lt;br /&gt;
* Attività di laboratorio.&lt;br /&gt;
* Esercitazioni.&lt;br /&gt;
&lt;br /&gt;
Inoltre, durante l'ultimo periodo di lezioni del secondo semestre, vi sarà un periodo di ripasso del programma tramite esercizi, in vista dell'esame.&lt;br /&gt;
Durante la lezione del 3/10 sono state indicate alcune propedeuticità del corso. tra cui: programmazione, algoritmi e architettura degli elaboratori in primis.&lt;br /&gt;
Importanti sono anche i corsi di Analisi matematica e Algebra e geometria. Per farla breve, il primo anno è propedeutico al secondo.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* Why:&lt;br /&gt;
----&lt;br /&gt;
* '''Fonti e strumenti del corso:''' wiki creata di gruppo, lezioni frontali e esercitazioni.&lt;br /&gt;
**Non esiste un vero e proprio testo consigliato. Tutte le informazioni sono date DURANTE il corso.&lt;br /&gt;
** Per domande specifiche scrivere su mailing list (so@cs.unibo.it);&lt;br /&gt;
** Per esercizi, appunti e programma svolto rivolgersi al wiki (so.v2.cs.unibo.it);&lt;br /&gt;
** Per live streaming (www.cs.unibo.it/~renzo/live/).&lt;br /&gt;
* '''Modalit&amp;amp;agrave; di esame:''' esame scritto (diviso in due parti: una parte generale e una di programmazione concorrente) + progetto + orale (facoltativo), possibilità di dare solo lo scritto per ottenere un massimo di 18. La modalità per studenti in &amp;quot;difficoltà&amp;quot; è disponibile solo fino agli appelli autunnali.&lt;br /&gt;
** Si parlerà del progetto indicativamente a partire da Dicembre 2017.&lt;br /&gt;
** L'orale può essere sostenuto da chi vuole migliorare (peggiorare) il voto ottenuto, o da chi vuole ottenere la lode.&lt;br /&gt;
* '''Orario di ricevimento per il primo semestre (fino al 15/09/17):''' martedì alle 11:30.&lt;br /&gt;
* Universit&amp;amp;agrave; = docenti + studenti.&lt;br /&gt;
* Informatica = come generare informazione automatica.&lt;br /&gt;
* Hardware, Software, Elaborazione, Comunicazione, Memorizzazione, Digitale/Analogico.&lt;br /&gt;
La principale distinzione che facciamo tra '''dato''' e '''informazione''' è che il dato, da solo, è privo di significato. Se, tuttavia, viene interpretato in un particolare contesto allora può diventare informazione significativa per chi sta interpretando i dati.&lt;br /&gt;
&lt;br /&gt;
Un '''algoritmo''' è una o più sequenze non ambigue di passi che, dato un problema, ci permette di risolverlo. &lt;br /&gt;
&lt;br /&gt;
Il nostro algoritmo, scritto in un qualche linguaggio formale, diventa un '''programma''' (sostanzialmente un testo, un insieme di istruzioni).&lt;br /&gt;
&lt;br /&gt;
Un linguaggio è un entità software, ed è definito come una quadrupla (alfabeto, sintassi, lessico, semantica) dove:&lt;br /&gt;
*l'''alfabeto'' è l'insieme di simboli che compone il linguaggio.&lt;br /&gt;
*il ''lessico'' si può vedere come una funzione che va dai simboli a un booleano e ci dice quali sono le frasi ben formate.&lt;br /&gt;
*la ''sintassi'' determina quali delle sequenze scritte sono effettivamente corrette.&lt;br /&gt;
*la ''semantica'' associa un significato alle parole ben formate.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 29 settembre 2017 ==&lt;br /&gt;
'''Cos'è un sistema operativo e un po' di storia'''&lt;br /&gt;
&lt;br /&gt;
Un ''sistema operativo'' è un programma che gestisce i processi, le risorse e interfaccia le applicazioni con l'hardware dell'elaboratore.&lt;br /&gt;
&lt;br /&gt;
In particolare, il sistema operativo (laddove esiste) è il primo processo ad essere attivato e resta in vita fino allo spegnimento del calcolatore o al sopraggiungere di un errore fatale per il sistema.&lt;br /&gt;
&lt;br /&gt;
A cosa serve un sistema operativo?&lt;br /&gt;
Principalmente, ha i seguenti scopi:&lt;br /&gt;
# Facilitare l'utilizzo del sistema (senza S.O. sarebbe a carico del programmatore conoscere tutti i linguaggi con cui si comunica con l'architettura della macchina);&lt;br /&gt;
# Rendere affidabile, protetto e sicuro l'utilizzo del sistema (e.g. un processo potrebbe recar danno all'intero sistema se non controllato o gestito, potrebbe ignorare i permessi di visualizzazione di un file);&lt;br /&gt;
# Astrarre l'hardware (e.g. filesystem);&lt;br /&gt;
# Garantire l'efficienza (e.g. non tenere la CPU in idle adottando opportuni algoritmi di scheduling);&lt;br /&gt;
# Assicurare portabilità ;&lt;br /&gt;
&lt;br /&gt;
Un approccio molto usato è il cosiddetto approccio a strati (&amp;quot;a livelli&amp;quot;) in cui ogni strato utilizza i servizi forniti al livello inferiore e ne fornisce di nuovi a quello superiore.&lt;br /&gt;
Astraendo il nostro calcolatore possiamo piazzare al livello più basso l'hardware e, subito sopra, il sistema operativo. I due layer comunicano usando il linguaggio ISA (Instruction Set Architecture), nativo della CPU stessa, al quale si aggiungono quelli che permettono di comunicare con i vari controllori dei dispositivi come la scheda di rete, la stampante, etc. Sopra il livello del sistema operativo possiamo collocare quello delle librerie e, infine, quello degli applicativi.&lt;br /&gt;
&lt;br /&gt;
* '''Systemcall''' = meccanismo usato da un processo per richiedere al SO una qualche funzionalità a livello kernel. Un esempio è la funzione printf() del linguaggio C (stampa a video) che utilizza la systemcall write() (una delle systemcall per la gestione dei dispositivi).&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
----&lt;br /&gt;
'''Storia dei sistemi operativi'''&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''zero''' (1800 ca.) possiamo includere Babbage con la sua macchina analitica e lady Ada Lovelace. [[File:290px-AnalyticalMachine Babbage London.jpg |200px|thumb|right|Macchina analitica di Babbage]]&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''uno''' vediamo la comparsa delle valvole con tutti i problemi connessi. Non c'erano utilizzatori delle macchine, le stesse persone che le costruivano erano anche programmatori e fruitori delle stesse.&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''due''' (fine anni '60) arriva il transistor. Quest'ultimo è molto più veloce e piccolo della valvola e, soprattutto, meno incline a guasti. Le macchine iniziano ad essere più economiche grazie alla possibilità di avere un'economia di scala e quindi accessibili al grande pubblico. Questo fa si che, in queste prime fasi, i costruttori non siano più gli unici utilizzatori. [[File:Transistors.jpg |300px|thumb|right|Diversi tipi di Transistor]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
A questo punto poiché, pur essendo diventata più accessibile, una macchina costava ancora molto, nasce l'esigenza di non lasciare mai un processore senza lavoro (al fine di massimizzare i ricavi) e di condividere i dati.&lt;br /&gt;
In questa fase, infatti, abbiamo sistemi operativi di tipo ''batch'' (che collezionano tutto l'input all'inizio per calcolare e restituire l'output) dove l'inserimento di programmi e dati veniva fatto tramite schede perforate e non c'era interazione. Dai sistemi batch nasce lo ''SPOOL'' (Simultaneous Peripheral Operations On-Line).&lt;br /&gt;
&lt;br /&gt;
Si comincia a pensare di utilizzare i tempi di I/O per poter eseguire altri processi. Questo però solleva almeno due problematiche: da un lato serve un modo per sapere quando un input è davvero finito mentre, dall'altro, bisogna fare in modo che un processo che non richieda I/O non occupi la CPU per un tempo indefinito.&lt;br /&gt;
Per la prima problematica la soluzione è rappresentata dall'introduzione degli '''interrupt''', segnali elettrici inviati alla CPU alla fine di ogni input.&lt;br /&gt;
Per la seconda, invece, è stato introdotto il cosiddetto '''interval timer''' che non è altro che un dispositivo che invia interrupt dopo un determinato quanto di tempo assicurando che un processo non occupi per troppo tempo il processore.&lt;br /&gt;
Questo consente di realizzare sistemi time sharing (il cui più semplice algoritmo è il round-robin) dove un processo può essere in uno dei seguenti tre stati:&lt;br /&gt;
*'''READY''' pronto per essere eseguito ma, il processore è già occupato;&lt;br /&gt;
*'''RUNNING''' in esecuzione;&lt;br /&gt;
*'''WAIT''' in attesa di I/O.&lt;br /&gt;
&lt;br /&gt;
[[File:Dds.png]]&lt;br /&gt;
&lt;br /&gt;
Nella '''quarta''' generazione (anni '70 ca.) vediamo la nascita di molte innovazioni importanti. Si riesce a rimpicciolire di molto i processori facendoli divenire a tutti gli effetti micro-processori. Nei laboratori Bell nasce Unix, un sistema operativo rivoluzionario con time sharing e clonazione dei processi (un processo non viene mai creato dal nulla ma viene prima clonato da uno già esistente e poi, alla copia, viene fatto eseguire un programma). A causa di problemi di portabilità da PDP-9, macchina su cui Unix è nato, a PDP-11 nasce il linguaggio C e il suo compilatore (scritto anch'esso in C).&lt;br /&gt;
Mentre Apple I &amp;quot;porta&amp;quot; una nuova idea di pc per tutte le famiglie, nascono molte versioni di Unix.&lt;br /&gt;
Per impedire una deriva proprietaria in cui ogni produttore offriva tutto quello che offrivano gli altri ma con qualche feature in più, Richard Stallman fonda il progetto GNU (GNU's Not Unix) per il quale scrive tutta una serie di utilities come il famoso compilatore gcc.&lt;br /&gt;
A GNU, per essere operativo, manca però un kernel. Non volendo aspettare i tempi di sviluppo del kernel che Stallman aveva in mente, Linus Torlvalds scrive il kernel '''Linux''' da cui nasce il sistema operativo GNU/Linux.&lt;br /&gt;
&lt;br /&gt;
Nei tempi più recenti nascono i processori multi-core.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 3 ottobre 2017 ==&lt;br /&gt;
 &lt;br /&gt;
'''Introduzione alla programmazione concorrente: processi, thread e  librerie'''&lt;br /&gt;
&lt;br /&gt;
Quando parliamo di ''sistema concorrente'', ci riferiamo ad un sistema in cui due o più &amp;quot;attori&amp;quot; sono eseguiti parallelamente.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Questi attori possono essere '''processi''' o '''thread'''.&lt;br /&gt;
La differenza sostanziale tra queste due tipologie di attori riguarda la condivisione della memoria e dei dati su cui operano.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Più processi possono eseguire lo stesso programma ma, ognuno di essi, ha i propri dati su cui operare e il suo stato di avanzamento.&amp;lt;br&amp;gt;&lt;br /&gt;
Più thread che vengono eseguiti parallelamente, invece, condividono gli stessi dati su cui operare.&amp;lt;br&amp;gt;&lt;br /&gt;
Codici di esempio:&lt;br /&gt;
&lt;br /&gt;
'''processi'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=C&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;unistd.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int main(int argc, char *argv[]){&lt;br /&gt;
   if(fork()) printf(&amp;quot;Yes&amp;quot;);&lt;br /&gt;
   else printf(&amp;quot;No&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
L'output generato sarà &amp;quot;YesNo&amp;quot; poiché, usando la systemcall ''fork()'', abbiamo creato una nuova copia del processo (un processo figlio). Il processo padre, che ha eseguito con successo la fork(), stamperà &amp;quot;Yes&amp;quot; mentre il figlio stamperà &amp;quot;No&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
'''thread'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=C&amp;gt;&lt;br /&gt;
#include &amp;lt;pthread.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
#include &amp;lt;assert.h&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
#define NUM_THREADS     5&lt;br /&gt;
#define MAX 1000000&lt;br /&gt;
&lt;br /&gt;
int sum;&lt;br /&gt;
 &lt;br /&gt;
void* perform_work(){&lt;br /&gt;
	int i;&lt;br /&gt;
	&lt;br /&gt;
	for(i = 0; i &amp;lt; MAX; i++) sum = sum + 1;&lt;br /&gt;
  	/* optionally: insert more useful stuff here */ &lt;br /&gt;
  	return NULL;&lt;br /&gt;
}&lt;br /&gt;
 &lt;br /&gt;
int main( int argc, char** argv ){&lt;br /&gt;
	pthread_t threads[NUM_THREADS];&lt;br /&gt;
  	int thread_args[NUM_THREADS];&lt;br /&gt;
  	int result_code;&lt;br /&gt;
  	unsigned index;&lt;br /&gt;
 &lt;br /&gt;
	sum = 0;&lt;br /&gt;
  	// create all threads one by one&lt;br /&gt;
  	for(index = 0; index &amp;lt; NUM_THREADS; ++index ){&lt;br /&gt;
    		thread_args[index] = index;&lt;br /&gt;
    		printf(&amp;quot;In main: creating thread %d\n&amp;quot;, index);&lt;br /&gt;
    		result_code = pthread_create(&amp;amp;threads[index], NULL, perform_work, NULL);&lt;br /&gt;
    		assert(!result_code);&lt;br /&gt;
  	} &lt;br /&gt;
  	// wait for each thread to complete&lt;br /&gt;
  	for(index = 0; index &amp;lt; NUM_THREADS; ++index ){&lt;br /&gt;
    		// block until thread 'index' completes&lt;br /&gt;
    		result_code = pthread_join(threads[index], NULL);&lt;br /&gt;
    		assert(!result_code);&lt;br /&gt;
    		printf(&amp;quot;In main: thread %d has completed\n&amp;quot;, index);&lt;br /&gt;
   	}&lt;br /&gt;
 &lt;br /&gt;
   	printf(&amp;quot;In main: All threads completed successfully\n&amp;quot; );&lt;br /&gt;
	printf(&amp;quot;Result: %d\n&amp;quot;, sum);&lt;br /&gt;
   	exit( EXIT_SUCCESS );&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Prima di discutere l'output generato dall'esecuzione di questo codice, è opportuno introdurre, brevemente, gli strumenti utilizzati.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
'''Pthread (POSIX thread)'''&lt;br /&gt;
&lt;br /&gt;
Citazione dal Silberschatz, Galvin, Gagne:&lt;br /&gt;
''Pthread si riferisce allo standard POSIX (IEEE 1003.1c) che definisce una API per la creazione e sincronizzazione dei thread.''&lt;br /&gt;
&lt;br /&gt;
Le istruzioni fondamentali sono:&lt;br /&gt;
* '''int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);''' che crea un thread;&lt;br /&gt;
* '''int pthread_join(pthread_t thread, void **retval);''' che aspetta che un thread sia completato prima di continuare l'esecuzione.&lt;br /&gt;
&lt;br /&gt;
Da ricordare che, per utilizzare pthread, va incluso l'header ''&amp;lt;pthread.h&amp;gt;'' e durante la compilazione va inclusa la libreria esterna tramite l'opzione ''-pthread''.&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
Torniamo all'output generato dall'esecuzione del secondo codice.&amp;lt;br&amp;gt;&lt;br /&gt;
Nel codice vengono generati cinque thread ed ognuno somma MAX (=1000000) volte 1 a sum. Ci aspettiamo un output di 5000000 ma, in realtà, viene stampato un valore molto minore.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Questo accade principalmente per due motivi:&lt;br /&gt;
*sommare una costante ad una variabile non è un'operazione atomica (richiede almeno tre istruzioni assembler: due mov ed una sum).&lt;br /&gt;
*l'interval timer può fermare l'esecuzione del thread mentre sta effettuando una delle tre operazioni richieste per aggiungere uno a sum e provocare un risultato errato.&lt;br /&gt;
&lt;br /&gt;
Questo è uno dei possibili problemi della programmazione concorrente conosciuto come ''race condition''.&amp;lt;br&amp;gt;&lt;br /&gt;
Un sistema di processi multipli presenta una '''race condition''' qualora il risultato finale dell'esecuzione dipenda dalla temporizzazione, o dall'ordine con cui i processi vengono eseguiti.&lt;br /&gt;
&lt;br /&gt;
Per risolvere questo problema possiamo adoperare ancora una volta pthread.&lt;br /&gt;
Nello specifico, ci venogono offerte istruzione per realizzare una '''mutua esclusione''' sulle risorse condivise in modo che l'operazione che prima non era atomiche,ora, lo sia e non porti più ad una computazione errata.&amp;lt;br&amp;gt;&lt;br /&gt;
Codice di esempio:&amp;lt;br&amp;gt;&lt;br /&gt;
'''Mutex'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=C&amp;gt;&lt;br /&gt;
#include &amp;lt;pthread.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
#include &amp;lt;assert.h&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
#define NUM_THREADS     5&lt;br /&gt;
#define MAX 1000000&lt;br /&gt;
&lt;br /&gt;
int sum;&lt;br /&gt;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;&lt;br /&gt;
 &lt;br /&gt;
void* perform_work(){&lt;br /&gt;
	int i;&lt;br /&gt;
	&lt;br /&gt;
	for(i = 0; i &amp;lt; MAX; i++){ &lt;br /&gt;
		pthread_mutex_lock(&amp;amp;mutex);		&lt;br /&gt;
		sum = sum + 1;&lt;br /&gt;
		pthread_mutex_unlock(&amp;amp;mutex);&lt;br /&gt;
	}&lt;br /&gt;
  	/* optionally: insert more useful stuff here */ &lt;br /&gt;
  	return NULL;&lt;br /&gt;
}&lt;br /&gt;
 &lt;br /&gt;
int main( int argc, char** argv ){&lt;br /&gt;
	pthread_t threads[NUM_THREADS];&lt;br /&gt;
  	int thread_args[NUM_THREADS];&lt;br /&gt;
  	int result_code;&lt;br /&gt;
  	unsigned index;&lt;br /&gt;
&lt;br /&gt;
	sum = 0;&lt;br /&gt;
  	// create all threads one by one&lt;br /&gt;
  	for(index = 0; index &amp;lt; NUM_THREADS; ++index ){&lt;br /&gt;
    		thread_args[index] = index;&lt;br /&gt;
    		printf(&amp;quot;In main: creating thread %d\n&amp;quot;, index);&lt;br /&gt;
    		result_code = pthread_create(&amp;amp;threads[index], NULL, perform_work, NULL);&lt;br /&gt;
    		assert(!result_code);&lt;br /&gt;
  	} &lt;br /&gt;
  	// wait for each thread to complete&lt;br /&gt;
  	for(index = 0; index &amp;lt; NUM_THREADS; ++index ){&lt;br /&gt;
    		// block until thread 'index' completes&lt;br /&gt;
    		result_code = pthread_join(threads[index], NULL);&lt;br /&gt;
    		assert(!result_code);&lt;br /&gt;
    		printf(&amp;quot;In main: thread %d has completed\n&amp;quot;, index);&lt;br /&gt;
   	}&lt;br /&gt;
 &lt;br /&gt;
   	printf(&amp;quot;In main: All threads completed successfully\n&amp;quot; );&lt;br /&gt;
	printf(&amp;quot;Result: %d\n&amp;quot;, sum);&lt;br /&gt;
   	exit( EXIT_SUCCESS );&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
'''MUTEX'''&lt;br /&gt;
&lt;br /&gt;
Un mutex è un dispositivo di MUTual EXclusion (mutua esclusione), ed è utile per proteggere le strutture dati condivise da modifiche concorrenti, implementare sezioni critiche e monitor.&lt;br /&gt;
&lt;br /&gt;
Un mutex ha due possibili stati: sbloccato (non posseduto da nessun thread) e bloccato (posseduto da un thread). Un mutex non può mai essere posseduto da due thread contemporaneamente.&amp;lt;br&amp;gt;Se un thread provasse ad acquisire il mutex, mentre quest'ultimo è già in possesso di un altro thread, dovrebbe aspettare finché non viene rilasciato.&lt;br /&gt;
La variabile che realizza il mutex, di tipo ''pthread_mutex_t'', viene inizializzata staticamente assegnandogli PTHREAD_MUTEX_INITIALIZER.&lt;br /&gt;
'''pthread_mutex_lock''' acquisisce immediatamente il mutex, se libero. Altrimenti aspetta fino a trovarla, prima o poi, libera.&amp;lt;br&amp;gt;&lt;br /&gt;
'''pthread_mutex_unlock''' rilascia il mutex.&lt;br /&gt;
&lt;br /&gt;
[da: https://sourceware.org/pthreads-win32/manual/pthread_mutex_init.html]&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
Con questa soluzione abbiamo raggiunto un risultato esatto pur avendo pagato in prestazioni: la computazione infatti è più lenta a causa del tempo richiesto dalla sincronizzazione tra i thread.&lt;br /&gt;
&lt;br /&gt;
Consideriamo ora due scenari leggermente più complicati:&lt;br /&gt;
* nel primo abbiamo due processi, A e B, che devono accedere a due risorse, X e Y, contemporaneamente prima di poter terminare. Cosa succede se, mentre A acquisisce X, B acquisisce Y?&lt;br /&gt;
* nel secondo abbiamo tre processi, A, B e C, che devono accedere alla risorsa X. Che succede se A e B, essendo più veloci, occupano insistentemente la risorsa e non permettono a C di utilizzarla?&lt;br /&gt;
&lt;br /&gt;
== Lezione del 6 ottobre 2017 ==&lt;br /&gt;
== Lezione del 10 ottobre 2017 ==&lt;br /&gt;
== Lezione del 13 ottobre 2017 ==&lt;br /&gt;
== Lezione del 17 ottobre 2017 ==&lt;br /&gt;
== Lezione del 20 ottobre 2017 ==&lt;br /&gt;
== Lezione del 24 ottobre 2017 ==&lt;br /&gt;
== Lezione del 27 ottobre 2017 ==&lt;br /&gt;
== Lezione del 31 ottobre 2017 ==&lt;br /&gt;
== Lezione del 3 novembre 2017 ==&lt;br /&gt;
== Lezione del 7 novembre 2017 ==&lt;br /&gt;
== Lezione del 10 novembre 2017 ==&lt;br /&gt;
== Lezione del 14 novembre 2017 ==&lt;br /&gt;
== Lezione del 17 novembre 2017 ==&lt;br /&gt;
== Lezione del 21 novembre 2017 ==&lt;br /&gt;
== Lezione del 24 novembre 2017 ==&lt;br /&gt;
== Lezione del 28 novembre 2017 ==&lt;br /&gt;
== Lezione del 1 dicembre 2017 ==&lt;br /&gt;
== Lezione del 5 dicembre 2017 ==&lt;br /&gt;
== Lezione del 12 dicembre 2017 ==&lt;br /&gt;
== Lezione del 15 dicembre 2017 ==&lt;/div&gt;</summary>
		<author><name>Matt</name></author>
	</entry>
	<entry>
		<id>https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_I_semestre&amp;diff=2004</id>
		<title>Lezioni Anno Accademico 2017/18 I semestre</title>
		<link rel="alternate" type="text/html" href="https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_I_semestre&amp;diff=2004"/>
		<updated>2017-10-04T15:23:51Z</updated>

		<summary type="html">&lt;p&gt;Matt: /* Lezione del 3 ottobre 2017 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;''scrivete qui idee, riassunti dei concetti espressi, commenti approfondimenti sulle lezioni.''&lt;br /&gt;
&lt;br /&gt;
== Lezione del 26 settembre 2017 ==&lt;br /&gt;
'''Titolo della lezione: W&amp;amp;#x2074; H Y'''&lt;br /&gt;
&lt;br /&gt;
* What:&lt;br /&gt;
* Who:&lt;br /&gt;
----&lt;br /&gt;
Noi studenti ed &amp;quot;entrambi&amp;quot; i professori.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* When:&lt;br /&gt;
----&lt;br /&gt;
Il corso ha durata annuale. Verrà svolto ogni martedì e venerdì dalle 15:30 alle 18:30.&lt;br /&gt;
* La lezione del martedì sarà dedicata alla programmazione concorrente;&lt;br /&gt;
* La lezione del venerdì sarà dedicata alla parte generale. Terminerà circa 15 minuti prima dell'orario stabilito.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* Where:&lt;br /&gt;
----&lt;br /&gt;
Sempre in Aula 1 Ercolani (E1): DISI, Scuole Ercolani, Mura Anteo Zamboni 2B.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* How:&lt;br /&gt;
----&lt;br /&gt;
Il corso si svolgerà tramite:&lt;br /&gt;
* Lezioni frontali in aula.&lt;br /&gt;
* Attività di laboratorio.&lt;br /&gt;
* Esercitazioni.&lt;br /&gt;
&lt;br /&gt;
Inoltre, durante l'ultimo periodo di lezioni del secondo semestre, vi sarà un periodo di ripasso del programma tramite esercizi, in vista dell'esame.&lt;br /&gt;
Durante la lezione del 3/10 sono state indicate alcune propedeuticità del corso. tra cui: programmazione, algoritmi e architettura degli elaboratori in primis.&lt;br /&gt;
Importanti sono anche i corsi di Analisi matematica e Algebra e geometria. Per farla breve, il primo anno è propedeutico al secondo.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* Why:&lt;br /&gt;
----&lt;br /&gt;
* '''Fonti e strumenti del corso:''' wiki creata di gruppo, lezioni frontali e esercitazioni.&lt;br /&gt;
**Non esiste un vero e proprio testo consigliato. Tutte le informazioni sono date DURANTE il corso.&lt;br /&gt;
** Per domande specifiche scrivere su mailing list (so@cs.unibo.it);&lt;br /&gt;
** Per esercizi, appunti e programma svolto rivolgersi al wiki (so.v2.cs.unibo.it);&lt;br /&gt;
** Per live streaming (www.cs.unibo.it/~renzo/live/).&lt;br /&gt;
* '''Modalit&amp;amp;agrave; di esame:''' esame scritto (diviso in due parti: una parte generale e una di programmazione concorrente) + progetto + orale (facoltativo), possibilità di dare solo lo scritto per ottenere un massimo di 18. La modalità per studenti in &amp;quot;difficoltà&amp;quot; è disponibile solo fino agli appelli autunnali.&lt;br /&gt;
** Si parlerà del progetto indicativamente a partire da Dicembre 2017.&lt;br /&gt;
** L'orale può essere sostenuto da chi vuole migliorare (peggiorare) il voto ottenuto, o da chi vuole ottenere la lode.&lt;br /&gt;
* '''Orario di ricevimento per il primo semestre (fino al 15/09/17):''' martedì alle 11:30.&lt;br /&gt;
* Universit&amp;amp;agrave; = docenti + studenti.&lt;br /&gt;
* Informatica = come generare informazione automatica.&lt;br /&gt;
* Hardware, Software, Elaborazione, Comunicazione, Memorizzazione, Digitale/Analogico.&lt;br /&gt;
La principale distinzione che facciamo tra '''dato''' e '''informazione''' è che il dato, da solo, è privo di significato. Se, tuttavia, viene interpretato in un particolare contesto allora può diventare informazione significativa per chi sta interpretando i dati.&lt;br /&gt;
&lt;br /&gt;
Un '''algoritmo''' è una o più sequenze non ambigue di passi che, dato un problema, ci permette di risolverlo. &lt;br /&gt;
&lt;br /&gt;
Il nostro algoritmo, scritto in un qualche linguaggio formale, diventa un '''programma''' (sostanzialmente un testo, un insieme di istruzioni).&lt;br /&gt;
&lt;br /&gt;
Un linguaggio è un entità software, ed è definito come una quadrupla (alfabeto, sintassi, lessico, semantica) dove:&lt;br /&gt;
*l'''alfabeto'' è l'insieme di simboli che compone il linguaggio.&lt;br /&gt;
*il ''lessico'' si può vedere come una funzione che va dai simboli a un booleano e ci dice quali sono le frasi ben formate.&lt;br /&gt;
*la ''sintassi'' determina quali delle sequenze scritte sono effettivamente corrette.&lt;br /&gt;
*la ''semantica'' associa un significato alle parole ben formate.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 29 settembre 2017 ==&lt;br /&gt;
'''Cos'è un sistema operativo e un po' di storia'''&lt;br /&gt;
&lt;br /&gt;
Un ''sistema operativo'' è un programma che gestisce i processi, le risorse e interfaccia le applicazioni con l'hardware dell'elaboratore.&lt;br /&gt;
&lt;br /&gt;
In particolare, il sistema operativo (laddove esiste) è il primo processo ad essere attivato e resta in vita fino allo spegnimento del calcolatore o al sopraggiungere di un errore fatale per il sistema.&lt;br /&gt;
&lt;br /&gt;
A cosa serve un sistema operativo?&lt;br /&gt;
Principalmente, ha i seguenti scopi:&lt;br /&gt;
# Facilitare l'utilizzo del sistema (senza S.O. sarebbe a carico del programmatore conoscere tutti i linguaggi con cui si comunica con l'architettura della macchina);&lt;br /&gt;
# Rendere affidabile, protetto e sicuro l'utilizzo del sistema (e.g. un processo potrebbe recar danno all'intero sistema se non controllato o gestito, potrebbe ignorare i permessi di visualizzazione di un file);&lt;br /&gt;
# Astrarre l'hardware (e.g. filesystem);&lt;br /&gt;
# Garantire l'efficienza (e.g. non tenere la CPU in idle adottando opportuni algoritmi di scheduling);&lt;br /&gt;
# Assicurare portabilità ;&lt;br /&gt;
&lt;br /&gt;
Un approccio molto usato è il cosiddetto approccio a strati (&amp;quot;a livelli&amp;quot;) in cui ogni strato utilizza i servizi forniti al livello inferiore e ne fornisce di nuovi a quello superiore.&lt;br /&gt;
Astraendo il nostro calcolatore possiamo piazzare al livello più basso l'hardware e, subito sopra, il sistema operativo. I due layer comunicano usando il linguaggio ISA (Instruction Set Architecture), nativo della CPU stessa, al quale si aggiungono quelli che permettono di comunicare con i vari controllori dei dispositivi come la scheda di rete, la stampante, etc. Sopra il livello del sistema operativo possiamo collocare quello delle librerie e, infine, quello degli applicativi.&lt;br /&gt;
&lt;br /&gt;
* '''Systemcall''' = meccanismo usato da un processo per richiedere al SO una qualche funzionalità a livello kernel. Un esempio è la funzione printf() del linguaggio C (stampa a video) che utilizza la systemcall write() (una delle systemcall per la gestione dei dispositivi).&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
----&lt;br /&gt;
'''Storia dei sistemi operativi'''&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''zero''' (1800 ca.) possiamo includere Babbage con la sua macchina analitica e lady Ada Lovelace. [[File:290px-AnalyticalMachine Babbage London.jpg |200px|thumb|right|Macchina analitica di Babbage]]&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''uno''' vediamo la comparsa delle valvole con tutti i problemi connessi. Non c'erano utilizzatori delle macchine, le stesse persone che le costruivano erano anche programmatori e fruitori delle stesse.&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''due''' (fine anni '60) arriva il transistor. Quest'ultimo è molto più veloce e piccolo della valvola e, soprattutto, meno incline a guasti. Le macchine iniziano ad essere più economiche grazie alla possibilità di avere un'economia di scala e quindi accessibili al grande pubblico. Questo fa si che, in queste prime fasi, i costruttori non siano più gli unici utilizzatori. [[File:Transistors.jpg |300px|thumb|right|Diversi tipi di Transistor]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
A questo punto poiché, pur essendo diventata più accessibile, una macchina costava ancora molto, nasce l'esigenza di non lasciare mai un processore senza lavoro (al fine di massimizzare i ricavi) e di condividere i dati.&lt;br /&gt;
In questa fase, infatti, abbiamo sistemi operativi di tipo ''batch'' (che collezionano tutto l'input all'inizio per calcolare e restituire l'output) dove l'inserimento di programmi e dati veniva fatto tramite schede perforate e non c'era interazione. Dai sistemi batch nasce lo ''SPOOL'' (Simultaneous Peripheral Operations On-Line).&lt;br /&gt;
&lt;br /&gt;
Si comincia a pensare di utilizzare i tempi di I/O per poter eseguire altri processi. Questo però solleva almeno due problematiche: da un lato serve un modo per sapere quando un input è davvero finito mentre, dall'altro, bisogna fare in modo che un processo che non richieda I/O non occupi la CPU per un tempo indefinito.&lt;br /&gt;
Per la prima problematica la soluzione è rappresentata dall'introduzione degli '''interrupt''', segnali elettrici inviati alla CPU alla fine di ogni input.&lt;br /&gt;
Per la seconda, invece, è stato introdotto il cosiddetto '''interval timer''' che non è altro che un dispositivo che invia interrupt dopo un determinato quanto di tempo assicurando che un processo non occupi per troppo tempo il processore.&lt;br /&gt;
Questo consente di realizzare sistemi time sharing (il cui più semplice algoritmo è il round-robin) dove un processo può essere in uno dei seguenti tre stati:&lt;br /&gt;
*'''READY''' pronto per essere eseguito ma, il processore è già occupato;&lt;br /&gt;
*'''RUNNING''' in esecuzione;&lt;br /&gt;
*'''WAIT''' in attesa di I/O.&lt;br /&gt;
&lt;br /&gt;
[[File:Dds.png]]&lt;br /&gt;
&lt;br /&gt;
Nella '''quarta''' generazione (anni '70 ca.) vediamo la nascita di molte innovazioni importanti. Si riesce a rimpicciolire di molto i processori facendoli divenire a tutti gli effetti micro-processori. Nei laboratori Bell nasce Unix, un sistema operativo rivoluzionario con time sharing e clonazione dei processi (un processo non viene mai creato dal nulla ma viene prima clonato da uno già esistente e poi, alla copia, viene fatto eseguire un programma). A causa di problemi di portabilità da PDP-9, macchina su cui Unix è nato, a PDP-11 nasce il linguaggio C e il suo compilatore (scritto anch'esso in C).&lt;br /&gt;
Mentre Apple I &amp;quot;porta&amp;quot; una nuova idea di pc per tutte le famiglie, nascono molte versioni di Unix.&lt;br /&gt;
Per impedire una deriva proprietaria in cui ogni produttore offriva tutto quello che offrivano gli altri ma con qualche feature in più, Richard Stallman fonda il progetto GNU (GNU's Not Unix) per il quale scrive tutta una serie di utilities come il famoso compilatore gcc.&lt;br /&gt;
A GNU, per essere operativo, manca però un kernel. Non volendo aspettare i tempi di sviluppo del kernel che Stallman aveva in mente, Linus Torlvalds scrive il kernel '''Linux''' da cui nasce il sistema operativo GNU/Linux.&lt;br /&gt;
&lt;br /&gt;
Nei tempi più recenti nascono i processori multi-core.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 3 ottobre 2017 ==&lt;br /&gt;
 &lt;br /&gt;
'''Introduzione alla programmazione concorrente: processi, thread e  librerie'''&lt;br /&gt;
&lt;br /&gt;
Quando parliamo di ''sistema concorrente'', ci riferiamo ad un sistema in cui due o più &amp;quot;attori&amp;quot; sono eseguiti parallelamente.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Questi attori possono essere '''processi''' o '''thread'''.&lt;br /&gt;
La differenza sostanziale tra queste due tipologie di attori riguarda la condivisione della memoria e dei dati su cui operano.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Più processi possono eseguire lo stesso programma ma, ognuno di essi, ha i propri dati su cui operare e il suo stato di avanzamento.&amp;lt;br&amp;gt;&lt;br /&gt;
Più thread che vengono eseguiti parallelamente, invece, condividono gli stessi dati su cui operare.&amp;lt;br&amp;gt;&lt;br /&gt;
Codici di esempio:&lt;br /&gt;
&lt;br /&gt;
'''processi'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=C&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;unistd.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int main(int argc, char *argv[]){&lt;br /&gt;
   if(fork()) printf(&amp;quot;Yes&amp;quot;);&lt;br /&gt;
   else printf(&amp;quot;No&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
L'output generato sarà &amp;quot;YesNo&amp;quot; poiché, usando la systemcall ''fork()'', abbiamo creato una nuova copia del processo (un processo figlio). Il processo padre, che ha eseguito con successo la fork(), stamperà &amp;quot;Yes&amp;quot; mentre il figlio stamperà &amp;quot;No&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
'''thread'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=C&amp;gt;&lt;br /&gt;
#include &amp;lt;pthread.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
#include &amp;lt;assert.h&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
#define NUM_THREADS     5&lt;br /&gt;
#define MAX 1000000&lt;br /&gt;
&lt;br /&gt;
int sum;&lt;br /&gt;
 &lt;br /&gt;
void* perform_work(){&lt;br /&gt;
	int i;&lt;br /&gt;
	&lt;br /&gt;
	for(i = 0; i &amp;lt; MAX; i++) sum = sum + 1;&lt;br /&gt;
  	/* optionally: insert more useful stuff here */ &lt;br /&gt;
  	return NULL;&lt;br /&gt;
}&lt;br /&gt;
 &lt;br /&gt;
int main( int argc, char** argv ){&lt;br /&gt;
	pthread_t threads[NUM_THREADS];&lt;br /&gt;
  	int thread_args[NUM_THREADS];&lt;br /&gt;
  	int result_code;&lt;br /&gt;
  	unsigned index;&lt;br /&gt;
 &lt;br /&gt;
	sum = 0;&lt;br /&gt;
  	// create all threads one by one&lt;br /&gt;
  	for(index = 0; index &amp;lt; NUM_THREADS; ++index ){&lt;br /&gt;
    		thread_args[index] = index;&lt;br /&gt;
    		printf(&amp;quot;In main: creating thread %d\n&amp;quot;, index);&lt;br /&gt;
    		result_code = pthread_create(&amp;amp;threads[index], NULL, perform_work, NULL);&lt;br /&gt;
    		assert(!result_code);&lt;br /&gt;
  	} &lt;br /&gt;
  	// wait for each thread to complete&lt;br /&gt;
  	for(index = 0; index &amp;lt; NUM_THREADS; ++index ){&lt;br /&gt;
    		// block until thread 'index' completes&lt;br /&gt;
    		result_code = pthread_join(threads[index], NULL);&lt;br /&gt;
    		assert(!result_code);&lt;br /&gt;
    		printf(&amp;quot;In main: thread %d has completed\n&amp;quot;, index);&lt;br /&gt;
   	}&lt;br /&gt;
 &lt;br /&gt;
   	printf(&amp;quot;In main: All threads completed successfully\n&amp;quot; );&lt;br /&gt;
	printf(&amp;quot;Result: %d\n&amp;quot;, sum);&lt;br /&gt;
   	exit( EXIT_SUCCESS );&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Prima di discutere l'output generato dall'esecuzione di questo codice, è opportuno introdurre, brevemente, gli strumenti utilizzati.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
'''Pthread (POSIX thread)'''&lt;br /&gt;
&lt;br /&gt;
Citazione dal Silberschatz, Galvin, Gagne:&lt;br /&gt;
''Pthread si riferisce allo standard POSIX (IEEE 1003.1c) che definisce una API per la creazione e sincronizzazione dei thread.''&lt;br /&gt;
&lt;br /&gt;
Le istruzioni fondamentali sono:&lt;br /&gt;
* '''int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);''' che crea un thread;&lt;br /&gt;
* '''int pthread_join(pthread_t thread, void **retval);''' che aspetta che un thread sia completato prima di continuare l'esecuzione.&lt;br /&gt;
&lt;br /&gt;
Da ricordare che, per utilizzare pthread, va incluso l'header ''&amp;lt;pthread.h&amp;gt;'' e durante la compilazione va inclusa la libreria esterna tramite l'opzione ''-pthread''.&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
Torniamo all'output generato dall'esecuzione del secondo codice.&amp;lt;br&amp;gt;&lt;br /&gt;
Nel codice vengono generati cinque thread ed ognuno somma MAX (=1000000) volte 1 a sum. Ci aspettiamo un output di 5000000 ma, in realtà, viene stampato un valore molto minore.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Questo accade principalmente per due motivi:&lt;br /&gt;
*sommare una costante ad una variabile non è un'operazione atomica (richiede almeno tre istruzioni assembler: due mov ed una sum).&lt;br /&gt;
*l'interval timer può fermare l'esecuzione del thread mentre sta effettuando una delle tre operazioni richieste per aggiungere uno a sum e provocare un risultato errato.&lt;br /&gt;
&lt;br /&gt;
Questo è uno dei possibili problemi della programmazione concorrente conosciuto come ''race condition''.&amp;lt;br&amp;gt;&lt;br /&gt;
Un sistema di processi multipli presenta una '''race condition''' qualora il risultato finale dell'esecuzione dipenda dalla temporizzazione, o dall'ordine con cui i processi vengono eseguiti.&lt;br /&gt;
&lt;br /&gt;
Per risolvere questo problema possiamo adoperare ancora una volta pthread.&lt;br /&gt;
Nello specifico, ci venogono offerte istruzione per realizzare una '''mutua esclusione''' sulle risorse condivise in modo che l'operazione che prima non era atomiche,ora, lo sia e non porti più ad una computazione errata.&amp;lt;br&amp;gt;&lt;br /&gt;
Codice di esempio:&amp;lt;br&amp;gt;&lt;br /&gt;
'''Mutex'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=C&amp;gt;&lt;br /&gt;
#include &amp;lt;pthread.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
#include &amp;lt;assert.h&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
#define NUM_THREADS     5&lt;br /&gt;
#define MAX 1000000&lt;br /&gt;
&lt;br /&gt;
int sum;&lt;br /&gt;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;&lt;br /&gt;
 &lt;br /&gt;
void* perform_work(){&lt;br /&gt;
	int i;&lt;br /&gt;
	&lt;br /&gt;
	for(i = 0; i &amp;lt; MAX; i++){ &lt;br /&gt;
		pthread_mutex_lock(&amp;amp;mutex);		&lt;br /&gt;
		sum = sum + 1;&lt;br /&gt;
		pthread_mutex_unlock(&amp;amp;mutex);&lt;br /&gt;
	}&lt;br /&gt;
  	/* optionally: insert more useful stuff here */ &lt;br /&gt;
  	return NULL;&lt;br /&gt;
}&lt;br /&gt;
 &lt;br /&gt;
int main( int argc, char** argv ){&lt;br /&gt;
	pthread_t threads[NUM_THREADS];&lt;br /&gt;
  	int thread_args[NUM_THREADS];&lt;br /&gt;
  	int result_code;&lt;br /&gt;
  	unsigned index;&lt;br /&gt;
&lt;br /&gt;
	sum = 0;&lt;br /&gt;
  	// create all threads one by one&lt;br /&gt;
  	for(index = 0; index &amp;lt; NUM_THREADS; ++index ){&lt;br /&gt;
    		thread_args[index] = index;&lt;br /&gt;
    		printf(&amp;quot;In main: creating thread %d\n&amp;quot;, index);&lt;br /&gt;
    		result_code = pthread_create(&amp;amp;threads[index], NULL, perform_work, NULL);&lt;br /&gt;
    		assert(!result_code);&lt;br /&gt;
  	} &lt;br /&gt;
  	// wait for each thread to complete&lt;br /&gt;
  	for(index = 0; index &amp;lt; NUM_THREADS; ++index ){&lt;br /&gt;
    		// block until thread 'index' completes&lt;br /&gt;
    		result_code = pthread_join(threads[index], NULL);&lt;br /&gt;
    		assert(!result_code);&lt;br /&gt;
    		printf(&amp;quot;In main: thread %d has completed\n&amp;quot;, index);&lt;br /&gt;
   	}&lt;br /&gt;
 &lt;br /&gt;
   	printf(&amp;quot;In main: All threads completed successfully\n&amp;quot; );&lt;br /&gt;
	printf(&amp;quot;Result: %d\n&amp;quot;, sum);&lt;br /&gt;
   	exit( EXIT_SUCCESS );&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
'''MUTEX'''&lt;br /&gt;
&lt;br /&gt;
Un mutex è un dispositivo di MUTual EXclusion (mutua esclusione), ed è utile per proteggere le strutture dati condivise da modifiche concorrenti, implementare sezioni critiche e monitor.&lt;br /&gt;
&lt;br /&gt;
Un mutex ha due possibili stati: sbloccato (non posseduto da nessun thread) e bloccato (posseduto da un thread). Un mutex non può mai essere posseduto da due thread contemporaneamente.&amp;lt;br&amp;gt;Se un thread provasse ad acquisire il mutex, mentre quest'ultimo è già in possesso di un altro thread, dovrebbe aspettare finché non viene rilasciato.&lt;br /&gt;
La variabile che realizza il mutex, di tipo ''pthread_mutex_t'', viene inizializzata staticamente assegnandogli PTHREAD_MUTEX_INITIALIZER.&lt;br /&gt;
'''pthread_mutex_lock''' acquisisce immediatamente il mutex, se libero. Altrimenti aspetta fino a trovarla, prima o poi, libera.&amp;lt;br&amp;gt;&lt;br /&gt;
'''pthread_mutex_unlock''' rilascia il mutex.&lt;br /&gt;
&lt;br /&gt;
[da: https://sourceware.org/pthreads-win32/manual/pthread_mutex_init.html]&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
Con questa soluzione abbiamo raggiunto un risultato esatto pur avendo pagato in prestazioni: la computazione infatti è più lenta a causa del tempo richiesto dalla sincronizzazione tra i thread.&lt;br /&gt;
&lt;br /&gt;
Consideriamo ora uno scenario leggermente più complicato:&lt;br /&gt;
&lt;br /&gt;
== Lezione del 6 ottobre 2017 ==&lt;br /&gt;
== Lezione del 10 ottobre 2017 ==&lt;br /&gt;
== Lezione del 13 ottobre 2017 ==&lt;br /&gt;
== Lezione del 17 ottobre 2017 ==&lt;br /&gt;
== Lezione del 20 ottobre 2017 ==&lt;br /&gt;
== Lezione del 24 ottobre 2017 ==&lt;br /&gt;
== Lezione del 27 ottobre 2017 ==&lt;br /&gt;
== Lezione del 31 ottobre 2017 ==&lt;br /&gt;
== Lezione del 3 novembre 2017 ==&lt;br /&gt;
== Lezione del 7 novembre 2017 ==&lt;br /&gt;
== Lezione del 10 novembre 2017 ==&lt;br /&gt;
== Lezione del 14 novembre 2017 ==&lt;br /&gt;
== Lezione del 17 novembre 2017 ==&lt;br /&gt;
== Lezione del 21 novembre 2017 ==&lt;br /&gt;
== Lezione del 24 novembre 2017 ==&lt;br /&gt;
== Lezione del 28 novembre 2017 ==&lt;br /&gt;
== Lezione del 1 dicembre 2017 ==&lt;br /&gt;
== Lezione del 5 dicembre 2017 ==&lt;br /&gt;
== Lezione del 12 dicembre 2017 ==&lt;br /&gt;
== Lezione del 15 dicembre 2017 ==&lt;/div&gt;</summary>
		<author><name>Matt</name></author>
	</entry>
	<entry>
		<id>https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_I_semestre&amp;diff=2003</id>
		<title>Lezioni Anno Accademico 2017/18 I semestre</title>
		<link rel="alternate" type="text/html" href="https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_I_semestre&amp;diff=2003"/>
		<updated>2017-10-04T14:48:22Z</updated>

		<summary type="html">&lt;p&gt;Matt: /* Lezione del 3 ottobre 2017 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;''scrivete qui idee, riassunti dei concetti espressi, commenti approfondimenti sulle lezioni.''&lt;br /&gt;
&lt;br /&gt;
== Lezione del 26 settembre 2017 ==&lt;br /&gt;
'''Titolo della lezione: W&amp;amp;#x2074; H Y'''&lt;br /&gt;
&lt;br /&gt;
* What:&lt;br /&gt;
* Who:&lt;br /&gt;
----&lt;br /&gt;
Noi studenti ed &amp;quot;entrambi&amp;quot; i professori.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* When:&lt;br /&gt;
----&lt;br /&gt;
Il corso ha durata annuale. Verrà svolto ogni martedì e venerdì dalle 15:30 alle 18:30.&lt;br /&gt;
* La lezione del martedì sarà dedicata alla programmazione concorrente;&lt;br /&gt;
* La lezione del venerdì sarà dedicata alla parte generale. Terminerà circa 15 minuti prima dell'orario stabilito.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* Where:&lt;br /&gt;
----&lt;br /&gt;
Sempre in Aula 1 Ercolani (E1): DISI, Scuole Ercolani, Mura Anteo Zamboni 2B.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* How:&lt;br /&gt;
----&lt;br /&gt;
Il corso si svolgerà tramite:&lt;br /&gt;
* Lezioni frontali in aula.&lt;br /&gt;
* Attività di laboratorio.&lt;br /&gt;
* Esercitazioni.&lt;br /&gt;
&lt;br /&gt;
Inoltre, durante l'ultimo periodo di lezioni del secondo semestre, vi sarà un periodo di ripasso del programma tramite esercizi, in vista dell'esame.&lt;br /&gt;
Durante la lezione del 3/10 sono state indicate alcune propedeuticità del corso. tra cui: programmazione, algoritmi e architettura degli elaboratori in primis.&lt;br /&gt;
Importanti sono anche i corsi di Analisi matematica e Algebra e geometria. Per farla breve, il primo anno è propedeutico al secondo.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* Why:&lt;br /&gt;
----&lt;br /&gt;
* '''Fonti e strumenti del corso:''' wiki creata di gruppo, lezioni frontali e esercitazioni.&lt;br /&gt;
**Non esiste un vero e proprio testo consigliato. Tutte le informazioni sono date DURANTE il corso.&lt;br /&gt;
** Per domande specifiche scrivere su mailing list (so@cs.unibo.it);&lt;br /&gt;
** Per esercizi, appunti e programma svolto rivolgersi al wiki (so.v2.cs.unibo.it);&lt;br /&gt;
** Per live streaming (www.cs.unibo.it/~renzo/live/).&lt;br /&gt;
* '''Modalit&amp;amp;agrave; di esame:''' esame scritto (diviso in due parti: una parte generale e una di programmazione concorrente) + progetto + orale (facoltativo), possibilità di dare solo lo scritto per ottenere un massimo di 18. La modalità per studenti in &amp;quot;difficoltà&amp;quot; è disponibile solo fino agli appelli autunnali.&lt;br /&gt;
** Si parlerà del progetto indicativamente a partire da Dicembre 2017.&lt;br /&gt;
** L'orale può essere sostenuto da chi vuole migliorare (peggiorare) il voto ottenuto, o da chi vuole ottenere la lode.&lt;br /&gt;
* '''Orario di ricevimento per il primo semestre (fino al 15/09/17):''' martedì alle 11:30.&lt;br /&gt;
* Universit&amp;amp;agrave; = docenti + studenti.&lt;br /&gt;
* Informatica = come generare informazione automatica.&lt;br /&gt;
* Hardware, Software, Elaborazione, Comunicazione, Memorizzazione, Digitale/Analogico.&lt;br /&gt;
La principale distinzione che facciamo tra '''dato''' e '''informazione''' è che il dato, da solo, è privo di significato. Se, tuttavia, viene interpretato in un particolare contesto allora può diventare informazione significativa per chi sta interpretando i dati.&lt;br /&gt;
&lt;br /&gt;
Un '''algoritmo''' è una o più sequenze non ambigue di passi che, dato un problema, ci permette di risolverlo. &lt;br /&gt;
&lt;br /&gt;
Il nostro algoritmo, scritto in un qualche linguaggio formale, diventa un '''programma''' (sostanzialmente un testo, un insieme di istruzioni).&lt;br /&gt;
&lt;br /&gt;
Un linguaggio è un entità software, ed è definito come una quadrupla (alfabeto, sintassi, lessico, semantica) dove:&lt;br /&gt;
*l'''alfabeto'' è l'insieme di simboli che compone il linguaggio.&lt;br /&gt;
*il ''lessico'' si può vedere come una funzione che va dai simboli a un booleano e ci dice quali sono le frasi ben formate.&lt;br /&gt;
*la ''sintassi'' determina quali delle sequenze scritte sono effettivamente corrette.&lt;br /&gt;
*la ''semantica'' associa un significato alle parole ben formate.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 29 settembre 2017 ==&lt;br /&gt;
'''Cos'è un sistema operativo e un po' di storia'''&lt;br /&gt;
&lt;br /&gt;
Un ''sistema operativo'' è un programma che gestisce i processi, le risorse e interfaccia le applicazioni con l'hardware dell'elaboratore.&lt;br /&gt;
&lt;br /&gt;
In particolare, il sistema operativo (laddove esiste) è il primo processo ad essere attivato e resta in vita fino allo spegnimento del calcolatore o al sopraggiungere di un errore fatale per il sistema.&lt;br /&gt;
&lt;br /&gt;
A cosa serve un sistema operativo?&lt;br /&gt;
Principalmente, ha i seguenti scopi:&lt;br /&gt;
# Facilitare l'utilizzo del sistema (senza S.O. sarebbe a carico del programmatore conoscere tutti i linguaggi con cui si comunica con l'architettura della macchina);&lt;br /&gt;
# Rendere affidabile, protetto e sicuro l'utilizzo del sistema (e.g. un processo potrebbe recar danno all'intero sistema se non controllato o gestito, potrebbe ignorare i permessi di visualizzazione di un file);&lt;br /&gt;
# Astrarre l'hardware (e.g. filesystem);&lt;br /&gt;
# Garantire l'efficienza (e.g. non tenere la CPU in idle adottando opportuni algoritmi di scheduling);&lt;br /&gt;
# Assicurare portabilità ;&lt;br /&gt;
&lt;br /&gt;
Un approccio molto usato è il cosiddetto approccio a strati (&amp;quot;a livelli&amp;quot;) in cui ogni strato utilizza i servizi forniti al livello inferiore e ne fornisce di nuovi a quello superiore.&lt;br /&gt;
Astraendo il nostro calcolatore possiamo piazzare al livello più basso l'hardware e, subito sopra, il sistema operativo. I due layer comunicano usando il linguaggio ISA (Instruction Set Architecture), nativo della CPU stessa, al quale si aggiungono quelli che permettono di comunicare con i vari controllori dei dispositivi come la scheda di rete, la stampante, etc. Sopra il livello del sistema operativo possiamo collocare quello delle librerie e, infine, quello degli applicativi.&lt;br /&gt;
&lt;br /&gt;
* '''Systemcall''' = meccanismo usato da un processo per richiedere al SO una qualche funzionalità a livello kernel. Un esempio è la funzione printf() del linguaggio C (stampa a video) che utilizza la systemcall write() (una delle systemcall per la gestione dei dispositivi).&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
----&lt;br /&gt;
'''Storia dei sistemi operativi'''&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''zero''' (1800 ca.) possiamo includere Babbage con la sua macchina analitica e lady Ada Lovelace. [[File:290px-AnalyticalMachine Babbage London.jpg |200px|thumb|right|Macchina analitica di Babbage]]&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''uno''' vediamo la comparsa delle valvole con tutti i problemi connessi. Non c'erano utilizzatori delle macchine, le stesse persone che le costruivano erano anche programmatori e fruitori delle stesse.&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''due''' (fine anni '60) arriva il transistor. Quest'ultimo è molto più veloce e piccolo della valvola e, soprattutto, meno incline a guasti. Le macchine iniziano ad essere più economiche grazie alla possibilità di avere un'economia di scala e quindi accessibili al grande pubblico. Questo fa si che, in queste prime fasi, i costruttori non siano più gli unici utilizzatori. [[File:Transistors.jpg |300px|thumb|right|Diversi tipi di Transistor]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
A questo punto poiché, pur essendo diventata più accessibile, una macchina costava ancora molto, nasce l'esigenza di non lasciare mai un processore senza lavoro (al fine di massimizzare i ricavi) e di condividere i dati.&lt;br /&gt;
In questa fase, infatti, abbiamo sistemi operativi di tipo ''batch'' (che collezionano tutto l'input all'inizio per calcolare e restituire l'output) dove l'inserimento di programmi e dati veniva fatto tramite schede perforate e non c'era interazione. Dai sistemi batch nasce lo ''SPOOL'' (Simultaneous Peripheral Operations On-Line).&lt;br /&gt;
&lt;br /&gt;
Si comincia a pensare di utilizzare i tempi di I/O per poter eseguire altri processi. Questo però solleva almeno due problematiche: da un lato serve un modo per sapere quando un input è davvero finito mentre, dall'altro, bisogna fare in modo che un processo che non richieda I/O non occupi la CPU per un tempo indefinito.&lt;br /&gt;
Per la prima problematica la soluzione è rappresentata dall'introduzione degli '''interrupt''', segnali elettrici inviati alla CPU alla fine di ogni input.&lt;br /&gt;
Per la seconda, invece, è stato introdotto il cosiddetto '''interval timer''' che non è altro che un dispositivo che invia interrupt dopo un determinato quanto di tempo assicurando che un processo non occupi per troppo tempo il processore.&lt;br /&gt;
Questo consente di realizzare sistemi time sharing (il cui più semplice algoritmo è il round-robin) dove un processo può essere in uno dei seguenti tre stati:&lt;br /&gt;
*'''READY''' pronto per essere eseguito ma, il processore è già occupato;&lt;br /&gt;
*'''RUNNING''' in esecuzione;&lt;br /&gt;
*'''WAIT''' in attesa di I/O.&lt;br /&gt;
&lt;br /&gt;
[[File:Dds.png]]&lt;br /&gt;
&lt;br /&gt;
Nella '''quarta''' generazione (anni '70 ca.) vediamo la nascita di molte innovazioni importanti. Si riesce a rimpicciolire di molto i processori facendoli divenire a tutti gli effetti micro-processori. Nei laboratori Bell nasce Unix, un sistema operativo rivoluzionario con time sharing e clonazione dei processi (un processo non viene mai creato dal nulla ma viene prima clonato da uno già esistente e poi, alla copia, viene fatto eseguire un programma). A causa di problemi di portabilità da PDP-9, macchina su cui Unix è nato, a PDP-11 nasce il linguaggio C e il suo compilatore (scritto anch'esso in C).&lt;br /&gt;
Mentre Apple I &amp;quot;porta&amp;quot; una nuova idea di pc per tutte le famiglie, nascono molte versioni di Unix.&lt;br /&gt;
Per impedire una deriva proprietaria in cui ogni produttore offriva tutto quello che offrivano gli altri ma con qualche feature in più, Richard Stallman fonda il progetto GNU (GNU's Not Unix) per il quale scrive tutta una serie di utilities come il famoso compilatore gcc.&lt;br /&gt;
A GNU, per essere operativo, manca però un kernel. Non volendo aspettare i tempi di sviluppo del kernel che Stallman aveva in mente, Linus Torlvalds scrive il kernel '''Linux''' da cui nasce il sistema operativo GNU/Linux.&lt;br /&gt;
&lt;br /&gt;
Nei tempi più recenti nascono i processori multi-core.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 3 ottobre 2017 ==&lt;br /&gt;
 &lt;br /&gt;
'''Introduzione alla programmazione concorrente: processi, thread e  librerie'''&lt;br /&gt;
&lt;br /&gt;
Quando parliamo di ''sistema concorrente'', ci riferiamo ad un sistema in cui due o più &amp;quot;attori&amp;quot; sono eseguiti parallelamente.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Questi attori possono essere '''processi''' o '''thread'''.&lt;br /&gt;
La differenza sostanziale tra queste due tipologie di attori riguarda la condivisione della memoria e dei dati su cui operano.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Più processi possono eseguire lo stesso programma ma, ognuno di essi, ha i propri dati su cui operare e il suo stato di avanzamento.&amp;lt;br&amp;gt;&lt;br /&gt;
Più thread che vengono eseguiti parallelamente, invece, condividono gli stessi dati su cui operare.&amp;lt;br&amp;gt;&lt;br /&gt;
Codici di esempio:&lt;br /&gt;
&lt;br /&gt;
'''processi'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=C&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;unistd.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int main(int argc, char *argv[]){&lt;br /&gt;
   if(fork()) printf(&amp;quot;Yes&amp;quot;);&lt;br /&gt;
   else printf(&amp;quot;No&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
L'output generato sarà &amp;quot;YesNo&amp;quot; poiché, usando la systemcall ''fork()'', abbiamo creato una nuova copia del processo (un processo figlio). Il processo padre, che ha eseguito con successo la fork(), stamperà &amp;quot;Yes&amp;quot; mentre il figlio stamperà &amp;quot;No&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
'''thread'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=C&amp;gt;&lt;br /&gt;
#include &amp;lt;pthread.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
#include &amp;lt;assert.h&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
#define NUM_THREADS     5&lt;br /&gt;
#define MAX 1000000&lt;br /&gt;
&lt;br /&gt;
int sum;&lt;br /&gt;
 &lt;br /&gt;
void* perform_work(){&lt;br /&gt;
	int i;&lt;br /&gt;
	&lt;br /&gt;
	for(i = 0; i &amp;lt; MAX; i++) sum = sum + 1;&lt;br /&gt;
  	/* optionally: insert more useful stuff here */ &lt;br /&gt;
  	return NULL;&lt;br /&gt;
}&lt;br /&gt;
 &lt;br /&gt;
int main( int argc, char** argv ){&lt;br /&gt;
	pthread_t threads[NUM_THREADS];&lt;br /&gt;
  	int thread_args[NUM_THREADS];&lt;br /&gt;
  	int result_code;&lt;br /&gt;
  	unsigned index;&lt;br /&gt;
 &lt;br /&gt;
	sum = 0;&lt;br /&gt;
  	// create all threads one by one&lt;br /&gt;
  	for(index = 0; index &amp;lt; NUM_THREADS; ++index ){&lt;br /&gt;
    		thread_args[index] = index;&lt;br /&gt;
    		printf(&amp;quot;In main: creating thread %d\n&amp;quot;, index);&lt;br /&gt;
    		result_code = pthread_create(&amp;amp;threads[index], NULL, perform_work, NULL);&lt;br /&gt;
    		assert(!result_code);&lt;br /&gt;
  	} &lt;br /&gt;
  	// wait for each thread to complete&lt;br /&gt;
  	for(index = 0; index &amp;lt; NUM_THREADS; ++index ){&lt;br /&gt;
    		// block until thread 'index' completes&lt;br /&gt;
    		result_code = pthread_join(threads[index], NULL);&lt;br /&gt;
    		assert(!result_code);&lt;br /&gt;
    		printf(&amp;quot;In main: thread %d has completed\n&amp;quot;, index);&lt;br /&gt;
   	}&lt;br /&gt;
 &lt;br /&gt;
   	printf(&amp;quot;In main: All threads completed successfully\n&amp;quot; );&lt;br /&gt;
	printf(&amp;quot;Result: %d\n&amp;quot;, sum);&lt;br /&gt;
   	exit( EXIT_SUCCESS );&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Prima di discutere l'output generato dall'esecuzione di questo codice, è opportuno introdurre, brevemente, gli strumenti utilizzati.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
'''Pthread (POSIX thread)'''&lt;br /&gt;
&lt;br /&gt;
Citazione dal Silberschatz, Galvin, Gagne:&lt;br /&gt;
''Pthread si riferisce allo standard POSIX (IEEE 1003.1c) che definisce una API per la creazione e sincronizzazione dei thread.''&lt;br /&gt;
&lt;br /&gt;
Le istruzioni fondamentali sono:&lt;br /&gt;
* '''int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);''' che crea un thread;&lt;br /&gt;
* '''int pthread_join(pthread_t thread, void **retval);''' che aspetta che un thread sia completato prima di continuare l'esecuzione.&lt;br /&gt;
&lt;br /&gt;
Da ricordare che, per utilizzare pthread, va incluso l'header ''&amp;lt;pthread.h&amp;gt;'' e durante la compilazione va inclusa la libreria esterna tramite l'opzione ''-pthread''.&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
Torniamo all'output generato dall'esecuzione del secondo codice.&amp;lt;br&amp;gt;&lt;br /&gt;
Nel codice vengono generati cinque thread ed ognuno somma MAX (=1000000) volte 1 a sum. Ci aspettiamo un output di 5000000 ma, in realtà, viene stampato un valore molto minore.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Questo accade principalmente per due motivi:&lt;br /&gt;
*sommare una costante ad una variabile non è un'operazione atomica (richiede almeno tre istruzioni assembler: due mov ed una sum).&lt;br /&gt;
*l'interval timer può fermare l'esecuzione del thread mentre sta effettuando una delle tre operazioni richieste per aggiungere uno a sum e provocare un risultato errato.&lt;br /&gt;
&lt;br /&gt;
Questo è uno dei possibili problemi della programmazione concorrente conosciuto come ''race condition''.&amp;lt;br&amp;gt;&lt;br /&gt;
Un sistema di processi multipli presenta una '''race condition''' qualora il risultato finale dell'esecuzione dipenda dalla temporizzazione, o dall'ordine con cui i processi vengono eseguiti.&lt;br /&gt;
&lt;br /&gt;
Per risolvere questo problema possiamo adoperare ancora una volta pthread.&lt;br /&gt;
Nello specifico, ci venogono offerte istruzione per realizzare una '''mutua esclusione''' sulle risorse condivise in modo che l'operazione che prima non era atomiche,ora, lo sia e non porti più ad una computazione errata.&amp;lt;br&amp;gt;&lt;br /&gt;
Codice di esempio:&amp;lt;br&amp;gt;&lt;br /&gt;
'''Mutex'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=C&amp;gt;&lt;br /&gt;
#include &amp;lt;pthread.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
#include &amp;lt;assert.h&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
#define NUM_THREADS     5&lt;br /&gt;
#define MAX 1000000&lt;br /&gt;
&lt;br /&gt;
int sum;&lt;br /&gt;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;&lt;br /&gt;
 &lt;br /&gt;
void* perform_work(){&lt;br /&gt;
	int i;&lt;br /&gt;
	&lt;br /&gt;
	for(i = 0; i &amp;lt; MAX; i++){ &lt;br /&gt;
		pthread_mutex_lock(&amp;amp;mutex);		&lt;br /&gt;
		sum = sum + 1;&lt;br /&gt;
		pthread_mutex_unlock(&amp;amp;mutex);&lt;br /&gt;
	}&lt;br /&gt;
  	/* optionally: insert more useful stuff here */ &lt;br /&gt;
  	return NULL;&lt;br /&gt;
}&lt;br /&gt;
 &lt;br /&gt;
int main( int argc, char** argv ){&lt;br /&gt;
	pthread_t threads[NUM_THREADS];&lt;br /&gt;
  	int thread_args[NUM_THREADS];&lt;br /&gt;
  	int result_code;&lt;br /&gt;
  	unsigned index;&lt;br /&gt;
&lt;br /&gt;
	sum = 0;&lt;br /&gt;
  	// create all threads one by one&lt;br /&gt;
  	for(index = 0; index &amp;lt; NUM_THREADS; ++index ){&lt;br /&gt;
    		thread_args[index] = index;&lt;br /&gt;
    		printf(&amp;quot;In main: creating thread %d\n&amp;quot;, index);&lt;br /&gt;
    		result_code = pthread_create(&amp;amp;threads[index], NULL, perform_work, NULL);&lt;br /&gt;
    		assert(!result_code);&lt;br /&gt;
  	} &lt;br /&gt;
  	// wait for each thread to complete&lt;br /&gt;
  	for(index = 0; index &amp;lt; NUM_THREADS; ++index ){&lt;br /&gt;
    		// block until thread 'index' completes&lt;br /&gt;
    		result_code = pthread_join(threads[index], NULL);&lt;br /&gt;
    		assert(!result_code);&lt;br /&gt;
    		printf(&amp;quot;In main: thread %d has completed\n&amp;quot;, index);&lt;br /&gt;
   	}&lt;br /&gt;
 &lt;br /&gt;
   	printf(&amp;quot;In main: All threads completed successfully\n&amp;quot; );&lt;br /&gt;
	printf(&amp;quot;Result: %d\n&amp;quot;, sum);&lt;br /&gt;
   	exit( EXIT_SUCCESS );&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
Un mutex è un dispositivo di MUTual EXclusion (mutua esclusione), ed è utile per proteggere le strutture dati condivise da modifiche concorrenti, implementare sezioni critiche e monitor.&lt;br /&gt;
&lt;br /&gt;
Un mutex ha due possibili stati: sbloccato (non posseduto da nessun thread) e bloccato (posseduto da un thread). Un mutex non può mai essere posseduto da due thread contemporaneamente.&amp;lt;br&amp;gt;Se un thread provasse ad acquisire il mutex, mentre quest'ultimo è già in possesso di un altro thread, dovrebbe aspettare finché non viene rilasciato.&lt;br /&gt;
La variabile che realizza il mutex, di tipo ''pthread_mutex_t'', viene inizializzata staticamente assegnandogli PTHREAD_MUTEX_INITIALIZER.&lt;br /&gt;
'''pthread_mutex_lock''' acquisisce immediatamente il mutex, se libero. Altrimenti aspetta fino a trovarla, prima o poi, libera.&amp;lt;br&amp;gt;&lt;br /&gt;
'''pthread_mutex_unlock''' rilascia il mutex.&lt;br /&gt;
&lt;br /&gt;
[da: https://sourceware.org/pthreads-win32/manual/pthread_mutex_init.html]&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Lezione del 6 ottobre 2017 ==&lt;br /&gt;
== Lezione del 10 ottobre 2017 ==&lt;br /&gt;
== Lezione del 13 ottobre 2017 ==&lt;br /&gt;
== Lezione del 17 ottobre 2017 ==&lt;br /&gt;
== Lezione del 20 ottobre 2017 ==&lt;br /&gt;
== Lezione del 24 ottobre 2017 ==&lt;br /&gt;
== Lezione del 27 ottobre 2017 ==&lt;br /&gt;
== Lezione del 31 ottobre 2017 ==&lt;br /&gt;
== Lezione del 3 novembre 2017 ==&lt;br /&gt;
== Lezione del 7 novembre 2017 ==&lt;br /&gt;
== Lezione del 10 novembre 2017 ==&lt;br /&gt;
== Lezione del 14 novembre 2017 ==&lt;br /&gt;
== Lezione del 17 novembre 2017 ==&lt;br /&gt;
== Lezione del 21 novembre 2017 ==&lt;br /&gt;
== Lezione del 24 novembre 2017 ==&lt;br /&gt;
== Lezione del 28 novembre 2017 ==&lt;br /&gt;
== Lezione del 1 dicembre 2017 ==&lt;br /&gt;
== Lezione del 5 dicembre 2017 ==&lt;br /&gt;
== Lezione del 12 dicembre 2017 ==&lt;br /&gt;
== Lezione del 15 dicembre 2017 ==&lt;/div&gt;</summary>
		<author><name>Matt</name></author>
	</entry>
	<entry>
		<id>https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_I_semestre&amp;diff=2002</id>
		<title>Lezioni Anno Accademico 2017/18 I semestre</title>
		<link rel="alternate" type="text/html" href="https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_I_semestre&amp;diff=2002"/>
		<updated>2017-10-04T14:20:27Z</updated>

		<summary type="html">&lt;p&gt;Matt: /* Lezione del 3 ottobre 2017 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;''scrivete qui idee, riassunti dei concetti espressi, commenti approfondimenti sulle lezioni.''&lt;br /&gt;
&lt;br /&gt;
== Lezione del 26 settembre 2017 ==&lt;br /&gt;
'''Titolo della lezione: W&amp;amp;#x2074; H Y'''&lt;br /&gt;
&lt;br /&gt;
* What:&lt;br /&gt;
* Who:&lt;br /&gt;
----&lt;br /&gt;
Noi studenti ed &amp;quot;entrambi&amp;quot; i professori.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* When:&lt;br /&gt;
----&lt;br /&gt;
Il corso ha durata annuale. Verrà svolto ogni martedì e venerdì dalle 15:30 alle 18:30.&lt;br /&gt;
* La lezione del martedì sarà dedicata alla programmazione concorrente;&lt;br /&gt;
* La lezione del venerdì sarà dedicata alla parte generale. Terminerà circa 15 minuti prima dell'orario stabilito.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* Where:&lt;br /&gt;
----&lt;br /&gt;
Sempre in Aula 1 Ercolani (E1): DISI, Scuole Ercolani, Mura Anteo Zamboni 2B.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* How:&lt;br /&gt;
----&lt;br /&gt;
Il corso si svolgerà tramite:&lt;br /&gt;
* Lezioni frontali in aula.&lt;br /&gt;
* Attività di laboratorio.&lt;br /&gt;
* Esercitazioni.&lt;br /&gt;
&lt;br /&gt;
Inoltre, durante l'ultimo periodo di lezioni del secondo semestre, vi sarà un periodo di ripasso del programma tramite esercizi, in vista dell'esame.&lt;br /&gt;
Durante la lezione del 3/10 sono state indicate alcune propedeuticità del corso. tra cui: programmazione, algoritmi e architettura degli elaboratori in primis.&lt;br /&gt;
Importanti sono anche i corsi di Analisi matematica e Algebra e geometria. Per farla breve, il primo anno è propedeutico al secondo.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* Why:&lt;br /&gt;
----&lt;br /&gt;
* '''Fonti e strumenti del corso:''' wiki creata di gruppo, lezioni frontali e esercitazioni.&lt;br /&gt;
**Non esiste un vero e proprio testo consigliato. Tutte le informazioni sono date DURANTE il corso.&lt;br /&gt;
** Per domande specifiche scrivere su mailing list (so@cs.unibo.it);&lt;br /&gt;
** Per esercizi, appunti e programma svolto rivolgersi al wiki (so.v2.cs.unibo.it);&lt;br /&gt;
** Per live streaming (www.cs.unibo.it/~renzo/live/).&lt;br /&gt;
* '''Modalit&amp;amp;agrave; di esame:''' esame scritto (diviso in due parti: una parte generale e una di programmazione concorrente) + progetto + orale (facoltativo), possibilità di dare solo lo scritto per ottenere un massimo di 18. La modalità per studenti in &amp;quot;difficoltà&amp;quot; è disponibile solo fino agli appelli autunnali.&lt;br /&gt;
** Si parlerà del progetto indicativamente a partire da Dicembre 2017.&lt;br /&gt;
** L'orale può essere sostenuto da chi vuole migliorare (peggiorare) il voto ottenuto, o da chi vuole ottenere la lode.&lt;br /&gt;
* '''Orario di ricevimento per il primo semestre (fino al 15/09/17):''' martedì alle 11:30.&lt;br /&gt;
* Universit&amp;amp;agrave; = docenti + studenti.&lt;br /&gt;
* Informatica = come generare informazione automatica.&lt;br /&gt;
* Hardware, Software, Elaborazione, Comunicazione, Memorizzazione, Digitale/Analogico.&lt;br /&gt;
La principale distinzione che facciamo tra '''dato''' e '''informazione''' è che il dato, da solo, è privo di significato. Se, tuttavia, viene interpretato in un particolare contesto allora può diventare informazione significativa per chi sta interpretando i dati.&lt;br /&gt;
&lt;br /&gt;
Un '''algoritmo''' è una o più sequenze non ambigue di passi che, dato un problema, ci permette di risolverlo. &lt;br /&gt;
&lt;br /&gt;
Il nostro algoritmo, scritto in un qualche linguaggio formale, diventa un '''programma''' (sostanzialmente un testo, un insieme di istruzioni).&lt;br /&gt;
&lt;br /&gt;
Un linguaggio è un entità software, ed è definito come una quadrupla (alfabeto, sintassi, lessico, semantica) dove:&lt;br /&gt;
*l'''alfabeto'' è l'insieme di simboli che compone il linguaggio.&lt;br /&gt;
*il ''lessico'' si può vedere come una funzione che va dai simboli a un booleano e ci dice quali sono le frasi ben formate.&lt;br /&gt;
*la ''sintassi'' determina quali delle sequenze scritte sono effettivamente corrette.&lt;br /&gt;
*la ''semantica'' associa un significato alle parole ben formate.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 29 settembre 2017 ==&lt;br /&gt;
'''Cos'è un sistema operativo e un po' di storia'''&lt;br /&gt;
&lt;br /&gt;
Un ''sistema operativo'' è un programma che gestisce i processi, le risorse e interfaccia le applicazioni con l'hardware dell'elaboratore.&lt;br /&gt;
&lt;br /&gt;
In particolare, il sistema operativo (laddove esiste) è il primo processo ad essere attivato e resta in vita fino allo spegnimento del calcolatore o al sopraggiungere di un errore fatale per il sistema.&lt;br /&gt;
&lt;br /&gt;
A cosa serve un sistema operativo?&lt;br /&gt;
Principalmente, ha i seguenti scopi:&lt;br /&gt;
# Facilitare l'utilizzo del sistema (senza S.O. sarebbe a carico del programmatore conoscere tutti i linguaggi con cui si comunica con l'architettura della macchina);&lt;br /&gt;
# Rendere affidabile, protetto e sicuro l'utilizzo del sistema (e.g. un processo potrebbe recar danno all'intero sistema se non controllato o gestito, potrebbe ignorare i permessi di visualizzazione di un file);&lt;br /&gt;
# Astrarre l'hardware (e.g. filesystem);&lt;br /&gt;
# Garantire l'efficienza (e.g. non tenere la CPU in idle adottando opportuni algoritmi di scheduling);&lt;br /&gt;
# Assicurare portabilità ;&lt;br /&gt;
&lt;br /&gt;
Un approccio molto usato è il cosiddetto approccio a strati (&amp;quot;a livelli&amp;quot;) in cui ogni strato utilizza i servizi forniti al livello inferiore e ne fornisce di nuovi a quello superiore.&lt;br /&gt;
Astraendo il nostro calcolatore possiamo piazzare al livello più basso l'hardware e, subito sopra, il sistema operativo. I due layer comunicano usando il linguaggio ISA (Instruction Set Architecture), nativo della CPU stessa, al quale si aggiungono quelli che permettono di comunicare con i vari controllori dei dispositivi come la scheda di rete, la stampante, etc. Sopra il livello del sistema operativo possiamo collocare quello delle librerie e, infine, quello degli applicativi.&lt;br /&gt;
&lt;br /&gt;
* '''Systemcall''' = meccanismo usato da un processo per richiedere al SO una qualche funzionalità a livello kernel. Un esempio è la funzione printf() del linguaggio C (stampa a video) che utilizza la systemcall write() (una delle systemcall per la gestione dei dispositivi).&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
----&lt;br /&gt;
'''Storia dei sistemi operativi'''&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''zero''' (1800 ca.) possiamo includere Babbage con la sua macchina analitica e lady Ada Lovelace. [[File:290px-AnalyticalMachine Babbage London.jpg |200px|thumb|right|Macchina analitica di Babbage]]&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''uno''' vediamo la comparsa delle valvole con tutti i problemi connessi. Non c'erano utilizzatori delle macchine, le stesse persone che le costruivano erano anche programmatori e fruitori delle stesse.&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''due''' (fine anni '60) arriva il transistor. Quest'ultimo è molto più veloce e piccolo della valvola e, soprattutto, meno incline a guasti. Le macchine iniziano ad essere più economiche grazie alla possibilità di avere un'economia di scala e quindi accessibili al grande pubblico. Questo fa si che, in queste prime fasi, i costruttori non siano più gli unici utilizzatori. [[File:Transistors.jpg |300px|thumb|right|Diversi tipi di Transistor]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
A questo punto poiché, pur essendo diventata più accessibile, una macchina costava ancora molto, nasce l'esigenza di non lasciare mai un processore senza lavoro (al fine di massimizzare i ricavi) e di condividere i dati.&lt;br /&gt;
In questa fase, infatti, abbiamo sistemi operativi di tipo ''batch'' (che collezionano tutto l'input all'inizio per calcolare e restituire l'output) dove l'inserimento di programmi e dati veniva fatto tramite schede perforate e non c'era interazione. Dai sistemi batch nasce lo ''SPOOL'' (Simultaneous Peripheral Operations On-Line).&lt;br /&gt;
&lt;br /&gt;
Si comincia a pensare di utilizzare i tempi di I/O per poter eseguire altri processi. Questo però solleva almeno due problematiche: da un lato serve un modo per sapere quando un input è davvero finito mentre, dall'altro, bisogna fare in modo che un processo che non richieda I/O non occupi la CPU per un tempo indefinito.&lt;br /&gt;
Per la prima problematica la soluzione è rappresentata dall'introduzione degli '''interrupt''', segnali elettrici inviati alla CPU alla fine di ogni input.&lt;br /&gt;
Per la seconda, invece, è stato introdotto il cosiddetto '''interval timer''' che non è altro che un dispositivo che invia interrupt dopo un determinato quanto di tempo assicurando che un processo non occupi per troppo tempo il processore.&lt;br /&gt;
Questo consente di realizzare sistemi time sharing (il cui più semplice algoritmo è il round-robin) dove un processo può essere in uno dei seguenti tre stati:&lt;br /&gt;
*'''READY''' pronto per essere eseguito ma, il processore è già occupato;&lt;br /&gt;
*'''RUNNING''' in esecuzione;&lt;br /&gt;
*'''WAIT''' in attesa di I/O.&lt;br /&gt;
&lt;br /&gt;
[[File:Dds.png]]&lt;br /&gt;
&lt;br /&gt;
Nella '''quarta''' generazione (anni '70 ca.) vediamo la nascita di molte innovazioni importanti. Si riesce a rimpicciolire di molto i processori facendoli divenire a tutti gli effetti micro-processori. Nei laboratori Bell nasce Unix, un sistema operativo rivoluzionario con time sharing e clonazione dei processi (un processo non viene mai creato dal nulla ma viene prima clonato da uno già esistente e poi, alla copia, viene fatto eseguire un programma). A causa di problemi di portabilità da PDP-9, macchina su cui Unix è nato, a PDP-11 nasce il linguaggio C e il suo compilatore (scritto anch'esso in C).&lt;br /&gt;
Mentre Apple I &amp;quot;porta&amp;quot; una nuova idea di pc per tutte le famiglie, nascono molte versioni di Unix.&lt;br /&gt;
Per impedire una deriva proprietaria in cui ogni produttore offriva tutto quello che offrivano gli altri ma con qualche feature in più, Richard Stallman fonda il progetto GNU (GNU's Not Unix) per il quale scrive tutta una serie di utilities come il famoso compilatore gcc.&lt;br /&gt;
A GNU, per essere operativo, manca però un kernel. Non volendo aspettare i tempi di sviluppo del kernel che Stallman aveva in mente, Linus Torlvalds scrive il kernel '''Linux''' da cui nasce il sistema operativo GNU/Linux.&lt;br /&gt;
&lt;br /&gt;
Nei tempi più recenti nascono i processori multi-core.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 3 ottobre 2017 ==&lt;br /&gt;
 &lt;br /&gt;
'''Introduzione alla programmazione concorrente: processi, thread e  librerie'''&lt;br /&gt;
&lt;br /&gt;
Quando parliamo di ''sistema concorrente'', ci riferiamo ad un sistema in cui due o più &amp;quot;attori&amp;quot; sono eseguiti parallelamente.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Questi attori possono essere '''processi''' o '''thread'''.&lt;br /&gt;
La differenza sostanziale tra queste due tipologie di attori riguarda la condivisione della memoria e dei dati su cui operano.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Più processi possono eseguire lo stesso programma ma, ognuno di essi, ha i propri dati su cui operare e il suo stato di avanzamento.&amp;lt;br&amp;gt;&lt;br /&gt;
Più thread che vengono eseguiti parallelamente, invece, condividono gli stessi dati su cui operare.&amp;lt;br&amp;gt;&lt;br /&gt;
Codici di esempio:&lt;br /&gt;
&lt;br /&gt;
'''processi'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=C&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;unistd.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int main(int argc, char *argv[]){&lt;br /&gt;
   if(fork()) printf(&amp;quot;Yes&amp;quot;);&lt;br /&gt;
   else printf(&amp;quot;No&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
L'output generato sarà &amp;quot;YesNo&amp;quot; poiché, usando la systemcall ''fork()'', abbiamo creato una nuova copia del processo (un processo figlio). Il processo padre, che ha eseguito con successo la fork(), stamperà &amp;quot;Yes&amp;quot; mentre il figlio stamperà &amp;quot;No&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
'''thread'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=C&amp;gt;&lt;br /&gt;
#include &amp;lt;pthread.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
#include &amp;lt;assert.h&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
#define NUM_THREADS     5&lt;br /&gt;
#define MAX 1000000&lt;br /&gt;
&lt;br /&gt;
int sum;&lt;br /&gt;
 &lt;br /&gt;
void* perform_work(){&lt;br /&gt;
	int i;&lt;br /&gt;
	&lt;br /&gt;
	for(i = 0; i &amp;lt; MAX; i++) sum = sum + 1;&lt;br /&gt;
  	/* optionally: insert more useful stuff here */ &lt;br /&gt;
  	return NULL;&lt;br /&gt;
}&lt;br /&gt;
 &lt;br /&gt;
int main( int argc, char** argv ){&lt;br /&gt;
	pthread_t threads[NUM_THREADS];&lt;br /&gt;
  	int thread_args[NUM_THREADS];&lt;br /&gt;
  	int result_code;&lt;br /&gt;
  	unsigned index;&lt;br /&gt;
 &lt;br /&gt;
	sum = 0;&lt;br /&gt;
  	// create all threads one by one&lt;br /&gt;
  	for(index = 0; index &amp;lt; NUM_THREADS; ++index ){&lt;br /&gt;
    		thread_args[index] = index;&lt;br /&gt;
    		printf(&amp;quot;In main: creating thread %d\n&amp;quot;, index);&lt;br /&gt;
    		result_code = pthread_create(&amp;amp;threads[index], NULL, perform_work, NULL);&lt;br /&gt;
    		assert(!result_code);&lt;br /&gt;
  	} &lt;br /&gt;
  	// wait for each thread to complete&lt;br /&gt;
  	for(index = 0; index &amp;lt; NUM_THREADS; ++index ){&lt;br /&gt;
    		// block until thread 'index' completes&lt;br /&gt;
    		result_code = pthread_join(threads[index], NULL);&lt;br /&gt;
    		assert(!result_code);&lt;br /&gt;
    		printf(&amp;quot;In main: thread %d has completed\n&amp;quot;, index);&lt;br /&gt;
   	}&lt;br /&gt;
 &lt;br /&gt;
   	printf(&amp;quot;In main: All threads completed successfully\n&amp;quot; );&lt;br /&gt;
	printf(&amp;quot;Result: %d\n&amp;quot;, sum);&lt;br /&gt;
   	exit( EXIT_SUCCESS );&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Prima di discutere l'output generato dall'esecuzione di questo codice, è opportuno introdurre, brevemente, gli strumenti utilizzati.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
'''Pthread (POSIX thread)'''&lt;br /&gt;
&lt;br /&gt;
Citazione dal Silberschatz, Galvin, Gagne:&lt;br /&gt;
''Pthread si riferisce allo standard POSIX (IEEE 1003.1c) che definisce una API per la creazione e sincronizzazione dei thread.''&lt;br /&gt;
&lt;br /&gt;
Le istruzioni fondamentali sono:&lt;br /&gt;
* '''int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);''' che crea un thread;&lt;br /&gt;
* '''int pthread_join(pthread_t thread, void **retval);''' che aspetta che un thread sia completato prima di continuare l'esecuzione.&lt;br /&gt;
&lt;br /&gt;
Da ricordare che, per utilizzare pthread, va incluso l'header ''&amp;lt;pthread.h&amp;gt;'' e durante la compilazione va inclusa la libreria esterna tramite l'opzione ''-pthread''.&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
Torniamo all'output generato dall'esecuzione del secondo codice.&amp;lt;br&amp;gt;&lt;br /&gt;
Nel codice vengono generati cinque thread ed ognuno somma MAX (=1000000) volte 1 a sum. Ci aspettiamo un output di 5000000 ma, in realtà, viene stampato un valore molto minore.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Questo accade principalmente per due motivi:&lt;br /&gt;
*sommare una costante ad una variabile non è un'operazione atomica (richiede almeno tre istruzioni assembler: due mov ed una sum).&lt;br /&gt;
*l'interval timer può fermare l'esecuzione del thread mentre sta effettuando una delle tre operazioni richieste per aggiungere uno a sum e provocare un risultato errato.&lt;br /&gt;
&lt;br /&gt;
Questo è uno dei possibili problemi della programmazione concorrente conosciuto come ''race condition''.&amp;lt;br&amp;gt;&lt;br /&gt;
Un sistema di processi multipli presenta una '''race condition''' qualora il risultato finale dell'esecuzione dipenda dalla temporizzazione, o dall'ordine con cui i processi vengono eseguiti.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 6 ottobre 2017 ==&lt;br /&gt;
== Lezione del 10 ottobre 2017 ==&lt;br /&gt;
== Lezione del 13 ottobre 2017 ==&lt;br /&gt;
== Lezione del 17 ottobre 2017 ==&lt;br /&gt;
== Lezione del 20 ottobre 2017 ==&lt;br /&gt;
== Lezione del 24 ottobre 2017 ==&lt;br /&gt;
== Lezione del 27 ottobre 2017 ==&lt;br /&gt;
== Lezione del 31 ottobre 2017 ==&lt;br /&gt;
== Lezione del 3 novembre 2017 ==&lt;br /&gt;
== Lezione del 7 novembre 2017 ==&lt;br /&gt;
== Lezione del 10 novembre 2017 ==&lt;br /&gt;
== Lezione del 14 novembre 2017 ==&lt;br /&gt;
== Lezione del 17 novembre 2017 ==&lt;br /&gt;
== Lezione del 21 novembre 2017 ==&lt;br /&gt;
== Lezione del 24 novembre 2017 ==&lt;br /&gt;
== Lezione del 28 novembre 2017 ==&lt;br /&gt;
== Lezione del 1 dicembre 2017 ==&lt;br /&gt;
== Lezione del 5 dicembre 2017 ==&lt;br /&gt;
== Lezione del 12 dicembre 2017 ==&lt;br /&gt;
== Lezione del 15 dicembre 2017 ==&lt;/div&gt;</summary>
		<author><name>Matt</name></author>
	</entry>
	<entry>
		<id>https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_I_semestre&amp;diff=2001</id>
		<title>Lezioni Anno Accademico 2017/18 I semestre</title>
		<link rel="alternate" type="text/html" href="https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_I_semestre&amp;diff=2001"/>
		<updated>2017-10-04T13:49:45Z</updated>

		<summary type="html">&lt;p&gt;Matt: /* Lezione del 3 ottobre 2017 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;''scrivete qui idee, riassunti dei concetti espressi, commenti approfondimenti sulle lezioni.''&lt;br /&gt;
&lt;br /&gt;
== Lezione del 26 settembre 2017 ==&lt;br /&gt;
'''Titolo della lezione: W&amp;amp;#x2074; H Y'''&lt;br /&gt;
&lt;br /&gt;
* What:&lt;br /&gt;
* Who:&lt;br /&gt;
----&lt;br /&gt;
Noi studenti ed &amp;quot;entrambi&amp;quot; i professori.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* When:&lt;br /&gt;
----&lt;br /&gt;
Il corso ha durata annuale. Verrà svolto ogni martedì e venerdì dalle 15:30 alle 18:30.&lt;br /&gt;
* La lezione del martedì sarà dedicata alla programmazione concorrente;&lt;br /&gt;
* La lezione del venerdì sarà dedicata alla parte generale. Terminerà circa 15 minuti prima dell'orario stabilito.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* Where:&lt;br /&gt;
----&lt;br /&gt;
Sempre in Aula 1 Ercolani (E1): DISI, Scuole Ercolani, Mura Anteo Zamboni 2B.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* How:&lt;br /&gt;
----&lt;br /&gt;
Il corso si svolgerà tramite:&lt;br /&gt;
* Lezioni frontali in aula.&lt;br /&gt;
* Attività di laboratorio.&lt;br /&gt;
* Esercitazioni.&lt;br /&gt;
&lt;br /&gt;
Inoltre, durante l'ultimo periodo di lezioni del secondo semestre, vi sarà un periodo di ripasso del programma tramite esercizi, in vista dell'esame.&lt;br /&gt;
Durante la lezione del 3/10 sono state indicate alcune propedeuticità del corso. tra cui: programmazione, algoritmi e architettura degli elaboratori in primis.&lt;br /&gt;
Importanti sono anche i corsi di Analisi matematica e Algebra e geometria. Per farla breve, il primo anno è propedeutico al secondo.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* Why:&lt;br /&gt;
----&lt;br /&gt;
* '''Fonti e strumenti del corso:''' wiki creata di gruppo, lezioni frontali e esercitazioni.&lt;br /&gt;
**Non esiste un vero e proprio testo consigliato. Tutte le informazioni sono date DURANTE il corso.&lt;br /&gt;
** Per domande specifiche scrivere su mailing list (so@cs.unibo.it);&lt;br /&gt;
** Per esercizi, appunti e programma svolto rivolgersi al wiki (so.v2.cs.unibo.it);&lt;br /&gt;
** Per live streaming (www.cs.unibo.it/~renzo/live/).&lt;br /&gt;
* '''Modalit&amp;amp;agrave; di esame:''' esame scritto (diviso in due parti: una parte generale e una di programmazione concorrente) + progetto + orale (facoltativo), possibilità di dare solo lo scritto per ottenere un massimo di 18. La modalità per studenti in &amp;quot;difficoltà&amp;quot; è disponibile solo fino agli appelli autunnali.&lt;br /&gt;
** Si parlerà del progetto indicativamente a partire da Dicembre 2017.&lt;br /&gt;
** L'orale può essere sostenuto da chi vuole migliorare (peggiorare) il voto ottenuto, o da chi vuole ottenere la lode.&lt;br /&gt;
* '''Orario di ricevimento per il primo semestre (fino al 15/09/17):''' martedì alle 11:30.&lt;br /&gt;
* Universit&amp;amp;agrave; = docenti + studenti.&lt;br /&gt;
* Informatica = come generare informazione automatica.&lt;br /&gt;
* Hardware, Software, Elaborazione, Comunicazione, Memorizzazione, Digitale/Analogico.&lt;br /&gt;
La principale distinzione che facciamo tra '''dato''' e '''informazione''' è che il dato, da solo, è privo di significato. Se, tuttavia, viene interpretato in un particolare contesto allora può diventare informazione significativa per chi sta interpretando i dati.&lt;br /&gt;
&lt;br /&gt;
Un '''algoritmo''' è una o più sequenze non ambigue di passi che, dato un problema, ci permette di risolverlo. &lt;br /&gt;
&lt;br /&gt;
Il nostro algoritmo, scritto in un qualche linguaggio formale, diventa un '''programma''' (sostanzialmente un testo, un insieme di istruzioni).&lt;br /&gt;
&lt;br /&gt;
Un linguaggio è un entità software, ed è definito come una quadrupla (alfabeto, sintassi, lessico, semantica) dove:&lt;br /&gt;
*l'''alfabeto'' è l'insieme di simboli che compone il linguaggio.&lt;br /&gt;
*il ''lessico'' si può vedere come una funzione che va dai simboli a un booleano e ci dice quali sono le frasi ben formate.&lt;br /&gt;
*la ''sintassi'' determina quali delle sequenze scritte sono effettivamente corrette.&lt;br /&gt;
*la ''semantica'' associa un significato alle parole ben formate.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 29 settembre 2017 ==&lt;br /&gt;
'''Cos'è un sistema operativo e un po' di storia'''&lt;br /&gt;
&lt;br /&gt;
Un ''sistema operativo'' è un programma che gestisce i processi, le risorse e interfaccia le applicazioni con l'hardware dell'elaboratore.&lt;br /&gt;
&lt;br /&gt;
In particolare, il sistema operativo (laddove esiste) è il primo processo ad essere attivato e resta in vita fino allo spegnimento del calcolatore o al sopraggiungere di un errore fatale per il sistema.&lt;br /&gt;
&lt;br /&gt;
A cosa serve un sistema operativo?&lt;br /&gt;
Principalmente, ha i seguenti scopi:&lt;br /&gt;
# Facilitare l'utilizzo del sistema (senza S.O. sarebbe a carico del programmatore conoscere tutti i linguaggi con cui si comunica con l'architettura della macchina);&lt;br /&gt;
# Rendere affidabile, protetto e sicuro l'utilizzo del sistema (e.g. un processo potrebbe recar danno all'intero sistema se non controllato o gestito, potrebbe ignorare i permessi di visualizzazione di un file);&lt;br /&gt;
# Astrarre l'hardware (e.g. filesystem);&lt;br /&gt;
# Garantire l'efficienza (e.g. non tenere la CPU in idle adottando opportuni algoritmi di scheduling);&lt;br /&gt;
# Assicurare portabilità ;&lt;br /&gt;
&lt;br /&gt;
Un approccio molto usato è il cosiddetto approccio a strati (&amp;quot;a livelli&amp;quot;) in cui ogni strato utilizza i servizi forniti al livello inferiore e ne fornisce di nuovi a quello superiore.&lt;br /&gt;
Astraendo il nostro calcolatore possiamo piazzare al livello più basso l'hardware e, subito sopra, il sistema operativo. I due layer comunicano usando il linguaggio ISA (Instruction Set Architecture), nativo della CPU stessa, al quale si aggiungono quelli che permettono di comunicare con i vari controllori dei dispositivi come la scheda di rete, la stampante, etc. Sopra il livello del sistema operativo possiamo collocare quello delle librerie e, infine, quello degli applicativi.&lt;br /&gt;
&lt;br /&gt;
* '''Systemcall''' = meccanismo usato da un processo per richiedere al SO una qualche funzionalità a livello kernel. Un esempio è la funzione printf() del linguaggio C (stampa a video) che utilizza la systemcall write() (una delle systemcall per la gestione dei dispositivi).&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
----&lt;br /&gt;
'''Storia dei sistemi operativi'''&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''zero''' (1800 ca.) possiamo includere Babbage con la sua macchina analitica e lady Ada Lovelace. [[File:290px-AnalyticalMachine Babbage London.jpg |200px|thumb|right|Macchina analitica di Babbage]]&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''uno''' vediamo la comparsa delle valvole con tutti i problemi connessi. Non c'erano utilizzatori delle macchine, le stesse persone che le costruivano erano anche programmatori e fruitori delle stesse.&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''due''' (fine anni '60) arriva il transistor. Quest'ultimo è molto più veloce e piccolo della valvola e, soprattutto, meno incline a guasti. Le macchine iniziano ad essere più economiche grazie alla possibilità di avere un'economia di scala e quindi accessibili al grande pubblico. Questo fa si che, in queste prime fasi, i costruttori non siano più gli unici utilizzatori. [[File:Transistors.jpg |300px|thumb|right|Diversi tipi di Transistor]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
A questo punto poiché, pur essendo diventata più accessibile, una macchina costava ancora molto, nasce l'esigenza di non lasciare mai un processore senza lavoro (al fine di massimizzare i ricavi) e di condividere i dati.&lt;br /&gt;
In questa fase, infatti, abbiamo sistemi operativi di tipo ''batch'' (che collezionano tutto l'input all'inizio per calcolare e restituire l'output) dove l'inserimento di programmi e dati veniva fatto tramite schede perforate e non c'era interazione. Dai sistemi batch nasce lo ''SPOOL'' (Simultaneous Peripheral Operations On-Line).&lt;br /&gt;
&lt;br /&gt;
Si comincia a pensare di utilizzare i tempi di I/O per poter eseguire altri processi. Questo però solleva almeno due problematiche: da un lato serve un modo per sapere quando un input è davvero finito mentre, dall'altro, bisogna fare in modo che un processo che non richieda I/O non occupi la CPU per un tempo indefinito.&lt;br /&gt;
Per la prima problematica la soluzione è rappresentata dall'introduzione degli '''interrupt''', segnali elettrici inviati alla CPU alla fine di ogni input.&lt;br /&gt;
Per la seconda, invece, è stato introdotto il cosiddetto '''interval timer''' che non è altro che un dispositivo che invia interrupt dopo un determinato quanto di tempo assicurando che un processo non occupi per troppo tempo il processore.&lt;br /&gt;
Questo consente di realizzare sistemi time sharing (il cui più semplice algoritmo è il round-robin) dove un processo può essere in uno dei seguenti tre stati:&lt;br /&gt;
*'''READY''' pronto per essere eseguito ma, il processore è già occupato;&lt;br /&gt;
*'''RUNNING''' in esecuzione;&lt;br /&gt;
*'''WAIT''' in attesa di I/O.&lt;br /&gt;
&lt;br /&gt;
[[File:Dds.png]]&lt;br /&gt;
&lt;br /&gt;
Nella '''quarta''' generazione (anni '70 ca.) vediamo la nascita di molte innovazioni importanti. Si riesce a rimpicciolire di molto i processori facendoli divenire a tutti gli effetti micro-processori. Nei laboratori Bell nasce Unix, un sistema operativo rivoluzionario con time sharing e clonazione dei processi (un processo non viene mai creato dal nulla ma viene prima clonato da uno già esistente e poi, alla copia, viene fatto eseguire un programma). A causa di problemi di portabilità da PDP-9, macchina su cui Unix è nato, a PDP-11 nasce il linguaggio C e il suo compilatore (scritto anch'esso in C).&lt;br /&gt;
Mentre Apple I &amp;quot;porta&amp;quot; una nuova idea di pc per tutte le famiglie, nascono molte versioni di Unix.&lt;br /&gt;
Per impedire una deriva proprietaria in cui ogni produttore offriva tutto quello che offrivano gli altri ma con qualche feature in più, Richard Stallman fonda il progetto GNU (GNU's Not Unix) per il quale scrive tutta una serie di utilities come il famoso compilatore gcc.&lt;br /&gt;
A GNU, per essere operativo, manca però un kernel. Non volendo aspettare i tempi di sviluppo del kernel che Stallman aveva in mente, Linus Torlvalds scrive il kernel '''Linux''' da cui nasce il sistema operativo GNU/Linux.&lt;br /&gt;
&lt;br /&gt;
Nei tempi più recenti nascono i processori multi-core.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 3 ottobre 2017 ==&lt;br /&gt;
 &lt;br /&gt;
'''Introduzione alla programmazione concorrente: processi, thread e  librerie'''&lt;br /&gt;
&lt;br /&gt;
Quando parliamo di ''sistema concorrente'', ci riferiamo ad un sistema in cui due o più &amp;quot;attori&amp;quot; sono eseguiti parallelamente.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Questi attori possono essere '''processi''' o '''thread'''.&lt;br /&gt;
La differenza sostanziale tra queste due tipologie di attori riguarda la condivisione della memoria e dei dati su cui operano.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Più processi possono eseguire lo stesso programma ma, ognuno di essi, ha i propri dati su cui operare e il suo stato di avanzamento.&amp;lt;br&amp;gt;&lt;br /&gt;
Più thread che vengono eseguiti parallelamente, invece, condividono gli stessi dati su cui operare.&amp;lt;br&amp;gt;&lt;br /&gt;
Codici di esempio:&lt;br /&gt;
&lt;br /&gt;
'''processi'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=C&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;unistd.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int main(int argc, char *argv[]){&lt;br /&gt;
   if(fork()) printf(&amp;quot;Yes&amp;quot;);&lt;br /&gt;
   else printf(&amp;quot;No&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
L'output generato sarà &amp;quot;YesNo&amp;quot; poiché, usando la systemcall ''fork()'', abbiamo creato una nuova copia del processo (un processo figlio). Il processo padre, che ha eseguito con successo la fork(), stamperà &amp;quot;Yes&amp;quot; mentre il figlio stamperà &amp;quot;No&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
'''thread'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=C&amp;gt;&lt;br /&gt;
#include &amp;lt;pthread.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
#include &amp;lt;assert.h&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
#define NUM_THREADS     5&lt;br /&gt;
#define MAX 1000000&lt;br /&gt;
&lt;br /&gt;
int sum;&lt;br /&gt;
 &lt;br /&gt;
void* perform_work(){&lt;br /&gt;
	int i;&lt;br /&gt;
	&lt;br /&gt;
	for(i = 0; i &amp;lt; MAX; i++) sum = sum + 1;&lt;br /&gt;
  	/* optionally: insert more useful stuff here */ &lt;br /&gt;
  	return NULL;&lt;br /&gt;
}&lt;br /&gt;
 &lt;br /&gt;
int main( int argc, char** argv ){&lt;br /&gt;
	pthread_t threads[NUM_THREADS];&lt;br /&gt;
  	int thread_args[NUM_THREADS];&lt;br /&gt;
  	int result_code;&lt;br /&gt;
  	unsigned index;&lt;br /&gt;
 &lt;br /&gt;
	sum = 0;&lt;br /&gt;
  	// create all threads one by one&lt;br /&gt;
  	for(index = 0; index &amp;lt; NUM_THREADS; ++index ){&lt;br /&gt;
    		thread_args[index] = index;&lt;br /&gt;
    		printf(&amp;quot;In main: creating thread %d\n&amp;quot;, index);&lt;br /&gt;
    		result_code = pthread_create(&amp;amp;threads[index], NULL, perform_work, NULL);&lt;br /&gt;
    		assert(!result_code);&lt;br /&gt;
  	} &lt;br /&gt;
  	// wait for each thread to complete&lt;br /&gt;
  	for(index = 0; index &amp;lt; NUM_THREADS; ++index ){&lt;br /&gt;
    		// block until thread 'index' completes&lt;br /&gt;
    		result_code = pthread_join(threads[index], NULL);&lt;br /&gt;
    		assert(!result_code);&lt;br /&gt;
    		printf(&amp;quot;In main: thread %d has completed\n&amp;quot;, index);&lt;br /&gt;
   	}&lt;br /&gt;
 &lt;br /&gt;
   	printf(&amp;quot;In main: All threads completed successfully\n&amp;quot; );&lt;br /&gt;
	printf(&amp;quot;Result: %d\n&amp;quot;, sum);&lt;br /&gt;
   	exit( EXIT_SUCCESS );&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Prima di discutere l'output generato dall'esecuzione di questo codice, è opportuno introdurre, brevemente, gli strumenti utilizzati.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
'''Pthread (POSIX thread)'''&lt;br /&gt;
&lt;br /&gt;
Citazione dal Silberschatz, Galvin, Gagne:&lt;br /&gt;
''Pthread si riferisce allo standard POSIX (IEEE 1003.1c) che definisce una API per la creazione e sincronizzazione dei thread.''&lt;br /&gt;
&lt;br /&gt;
Le istruzioni fondamentali sono:&lt;br /&gt;
* '''int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);''' che crea un thread;&lt;br /&gt;
* '''int pthread_join(pthread_t thread, void **retval);''' che aspetta che un thread sia completato prima di continuare l'esecuzione.&lt;br /&gt;
&lt;br /&gt;
Da ricordare che, per utilizzare pthread, va incluso l'header ''&amp;lt;pthread.h&amp;gt;'' e durante la compilazione va inclusa la libreria esterna tramite l'opzione ''-pthread''.&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
Torniamo all'output generato dall'esecuzione del secondo codice.&amp;lt;br&amp;gt;&lt;br /&gt;
Nel codice vengono generati cinque thread ed ognuno somma MAX (=1000000) volte 1 a sum. Ci aspettiamo un output di 5000000 ma, in realtà, viene stampato un valore molto minore.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Questo accade principalmente per due motivi:&lt;br /&gt;
*sommare una costante ad una variabile non è un'operazione atomica (richiede almeno tre istruzioni assembler: due mov ed una sum).&lt;br /&gt;
*l'interval timer può fermare l'esecuzione del thread mentre sta effettuando una delle tre operazioni richieste per aggiungere uno a sum e provocare un risultato errato.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 6 ottobre 2017 ==&lt;br /&gt;
== Lezione del 10 ottobre 2017 ==&lt;br /&gt;
== Lezione del 13 ottobre 2017 ==&lt;br /&gt;
== Lezione del 17 ottobre 2017 ==&lt;br /&gt;
== Lezione del 20 ottobre 2017 ==&lt;br /&gt;
== Lezione del 24 ottobre 2017 ==&lt;br /&gt;
== Lezione del 27 ottobre 2017 ==&lt;br /&gt;
== Lezione del 31 ottobre 2017 ==&lt;br /&gt;
== Lezione del 3 novembre 2017 ==&lt;br /&gt;
== Lezione del 7 novembre 2017 ==&lt;br /&gt;
== Lezione del 10 novembre 2017 ==&lt;br /&gt;
== Lezione del 14 novembre 2017 ==&lt;br /&gt;
== Lezione del 17 novembre 2017 ==&lt;br /&gt;
== Lezione del 21 novembre 2017 ==&lt;br /&gt;
== Lezione del 24 novembre 2017 ==&lt;br /&gt;
== Lezione del 28 novembre 2017 ==&lt;br /&gt;
== Lezione del 1 dicembre 2017 ==&lt;br /&gt;
== Lezione del 5 dicembre 2017 ==&lt;br /&gt;
== Lezione del 12 dicembre 2017 ==&lt;br /&gt;
== Lezione del 15 dicembre 2017 ==&lt;/div&gt;</summary>
		<author><name>Matt</name></author>
	</entry>
	<entry>
		<id>https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_I_semestre&amp;diff=2000</id>
		<title>Lezioni Anno Accademico 2017/18 I semestre</title>
		<link rel="alternate" type="text/html" href="https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_I_semestre&amp;diff=2000"/>
		<updated>2017-10-04T13:18:30Z</updated>

		<summary type="html">&lt;p&gt;Matt: /* Lezione del 3 ottobre 2017 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;''scrivete qui idee, riassunti dei concetti espressi, commenti approfondimenti sulle lezioni.''&lt;br /&gt;
&lt;br /&gt;
== Lezione del 26 settembre 2017 ==&lt;br /&gt;
'''Titolo della lezione: W&amp;amp;#x2074; H Y'''&lt;br /&gt;
&lt;br /&gt;
* What:&lt;br /&gt;
* Who:&lt;br /&gt;
----&lt;br /&gt;
Noi studenti ed &amp;quot;entrambi&amp;quot; i professori.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* When:&lt;br /&gt;
----&lt;br /&gt;
Il corso ha durata annuale. Verrà svolto ogni martedì e venerdì dalle 15:30 alle 18:30.&lt;br /&gt;
* La lezione del martedì sarà dedicata alla programmazione concorrente;&lt;br /&gt;
* La lezione del venerdì sarà dedicata alla parte generale. Terminerà circa 15 minuti prima dell'orario stabilito.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* Where:&lt;br /&gt;
----&lt;br /&gt;
Sempre in Aula 1 Ercolani (E1): DISI, Scuole Ercolani, Mura Anteo Zamboni 2B.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* How:&lt;br /&gt;
----&lt;br /&gt;
Il corso si svolgerà tramite:&lt;br /&gt;
* Lezioni frontali in aula.&lt;br /&gt;
* Attività di laboratorio.&lt;br /&gt;
* Esercitazioni.&lt;br /&gt;
&lt;br /&gt;
Inoltre, durante l'ultimo periodo di lezioni del secondo semestre, vi sarà un periodo di ripasso del programma tramite esercizi, in vista dell'esame.&lt;br /&gt;
Durante la lezione del 3/10 sono state indicate alcune propedeuticità del corso. tra cui: programmazione, algoritmi e architettura degli elaboratori in primis.&lt;br /&gt;
Importanti sono anche i corsi di Analisi matematica e Algebra e geometria. Per farla breve, il primo anno è propedeutico al secondo.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* Why:&lt;br /&gt;
----&lt;br /&gt;
* '''Fonti e strumenti del corso:''' wiki creata di gruppo, lezioni frontali e esercitazioni.&lt;br /&gt;
**Non esiste un vero e proprio testo consigliato. Tutte le informazioni sono date DURANTE il corso.&lt;br /&gt;
** Per domande specifiche scrivere su mailing list (so@cs.unibo.it);&lt;br /&gt;
** Per esercizi, appunti e programma svolto rivolgersi al wiki (so.v2.cs.unibo.it);&lt;br /&gt;
** Per live streaming (www.cs.unibo.it/~renzo/live/).&lt;br /&gt;
* '''Modalit&amp;amp;agrave; di esame:''' esame scritto (diviso in due parti: una parte generale e una di programmazione concorrente) + progetto + orale (facoltativo), possibilità di dare solo lo scritto per ottenere un massimo di 18. La modalità per studenti in &amp;quot;difficoltà&amp;quot; è disponibile solo fino agli appelli autunnali.&lt;br /&gt;
** Si parlerà del progetto indicativamente a partire da Dicembre 2017.&lt;br /&gt;
** L'orale può essere sostenuto da chi vuole migliorare (peggiorare) il voto ottenuto, o da chi vuole ottenere la lode.&lt;br /&gt;
* '''Orario di ricevimento per il primo semestre (fino al 15/09/17):''' martedì alle 11:30.&lt;br /&gt;
* Universit&amp;amp;agrave; = docenti + studenti.&lt;br /&gt;
* Informatica = come generare informazione automatica.&lt;br /&gt;
* Hardware, Software, Elaborazione, Comunicazione, Memorizzazione, Digitale/Analogico.&lt;br /&gt;
La principale distinzione che facciamo tra '''dato''' e '''informazione''' è che il dato, da solo, è privo di significato. Se, tuttavia, viene interpretato in un particolare contesto allora può diventare informazione significativa per chi sta interpretando i dati.&lt;br /&gt;
&lt;br /&gt;
Un '''algoritmo''' è una o più sequenze non ambigue di passi che, dato un problema, ci permette di risolverlo. &lt;br /&gt;
&lt;br /&gt;
Il nostro algoritmo, scritto in un qualche linguaggio formale, diventa un '''programma''' (sostanzialmente un testo, un insieme di istruzioni).&lt;br /&gt;
&lt;br /&gt;
Un linguaggio è un entità software, ed è definito come una quadrupla (alfabeto, sintassi, lessico, semantica) dove:&lt;br /&gt;
*l'''alfabeto'' è l'insieme di simboli che compone il linguaggio.&lt;br /&gt;
*il ''lessico'' si può vedere come una funzione che va dai simboli a un booleano e ci dice quali sono le frasi ben formate.&lt;br /&gt;
*la ''sintassi'' determina quali delle sequenze scritte sono effettivamente corrette.&lt;br /&gt;
*la ''semantica'' associa un significato alle parole ben formate.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 29 settembre 2017 ==&lt;br /&gt;
'''Cos'è un sistema operativo e un po' di storia'''&lt;br /&gt;
&lt;br /&gt;
Un ''sistema operativo'' è un programma che gestisce i processi, le risorse e interfaccia le applicazioni con l'hardware dell'elaboratore.&lt;br /&gt;
&lt;br /&gt;
In particolare, il sistema operativo (laddove esiste) è il primo processo ad essere attivato e resta in vita fino allo spegnimento del calcolatore o al sopraggiungere di un errore fatale per il sistema.&lt;br /&gt;
&lt;br /&gt;
A cosa serve un sistema operativo?&lt;br /&gt;
Principalmente, ha i seguenti scopi:&lt;br /&gt;
# Facilitare l'utilizzo del sistema (senza S.O. sarebbe a carico del programmatore conoscere tutti i linguaggi con cui si comunica con l'architettura della macchina);&lt;br /&gt;
# Rendere affidabile, protetto e sicuro l'utilizzo del sistema (e.g. un processo potrebbe recar danno all'intero sistema se non controllato o gestito, potrebbe ignorare i permessi di visualizzazione di un file);&lt;br /&gt;
# Astrarre l'hardware (e.g. filesystem);&lt;br /&gt;
# Garantire l'efficienza (e.g. non tenere la CPU in idle adottando opportuni algoritmi di scheduling);&lt;br /&gt;
# Assicurare portabilità ;&lt;br /&gt;
&lt;br /&gt;
Un approccio molto usato è il cosiddetto approccio a strati (&amp;quot;a livelli&amp;quot;) in cui ogni strato utilizza i servizi forniti al livello inferiore e ne fornisce di nuovi a quello superiore.&lt;br /&gt;
Astraendo il nostro calcolatore possiamo piazzare al livello più basso l'hardware e, subito sopra, il sistema operativo. I due layer comunicano usando il linguaggio ISA (Instruction Set Architecture), nativo della CPU stessa, al quale si aggiungono quelli che permettono di comunicare con i vari controllori dei dispositivi come la scheda di rete, la stampante, etc. Sopra il livello del sistema operativo possiamo collocare quello delle librerie e, infine, quello degli applicativi.&lt;br /&gt;
&lt;br /&gt;
* '''Systemcall''' = meccanismo usato da un processo per richiedere al SO una qualche funzionalità a livello kernel. Un esempio è la funzione printf() del linguaggio C (stampa a video) che utilizza la systemcall write() (una delle systemcall per la gestione dei dispositivi).&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
----&lt;br /&gt;
'''Storia dei sistemi operativi'''&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''zero''' (1800 ca.) possiamo includere Babbage con la sua macchina analitica e lady Ada Lovelace. [[File:290px-AnalyticalMachine Babbage London.jpg |200px|thumb|right|Macchina analitica di Babbage]]&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''uno''' vediamo la comparsa delle valvole con tutti i problemi connessi. Non c'erano utilizzatori delle macchine, le stesse persone che le costruivano erano anche programmatori e fruitori delle stesse.&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''due''' (fine anni '60) arriva il transistor. Quest'ultimo è molto più veloce e piccolo della valvola e, soprattutto, meno incline a guasti. Le macchine iniziano ad essere più economiche grazie alla possibilità di avere un'economia di scala e quindi accessibili al grande pubblico. Questo fa si che, in queste prime fasi, i costruttori non siano più gli unici utilizzatori. [[File:Transistors.jpg |300px|thumb|right|Diversi tipi di Transistor]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
A questo punto poiché, pur essendo diventata più accessibile, una macchina costava ancora molto, nasce l'esigenza di non lasciare mai un processore senza lavoro (al fine di massimizzare i ricavi) e di condividere i dati.&lt;br /&gt;
In questa fase, infatti, abbiamo sistemi operativi di tipo ''batch'' (che collezionano tutto l'input all'inizio per calcolare e restituire l'output) dove l'inserimento di programmi e dati veniva fatto tramite schede perforate e non c'era interazione. Dai sistemi batch nasce lo ''SPOOL'' (Simultaneous Peripheral Operations On-Line).&lt;br /&gt;
&lt;br /&gt;
Si comincia a pensare di utilizzare i tempi di I/O per poter eseguire altri processi. Questo però solleva almeno due problematiche: da un lato serve un modo per sapere quando un input è davvero finito mentre, dall'altro, bisogna fare in modo che un processo che non richieda I/O non occupi la CPU per un tempo indefinito.&lt;br /&gt;
Per la prima problematica la soluzione è rappresentata dall'introduzione degli '''interrupt''', segnali elettrici inviati alla CPU alla fine di ogni input.&lt;br /&gt;
Per la seconda, invece, è stato introdotto il cosiddetto '''interval timer''' che non è altro che un dispositivo che invia interrupt dopo un determinato quanto di tempo assicurando che un processo non occupi per troppo tempo il processore.&lt;br /&gt;
Questo consente di realizzare sistemi time sharing (il cui più semplice algoritmo è il round-robin) dove un processo può essere in uno dei seguenti tre stati:&lt;br /&gt;
*'''READY''' pronto per essere eseguito ma, il processore è già occupato;&lt;br /&gt;
*'''RUNNING''' in esecuzione;&lt;br /&gt;
*'''WAIT''' in attesa di I/O.&lt;br /&gt;
&lt;br /&gt;
[[File:Dds.png]]&lt;br /&gt;
&lt;br /&gt;
Nella '''quarta''' generazione (anni '70 ca.) vediamo la nascita di molte innovazioni importanti. Si riesce a rimpicciolire di molto i processori facendoli divenire a tutti gli effetti micro-processori. Nei laboratori Bell nasce Unix, un sistema operativo rivoluzionario con time sharing e clonazione dei processi (un processo non viene mai creato dal nulla ma viene prima clonato da uno già esistente e poi, alla copia, viene fatto eseguire un programma). A causa di problemi di portabilità da PDP-9, macchina su cui Unix è nato, a PDP-11 nasce il linguaggio C e il suo compilatore (scritto anch'esso in C).&lt;br /&gt;
Mentre Apple I &amp;quot;porta&amp;quot; una nuova idea di pc per tutte le famiglie, nascono molte versioni di Unix.&lt;br /&gt;
Per impedire una deriva proprietaria in cui ogni produttore offriva tutto quello che offrivano gli altri ma con qualche feature in più, Richard Stallman fonda il progetto GNU (GNU's Not Unix) per il quale scrive tutta una serie di utilities come il famoso compilatore gcc.&lt;br /&gt;
A GNU, per essere operativo, manca però un kernel. Non volendo aspettare i tempi di sviluppo del kernel che Stallman aveva in mente, Linus Torlvalds scrive il kernel '''Linux''' da cui nasce il sistema operativo GNU/Linux.&lt;br /&gt;
&lt;br /&gt;
Nei tempi più recenti nascono i processori multi-core.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 3 ottobre 2017 ==&lt;br /&gt;
 &lt;br /&gt;
'''Introduzione alla programmazione concorrente: processi, thread e  librerie'''&lt;br /&gt;
&lt;br /&gt;
Quando parliamo di ''sistema concorrente'', ci riferiamo ad un sistema in cui due o più &amp;quot;attori&amp;quot; sono eseguiti parallelamente.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Questi attori possono essere '''processi''' o '''thread'''.&lt;br /&gt;
La differenza sostanziale tra queste due tipologie di attori riguarda la condivisione della memoria e dei dati su cui operano.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Più processi possono eseguire lo stesso programma ma, ognuno di essi, ha i propri dati su cui operare e il suo stato di avanzamento.&amp;lt;br&amp;gt;&lt;br /&gt;
Più thread che vengono eseguiti parallelamente, invece, condividono gli stessi dati su cui operare.&amp;lt;br&amp;gt;&lt;br /&gt;
Codici di esempio:&lt;br /&gt;
&lt;br /&gt;
'''processi'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=C&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;unistd.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int main(int argc, char *argv[]){&lt;br /&gt;
   if(fork()) printf(&amp;quot;Yes&amp;quot;);&lt;br /&gt;
   else printf(&amp;quot;No&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
L'output generato sarà &amp;quot;YesNo&amp;quot; poiché, usando la systemcall ''fork()'', abbiamo creato una nuova copia del processo (un processo figlio). Il processo padre, che ha eseguito con successo la fork(), stamperà &amp;quot;Yes&amp;quot; mentre il figlio stamperà &amp;quot;No&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
'''thread'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=C&amp;gt;&lt;br /&gt;
#include &amp;lt;pthread.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
#include &amp;lt;assert.h&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
#define NUM_THREADS     5&lt;br /&gt;
#define MAX 1000000&lt;br /&gt;
&lt;br /&gt;
int sum;&lt;br /&gt;
 &lt;br /&gt;
void* perform_work(){&lt;br /&gt;
	int i;&lt;br /&gt;
	&lt;br /&gt;
	for(i = 0; i &amp;lt; MAX; i++) sum = sum + 1;&lt;br /&gt;
  	/* optionally: insert more useful stuff here */ &lt;br /&gt;
  	return NULL;&lt;br /&gt;
}&lt;br /&gt;
 &lt;br /&gt;
int main( int argc, char** argv ){&lt;br /&gt;
	pthread_t threads[NUM_THREADS];&lt;br /&gt;
  	int thread_args[NUM_THREADS];&lt;br /&gt;
  	int result_code;&lt;br /&gt;
  	unsigned index;&lt;br /&gt;
 &lt;br /&gt;
	sum = 0;&lt;br /&gt;
  	// create all threads one by one&lt;br /&gt;
  	for(index = 0; index &amp;lt; NUM_THREADS; ++index ){&lt;br /&gt;
    		thread_args[index] = index;&lt;br /&gt;
    		printf(&amp;quot;In main: creating thread %d\n&amp;quot;, index);&lt;br /&gt;
    		result_code = pthread_create(&amp;amp;threads[index], NULL, perform_work, NULL);&lt;br /&gt;
    		assert(!result_code);&lt;br /&gt;
  	} &lt;br /&gt;
  	// wait for each thread to complete&lt;br /&gt;
  	for(index = 0; index &amp;lt; NUM_THREADS; ++index ){&lt;br /&gt;
    		// block until thread 'index' completes&lt;br /&gt;
    		result_code = pthread_join(threads[index], NULL);&lt;br /&gt;
    		assert(!result_code);&lt;br /&gt;
    		printf(&amp;quot;In main: thread %d has completed\n&amp;quot;, index);&lt;br /&gt;
   	}&lt;br /&gt;
 &lt;br /&gt;
   	printf(&amp;quot;In main: All threads completed successfully\n&amp;quot; );&lt;br /&gt;
	printf(&amp;quot;Result: %d\n&amp;quot;, sum);&lt;br /&gt;
   	exit( EXIT_SUCCESS );&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Prima di discutere l'output generato dall'esecuzione di questo codice, è opportuno introdurre, brevemente, gli strumenti utilizzati.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
'''Pthread (POSIX thread)'''&lt;br /&gt;
&lt;br /&gt;
Citazione dal Silberschatz, Galvin, Gagne:&lt;br /&gt;
''Pthread si riferisce allo standard POSIX (IEEE 1003.1c) che definisce una API per la creazione e sincronizzazione dei thread.''&lt;br /&gt;
&lt;br /&gt;
Le istruzioni fondamentali sono:&lt;br /&gt;
* '''int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);''' che crea un thread;&lt;br /&gt;
* '''int pthread_join(pthread_t thread, void **retval);''' che aspetta che un thread sia completato prima di continuare l'esecuzione.&lt;br /&gt;
&lt;br /&gt;
Da ricordare che, per utilizzare pthread, va incluso l'header ''&amp;lt;pthread.h&amp;gt;'' e durante la compilazione va inclusa la libreria esterna tramite l'opzione ''-pthread''.&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Lezione del 6 ottobre 2017 ==&lt;br /&gt;
== Lezione del 10 ottobre 2017 ==&lt;br /&gt;
== Lezione del 13 ottobre 2017 ==&lt;br /&gt;
== Lezione del 17 ottobre 2017 ==&lt;br /&gt;
== Lezione del 20 ottobre 2017 ==&lt;br /&gt;
== Lezione del 24 ottobre 2017 ==&lt;br /&gt;
== Lezione del 27 ottobre 2017 ==&lt;br /&gt;
== Lezione del 31 ottobre 2017 ==&lt;br /&gt;
== Lezione del 3 novembre 2017 ==&lt;br /&gt;
== Lezione del 7 novembre 2017 ==&lt;br /&gt;
== Lezione del 10 novembre 2017 ==&lt;br /&gt;
== Lezione del 14 novembre 2017 ==&lt;br /&gt;
== Lezione del 17 novembre 2017 ==&lt;br /&gt;
== Lezione del 21 novembre 2017 ==&lt;br /&gt;
== Lezione del 24 novembre 2017 ==&lt;br /&gt;
== Lezione del 28 novembre 2017 ==&lt;br /&gt;
== Lezione del 1 dicembre 2017 ==&lt;br /&gt;
== Lezione del 5 dicembre 2017 ==&lt;br /&gt;
== Lezione del 12 dicembre 2017 ==&lt;br /&gt;
== Lezione del 15 dicembre 2017 ==&lt;/div&gt;</summary>
		<author><name>Matt</name></author>
	</entry>
	<entry>
		<id>https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_I_semestre&amp;diff=1999</id>
		<title>Lezioni Anno Accademico 2017/18 I semestre</title>
		<link rel="alternate" type="text/html" href="https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_I_semestre&amp;diff=1999"/>
		<updated>2017-10-04T12:51:04Z</updated>

		<summary type="html">&lt;p&gt;Matt: /* Lezione del 3 ottobre 2017 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;''scrivete qui idee, riassunti dei concetti espressi, commenti approfondimenti sulle lezioni.''&lt;br /&gt;
&lt;br /&gt;
== Lezione del 26 settembre 2017 ==&lt;br /&gt;
'''Titolo della lezione: W&amp;amp;#x2074; H Y'''&lt;br /&gt;
&lt;br /&gt;
* What:&lt;br /&gt;
* Who:&lt;br /&gt;
----&lt;br /&gt;
Noi studenti ed &amp;quot;entrambi&amp;quot; i professori.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* When:&lt;br /&gt;
----&lt;br /&gt;
Il corso ha durata annuale. Verrà svolto ogni martedì e venerdì dalle 15:30 alle 18:30.&lt;br /&gt;
* La lezione del martedì sarà dedicata alla programmazione concorrente;&lt;br /&gt;
* La lezione del venerdì sarà dedicata alla parte generale. Terminerà circa 15 minuti prima dell'orario stabilito.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* Where:&lt;br /&gt;
----&lt;br /&gt;
Sempre in Aula 1 Ercolani (E1): DISI, Scuole Ercolani, Mura Anteo Zamboni 2B.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* How:&lt;br /&gt;
----&lt;br /&gt;
Il corso si svolgerà tramite:&lt;br /&gt;
* Lezioni frontali in aula.&lt;br /&gt;
* Attività di laboratorio.&lt;br /&gt;
* Esercitazioni.&lt;br /&gt;
&lt;br /&gt;
Inoltre, durante l'ultimo periodo di lezioni del secondo semestre, vi sarà un periodo di ripasso del programma tramite esercizi, in vista dell'esame.&lt;br /&gt;
Durante la lezione del 3/10 sono state indicate alcune propedeuticità del corso. tra cui: programmazione, algoritmi e architettura degli elaboratori in primis.&lt;br /&gt;
Importanti sono anche i corsi di Analisi matematica e Algebra e geometria. Per farla breve, il primo anno è propedeutico al secondo.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* Why:&lt;br /&gt;
----&lt;br /&gt;
* '''Fonti e strumenti del corso:''' wiki creata di gruppo, lezioni frontali e esercitazioni.&lt;br /&gt;
**Non esiste un vero e proprio testo consigliato. Tutte le informazioni sono date DURANTE il corso.&lt;br /&gt;
** Per domande specifiche scrivere su mailing list (so@cs.unibo.it);&lt;br /&gt;
** Per esercizi, appunti e programma svolto rivolgersi al wiki (so.v2.cs.unibo.it);&lt;br /&gt;
** Per live streaming (www.cs.unibo.it/~renzo/live/).&lt;br /&gt;
* '''Modalit&amp;amp;agrave; di esame:''' esame scritto (diviso in due parti: una parte generale e una di programmazione concorrente) + progetto + orale (facoltativo), possibilità di dare solo lo scritto per ottenere un massimo di 18. La modalità per studenti in &amp;quot;difficoltà&amp;quot; è disponibile solo fino agli appelli autunnali.&lt;br /&gt;
** Si parlerà del progetto indicativamente a partire da Dicembre 2017.&lt;br /&gt;
** L'orale può essere sostenuto da chi vuole migliorare (peggiorare) il voto ottenuto, o da chi vuole ottenere la lode.&lt;br /&gt;
* '''Orario di ricevimento per il primo semestre (fino al 15/09/17):''' martedì alle 11:30.&lt;br /&gt;
* Universit&amp;amp;agrave; = docenti + studenti.&lt;br /&gt;
* Informatica = come generare informazione automatica.&lt;br /&gt;
* Hardware, Software, Elaborazione, Comunicazione, Memorizzazione, Digitale/Analogico.&lt;br /&gt;
La principale distinzione che facciamo tra '''dato''' e '''informazione''' è che il dato, da solo, è privo di significato. Se, tuttavia, viene interpretato in un particolare contesto allora può diventare informazione significativa per chi sta interpretando i dati.&lt;br /&gt;
&lt;br /&gt;
Un '''algoritmo''' è una o più sequenze non ambigue di passi che, dato un problema, ci permette di risolverlo. &lt;br /&gt;
&lt;br /&gt;
Il nostro algoritmo, scritto in un qualche linguaggio formale, diventa un '''programma''' (sostanzialmente un testo, un insieme di istruzioni).&lt;br /&gt;
&lt;br /&gt;
Un linguaggio è un entità software, ed è definito come una quadrupla (alfabeto, sintassi, lessico, semantica) dove:&lt;br /&gt;
*l'''alfabeto'' è l'insieme di simboli che compone il linguaggio.&lt;br /&gt;
*il ''lessico'' si può vedere come una funzione che va dai simboli a un booleano e ci dice quali sono le frasi ben formate.&lt;br /&gt;
*la ''sintassi'' determina quali delle sequenze scritte sono effettivamente corrette.&lt;br /&gt;
*la ''semantica'' associa un significato alle parole ben formate.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 29 settembre 2017 ==&lt;br /&gt;
'''Cos'è un sistema operativo e un po' di storia'''&lt;br /&gt;
&lt;br /&gt;
Un ''sistema operativo'' è un programma che gestisce i processi, le risorse e interfaccia le applicazioni con l'hardware dell'elaboratore.&lt;br /&gt;
&lt;br /&gt;
In particolare, il sistema operativo (laddove esiste) è il primo processo ad essere attivato e resta in vita fino allo spegnimento del calcolatore o al sopraggiungere di un errore fatale per il sistema.&lt;br /&gt;
&lt;br /&gt;
A cosa serve un sistema operativo?&lt;br /&gt;
Principalmente, ha i seguenti scopi:&lt;br /&gt;
# Facilitare l'utilizzo del sistema (senza S.O. sarebbe a carico del programmatore conoscere tutti i linguaggi con cui si comunica con l'architettura della macchina);&lt;br /&gt;
# Rendere affidabile, protetto e sicuro l'utilizzo del sistema (e.g. un processo potrebbe recar danno all'intero sistema se non controllato o gestito, potrebbe ignorare i permessi di visualizzazione di un file);&lt;br /&gt;
# Astrarre l'hardware (e.g. filesystem);&lt;br /&gt;
# Garantire l'efficienza (e.g. non tenere la CPU in idle adottando opportuni algoritmi di scheduling);&lt;br /&gt;
# Assicurare portabilità ;&lt;br /&gt;
&lt;br /&gt;
Un approccio molto usato è il cosiddetto approccio a strati (&amp;quot;a livelli&amp;quot;) in cui ogni strato utilizza i servizi forniti al livello inferiore e ne fornisce di nuovi a quello superiore.&lt;br /&gt;
Astraendo il nostro calcolatore possiamo piazzare al livello più basso l'hardware e, subito sopra, il sistema operativo. I due layer comunicano usando il linguaggio ISA (Instruction Set Architecture), nativo della CPU stessa, al quale si aggiungono quelli che permettono di comunicare con i vari controllori dei dispositivi come la scheda di rete, la stampante, etc. Sopra il livello del sistema operativo possiamo collocare quello delle librerie e, infine, quello degli applicativi.&lt;br /&gt;
&lt;br /&gt;
* '''Systemcall''' = meccanismo usato da un processo per richiedere al SO una qualche funzionalità a livello kernel. Un esempio è la funzione printf() del linguaggio C (stampa a video) che utilizza la systemcall write() (una delle systemcall per la gestione dei dispositivi).&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
----&lt;br /&gt;
'''Storia dei sistemi operativi'''&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''zero''' (1800 ca.) possiamo includere Babbage con la sua macchina analitica e lady Ada Lovelace. [[File:290px-AnalyticalMachine Babbage London.jpg |200px|thumb|right|Macchina analitica di Babbage]]&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''uno''' vediamo la comparsa delle valvole con tutti i problemi connessi. Non c'erano utilizzatori delle macchine, le stesse persone che le costruivano erano anche programmatori e fruitori delle stesse.&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''due''' (fine anni '60) arriva il transistor. Quest'ultimo è molto più veloce e piccolo della valvola e, soprattutto, meno incline a guasti. Le macchine iniziano ad essere più economiche grazie alla possibilità di avere un'economia di scala e quindi accessibili al grande pubblico. Questo fa si che, in queste prime fasi, i costruttori non siano più gli unici utilizzatori. [[File:Transistors.jpg |300px|thumb|right|Diversi tipi di Transistor]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
A questo punto poiché, pur essendo diventata più accessibile, una macchina costava ancora molto, nasce l'esigenza di non lasciare mai un processore senza lavoro (al fine di massimizzare i ricavi) e di condividere i dati.&lt;br /&gt;
In questa fase, infatti, abbiamo sistemi operativi di tipo ''batch'' (che collezionano tutto l'input all'inizio per calcolare e restituire l'output) dove l'inserimento di programmi e dati veniva fatto tramite schede perforate e non c'era interazione. Dai sistemi batch nasce lo ''SPOOL'' (Simultaneous Peripheral Operations On-Line).&lt;br /&gt;
&lt;br /&gt;
Si comincia a pensare di utilizzare i tempi di I/O per poter eseguire altri processi. Questo però solleva almeno due problematiche: da un lato serve un modo per sapere quando un input è davvero finito mentre, dall'altro, bisogna fare in modo che un processo che non richieda I/O non occupi la CPU per un tempo indefinito.&lt;br /&gt;
Per la prima problematica la soluzione è rappresentata dall'introduzione degli '''interrupt''', segnali elettrici inviati alla CPU alla fine di ogni input.&lt;br /&gt;
Per la seconda, invece, è stato introdotto il cosiddetto '''interval timer''' che non è altro che un dispositivo che invia interrupt dopo un determinato quanto di tempo assicurando che un processo non occupi per troppo tempo il processore.&lt;br /&gt;
Questo consente di realizzare sistemi time sharing (il cui più semplice algoritmo è il round-robin) dove un processo può essere in uno dei seguenti tre stati:&lt;br /&gt;
*'''READY''' pronto per essere eseguito ma, il processore è già occupato;&lt;br /&gt;
*'''RUNNING''' in esecuzione;&lt;br /&gt;
*'''WAIT''' in attesa di I/O.&lt;br /&gt;
&lt;br /&gt;
[[File:Dds.png]]&lt;br /&gt;
&lt;br /&gt;
Nella '''quarta''' generazione (anni '70 ca.) vediamo la nascita di molte innovazioni importanti. Si riesce a rimpicciolire di molto i processori facendoli divenire a tutti gli effetti micro-processori. Nei laboratori Bell nasce Unix, un sistema operativo rivoluzionario con time sharing e clonazione dei processi (un processo non viene mai creato dal nulla ma viene prima clonato da uno già esistente e poi, alla copia, viene fatto eseguire un programma). A causa di problemi di portabilità da PDP-9, macchina su cui Unix è nato, a PDP-11 nasce il linguaggio C e il suo compilatore (scritto anch'esso in C).&lt;br /&gt;
Mentre Apple I &amp;quot;porta&amp;quot; una nuova idea di pc per tutte le famiglie, nascono molte versioni di Unix.&lt;br /&gt;
Per impedire una deriva proprietaria in cui ogni produttore offriva tutto quello che offrivano gli altri ma con qualche feature in più, Richard Stallman fonda il progetto GNU (GNU's Not Unix) per il quale scrive tutta una serie di utilities come il famoso compilatore gcc.&lt;br /&gt;
A GNU, per essere operativo, manca però un kernel. Non volendo aspettare i tempi di sviluppo del kernel che Stallman aveva in mente, Linus Torlvalds scrive il kernel '''Linux''' da cui nasce il sistema operativo GNU/Linux.&lt;br /&gt;
&lt;br /&gt;
Nei tempi più recenti nascono i processori multi-core.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 3 ottobre 2017 ==&lt;br /&gt;
 &lt;br /&gt;
'''Introduzione alla programmazione concorrente: processi, thread e  librerie'''&lt;br /&gt;
&lt;br /&gt;
Quando parliamo di ''sistema concorrente'', ci riferiamo ad un sistema in cui due o più &amp;quot;attori&amp;quot; sono eseguiti parallelamente.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Questi attori possono essere '''processi''' o '''thread'''.&lt;br /&gt;
La differenza sostanziale tra queste due tipologie di attori riguarda la condivisione della memoria e dei dati su cui operano.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Più processi possono eseguire lo stesso programma ma, ognuno di essi, ha i propri dati su cui operare e il suo stato di avanzamento.&amp;lt;br&amp;gt;&lt;br /&gt;
Più thread che vengono eseguiti parallelamente, invece, condividono gli stessi dati su cui operare.&amp;lt;br&amp;gt;&lt;br /&gt;
Codici di esempio:&lt;br /&gt;
&lt;br /&gt;
'''processi'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=C&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;unistd.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int main(int argc, char *argv[]){&lt;br /&gt;
   if(fork()) printf(&amp;quot;Yes&amp;quot;);&lt;br /&gt;
   else printf(&amp;quot;No&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
L'output generato sarà &amp;quot;YesNo&amp;quot; poiché, usando la systemcall ''fork()'', abbiamo creato una nuova copia del processo (un processo figlio). Il processo padre, che ha eseguito con successo la fork(), stamperà &amp;quot;Yes&amp;quot; mentre il figlio stamperà &amp;quot;No&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
'''thread'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=C&amp;gt;&lt;br /&gt;
#include &amp;lt;pthread.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
#include &amp;lt;assert.h&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
#define NUM_THREADS     5&lt;br /&gt;
#define MAX 1000000&lt;br /&gt;
&lt;br /&gt;
int sum;&lt;br /&gt;
 &lt;br /&gt;
void* perform_work(){&lt;br /&gt;
	int i;&lt;br /&gt;
	&lt;br /&gt;
	for(i = 0; i &amp;lt; MAX; i++) sum = sum + 1;&lt;br /&gt;
  	/* optionally: insert more useful stuff here */ &lt;br /&gt;
  	return NULL;&lt;br /&gt;
}&lt;br /&gt;
 &lt;br /&gt;
int main( int argc, char** argv ){&lt;br /&gt;
	pthread_t threads[NUM_THREADS];&lt;br /&gt;
  	int thread_args[NUM_THREADS];&lt;br /&gt;
  	int result_code;&lt;br /&gt;
  	unsigned index;&lt;br /&gt;
 &lt;br /&gt;
	sum = 0;&lt;br /&gt;
  	// create all threads one by one&lt;br /&gt;
  	for(index = 0; index &amp;lt; NUM_THREADS; ++index ){&lt;br /&gt;
    		thread_args[index] = index;&lt;br /&gt;
    		printf(&amp;quot;In main: creating thread %d\n&amp;quot;, index);&lt;br /&gt;
    		result_code = pthread_create(&amp;amp;threads[index], NULL, perform_work, NULL);&lt;br /&gt;
    		assert(!result_code);&lt;br /&gt;
  	} &lt;br /&gt;
  	// wait for each thread to complete&lt;br /&gt;
  	for(index = 0; index &amp;lt; NUM_THREADS; ++index ){&lt;br /&gt;
    		// block until thread 'index' completes&lt;br /&gt;
    		result_code = pthread_join(threads[index], NULL);&lt;br /&gt;
    		assert(!result_code);&lt;br /&gt;
    		printf(&amp;quot;In main: thread %d has completed\n&amp;quot;, index);&lt;br /&gt;
   	}&lt;br /&gt;
 &lt;br /&gt;
   	printf(&amp;quot;In main: All threads completed successfully\n&amp;quot; );&lt;br /&gt;
	printf(&amp;quot;Result: %d\n&amp;quot;, sum);&lt;br /&gt;
   	exit( EXIT_SUCCESS );&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Lezione del 6 ottobre 2017 ==&lt;br /&gt;
== Lezione del 10 ottobre 2017 ==&lt;br /&gt;
== Lezione del 13 ottobre 2017 ==&lt;br /&gt;
== Lezione del 17 ottobre 2017 ==&lt;br /&gt;
== Lezione del 20 ottobre 2017 ==&lt;br /&gt;
== Lezione del 24 ottobre 2017 ==&lt;br /&gt;
== Lezione del 27 ottobre 2017 ==&lt;br /&gt;
== Lezione del 31 ottobre 2017 ==&lt;br /&gt;
== Lezione del 3 novembre 2017 ==&lt;br /&gt;
== Lezione del 7 novembre 2017 ==&lt;br /&gt;
== Lezione del 10 novembre 2017 ==&lt;br /&gt;
== Lezione del 14 novembre 2017 ==&lt;br /&gt;
== Lezione del 17 novembre 2017 ==&lt;br /&gt;
== Lezione del 21 novembre 2017 ==&lt;br /&gt;
== Lezione del 24 novembre 2017 ==&lt;br /&gt;
== Lezione del 28 novembre 2017 ==&lt;br /&gt;
== Lezione del 1 dicembre 2017 ==&lt;br /&gt;
== Lezione del 5 dicembre 2017 ==&lt;br /&gt;
== Lezione del 12 dicembre 2017 ==&lt;br /&gt;
== Lezione del 15 dicembre 2017 ==&lt;/div&gt;</summary>
		<author><name>Matt</name></author>
	</entry>
	<entry>
		<id>https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_I_semestre&amp;diff=1998</id>
		<title>Lezioni Anno Accademico 2017/18 I semestre</title>
		<link rel="alternate" type="text/html" href="https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_I_semestre&amp;diff=1998"/>
		<updated>2017-10-04T12:10:02Z</updated>

		<summary type="html">&lt;p&gt;Matt: /* Lezione del 3 ottobre 2017 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;''scrivete qui idee, riassunti dei concetti espressi, commenti approfondimenti sulle lezioni.''&lt;br /&gt;
&lt;br /&gt;
== Lezione del 26 settembre 2017 ==&lt;br /&gt;
'''Titolo della lezione: W&amp;amp;#x2074; H Y'''&lt;br /&gt;
&lt;br /&gt;
* What:&lt;br /&gt;
* Who:&lt;br /&gt;
----&lt;br /&gt;
Noi studenti ed &amp;quot;entrambi&amp;quot; i professori.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* When:&lt;br /&gt;
----&lt;br /&gt;
Il corso ha durata annuale. Verrà svolto ogni martedì e venerdì dalle 15:30 alle 18:30.&lt;br /&gt;
* La lezione del martedì sarà dedicata alla programmazione concorrente;&lt;br /&gt;
* La lezione del venerdì sarà dedicata alla parte generale. Terminerà circa 15 minuti prima dell'orario stabilito.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* Where:&lt;br /&gt;
----&lt;br /&gt;
Sempre in Aula 1 Ercolani (E1): DISI, Scuole Ercolani, Mura Anteo Zamboni 2B.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* How:&lt;br /&gt;
----&lt;br /&gt;
Il corso si svolgerà tramite:&lt;br /&gt;
* Lezioni frontali in aula.&lt;br /&gt;
* Attività di laboratorio.&lt;br /&gt;
* Esercitazioni.&lt;br /&gt;
&lt;br /&gt;
Inoltre, durante l'ultimo periodo di lezioni del secondo semestre, vi sarà un periodo di ripasso del programma tramite esercizi, in vista dell'esame.&lt;br /&gt;
Durante la lezione del 3/10 sono state indicate alcune propedeuticità del corso. tra cui: programmazione, algoritmi e architettura degli elaboratori in primis.&lt;br /&gt;
Importanti sono anche i corsi di Analisi matematica e Algebra e geometria. Per farla breve, il primo anno è propedeutico al secondo.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* Why:&lt;br /&gt;
----&lt;br /&gt;
* '''Fonti e strumenti del corso:''' wiki creata di gruppo, lezioni frontali e esercitazioni.&lt;br /&gt;
**Non esiste un vero e proprio testo consigliato. Tutte le informazioni sono date DURANTE il corso.&lt;br /&gt;
** Per domande specifiche scrivere su mailing list (so@cs.unibo.it);&lt;br /&gt;
** Per esercizi, appunti e programma svolto rivolgersi al wiki (so.v2.cs.unibo.it);&lt;br /&gt;
** Per live streaming (www.cs.unibo.it/~renzo/live/).&lt;br /&gt;
* '''Modalit&amp;amp;agrave; di esame:''' esame scritto (diviso in due parti: una parte generale e una di programmazione concorrente) + progetto + orale (facoltativo), possibilità di dare solo lo scritto per ottenere un massimo di 18. La modalità per studenti in &amp;quot;difficoltà&amp;quot; è disponibile solo fino agli appelli autunnali.&lt;br /&gt;
** Si parlerà del progetto indicativamente a partire da Dicembre 2017.&lt;br /&gt;
** L'orale può essere sostenuto da chi vuole migliorare (peggiorare) il voto ottenuto, o da chi vuole ottenere la lode.&lt;br /&gt;
* '''Orario di ricevimento per il primo semestre (fino al 15/09/17):''' martedì alle 11:30.&lt;br /&gt;
* Universit&amp;amp;agrave; = docenti + studenti.&lt;br /&gt;
* Informatica = come generare informazione automatica.&lt;br /&gt;
* Hardware, Software, Elaborazione, Comunicazione, Memorizzazione, Digitale/Analogico.&lt;br /&gt;
La principale distinzione che facciamo tra '''dato''' e '''informazione''' è che il dato, da solo, è privo di significato. Se, tuttavia, viene interpretato in un particolare contesto allora può diventare informazione significativa per chi sta interpretando i dati.&lt;br /&gt;
&lt;br /&gt;
Un '''algoritmo''' è una o più sequenze non ambigue di passi che, dato un problema, ci permette di risolverlo. &lt;br /&gt;
&lt;br /&gt;
Il nostro algoritmo, scritto in un qualche linguaggio formale, diventa un '''programma''' (sostanzialmente un testo, un insieme di istruzioni).&lt;br /&gt;
&lt;br /&gt;
Un linguaggio è un entità software, ed è definito come una quadrupla (alfabeto, sintassi, lessico, semantica) dove:&lt;br /&gt;
*l'''alfabeto'' è l'insieme di simboli che compone il linguaggio.&lt;br /&gt;
*il ''lessico'' si può vedere come una funzione che va dai simboli a un booleano e ci dice quali sono le frasi ben formate.&lt;br /&gt;
*la ''sintassi'' determina quali delle sequenze scritte sono effettivamente corrette.&lt;br /&gt;
*la ''semantica'' associa un significato alle parole ben formate.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 29 settembre 2017 ==&lt;br /&gt;
'''Cos'è un sistema operativo e un po' di storia'''&lt;br /&gt;
&lt;br /&gt;
Un ''sistema operativo'' è un programma che gestisce i processi, le risorse e interfaccia le applicazioni con l'hardware dell'elaboratore.&lt;br /&gt;
&lt;br /&gt;
In particolare, il sistema operativo (laddove esiste) è il primo processo ad essere attivato e resta in vita fino allo spegnimento del calcolatore o al sopraggiungere di un errore fatale per il sistema.&lt;br /&gt;
&lt;br /&gt;
A cosa serve un sistema operativo?&lt;br /&gt;
Principalmente, ha i seguenti scopi:&lt;br /&gt;
# Facilitare l'utilizzo del sistema (senza S.O. sarebbe a carico del programmatore conoscere tutti i linguaggi con cui si comunica con l'architettura della macchina);&lt;br /&gt;
# Rendere affidabile, protetto e sicuro l'utilizzo del sistema (e.g. un processo potrebbe recar danno all'intero sistema se non controllato o gestito, potrebbe ignorare i permessi di visualizzazione di un file);&lt;br /&gt;
# Astrarre l'hardware (e.g. filesystem);&lt;br /&gt;
# Garantire l'efficienza (e.g. non tenere la CPU in idle adottando opportuni algoritmi di scheduling);&lt;br /&gt;
# Assicurare portabilità ;&lt;br /&gt;
&lt;br /&gt;
Un approccio molto usato è il cosiddetto approccio a strati (&amp;quot;a livelli&amp;quot;) in cui ogni strato utilizza i servizi forniti al livello inferiore e ne fornisce di nuovi a quello superiore.&lt;br /&gt;
Astraendo il nostro calcolatore possiamo piazzare al livello più basso l'hardware e, subito sopra, il sistema operativo. I due layer comunicano usando il linguaggio ISA (Instruction Set Architecture), nativo della CPU stessa, al quale si aggiungono quelli che permettono di comunicare con i vari controllori dei dispositivi come la scheda di rete, la stampante, etc. Sopra il livello del sistema operativo possiamo collocare quello delle librerie e, infine, quello degli applicativi.&lt;br /&gt;
&lt;br /&gt;
* '''Systemcall''' = meccanismo usato da un processo per richiedere al SO una qualche funzionalità a livello kernel. Un esempio è la funzione printf() del linguaggio C (stampa a video) che utilizza la systemcall write() (una delle systemcall per la gestione dei dispositivi).&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
----&lt;br /&gt;
'''Storia dei sistemi operativi'''&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''zero''' (1800 ca.) possiamo includere Babbage con la sua macchina analitica e lady Ada Lovelace. [[File:290px-AnalyticalMachine Babbage London.jpg |200px|thumb|right|Macchina analitica di Babbage]]&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''uno''' vediamo la comparsa delle valvole con tutti i problemi connessi. Non c'erano utilizzatori delle macchine, le stesse persone che le costruivano erano anche programmatori e fruitori delle stesse.&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''due''' (fine anni '60) arriva il transistor. Quest'ultimo è molto più veloce e piccolo della valvola e, soprattutto, meno incline a guasti. Le macchine iniziano ad essere più economiche grazie alla possibilità di avere un'economia di scala e quindi accessibili al grande pubblico. Questo fa si che, in queste prime fasi, i costruttori non siano più gli unici utilizzatori. [[File:Transistors.jpg |300px|thumb|right|Diversi tipi di Transistor]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
A questo punto poiché, pur essendo diventata più accessibile, una macchina costava ancora molto, nasce l'esigenza di non lasciare mai un processore senza lavoro (al fine di massimizzare i ricavi) e di condividere i dati.&lt;br /&gt;
In questa fase, infatti, abbiamo sistemi operativi di tipo ''batch'' (che collezionano tutto l'input all'inizio per calcolare e restituire l'output) dove l'inserimento di programmi e dati veniva fatto tramite schede perforate e non c'era interazione. Dai sistemi batch nasce lo ''SPOOL'' (Simultaneous Peripheral Operations On-Line).&lt;br /&gt;
&lt;br /&gt;
Si comincia a pensare di utilizzare i tempi di I/O per poter eseguire altri processi. Questo però solleva almeno due problematiche: da un lato serve un modo per sapere quando un input è davvero finito mentre, dall'altro, bisogna fare in modo che un processo che non richieda I/O non occupi la CPU per un tempo indefinito.&lt;br /&gt;
Per la prima problematica la soluzione è rappresentata dall'introduzione degli '''interrupt''', segnali elettrici inviati alla CPU alla fine di ogni input.&lt;br /&gt;
Per la seconda, invece, è stato introdotto il cosiddetto '''interval timer''' che non è altro che un dispositivo che invia interrupt dopo un determinato quanto di tempo assicurando che un processo non occupi per troppo tempo il processore.&lt;br /&gt;
Questo consente di realizzare sistemi time sharing (il cui più semplice algoritmo è il round-robin) dove un processo può essere in uno dei seguenti tre stati:&lt;br /&gt;
*'''READY''' pronto per essere eseguito ma, il processore è già occupato;&lt;br /&gt;
*'''RUNNING''' in esecuzione;&lt;br /&gt;
*'''WAIT''' in attesa di I/O.&lt;br /&gt;
&lt;br /&gt;
[[File:Dds.png]]&lt;br /&gt;
&lt;br /&gt;
Nella '''quarta''' generazione (anni '70 ca.) vediamo la nascita di molte innovazioni importanti. Si riesce a rimpicciolire di molto i processori facendoli divenire a tutti gli effetti micro-processori. Nei laboratori Bell nasce Unix, un sistema operativo rivoluzionario con time sharing e clonazione dei processi (un processo non viene mai creato dal nulla ma viene prima clonato da uno già esistente e poi, alla copia, viene fatto eseguire un programma). A causa di problemi di portabilità da PDP-9, macchina su cui Unix è nato, a PDP-11 nasce il linguaggio C e il suo compilatore (scritto anch'esso in C).&lt;br /&gt;
Mentre Apple I &amp;quot;porta&amp;quot; una nuova idea di pc per tutte le famiglie, nascono molte versioni di Unix.&lt;br /&gt;
Per impedire una deriva proprietaria in cui ogni produttore offriva tutto quello che offrivano gli altri ma con qualche feature in più, Richard Stallman fonda il progetto GNU (GNU's Not Unix) per il quale scrive tutta una serie di utilities come il famoso compilatore gcc.&lt;br /&gt;
A GNU, per essere operativo, manca però un kernel. Non volendo aspettare i tempi di sviluppo del kernel che Stallman aveva in mente, Linus Torlvalds scrive il kernel '''Linux''' da cui nasce il sistema operativo GNU/Linux.&lt;br /&gt;
&lt;br /&gt;
Nei tempi più recenti nascono i processori multi-core.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 3 ottobre 2017 ==&lt;br /&gt;
 &lt;br /&gt;
'''Introduzione alla programmazione concorrente: processi, thread e  librerie'''&lt;br /&gt;
&lt;br /&gt;
Quando parliamo di ''sistema concorrente'', ci riferiamo ad un sistema in cui due o più &amp;quot;attori&amp;quot; sono eseguiti parallelamente.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Questi attori possono essere '''processi''' o '''thread'''.&lt;br /&gt;
La differenza sostanziale tra queste due tipologie di attori riguarda la condivisione della memoria e dei dati su cui operano.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Più processi possono eseguire lo stesso programma ma, ognuno di essi, ha i propri dati su cui operare e il suo stato di avanzamento.&amp;lt;br&amp;gt;&lt;br /&gt;
Più thread che vengono eseguiti parallelamente, invece, condividono gli stessi dati su cui operare.&amp;lt;br&amp;gt;&lt;br /&gt;
Codici di esempio:&lt;br /&gt;
&lt;br /&gt;
'''processi'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=C&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;unistd.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int main(int argc, char *argv[]){&lt;br /&gt;
   if(fork()) printf(&amp;quot;Yes&amp;quot;);&lt;br /&gt;
   else printf(&amp;quot;No&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Lezione del 6 ottobre 2017 ==&lt;br /&gt;
== Lezione del 10 ottobre 2017 ==&lt;br /&gt;
== Lezione del 13 ottobre 2017 ==&lt;br /&gt;
== Lezione del 17 ottobre 2017 ==&lt;br /&gt;
== Lezione del 20 ottobre 2017 ==&lt;br /&gt;
== Lezione del 24 ottobre 2017 ==&lt;br /&gt;
== Lezione del 27 ottobre 2017 ==&lt;br /&gt;
== Lezione del 31 ottobre 2017 ==&lt;br /&gt;
== Lezione del 3 novembre 2017 ==&lt;br /&gt;
== Lezione del 7 novembre 2017 ==&lt;br /&gt;
== Lezione del 10 novembre 2017 ==&lt;br /&gt;
== Lezione del 14 novembre 2017 ==&lt;br /&gt;
== Lezione del 17 novembre 2017 ==&lt;br /&gt;
== Lezione del 21 novembre 2017 ==&lt;br /&gt;
== Lezione del 24 novembre 2017 ==&lt;br /&gt;
== Lezione del 28 novembre 2017 ==&lt;br /&gt;
== Lezione del 1 dicembre 2017 ==&lt;br /&gt;
== Lezione del 5 dicembre 2017 ==&lt;br /&gt;
== Lezione del 12 dicembre 2017 ==&lt;br /&gt;
== Lezione del 15 dicembre 2017 ==&lt;/div&gt;</summary>
		<author><name>Matt</name></author>
	</entry>
	<entry>
		<id>https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_I_semestre&amp;diff=1993</id>
		<title>Lezioni Anno Accademico 2017/18 I semestre</title>
		<link rel="alternate" type="text/html" href="https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_I_semestre&amp;diff=1993"/>
		<updated>2017-10-03T23:57:34Z</updated>

		<summary type="html">&lt;p&gt;Matt: /* Lezione del 26 settembre 2017 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;''scrivete qui idee, riassunti dei concetti espressi, commenti approfondimenti sulle lezioni.''&lt;br /&gt;
&lt;br /&gt;
== Lezione del 26 settembre 2017 ==&lt;br /&gt;
'''Titolo della lezione: W&amp;amp;#x2074; H Y'''&lt;br /&gt;
&lt;br /&gt;
* What:&lt;br /&gt;
* Who:&lt;br /&gt;
----&lt;br /&gt;
Noi studenti ed &amp;quot;entrambi&amp;quot; i professori.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* When:&lt;br /&gt;
----&lt;br /&gt;
Il corso ha durata annuale. Verrà svolto ogni martedì e venerdì dalle 15:30 alle 18:30.&lt;br /&gt;
* La lezione del martedì sarà dedicata alla programmazione concorrente;&lt;br /&gt;
* La lezione del venerdì sarà dedicata alla parte generale. Terminerà circa 15 minuti prima dell'orario stabilito.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* Where:&lt;br /&gt;
----&lt;br /&gt;
Sempre in Aula 1 Ercolani (E1): DISI, Scuole Ercolani, Mura Anteo Zamboni 2B.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* How:&lt;br /&gt;
----&lt;br /&gt;
Il corso si svolgerà tramite:&lt;br /&gt;
* Lezioni frontali in aula.&lt;br /&gt;
* Attività di laboratorio.&lt;br /&gt;
* Esercitazioni.&lt;br /&gt;
&lt;br /&gt;
Inoltre, durante l'ultimo periodo di lezioni del secondo semestre, vi sarà un periodo di ripasso del programma tramite esercizi, in vista dell'esame.&lt;br /&gt;
Durante la lezione del 3/10 sono state indicate alcune propedeuticità del corso. tra cui: programmazione, algoritmi e architettura degli elaboratori in primis.&lt;br /&gt;
Importanti sono anche i corsi di Analisi matematica e Algebra e geometria. Per farla breve, il primo anno è propedeutico al secondo.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* Why:&lt;br /&gt;
----&lt;br /&gt;
* '''Fonti e strumenti del corso:''' wiki creata di gruppo, lezioni frontali e esercitazioni.&lt;br /&gt;
**Non esiste un vero e proprio testo consigliato. Tutte le informazioni sono date DURANTE il corso.&lt;br /&gt;
** Per domande specifiche scrivere su mailing list (so@cs.unibo.it);&lt;br /&gt;
** Per esercizi, appunti e programma svolto rivolgersi al wiki (so.v2.cs.unibo.it);&lt;br /&gt;
** Per live streaming (www.cs.unibo.it/~renzo/live/).&lt;br /&gt;
* '''Modalit&amp;amp;agrave; di esame:''' esame scritto (diviso in due parti: una parte generale e una di programmazione concorrente) + progetto + orale (facoltativo), possibilità di dare solo lo scritto per ottenere un massimo di 18. La modalità per studenti in &amp;quot;difficoltà&amp;quot; è disponibile solo fino agli appelli autunnali.&lt;br /&gt;
** Si parlerà del progetto indicativamente a partire da Dicembre 2017.&lt;br /&gt;
** L'orale può essere sostenuto da chi vuole migliorare (peggiorare) il voto ottenuto, o da chi vuole ottenere la lode.&lt;br /&gt;
* '''Orario di ricevimento per il primo semestre (fino al 15/09/17):''' martedì alle 11:30.&lt;br /&gt;
* Universit&amp;amp;agrave; = docenti + studenti.&lt;br /&gt;
* Informatica = come generare informazione automatica.&lt;br /&gt;
* Hardware, Software, Elaborazione, Comunicazione, Memorizzazione, Digitale/Analogico.&lt;br /&gt;
La principale distinzione che facciamo tra '''dato''' e '''informazione''' è che il dato, da solo, è privo di significato. Se, tuttavia, viene interpretato in un particolare contesto allora può diventare informazione significativa per chi sta interpretando i dati.&lt;br /&gt;
&lt;br /&gt;
Un '''algoritmo''' è una o più sequenze non ambigue di passi che, dato un problema, ci permette di risolverlo. &lt;br /&gt;
&lt;br /&gt;
Il nostro algoritmo, scritto in un qualche linguaggio formale, diventa un '''programma''' (sostanzialmente un testo, un insieme di istruzioni).&lt;br /&gt;
&lt;br /&gt;
Un linguaggio è un entità software, ed è definito come una quadrupla (alfabeto, sintassi, lessico, semantica) dove:&lt;br /&gt;
*l'''alfabeto'' è l'insieme di simboli che compone il linguaggio.&lt;br /&gt;
*il ''lessico'' si può vedere come una funzione che va dai simboli a un booleano e ci dice quali sono le frasi ben formate.&lt;br /&gt;
*la ''sintassi'' determina quali delle sequenze scritte sono effettivamente corrette.&lt;br /&gt;
*la ''semantica'' associa un significato alle parole ben formate.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 29 settembre 2017 ==&lt;br /&gt;
'''Cos'è un sistema operativo e un po' di storia'''&lt;br /&gt;
&lt;br /&gt;
Un ''sistema operativo'' è un programma che gestisce i processi, le risorse e interfaccia le applicazioni con l'hardware dell'elaboratore.&lt;br /&gt;
&lt;br /&gt;
In particolare, il sistema operativo (laddove esiste) è il primo processo ad essere attivato e resta in vita fino allo spegnimento del calcolatore o al sopraggiungere di un errore fatale per il sistema.&lt;br /&gt;
&lt;br /&gt;
A cosa serve un sistema operativo?&lt;br /&gt;
Principalmente, ha i seguenti scopi:&lt;br /&gt;
# facilitare l'utilizzo del sistema;&lt;br /&gt;
# rendere affidabile, protetto e sicuro l'utilizzo del sistema (e.g. un processo potrebbe recar danno all'intero sistema se non controllato o potrebbe ignorare i permessi di visualizzazione di un file);&lt;br /&gt;
# astrarre l'hardware (e.g. filesystem);&lt;br /&gt;
# garantire l'efficienza (e.g. non tenere la CPU in idle adottando opportuni algoritmi di scheduling);&lt;br /&gt;
# assicurare portabilità;&lt;br /&gt;
&lt;br /&gt;
Un approccio molto usato è il cosiddetto approccio a strati in cui ogni strato utilizza i servizi forniti al livello inferiore e ne fornisce di nuovi a quello superiore.&lt;br /&gt;
Astraendo il nostro calcolatore possiamo piazzare al livello più basso l'hardware e, subito sopra, il sistema operativo. I due layer comunicano usando il linguaggio ISA (Instruction Set Architecture), nativo della CPU stessa, al quale si aggiungono quelli che permettono di comunicare con i vari controllori dei dispositivi come la scheda di rete, la stampante, etc. Sopra il livello del sistema operativo possiamo collocare quello delle librerie e, infine, quello degli applicativi.&lt;br /&gt;
&lt;br /&gt;
* '''Systemcall''' = meccanismo usato da un processo per richiedere al SO una qualche funzionalità a livello kernel. Un esempio è la funzione printf() del linguaggio C (stampa a video) che utilizza la systemcall write() (una delle systemcall per la gestione dei dispositivi).&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
----&lt;br /&gt;
'''Storia dei sistemi operativi'''&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''zero''' (1800 ca.) possiamo include Babbage con la sua macchina analitica e lady Ada Lovelace.&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''uno''' vediamo la comparsa delle valvole con tutti i problemi connessi. Non c'erano utilizzatori delle macchine, le stesse persone che le costruivano erano anche programmatori e fruitori delle stesse.&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''due''' (fine anni '60) arriva il transistor. Quest'ultimo è molto più veloce e piccolo della valvola e, soprattutto, meno incline a guasti. Le macchine iniziano ad essere più economiche grazie alla possibilità di avere un'economia di scala e quindi accessibili al grande pubblico. Questo fa si che, in queste prime fasi, i costruttori non siano più gli unici utilizzatori.&lt;br /&gt;
A questo punto poiché, pur essendo diventata più accessibile, una macchina costava ancora molto, nasce l'esigenza di non lasciare mai un processore senza lavoro (al fine di massimizzare i ricavi) e di condividere i dati.&lt;br /&gt;
In questa fase, infatti, abbiamo sistemi operativi di tipo ''batch'' (che collezionano tutto l'input all'inizio per calcolare e restituire l'output) dove l'inserimento di programmi e dati veniva fatto tramite schede perforate e non c'era interazione. Dai sistemi batch nasce lo ''SPOOL'' (Simultaneous Peripheral Operations On-Line).&lt;br /&gt;
&lt;br /&gt;
Si comincia a pensare di utilizzare i tempi di I/O per poter eseguire altri processi. Questo però solleva almeno due problematiche: da un lato serve un modo per sapere quando un input è davvero finito mentre, dall'altro, bisogna fare in modo che un processo che non richieda I/O non occupi la CPU per un tempo indefinito.&lt;br /&gt;
Per la prima problematica la soluzione è rappresentata dall'introduzione degli '''interrupt''', segnali elettrici inviati alla CPU alla fine di ogni input.&lt;br /&gt;
Per la seconda, invece, è stato introdotto il cosiddetto '''interval timer''' che non è altro che un dispositivo che invia interrupt dopo un determinato quanto di tempo assicurando che un processo non occupi per troppo tempo il processore.&lt;br /&gt;
Questo consente di realizzare sistemi time sharing (il cui più semplice algoritmo è il round-robin) dove un processo può essere in uno dei seguenti tre stati:&lt;br /&gt;
*'''READY''' pronto per essere eseguito ma, il processore è già occupato;&lt;br /&gt;
*'''RUNNING''' in esecuzione;&lt;br /&gt;
*'''WAIT''' in attesa di I/O.&lt;br /&gt;
&lt;br /&gt;
[[File:Dds.png]]&lt;br /&gt;
&lt;br /&gt;
Nella '''quarta''' generazione (anni '70 ca.) vediamo la nascita di molte innovazioni importanti. Si riesce a rimpicciolire di molto i processori facendoli divenire a tutti gli effetti micro-processori. Nei laboratori Bell nasce Unix, un sistema operativo rivoluzionario con time sharing e clonazione dei processi (un processo non viene mai creato dal nulla ma viene prima clonato da uno già esistente e poi, alla copia, viene fatto eseguire un programma). A causa di problemi di portabilità da PDP-9, macchina su cui Unix è nato, a PDP-11 nasce il linguaggio C e il suo compilatore (scritto anch'esso in C).&lt;br /&gt;
Mentre Apple I &amp;quot;porta&amp;quot; una nuova idea di pc per tutte le famiglie, nascono molte versioni di Unix.&lt;br /&gt;
Per impedire una deriva proprietaria in cui ogni produttore offriva tutto quello che offrivano gli altri ma con qualche feature in più, Richard Stallman fonda il progetto GNU (GNU's Not Unix) per il quale scrive tutta una serie di utilities come il famoso compilatore gcc.&lt;br /&gt;
A GNU, per essere operativo, manca però un kernel. Non volendo aspettare i tempi di sviluppo del kernel che Stallman aveva in mente, Linus Torlvalds scrive il kernel '''Linux''' da cui nasce il sistema operativo GNU/Linux.&lt;br /&gt;
&lt;br /&gt;
Nei tempi più recenti nascono i processori multi-core.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 3 ottobre 2017 ==&lt;br /&gt;
&lt;br /&gt;
'''Introduzione alla programmazione concorrente: processi e thread'''&lt;br /&gt;
&lt;br /&gt;
== Lezione del 6 ottobre 2017 ==&lt;br /&gt;
== Lezione del 10 ottobre 2017 ==&lt;br /&gt;
== Lezione del 13 ottobre 2017 ==&lt;br /&gt;
== Lezione del 17 ottobre 2017 ==&lt;br /&gt;
== Lezione del 20 ottobre 2017 ==&lt;br /&gt;
== Lezione del 24 ottobre 2017 ==&lt;br /&gt;
== Lezione del 27 ottobre 2017 ==&lt;br /&gt;
== Lezione del 31 ottobre 2017 ==&lt;br /&gt;
== Lezione del 3 novembre 2017 ==&lt;br /&gt;
== Lezione del 7 novembre 2017 ==&lt;br /&gt;
== Lezione del 10 novembre 2017 ==&lt;br /&gt;
== Lezione del 14 novembre 2017 ==&lt;br /&gt;
== Lezione del 17 novembre 2017 ==&lt;br /&gt;
== Lezione del 21 novembre 2017 ==&lt;br /&gt;
== Lezione del 24 novembre 2017 ==&lt;br /&gt;
== Lezione del 28 novembre 2017 ==&lt;br /&gt;
== Lezione del 1 dicembre 2017 ==&lt;br /&gt;
== Lezione del 5 dicembre 2017 ==&lt;br /&gt;
== Lezione del 12 dicembre 2017 ==&lt;br /&gt;
== Lezione del 15 dicembre 2017 ==&lt;/div&gt;</summary>
		<author><name>Matt</name></author>
	</entry>
	<entry>
		<id>https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_I_semestre&amp;diff=1992</id>
		<title>Lezioni Anno Accademico 2017/18 I semestre</title>
		<link rel="alternate" type="text/html" href="https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_I_semestre&amp;diff=1992"/>
		<updated>2017-10-03T23:52:36Z</updated>

		<summary type="html">&lt;p&gt;Matt: /* Lezione del 3 ottobre 2017 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;''scrivete qui idee, riassunti dei concetti espressi, commenti approfondimenti sulle lezioni.''&lt;br /&gt;
&lt;br /&gt;
== Lezione del 26 settembre 2017 ==&lt;br /&gt;
'''Titolo della lezione: W&amp;amp;#x2074; H Y'''&lt;br /&gt;
&lt;br /&gt;
* What:&lt;br /&gt;
* Who:&lt;br /&gt;
----&lt;br /&gt;
Noi studenti ed &amp;quot;entrambi&amp;quot; i professori.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* When:&lt;br /&gt;
----&lt;br /&gt;
Il corso ha durata annuale. Verrà svolto ogni martedì e venerdì dalle 15:30 alle 18:30.&lt;br /&gt;
* La lezione del martedì sarà dedicata alla programmazione concorrente;&lt;br /&gt;
* La lezione del venerdì sarà dedicata alla parte generale. Terminerà circa 15 minuti prima dell'orario stabilito.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* Where:&lt;br /&gt;
----&lt;br /&gt;
Sempre in Aula 1 Ercolani (E1): DISI, Scuole Ercolani, Mura Anteo Zamboni 2B.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* How:&lt;br /&gt;
----&lt;br /&gt;
Il corso si svolgerà tramite:&lt;br /&gt;
* Lezioni frontali in aula.&lt;br /&gt;
* Attività di laboratorio.&lt;br /&gt;
* Esercitazioni.&lt;br /&gt;
&lt;br /&gt;
Inoltre, durante l'ultimo periodo di lezioni del secondo semestre, vi sarà un periodo di ripasso del programma tramite esercizi, in vista dell'esame.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* Why:&lt;br /&gt;
----&lt;br /&gt;
* '''Fonti e strumenti del corso:''' wiki creata di gruppo, lezioni frontali e esercitazioni.&lt;br /&gt;
**Non esiste un vero e proprio testo consigliato. Tutte le informazioni sono date DURANTE il corso.&lt;br /&gt;
** Per domande specifiche scrivere su mailing list (so@cs.unibo.it);&lt;br /&gt;
** Per esercizi, appunti e programma svolto rivolgersi al wiki (so.v2.cs.unibo.it);&lt;br /&gt;
** Per live streaming (www.cs.unibo.it/~renzo/live/).&lt;br /&gt;
* '''Modalit&amp;amp;agrave; di esame:''' esame scritto (diviso in due parti: una parte generale e una di programmazione concorrente) + progetto + orale (facoltativo), possibilità di dare solo lo scritto per ottenere un massimo di 18. La modalità per studenti in &amp;quot;difficoltà&amp;quot; è disponibile solo fino agli appelli autunnali.&lt;br /&gt;
** Si parlerà del progetto indicativamente a partire da Dicembre 2017.&lt;br /&gt;
** L'orale può essere sostenuto da chi vuole migliorare (peggiorare) il voto ottenuto, o da chi vuole ottenere la lode.&lt;br /&gt;
* '''Orario di ricevimento per il primo semestre (fino al 15/09/17):''' martedì alle 11:30.&lt;br /&gt;
* Universit&amp;amp;agrave; = docenti + studenti.&lt;br /&gt;
* Informatica = come generare informazione automatica.&lt;br /&gt;
* Hardware, Software, Elaborazione, Comunicazione, Memorizzazione, Digitale/Analogico.&lt;br /&gt;
La principale distinzione che facciamo tra '''dato''' e '''informazione''' è che il dato, da solo, è privo di significato. Se, tuttavia, viene interpretato in un particolare contesto allora può diventare informazione significativa per chi sta interpretando i dati.&lt;br /&gt;
&lt;br /&gt;
Un '''algoritmo''' è una o più sequenze non ambigue di passi che, dato un problema, ci permette di risolverlo. &lt;br /&gt;
&lt;br /&gt;
Il nostro algoritmo, scritto in un qualche linguaggio formale, diventa un '''programma''' (sostanzialmente un testo, un insieme di istruzioni).&lt;br /&gt;
&lt;br /&gt;
Un linguaggio è un entità software, ed è definito come una quadrupla (alfabeto, sintassi, lessico, semantica) dove:&lt;br /&gt;
*l'''alfabeto'' è l'insieme di simboli che compone il linguaggio.&lt;br /&gt;
*il ''lessico'' si può vedere come una funzione che va dai simboli a un booleano e ci dice quali sono le frasi ben formate.&lt;br /&gt;
*la ''sintassi'' determina quali delle sequenze scritte sono effettivamente corrette.&lt;br /&gt;
*la ''semantica'' associa un significato alle parole ben formate.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 29 settembre 2017 ==&lt;br /&gt;
'''Cos'è un sistema operativo e un po' di storia'''&lt;br /&gt;
&lt;br /&gt;
Un ''sistema operativo'' è un programma che gestisce i processi, le risorse e interfaccia le applicazioni con l'hardware dell'elaboratore.&lt;br /&gt;
&lt;br /&gt;
In particolare, il sistema operativo (laddove esiste) è il primo processo ad essere attivato e resta in vita fino allo spegnimento del calcolatore o al sopraggiungere di un errore fatale per il sistema.&lt;br /&gt;
&lt;br /&gt;
A cosa serve un sistema operativo?&lt;br /&gt;
Principalmente, ha i seguenti scopi:&lt;br /&gt;
# facilitare l'utilizzo del sistema;&lt;br /&gt;
# rendere affidabile, protetto e sicuro l'utilizzo del sistema (e.g. un processo potrebbe recar danno all'intero sistema se non controllato o potrebbe ignorare i permessi di visualizzazione di un file);&lt;br /&gt;
# astrarre l'hardware (e.g. filesystem);&lt;br /&gt;
# garantire l'efficienza (e.g. non tenere la CPU in idle adottando opportuni algoritmi di scheduling);&lt;br /&gt;
# assicurare portabilità;&lt;br /&gt;
&lt;br /&gt;
Un approccio molto usato è il cosiddetto approccio a strati in cui ogni strato utilizza i servizi forniti al livello inferiore e ne fornisce di nuovi a quello superiore.&lt;br /&gt;
Astraendo il nostro calcolatore possiamo piazzare al livello più basso l'hardware e, subito sopra, il sistema operativo. I due layer comunicano usando il linguaggio ISA (Instruction Set Architecture), nativo della CPU stessa, al quale si aggiungono quelli che permettono di comunicare con i vari controllori dei dispositivi come la scheda di rete, la stampante, etc. Sopra il livello del sistema operativo possiamo collocare quello delle librerie e, infine, quello degli applicativi.&lt;br /&gt;
&lt;br /&gt;
* '''Systemcall''' = meccanismo usato da un processo per richiedere al SO una qualche funzionalità a livello kernel. Un esempio è la funzione printf() del linguaggio C (stampa a video) che utilizza la systemcall write() (una delle systemcall per la gestione dei dispositivi).&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
----&lt;br /&gt;
'''Storia dei sistemi operativi'''&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''zero''' (1800 ca.) possiamo include Babbage con la sua macchina analitica e lady Ada Lovelace.&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''uno''' vediamo la comparsa delle valvole con tutti i problemi connessi. Non c'erano utilizzatori delle macchine, le stesse persone che le costruivano erano anche programmatori e fruitori delle stesse.&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''due''' (fine anni '60) arriva il transistor. Quest'ultimo è molto più veloce e piccolo della valvola e, soprattutto, meno incline a guasti. Le macchine iniziano ad essere più economiche grazie alla possibilità di avere un'economia di scala e quindi accessibili al grande pubblico. Questo fa si che, in queste prime fasi, i costruttori non siano più gli unici utilizzatori.&lt;br /&gt;
A questo punto poiché, pur essendo diventata più accessibile, una macchina costava ancora molto, nasce l'esigenza di non lasciare mai un processore senza lavoro (al fine di massimizzare i ricavi) e di condividere i dati.&lt;br /&gt;
In questa fase, infatti, abbiamo sistemi operativi di tipo ''batch'' (che collezionano tutto l'input all'inizio per calcolare e restituire l'output) dove l'inserimento di programmi e dati veniva fatto tramite schede perforate e non c'era interazione. Dai sistemi batch nasce lo ''SPOOL'' (Simultaneous Peripheral Operations On-Line).&lt;br /&gt;
&lt;br /&gt;
Si comincia a pensare di utilizzare i tempi di I/O per poter eseguire altri processi. Questo però solleva almeno due problematiche: da un lato serve un modo per sapere quando un input è davvero finito mentre, dall'altro, bisogna fare in modo che un processo che non richieda I/O non occupi la CPU per un tempo indefinito.&lt;br /&gt;
Per la prima problematica la soluzione è rappresentata dall'introduzione degli '''interrupt''', segnali elettrici inviati alla CPU alla fine di ogni input.&lt;br /&gt;
Per la seconda, invece, è stato introdotto il cosiddetto '''interval timer''' che non è altro che un dispositivo che invia interrupt dopo un determinato quanto di tempo assicurando che un processo non occupi per troppo tempo il processore.&lt;br /&gt;
Questo consente di realizzare sistemi time sharing (il cui più semplice algoritmo è il round-robin) dove un processo può essere in uno dei seguenti tre stati:&lt;br /&gt;
*'''READY''' pronto per essere eseguito ma, il processore è già occupato;&lt;br /&gt;
*'''RUNNING''' in esecuzione;&lt;br /&gt;
*'''WAIT''' in attesa di I/O.&lt;br /&gt;
&lt;br /&gt;
[[File:Dds.png]]&lt;br /&gt;
&lt;br /&gt;
Nella '''quarta''' generazione (anni '70 ca.) vediamo la nascita di molte innovazioni importanti. Si riesce a rimpicciolire di molto i processori facendoli divenire a tutti gli effetti micro-processori. Nei laboratori Bell nasce Unix, un sistema operativo rivoluzionario con time sharing e clonazione dei processi (un processo non viene mai creato dal nulla ma viene prima clonato da uno già esistente e poi, alla copia, viene fatto eseguire un programma). A causa di problemi di portabilità da PDP-9, macchina su cui Unix è nato, a PDP-11 nasce il linguaggio C e il suo compilatore (scritto anch'esso in C).&lt;br /&gt;
Mentre Apple I &amp;quot;porta&amp;quot; una nuova idea di pc per tutte le famiglie, nascono molte versioni di Unix.&lt;br /&gt;
Per impedire una deriva proprietaria in cui ogni produttore offriva tutto quello che offrivano gli altri ma con qualche feature in più, Richard Stallman fonda il progetto GNU (GNU's Not Unix) per il quale scrive tutta una serie di utilities come il famoso compilatore gcc.&lt;br /&gt;
A GNU, per essere operativo, manca però un kernel. Non volendo aspettare i tempi di sviluppo del kernel che Stallman aveva in mente, Linus Torlvalds scrive il kernel '''Linux''' da cui nasce il sistema operativo GNU/Linux.&lt;br /&gt;
&lt;br /&gt;
Nei tempi più recenti nascono i processori multi-core.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 3 ottobre 2017 ==&lt;br /&gt;
&lt;br /&gt;
'''Introduzione alla programmazione concorrente: processi e thread'''&lt;br /&gt;
&lt;br /&gt;
== Lezione del 6 ottobre 2017 ==&lt;br /&gt;
== Lezione del 10 ottobre 2017 ==&lt;br /&gt;
== Lezione del 13 ottobre 2017 ==&lt;br /&gt;
== Lezione del 17 ottobre 2017 ==&lt;br /&gt;
== Lezione del 20 ottobre 2017 ==&lt;br /&gt;
== Lezione del 24 ottobre 2017 ==&lt;br /&gt;
== Lezione del 27 ottobre 2017 ==&lt;br /&gt;
== Lezione del 31 ottobre 2017 ==&lt;br /&gt;
== Lezione del 3 novembre 2017 ==&lt;br /&gt;
== Lezione del 7 novembre 2017 ==&lt;br /&gt;
== Lezione del 10 novembre 2017 ==&lt;br /&gt;
== Lezione del 14 novembre 2017 ==&lt;br /&gt;
== Lezione del 17 novembre 2017 ==&lt;br /&gt;
== Lezione del 21 novembre 2017 ==&lt;br /&gt;
== Lezione del 24 novembre 2017 ==&lt;br /&gt;
== Lezione del 28 novembre 2017 ==&lt;br /&gt;
== Lezione del 1 dicembre 2017 ==&lt;br /&gt;
== Lezione del 5 dicembre 2017 ==&lt;br /&gt;
== Lezione del 12 dicembre 2017 ==&lt;br /&gt;
== Lezione del 15 dicembre 2017 ==&lt;/div&gt;</summary>
		<author><name>Matt</name></author>
	</entry>
	<entry>
		<id>https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_I_semestre&amp;diff=1979</id>
		<title>Lezioni Anno Accademico 2017/18 I semestre</title>
		<link rel="alternate" type="text/html" href="https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_I_semestre&amp;diff=1979"/>
		<updated>2017-09-29T20:57:08Z</updated>

		<summary type="html">&lt;p&gt;Matt: /* Lezione del 29 settembre 2017 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;''scrivete qui idee, riassunti dei concetti espressi, commenti approfondimenti sulle lezioni.''&lt;br /&gt;
&lt;br /&gt;
== Lezione del 26 settembre 2017 ==&lt;br /&gt;
'''Titolo della lezione: W H Y'''&lt;br /&gt;
&lt;br /&gt;
* What:&lt;br /&gt;
* Who:&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* When:&lt;br /&gt;
----&lt;br /&gt;
Il corso ha durata annuale. Verrà svolto ogni martedì e venerdì dalle 15:30 alle 18:30.&lt;br /&gt;
* La lezione del martedì sarà dedicata alla programmazione concorrente;&lt;br /&gt;
* La lezione del venerdì sarà dedicata alla parte generale. Terminerà circa 15 minuti prima dell'orario stabilito.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* Where:&lt;br /&gt;
----&lt;br /&gt;
Sempre in Aula 1 Ercolani (E1): DISI, Scuole Ercolani, Mura Anteo Zamboni 2B.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* How:&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* Why:&lt;br /&gt;
----&lt;br /&gt;
* '''Fonti e strumenti del corso:''' wiki creata di gruppo, lezioni frontali e esercitazioni.&lt;br /&gt;
**Non esiste un vero e proprio testo consigliato. Tutte le informazioni sono date DURANTE il corso.&lt;br /&gt;
** Per domande specifiche scrivere su mailing list (so@cs.unibo.it);&lt;br /&gt;
** Per esercizi, appunti e programma svolto rivolgersi al wiki (so.v2.cs.unibo.it);&lt;br /&gt;
** Per live streaming (www.cs.unibo.it/~renzo/live/).&lt;br /&gt;
* '''Modalit&amp;amp;agrave; di esame:''' esame scritto (diviso in due parti: una parte generale e una di programmazione concorrente) + progetto + orale (facoltativo), possibilità di dare solo lo scritto per ottenere un massimo di 18. La modalità per studenti in &amp;quot;difficoltà&amp;quot; è disponibile solo fino agli appelli autunnali.&lt;br /&gt;
** Si parlerà del progetto indicativamente a partire da Dicembre 2017.&lt;br /&gt;
** L'orale può essere sostenuto da chi vuole migliorare (peggiorare) il voto ottenuto, o da chi vuole ottenere la lode.&lt;br /&gt;
* '''Orario di ricevimento per il primo semestre (fino al 15/09/17):''' martedì alle 11:30.&lt;br /&gt;
* Universit&amp;amp;agrave; = docenti + studenti.&lt;br /&gt;
* Informatica = come generare informazione automatica.&lt;br /&gt;
* Hardware, Software, Elaborazione, Comunicazione, Memorizzazione, Digitale/Analogico.&lt;br /&gt;
La principale distinzione che facciamo tra '''dato''' e '''informazione''' è che il dato, da solo, è privo di significato. Se, tuttavia, viene interpretato in un particolare contesto allora può diventare informazione significativa per chi sta interpretando i dati.&lt;br /&gt;
&lt;br /&gt;
Un '''algoritmo''' è un insieme non ambiguo di passi che, dato un problema, ci permette di risolverlo. &lt;br /&gt;
&lt;br /&gt;
Il nostro algoritmo, scritto in un qualche linguaggio formale, diventa un '''programma''' (sostanzialmente un testo, un insieme di istruzioni).&lt;br /&gt;
&lt;br /&gt;
Un linguaggio non è altro che una quadrupla (alfabeto, sintassi, lessico, semantica) dove:&lt;br /&gt;
*l'''alfabeto'' è l'insieme di simboli che compone il linguaggio.&lt;br /&gt;
*la ''sintassi'' determina quali delle sequenze scritte sono effettivamente corrette.&lt;br /&gt;
*il ''lessico'' si può vedere come una funzione che va dai simboli a un booleano e ci dice quali sono le parole ben formate.&lt;br /&gt;
*la ''semantica'' associa un significato alle parole ben formate.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 29 settembre 2017 ==&lt;br /&gt;
'''Cos'è un sistema operativo e un po' di storia'''&lt;br /&gt;
&lt;br /&gt;
Un ''sistema operativo'' è un programma che gestisce i processi, le risorse e interfaccia le applicazioni con l'hardware dell'elaboratore.&lt;br /&gt;
&lt;br /&gt;
In particolare, il sistema operativo (laddove esiste) è il primo processo ad essere attivato e resta in vita fino allo spegnimento del calcolatore o al sopraggiungere di un errore fatale per il sistema.&lt;br /&gt;
&lt;br /&gt;
A cosa serve un sistema operativo?&lt;br /&gt;
Principalmente, ha i seguenti scopi:&lt;br /&gt;
# facilitare l'utilizzo del sistema;&lt;br /&gt;
# rendere affidabile, protetto e sicuro l'utilizzo del sistema (e.g. un processo potrebbe recar danno all'intero sistema se non controllato o potrebbe ignorare i permessi di visualizzazione di un file);&lt;br /&gt;
# astrarre l'hardware (e.g. filesystem);&lt;br /&gt;
# garantire l'efficienza (e.g. non tenere la CPU in idle adottando opportuni algoritmi di scheduling);&lt;br /&gt;
# assicurare portabilità;&lt;br /&gt;
&lt;br /&gt;
Un approccio molto usato è il cosiddetto approccio a strati in cui ogni strato utilizza i servizi forniti al livello inferiore e ne fornisce di nuovi a quello superiore.&lt;br /&gt;
Astraendo il nostro calcolatore possiamo piazzare al livello più basso l'hardware e, subito sopra, il sistema operativo. I due layer comunicano usando il linguaggio ISA (Instruction Set Architecture), nativo della CPU stessa, al quale si aggiungono quelli che permettono di comunicare con i vari dispositivi come la scheda di rete, la stampante, etc. Sopra il livello del sistema operativo possiamo collocare quello delle librerie e, infine, quello degli applicativi.&lt;br /&gt;
&lt;br /&gt;
*systemcall&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''zero''' (1800 ca.) possiamo include Babbage con la sua macchina analitica e lady Ada Lovelace.&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''uno''' vediamo la comparsa delle valvole con tutti i problemi connessi. Non c'erano utilizzatori delle macchine, le stesse persone che le costruivano erano anche programmatori e fruitori delle stesse.&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''due''' (fine anni '60) arriva il transistor. Quest'ultimo è molto più veloce e piccolo della valvola e, soprattutto, meno incline a guasti. Le macchine iniziano ad essere più economiche grazie alla possibilità di avere un'economia di scala e quindi accessibili al grande pubblico. Questo fa si che, in queste prime fasi, i costruttori non siano più gli unici utilizzatori.&lt;br /&gt;
A questo punto poiché, pur essendo diventata più accessibile, una macchina costava ancora molto, nasce l'esigenza di non lasciare mai un processore senza lavoro (al fine di massimizzare i ricavi) e di condividere i dati.&lt;br /&gt;
In questa fase, infatti, abbiamo sistemi operativi di tipo ''batch'' (che collezionano tutto l'input all'inizio per calcolare e restituire l'output) dove l'inserimento di programmi e dati veniva fatto tramite schede perforate e non c'era interazione. Dai sistemi batch nasce lo ''SPOOL'' (Simultaneous Peripheral Operations On-Line).&lt;br /&gt;
&lt;br /&gt;
Si comincia a pensare di utilizzare i tempi di I/O per poter eseguire altri processi. Questo però solleva almeno due problematiche: da un lato serve un modo per sapere quando un input è davvero finito mentre, dall'altro, bisogna fare in modo che un processo che non richieda I/O occupi la CPU per un tempo indefinito.&lt;br /&gt;
Per la prima problematica la soluzione è rappresentata dall'introduzione degli '''interrupt''', segnali elettrici inviati alla CPU alla fine di ogni input.&lt;br /&gt;
Per la seconda, invece, è stato introdotto il cosiddetto '''interval timer''' che non è altro che un dispositivo che invia interrupt dopo un determinato quanto di tempo assicurando che un processo non occupi per troppo tempo il processore.&lt;br /&gt;
Questo consente di realizzare sistemi time sharing (il cui più semplice algoritmo è il round-robin) dove un processo può essere in uno dei seguenti tre stati:&lt;br /&gt;
*'''READY''' pronto per essere eseguito ma, il processore è già occupato;&lt;br /&gt;
*'''RUNNING''' in esecuzione;&lt;br /&gt;
*'''WAIT''' in attesa di I/O.&lt;br /&gt;
&lt;br /&gt;
Nella '''quarta''' generazione (anni '70 ca.) vediamo la nascita di molte innovazioni importanti. Si riesce a rimpicciolire di molto i processori facendoli divenire a tutti gli effetti micro-processori. Nei laboratori Bell nasce Unix, un sistema operativo rivoluzionari con time sharing e clonazione dei processi (un processo non viene mai creato dal nulla ma viene prima clonato da uno già esistente e poi, alla copia, viene fatto eseguire un programma). A causa di problemi di portabilità da PDP-9, macchina su cui Unix è nato, a PDP-11 nasce il linguaggio C e il suo compilatore (scritto anch'esso in C).&lt;br /&gt;
Mentre Apple I &amp;quot;porta&amp;quot; una nuova idea di pc per tutte le famiglie, nascono molte versioni di Unix.&lt;br /&gt;
Per impedire una deriva proprietaria in cui ogni produttore offriva tutto quello che offrivano gli altri ma con qualche feature in più, Richard Stallman fonda il progetto GNU (GNU's Not Unix) per il quale scrive tutta una serie di utilities come il famoso compilatore gcc.&lt;br /&gt;
A GNU, per essere operativo, manca però un kernel. Non volendo aspettare i tempi di sviluppo del kernel che Stallman aveva in mente, Linus Torlvalds scrive il kernel '''Linux''' da cui nasce il sistema operativo GNU/Linux.&lt;br /&gt;
&lt;br /&gt;
Nei tempi più recenti nascono i processori multi-core.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 3 ottobre 2017 ==&lt;br /&gt;
== Lezione del 6 ottobre 2017 ==&lt;br /&gt;
== Lezione del 10 ottobre 2017 ==&lt;br /&gt;
== Lezione del 13 ottobre 2017 ==&lt;br /&gt;
== Lezione del 17 ottobre 2017 ==&lt;br /&gt;
== Lezione del 20 ottobre 2017 ==&lt;br /&gt;
== Lezione del 24 ottobre 2017 ==&lt;br /&gt;
== Lezione del 27 ottobre 2017 ==&lt;br /&gt;
== Lezione del 31 ottobre 2017 ==&lt;br /&gt;
== Lezione del 3 novembre 2017 ==&lt;br /&gt;
== Lezione del 7 novembre 2017 ==&lt;br /&gt;
== Lezione del 10 novembre 2017 ==&lt;br /&gt;
== Lezione del 14 novembre 2017 ==&lt;br /&gt;
== Lezione del 17 novembre 2017 ==&lt;br /&gt;
== Lezione del 21 novembre 2017 ==&lt;br /&gt;
== Lezione del 24 novembre 2017 ==&lt;br /&gt;
== Lezione del 28 novembre 2017 ==&lt;br /&gt;
== Lezione del 1 dicembre 2017 ==&lt;br /&gt;
== Lezione del 5 dicembre 2017 ==&lt;br /&gt;
== Lezione del 12 dicembre 2017 ==&lt;br /&gt;
== Lezione del 15 dicembre 2017 ==&lt;/div&gt;</summary>
		<author><name>Matt</name></author>
	</entry>
	<entry>
		<id>https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_I_semestre&amp;diff=1978</id>
		<title>Lezioni Anno Accademico 2017/18 I semestre</title>
		<link rel="alternate" type="text/html" href="https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_I_semestre&amp;diff=1978"/>
		<updated>2017-09-29T20:23:04Z</updated>

		<summary type="html">&lt;p&gt;Matt: /* Lezione del 29 settembre 2017 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;''scrivete qui idee, riassunti dei concetti espressi, commenti approfondimenti sulle lezioni.''&lt;br /&gt;
&lt;br /&gt;
== Lezione del 26 settembre 2017 ==&lt;br /&gt;
'''Titolo della lezione: W H Y'''&lt;br /&gt;
&lt;br /&gt;
* What:&lt;br /&gt;
* Who:&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* When:&lt;br /&gt;
----&lt;br /&gt;
Il corso ha durata annuale. Verrà svolto ogni martedì e venerdì dalle 15:30 alle 18:30.&lt;br /&gt;
* La lezione del martedì sarà dedicata alla programmazione concorrente;&lt;br /&gt;
* La lezione del venerdì sarà dedicata alla parte generale. Terminerà circa 15 minuti prima dell'orario stabilito.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* Where:&lt;br /&gt;
----&lt;br /&gt;
Sempre in Aula 1 Ercolani (E1): DISI, Scuole Ercolani, Mura Anteo Zamboni 2B.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* How:&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* Why:&lt;br /&gt;
----&lt;br /&gt;
* '''Fonti e strumenti del corso:''' wiki creata di gruppo, lezioni frontali e esercitazioni.&lt;br /&gt;
**Non esiste un vero e proprio testo consigliato. Tutte le informazioni sono date DURANTE il corso.&lt;br /&gt;
** Per domande specifiche scrivere su mailing list (so@cs.unibo.it);&lt;br /&gt;
** Per esercizi, appunti e programma svolto rivolgersi al wiki (so.v2.cs.unibo.it);&lt;br /&gt;
** Per live streaming (www.cs.unibo.it/~renzo/live/).&lt;br /&gt;
* '''Modalit&amp;amp;agrave; di esame:''' esame scritto (diviso in due parti: una parte generale e una di programmazione concorrente) + progetto + orale (facoltativo), possibilità di dare solo lo scritto per ottenere un massimo di 18. La modalità per studenti in &amp;quot;difficoltà&amp;quot; è disponibile solo fino agli appelli autunnali.&lt;br /&gt;
** Si parlerà del progetto indicativamente a partire da Dicembre 2017.&lt;br /&gt;
** L'orale può essere sostenuto da chi vuole migliorare (peggiorare) il voto ottenuto, o da chi vuole ottenere la lode.&lt;br /&gt;
* '''Orario di ricevimento per il primo semestre (fino al 15/09/17):''' martedì alle 11:30.&lt;br /&gt;
* Universit&amp;amp;agrave; = docenti + studenti.&lt;br /&gt;
* Informatica = come generare informazione automatica.&lt;br /&gt;
* Hardware, Software, Elaborazione, Comunicazione, Memorizzazione, Digitale/Analogico.&lt;br /&gt;
La principale distinzione che facciamo tra '''dato''' e '''informazione''' è che il dato, da solo, è privo di significato. Se, tuttavia, viene interpretato in un particolare contesto allora può diventare informazione significativa per chi sta interpretando i dati.&lt;br /&gt;
&lt;br /&gt;
Un '''algoritmo''' è un insieme non ambiguo di passi che, dato un problema, ci permette di risolverlo. &lt;br /&gt;
&lt;br /&gt;
Il nostro algoritmo, scritto in un qualche linguaggio formale, diventa un '''programma''' (sostanzialmente un testo, un insieme di istruzioni).&lt;br /&gt;
&lt;br /&gt;
Un linguaggio non è altro che una quadrupla (alfabeto, sintassi, lessico, semantica) dove:&lt;br /&gt;
*l'''alfabeto'' è l'insieme di simboli che compone il linguaggio.&lt;br /&gt;
*la ''sintassi'' determina quali delle sequenze scritte sono effettivamente corrette.&lt;br /&gt;
*il ''lessico'' si può vedere come una funzione che va dai simboli a un booleano e ci dice quali sono le parole ben formate.&lt;br /&gt;
*la ''semantica'' associa un significato alle parole ben formate.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 29 settembre 2017 ==&lt;br /&gt;
'''Cos'è un sistema operativo e un po' di storia'''&lt;br /&gt;
&lt;br /&gt;
Un ''sistema operativo'' è un programma che gestisce i processi, le risorse e interfaccia le applicazioni con l'hardware dell'elaboratore.&lt;br /&gt;
&lt;br /&gt;
In particolare, il sistema operativo (laddove esiste) è il primo processo ad essere attivato e resta in vita fino allo spegnimento del calcolatore o al sopraggiungere di un errore fatale per il sistema.&lt;br /&gt;
&lt;br /&gt;
A cosa serve un sistema operativo?&lt;br /&gt;
Principalmente, ha i seguenti scopi:&lt;br /&gt;
# facilitare l'utilizzo del sistema;&lt;br /&gt;
# rendere affidabile, protetto e sicuro l'utilizzo del sistema (e.g. un processo potrebbe recar danno all'intero sistema se non controllato o potrebbe ignorare i permessi di visualizzazione di un file);&lt;br /&gt;
# astrarre l'hardware (e.g. filesystem);&lt;br /&gt;
# garantire l'efficienza (e.g. non tenere la CPU in idle adottando opportuni algoritmi di scheduling);&lt;br /&gt;
# assicurare portabilità;&lt;br /&gt;
&lt;br /&gt;
Astraendo il nostro calcolatore possiamo piazzare al livello più basso l'hardware e, subito sopra, il sistema operativo. I due layer comunicano usando il linguaggio ISA (Instruction Set Architecture), nativo della CPU stessa, al quale si aggiungono quelli che permettono di comunicare con i vari dispositivi come la scheda di rete, la stampante, etc. Sopra il livello del sistema operativo possiamo collocare quello delle librerie e, infine, quello degli applicativi.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''zero''' (1800 ca.) possiamo include Babbage con la sua macchina analitica e lady Ada Lovelace.&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''uno''' vediamo la comparsa delle valvole con tutti i problemi connessi. Non c'erano utilizzatori delle macchine, le stesse persone che le costruivano erano anche programmatori e fruitori delle stesse.&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''due''' (fine anni '60) arriva il transistor. Quest'ultimo è molto più veloce e piccolo della valvola e, soprattutto, meno incline a guasti. Le macchine iniziano ad essere più economiche grazie alla possibilità di avere un'economia di scala e quindi accessibili al grande pubblico. Questo fa si che, in queste prime fasi, i costruttori non siano più gli unici utilizzatori.&lt;br /&gt;
A questo punto poiché, pur essendo diventata più accessibile, una macchina costava ancora molto, nasce l'esigenza di non lasciare mai un processore senza lavoro (al fine di massimizzare i ricavi) e di condividere i dati.&lt;br /&gt;
In questa fase, infatti, abbiamo sistemi operativi di tipo ''batch'' (che collezionano tutto l'input all'inizio per calcolare e restituire l'output) dove l'inserimento di programmi e dati veniva fatto tramite schede perforate e non c'era interazione. Dai sistemi batch nasce lo ''SPOOL'' (Simultaneous Peripheral Operations On-Line).&lt;br /&gt;
&lt;br /&gt;
Si comincia a pensare di utilizzare i tempi di I/O per poter eseguire altri processi. Questo però solleva almeno due problematiche: da un lato serve un modo per sapere quando un input è davvero finito mentre, dall'altro, bisogna fare in modo che un processo che non richieda I/O occupi la CPU per un tempo indefinito.&lt;br /&gt;
Per la prima problematica la soluzione è rappresentata dall'introduzione degli '''interrupt''', segnali elettrici inviati alla CPU alla fine di ogni input.&lt;br /&gt;
Per la seconda, invece, è stato introdotto il cosiddetto '''interval timer''' che non è altro che un dispositivo che invia interrupt dopo un determinato quanto di tempo assicurando che un processo non occupi per troppo tempo il processore.&lt;br /&gt;
Questo consente di realizzare sistemi time sharing (il cui più semplice algoritmo è il round-robin) dove un processo può essere in uno dei seguenti tre stati:&lt;br /&gt;
*'''READY''' pronto per essere eseguito ma, il processore è già occupato;&lt;br /&gt;
*'''RUNNING''' in esecuzione;&lt;br /&gt;
*'''WAIT''' in attesa di I/O.&lt;br /&gt;
&lt;br /&gt;
Nella '''quarta''' generazione (anni '70 ca.) vediamo la nascita di molte innovazioni importanti. Si riesce a rimpicciolire di molto i processori facendoli divenire a tutti gli effetti micro-processori. Nei laboratori Bell nasce Unix, un sistema operativo rivoluzionari con time sharing e clonazione dei processi (un processo non viene mai creato dal nulla ma viene prima clonato da uno già esistente e poi, alla copia, viene fatto eseguire un programma). A causa di problemi di portabilità da PDP-9, macchina su cui Unix è nato, a PDP-11 nasce il linguaggio C e il suo compilatore (scritto anch'esso in C).&lt;br /&gt;
Mentre Apple I &amp;quot;porta&amp;quot; una nuova idea di pc per tutte le famiglie, nascono molte versioni di Unix.&lt;br /&gt;
Per impedire una deriva proprietaria in cui ogni produttore offriva tutto quello che offrivano gli altri ma con qualche feature in più, Richard Stallman fonda il progetto GNU (GNU's Not Unix) per il quale scrive tutta una serie di utilities come il famoso compilatore gcc.&lt;br /&gt;
A GNU, per essere operativo, manca però un kernel. Non volendo aspettare i tempi di sviluppo del kernel che Stallman aveva in mente, Linus Torlvalds scrive il kernel '''Linux''' da cui nasce il sistema operativo GNU/Linux.&lt;br /&gt;
&lt;br /&gt;
Nei tempi più recenti nascono i processori multi-core.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 3 ottobre 2017 ==&lt;br /&gt;
== Lezione del 6 ottobre 2017 ==&lt;br /&gt;
== Lezione del 10 ottobre 2017 ==&lt;br /&gt;
== Lezione del 13 ottobre 2017 ==&lt;br /&gt;
== Lezione del 17 ottobre 2017 ==&lt;br /&gt;
== Lezione del 20 ottobre 2017 ==&lt;br /&gt;
== Lezione del 24 ottobre 2017 ==&lt;br /&gt;
== Lezione del 27 ottobre 2017 ==&lt;br /&gt;
== Lezione del 31 ottobre 2017 ==&lt;br /&gt;
== Lezione del 3 novembre 2017 ==&lt;br /&gt;
== Lezione del 7 novembre 2017 ==&lt;br /&gt;
== Lezione del 10 novembre 2017 ==&lt;br /&gt;
== Lezione del 14 novembre 2017 ==&lt;br /&gt;
== Lezione del 17 novembre 2017 ==&lt;br /&gt;
== Lezione del 21 novembre 2017 ==&lt;br /&gt;
== Lezione del 24 novembre 2017 ==&lt;br /&gt;
== Lezione del 28 novembre 2017 ==&lt;br /&gt;
== Lezione del 1 dicembre 2017 ==&lt;br /&gt;
== Lezione del 5 dicembre 2017 ==&lt;br /&gt;
== Lezione del 12 dicembre 2017 ==&lt;br /&gt;
== Lezione del 15 dicembre 2017 ==&lt;/div&gt;</summary>
		<author><name>Matt</name></author>
	</entry>
	<entry>
		<id>https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_I_semestre&amp;diff=1977</id>
		<title>Lezioni Anno Accademico 2017/18 I semestre</title>
		<link rel="alternate" type="text/html" href="https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_I_semestre&amp;diff=1977"/>
		<updated>2017-09-29T20:01:32Z</updated>

		<summary type="html">&lt;p&gt;Matt: /* Lezione del 29 settembre 2017 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;''scrivete qui idee, riassunti dei concetti espressi, commenti approfondimenti sulle lezioni.''&lt;br /&gt;
&lt;br /&gt;
== Lezione del 26 settembre 2017 ==&lt;br /&gt;
'''Titolo della lezione: W H Y'''&lt;br /&gt;
&lt;br /&gt;
* What:&lt;br /&gt;
* Who:&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* When:&lt;br /&gt;
----&lt;br /&gt;
Il corso ha durata annuale. Verrà svolto ogni martedì e venerdì dalle 15:30 alle 18:30.&lt;br /&gt;
* La lezione del martedì sarà dedicata alla programmazione concorrente;&lt;br /&gt;
* La lezione del venerdì sarà dedicata alla parte generale. Terminerà circa 15 minuti prima dell'orario stabilito.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* Where:&lt;br /&gt;
----&lt;br /&gt;
Sempre in Aula 1 Ercolani (E1): DISI, Scuole Ercolani, Mura Anteo Zamboni 2B.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* How:&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* Why:&lt;br /&gt;
----&lt;br /&gt;
* '''Fonti e strumenti del corso:''' wiki creata di gruppo, lezioni frontali e esercitazioni.&lt;br /&gt;
**Non esiste un vero e proprio testo consigliato. Tutte le informazioni sono date DURANTE il corso.&lt;br /&gt;
** Per domande specifiche scrivere su mailing list (so@cs.unibo.it);&lt;br /&gt;
** Per esercizi, appunti e programma svolto rivolgersi al wiki (so.v2.cs.unibo.it);&lt;br /&gt;
** Per live streaming (www.cs.unibo.it/~renzo/live/).&lt;br /&gt;
* '''Modalit&amp;amp;agrave; di esame:''' esame scritto (diviso in due parti: una parte generale e una di programmazione concorrente) + progetto + orale (facoltativo), possibilità di dare solo lo scritto per ottenere un massimo di 18. La modalità per studenti in &amp;quot;difficoltà&amp;quot; è disponibile solo fino agli appelli autunnali.&lt;br /&gt;
** Si parlerà del progetto indicativamente a partire da Dicembre 2017.&lt;br /&gt;
** L'orale può essere sostenuto da chi vuole migliorare (peggiorare) il voto ottenuto, o da chi vuole ottenere la lode.&lt;br /&gt;
* '''Orario di ricevimento per il primo semestre (fino al 15/09/17):''' martedì alle 11:30.&lt;br /&gt;
* Universit&amp;amp;agrave; = docenti + studenti.&lt;br /&gt;
* Informatica = come generare informazione automatica.&lt;br /&gt;
* Hardware, Software, Elaborazione, Comunicazione, Memorizzazione, Digitale/Analogico.&lt;br /&gt;
La principale distinzione che facciamo tra '''dato''' e '''informazione''' è che il dato, da solo, è privo di significato. Se, tuttavia, viene interpretato in un particolare contesto allora può diventare informazione significativa per chi sta interpretando i dati.&lt;br /&gt;
&lt;br /&gt;
Un '''algoritmo''' è un insieme non ambiguo di passi che, dato un problema, ci permette di risolverlo. &lt;br /&gt;
&lt;br /&gt;
Il nostro algoritmo, scritto in un qualche linguaggio formale, diventa un '''programma''' (sostanzialmente un testo, un insieme di istruzioni).&lt;br /&gt;
&lt;br /&gt;
Un linguaggio non è altro che una quadrupla (alfabeto, sintassi, lessico, semantica) dove:&lt;br /&gt;
*l'''alfabeto'' è l'insieme di simboli che compone il linguaggio.&lt;br /&gt;
*la ''sintassi'' determina quali delle sequenze scritte sono effettivamente corrette.&lt;br /&gt;
*il ''lessico'' si può vedere come una funzione che va dai simboli a un booleano e ci dice quali sono le parole ben formate.&lt;br /&gt;
*la ''semantica'' associa un significato alle parole ben formate.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 29 settembre 2017 ==&lt;br /&gt;
'''Cos'è un sistema operativo e un po' di storia'''&lt;br /&gt;
&lt;br /&gt;
Un ''sistema operativo'' è un programma che gestisce i processi, le risorse e interfaccia le applicazioni con l'hardware dell'elaboratore.&lt;br /&gt;
&lt;br /&gt;
In particolare, il sistema operativo (laddove esiste) è il primo processo ad essere attivato e resta in vita fino allo spegnimento del calcolatore o al sopraggiungere di un errore fatale per il sistema.&lt;br /&gt;
&lt;br /&gt;
A cosa serve un sistema operativo?&lt;br /&gt;
Principalmente, ha i seguenti scopi:&lt;br /&gt;
# facilitare l'utilizzo del sistema;&lt;br /&gt;
# rendere affidabile, protetto e sicuro l'utilizzo del sistema (e.g. un processo potrebbe recar danno all'intero sistema se non controllato o potrebbe ignorare i permessi di visualizzazione di un file);&lt;br /&gt;
# astrarre l'hardware (e.g. filesystem);&lt;br /&gt;
# garantire l'efficienza (e.g. non tenere la CPU in idle adottando opportuni algoritmi di scheduling);&lt;br /&gt;
# assicurare portabilità;&lt;br /&gt;
&lt;br /&gt;
Astraendo il nostro calcolatore possiamo piazzare al livello più basso l'hardware e, subito sopra, il sistema operativo. I due layer comunicano usando il linguaggio ISA (Instruction Set Architecture), nativo della CPU stessa, al quale si aggiungono quelli che permettono di comunicare con i vari dispositivi come la scheda di rete, la stampante, etc. Sopra il livello del sistema operativo possiamo collocare quello delle librerie e, infine, quello degli applicativi.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''zero''' (1800 ca.) possiamo include Babbage con la sua macchina analitica e lady Ada Lovelace.&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''uno''' vediamo la comparsa delle valvole con tutti i problemi connessi. Non c'erano utilizzatori delle macchine, le stesse persone che le costruivano erano anche programmatori e utilizzatori.&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''due''' arriva il transistor. Quest'ultimo è molto più veloce e piccolo della valvola e, soprattutto, meno incline a guasti. Le macchine iniziano ad essere più economiche grazie alla possibilità di avere un'economia di scala e quindi più accessibili al grande pubblico. Questo fa si che, in queste prime fasi, i costruttori non siano più gli unici utilizzatori.&lt;br /&gt;
A questo punto, poiché pur essendo diventata più accessibile, una macchina costava ancora molto, nasce l'esigenza di non lasciare mai un processore senza lavoro (al fine di massimizzare i ricavi) e di condividere i dati.&lt;br /&gt;
In questa fase, infatti, abbiamo sistemi operativi di tipo ''batch'' (che collezionano tutto l'input all'inizio per calcolare e restituire l'output) dove l'inserimento di programmi e dati veniva fatto tramite schede perforate e non c'era interazione. Dai sistemi batch nasce lo ''SPOOL'' (Simultaneous Peripheral Operations On-Line).&lt;br /&gt;
&lt;br /&gt;
Si comincia a pensare di utilizzare i tempi di I/O per poter eseguire altri processi. Questo però solleva almeno due problematiche: da un lato serve un modo per sapere quando un input è davvero finito mentre, dall'altro, bisogna fare in modo che un processo che non richieda I/O occupi la CPU per un tempo indefinito.&lt;br /&gt;
Per la prima problematica la soluzione è rappresentata dall'introduzione degli '''interrupt''', segnali elettrici inviati alla CPU alla fine di ogni input.&lt;br /&gt;
Per la seconda, invece, è stato introdotto il cosiddetto '''interval timer''' che non è altro che un dispositivo che invia interrupt dopo un determinato quanto di tempo assicurando che un processo non occupi per troppo tempo il processore.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 3 ottobre 2017 ==&lt;br /&gt;
== Lezione del 6 ottobre 2017 ==&lt;br /&gt;
== Lezione del 10 ottobre 2017 ==&lt;br /&gt;
== Lezione del 13 ottobre 2017 ==&lt;br /&gt;
== Lezione del 17 ottobre 2017 ==&lt;br /&gt;
== Lezione del 20 ottobre 2017 ==&lt;br /&gt;
== Lezione del 24 ottobre 2017 ==&lt;br /&gt;
== Lezione del 27 ottobre 2017 ==&lt;br /&gt;
== Lezione del 31 ottobre 2017 ==&lt;br /&gt;
== Lezione del 3 novembre 2017 ==&lt;br /&gt;
== Lezione del 7 novembre 2017 ==&lt;br /&gt;
== Lezione del 10 novembre 2017 ==&lt;br /&gt;
== Lezione del 14 novembre 2017 ==&lt;br /&gt;
== Lezione del 17 novembre 2017 ==&lt;br /&gt;
== Lezione del 21 novembre 2017 ==&lt;br /&gt;
== Lezione del 24 novembre 2017 ==&lt;br /&gt;
== Lezione del 28 novembre 2017 ==&lt;br /&gt;
== Lezione del 1 dicembre 2017 ==&lt;br /&gt;
== Lezione del 5 dicembre 2017 ==&lt;br /&gt;
== Lezione del 12 dicembre 2017 ==&lt;br /&gt;
== Lezione del 15 dicembre 2017 ==&lt;/div&gt;</summary>
		<author><name>Matt</name></author>
	</entry>
	<entry>
		<id>https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_I_semestre&amp;diff=1976</id>
		<title>Lezioni Anno Accademico 2017/18 I semestre</title>
		<link rel="alternate" type="text/html" href="https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_I_semestre&amp;diff=1976"/>
		<updated>2017-09-29T19:49:43Z</updated>

		<summary type="html">&lt;p&gt;Matt: /* Lezione del 29 settembre 2017 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;''scrivete qui idee, riassunti dei concetti espressi, commenti approfondimenti sulle lezioni.''&lt;br /&gt;
&lt;br /&gt;
== Lezione del 26 settembre 2017 ==&lt;br /&gt;
'''Titolo della lezione: W H Y'''&lt;br /&gt;
&lt;br /&gt;
* What:&lt;br /&gt;
* Who:&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* When:&lt;br /&gt;
----&lt;br /&gt;
Il corso ha durata annuale. Verrà svolto ogni martedì e venerdì dalle 15:30 alle 18:30.&lt;br /&gt;
* La lezione del martedì sarà dedicata alla programmazione concorrente;&lt;br /&gt;
* La lezione del venerdì sarà dedicata alla parte generale. Terminerà circa 15 minuti prima dell'orario stabilito.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* Where:&lt;br /&gt;
----&lt;br /&gt;
Sempre in Aula 1 Ercolani (E1): DISI, Scuole Ercolani, Mura Anteo Zamboni 2B.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* How:&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* Why:&lt;br /&gt;
----&lt;br /&gt;
* '''Fonti e strumenti del corso:''' wiki creata di gruppo, lezioni frontali e esercitazioni.&lt;br /&gt;
**Non esiste un vero e proprio testo consigliato. Tutte le informazioni sono date DURANTE il corso.&lt;br /&gt;
** Per domande specifiche scrivere su mailing list (so@cs.unibo.it);&lt;br /&gt;
** Per esercizi, appunti e programma svolto rivolgersi al wiki (so.v2.cs.unibo.it);&lt;br /&gt;
** Per live streaming (www.cs.unibo.it/~renzo/live/).&lt;br /&gt;
* '''Modalit&amp;amp;agrave; di esame:''' esame scritto (diviso in due parti: una parte generale e una di programmazione concorrente) + progetto + orale (facoltativo), possibilità di dare solo lo scritto per ottenere un massimo di 18. La modalità per studenti in &amp;quot;difficoltà&amp;quot; è disponibile solo fino agli appelli autunnali.&lt;br /&gt;
** Si parlerà del progetto indicativamente a partire da Dicembre 2017.&lt;br /&gt;
** L'orale può essere sostenuto da chi vuole migliorare (peggiorare) il voto ottenuto, o da chi vuole ottenere la lode.&lt;br /&gt;
* '''Orario di ricevimento per il primo semestre (fino al 15/09/17):''' martedì alle 11:30.&lt;br /&gt;
* Universit&amp;amp;agrave; = docenti + studenti.&lt;br /&gt;
* Informatica = come generare informazione automatica.&lt;br /&gt;
* Hardware, Software, Elaborazione, Comunicazione, Memorizzazione, Digitale/Analogico.&lt;br /&gt;
La principale distinzione che facciamo tra '''dato''' e '''informazione''' è che il dato, da solo, è privo di significato. Se, tuttavia, viene interpretato in un particolare contesto allora può diventare informazione significativa per chi sta interpretando i dati.&lt;br /&gt;
&lt;br /&gt;
Un '''algoritmo''' è un insieme non ambiguo di passi che, dato un problema, ci permette di risolverlo. &lt;br /&gt;
&lt;br /&gt;
Il nostro algoritmo, scritto in un qualche linguaggio formale, diventa un '''programma''' (sostanzialmente un testo, un insieme di istruzioni).&lt;br /&gt;
&lt;br /&gt;
Un linguaggio non è altro che una quadrupla (alfabeto, sintassi, lessico, semantica) dove:&lt;br /&gt;
*l'''alfabeto'' è l'insieme di simboli che compone il linguaggio.&lt;br /&gt;
*la ''sintassi'' determina quali delle sequenze scritte sono effettivamente corrette.&lt;br /&gt;
*il ''lessico'' si può vedere come una funzione che va dai simboli a un booleano e ci dice quali sono le parole ben formate.&lt;br /&gt;
*la ''semantica'' associa un significato alle parole ben formate.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 29 settembre 2017 ==&lt;br /&gt;
'''Cos'è un sistema operativo e un po' di storia'''&lt;br /&gt;
&lt;br /&gt;
Un ''sistema operativo'' è un programma che gestisce i processi, le risorse e interfaccia le applicazioni con l'hardware dell'elaboratore.&lt;br /&gt;
&lt;br /&gt;
In particolare, il sistema operativo (laddove esiste) è il primo processo ad essere attivato e resta in vita fino allo spegnimento del calcolatore o al sopraggiungere di un errore fatale per il sistema.&lt;br /&gt;
&lt;br /&gt;
A cosa serve un sistema operativo?&lt;br /&gt;
Principalmente, ha i seguenti scopi:&lt;br /&gt;
# facilitare l'utilizzo del sistema;&lt;br /&gt;
# rendere affidabile, protetto e sicuro l'utilizzo del sistema (e.g. un processo potrebbe recar danno all'intero sistema se non controllato o potrebbe ignorare i permessi di visualizzazione di un file);&lt;br /&gt;
# astrarre l'hardware (e.g. filesystem);&lt;br /&gt;
# garantire l'efficienza (e.g. non tenere la CPU in idle adottando opportuni algoritmi di scheduling);&lt;br /&gt;
# assicurare portabilità;&lt;br /&gt;
&lt;br /&gt;
Astraendo il nostro calcolatore possiamo piazzare al livello più basso l'hardware e, subito sopra, il sistema operativo. I due layer comunicano usando il linguaggio ISA (Instruction Set Architecture), nativo della CPU stessa, al quale si aggiungono quelli che permettono di comunicare con i vari dispositivi come la scheda di rete, la stampante, etc. Sopra il livello del sistema operativo possiamo collocare quello delle librerie e, infine, quello degli applicativi.&lt;br /&gt;
&lt;br /&gt;
---&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''zero''' (1800 ca.) possiamo include Babbage con la sua macchina analitica e lady Ada Lovelace.&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''uno''' vediamo la comparsa delle valvole con tutti i problemi connessi. Non c'erano utilizzatori delle macchine, le stesse persone che le costruivano erano anche programmatori e utilizzatori.&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''due''' arriva il transistor. Quest'ultimo è molto più veloce e piccolo della valvola e, soprattutto, meno incline a guasti. Le macchine iniziano ad essere più economiche grazie alla possibilità di avere un'economia di scala e quindi più accessibili al grande pubblico. Questo fa si che, in queste prime fasi, i costruttori non siano più gli unici utilizzatori.&lt;br /&gt;
A questo punto, poiché pur essendo diventata più accessibile, una macchina costava ancora molto, nasce l'esigenza di non lasciare mai un processore senza lavoro (al fine di massimizzare i ricavi) e di condividere i dati.&lt;br /&gt;
In questa fase, infatti, abbiamo sistemi operativi di tipo batch (che collezionano tutto l'input all'inizio per calcolare e restituire l'output) dove l'inserimento di programmi e dati veniva fatto tramite schede perforate e non c'era interazione.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 3 ottobre 2017 ==&lt;br /&gt;
== Lezione del 6 ottobre 2017 ==&lt;br /&gt;
== Lezione del 10 ottobre 2017 ==&lt;br /&gt;
== Lezione del 13 ottobre 2017 ==&lt;br /&gt;
== Lezione del 17 ottobre 2017 ==&lt;br /&gt;
== Lezione del 20 ottobre 2017 ==&lt;br /&gt;
== Lezione del 24 ottobre 2017 ==&lt;br /&gt;
== Lezione del 27 ottobre 2017 ==&lt;br /&gt;
== Lezione del 31 ottobre 2017 ==&lt;br /&gt;
== Lezione del 3 novembre 2017 ==&lt;br /&gt;
== Lezione del 7 novembre 2017 ==&lt;br /&gt;
== Lezione del 10 novembre 2017 ==&lt;br /&gt;
== Lezione del 14 novembre 2017 ==&lt;br /&gt;
== Lezione del 17 novembre 2017 ==&lt;br /&gt;
== Lezione del 21 novembre 2017 ==&lt;br /&gt;
== Lezione del 24 novembre 2017 ==&lt;br /&gt;
== Lezione del 28 novembre 2017 ==&lt;br /&gt;
== Lezione del 1 dicembre 2017 ==&lt;br /&gt;
== Lezione del 5 dicembre 2017 ==&lt;br /&gt;
== Lezione del 12 dicembre 2017 ==&lt;br /&gt;
== Lezione del 15 dicembre 2017 ==&lt;/div&gt;</summary>
		<author><name>Matt</name></author>
	</entry>
	<entry>
		<id>https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_I_semestre&amp;diff=1975</id>
		<title>Lezioni Anno Accademico 2017/18 I semestre</title>
		<link rel="alternate" type="text/html" href="https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_I_semestre&amp;diff=1975"/>
		<updated>2017-09-29T19:49:23Z</updated>

		<summary type="html">&lt;p&gt;Matt: /* Lezione del 29 settembre 2017 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;''scrivete qui idee, riassunti dei concetti espressi, commenti approfondimenti sulle lezioni.''&lt;br /&gt;
&lt;br /&gt;
== Lezione del 26 settembre 2017 ==&lt;br /&gt;
'''Titolo della lezione: W H Y'''&lt;br /&gt;
&lt;br /&gt;
* What:&lt;br /&gt;
* Who:&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* When:&lt;br /&gt;
----&lt;br /&gt;
Il corso ha durata annuale. Verrà svolto ogni martedì e venerdì dalle 15:30 alle 18:30.&lt;br /&gt;
* La lezione del martedì sarà dedicata alla programmazione concorrente;&lt;br /&gt;
* La lezione del venerdì sarà dedicata alla parte generale. Terminerà circa 15 minuti prima dell'orario stabilito.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* Where:&lt;br /&gt;
----&lt;br /&gt;
Sempre in Aula 1 Ercolani (E1): DISI, Scuole Ercolani, Mura Anteo Zamboni 2B.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* How:&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* Why:&lt;br /&gt;
----&lt;br /&gt;
* '''Fonti e strumenti del corso:''' wiki creata di gruppo, lezioni frontali e esercitazioni.&lt;br /&gt;
**Non esiste un vero e proprio testo consigliato. Tutte le informazioni sono date DURANTE il corso.&lt;br /&gt;
** Per domande specifiche scrivere su mailing list (so@cs.unibo.it);&lt;br /&gt;
** Per esercizi, appunti e programma svolto rivolgersi al wiki (so.v2.cs.unibo.it);&lt;br /&gt;
** Per live streaming (www.cs.unibo.it/~renzo/live/).&lt;br /&gt;
* '''Modalit&amp;amp;agrave; di esame:''' esame scritto (diviso in due parti: una parte generale e una di programmazione concorrente) + progetto + orale (facoltativo), possibilità di dare solo lo scritto per ottenere un massimo di 18. La modalità per studenti in &amp;quot;difficoltà&amp;quot; è disponibile solo fino agli appelli autunnali.&lt;br /&gt;
** Si parlerà del progetto indicativamente a partire da Dicembre 2017.&lt;br /&gt;
** L'orale può essere sostenuto da chi vuole migliorare (peggiorare) il voto ottenuto, o da chi vuole ottenere la lode.&lt;br /&gt;
* '''Orario di ricevimento per il primo semestre (fino al 15/09/17):''' martedì alle 11:30.&lt;br /&gt;
* Universit&amp;amp;agrave; = docenti + studenti.&lt;br /&gt;
* Informatica = come generare informazione automatica.&lt;br /&gt;
* Hardware, Software, Elaborazione, Comunicazione, Memorizzazione, Digitale/Analogico.&lt;br /&gt;
La principale distinzione che facciamo tra '''dato''' e '''informazione''' è che il dato, da solo, è privo di significato. Se, tuttavia, viene interpretato in un particolare contesto allora può diventare informazione significativa per chi sta interpretando i dati.&lt;br /&gt;
&lt;br /&gt;
Un '''algoritmo''' è un insieme non ambiguo di passi che, dato un problema, ci permette di risolverlo. &lt;br /&gt;
&lt;br /&gt;
Il nostro algoritmo, scritto in un qualche linguaggio formale, diventa un '''programma''' (sostanzialmente un testo, un insieme di istruzioni).&lt;br /&gt;
&lt;br /&gt;
Un linguaggio non è altro che una quadrupla (alfabeto, sintassi, lessico, semantica) dove:&lt;br /&gt;
*l'''alfabeto'' è l'insieme di simboli che compone il linguaggio.&lt;br /&gt;
*la ''sintassi'' determina quali delle sequenze scritte sono effettivamente corrette.&lt;br /&gt;
*il ''lessico'' si può vedere come una funzione che va dai simboli a un booleano e ci dice quali sono le parole ben formate.&lt;br /&gt;
*la ''semantica'' associa un significato alle parole ben formate.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 29 settembre 2017 ==&lt;br /&gt;
'''Cos'è un sistema operativo e un po' di storia'''&lt;br /&gt;
&lt;br /&gt;
Un ''sistema operativo'' è un programma che gestisce i processi, le risorse e interfaccia le applicazioni con l'hardware dell'elaboratore.&lt;br /&gt;
&lt;br /&gt;
In particolare, il sistema operativo (laddove esiste) è il primo processo ad essere attivato e resta in vita fino allo spegnimento del calcolatore o al sopraggiungere di un errore fatale per il sistema.&lt;br /&gt;
&lt;br /&gt;
A cosa serve un sistema operativo?&lt;br /&gt;
Principalmente, ha i seguenti scopi:&lt;br /&gt;
# facilitare l'utilizzo del sistema;&lt;br /&gt;
# rendere affidabile, protetto e sicuro l'utilizzo del sistema (e.g. un processo potrebbe recar danno all'intero sistema se non controllato o potrebbe ignorare i permessi di visualizzazione di un file);&lt;br /&gt;
# astrarre l'hardware (e.g. filesystem);&lt;br /&gt;
# garantire l'efficienza (e.g. non tenere la CPU in idle adottando opportuni algoritmi di scheduling);&lt;br /&gt;
# assicurare portabilità;&lt;br /&gt;
&lt;br /&gt;
Astraendo il nostro calcolatore possiamo piazzare al livello più basso l'hardware e, subito sopra, il sistema operativo. I due layer comunicano usando il linguaggio ISA (Instruction Set Architecture), nativo della CPU stessa, al quale si aggiungono quelli che permettono di comunicare con i vari dispositivi come la scheda di rete, la stampante, etc. Sopra il livello del sistema operativo possiamo collocare quello delle librerie e, infine, quello degli applicativi.&lt;br /&gt;
---&lt;br /&gt;
Nella generazione '''zero''' (1800 ca.) possiamo include Babbage con la sua macchina analitica e lady Ada Lovelace.&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''uno''' vediamo la comparsa delle valvole con tutti i problemi connessi. Non c'erano utilizzatori delle macchine, le stesse persone che le costruivano erano anche programmatori e utilizzatori.&lt;br /&gt;
&lt;br /&gt;
Nella generazione '''due''' arriva il transistor. Quest'ultimo è molto più veloce e piccolo della valvola e, soprattutto, meno incline a guasti. Le macchine iniziano ad essere più economiche grazie alla possibilità di avere un'economia di scala e quindi più accessibili al grande pubblico. Questo fa si che, in queste prime fasi, i costruttori non siano più gli unici utilizzatori.&lt;br /&gt;
A questo punto, poiché pur essendo diventata più accessibile, una macchina costava ancora molto, nasce l'esigenza di non lasciare mai un processore senza lavoro (al fine di massimizzare i ricavi) e di condividere i dati.&lt;br /&gt;
In questa fase, infatti, abbiamo sistemi operativi di tipo batch (che collezionano tutto l'input all'inizio per calcolare e restituire l'output) dove l'inserimento di programmi e dati veniva fatto tramite schede perforate e non c'era interazione.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 3 ottobre 2017 ==&lt;br /&gt;
== Lezione del 6 ottobre 2017 ==&lt;br /&gt;
== Lezione del 10 ottobre 2017 ==&lt;br /&gt;
== Lezione del 13 ottobre 2017 ==&lt;br /&gt;
== Lezione del 17 ottobre 2017 ==&lt;br /&gt;
== Lezione del 20 ottobre 2017 ==&lt;br /&gt;
== Lezione del 24 ottobre 2017 ==&lt;br /&gt;
== Lezione del 27 ottobre 2017 ==&lt;br /&gt;
== Lezione del 31 ottobre 2017 ==&lt;br /&gt;
== Lezione del 3 novembre 2017 ==&lt;br /&gt;
== Lezione del 7 novembre 2017 ==&lt;br /&gt;
== Lezione del 10 novembre 2017 ==&lt;br /&gt;
== Lezione del 14 novembre 2017 ==&lt;br /&gt;
== Lezione del 17 novembre 2017 ==&lt;br /&gt;
== Lezione del 21 novembre 2017 ==&lt;br /&gt;
== Lezione del 24 novembre 2017 ==&lt;br /&gt;
== Lezione del 28 novembre 2017 ==&lt;br /&gt;
== Lezione del 1 dicembre 2017 ==&lt;br /&gt;
== Lezione del 5 dicembre 2017 ==&lt;br /&gt;
== Lezione del 12 dicembre 2017 ==&lt;br /&gt;
== Lezione del 15 dicembre 2017 ==&lt;/div&gt;</summary>
		<author><name>Matt</name></author>
	</entry>
	<entry>
		<id>https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_I_semestre&amp;diff=1974</id>
		<title>Lezioni Anno Accademico 2017/18 I semestre</title>
		<link rel="alternate" type="text/html" href="https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_I_semestre&amp;diff=1974"/>
		<updated>2017-09-29T19:35:39Z</updated>

		<summary type="html">&lt;p&gt;Matt: /* Lezione del 26 settembre 2017 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;''scrivete qui idee, riassunti dei concetti espressi, commenti approfondimenti sulle lezioni.''&lt;br /&gt;
&lt;br /&gt;
== Lezione del 26 settembre 2017 ==&lt;br /&gt;
'''Titolo della lezione: W H Y'''&lt;br /&gt;
&lt;br /&gt;
* What:&lt;br /&gt;
* Who:&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* When:&lt;br /&gt;
----&lt;br /&gt;
Il corso ha durata annuale. Verrà svolto ogni martedì e venerdì dalle 15:30 alle 18:30.&lt;br /&gt;
* La lezione del martedì sarà dedicata alla programmazione concorrente;&lt;br /&gt;
* La lezione del venerdì sarà dedicata alla parte generale. Terminerà circa 15 minuti prima dell'orario stabilito.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* Where:&lt;br /&gt;
----&lt;br /&gt;
Sempre in Aula 1 Ercolani (E1): DISI, Scuole Ercolani, Mura Anteo Zamboni 2B.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* How:&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* Why:&lt;br /&gt;
----&lt;br /&gt;
* '''Fonti e strumenti del corso:''' wiki creata di gruppo, lezioni frontali e esercitazioni.&lt;br /&gt;
**Non esiste un vero e proprio testo consigliato. Tutte le informazioni sono date DURANTE il corso.&lt;br /&gt;
** Per domande specifiche scrivere su mailing list (so@cs.unibo.it);&lt;br /&gt;
** Per esercizi, appunti e programma svolto rivolgersi al wiki (so.v2.cs.unibo.it);&lt;br /&gt;
** Per live streaming (www.cs.unibo.it/~renzo/live/).&lt;br /&gt;
* '''Modalit&amp;amp;agrave; di esame:''' esame scritto (diviso in due parti: una parte generale e una di programmazione concorrente) + progetto + orale (facoltativo), possibilità di dare solo lo scritto per ottenere un massimo di 18. La modalità per studenti in &amp;quot;difficoltà&amp;quot; è disponibile solo fino agli appelli autunnali.&lt;br /&gt;
** Si parlerà del progetto indicativamente a partire da Dicembre 2017.&lt;br /&gt;
** L'orale può essere sostenuto da chi vuole migliorare (peggiorare) il voto ottenuto, o da chi vuole ottenere la lode.&lt;br /&gt;
* '''Orario di ricevimento per il primo semestre (fino al 15/09/17):''' martedì alle 11:30.&lt;br /&gt;
* Universit&amp;amp;agrave; = docenti + studenti.&lt;br /&gt;
* Informatica = come generare informazione automatica.&lt;br /&gt;
* Hardware, Software, Elaborazione, Comunicazione, Memorizzazione, Digitale/Analogico.&lt;br /&gt;
La principale distinzione che facciamo tra '''dato''' e '''informazione''' è che il dato, da solo, è privo di significato. Se, tuttavia, viene interpretato in un particolare contesto allora può diventare informazione significativa per chi sta interpretando i dati.&lt;br /&gt;
&lt;br /&gt;
Un '''algoritmo''' è un insieme non ambiguo di passi che, dato un problema, ci permette di risolverlo. &lt;br /&gt;
&lt;br /&gt;
Il nostro algoritmo, scritto in un qualche linguaggio formale, diventa un '''programma''' (sostanzialmente un testo, un insieme di istruzioni).&lt;br /&gt;
&lt;br /&gt;
Un linguaggio non è altro che una quadrupla (alfabeto, sintassi, lessico, semantica) dove:&lt;br /&gt;
*l'''alfabeto'' è l'insieme di simboli che compone il linguaggio.&lt;br /&gt;
*la ''sintassi'' determina quali delle sequenze scritte sono effettivamente corrette.&lt;br /&gt;
*il ''lessico'' si può vedere come una funzione che va dai simboli a un booleano e ci dice quali sono le parole ben formate.&lt;br /&gt;
*la ''semantica'' associa un significato alle parole ben formate.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 29 settembre 2017 ==&lt;br /&gt;
'''Cos'è un sistema operativo e un po' di storia'''&lt;br /&gt;
&lt;br /&gt;
Un ''sistema operativo'' è un programma che gestisce i processi, le risorse e interfaccia le applicazioni con l'hardware dell'elaboratore.&lt;br /&gt;
&lt;br /&gt;
In particolare, il sistema operativo (laddove esiste) è il primo processo ad essere attivato e resta in vita fino allo spegnimento del calcolatore o al sopraggiungere di un errore fatale per il sistema.&lt;br /&gt;
&lt;br /&gt;
A cosa serve un sistema operativo?&lt;br /&gt;
Principalmente, ha i seguenti scopi:&lt;br /&gt;
# facilitare l'utilizzo del sistema;&lt;br /&gt;
# rendere affidabile, protetto e sicuro l'utilizzo del sistema (e.g. un processo potrebbe recar danno all'intero sistema se non controllato o potrebbe ignorare i permessi di visualizzazione di un file);&lt;br /&gt;
# astrarre l'hardware (e.g. filesystem);&lt;br /&gt;
# garantire l'efficienza (e.g. non tenere la CPU in idle adottando opportuni algoritmi di scheduling);&lt;br /&gt;
# assicurare portabilità;&lt;br /&gt;
&lt;br /&gt;
Astraendo il nostro calcolatore possiamo piazzare al livello più basso l'hardware e, subito sopra, il sistema operativo. I due layer comunicano usando il linguaggio ISA (Instruction Set Architecture), nativo della CPU stessa, al quale si aggiungono quelli che permettono di comunicare con i vari dispositivi come la scheda di rete, la stampante, etc. Sopra il livello del sistema operativo possiamo collocare quello delle librerie e, infine, quello degli applicativi.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 3 ottobre 2017 ==&lt;br /&gt;
== Lezione del 6 ottobre 2017 ==&lt;br /&gt;
== Lezione del 10 ottobre 2017 ==&lt;br /&gt;
== Lezione del 13 ottobre 2017 ==&lt;br /&gt;
== Lezione del 17 ottobre 2017 ==&lt;br /&gt;
== Lezione del 20 ottobre 2017 ==&lt;br /&gt;
== Lezione del 24 ottobre 2017 ==&lt;br /&gt;
== Lezione del 27 ottobre 2017 ==&lt;br /&gt;
== Lezione del 31 ottobre 2017 ==&lt;br /&gt;
== Lezione del 3 novembre 2017 ==&lt;br /&gt;
== Lezione del 7 novembre 2017 ==&lt;br /&gt;
== Lezione del 10 novembre 2017 ==&lt;br /&gt;
== Lezione del 14 novembre 2017 ==&lt;br /&gt;
== Lezione del 17 novembre 2017 ==&lt;br /&gt;
== Lezione del 21 novembre 2017 ==&lt;br /&gt;
== Lezione del 24 novembre 2017 ==&lt;br /&gt;
== Lezione del 28 novembre 2017 ==&lt;br /&gt;
== Lezione del 1 dicembre 2017 ==&lt;br /&gt;
== Lezione del 5 dicembre 2017 ==&lt;br /&gt;
== Lezione del 12 dicembre 2017 ==&lt;br /&gt;
== Lezione del 15 dicembre 2017 ==&lt;/div&gt;</summary>
		<author><name>Matt</name></author>
	</entry>
	<entry>
		<id>https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_I_semestre&amp;diff=1972</id>
		<title>Lezioni Anno Accademico 2017/18 I semestre</title>
		<link rel="alternate" type="text/html" href="https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_I_semestre&amp;diff=1972"/>
		<updated>2017-09-29T19:09:24Z</updated>

		<summary type="html">&lt;p&gt;Matt: /* Lezione del 29 settembre 2017 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;''scrivete qui idee, riassunti dei concetti espressi, commenti approfondimenti sulle lezioni.''&lt;br /&gt;
&lt;br /&gt;
== Lezione del 26 settembre 2017 ==&lt;br /&gt;
'''Titolo della lezione: W H Y'''&lt;br /&gt;
&lt;br /&gt;
* What:&lt;br /&gt;
* Who:&lt;br /&gt;
* When:&lt;br /&gt;
* Where:&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* How:&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* Why:&lt;br /&gt;
----&lt;br /&gt;
* '''Fonti e strumenti del corso:''' wiki creata di gruppo, lezioni frontali e esercitazioni.&lt;br /&gt;
* '''Modalit&amp;amp;agrave; di esame:''' esame scritto (diviso in due parti: una parte generale e una di programmazione concorrente) + progetto + orale (facoltativo), possibilità di dare solo lo scritto per ottenere un massimo di 18. La modalità per studenti in &amp;quot;difficoltà&amp;quot; è disponibile solo fino agli appelli autunnali.&lt;br /&gt;
* '''Orario di ricevimento per il primo semestre (fino al 15/09/17):''' martedì alle 11:30.&lt;br /&gt;
* Universit&amp;amp;agrave; = docenti + studenti.&lt;br /&gt;
* Hardware, Software, Informazione, Dato, Elaborazione, Comunicazione, Memorizzazione, Algoritmo, Programma, Linguaggio, Informatica.&lt;br /&gt;
Digitale/Analogico.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 29 settembre 2017 ==&lt;br /&gt;
'''Cos'è un sistema operativo e un po' di storia'''&lt;br /&gt;
&lt;br /&gt;
Un ''sistema operativo'' è un programma che gestisce i processi, le risorse e interfaccia le applicazioni con l'hardware dell'elaboratore.&lt;br /&gt;
&lt;br /&gt;
In particolare, il sistema operativo (laddove esiste) è il primo processo ad essere attivato e resta in vita fino allo spegnimento del calcolatore o al sopraggiungere di un errore fatale per il sistema.&lt;br /&gt;
&lt;br /&gt;
A cosa serve un sistema operativo?&lt;br /&gt;
Principalmente, ha i seguenti scopi:&lt;br /&gt;
# facilitare l'utilizzo del sistema;&lt;br /&gt;
# rendere affidabile, protetto e sicuro l'utilizzo del sistema (e.g. un processo potrebbe recar danno all'intero sistema se non controllato o potrebbe ignorare i permessi di visualizzazione di un file);&lt;br /&gt;
# astrarre l'hardware (e.g. filesystem);&lt;br /&gt;
# garantire l'efficienza (e.g. non tenere la CPU in idle adottando opportuni algoritmi di scheduling);&lt;br /&gt;
# assicurare portabilità;&lt;br /&gt;
&lt;br /&gt;
Astraendo il nostro calcolatore possiamo piazzare al livello più basso l'hardware e, subito sopra, il sistema operativo. I due layer comunicano usando il linguaggio ISA (Instruction Set Architecture), nativo della CPU stessa, al quale si aggiungono quelli che permettono di comunicare con i vari dispositivi come la scheda di rete, la stampante, etc. Sopra il livello del sistema operativo possiamo collocare quello delle librerie e, infine, quello degli applicativi.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 3 ottobre 2017 ==&lt;br /&gt;
== Lezione del 6 ottobre 2017 ==&lt;br /&gt;
== Lezione del 10 ottobre 2017 ==&lt;br /&gt;
== Lezione del 13 ottobre 2017 ==&lt;br /&gt;
== Lezione del 17 ottobre 2017 ==&lt;br /&gt;
== Lezione del 20 ottobre 2017 ==&lt;br /&gt;
== Lezione del 24 ottobre 2017 ==&lt;br /&gt;
== Lezione del 27 ottobre 2017 ==&lt;br /&gt;
== Lezione del 31 ottobre 2017 ==&lt;br /&gt;
== Lezione del 3 novembre 2017 ==&lt;br /&gt;
== Lezione del 7 novembre 2017 ==&lt;br /&gt;
== Lezione del 10 novembre 2017 ==&lt;br /&gt;
== Lezione del 14 novembre 2017 ==&lt;br /&gt;
== Lezione del 17 novembre 2017 ==&lt;br /&gt;
== Lezione del 21 novembre 2017 ==&lt;br /&gt;
== Lezione del 24 novembre 2017 ==&lt;br /&gt;
== Lezione del 28 novembre 2017 ==&lt;br /&gt;
== Lezione del 1 dicembre 2017 ==&lt;br /&gt;
== Lezione del 5 dicembre 2017 ==&lt;br /&gt;
== Lezione del 12 dicembre 2017 ==&lt;br /&gt;
== Lezione del 15 dicembre 2017 ==&lt;/div&gt;</summary>
		<author><name>Matt</name></author>
	</entry>
	<entry>
		<id>https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_I_semestre&amp;diff=1971</id>
		<title>Lezioni Anno Accademico 2017/18 I semestre</title>
		<link rel="alternate" type="text/html" href="https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_I_semestre&amp;diff=1971"/>
		<updated>2017-09-29T18:45:27Z</updated>

		<summary type="html">&lt;p&gt;Matt: /* Lezione del 26 settembre 2017 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;''scrivete qui idee, riassunti dei concetti espressi, commenti approfondimenti sulle lezioni.''&lt;br /&gt;
&lt;br /&gt;
== Lezione del 26 settembre 2017 ==&lt;br /&gt;
'''Titolo della lezione: W H Y'''&lt;br /&gt;
&lt;br /&gt;
* What:&lt;br /&gt;
* Who:&lt;br /&gt;
* When:&lt;br /&gt;
* Where:&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* How:&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
* Why:&lt;br /&gt;
----&lt;br /&gt;
* '''Fonti e strumenti del corso:''' wiki creata di gruppo, lezioni frontali e esercitazioni.&lt;br /&gt;
* '''Modalit&amp;amp;agrave; di esame:''' esame scritto (diviso in due parti: una parte generale e una di programmazione concorrente) + progetto + orale (facoltativo), possibilità di dare solo lo scritto per ottenere un massimo di 18. La modalità per studenti in &amp;quot;difficoltà&amp;quot; è disponibile solo fino agli appelli autunnali.&lt;br /&gt;
* '''Orario di ricevimento per il primo semestre (fino al 15/09/17):''' martedì alle 11:30.&lt;br /&gt;
* Universit&amp;amp;agrave; = docenti + studenti.&lt;br /&gt;
* Hardware, Software, Informazione, Dato, Elaborazione, Comunicazione, Memorizzazione, Algoritmo, Programma, Linguaggio, Informatica.&lt;br /&gt;
Digitale/Analogico.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 29 settembre 2017 ==&lt;br /&gt;
== Lezione del 3 ottobre 2017 ==&lt;br /&gt;
== Lezione del 6 ottobre 2017 ==&lt;br /&gt;
== Lezione del 10 ottobre 2017 ==&lt;br /&gt;
== Lezione del 13 ottobre 2017 ==&lt;br /&gt;
== Lezione del 17 ottobre 2017 ==&lt;br /&gt;
== Lezione del 20 ottobre 2017 ==&lt;br /&gt;
== Lezione del 24 ottobre 2017 ==&lt;br /&gt;
== Lezione del 27 ottobre 2017 ==&lt;br /&gt;
== Lezione del 31 ottobre 2017 ==&lt;br /&gt;
== Lezione del 3 novembre 2017 ==&lt;br /&gt;
== Lezione del 7 novembre 2017 ==&lt;br /&gt;
== Lezione del 10 novembre 2017 ==&lt;br /&gt;
== Lezione del 14 novembre 2017 ==&lt;br /&gt;
== Lezione del 17 novembre 2017 ==&lt;br /&gt;
== Lezione del 21 novembre 2017 ==&lt;br /&gt;
== Lezione del 24 novembre 2017 ==&lt;br /&gt;
== Lezione del 28 novembre 2017 ==&lt;br /&gt;
== Lezione del 1 dicembre 2017 ==&lt;br /&gt;
== Lezione del 5 dicembre 2017 ==&lt;br /&gt;
== Lezione del 12 dicembre 2017 ==&lt;br /&gt;
== Lezione del 15 dicembre 2017 ==&lt;/div&gt;</summary>
		<author><name>Matt</name></author>
	</entry>
	<entry>
		<id>https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_I_semestre&amp;diff=1970</id>
		<title>Lezioni Anno Accademico 2017/18 I semestre</title>
		<link rel="alternate" type="text/html" href="https://so.v2.cs.unibo.it/wiki/index.php?title=Lezioni_Anno_Accademico_2017/18_I_semestre&amp;diff=1970"/>
		<updated>2017-09-29T18:30:29Z</updated>

		<summary type="html">&lt;p&gt;Matt: /* Lezione del 26 settembre 2017 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;''scrivete qui idee, riassunti dei concetti espressi, commenti approfondimenti sulle lezioni.''&lt;br /&gt;
&lt;br /&gt;
== Lezione del 26 settembre 2017 ==&lt;br /&gt;
'''W H Y'''&lt;br /&gt;
&lt;br /&gt;
* What:&lt;br /&gt;
* Who:&lt;br /&gt;
* When:&lt;br /&gt;
* Where:&lt;br /&gt;
* How:&lt;br /&gt;
* Why:&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
* Fonti e strumenti del corso: wiki creata di gruppo, lezioni frontali e esercitazioni.&lt;br /&gt;
* Modalit&amp;amp;agrave; di esame: esame scritto (diviso in due parti: una parte generale e una di programmazione concorrente) + progetto + orale (facoltativo), possibilità di dare solo lo scritto per ottenere un massimo di 18. La modalità per studenti in &amp;quot;difficoltà&amp;quot; è disponibile solo fino agli appelli autunnali.&lt;br /&gt;
* Orario di ricevimento per il primo semestre (fino al 15/09/17): martedì alle 11:30.&lt;br /&gt;
* Universit&amp;amp;agrave; = docenti + studenti.&lt;br /&gt;
* Hardware, Software, Informazione, Dato, Elaborazione, Comunicazione, Memorizzazione, Algoritmo, Programma, Linguaggio, Informatica.&lt;br /&gt;
Digitale/Analogico.&lt;br /&gt;
&lt;br /&gt;
== Lezione del 29 settembre 2017 ==&lt;br /&gt;
== Lezione del 3 ottobre 2017 ==&lt;br /&gt;
== Lezione del 6 ottobre 2017 ==&lt;br /&gt;
== Lezione del 10 ottobre 2017 ==&lt;br /&gt;
== Lezione del 13 ottobre 2017 ==&lt;br /&gt;
== Lezione del 17 ottobre 2017 ==&lt;br /&gt;
== Lezione del 20 ottobre 2017 ==&lt;br /&gt;
== Lezione del 24 ottobre 2017 ==&lt;br /&gt;
== Lezione del 27 ottobre 2017 ==&lt;br /&gt;
== Lezione del 31 ottobre 2017 ==&lt;br /&gt;
== Lezione del 3 novembre 2017 ==&lt;br /&gt;
== Lezione del 7 novembre 2017 ==&lt;br /&gt;
== Lezione del 10 novembre 2017 ==&lt;br /&gt;
== Lezione del 14 novembre 2017 ==&lt;br /&gt;
== Lezione del 17 novembre 2017 ==&lt;br /&gt;
== Lezione del 21 novembre 2017 ==&lt;br /&gt;
== Lezione del 24 novembre 2017 ==&lt;br /&gt;
== Lezione del 28 novembre 2017 ==&lt;br /&gt;
== Lezione del 1 dicembre 2017 ==&lt;br /&gt;
== Lezione del 5 dicembre 2017 ==&lt;br /&gt;
== Lezione del 12 dicembre 2017 ==&lt;br /&gt;
== Lezione del 15 dicembre 2017 ==&lt;/div&gt;</summary>
		<author><name>Matt</name></author>
	</entry>
</feed>