Architettura di integrazione CLR - Ambiente ospitato in CLR
Si applica a:SQL ServerIstanza gestita di SQL di Azure
L'integrazione di SQL Server con .NET Framework Common Language Runtime (CLR) consente ai programmatori di database di usare linguaggi come C#, Visual Basic .NET e Visual C++. Tra i tipi di logica di business che i programmatori possono scrivere con tali linguaggi figurano le funzioni, le stored procedure, i trigger, i tipi di dati e le aggregazioni.
CLR include memoria garbage collection, threading preemptive, servizi di metadati (reflection dei tipi), verificabilità del codice e sicurezza dell'accesso al codice. CLR utilizza metadati per individuare e caricare classi, disporre istanze in memoria, risolvere chiamate a metodi, generare codice nativo, implementare la sicurezza e impostare limiti di contesto per la fase di esecuzione.
CLR e SQL Server differiscono come ambienti di runtime nel modo in cui gestiscono memoria, thread e sincronizzazione. Questo articolo descrive il modo in cui queste due esecuzioni vengono integrate in modo che tutte le risorse di sistema vengano gestite in modo uniforme. Questo articolo illustra anche la modalità di integrazione della sicurezza dall'accesso al codice CLR e della sicurezza di SQL Server per fornire un ambiente di esecuzione affidabile e sicuro per il codice utente.
Concetti di base dell'architettura CLR
In .NET Framework un programmatore scrive in un linguaggio di alto livello che implementa una classe definendone la struttura, ad esempio i campi o le proprietà della classe, e i metodi. Alcuni di questi metodi possono essere funzioni statiche. La compilazione del programma produce un file denominato assembly contenente il codice compilato nel linguaggio intermedio comune (CIL) e un manifesto che contiene tutti i riferimenti agli assembly dipendenti.
Nota
Gli assembly sono un elemento essenziale nell'architettura di CLR, Sono le unità di creazione di pacchetti, distribuzione e controllo delle versioni del codice dell'applicazione in .NET Framework. Utilizzando gli assembly è possibile distribuire codice dell'applicazione nel database e garantire un metodo uniforme per amministrare, eseguire il backup e ripristinare applicazioni di database complete.
Il manifesto dell'assembly contiene metadati sull'assembly che descrivono tutti i campi, le strutture, le proprietà, le classi, le relazioni di ereditarietà, le funzioni e i metodi definiti nel programma. Il manifesto consente di stabilire l'identità dell'assembly, di specificare i file che costituiscono l'implementazione dell'assembly, di specificare i tipi e le risorse che compongono l'assembly, di rilevare le dipendenze in fase di compilazione da altri assembly e di specificare il set di autorizzazioni necessarie per la corretta esecuzione dell'assembly. Queste informazioni vengono utilizzate in fase di esecuzione per risolvere i riferimenti, applicare i criteri di associazione delle versioni e convalidare l'integrità degli assembly caricati.
.NET Framework supporta attributi personalizzati per annotare classi, proprietà, funzioni e metodi con informazioni aggiuntive che l'applicazione potrebbe acquisire nei metadati. Tutti i compilatori di .NET Framework utilizzano tali annotazioni senza interpretazione e le archiviano come metadati dell'assembly. Le annotazioni possono essere esaminate allo stesso modo di tutti gli altri metadati.
Il codice gestito viene eseguito in CLR anziché direttamente dal sistema operativo. Le applicazioni con codice gestito acquisiscono i servizi CLR, quali operazioni automatiche di Garbage Collection, controllo dei tipi in fase di esecuzione, supporto della sicurezza e così via. Tali servizi consentono di garantire un comportamento uniforme e indipendente dalla piattaforma e dalla lingua per le applicazioni con codice gestito.
Obiettivi di progettazione dell'integrazione con CLR
Quando il codice utente viene eseguito all'interno dell'ambiente ospitato in CLR in SQL Server (denominato integrazione CLR), si applicano gli obiettivi di progettazione seguenti:
Affidabilità (sicurezza)
Il codice utente non deve essere autorizzato a eseguire operazioni che compromettono l'integrità del processo del motore di database, ad esempio la visualizzazione di una finestra di messaggio che richiede una risposta dell'utente o l'uscita dal processo. Il codice utente non deve essere in grado di sovrascrivere buffer di memoria del motore di database o strutture di dati interne.
Scalabilità
SQL Server e CLR hanno modelli interni diversi per la pianificazione e la gestione della memoria. SQL Server supporta un modello di threading cooperativo e non preemptive in cui i thread producono volontariamente l'esecuzione periodicamente o quando sono in attesa di blocchi o I/O. CLR supporta un modello di threading preemptive. Se il codice utente in esecuzione all'interno di SQL Server può chiamare direttamente le primitive di threading del sistema operativo, non si integra correttamente nell'utilità di pianificazione dell'attività di SQL Server e può ridurre la scalabilità del sistema. CLR non distingue tra memoria virtuale e fisica, ma SQL Server gestisce direttamente la memoria fisica ed è necessario usare la memoria fisica entro un limite configurabile.
I modelli diversi per threading, pianificazione e gestione della memoria presentano una sfida di integrazione per un sistema di gestione di database relazionali (RDBMS) con scalabilità in grado di supportare migliaia di sessioni utente simultanee. L'architettura deve garantire che la scalabilità del sistema non venga compromessa dal codice utente chiamando direttamente le API (Application Programming Interface) per threading, memoria e primitive di sincronizzazione.
Sicurezza
Il codice utente in esecuzione nel database deve seguire le regole di autenticazione e autorizzazione di SQL Server quando si accede a oggetti di database come tabelle e colonne. Gli amministratori del database, inoltre, devono essere in grado di controllare l'accesso alle risorse del sistema operativo, ad esempio file e accesso di rete, dal codice utente in esecuzione nel database. Questa procedura diventa importante come linguaggi di programmazione gestiti (a differenza dei linguaggi non gestiti, ad esempio Transact-SQL) fornisce API per accedere a tali risorse. Il sistema deve fornire un modo sicuro per consentire al codice utente di accedere alle risorse del computer all'esterno del processo di motore di database. Per altre informazioni, vedere di sicurezza dell'integrazione con CLR.
Prestazioni
Il codice utente gestito in esecuzione nel motore di database deve avere prestazioni di calcolo paragonabili allo stesso codice eseguito all'esterno del server. L'accesso al database dal codice utente gestito non è altrettanto veloce di Transact-SQL nativo. Per altre informazioni, vedere Performance of CLR integration architecture.
Servizi CLR
CLR offre diversi servizi per raggiungere gli obiettivi di progettazione dell'integrazione clr con SQL Server.
Verifica dell'indipendenza dai tipi
Il codice indipendente dai tipi è un codice che accede alle strutture di memoria solo in modalità ben definite. Dato un riferimento a un oggetto valido, ad esempio, il codice indipendente dai tipi può accedere alla memoria solo a offset fissi corrispondenti ai membri di campo effettivi. Tuttavia, se il codice accede alla memoria in offset arbitrari all'interno o all'esterno dell'intervallo di memoria che appartiene all'oggetto, non è indipendente dai tipi. Quando gli assembly vengono caricati in CLR, prima che il CIL venga compilato usando la compilazione JIT (Just-In-Time), il runtime esegue una fase di verifica che esamina il codice per determinarne la sicurezza dei tipi. Il codice che supera correttamente questa verifica viene denominato codice effettivamente indipendente dai tipi.
Domini applicazione
CLR supporta il concetto di domini applicazione come aree di esecuzione all'interno di un processo host in cui è possibile caricare ed eseguire assembly di codice gestito. Il confine del dominio applicazione fornisce isolamento tra assembly. Gli assembly sono isolati in termini di visibilità di variabili statiche e membri di dati e di possibilità di chiamare codice dinamicamente. I domini applicazione costituiscono inoltre il meccanismo per caricare e scaricare codice. Il codice può essere scaricato dalla memoria solo scaricando il dominio applicazione. Per altre informazioni, vedere Domini applicazione e sicurezza di integrazione CLR.
Sicurezza dall'accesso al codice
Il sistema di sicurezza CLR fornisce un metodo per determinare i possibili tipi di operazioni eseguite dal codice gestito tramite l'assegnazione di autorizzazioni al codice. Le autorizzazioni di accesso per il codice vengono assegnate in base all'identità del codice, ad esempio la firma dell'assembly o l'origine del codice.
CLR fornisce criteri a livello di computer che possono essere impostati dall'amministratore del computer. Tali criteri definiscono le autorizzazioni concesse per qualsiasi codice gestito in esecuzione nel computer. Sono inoltre disponibili criteri di sicurezza a livello di host che possono essere usati da host come SQL Server per specificare restrizioni aggiuntive sul codice gestito.
Se un'API gestita in .NET Framework espone operazioni sulle risorse protette da un'autorizzazione di accesso al codice, l'API richiede tale autorizzazione prima di accedere alla risorsa. Questa richiesta fa in modo che il sistema di sicurezza CLR attivi un controllo completo di ogni unità di codice (assembly) nello stack di chiamate. L'accesso alla risorsa viene concesso solo se l'intera catena di chiamate dispone dell'autorizzazione.
La possibilità di generare codice gestito in modo dinamico, usando l'API Reflection.Emit
, non è supportata all'interno dell'ambiente ospitato in CLR in SQL Server. Questo codice non avrebbe le autorizzazioni cas per l'esecuzione e quindi non riuscirebbe in fase di esecuzione. Per altre informazioni, vedere CLR Integration Code Access Security.
Attributi di protezione host (HPA)
CLR fornisce un meccanismo per annotare le API gestite che fanno parte di .NET Framework con determinati attributi che potrebbero essere di interesse per un host di CLR. Tra gli esempi di tali attributi sono inclusi i seguenti:
SharedState
, che indica se l'API espone la possibilità di creare o gestire lo stato condiviso (ad esempio, campi di classe statici).Synchronization
, che indica se l'API espone la possibilità di eseguire la sincronizzazione tra thread.ExternalProcessMgmt
, che indica se l'API espone un modo per controllare il processo host.
Dato questi attributi, l'host può specificare un elenco di HPA, ad esempio l'attributo SharedState
, che deve essere non consentito nell'ambiente ospitato. In tal caso, CLR rifiuta i tentativi da parte del codice utente di chiamare API annotate tramite attributi di protezione host inclusi nell'elenco degli attributi non consentiti. Per altre informazioni, vedere Attributi di protezione host e programmazione di integrazione CLR.
Funzionamento di SQL Server e CLR
Questa sezione illustra come SQL Server integra i modelli di threading, pianificazione, sincronizzazione e gestione della memoria di SQL Server e CLR. In particolare, in questa sezione viene esaminata l'integrazione alla luce di obiettivi di scalabilità, affidabilità e sicurezza. SQL Server funge essenzialmente da sistema operativo per CLR quando è ospitato all'interno di SQL Server. CLR chiama routine di basso livello implementate da SQL Server per threading, pianificazione, sincronizzazione e gestione della memoria. Queste routine sono le stesse primitive usate dal resto del motore di SQL Server. Questo approccio offre diversi vantaggi correlati a scalabilità, affidabilità e sicurezza.
Scalabilità: threading, pianificazione e sincronizzazione comuni
CLR chiama le API di SQL Server per la creazione di thread, sia per l'esecuzione del codice utente che per il proprio uso interno. Per eseguire la sincronizzazione tra più thread, CLR chiama gli oggetti di sincronizzazione di SQL Server. Questa procedura consente all'utilità di pianificazione di SQL Server di pianificare altre attività quando un thread è in attesa su un oggetto di sincronizzazione. Quando, ad esempio, CLR avvia la procedura di Garbage Collection, tutti i relativi thread sono in attesa del completamento di tale procedura. Poiché i thread CLR e gli oggetti di sincronizzazione in attesa sono noti all'utilità di pianificazione di SQL Server, SQL Server può pianificare thread che eseguono altre attività di database che non coinvolgono CLR. Ciò consente anche a SQL Server di rilevare deadlock che comportano blocchi eseguiti da oggetti di sincronizzazione CLR e di usare tecniche tradizionali per la rimozione di deadlock.
Il codice gestito viene eseguito in modo preemptive in SQL Server. L'utilità di pianificazione di SQL Server ha la possibilità di rilevare e arrestare i thread che non sono stati restituiti per una quantità significativa di tempo. La possibilità di associare thread CLR ai thread di SQL Server implica che l'utilità di pianificazione di SQL Server può identificare i thread "runaway" in CLR e gestire la priorità. Tali thread sfuggiti al controllo vengono sospesi e reinseriti nella coda. I thread identificati ripetutamente come thread di fuga non possono essere eseguiti per un determinato periodo di tempo in modo che altri ruoli di lavoro in esecuzione possano essere eseguiti.
Esistono alcune situazioni in cui il codice gestito a esecuzione prolungata produce automaticamente e alcune situazioni in cui non lo è. Nelle situazioni seguenti, il codice gestito a esecuzione prolungata restituisce automaticamente:
- Se il codice chiama il sistema operativo SQL (ad esempio, per eseguire query sui dati)
- Se è allocata memoria sufficiente per attivare Garbage Collection
- Se il codice entra in modalità preemptive chiamando le funzioni del sistema operativo
Il codice che non esegue alcuna di queste azioni, ad esempio cicli stretti che contengono solo calcoli, non restituisce automaticamente l'utilità di pianificazione, che può portare a lunghe attese per altri carichi di lavoro nel sistema. In queste situazioni, spetta allo sviluppatore restituire in modo esplicito chiamando la funzione System.Thread.Sleep()
di .NET Framework o immettendo in modo esplicito la modalità preemptive con System.Thread.BeginThreadAffinity()
, in tutte le sezioni di codice previste per un'esecuzione prolungata. Negli esempi di codice seguenti viene illustrato come produrre manualmente usando ognuno di questi metodi.
Esempi
Restituire manualmente all'utilità di pianificazione SOS
for (int i = 0; i < Int32.MaxValue; i++)
{
// *Code that does compute-heavy operation, and does not call into
// any OS functions.*
// Manually yield to the scheduler regularly after every few cycles.
if (i % 1000 == 0)
{
Thread.Sleep(0);
}
}
Usare ThreadAffinity per eseguire in modo preemptively
In questo esempio il codice CLR viene eseguito in modalità preemptive all'interno di BeginThreadAffinity
e EndThreadAffinity
.
Thread.BeginThreadAffinity();
for (int i = 0; i < Int32.MaxValue; i++)
{
// *Code that does compute-heavy operation, and does not call into
// any OS functions.*
}
Thread.EndThreadAffinity();
Scalabilità: gestione della memoria comune
CLR chiama le primitive di SQL Server per l'allocazione e la deallocazione della memoria. Poiché la memoria usata da CLR è considerata nell'utilizzo totale della memoria del sistema, SQL Server può rimanere entro i limiti di memoria configurati e assicurarsi che CLR e SQL Server non siano in competizione tra loro per la memoria. SQL Server può anche rifiutare le richieste di memoria CLR quando la memoria di sistema è vincolata e chiedere a CLR di ridurre l'uso della memoria quando sono necessarie altre attività di memoria.
Affidabilità: domini applicazione ed eccezioni irreversibili
Quando il codice gestito nelle API .NET Framework rileva eccezioni critiche, ad esempio l'overflow insufficiente della memoria o dello stack, non è sempre possibile eseguire il ripristino da tali errori e garantire una semantica coerente e corretta per l'implementazione. Le API generano un'eccezione di interruzione del thread in risposta a tali errori.
Se ospitato in SQL Server, tali interruzioni di thread vengono gestite come segue: CLR rileva qualsiasi stato condiviso nel dominio applicazione in cui si verifica l'interruzione del thread. CLR rileva questa situazione controllando la presenza di oggetti di sincronizzazione. Se è presente uno stato condiviso nel dominio dell'applicazione, il dominio applicazione stesso viene scaricato. Lo scaricamento del dominio applicazione comporta l'arresto delle transazioni del database in esecuzione nel dominio applicazione. Poiché la presenza di stato condiviso può ampliare l'effetto di tali eccezioni critiche alle sessioni utente diverse da quella che attiva l'eccezione, SQL Server e CLR hanno adottato misure per ridurre la probabilità di stato condiviso. Per altre informazioni, vedere .NET Framework.
Sicurezza: set di autorizzazioni
SQL Server consente agli utenti di specificare i requisiti di affidabilità e sicurezza per il codice distribuito nel database. Quando gli assembly vengono caricati nel database, l'autore dell'assembly può specificare uno dei tre set di autorizzazioni per tale assembly: SAFE
, EXTERNAL_ACCESS
e UNSAFE
.
Funzionalità | SAFE |
EXTERNAL_ACCESS |
UNSAFE |
---|---|---|---|
Code Access Security |
Sola esecuzione | Esecuzione più accesso a risorse esterne | Senza restrizioni |
Programming model restrictions |
Sì | Sì | Nessuna restrizione |
Verifiability requirement |
Sì | Sì | No |
Ability to call native code |
No | No | Sì |
SAFE
è la modalità più affidabile e sicura con restrizioni associate in termini di modello di programmazione consentito.
SAFE
gli assembly dispongono di autorizzazioni sufficienti per l'esecuzione, l'esecuzione di calcoli e l'accesso al database locale.
SAFE
gli assembly devono essere verificabili in modo sicuro e non possono chiamare codice non gestito.
UNSAFE
è destinato a codice altamente attendibile che può essere creato solo dagli amministratori di database. Questo codice attendibile non presenta restrizioni di sicurezza dall'accesso di codice e può chiamare codice non gestito (nativo).
EXTERNAL_ACCESS
offre un'opzione di sicurezza intermedia, consentendo al codice di accedere alle risorse esterne al database, ma mantenendo le garanzie di affidabilità di SAFE
.
SQL Server usa il livello di criteri cas a livello di host per configurare un criterio host che concede uno dei tre set di autorizzazioni in base al set di autorizzazioni archiviato nei cataloghi di SQL Server. Il codice gestito in esecuzione all'interno del database ottiene sempre uno di questi set di autorizzazioni di accesso per il codice.
Restrizioni del modello di programmazione
Il modello di programmazione per il codice gestito in SQL Server prevede la scrittura di funzioni, procedure e tipi che in genere non richiedono l'uso dello stato mantenuto tra più chiamate o la condivisione dello stato tra più sessioni utente. Inoltre, come descritto in precedenza, la presenza di stato condiviso può causare eccezioni critiche che influiscono sulla scalabilità e sull'affidabilità dell'applicazione.
Considerate queste considerazioni, è consigliabile usare variabili statiche e membri dati statici delle classi usate in SQL Server. Per SAFE
e EXTERNAL_ACCESS
assembly, SQL Server esamina i metadati dell'assembly in CREATE ASSEMBLY
momento e non riesce a creare tali assembly se trova l'uso di membri dati statici e variabili.
SQL Server non consente inoltre le chiamate alle API di .NET Framework annotate con gli attributi di protezione host SharedState
, Synchronization
e ExternalProcessMgmt
. Ciò impedisce SAFE
e EXTERNAL_ACCESS
assembly di chiamare qualsiasi API che consenta la condivisione dello stato, l'esecuzione della sincronizzazione e l'integrità del processo di SQL Server. Per altre informazioni, vedere restrizioni del modello di programmazione dell'integrazione CLR.
Contenuto correlato
- di sicurezza dell'integrazione con CLR
- prestazioni dell'architettura di integrazione CLR