LA CAMPANA

C'è chi ha letto questa notizia prima di te.
Iscriviti per ricevere gli ultimi articoli.
E-mail
Nome
Cognome
Come vuoi leggere The Bell
Niente spam

Mentre viene eseguito il codice in modalità kernel con la priorità più alta, nessun altro codice su questo processore può essere avviato. Naturalmente, se una quantità eccessiva di codice di programma viene eseguita con valori IRQL troppo alti, ciò porterà inevitabilmente a un degrado generale del sistema.

Per risolvere questo tipo di problema, è necessario progettare il codice in modalità kernel per evitare operazioni prolungate con IRQL superiori. Una delle componenti più importanti di questa strategia sono le chiamate di procedura differite (DPC), chiamate di procedura differite.

Operazione DPC

Lo schema per l'utilizzo di chiamate procedurali differite consente di costruire il processo di esecuzione in modo tale che l'attività possa essere pianificato codice eseguito a un livello IRQL elevato, ma non lo è ancora eseguita ... Questo differimento è utile se è presente un servizio di interruzione nel driver e non c'è motivo di bloccare l'esecuzione di altro codice a un IRQL inferiore. In altre parole, quando il trattamento di questa situazione può essere rimandato indolore a un momento successivo.

Il sistema operativo mantiene una coda di oggetti DPC per tenere traccia delle richieste di chiamata alle procedure DPC.

Per cominciare, ci limiteremo a considerare un caso più semplice di lavorare con procedure DPC progettate per essere utilizzate insieme alle procedure di gestione degli interrupt. Questo tipo di procedure DPC ha ricevuto un nome speciale nella letteratura DpcForIsr.

L'oggetto DPC da utilizzare nelle routine di interrupt viene creato su chiamata IoInitializeDpcRequestsolitamente eseguita nelle procedure di avvio del driver. Questa chiamata registra la procedura offerta dal driver DpcForIsr e la associa all'oggetto creato, una tecnica abbastanza comune in Windows. Va notato in particolare che l'oggetto DPC creato da questa chiamata rimarrà nelle viscere del sistema operativo, inaccessibile allo sviluppatore del driver. (La differenza tra DpcForIsr e altre procedure DPC è solo che queste ultime vengono gestite dalle chiamate Ke ... Dpce gli oggetti DPC creati per loro sono disponibili per lo sviluppatore del driver.)

Se il driver ha registrato la sua procedura DpcForIsr, durante l'elaborazione dell'interrupt ISR da parte della procedura, l'oggetto DPC corrispondente può essere inserito nella coda di sistema DPC (in effetti, una richiesta per chiamare questa procedura DpcForIsr in un secondo momento) - chiamando IoRequestDpc... La procedura DpcForIsr completerà successivamente l'elaborazione della richiesta ricevuta dall'ISR dalla procedura, che verrà eseguita in condizioni meno critiche e con IRQL basso.

In termini generali, il funzionamento delle procedure DPC (in questo caso, DpcForIsr) è costituito dalle seguenti operazioni:

  • Quando una parte di codice in esecuzione con un IRQL alto (hardware) vuole programmare parte del suo lavoro da fare con un IRQL basso, aggiunge un oggetto DPC alla coda di chiamata della procedura in sospeso del sistema.
  • Prima o poi, l'IRQL del processore scende al di sotto di DISPATCH_LEVEL e il lavoro ritardato dall'interruzione viene svolto dalla funzione DPC. Il dispatcher DPC recupera ogni oggetto DPC dalla coda e chiama la funzione appropriata, il cui puntatore è memorizzato in quell'oggetto. Questa funzione viene chiamata mentre il processore opera su DISPATCH_LEVEL.

Caratteristiche del meccanismo DPC

In generale, lavorare con chiamate procedurali differite non è difficile perché i sistemi operativi Windows 2000 / XP / Server 2003 offrono un ampio set di chiamate di sistema che nascondono gran parte dei dettagli del processo. Tuttavia, dovrebbero essere evidenziati due degli aspetti più ingannevoli del lavoro con DPC.

In primo luogo, Windows NT 5 impone una limitazione che una singola istanza di un oggetto DPC può essere inserita nella coda DPC di sistema a un determinato intervallo di tempo. I tentativi di inserire un oggetto nella coda DPC che corrisponde esattamente a quello già presente vengono rifiutati. Di conseguenza, si verifica solo una chiamata alla procedura DPC, anche se il driver attende il completamento di due chiamate. Ciò può accadere se due interruzioni sono state attivate dal dispositivo in manutenzione e l'elaborazione della prima chiamata procedurale differita non è ancora iniziata. La prima istanza dell'oggetto DPC è ancora in coda, mentre il driver ha già avviato l'elaborazione del secondo interrupt.

Il design del driver dovrebbe prevedere un tale corso del meccanismo DPC. Forse dovrebbe essere fornito un contatore di richieste DPC aggiuntivo o il driver può implementare la propria implementazione della coda delle richieste. Al momento dell'effettiva procedura lazy, puoi controllare il contatore e la tua coda di richieste per determinare quale lavoro specifico dovrebbe essere fatto.

In secondo luogo, ci sono problemi di sincronizzazione significativi quando si lavora su piattaforme multiprocessore. Si supponga che un codice di programma in esecuzione su un processore esegua il servizio di interruzione e pianifichi una chiamata di procedura DPC (inserendo un oggetto DPC nella coda di sistema). Tuttavia, anche prima che l'interruzione venga completamente elaborata, un altro processore può iniziare a elaborare l'oggetto DPC inserito nella coda di sistema. Pertanto, si verifica una situazione in cui il codice del servizio di interruzione viene eseguito in parallelo e contemporaneamente al codice della procedura DPC. Per questo motivo, è necessario prevedere misure per la sincronizzazione affidabile dell'accesso del codice del programma della procedura DPC alle risorse utilizzate in combinazione con la procedura del servizio di interruzione del driver.

Se guardi attentamente l'elenco dei parametri di chiamata IoInitializeDpcRequest e IoRequestDpc (progettato per funzionare con le procedure DpcForIsr), è facile vedere che l'oggetto DPC è "associato" all'oggetto dispositivo. Quando si inserisce questo oggetto nella coda DPC al momento della procedura ISR, viene indicato anche l'oggetto dispositivo. Ciò raggiunge una certezza, la chiamata a quale particolare procedura DPC è "ordinata" dalla procedura ISR (correlazione per oggetto dispositivo). Ciò significa anche che un driver che ha creato diversi oggetti dispositivo (un caso piuttosto raro) può anche sfruttare diverse procedure DpcForIsr, una per ogni oggetto dispositivo.

Il meccanismo DPC di sistema impedisce l'elaborazione simultanea di oggetti DPC dalla coda di sistema, anche nelle configurazioni multiprocessore. Pertanto, se le risorse sono condivise da diverse procedure differite, non è necessario preoccuparsi di sincronizzare l'accesso ad esse.

Quanto sopra ha discusso l'uso delle procedure DPC per completare la gestione degli interrupt, ovvero DpcForIsr. Tuttavia, le procedure DPC possono essere utilizzate in un altro modo, ad esempio in combinazione con i timer per organizzare l'attesa. A tale scopo, verrà creato un oggetto DPC utilizzando la chiamata KeInitializeDPCche associa questo oggetto alla procedura DPC inclusa con il driver. Dopodiché, puoi impostare il timeout nel file precedentemente inizializzato (usando KeInitializeTimer o KeInitializeEx) oggetto timer. La chiamata viene utilizzata per impostare l'intervallo di attesa KeSetTimer, a cui, come uno dei parametri, deve essere passato un puntatore a un oggetto DPC inizializzato. Allo scadere del timeout DPC, l'oggetto verrà aggiunto alla coda DPC di sistema e la procedura DPC ad esso associata verrà chiamata appena possibile. I DPC di questo secondo tipo sono indicati nella documentazione DDK come "DPC personalizzato". (Questo utilizzo delle routine DPC le rende molto simili alle chiamate APC in modalità utente.)

Per inserire nella coda DPC di sistema degli oggetti corrispondenti al secondo tipo di procedure DPC (non relative agli interrupt), utilizzare la chiamata KeInsertQueueDpc... Di conseguenza, il codice iniziatore chiamante deve funzionare a un livello IRQL di almeno DISPATCH_LEVEL.

Per cancellare la coda DPC di sistema dalle procedure DPC personalizzate, ad esempio, se il driver deve terminare urgentemente, chiamare KeRemoveQueueDpcche può essere chiamato da qualsiasi codice di livello IRQL.

Gestione degli interrupt del timer

Ogni computer ha un timer hardware o un orologio di sistema che genera un interrupt hardware a intervalli fissi. L'intervallo di tempo tra interrupt adiacenti è chiamato tick del processore o semplicemente tick (tick della CPU, tick dell'orologio). In genere, il timer di sistema supporta più valori di tick, ma su UNIX questo valore è solitamente impostato su 10 millisecondi, sebbene questo valore possa differire per le diverse versioni del sistema operativo. La maggior parte dei sistemi memorizza questo valore nella costante HZ, che è definita nel file di intestazione Ad esempio, per un tick di 10 millisecondi, il valore HZ è impostato su 100.

Il gestore di interrupt del kernel viene richiamato da un interrupt del timer hardware, che di solito è la priorità più alta. Pertanto, la gestione degli interrupt dovrebbe richiedere un tempo minimo. In generale, il gestore risolve le seguenti attività:

1. Aggiornamento delle statistiche sull'utilizzo della CPU per il processo corrente

2. Esecuzione di una serie di funzioni relative alla pianificazione dei processi, come il ricalcolo delle priorità e il controllo della fascia oraria di un processo

3. Verificare se la quota del processore per questo processo è stata superata e inviare il segnale SIGXCPU a questo processo se

4. Aggiornamento dell'ora di sistema (ora del giorno) e di altri timer correlati

5. Gestione delle chiamate ritardate

6. Gestione degli allarmi

7. Riattivare, se necessario, i processi di sistema, come il page manager e lo scambio

Alcune attività non devono essere completate ad ogni tick. La maggior parte dei sistemi introduce una notazione di tick principale, che ricorre a ogni tick, a seconda della versione specifica del sistema. Un certo insieme di funzioni viene eseguito solo sui tick principali. Ad esempio, ricalcola le priorità ogni 4 tick e SVR4 elabora e riattiva i processi di sistema una volta al secondo McKusick M.K., Neville-Neil J.W. . FreeBSD: architettura e implementazione . - M .: KUDITS-OBRAZ, 2006 - 800 p ...

Chiamate ritardate

Una chiamata differita definisce una funzione che verrà chiamata dal kernel dopo un po 'di tempo. Ad esempio, in SVR4, qualsiasi sottosistema del kernel può registrare una chiamata in sospeso come questa:

int co_ID \u003d timeout (void (* fn) (), caddr_t arg, delta lungo)

dove fn definisce l'indirizzo della funzione che deve essere chiamata, mentre l'argomento arg verrà passato ad essa e la chiamata stessa sarà effettuata tramite delta tick. Il kernel effettua una chiamata a fn () nel contesto del sistema, quindi la funzione di chiamata differita non dovrebbe accedere allo spazio degli indirizzi del processo corrente (poiché non ha nulla a che fare con esso), e inoltre non dovrebbe entrare in uno stato di sospensione.

Le chiamate differite vengono utilizzate per eseguire molte funzioni, ad esempio:

1. Esecuzione di una serie di funzioni dello scheduler e del sottosistema di gestione della memoria.

2. Esecuzione di numerose funzioni del driver di periferica per eventi che è relativamente improbabile che si verifichino. Un esempio è il modulo del protocollo TCP, che implementa così la ritrasmissione dei pacchetti di rete per timeout.

3. Dispositivi di polling che non supportano gli interrupt.

Notare che le funzioni di chiamata differita vengono eseguite nel contesto di sistema, non nel contesto di interrupt. Queste funzioni non vengono chiamate dal gestore di interrupt del timer, ma da un gestore di chiamate in sospeso separato che viene eseguito dopo l'elaborazione dell'interruzione del timer. Durante l'elaborazione di un interrupt del timer, il sistema verifica se è necessario avviare determinate funzioni di chiamata differita e imposta il flag appropriato per esse. A sua volta, il gestore delle chiamate differite controlla i flag e lancia il necessario nel contesto del sistema McKusick MK, Neville-Neil JV FreeBSD: architettura e implementazione. - M .: KUDITS-OBRAZ, 2006 - 800 p ...

Contesto del processo

Il contesto del processo include il contenuto dello spazio degli indirizzi dell'attività allocato al processo, nonché i contenuti dei registri hardware relativi al processo e delle strutture dati del kernel. Da un punto di vista formale, il contesto del processo combina il contesto dell'utente, il contesto del registro e il contesto del sistema.

Il contesto utente è costituito dalle istruzioni e dai dati del processo, dallo stack delle attività e dal contenuto dello spazio di memoria condiviso negli indirizzi virtuali del processo. Anche quelle parti dello spazio degli indirizzi virtuali del processo che sono periodicamente assenti dalla RAM a causa dello scaricamento o della sostituzione della pagina sono incluse nel contesto dell'utente.

Il contesto del registro è costituito dai seguenti componenti:

1. Un contatore di istruzioni che indica l'indirizzo della prossima istruzione che deve essere eseguita dall'elaboratore centrale; questo indirizzo è un indirizzo virtuale all'interno dello spazio kernel o spazio attività.

2. Processor Status Register (PS), che indica lo stato hardware della macchina in relazione al processo. Il registro PS, ad esempio, di solito contiene sottocampi che indicano se il risultato dell'ultimo calcolo è zero, positivo o negativo, se il registro è in overflow con il set del bit di riporto, ecc. Le operazioni che influenzano l'impostazione del registro PS vengono eseguite per un processo separato, perché quindi il registro PS contiene lo stato hardware della macchina in relazione al processo. Altri importanti sottocampi del registro PS indicano il livello di interruzione del processore corrente, nonché le modalità di esecuzione attuali e precedenti del processo (modalità kernel / task). Il valore del sottocampo della modalità di esecuzione corrente del processo determina se il processo può eseguire comandi privilegiati e accedere allo spazio degli indirizzi del kernel.

3. Puntatore della parte superiore dello stack, che contiene l'indirizzo dell'elemento successivo del kernel o dello stack di attività, in base alla modalità di esecuzione del processo. A seconda dell'architettura della macchina, il puntatore dello stack punta al successivo elemento dello stack libero o all'ultimo elemento utilizzato. La direzione di aumentare lo stack (verso indirizzi più alti o più bassi) dipende anche dall'architettura della macchina, ma per noi queste domande sono ormai insignificanti.

4. Registri di uso generale, che contengono le informazioni generate dal processo durante la sua esecuzione.

Il contesto di sistema di un processo ha una "parte statica" (i primi tre elementi nell'elenco seguente) e una "parte dinamica" (gli ultimi due elementi). Durante il suo tempo di esecuzione, un processo ha sempre una parte statica del contesto di sistema, ma può avere un numero variabile di parti dinamiche. La parte dinamica del contesto di sistema può essere rappresentata come uno stack, i cui elementi sono livelli di contesto che vengono inseriti nello stack dal kernel o estratti dallo stack quando si verificano vari eventi. Il contesto di sistema include i seguenti componenti:

1. Una voce nella tabella del processo che descrive lo stato del processo (Sezione 6.1) e contiene varie informazioni di controllo a cui il kernel può sempre accedere.

2. La parte dello spazio di indirizzamento dell'attività assegnata al processo, che memorizza le informazioni di controllo sul processo, disponibile solo nel contesto del processo. I parametri di controllo generali come la priorità del processo sono memorizzati nella tabella del processo perché è necessario accedervi al di fuori del contesto del processo.

3. Le voci nella tabella dell'area del processo privata, nelle tabelle dell'area generale e nelle tabelle delle pagine, necessarie per convertire gli indirizzi virtuali in indirizzi fisici, e quindi descrivere il comando, i dati, lo stack e altre aree del processo. Se più processi condividono aree comuni, queste aree fanno parte del contesto di ogni processo, poiché ogni processo funziona con queste aree indipendentemente dagli altri processi. Le attività di gestione della memoria includono l'identificazione di porzioni non residenti in memoria dello spazio degli indirizzi virtuali di un processo.

4. Stack del kernel, che memorizza i record delle procedure del kernel se il processo è in esecuzione in modalità kernel. Sebbene tutti i processi utilizzino gli stessi programmi del kernel, ogni processo ha la propria copia dello stack del kernel per memorizzare le singole chiamate alle funzioni del kernel. Ad esempio, supponiamo che un processo chiami creat e si interrompa in attesa di una nuova assegnazione di indice, e un altro processo chiami read e sospenda i trasferimenti da disco a memoria in sospeso. Entrambi i processi accedono alle funzioni del kernel e ciascuno di essi ha uno stack separato, che memorizza la sequenza delle chiamate effettuate. Il kernel deve essere in grado di ripristinare il contenuto dello stack del kernel e la posizione del puntatore allo stack per riprendere l'esecuzione di un processo in modalità kernel. Su vari sistemi, lo stack del kernel si trova spesso nello spazio del processo, ma questo stack è logicamente indipendente e quindi può stare in un'area separata di memoria. Quando un processo è in esecuzione in modalità attività, lo stack del kernel corrispondente è vuoto.

5. La parte dinamica del contesto di sistema del processo, costituita da più livelli e avente la forma di una pila, che viene liberata dagli elementi nell'ordine inverso al loro arrivo. Ogni livello del contesto del sistema contiene le informazioni necessarie per ripristinare il livello precedente, incluso il contesto del registro del livello precedente.

Il kernel inserisce il livello di contesto nello stack quando si verifica un interrupt, quando viene chiamata una funzione di sistema o quando il contesto di un processo cambia. Il livello di contesto viene estratto dallo stack quando viene elaborato un interrupt, quando il processo ritorna in modalità task dopo aver eseguito una funzione di sistema o quando viene effettuato un cambio di contesto. Pertanto, il cambio di contesto implica sia il push del livello di contesto nello stack che il rilascio del livello dallo stack: il kernel spinge il livello di contesto del vecchio processo nello stack e fa apparire il livello di contesto del nuovo processo dallo stack. Le informazioni richieste per ripristinare il livello di contesto corrente sono memorizzate nella voce della tabella dei processi Robachevsky A.M. Sistema operativo UNIX . - SPb.: BHV - Pietroburgo , 2002. -- 528 a partire dal . .

Sebbene la maggior parte degli interrupt venga generata a livello hardware, il kernel di Windows genera interrupt software per una varietà di attività, tra cui le seguenti:

  • lanciare l'invio di thread;
  • elaborazione di interruzioni non critiche per il tempo;
  • gestione della scadenza del timer;
  • esecuzione asincrona di una procedura nel contesto di un thread specifico;
  • supporto per operazioni I / O asincrone.

Queste attività vengono discusse nelle sezioni seguenti.

Interruzioni o interruzioni inviate di una chiamata di procedura differita (DeferredProcedureCall, DPC). Quando un thread non può più continuare l'esecuzione, forse perché è terminato o perché è entrato volontariamente in uno stato di attesa, il kernel chiama direttamente il dispatcher per cambiare immediatamente il contesto.

Ma a volte il kernel scopre che la riprogrammazione deve avvenire quando il codice in esecuzione è profondamente annidato. In una situazione del genere, il kernel richiede l'invio, ma lo rimanda fino a quando non completa il suo lavoro corrente. L'utilizzo di un interrupt DPC software è un modo conveniente per implementare questo ritardo.

Il kernel solleva sempre l'IRQL della CPU a DPC / invio o superiore quando ha bisogno di sincronizzare l'accesso alle strutture comuni del kernel. Ciò blocca gli interrupt software aggiuntivi e l'invio di thread. Quando il kernel rileva che è necessario un invio, richiede un interrupt DPC / invio, ma il processore ritarda l'interruzione perché l'IRQL è pari o superiore a quel livello.

Quando il kernel termina il suo lavoro corrente, il processore vede che sta per spingere l'IRQL al di sotto del livello DPC / invio e controlla eventuali interruzioni di invio in sospeso. Se sono presenti tali interrupt, l'IRQL viene abbassato al livello DPC / invio e vengono gestiti gli interrupt di invio. L'attivazione di un thread dispatcher utilizzando un interrupt software è un modo per rinviare l'invio fino a quando non si verificano le giuste circostanze. Ma Windows utilizza interruzioni software per rinviare anche altri tipi di elaborazione.

Oltre all'invio di thread, il kernel gestisce IRQL e chiamate di procedura differita (DPC) a questo livello. DPC è una funzione che esegue un'attività di sistema che è meno critica in termini di tempo rispetto all'attività corrente.

Le funzioni sono chiamate differite perché non richiedono l'esecuzione immediata.

Le chiamate di procedura differite consentono al sistema operativo di generare un interrupt ed eseguire una funzione di sistema in modalità kernel.

Il kernel utilizza chiamate DPC per servire le scadenze dei timer (e thread liberi che attendono la scadenza dei timer) e per ripianificare l'utilizzo della CPU dopo che il tempo allocato del thread (quantum del thread) è scaduto. I driver di dispositivo utilizzano chiamate DPC per gestire gli interrupt. Per garantire un servizio tempestivo delle interruzioni software, Windows collabora con i driver di dispositivo per mantenere gli IRQL al di sotto degli IRQL del dispositivo. Uno dei modi per raggiungere questo obiettivo è che gli ISR \u200b\u200bdel driver del dispositivo eseguano il lavoro minimo necessario per notificare i propri dispositivi, mantenere uno stato di interruzione temporanea e la latenza del trasferimento dei dati o elaborare altri interrupt meno critici in termini di tempo per l'esecuzione nelle chiamate DPC a livello DPC / IRQL. spedizione.

Una chiamata DPC è rappresentata da un oggetto DPC, che è un oggetto di controllo del kernel invisibile ai programmi in modalità utente, ma visibile ai driver di dispositivo e ad altro codice di sistema. Le informazioni più importanti contenute nell'oggetto DPC sono l'indirizzo della funzione di sistema che il kernel chiamerà durante la gestione dell'interrupt DPC. I DPC in sospeso vengono archiviati in code gestite dal kernel, una coda per ogni processore.

Queste sono chiamate code DPC. Per richiedere un DPC, il codice di sistema chiama il kernel per inizializzare l'oggetto DPC e quindi inserisce quell'oggetto nella coda DPC.

Per impostazione predefinita, il kernel posiziona gli oggetti DPC alla fine della coda DPC del processore su cui è stato richiesto il DPC (di solito il processore su cui è in esecuzione ISR). Tuttavia, un driver di dispositivo può ignorare questo comportamento specificando una priorità DPC (bassa, media, sopra la media o alta, che per impostazione predefinita è media) e indirizzando il DPC a un processore specifico. Una chiamata DPC che ha come destinazione una CPU specifica è nota come DPC di destinazione. Se il DPC ha una priorità alta, il kernel mette l'oggetto DPC in testa alla coda, altrimenti, per tutte le altre priorità, mette l'oggetto alla fine della coda.

Il kernel gestisce le chiamate DPC quando l'IRQL del processore sta per eseguire il downgrade da DPC / invio IRQL o superiore a un IRQL inferiore (APC o passivo). Windows garantisce che l'IRQL rimanga al livello DPC / invio e recupera gli oggetti DPC dalla coda del processore corrente finché non si esaurisce (ovvero, il kernel "consuma" la coda), chiamando a turno ciascuna funzione DPC. Il kernel consentirà al livello IRQL di scendere al di sotto del livello DPC / invio e consentirà l'esecuzione normale del thread solo quando la coda è esaurita. L'elaborazione DPC è illustrata nella figura Le priorità DPC possono influenzare il comportamento del sistema in altri modi.

In genere, il kernel avvia il consumo di una coda DPC con un interrupt DPC / invio. Il kernel genera un tale interrupt solo quando la chiamata DPC è diretta al processore corrente (quello su cui è in esecuzione l'ISR) e il DPC ha una priorità più alta che bassa. Se il DPC ha una priorità bassa, il kernel richiederà un interrupt solo quando il numero di richieste DPC del processore in sospeso supera la soglia, o se il numero di chiamate DPC richieste al processore in un determinato intervallo di tempo è basso.

Consegna delle chiamate DPC

Se la chiamata DPC ha come destinazione una CPU diversa da quella che esegue l'ISR e la priorità DPC è alta o medio-alta, il kernel segnala immediatamente la CPU di destinazione (inviandole un IPI di invio) a consumo della sua coda DPC, ma solo se il processore di destinazione è inattivo. Se la priorità è media o bassa, il numero di chiamate DPC in coda sul processore di destinazione deve superare una certa soglia affinché il kernel emetta un interrupt DPC / invio. Il thread inattivo del sistema svuota anche la coda DPC del processore su cui è in esecuzione. Nonostante la flessibilità data al sistema dalle destinazioni delle chiamate DPC e dai livelli di priorità, i driver di dispositivo raramente devono modificare il comportamento predefinito dei loro oggetti DPC. Le situazioni che avviano lo svuotamento della coda DPC sono riepilogate nella tabella. Se guardi le regole di generazione, allora, in effetti, risulta che le priorità sopra la media e le alte sono uguali tra loro. La differenza appare quando vengono inseriti nell'elenco, con interruzioni di alto livello nella parte anteriore e interruzioni superiori alla media nella parte posteriore.

Poiché i thread in modalità utente vengono eseguiti con un IRQL basso, è molto probabile che la chiamata DPC interrompa il normale thread utente. Le procedure DPC vengono eseguite indipendentemente dal thread in esecuzione, quindi quando viene avviata una procedura DPC, non è possibile ipotizzare lo spazio degli indirizzi, quale processo è attualmente visualizzato. Le routine DPC possono chiamare le funzioni del kernel, ma non possono chiamare i servizi di sistema, generare errori di uscita pagina o creare o attendere oggetti di invio. Tuttavia, possono accedere a indirizzi non di paging nella memoria di sistema perché lo spazio degli indirizzi di sistema è sempre mappato, indipendentemente dal processo corrente.

Regole di generazione degli interrupt DPC.

Priorità DPCLa chiamata DPC è destinata al processore in esecuzione
Procedura ISR
La chiamata DPC è mirata
a un altro processore
BassoLa lunghezza della coda DPC supera la lunghezza massima della coda DPC o il livello di richiesta DPC è inferiore al minimo
Livello di richiesta DPC
Medio
(Medio)
È sempreLa lunghezza della coda DPC supera la lunghezza massima della coda DPC o il sistema è inattivo
Sopra la media
(Media altezza)
È sempre
AltoÈ sempreIl processore di destinazione è inattivo

Le chiamate DPC vengono fornite principalmente ai driver di dispositivo, ma vengono anche utilizzate dal kernel. Molto spesso, il kernel utilizza il DPC per gestire la scadenza della porzione di tempo. Ogni volta che l'orologio di sistema ticchetta, si verifica un interrupt a livello di IRQL dell'orologio. Il gestore di interrupt dell'orologio (eseguito al livello IRQL dell'orologio) aggiorna l'ora di sistema e quindi riduce il contatore che tiene traccia del tempo di esecuzione del thread corrente.

Quando il contatore raggiunge lo zero, l'intervallo di tempo del thread scadrà e il kernel potrebbe dover riprogrammare il tempo del processore, ovvero, per eseguire un'attività con una priorità inferiore, che dovrebbe essere eseguita a livello IRQL DPC / invio.

Il gestore dell'interrupt di clock accoda una chiamata DPC per avviare l'invio del thread, quindi esce e abbassa l'IRQL della CPU. Poiché gli interrupt DPC hanno una priorità inferiore rispetto agli interrupt del dispositivo, tutti gli interrupt del dispositivo in sospeso che si verificano prima del completamento dell'interrupt di clock vengono elaborati prima che venga emesso l'interrupt DPC.

Poiché le chiamate DPC vengono eseguite indipendentemente da quale thread è attualmente in esecuzione sul sistema (che è molto simile agli interrupt), sono la ragione principale per cui il sistema su cui sono in esecuzione diventa immune al carico di lavoro dei sistemi client, o workstation perché anche i thread con la priorità più alta verranno interrotti dalla chiamata DPC in sospeso.

Alcune chiamate DPC richiedono così tanto tempo che gli utenti potrebbero notare ritardi nel video o nell'audio o persino riscontrare un rallentamento anomalo nella risposta del mouse o della tastiera, quindi per i driver con chiamate DPC lunghe, Windows supporta lo streaming di chiamate DPC.

I DPC in streaming, come suggerisce il nome, sono progettati per eseguire una procedura DPC a livello passivo in un flusso con una priorità in tempo reale (priorità 31). Ciò consente alla chiamata DPC di avere la precedenza sulla maggior parte dei thread in modalità utente (poiché la maggior parte dei thread dell'applicazione non viene avviata in intervalli di priorità in tempo reale). Ma questo consente ad altri interrupt, chiamate DPC non thread, chiamate APC e thread con priorità più alta di avere la priorità su questa procedura.

Oltre a essere utilizzato per eseguire il dispatcher (scheduler) di NT, IRQL dispatch_level viene anche utilizzato per gestire le chiamate di procedura differita (DPC). Le chiamate DPC sono callback a subroutine che verranno eseguite sul livello di invio IRQL. Le chiamate DPC vengono solitamente richieste da IRQL superiori per eseguire un'elaborazione estesa e non critica in termini di tempo.

Diamo un'occhiata a un paio di esempi di utilizzo dei DPC. I driver di periferica di Windows NT eseguono un'elaborazione minima all'interno delle routine di servizio di interrupt. Invece, quando un dispositivo viene interrotto (a livello DIRQL) e il suo driver determina che è necessaria un'elaborazione complessa, il driver richiede un DPC. La richiesta DPC fa sì che una specifica funzione del driver nel dispatch_level IRQL venga richiamata per eseguire il resto dell'elaborazione richiesta. Eseguendo questa elaborazione a livello di invio IRQL, il driver impiega meno tempo a livello di DIRQL e quindi riduce la latenza di interrupt per tutti gli altri dispositivi nel sistema.

Nella fig. 15 mostra una tipica sequenza di eventi.

L'ISR richiede prima il DPC e NT accoda l'oggetto DPC sulla coda del processore di destinazione. A seconda della priorità DPC e della lunghezza della coda DPC, NT genera un interrupt software DPC immediatamente o dopo un po 'di tempo. Quando il processore cancella la coda DPC, l'oggetto DPC lascia la coda e il controllo viene passato alla sua funzione DPC, che completa l'interrupt leggendo i dati dal dispositivo o scrivendo i dati sul dispositivo che ha generato l'interrupt.
Un altro uso comune dei DPC sono le routine del timer. Un driver può richiedere l'esecuzione di una funzione specifica per notificarlo quando è trascorso un certo periodo di tempo (ciò viene fatto utilizzando la funzione KeSetTimer ()). Il gestore di interrupt dell'orologio controlla il passare del tempo e, dopo un certo periodo di tempo, richiede il DPC per la routine definita dal driver. L'utilizzo del DPC per la notifica del timer consente al gestore di interrupt dell'orologio di tornare rapidamente, ma comporta comunque la chiamata di una routine specificata senza ritardi eccessivi.

Oggetti DPC

Una chiamata DPC è descritta da un oggetto DPC. La definizione dell'oggetto DPC (KDPC) è fatta in ntddk.h ed è mostrata in Fig. sedici.

Figura: 16. Oggetto DPC

Un oggetto DPC può essere allocato dal driver da qualsiasi spazio non di paging (come un pool non di paging). Gli oggetti DPC vengono inizializzati utilizzando la funzione KelnitializeDpc (), il cui prototipo è:

VOID KelnitializeDpc (IN PKDPC Dpc,
IN PKDEFERRED ^ ROUTINE DeferredRoutine,
IN PVOID DeferredContext);

Dove:
Dpc - Puntatore all'oggetto DPC da inizializzare; DeferredRoutine: un puntatore a una funzione mediante la quale deve essere eseguita una chiamata differita a livello IRQL DISPATCH_LEVEL. Il prototipo per la funzione DeferredRoutine è il seguente:

VOID (* PKDEFERRED_ROUTINE) (
IN PKDPC Dpc,
IN PVOID DeferredContext,
IN PVOID SystemArgumentI,
IN PVOID SystemArgument2);

Dove:
DeferredContext: il valore da passare a DeferredRoutine come parametro, insieme a un puntatore all'oggetto DPC e due parametri aggiuntivi.
Una richiesta per eseguire una specifica subroutine DPC viene effettuata inserendo un oggetto DPC che descrive quella subroutine DPC nella coda DPC della CPU specificata e quindi (di solito) richiedendo un interrupt software all'IRQL
dispatch_level. C'è una coda DPC per processore. La CPU a cui è accodato l'oggetto DPC è solitamente il processore corrente su cui viene emessa la richiesta (interrupt). Il modo in cui il processore viene selezionato per un particolare DPC viene discusso più avanti nella sezione Caratteristiche dell'oggetto DPC. L'oggetto DPC viene messo in coda utilizzando la funzione KelnsertQueueDpc (), il cui prototipo è:

VOID KelnsertQueueDpc (IN PKDPC Dpc,
IN PVOID SystemArgument1,
IN PVOID SystemArgument2);

Dove:
Dpc: indica l'oggetto DPC da accodare;
SystemArgumentl, SystemArgument2: valori arbitrari che devono essere passati alla funzione DeferredRoutme rispettivamente come 3 e 4 parametri, insieme a un puntatore all'oggetto DPC e al parametro DeferredContext specificato quando l'oggetto DPC è stato inizializzato.

Attivazione e manutenzione DPC

L'origine di un interrupt software Dispatch_level viene riconosciuta quando tale interrupt diventa l'evento IRQL in sospeso più elevato in sospeso su questo processore. Pertanto, dopo aver chiamato la funzione KelnsertQueueDpc (), di solito la prossima volta che il processore è pronto per tornare all'IRQL sotto dispatch_level, tornerà invece all'IRQL dispatch_level e proverà a elaborare il contenuto della coda DPC.
Come notato in precedenza in questo capitolo, IRQL DISPATCHJLEVEL viene utilizzato sia per l'invio che per l'elaborazione della coda DPC. In NT 4.0, quando viene gestito un interrupt DISPATCH_LEVEL, l'intera coda DPC viene prima servita e quindi il dispatcher viene chiamato per pianificare l'esecuzione del thread successivo. Ciò è ragionevole perché l'elaborazione eseguita dalla routine DPC potrebbe modificare lo stato del database di pianificazione dei thread, ad esempio, rendendo integro il thread in attesa in precedenza.
La coda DPC è servita dal Microkernel. Ogni volta che una coda DPC viene servita, vengono elaborati tutti gli elementi della coda DPC per il processore corrente. Uno alla volta, il Microkernel rimuove l'oggetto DPC dall'intestazione della coda e chiama il DeferredRoutine specificato nell'oggetto. Il microkernel passa un puntatore all'oggetto DPC, il contenuto dei campi DeferredContext, SystemArgumentl e SystemArgument2 dell'oggetto DPC come parametri alla funzione DeferredRoutine.
Poiché la coda DPC viene servita sull'IRQL dispatch_level, le routine DPC vengono chiamate sull'IRQL dispatch_level. Poiché la coda DPC viene servita ogni volta che il livello di invio IRQL è l'IRQL con la priorità più alta da servire (ad esempio, immediatamente dopo l'esecuzione del gestore di interrupt e prima di tornare al thread utente interrotto), le funzioni DPC vengono eseguite in un contesto di thread arbitrario. Per contesto di un thread arbitrario, si intende che il DPC viene eseguito in un processo e un thread, che potrebbero non avere nulla a che fare con la richiesta che il DPC gestisce. (Il contesto di esecuzione è descritto in maggiore dettaglio nella sezione "Modello di driver a strati".)
La routine DPC termina l'elaborazione e ritorna. Al ritorno dalla subroutine DPC, il Microkernel tenta di recuperare un altro oggetto DPC dalla coda DPC e di elaborarlo. Quando la coda DPC è vuota, l'elaborazione DPC termina. Il microkernel procede a chiamare il Dispatcher (scheduler).

Più chiamate a DPC

Ogni DPC è descritto da uno specifico oggetto DPC. Di conseguenza, ogni volta che viene chiamato KelnsertQueueDpc () e si scopre che l'oggetto DPC passato ad esso è già nella stessa coda DPC, la funzione KelnsertQueueDpcQ restituisce semplicemente (senza fare nulla). Pertanto, ogni volta che un oggetto DPC è già nella coda DPC, qualsiasi tentativo successivo di accodare lo stesso oggetto DPC prima che l'oggetto DPC venga rimosso dalla coda viene ignorato. Ciò ha senso perché un oggetto DPC può essere incluso fisicamente in una coda DPC alla volta.
La domanda ovvia potrebbe essere: cosa succede quando viene effettuata una richiesta per accodare un oggetto DPC, ma il sistema sta già eseguendo la subroutine DPC specificata da quell'oggetto DPC (sullo stesso o su un processore diverso)? La risposta a questa domanda può essere trovata leggendo attentamente la sezione precedente. Quando il Microkernel serve la coda DPC, rimuove l'oggetto DPC dall'intestazione della coda e solo allora chiama la subroutine DPC specificata dall'oggetto DPC. Pertanto, quando viene chiamata la routine DPC, l'oggetto DPC è già stato rimosso dalla coda DPC del processore. Pertanto, quando viene effettuata una richiesta per accodare un oggetto DPG e il sistema si trova all'interno della subroutine DPC specificata in tale oggetto DPC, il DPC viene accodato come al solito.

DPC su sistemi multiprocessore

Contrariamente a quanto sostenuto da altre fonti, e come dovrebbe essere ovvio dalla discussione precedente, la stessa routine DPC può essere eseguita su più processori contemporaneamente. Non c'è assolutamente alcun blocco da parte del Microkernel per impedirlo.
Considera il caso di un driver di dispositivo con più richieste in sospeso contemporaneamente. La periferica driver viene interrotta sul processore 0, il gestore di interrupt del driver viene eseguito e richiede al DPC di completare la gestione degli interrupt. Questo è il percorso standard seguito dai driver di Windows NT. Quando il gestore di interrupt termina e il sistema è pronto per tornare al thread utente interrotto, l'IRQL del processore O viene abbassato dal DIRQL in cui è stato eseguito l'ISR al livello di invio IRQL. Di conseguenza, il Microkernel mantiene la coda DPC rimuovendo l'oggetto DPC del driver e chiamando la subroutine DPC specificata in esso. La routine del driver DPC è ora in esecuzione sul processore 0.
Immediatamente dopo aver chiamato la subroutine del driver DPC, il dispositivo genera nuovamente l'interrupt. Tuttavia, questa volta, per motivi noti solo all'hardware, l'interrupt viene servito sul processore 1. Anche in questo caso, il gestore di interrupt del driver richiede il DPC. E, ancora una volta, quando la routine di interrupt termina, il sistema (Processore 1) è pronto per tornare al thread utente interrotto. Questo abbassa l'IRQL del processore 1 al dispatch_level IRQL e il Microkernel serve la coda DPC. In questo modo (e ancora in esecuzione sul processore 1), il microkernel elimina l'oggetto DPC driver e chiama la subroutine DPC del driver. La routine DPC del driver ora viene eseguita sul processore 1. Presupponendo che la routine DPC del driver non abbia ancora completato l'esecuzione sul processore 0, notare che la stessa routine DPC ora viene eseguita in parallelo su entrambi i processori.
Questo esempio sottolinea l'importanza di utilizzare il set corretto di meccanismi di sincronizzazione multiprocessore nei driver. In particolare, una funzione DPC dovrebbe utilizzare spinlock per serializzare l'accesso a qualsiasi struttura di dati a cui è necessario accedere nel suo insieme, a condizione che la progettazione del driver sia tale che più chiamate DPC possano verificarsi contemporaneamente.

Caratteristiche dell'oggetto DPC

Gli oggetti DPC hanno due caratteristiche che influiscono sul modo in cui vengono elaborati. Queste caratteristiche sono i campi Importanza e Numero.

Importanza del DPC

Ogni oggetto DPC ha importanza, che viene memorizzata nel campo Importance dell'oggetto DPC. I valori per questo campo sono elencati in ntddk.h sotto i nomi Highlmportance, Mediumlmportance e Lowlmportance. Questo valore DPC oggetto influisce sulla posizione nella coda DPC in cui viene inserito l'oggetto DPC quando viene accodato e se l'IRQL dispatch_level verrà interrotto quando l'oggetto DPC viene accodato. La funzione KelnitializeDpc () inizializza gli oggetti DPC con un livello di gravità medio. L'importanza di un oggetto DPC può essere impostata utilizzando la funzione KeSetlmportanceDpc (), il cui prototipo è:

VOID KeSetlmportanceDpc (IN PKDPC Dpc,
In KDPCIMPORTANCE Importance);

Dove:
Dpc - Puntatore all'oggetto DPC in cui deve essere impostato il campo Importance;
L'importanza è il valore dell'importanza da installare nell'oggetto DPC.
I DPC con importanza media o importanza bassa vengono inseriti alla fine della coda DPC. Gli oggetti DPC con Importanza elevata vengono inseriti all'inizio della coda DPC.
L'importanza degli oggetti DPC influisce anche sul fatto che un interrupt software dispatch_level venga generato quando un oggetto DPC viene messo in coda. Quando un oggetto DPC con importanza elevata o importanza media viene accodato sul processore corrente, viene sempre generato un interrupt del livello di distribuzione. L'interrupt dispatch_level viene generato per DPC a bassa importanza o per quei DPC destinati a un processore diverso da quello corrente, in base a un algoritmo di pianificazione complesso (e non documentato).
La Tabella 11 elenca le situazioni che attivano il rilascio della coda di oggetti DPC.
La maggior parte dei driver di dispositivo non avrà mai bisogno di impostare l'importanza dei propri oggetti DPC. Nel raro caso in cui il ritardo tra la richiesta DPC e l'esecuzione DPC sia eccessivo e lo sviluppatore del driver non sia in grado di risolvere il ritardo in un altro modo, puoi provare a impostare Object DPC su Highlmportance. In genere, tuttavia, i driver di dispositivo su Windows NT non modificano il loro valore DPC dal valore predefinito Mediumlmportance.

Tabella 11. Situazioni che attivano lo scaricamento della coda DPC

Priorità DPC

I DPC vengono eseguiti sullo stesso processore dell'ISR

I DPC vengono eseguiti su un processore diverso

Basso

La dimensione della coda DPC supera il massimo, la velocità di richiesta DPC è inferiore al minimo o il sistema è inattivo

La dimensione della coda DPC supera il massimo o il sistema è inattivo (thread inattivo in esecuzione)

DPC può essere limitato all'esecuzione su un processore specificato utilizzando la funzione KeSetTargetProcessorDpc (), il cui prototipo è:

VOID KeSetTargetProcessorDpc (IN PKDPC Dpc,
IN CCHAR numero);

Dove:
Dpc: indica l'oggetto DPC per cui deve essere installato il processore di destinazione;
Numero è il numero del processore in base zero su cui deve essere eseguito il DPC.
Analogamente all'importanza di DPC, il DPC di destinazione non viene quasi mai impostato da un driver di dispositivo. Il valore predefinito utilizzato per eseguire DPC sul processore corrente è quasi sempre desiderabile.
Quando un processore di destinazione specifico viene installato per un oggetto DPC, tale oggetto DPC verrà sempre accodato nella coda DPC del processore specificato. Pertanto, ad esempio, anche quando KelnsertQueueDpc () viene chiamato sul processore 0, l'oggetto DPC con il processore 1 impostato come processore di destinazione verrà inserito nella coda DPC sul processore 1.

Come discusso in precedenza in questo capitolo, i DPC sono più comunemente utilizzati per terminare un gestore di interrupt (ISR). Per rendere più semplice per i driver di dispositivo richiedere ai DPC di completare l'ISR dalle loro funzioni ISR, I / O Manager definisce un DPC speciale che può essere utilizzato a questo scopo. Questo DPC si chiama DpcForlsr.
Il gestore I / O inserisce un oggetto DPC in ogni oggetto dispositivo che crea. Questo oggetto DPC incorporato viene inizializzato dal driver del dispositivo, in genere la prima volta che il driver viene caricato, chiamando la funzione IoInitializeDpcRequest ().
IoInitializeDpcRequest () accetta come input un puntatore a un oggetto dispositivo in cui è incorporato l'oggetto DPC, un puntatore a una funzione del driver da chiamare e un valore di contesto a cui passare quella funzione. IoInitializeDpcRequest (), a sua volta, chiama KelnitializeDpc () per inizializzare l'oggetto DPC incorporato, passando un puntatore alla funzione del driver come parametro DeferredRoutine e il valore del contesto come parametro DeferredContext.
Per richiedere un DPC dall'ISR, il driver chiama semplicemente loRequestDpc () passando un puntatore a un oggetto dispositivo. IoRequestDpc (), a sua volta, chiama KelnsertQueueDpc () sull'oggetto DPC incorporato nell'oggetto Device.
Poiché tutti i driver di dispositivo dispongono di oggetti dispositivo e tutti i driver che utilizzano interrupt utilizzano anche DPC, è molto conveniente utilizzare il meccanismo DpcForlsr di I / O Manager. Infatti, la maggior parte dei driver di periferica su Windows NT non chiama mai direttamente KelnitializeDpc () o KelnsertQueueDpc (), ma chiama loInitializeDpcRequest () e IoRequestDpc ().

Gli oggetti di gestione includono oggetti primitivi per thread, interrupt, timer, sincronizzazione, profilazione e due oggetti speciali per l'implementazione di DPC e APC. Gli oggetti DPC (Deferred Procedure Call) vengono utilizzati per ridurre il tempo di esecuzione di un interrupt Service Routines (ISR) attivato da un interrupt da un dispositivo. Limitare il tempo impiegato per le procedure ISR riduce le possibilità di perdere un'interruzione.

L'hardware di sistema assegna gli interrupt a un livello di priorità hardware. Il processore associa anche un livello di priorità al lavoro che sta facendo. Il processore risponde solo agli interrupt che hanno una priorità maggiore di quella attualmente in uso. Il livello di priorità normale (incluso il livello di priorità dell'intera modalità utente) è 0. Gli interrupt del dispositivo si verificano a livello 3 o superiore e l'ISR dell'interruzione del dispositivo di solito funziona allo stesso livello di priorità dell'interrupt (quindi altri interrupt meno importanti non lo fanno si è verificato durante l'elaborazione di un interrupt più importante).

Se l'ISR impiega troppo tempo, la manutenzione degli interrupt di priorità inferiore verrà ritardata, con il rischio di perdita di dati o rallentamento dell'I / O del sistema. Diversi ISR \u200b\u200bpossono essere in esecuzione in un dato momento, con ogni ISR \u200b\u200bsuccessivo derivante da interruzioni con un livello di priorità sempre più alto.

Per ridurre il tempo di elaborazione dell'ISR, vengono eseguite solo le operazioni critiche, come la scrittura dei risultati delle operazioni di I / O e la reinizializzazione del dispositivo. L'ulteriore elaborazione dell'interruzione viene posticipata finché il livello di priorità del processore non viene abbassato e non blocca più il servizio di altre interruzioni. L'oggetto DPC viene utilizzato per rappresentare il lavoro da eseguire e l'ISR chiama il livello del kernel per inserire il DPC nell'elenco DPC specifico del processore. Se DPC è il primo nell'elenco, il kernel registra una richiesta di interrupt hardware speciale per il livello del processore 2 (al quale NT chiama il livello DISPATCH). Quando l'ultimo ISR esistente viene completato, il livello di interrupt del processore scende al di sotto di 2 e questo sblocca l'interrupt per l'elaborazione DPC. L'interrupt DPC ISR elaborerà ciascuno degli oggetti DPC (che il kernel ha messo in coda).

La tecnica di utilizzare gli interrupt software per differire la gestione degli interrupt è una tecnica consolidata per ridurre la latenza ISR. UNIX e altri sistemi hanno iniziato a utilizzare l'elaborazione lazy negli anni '70 (per far fronte a hardware lento e buffer seriali limitati). L'ISR ha ricevuto i simboli dall'attrezzatura e li ha messi in coda. Dopo che tutta l'elaborazione dell'interruzione di alto livello è stata completata, l'interruzione del software ha attivato un ISR a bassa priorità per elaborare i caratteri (ad esempio, per implementare il cursore indietro di una posizione - per questo, un carattere di controllo è stato inviato al terminale per cancellare l'ultimo carattere visualizzato e il cursore è stato spostato indietro) ...

Un esempio simile nella moderna Windows è la tastiera. Dopo aver premuto un tasto, l'ISR della tastiera legge il codice della chiave dal registro, quindi abilita nuovamente l'interruzione della tastiera, ma non elabora il tasto immediatamente. Invece, utilizza il DPC per accodare l'elaborazione del codice chiave (fino a quando tutte le interruzioni sul dispositivo da elaborare non sono state elaborate).

Poiché i DPC vengono eseguiti al livello 2, non interferiscono con l'ISR per i dispositivi, ma interferiscono con l'esecuzione dei thread fino al completamento di tutti i DPC in coda e il livello di priorità del processore scende al di sotto di 2. I driver dei dispositivi e il sistema non dovrebbero impiegare troppo tempo per l'esecuzione ISR o DPC. Poiché gli stream non possono essere eseguiti, ISR e DPC possono rendere il sistema lento e causare arresti anomali durante la riproduzione di musica (bloccando gli stream che scrivono musica dal buffer al dispositivo audio). Un altro caso d'uso comune per i DPC è l'esecuzione di routine di interrupt del timer. Per evitare il blocco dei thread, gli eventi timer (che devono richiedere molto tempo per essere eseguiti) dovrebbero accodare le richieste al pool di thread di lavoro (che il kernel supporta per il lavoro in background).

LA CAMPANA

C'è chi ha letto questa notizia prima di te.
Iscriviti per ricevere gli ultimi articoli.
E-mail
Nome
Cognome
Come vuoi leggere The Bell
Niente spam