os - Accesso Portabile alle Funzionalità Specifiche di un Sistema Operativo
Scopo: Accesso portabile alle funzionalità specifiche di un sistema operativo
Il modulo os fornisce un wrapper per moduli specifici per una piattaforma tipo posix
, nt
e mac
. L'API per le funzioni disponibile su tutte le piattaforme dovrebbe essere la stessa, quindi l'uso del modulo os offre una qualche garanzia di portabilità. Non tutte le funzioni sono disponibili in tutte le piattaforme, comunque. Molte delle funzioni di gestione dei processi qui descritte non sono disponibil in Windows.
La documentazione Python per il modulo os è sottotitolata "Interfacce varie per sistemi operativi". Il modulo comprende per lo più funzioni per creare e gestire processi in esecuzione oppure contenuto di filesystem (file e direcotry), con poche altre funzionalità qui e là.
Esaminare i Contenuti del File System
Per preparare una lista del contenuto di una directory nel file system si usa listdir()
.
# os_listdir.py
import os
import sys
print(os.listdir(sys.argv[1]))
Il valore ritornato è una lista di tutti i membri nominati della directory passata. Non viene fatta distinzione tra file, sottodirectory o collegamenti simbolici (symlink).
$ python3 os_listdir.py . ['index.rst', 'os_access.py', 'os_cwd_example.py', 'os_directories.py', 'os_environ_example.py', 'os_exec_example.py', 'os_fork_example.py', 'os_kill_example.py', 'os_listdir.py', 'os_listdir.py~', 'os_process_id_example.py', 'os_process_user_example.py', 'os_rename_replace.py', 'os_rename_replace.py~', 'os_scandir.py', 'os_scandir.py~', 'os_spawn_example.py', 'os_stat.py', 'os_stat_chmod.py', 'os_stat_chmod_example.txt', 'os_strerror.py', 'os_strerror.py~', 'os_symlinks.py', 'os_system_background.py', 'os_system_example.py', 'os_system_shell.py', 'os_wait_example.py', 'os_waitpid_example.py', 'os_walk.py']
La funzione walk()
attraversa ricorsivamente una directory e per ogni sotto-directory genera una tuple
che contiene il percorso della directory, ogni immediata sotto-directory di quel percorso, e una lista di nomi di tutti i file in quella directory.
# os_walk.py
import os
import sys
# Se non viene passato un percorso da elencare, si usa /tmp
if len(sys.argv) == 1:
root = '/tmp'
else:
root = sys.argv[1]
for dir_name, sub_dirs, files in os.walk(root):
print(dir_name)
# Risalta i nomi delle sotto-directory aggiungendo una /
sub_dirs = [n + '/' for n in sub_dirs]
# Combina i contenuti delle directory assieme
contents = sub_dirs + files
contents.sort()
# Mostra il contenuto
for c in contents:
print(' {}'.format(c))
print()
Questo esempio mostra una lista ricorsiva di una directory.
$ python3 os_walk.py ../zipimport ../zipimport __init__.py example_package/ index.rst zipimport_example.zip zipimport_find_module.py zipimport_get_code.py zipimport_get_data.py zipimport_get_data_nozip.py zipimport_get_data_zip.py zipimport_get_source.py zipimport_is_package.py zipimport_load_module.py zipimport_make_example.py ../zipimport/example_package README.txt __init__.py
Se sono necessarie maggiori informazioni oltre ai nomi dei file, è probabilmente più efficiente usare scandir()
in luogo di listdir()
visto che molte informazioni vengono raccolte con una sola chiamata di sistema quando la directory viene esaminata.
# os_scandir.py
import os
import sys
for entry in os.scandir(sys.argv[1]):
if entry.is_dir():
typ = 'dir'
elif entry.is_file():
typ = 'file'
elif entry.is_symlink():
typ = 'link'
else:
typ = 'sconosciuto'
print('{name} {typ}'.format(
name=entry.name,
typ=typ,
))
scandir
ritorna una sequenza di istanze di DirEntry
per gli elementi nella directory. L'oggetto ha parecchi attributi e metodi per accedere ai metadati riguardo ai file.
$ python3 os_scandir.py . index.rst file os_access.py file os_cwd_example.py file os_directories.py file os_environ_example.py file os_exec_example.py file os_fork_example.py file os_kill_example.py file os_listdir.py file os_listdir.py~ file os_process_id_example.py file os_process_user_example.py file os_rename_replace.py file os_rename_replace.py~ file os_scandir.py file os_scandir.py~ file os_spawn_example.py file os_stat.py file os_stat_chmod.py file os_stat_chmod_example.txt file os_strerror.py file os_strerror.py~ file os_symlinks.py file os_system_background.py file os_system_example.py file os_system_shell.py file os_wait_example.py file os_waitpid_example.py file os_walk.py file
Gestire i Permessi del File System
E' possibile accedere a informazioni dettagliate riguardo a un file tramite stat()
o lstat
(per verificare lo stato di qualcosa che potrebbe essere un collegamento simbolico). .
# os_stat.py
import os
import sys
import time
if len(sys.argv) == 1:
filename = __file__
else:
filename = sys.argv[1]
stat_info = os.stat(filename)
print('os.stat({}):'.format(filename))
print(' Dimensione :', stat_info.st_size)
print(' Permessi :', oct(stat_info.st_mode))
print(' Proprietario :', stat_info.st_uid)
print(' Dispositivo :', stat_info.st_dev)
print(' Creato :', time.ctime(stat_info.st_ctime))
print(' Ultima modifica:', time.ctime(stat_info.st_mtime))
print(' Ultimo accesso :', time.ctime(stat_info.st_atime))
L'output varierà in base a dove viene installato il codice di esempio. Si provi a passare diversi nomi di file nella riga di comando a os_stat.py
.
$ python3 os_stat.py os_stat.py os.stat(os_stat.py): Dimensione : 570 Permessi : 0o100600 Proprietario : 1000 Dispositivo : 2053 Creato : Sat Jan 7 18:38:14 2017 Ultima modifica: Sat Jan 7 18:38:14 2017 Ultimo accesso : Sat Jan 7 18:38:14 2017 $ python3 os_stat.py zipfile_write.zip os.stat(zipfile_write.zip): Dimensione : 197 Permessi : 0o100600 Proprietario : 1000 Dispositivo : 2053 Creato : Wed Nov 2 22:43:12 2016 Ultima modifica: Sun Oct 16 16:45:04 2011 Ultimo accesso : Sun Nov 13 08:43:44 2016
Nei sitemi tipo Unix, i premessi dei file possono essere cambiati usando chmod()
, passando la modalità come intero. I valori delle modalità possono essere costruiti utilizzando le costanti definite nel modulo stat
. Questo esempio modifica il bit del permesso di esecuzione per l'utente.
# os_stat_chmod.py
import os
import stat
filename = 'os_stat_chmod_example.txt'
if os.path.exists(filename):
os.unlink(filename)
with open(filename, 'wt') as f:
f.write('contenuto')
# Determina quali permessi sono già impostati usando stat
existing_permissions = stat.S_IMODE(os.stat(filename).st_mode)
if not os.access(filename, os.X_OK):
print('Aggiunta dei permessi di esecuzione')
new_permissions = existing_permissions | stat.S_IXUSR
else:
print('Rimozione dei permessi di esecuzione')
# usa xor per rimuovere il permesso di esecuzione per l'utente
new_permissions = existing_permissions ^ stat.S_IXUSR
os.chmod(filename, new_permissions)
Lo script assume che abbia i permessi necessari per modificare la modalità del file quando in esecuzione.
$ python3 os_stat_chmod.py Aggiunta dei permessi di esecuzione
La funzione access()
può essere usata per verificare i diritti di accesso che un processo ha per un file.
# os_access.py
import os
print('Verifica:', __file__)
print('Esiste:', os.access(__file__, os.F_OK))
print('Leggibile:', os.access(__file__, os.R_OK))
print('Scrivibile:', os.access(__file__, os.W_OK))
print('Eseguibile:', os.access(__file__, os.X_OK))
I risultati varieranno a seconda di dove viene installato il codice di esempio, ma l'output dovrebbe essere simile a questo:
$ python3 os_access.py Verifica: os_access.py Esiste: True Leggibile: True Scrivibile: True Eseguibile: False
La documentazione della libreria per access()
include due avvertimenti speciali. Primo, non ha molto senso chiamare access()
per verificare se un file possa essere aperto prima dell'effettiva chiamata di open()
su di esso. Esiste una piccola ma reale finestra temporale tra le due chiamate durante la quale i permessi del file potrebbero cambiare. L'altro avvertimento si applica per lo più a file system di rete che estendono le semantiche dei permessi POSIX. Alcuni tipi di file system potrebbero rispondere alle chiamate POSIX che un processo ha il permesso di accedere a un file, poi riportare un errore quando il tentativo viene fatto usando open()
per una qualche ragione non verificato tramite la chiamata POSIX. Tutto sommato, è meglio chiamare open()
con la modalità richiesta e catturare l'errore IOError
eventualmente sollevato in caso di problemi.
Creare ed Eliminare Directory
Ci sono parecchie funzioni per lavorare con le directory nel file system, incluse quelle per crearle, elencarne il contenuto ed eliminarle.
# os_directories.py
import os
dir_name = 'os_directory_esempio'
print('Creazione', dir_name)
os.makedirs(dir_name)
file_name = os.path.join(dir_name, 'esempio.txt')
print('Creazione', file_name)
with open(file_name, 'wt') as f:
f.write('file di esempio')
print('Pulizia')
os.unlink(file_name)
os.rmdir(dir_name)
Ci sono due insiemi di funzioni per creare ed eliminare directory. Quando si crea una nuova directory con mkdir()
, tutte le directory genitore devono già esistere. Quando si elimina una directory con rmdir()
, solo l'ultima directory (l'ultima parte del percorso) viene in realtà rimossa. Di contro, makedirs()
e removedirs()
operano su tutti i nodi del percorso. makedirs()
creerà qualsiasi parte del percorso che non esista e removedirs()
eliminerà tutte le directory genitore, fintanto che sono vuote.
$ python3 os_directories.py Creazione os_directory_esempio Creazione os_directory_esempio/esempio.txt Pulizia
Lavorare con i Collegamenti Simbolici
Per le piattaforme e i file system che li supportano, ci sono funzioni che lavorano con i collegamenti simbolici (symlink).
# os_symlinks.py
import os
link_name = '/tmp/' + os.path.basename(__file__)
print('Creaione del collegamento {} -> {}'.format(link_name, __file__))
os.symlink(__file__, link_name)
stat_info = os.lstat(link_name)
print('Permessi:', oct(stat_info.st_mode))
print('Punta a:', os.readlink(link_name))
# Pulizia
os.unlink(link_name)
Si usa symlink()
per creare un collegamento simbolico e readlink()
per leggerlo per determinare il file originale puntato dal collegamento. La funzione lstat()
funziona come stat()
, ma opera sui collegamento simbolico.
$ python3 os_symlinks.py Creaione del collegamento /tmp/os_symlinks.py -> os_symlinks.py Permessi: 0o120777 Punta a: os_symlinks.py
Rimpiazzare in Sicurezza un File Esistente
Sostituire o rinominare un file esistante non è idempotente e può esporre le applicazioni a situazioni di race condition. Le fuznioni rename()
e replace()
implementano algoritmi sicuri per queste azioni, utilizzando operazioni atomiche su sistemi conformi POSIX quando possibile,.
# os_rename_replace.py
import glob
import os
with open('inizio_rinomina.txt', 'w') as f:
f.write('parte come inizio_rinomina.txt')
print('Partenza:', glob.glob('*rinomina.txt'))
os.rename('inizio_rinomina.txt', 'fine_rinomina.txt')
print('Dopo rinomina:', glob.glob('*rinomina.txt'))
with open('fine_rinomina.txt', 'r') as f:
print('Contenuti:', repr(f.read()))
with open('rinomina_nuovi_contenuti.txt', 'w') as f:
f.write('termina con i contenuti di rinomina_nuovi_contenuti.txt')
os.replace('rinomina_nuovi_contenuti.txt', 'fine_rinomina.txt')
with open('fine_rinomina.txt', 'r') as f:
print('Dopo la sostituzione:', repr(f.read()))
for name in glob.glob('*rinomina.txt'):
os.unlink(name)
Le funzioni rename()
e replace()
lavorano attraverso i file system, per la maggior parte delle volte. La rinomina di un file può fallire se esso viene spostato verso un nuovo file system o se la destinazione esiste già.
$ python3 os_rename_replace.py Partenza: ['inizio_rinomina.txt'] Dopo rinomina: ['fine_rinomina.txt'] Contenuti: 'parte come inizio_rinomina.txt' Dopo la sostituzione: 'termina con i contenuti di rinomina_nuovi_contenuti.txt'
Identificare e Modificare il Proprietario del Processo
Il prossimo insieme di funzioni fornite da os sono usate per determinare e modificare gli identificativi del proprietario di un processo. Esse sono più frequentemente usate per chi deve programmare demoni o programmi speciali di sistema i quali necessitano la modifica del livello di permessi invece che l'esecuzione come root
. Non si tenta qui di spiegare gli intricati dettagli della sicurezza su Unix, proprietari di processi, ecc., Vedere la lista di riferimenti qui sotto per maggiori dettagli.
L'esempio seguente mostra le vere ed effettive informazioni circa il gruppo e l'utente di un processo, quindi ne cambia i valori effettivi. E' simile a quello che un demone dovrebbe fare quando parte come root
durante l'inizializzazione del sistema, per abbassare il livello di privilegi ed essere eseguito come diverso utente.
# os_process_user_example.py
import os
TEST_GID = 502
TEST_UID = 502
def show_user_info():
print('Utente (reale/effettivo) : {} / {}'.format(
os.getuid(), os.geteuid()))
print('Gruppo (reale/effettivo) : {} / {}'.format(
os.getgid(), os.getegid()))
print('Gruppi reali :', os.getgroups())
print('PRIMA DELLA MDOIFICA:')
show_user_info()
print()
try:
os.setegid(TEST_GID)
except OSError:
print('ERRORE: Non è stato possibile cambiare il gruupo effettivo. '
'Rieseguire come root.')
else:
print('MODIFICA GRUPPO:')
show_user_info()
print()
try:
os.seteuid(TEST_UID)
except OSError:
print('ERRORE: Non è stato possibile cambiare l\'utente effettivo. '
'Rieseguire come root.')
else:
print('MODIFICA UTENTE:')
show_user_info()
print()
Quando eseguito come utente/gruppo 502/502 su sistemi OS X, si vede questo risultato.
$ python3 os_process_user_example.py PRIMA DELLA MDOIFICA: User (actual/effective) : 527 / 527 Group (actual/effective) : 501 / 501 Actual Groups : [501, 701, 402, 702, 500, 12, 61, 80, 98, 398, 399, 33, 100, 204, 395] ERRORE: Non è stato possibile cambiare il gruppo effettivo. Rieseguire come root, ERRORE: Non è stato possibile cambiare l'utente effettivo. Rieseguire come root,
I valori non vengono modificati, visto che non si sta eseguendo come root, un processo non può modificare il valore del proprietario effettivo. Qualsiasi tentativo di impostare l'identificativo utente o il gruppo a qualsiasi cosa diversa dall'utente corrente causa il sollevamento di un errore OSError
Eseguendo lo stesso script usando sudo
per partire con i privilegi di root è un'altra storia.
$ sudo python3 os_process_user_example.py PRIMA DELLA MDOIFICA: User (actual/effective) : 0 / 0 Group (actual/effective) : 0 / 0 Actual Groups : [0, 1, 2, 3, 4, 5, 8, 9, 12, 20, 29, 61, 80, 702, 33, 98, 100, 204, 395, 398, 399, 701] MODIFICA GRUPPO: User (actual/effective) : 0 / 0 Group (actual/effective) : 0 / 502 Actual Groups : [0, 1, 2, 3, 4, 5, 8, 9, 12, 20, 29, 61, 80, 702, 33, 98, 100, 204, 395, 398, 399, 701] MODIFICA UTENTE: User (actual/effective) : 0 / 502 Group (actual/effective) : 0 / 502 Actual Groups : [0, 1, 2, 3, 4, 5, 8, 9, 12, 20, 29, 61, 80, 702, 33, 98, 100, 204, 395, 398, 399, 701]
In questo caso, visto che si è partiti come root, lo script può modificare utente e gruppo effettivi per il processo. Una volta che l'UID effettivo è stato cambiato, il processo è limitato ai permessi per quell'utente. Visto che utenti non root non possono modificare i loro gruppi effettivi, occorre modificare prima il gruppo, poi l'utente.
Gestire l'Ambiente del Processo
Un'altra caratteristica del sistema operativo a disposizione di un programma attraverso il modulo os è l'ambiente. La variabili impostate nell'ambiente sono visibili come stringhe che possono essere lette tramiteos.environ
oppure os.getenv()
. Le variabili d'ambiente sono comunemente usate per valori di configurazione tipo percorsi di ricerca, locazioni di file, e flag di debug. Questo esempio mostra come recuperare una variabile d'ambiente, e passare un valore attraverso un processo figlio.
# os_environ_example.py
import os
print('Valore iniziale:', os.environ.get('TESTVAR', None))
print('Processo figlio:')
os.system('echo $TESTVAR')
os.environ['TESTVAR'] = 'QUESTO VALORE E\' STATO CAMBIATO'
print()
print('Valore modificato:', os.environ['TESTVAR'])
print('Processo figlio:')
os.system('echo $TESTVAR')
del os.environ['TESTVAR']
print()
print('Valore rimosso:', os.environ.get('TESTVAR', None))
print('Processo figlio:')
os.system('echo $TESTVAR')
L'oggetto os.environ
segue la mappatura standard delle API di Python per recuperare e impostare valori. Le modifiche ad os.environ
sono esportate per i processi figli.
$ python3 -u os_environ_example.py Valore iniziale: None Processo figlio: Valore modificato: QUESTO VALORE E' STATO CAMBIATO Processo figlio: QUESTO VALORE E' STATO CAMBIATO Valore rimosso: None Processo figlio:
Gestire la Directory di Lavoro del Processo
I sistemi operativi con file system gerarchici hanno il concetto di directory di lavoro corrente - la directory nel file system che il processo usa come posizione iniziale quando i file sono indirizzati tramite percorsi relativi. La directory di lavoro corrente può essere recuperata con getpwd()
e modificata con chdir()
.
# os_cwd_example.py
import os
print('Partenza:', os.getcwd())
print('Risalita di un livello:', os.pardir)
os.chdir(os.pardir)
print('Dopo lo spostamento:', os.getcwd())
os.curdir
ed os.pardir
sono usate per riferirsi alle directory attuale e genitore in maniera portabile.
$ python3 os_cwd_example.py Partenza: .../pymotw-it3.0/dumpscripts Risalita di un livello: .. Dopo lo spostamento: .../pymotw-it3.0
Eseguire Comandi Esterni
Il modo più basico per eseguire un comando separato, senza interagire con esso, è system()
. Riceve un singolo argomento stringa, che è la riga di comando da eseguire da un sotto processo che esegue una shell.
# os_system_example.py
import os
# Semplice comando
os.system('pwd')
Il valore di ritorno di system()
è il valore di uscita della shell che sta eseguendo il programma impacchettata in un numero a 16 bit, con il byte più alto con lo stato di uscita e il più basso con il numero di segnale che ha causato l'uscita del processo, oppure zero.
$ python3 -u os_system_example.py .../pymotw-it3.0/dumpscripts
Visto che il comando è passato direttamente alla shell per l'esecuzione, può includere sintassi di shell tipo globbing oppure variabili di ambiente.
# os_system_shell.py
import os
# Comando con espansione della shell
os.system('echo $TMPDIR')
La variabile di ambiente $TMPDIR
in questa stringa viene espansa quando la shell esegue la riga di comando.
$ python3 -u os_system_shell.py /home/robby/temp
A meno che il comando sia eseguito esplicitamente in background, la chiamata a system()
blocca fino a che il comando è completato. I flussi di input, output ed errore dal processo figlio sono legati ai flussi appropriati detenuti dal chiamante in modalità predefinita, ma possono essere rediretti utilizzando la sintassi della shell.
# os_system_background.py
import os
import time
print('Chiamata...')
os.system('date; (sleep 3; date) &')
print('In pausa...')
time.sleep(5)
Si sta andando verso i trucchi della shell tuttavia, e ci sono modi migliori per ottenere la stessa cosa.
$ python3 -u os_system_background.py Chiamata... Sat Jan 21 17:03:26 CET 2017 In pausa... Sat Jan 21 17:03:29 CET 2017
Creare Processi con os.fork()
Le funzioni POSIX fork()
ed exec()
(a disposizione sotto Mac OS X, Linux e altre varianti Unix) sono esposte tramite il modulo os. Sono stati scritti interi libri circa l'affidabilità nell'utilizzo di queste funzioni, quindi si consultino le pubblicazioni per maggiori dettagli rispetto a quelli presentati in questa introduzione.
Per creare un nuovo processo come clone di quello corrente si usa fork()
.
# os_fork_example.py
import os
pid = os.fork()
if pid:
print('Id del processo figlio:', pid)
else:
print('Sono il figlio')
L'output varierà a seconda dello stato del sistema ogni volta che viene eseguito l'esempio, ma dovrebbe essere circa così:
$ python3 -u os_fork_example.py Id del processo figlio: 6891 Sono il figlio
Dopo il fork ci sono due processi che stanno eseguendo lo stesso codice. Affinchè un programma possa identificare chi sia chi, deve verificare il valore di ritorno di fork()
. Se il valore è 0
, il processo corrente è il figlio. Altrimenti il programma è in esecuzione nel processo genitore e il valore di ritorno è l'id del processo figlio.
# os_kill_example.py
import os
import signal
import time
def signal_usr1(signum, frame):
"Callback chiamto alla ricezione di un segnale"
pid = os.getpid()
print('Ricevuto USR1 nel processo {}'.format(pid))
print('Fase di fork...')
child_pid = os.fork()
if child_pid:
print('GENITORE: In pausa prima di inviare il segnale...')
time.sleep(1)
print('GENITORE: In segnalazione {}'.format(child_pid))
os.kill(child_pid, signal.SIGUSR1)
else:
print('FIGLIO: Impostazione del gestore di segnale')
signal.signal(signal.SIGUSR1, signal_usr1)
print('FIGLIO: In pausa, in attesa del segnale')
time.sleep(5)
Il genitore può inviare segnali al processo figlio usando kill()
e il modulo signal
. Per prima cosa definisce un gestore di segnale da chiamare quando il segnale viene ricevuto. Poi esegue il fork()
e nel genitore si mette in pausa per un breve periodo prima di inviare un segnale USR1 utilizzando kill()
. Questo esempio usa una breve pausa per dare tempo al processo figlio di impostare il gestore di segnale. Una vera applicazione, non dovrebbe (o vorrebbe) chiamare sleep()
. Nel figlio si imposta il gestore di segnale quindi si mette in pausa per un poco per dare tempo al genitore di inviare il segnale.
$ python3 -u os_kill_example.py Fase di fork... GENITORE: In pausa prima di inviare il segnale... FIGLIO: Impostazione del gestore di segnale FIGLIO: In pausa, in attesa del segnale GENITORE: Segnalazione di 7461 Ricevuto USR1 nel processo 7461
Un semplice modo per gestire un comportamento separato nel processo figlio è quello di verificare il valore di ritorno di fork()
e diramare. Un comportamento maggiormente complesso potrebbe riguardare una separazione del codice maggiore che una semplice diramazione. In altri casi, potrebbe esserci un programma esistente che deve essere impacchettato. Per entrambe queste situazioni le serie di funzioni che iniziano con exec*()
possono essere usate per eseguire un altro programma.
# os_exec_example.py
import os
child_pid = os.fork()
if child_pid:
os.waitpid(child_pid, 0)
else:
os.execlp('pwd', 'pwd', '-P')
Quando un programma viene eseguito da exec()
il codice da quel programma sostituisce il codice dal processo esistente.
$ python3 -u os_exec_example.py .../python/pymotw-it3.0/dumpscripts
Ci sono molte varianti di exec()
, a seconda della forma nella quale siano disponibili gli argomenti, oppure in base al fatto che il percorso e l'ambiente del processo genitore devve essere copiato nel figlio ecc. Per tutte le varianti il primo argomento è il percorso o il nome del file e i restanti argomenti controllano il modo in cui il programma viene eseguito. Essi sono passati sia come argomenti di riga di comando oppure sovrascrivendo l'ambiente del processo (si veda os.environ
e os.getenv()
). Fare riferimento alla documentazione della libreria per maggiori dettagli.
In Attesa dei Processi Figli
Diversi programmi molto esigenti dal punto di vista del calcolo utilizzano processori multipli per aggirare le limitazioni nella gestione dei thread e il lock globale dell'interprete. Quando si fanno partire parecchi processi per eseguire compiti separati, il processo principale dovrà attendere che finiscano uno o più di questi prima di farne partire di nuovi, per evitare un sovraccarico del server. Ci sono alcuni modi diversi per fare questo utilizzando wait()
e le funzioni correlate.
Quando non ha importanza quale processo figlio debba uscire per primo, si usa wait()
. Ritorna non appena un qualsiasi processo figlio esce.
# os_wait_example.py
import os
import sys
import time
for i in range(2):
print('GENITORE {}: Forking {}'.format(os.getpid(), i))
worker_pid = os.fork()
if not worker_pid:
print('WORKER {}: In partenza'.format(i))
time.sleep(2 + i)
print('WORKER {}: Sta finendo'.format(i))
sys.exit(i)
for i in range(2):
print('GENITORE: In attesa di {}'.format(i))
done = os.wait()
print('GENITORE: Figlio completato:', done)
Il valore di ritorno da wait()
è una tuple che contiene l'identificativo del processo e lo stato di uscita combinati in un valore a 16 bit. Il bite inferiore è il numero del segnale che ha ucciso il processo mentre quello superiore è il codice di stato ritornato dal processo in uscita.
$ python3 os_wait_example.py ENITORE 7008: Forking 0 GENITORE 7008: Forking 1 WORKER 0: In partenza GENITORE: In attesa di 0 WORKER 1: In partenza WORKER 0: Sta finendo GENITORE: Figlio completato: (7009, 0) GENITORE: In attesa di 1 WORKER 1: Sta finendo GENITORE: Figlio completato: (7010, 256)
Per attendere l'uscita di un processo specifico si usa waitpid()
.
# os_waitpid_example.py
import os
import sys
import time
workers = []
for i in range(2):
print('GENITORE {}: Forking {}'.format(os.getpid(), i))
worker_pid = os.fork()
if not worker_pid:
print('WORKER {}: In partenza'.format(i))
time.sleep(2 + i)
print('WORKER {}: Sta finendo'.format(i))
sys.exit(i)
workers.append(worker_pid)
for pid in workers:
print('GENITORE: In attesa di {}'.format(pid))
done = os.waitpid(pid, 0)
print('GENITORE: Figlio completato:', done)
Si passa l'identificativo del processo scelto e waitpid()
si interrompe fino a che quel processo esce.
$ python3 os_waitpid_example.py GENITORE 7656: Forking 0 GENITORE 7656: Forking 1 WORKER 0: In partenza GENITORE: In attesa di 7657 WORKER 1: In partenza WORKER 0: Sta finendo GENITORE: Figlio completato: (7657, 0) GENITORE: In attesa di 7658 WORKER 1: Sta finendo GENITORE: Figlio completato: (7658, 256)
wait3()
e wait4()
funzionano in modo simile, ma ritornano informazioni maggiormente dettagliate circa il processo figlio con l'identificativo, lo stato di uscita e l'utilizzo della risorsa.
Produrre Nuovi Processi
Per comodità, la famiglia di funzioni spawn()
gestisce fork()
ed exec()
in una sola istruzione:
# os_spawn_example.py
import os
os.spawnlp(os.P_WAIT, 'pwd', 'pwd', '-P')
Il primo argomento è una modalità che indica se attendere o meno la fine del processo prima di ritornare. Questo esempio attende. Si usa P_NOWAIT
per consentire agli altri processi di partire, poi riprendere nel processo corrente.
$ python3 os_spawn_example.py .../pymotw-it3.0/dumpscripts
Codici di Errore del Sistema Operativo
I codici di errore definiti dai sistemi operativi e gestiti dal modulo errno
possono essere trasformati in stringhe di messaggi usando strerror()
.
# os_strerror.py
import errno
import os
for num in [errno.ENOENT, errno.EINTR, errno.EBUSY]:
name = errno.errorcode[num]
print('[{num:>2}] {name:<6}: {msg}'.format(
name=name, num=num, msg=os.strerror(num)))
Questo esempio mostra i messaggi associati ad alcuni codici di errore che capitano frequentemente.
$ python3 os_strerror.py [ 2] ENOENT: No such file or directory [ 4] EINTR : Interrupted system call [16] EBUSY : Device or resource busy
Vedere anche:
- os
- La documentazione della libreria standard per questo modulo.
- Note di portabilità per os
- signal
- Dettaglio delle tecniche di gestione del segnale (in corso di traduzione)
- subprocess
- Il modulo subprocess sostituisce os.popen() (in corso di traduzione)
- multiprocessing
- Il modulo multiprocessing facilita il lavoro con ulteriori processi (in corso di traduzione)
- tempfile
- Il modulo tempfile per lavorare con file temporanei
- Lavorare con l'alberatura delle directory
- Il modulo shutil include anche funzioni per lavorare con l'alberatura delle directory
- Speaking UNIX, Part 8
- Imparare come UNIX esegue il multitask
- Canali Standard
- Per ulteriori discussioni su stdin, stdout e stderr
- Delve into Unix Process Creation
- Spiega il ciclo di vita di un processo Unix.
- Advanced Programming in the UNIX(R) Environment
- Tratta il lavorare con processi multipli, tipo la gestione di segnali, la chiusura di descrittori di file duplicati, ecc.