public inbox for gentoo-commits@lists.gentoo.org
 help / color / mirror / Atom feed
* [gentoo-commits] gentoo commit in xml/htdocs/doc/it/articles: l-posix2-it.xml
@ 2008-05-01 14:50 Davide Cendron (scen)
  0 siblings, 0 replies; only message in thread
From: Davide Cendron (scen) @ 2008-05-01 14:50 UTC (permalink / raw
  To: gentoo-commits

scen        08/05/01 14:50:07

  Added:                l-posix2-it.xml
  Log:
  Initial commit: version 1.2, revision 1.4 of EN CVS

Revision  Changes    Path
1.1                  xml/htdocs/doc/it/articles/l-posix2-it.xml

file : http://sources.gentoo.org/viewcvs.py/gentoo/xml/htdocs/doc/it/articles/l-posix2-it.xml?rev=1.1&view=markup
plain: http://sources.gentoo.org/viewcvs.py/gentoo/xml/htdocs/doc/it/articles/l-posix2-it.xml?rev=1.1&content-type=text/plain

Index: l-posix2-it.xml
===================================================================
<?xml version='1.0' encoding="UTF-8"?>
<!-- $Header: /var/cvsroot/gentoo/xml/htdocs/doc/it/articles/l-posix2-it.xml,v 1.1 2008/05/01 14:50:06 scen Exp $ -->
<!DOCTYPE guide SYSTEM "/dtd/guide.dtd">

<guide link="/doc/it/articles/l-posix2.xml" disclaimer="articles" lang="it">
<title>Spiegazioni sui thread POSIX, parte 2</title>

<author title="Autore">
  <mail link="drobbins@gentoo.org">Daniel Robbins</mail>
</author>
<author title="Traduzione">
  <mail link="zanetti.massimo@gmail.com">Massimo Zanetti</mail>
</author>

<abstract>
I thread POSIX sono un ottima modo per incrementare la reattività e le
prestazioni del vostro codice. In questo articolo, il secondo di una serie di
tre, Daniel Robbins vi mostra come proteggere l'integrità delle strutture di
dati condivise nel vostro codice con thread usando delle eccellenti piccole cose
chiamate mutex.
</abstract>

<!-- La versione originale di questo articolo è stata pubblicata su IBM
developerWorks, ed è di proprietà della Westtech Information Services. Questo
documento è una versione aggiornata dell'articolo originale, e contiene diversi
miglioramenti fatti dal Gentoo Linux Documentation Team
-->

<version>1.2</version>
<date>2005-10-09</date>

<chapter>
<title>Le piccole cose chiamate mutex</title>
<section id="thread3c">
<title>Mutex me!</title>
<body>

<p>
Nel mio <uri link="/doc/it/articles/l-posix1.xml">precedente articolo</uri>, ho
parlato del codice con thread che faceva cose inusuali ed inaspettate. Due
thread che incrementano una variabile globale venti volte. La variabile avrebbe
dovuto avere un valore finale di 40, ma invece terminava con un valore di 21.
Che cos'era successo? Il problema si è verificato perché un thread "cancellava"
ripetutamente l'incremento fatto dall'altro. Diamo un'occhiata a del codice
corretto che usa un <b>mutex</b> per risolvere il problema:
</p>

<pre caption="thread3.c">
#include &lt;pthread.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;unistd.h&gt;
#include &lt;stdio.h&gt;

int myglobal;
pthread_mutex_t mymutex=PTHREAD_MUTEX_INITIALIZER;

void *thread_function(void *arg) {
  int i,j;
  for ( i=0; i&lt;20; i++ ) {
    pthread_mutex_lock(&amp;mymutex);
    j=myglobal;
    j=j+1;
    printf(".");
    fflush(stdout);
   sleep(1);
    myglobal=j;
    pthread_mutex_unlock(&amp;mymutex);
  }
  return NULL;
}

int main(void) {

  pthread_t mythread;
  int i;

  if ( pthread_create( &amp;mythread, NULL, thread_function, NULL) ) {
    printf("error creating thread.");
    bort();
  }

  for ( i=0; i&lt;20; i++) {
    pthread_mutex_lock(&amp;mymutex);
    myglobal=myglobal+1;
    pthread_mutex_unlock(&amp;mymutex);
    printf("o");
    fflush(stdout);
    sleep(1);
  }

  if ( pthread_join ( mythread, NULL ) ) {
    printf("error joining thread.");
    abort();
  }

  printf("\nmyglobal equals %d\n",myglobal);

  exit(0);

}
</pre>

</body>
</section>
<section>
<title>Tempo di capire</title>
<body>

<p>
Se paragonate questo codice con la versione del mio <uri
link="/doc/it/articles/l-posix1.xml">precedente articolo</uri>, noterete
l'aggiunta delle chiamate a pthread_mutex_lock() e pthread_mutex_unlock().
Queste chiamate compiono una funzione fondamentale nei programmi con thread.
Forniscono un metodo di mutua esclusione (da cui il nome). Due thread non
possono avere lo stesso mutex bloccato nello stesso momento.
</p>

<p>
I mutex lavorano così. Se il thread "a" prova a bloccare un mutex mentre il
thread "b" ha lo stesso mutex bloccato, il thread "a" va in sleep. Appena il
thread  "b" rilascia il mutex (grazie ad una chiamata a pthread_mutex_unlock()),
il thread "a" riuscirà a bloccare il mutex (in altre parole ritornerà dalla
chiamata a pthread_mutex_lock() con il mutex bloccato). Allo stesso modo se il
thread "c" prova a bloccare il mutex mentre il thread "a" lo sta tenendo,
anche il thread "c" andrà temporaneamente in sleep. Tutti i thread che vanno in
sleep, avendo chiamato pthread_mutex_lock() su di un mutex già bloccato si
mettono in coda per l'accesso a quel mutex.
</p>

<p>
pthread_mutex_lock() e pthread_mutex_unlock() sono usati di norma per proteggere
strutture di dati. Cioè, siete sicuri che solo un thread alla volta possa
accedere ad una certa struttura dati bloccandola e sbloccandola. Come potete
aver già indovinato, la libreria POSIX dei thread garantisce un blocco senza
dover mettere il thread in sleep se un thread prova a bloccare un mutex
sbloccato.
</p>

<figure link="/images/docs/l-posix-mutex.gif" caption="Per il vostro piacere, 4
znurts rimettono in atto una scena da recenti chiamate pthread_mutex_lock()"/>

<p>
Il thread in questa immagine che ha il mutex bloccato può accedere alla
complessa struttura dati senza doversi preoccupare di avere altri thread che
ci provino allo stesso tempo. La struttura dati è effettivamente "congelata"
fino a che il mutex non viene sbloccato. È come se le chiamate a
pthread_mutex_lock() e pthread_mutex_unlock() fossero cartelli di "lavori in
corso" che delimitano una particolare parte dei dati condivisi che è stata
modificata  o letta. Le chiamate si comportano come avvisi per gli altri thread
per mandarli in sleep e aspettare il loro turno per bloccare il mutex.
Ovviamente questo è vero se si delimita ciascuna "read" e "write" ad una
particolare struttura dati con chiamate a pthread_mutex_lock() e
pthread_mutex_unlock().
</p>

</body>
</section>
<section>
<title>Ma perché i mutex?</title>
<body>

<p>
Sembra interessante, ma perché vogliamo mettere i nostri thread in sleep? Dopo
tutto, il principale pregio dei thread non è proprio la loro abilità nel
lavorare indipendentemente ed in molti casi contemporaneamente? Certo, questo è
assolutamente vero. Tuttavia, ciascun programma con thread non banali richiede
almeno un certo uso dei mutex.un po' di uso dei mutex. Rivediamo il nostro
programma dell'esempio per capire perché. Se date un'occhiata a
thread_function(), noterete che il mutex è bloccato all'inizio del ciclo e
rilasciato alla fine. In questo esempio, mymutex è usato per proteggere il
valore di myglobal.
</p>

<p>
Se guardate attentamente thread_function(), noterete che il codice relativo
all'incremento copia myglobal su di una variabile locale, incrementa la
variabile locale, va in sleep per un secondo, e solo allora copia il valore
della variabile locale  su myglobal. Senza il mutex
thread_function()sovrascriverebbe il valore incrementato quando si sveglia, se
il nostro thread principale incrementa myglobal durante il breve sonno di un
secondo di thread_function(). Usare un mutex assicura che questo non succeda
(Nel caso voi vi stiate chiedendo, ho aggiunto il ritardo di un secondo per
mostrare un risultato difettoso. Non c'è nessuna motivo perché thread_function()
vada in sleep per un secondo prima di riscrivere il valore della variabile
locale su myglobal). Il nostro nuovo programma usando il mutex produce l'effetto
desiderato.
</p>

<pre caption="Output del programma che usa il mutex">
$ <i>./thread3</i>
o..o..o.o..o..o.o.o.o.o..o..o..o.ooooooo
myglobal vale 40
</pre>

<p>
Per esplorare ulteriormente questo concetto estremamente importante, diamo
un'occhiata alla parte di codice del nostro programma che esegue l'incremento:
</p>

<pre caption="Codice per l'incremento">
thread_function() increment code:
   j=myglobal;
    j=j+1;
    printf(".");
    fflush(stdout);
    sleep(1);
    myglobal=j;

main thread increment code:
    myglobal=myglobal+1;
</pre>

<p>
Se questo codice fosse in un programma a singolo thread ci si aspetterebbe che
il codice thread_function() fosse completamente eseguita. L'esecuzione verrebbe
allora seguita dal codice principale del thread (o nella maniera inversa). In un
programma con thread ma senza mutex, il codice può (e spesso lo farà, grazie
alla chiamata a sleep()) finire l'esecuzione in quest'ordine:
</p>

<pre caption="Ordine di esecuzione">
  thread_function() thread        main thread

  j=myglobal;
  j=j+1;
  printf(".");
  fflush(stdout);
  sleep(1);                        myglobal=myglobal+1;
  myglobal=j;
</pre>

<p>
Quando il codice viene eseguito in questo particolare ordine la modifica fatta
dal thread principale alla variabile myglobal viene sovrascritta. In questo modo
finiamo con un valore non corretto alla fine del nostro programma. Se stessimo
manipolando dei puntatori probabilmente finiremmo con un segfault. Notate che il
nostro thread di thread_function() esegue tutte le sue istruzioni in ordine. Non
è come se thread_function() facesse qualche cosa non in ordine. Il problema è
che abbiamo un altro thread che sta facendo altre modifiche alla stessa
struttura dati nello stesso tempo.
</p>

</body>
</section>
<section>
<title>Dentro i thread 1</title>
<body>

<p>
Prima che vi spieghi come trovare il modo di usare i mutex, vi offro un piccolo
sguardo del lavoro interno dei thread. Ecco il nostro primo esempio:
</p>

<p>
Diciamo che voi abbiate un thread principale che crea tre nuovi thread: i thread
"a", "b" e "c". Decidiamo che il thread "a" sia stato creato per primo, il
thread "b" per secondo e il thread "c" per ultimo.
</p>

<pre caption="Ordine di creazione dei thread">
  pthread_create( &amp;thread_a, NULL, thread_function, NULL);
  pthread_create( &amp;thread_b, NULL, thread_function, NULL);
  pthread_create( &amp;thread_c, NULL, thread_function, NULL);
</pre>

<p>
Dopo che la prima chiamata a pthread_create() è completata, voi potete
immaginare che, o il thread "a" esiste o, che ha finito e che è ora fermo. Dopo
la seconda chiamata a pthread_create(), sia il thread principale che il thread
"b" possono pensare che il thread "a" esista ( o sia fermo).
</p>

<p>
Tuttavia, immediatamente dopo che la seconda chiamata a create() ritorna, il
thread principale non può immaginare quale thread (a o b) stia per partire per
primo. Benché ambedue i thread esistano è a discrezione del kernel e della
libreria dei thread dargli un intervallo di tempo di CPU. Non c'è una regola
fissa su chi parta per primo. Ora è molto probabile che il thread "a" incominci
l'esecuzione prima del thread "b", ma non è garantito. Questo è specialmente
vero su macchine multi-processore. Se voi scrivete del codice che assuma che il
thread "a" effettivamente incominci la sua esecuzione prima del thread "b", vi
ritroverete con un programma che funzionerà il 99% delle volte. O peggio, un
programma che funziona il 100% delle volte sulla vostra macchina ma mai ( 0%)
sul server a quattro processori del vostro cliente.
</p>

<p>
Un'altra cosa che possiamo imparare da questo esempio è che la libreria dei
thread conserva l'ordine di esecuzione del codice per ciascun thread
individuale. In altre parole, queste tre chiamate a pthread_create() verranno
eseguite nell'ordine in cui compaiono. Dal punto di vista del thread principale,
tutto il codice sta lavorando secondo ordine. Qualche volta possiamo sfruttare
questo per ottimizzare parte dei nostri programmi con thread. Come nell'esempio
sopra il thread "c" può pensare che i thread "a" e "b" stiano girando o abbiano
già terminato. Non ci si deve preoccupare della possibilità che i thread "a"
o "b" non siano ancora stati creati. Potete usare questa logica  per ottimizare
i vostri programmi con i thread.
</p>

</body>
</section>
<section>
<title>Dentro i thread 2</title>
<body>

<p>
OK, adesso come altro ipotetico esempio, supponiamo di avere un po' di thread
che stanno eseguendo il codice qui sotto:
</p>

<pre caption="Codice in esecuzione">
  myglobal=myglobal+1;
</pre>

<p>
Abbiamo bisogno di bloccare/sbloccare il mutex rispettivamente prima e dopo
l'incremento? Alcuni di voi potrebbe dire di no. Il compilatore, dopo tutto,
molto probabilmente compilerà l'assegnazione sopra in un unica istruzione
macchina. Come sapete una singola istruzione macchina non può essere interrotta
"mid-stream". Anche gli interrupt hardware rispettano l'atomicità delle
istruzioni macchina. A causa di questa tendenza si può essere tentati di
lasciare fuori le chiamate pthread_mutex_lock() e pthread_mutex_unlock(). Non
fatelo.
</p>

<p>
Sono stato un debole? Non proprio. Primo non dovreste presumere che
l'assegnamento sopra sarà compilato come una singola istruzione macchina, a meno
che non lo abbiate verificato di persona. Anche se inserite alcune parti scritte
in assembler per essere sicuri che l'incremento avvenga atomicamente o anche se
scrivete il compilatore da voi, potete ancora avere dei problemi.
</p>

<p>
Ecco perché. Usare una singola inline assembler opcode probabilmente funzionerà
magnificamente su di una macchina con un unico processore. Ciascun incremento
avviene atomicamente ed è probabile che il risultato sarà quello desiderato. Ma
su di una macchina multiprocessore è un'altra storia. Con le macchine con più
CPU potete avere due processori distinti che eseguono la assegnazione sopra
quasi (o esattamente) allo stesso tempo. E ricordatevi anche che questa modifica
della memoria ha bisogno di passare dalla cache L1 alla L2 e poi alla memoria
principale. (Una macchina SMP non è solo un processore addizionale; ha anche
bisogno di hardware speciale per arbitrare l'accesso alla RAM). Alla fine, voi
non avete idea di quale CPU vinca la "gara" per scrivere nella memoria
principale. Per produrre codice prevedibile, voi volete usare i mutex. I mutex
inseriscono una "memory barrier", che assicura che le scritture nella memoria
principale avvengano nell'ordine in cui i thread bloccano i mutex.
</p>

<p>
Considerate una architettura SMP che aggiorni la memoria principale in blocchi
da 32-bit. Se state incrementando un intero a 64-bit senza i mutex, i quattro
byte più significativi possono provenire da una CPU e gli altri quattro da
un'altra. Male! Peggio ancora, usando questa tecnica scadente, il vostro
programma fallirà una volta ad ogni morte di papa o anche alle tre di mattina
sul sistema di un vostro importante cliente. David R. Butenhof, nel suo libro,
Programming with POSIX Threads (vedi <uri link="#resources">Risorse</uri> alla
fine dell'articolo), contempla tutte le permutazioni possibili quando non si
usano i mutex.
</p>

</body>
</section>
<section>
<title>Troppi mutex</title>
<body>

<p>
Se usate troppi mutex, il vostro codice non avrà nessun tipo di concorrenza
e girerà più lentamente di uno con una soluzione single-threaded. Se ne mettete
troppo pochi, il vostro codice avrà bug strani ed imbarazzanti. Fortunatamente
c'è una via di mezzo. Prima di tutto i mutex sono usati per rendere seriale
l'accesso a dati condivisi. Non usateli per dati non condivisi e neanche se la
logica del vostro programma assicura che solo un thread abbia accesso ad una
certa struttura dati in un determinato tempo.
</p>

<p>
In secondo luogo se state usando dati condivisi, usate i mutex sia in lettura
che in scrittura. Circondate le vostre sezioni di lettura e scrittura con
pthread_mutex_lock() e pthread_mutex_unlock(), o usatele ogni volta che un
invariante del programma è momentaneamente rotto. Imparate a vedere il vostro
codice secondo la prospettiva di un singolo thread e assicuratevi che ciascun
thread nel vostro programma abbia una buona visione della memoria. Probabilmente
impiegherete molto tempo  prima di abituarvi a scrivere del codice con i mutex,
ma presto diventeranno una seconda natura per voi e sarete capaci di usarli in
modo corretto senza doverci pensare <e>troppo</e>.
</p>

</body>
</section>
<section>
<title>Usare le chiamate: inizializzazione</title>
<body>

<p>
OK, adesso è giunto il momento di vedere tutti i possibili utilizzi dei mutex.
Primo: incominciamo con l'inizializzazione. Nel nostro <uri
link="#thread3c">esempio thread3.c</uri>,, usiamo un metodo di inizializzazione
statico. Questo richiede la dichiarazione di una variabile pthread_mutex_t e
assegnarle il valore della costante PTHREAD_MUTEX_INITIALIZER:
</p>

<pre caption="Esempio di inizializzazione">
pthread_mutex_t mymutex=PTHREAD_MUTEX_INITIALIZER;
</pre>

<p>
Questo è abbastanza semplice. Ma si possono creare mutex anche dinamicamente.
Usate questo metodo dinamico ogni volta che il vostro codice alloca un nuovo
mutex usando malloc(). In questo caso  il metodo di inizializzazione statico
non funzionerebbe e dovrebbe venir usata la routine pthread_mutex_init():
</p>

<pre caption="Creare un mutex in modo dinamico">
int pthread_mutex_init( pthread_mutex_t *mymutex, const
pthread_mutexattr_t*attr)
</pre>

<p>
Come potete notare, pthread_mutex_init accetta un puntatore a una regione di
memoria preventivamente allocata per inizializzarlo come mutex. Come secondo
argomento, può anche accettare un puntatore opzionale pthread_mutexattr_t.
Questa struttura può essere usata per impostare diversi attributi del mutex. Ma
di norma questi attributi non sono necessari, così generalmente si specifica
NULL.
</p>

<p>
Ogni volta che inizializzate un mutex usando pthread_mutex_init(), dovrebbe
essere distrutto usando pthread_mutex_destroy(). pthread_mutex_destroy() accetta
un unico puntatore a pthread_mutex_t e libera tutte le risorse impegnate dal
mutex al momento della sua creazione. State attenti che pthread_mutex_destroy()
non liberi la memoria usata per salvare pthread_mutex_t. Dipende da voi svuotare
la memoria. Ricordatevi anche che sia pthread_mutex_init() che
pthread_mutex_destroy() ritornano zero se tutto va bene.
</p>

</body>
</section>
<section>
<title>Usare le chiamate: locking</title>
<body>

<pre caption="Esempio di locking">
pthread_mutex_lock(pthread_mutex_t *mutex)
</pre>

<p>
pthread_mutex_lock() accetta un unico puntatore ad un mutex per bloccarlo. Se il
mutex fosse già bloccato, il chiamante andrà in sleep. Quando la funzione
ritorna il chiamante sarà (ovviamente) svegliato e a questo punto terrà anche il
lock. Questa chiamata può tornare uno zero, in caso di successo, o un codice di
errore diverso da zero in caso di fallimento.
</p>

<pre caption="Esempio di unlocking">
pthread_mutex_unlock(pthread_mutex_t *mutex)
</pre>

<p>
pthread_mutex_unlock() complementa pthread_mutex_lock() e sblocca un mutex ch
e il thread aveva già bloccato. Dovreste sempre sbloccare un mutex che avete
bloccato quando è sufficientemente sicuro (per aumentarne le prestazione). E non
dovreste mai sbloccare un mutex di cui non abbiate il blocco (altrimenti la
chiamata a pthread_mutex_unlock() fallirà con un valore di ritorno EPERM diverso
da zero).
</p>

<pre caption="Provando l'esempio di lock">
pthread_mutex_trylock(pthread_mutex_t *mutex)
</pre>

<p>
Questa chiamata è comoda quando volete bloccare un mutex mentre il vostro thread
sta facendo qualcos'altro perché il mutex è al momento bloccato. Quando chiamate
pthread_mutex_trylock() cercherete di bloccare il mutex. Se il mutex è al
momento sbloccato voi lo bloccate e questa funzione ritornerà zero. Tuttavia, se
il mutex è bloccato, questa chiamata non lo bloccherà. Ritornerà invece un
valore di errore EBUSY, diverso da zero. A questo punto proseguite con le vostre
attività e provate a bloccarlo più tardi.
</p>

</body>
</section>
<section>
<title>Aspettando le condizioni</title>
<body>

<p>
I mutex sono strumenti necessari per i programmi con thread, ma non possono fare
tutto. Cosa succede, ad esempio, se il vostro thread sta aspettando che si
verifichi una certa condizione su dei dati condivisi? Il vostro codice sblocca e
blocca ripetutamente il mutex, controllando ogni cambiamento del valore. Allo
stesso tempo sbloccherà velocemente il mutex così che altri possano fare i
cambiamenti necessari. Ma questo è un approccio orribile, perché questo thread
dovrà fare un busy loop per determinare un cambiamento in un ragionevole lasso
di tempo.
</p>

<p>
Potete mettere il thread chiamante in sleep per un po', diciamo tre secondi tra
ogni controllo, ma allora il vostro codice con thread non sarà reattivo in
maniera ottimale. Quello di cui avete bisogno è un modo per mettere in sleep un
thread mentre aspetta che alcune condizioni vengano a verificarsi. Una volta che
si verificano le condizioni avete bisogno di un metodo per svegliare i o il
thread che sta aspettando che quella condizione si verifichi. Se riuscite a fare
ciò il vostro codice con thread sarà veramente efficiente e non vincolerà
importanti bloccaggi di mutex. Questo è ciò che le variabili della condizione
POSIX possono fare per voi.
</p>

<p>
E le variabili della condizione POSIX sono l'argomento del mio prossimo
articolo, dove vi mostrerà esattamente come usarle. A quel punto avrete tutte le
risorse per creare sofisticati programmi con thread che modellano gruppi di
lavoro, linee di assembler e altro ancora. Nel nuovo articolo accelererò il
passo ora che avete più familiarità con i thread. Spero di riuscire ad inserire
un programma con thread ragionevolmente sofisticato alla fine del prossimo
articolo e parlando di cura delle condizioni, arrivederci!
</p>

</body>
</section>
</chapter>

<chapter id="resources">
<title>Risorse</title>
<section>
<body>

<ul>
  <li>
    Leggi la spiegazione ai thread POSIX di Daniel <uri
    link="l-posix1.xml">Parte 1</uri> e <uri link="l-posix3.xml">Parte
    3</uri>.
  </li>
  <li>
    Leggete la documentazione su <uri
    link="http://metalab.unc.edu/pub/Linux/docs/faqs/Threads-FAQ/html/">Linux
    threads</uri>, di Sean Walton, KB7rfa.
  </li>
  <li>
    Date sempre un'occhiata all'amichevole pagina del manuale LINUX di pthread
    (<c>man -k pthread</c>).
  </li>
  <li>
    Guardate <uri link="http://pauillac.inria.fr/~xleroy/linuxthreads/">The
    LinuxThreads Library.</uri>
  </li>
  <li>
    <uri link="http://www.users.itl.net.ua/~prool/proolix.html">Proolix</uri> è
    un semplice sistema operativo POSIX-compliant per i8086+ in continuo
    sviluppo.
  </li>
  <li>
    Date un'occhiata al libro di David R. Butenhof <uri
    link="http://www.amazon.com/exec/obidos/ASIN/0201633922/o/qid=961544788/sr=8-1/ref=aps_sr_b_1_1/002-2882413-1227240">
    Programming with POSIX Threads</uri>, in cui lui affronta, tra le altre
    cose, le possibili permutazioni del non usare i mutex.
  </li>
  <li>
    Prendete il libro di W. Richard Stevens <!-- FIXME not available out
there,
    commenting out and leaving finding it pleasure to readers<uri

link="http://devworks.krcinfo.com/WebForms/ProductDetails.aspx?ProductID=
0139498761">-->"UNIX Network Programming".
  </li>
  <li>
    Trovate ulteriori risorse per sviluppatore Linux in <uri
    link="http://www.ibm.com/developerworks/linux/">developerWorks Linux
    zone</uri>.
  </li>
  <li>
    Fatevi coinvolgere dalla comunità di developerWorks partecipando ai
    <uri link="http://www.ibm.com/developerworks/blogs/">blog di
    developerWorks</uri>.
  </li>
  <!-- FIXME Ugly advertisement
  15:25  <rane> is that ok to put such advertisment at gentoo.org?
  15:26  * neysx would not copy them
  15:27  <SwifT> I wouldn't keep it
  <li>
    Purchase <uri

link="http://devworks.krcinfo.com/WebForms/ProductList.aspx?Search=Category&id=
300&parent=Linux">Linux
    books at discounted prices</uri> in the Linux section of the
    Developer Bookstore.
  </li>
  <li>
    Order the no-charge SEK for Linux, a two-DVD set containing the latest IBM
    trial software for Linux from DB2&registered;, Lotus&registered;,
    Rational&registered;, Tivoli&registered;, and WebSphere&registered;.
  </li>
  <li>
    Innovate your next Linux development project with <uri
    link="http://www.ibm.com/developerworks/downloads/?S_TACT=105AGX03">IBM
trial software
    </uri>, available for download directly from developerWorks.
  </li>
  -->
</ul>

</body>
</section>
</chapter>
</guide>



-- 
gentoo-commits@lists.gentoo.org mailing list



^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2008-05-01 14:50 UTC | newest]

Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2008-05-01 14:50 [gentoo-commits] gentoo commit in xml/htdocs/doc/it/articles: l-posix2-it.xml Davide Cendron (scen)

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox