Architettura di integrazione CLR - Ambiente ospitato in CLR

Si applica a: SQL Server Istanza gestita di SQL di Azure

SQL Server'integrazione con .NET Framework Common Language Runtime (CLR) consente ai programmatori di database di usare linguaggi come Visual 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 la memoria garbage collection, il threading preemptive, i servizi di metadati (reflection dei tipi), la verificabilità del codice e la 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 il modo in cui la sicurezza di accesso al codice CLR e la sicurezza SQL Server sono integrate 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 MSIL (Microsoft Intermediate Language) e un manifesto che contiene tutti i riferimenti agli assembly dipendenti.

Nota

Gli assembly sono un elemento essenziale nell'architettura di CLR, in quanto costituiscono le unità di compressione, 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 l'annotazione di classi, proprietà, funzioni e metodi con informazioni aggiuntive che l'applicazione può 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 da MSIL 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 CLR

Quando il codice utente viene eseguito all'interno dell'ambiente ospitato in CLR in SQL Server (denominata integrazione CLR), si applicano gli obiettivi di progettazione seguenti:

Affidabilità (protezione)

Il codice utente non deve essere in grado di eseguire operazioni che danneggiano l'integrità dell'elaborazione del Motore di database, quali la visualizzazione di una finestra di messaggio popup in cui viene richiesta una risposta dell'utente o l'uscita dal processo. Il codice utente non deve essere in grado di sovrascrivere i buffer di memoria del Motore di database o le 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 SQL Server attività 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 danneggiata dal codice utente che chiama direttamente le API per le primitive di threading, memoria e sincronizzazione.

Sicurezza

Il codice utente in esecuzione nel database deve seguire SQL Server regole di autenticazione e autorizzazione durante l'accesso a oggetti di database, ad esempio 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 del motore di database. Per altre informazioni, vedere Sicurezza per l'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 Prestazioni dell'integrazione CON CLR.

CLR Services

CLR fornisce 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. Se, tuttavia, il codice accede alla memoria a offset arbitrari interni o esterni all'intervallo di memoria dell'oggetto, non si tratta di codice indipendente dai tipi. Quando gli assembly vengono caricati in CLR, prima che MSIL venga compilato tramite compilazione JIT (Just-In-Time), il runtime esegue una fase di verifica che esamina il codice per determinarne l'indipendenza dai 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 di codice (CAS, Code Access Security)

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 dagli host, ad esempio SQL Server per specificare restrizioni aggiuntive sul codice gestito.

Se un'API gestita in .NET Framework espone operazioni in risorse protette da un'autorizzazione di accesso per il codice, l'API richiederà 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 verrà concesso solo se l'intera catena di chiamate dispone dell'autorizzazione.

Si noti che 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. Tale codice non disporrebbe delle autorizzazioni di protezione dall'accesso di codice per l'esecuzione e avrebbe quindi esito negativo in fase di esecuzione. Per altre informazioni, vedere Sicurezza dall'accesso al codice di integrazione CLR.

Attributi di protezione host

CLR fornisce un meccanismo per annotare API gestite che fanno parte di .NET Framework con determinati attributi che possono interessare 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 stato condiviso, ad esempio campi classe statici.

  • Synchronization, che indica se l'API espone la possibilità di eseguire la sincronizzazione tra thread.

  • ExternalProcessMgmt, che indica se l'API espone una modalità per controllare il processo host.

Tramite questi attributi l'host può specificare un elenco di attributi di protezione host, ad esempio l'attributo SharedState, che non devono essere consentiti nell'ambiente host. 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 dell'integrazione tra 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 SQL Server API 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 SQL Server oggetti di sincronizzazione. Questa procedura consente all'utilità di pianificazione 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 SQL Server, SQL Server possono pianificare thread che eseguono altre attività di database che non coinvolgono CLR. In questo modo SQL Server consente anche di rilevare deadlock che coinvolgono blocchi acquisiti dagli 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 SQL Server ha la possibilità di rilevare e arrestare i thread che non hanno restituito per un periodo di tempo significativo. La possibilità di associare thread CLR a SQL Server thread implica che l'utilità di pianificazione 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 sfuggiti al controllo non possono essere eseguiti per un determinato periodo di tempo, in modo da consentire l'esecuzione di altri thread worker.

Esistono alcune situazioni in cui il codice gestito a esecuzione prolungata produrrà automaticamente e alcune situazioni in cui non lo sarà. Nelle situazioni seguenti il codice gestito a esecuzione prolungata restituirà automaticamente:

  • Se il codice chiama il sistema operativo SQL (ad esempio, per eseguire query sui dati)
  • Se è allocata memoria sufficiente per attivare l'operazione di Garbage Collection
  • Se il codice entra in modalità preemptive chiamando le funzioni del sistema operativo

Il codice che non esegue alcuna delle operazioni precedenti, ad esempio cicli stretti che contengono solo il calcolo, non produrrà automaticamente l'utilità di pianificazione, che può causare attese lunghe 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à preemtive con System.Thread.BeginThreadAffinity(), in tutte le sezioni di codice previste per l'esecuzione prolungata. Gli esempi di codice seguenti illustrano come produrre manualmente usando ognuno di questi metodi.

// Example 1: Manually yield to SOS scheduler.
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);
 }
}
// Example 2: Use ThreadAffinity to run preemptively.
// Within BeginThreadAffinity/EndThreadAffinity the CLR code runs in preemptive mode.
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 SQL Server primitive per l'allocazione e l'annullamento dell'allocazione della memoria. Poiché la memoria usata da CLR viene 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 altre attività richiedono memoria.

Affidabilità: domini applicazione ed eccezioni irreversibili

Quando il codice gestito nelle API di .NET Framework rileva eccezioni critiche, ad esempio un errore di memoria insufficiente o di overflow dello stack, non è sempre possibile recuperare il sistema e garantire 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 del thread vengono gestite come segue: CLR rileva qualsiasi stato condiviso nel dominio dell'applicazione in cui si verifica l'interruzione del thread. CLR rileva questo comportamento controllando la presenza di oggetti di sincronizzazione. Se è presente uno stato condiviso nel dominio applicazione, viene scaricato il dominio applicazione stesso. Lo scaricamento del dominio applicazione comporta l'arresto delle transazioni del database in esecuzione nel dominio applicazione. Poiché la presenza di stato condiviso può aumentare l'impatto 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 ulteriori informazioni, vedere la documentazione di .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
Sicurezza dall'accesso di codice Sola esecuzione Esecuzione più accesso a risorse esterne Senza restrizioni
Restrizioni del modello di programmazione Nessuna restrizione
Requisito di verificabilità No
Possibilità di chiamare il codice nativo No No

Grazie alle restrizioni associate in termini di modello di programmazione consentito, SAFE rappresenta la modalità più affidabile e protetta. Gli assembly SAFE dispongono di autorizzazioni sufficienti per l'esecuzione, l'elaborazione di calcoli e l'accesso al database locale. Gli assembly SAFE devono essere effettivamente indipendenti dai tipi e non possono chiamare codice non gestito.

UNSAFE è un set di autorizzazioni per 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 fornisce un'opzione di sicurezza intermedia e consente al codice di accedere alle risorse esterne al database, offrendo lo stesso livello 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 in SQL Server cataloghi. 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 comporta 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. Come descritto in precedenza, la presenza dello stato condiviso può inoltre determinare eccezioni critiche che influiscono sulla scalabilità e l'affidabilità dell'applicazione.

Considerate queste considerazioni, è consigliabile usare variabili statiche e membri dati statici delle classi usate in SQL Server. Per gli assembly SAFE e EXTERNAL_ACCESS, SQL Server esamina i metadati dell'assembly in fase DI CREAZIONE ASSEMBLY e non riesce la creazione di tali assembly se rileva l'uso di membri dati e variabili statici.

SQL Server non consente anche le chiamate alle API di .NET Framework annotate con gli attributi di protezione host SharedState, Synchronization e ExternalProcessMgmt. Ciò impedisce agli assembly SAFE e EXTERNAL_ACCESS 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 per l'integrazione con CLR.

Vedere anche

Sicurezza per l'integrazione con CLR
Prestazioni dell'integrazione con CLR