Architecture de l’intégration du CLR - Environnement hébergé CLR

S’applique à : SQL Server Azure SQL Managed Instance

L’intégration de SQL Server au Common Language Runtime (CLR) du .NET Framework permet aux programmeurs de base de données d’utiliser des langages tels que Visual C#, Visual Basic .NET et Visual C++. Les fonctions, procédures stockées, déclencheurs, types de données et agrégats sont parmi les types de logique métier que les programmeurs peuvent écrire avec ces langages.

Le CLR comprend la mémoire collectée par le garbage, le threading préemptif, les services de métadonnées (réflexion de type), la verifiabilité du code et la sécurité de l’accès au code. Le CLR utilise les métadonnées pour rechercher et charger des classes, placer des instances en mémoire, résoudre des appels de méthode, générer un code natif, appliquer la sécurité et définir les limites du contexte d'exécution.

Le CLR et SQL Server diffèrent en tant qu’environnements d’exécution de la façon dont ils gèrent la mémoire, les threads et la synchronisation. Cet article décrit la façon dont ces deux exécutions sont intégrées afin que toutes les ressources système soient gérées uniformément. Cet article traite également de la façon dont la sécurité d’accès au code CLR (CAS) et SQL Server est intégrée pour fournir un environnement d’exécution fiable et sécurisé pour le code utilisateur.

Concepts de base de l'architecture du CLR

Dans le .NET Framework, un programmeur écrit dans un langage de haut niveau qui implémente une classe définissant sa structure (par exemple, les champs ou les propriétés de la classe) et ses méthodes. Certaines de ces méthodes peuvent être des fonctions statiques. La compilation du programme produit un fichier appelé assembly qui contient le code compilé dans le langage MSIL (Microsoft Intermediate Language) et un manifeste qui contient toutes les références aux assemblys dépendants.

Remarque

Les assemblys sont un élément essentiel dans l'architecture du CLR. Ils représentent les unités d'empaquetage, de déploiement et de contrôle de version du code d'application dans le .NET Framework. Grâce aux assemblys, vous pouvez déployer le code d'application au sein de la base de données et fournir une méthode uniforme pour administrer, sauvegarder et restaurer des applications de base de données complètes.

Le manifeste d'assembly contient les métadonnées sur l'assembly, décrivant toutes les structures, les champs, les propriétés, les classes, les relations d'héritage, les fonctions et les méthodes définis dans le programme. Le manifeste établit l'identité de l'assembly, spécifie les fichiers constituant l'implémentation de l'assembly, spécifie les types et les ressources de l'assembly, détaille les dépendances lors de la compilation par rapport aux autres assemblys et précise l'ensemble des autorisations requises pour que l'assembly fonctionne correctement. Ces informations sont utilisées au moment de l'exécution pour résoudre les références, appliquer la stratégie de liaison des versions et valider l'intégrité des assemblys chargés.

Le .NET Framework prend en charge les attributs personnalisés pour annoter les classes, les propriétés, les fonctions et les méthodes avec des informations supplémentaires que l'application peut capturer dans les métadonnées. Tous les compilateurs .NET Framework consomment ces annotations sans interprétation et les stockent comme des métadonnées d'assembly. Ces annotations peuvent être examinées de la même façon que d'autres métadonnées quelconques.

Le code managé correspond à du langage MSIL exécuté dans le CLR, plutôt que directement par le système d'exploitation. Les applications de code managé acquièrent les services CLR, tels que le garbage collection automatique, la vérification des types au moment de l'exécution et la prise en charge de la sécurité. Grâce à ces services, les applications de code managé ont un comportement uniforme, indépendant de la plateforme et du langage.

Objectifs de conception de l'intégration du CLR

Lorsque le code utilisateur s’exécute à l’intérieur de l’environnement hébergé par CLR dans SQL Server (appelé intégration CLR), les objectifs de conception suivants s’appliquent :

Fiabilité (sécurité)

Le code utilisateur ne doit pas être autorisé à effectuer des opérations qui compromettent l'intégrité du processus du moteur de base de données, telles que l'affichage d'une boîte de message demandant une réponse de l'utilisateur ou la sortie du processus. Le code utilisateur ne doit pas être en mesure de remplacer les mémoires tampons ou les structures de données internes du moteur de base de données.

Évolutivité

SQL Server et le CLR ont des modèles internes différents pour la planification et la gestion de la mémoire. SQL Server prend en charge un modèle de threading coopératif et non préemptif dans lequel les threads produisent volontairement l’exécution périodiquement, ou lorsqu’ils attendent des verrous ou des E/S. Le CLR prend en charge un modèle de thread préemptif. Si le code utilisateur s’exécutant à l’intérieur de SQL Server peut appeler directement les primitives de thread du système d’exploitation, il ne s’intègre pas correctement dans le planificateur de tâches SQL Server et peut dégrader l’extensibilité du système. Le CLR ne fait pas la distinction entre la mémoire virtuelle et physique, mais SQL Server gère directement la mémoire physique et est nécessaire pour utiliser la mémoire physique dans une limite configurable.

Les modèles différents de threading, de planification et de gestion de la mémoire présentent une difficulté d'intégration pour un système de gestion de base de données relationnelle (SGBDR) qui évolue pour prendre en charge des milliers de sessions utilisateur simultanées. L'architecture doit garantir que l'évolutivité du système n'est pas compromise lorsque le code d'utilisateur appelle des interfaces de programmation d'applications (API) directement pour des primitives de thread, de mémoire et de synchronisation.

Sécurité

Le code utilisateur s’exécutant dans la base de données doit suivre les règles d’authentification et d’autorisation SQL Server lors de l’accès aux objets de base de données tels que les tables et les colonnes. De plus, les administrateurs de base de données doivent être en mesure de contrôler l'accès aux ressources du système d'exploitation, tel que l'accès aux fichiers et au réseau, à partir du code utilisateur qui s'exécute dans la base de données. Cette pratique devient importante en tant que langages de programmation managés (contrairement aux langages non managés tels que Transact-SQL) fournissent des API pour accéder à ces ressources. Le système doit fournir un moyen sécurisé pour le code utilisateur d’accéder aux ressources de l’ordinateur en dehors du processus de Moteur de base de données. Pour plus d’informations, consultez Sécurité de l’intégration du CLR.

Performances

Le code utilisateur managé exécuté dans le Moteur de base de données doit avoir des performances de calcul comparables au même code exécuté en dehors du serveur. L’accès à la base de données à partir du code utilisateur managé n’est pas aussi rapide que Transact-SQL natif. Pour plus d’informations, consultez Performances de l’intégration clR.

Services CLR

Le CLR fournit un certain nombre de services pour aider à atteindre les objectifs de conception de l’intégration du CLR à SQL Server.

Vérification de la cohérence des types

Un code de type sécurisé est un code qui accède aux structures de mémoire uniquement de façons bien définies. Prenons par exemple une référence d'objet valide, le code de type sécurisé peut accéder à la mémoire à des offsets fixes correspondant aux membres de champ réels. Cependant, si le code accède à la mémoire à des offsets arbitraires dans ou hors de la plage de mémoire qui appartient à l'objet, il n'est pas de type sécurisé. Lorsque des assemblys sont chargés dans le CLR, avant que le langage MSIL soit compilé à l'aide de la compilation juste-à-temps (JIT), le runtime effectue une phase de vérification qui examine le code pour déterminer s'il est de type sécurisé. Le code qui réussit cette vérification est appelé code de type sécurisé vérifié.

Domaines d'application

Le CLR prend en charge la notion de domaines d'application comme des zones d'exécution au sein d'un processus hôte dans lesquelles des assemblys de code managé peuvent être chargés et exécutés. La limite du domaine d'application assure l'isolement entre les assemblys. Les assemblys sont isolés quant à la visibilité des variables statiques et des membres de données et à la capacité d'appeler dynamiquement le code. Les domaines d'application correspondent également au mécanisme de chargement et de déchargement du code. Le code peut être déchargé à partir de la mémoire seulement en déchargeant le domaine d'application. Pour plus d’informations, consultez Domaines d’application et sécurité d’intégration CLR.

Sécurité d'accès du code

Le système de sécurité du CLR offre un moyen pour à contrôler quels types d'opérations le code managé peut effectuer en assignant des autorisations au code. Les autorisations d'accès au code sont assignées en fonction de l'identité du code (par exemple, la signature de l'assembly ou l'origine du code).

Le CLR prévoit une stratégie de l'ordinateur qui peut être définie par l'administrateur de l'ordinateur. Cette stratégie définit les attributions d'autorisations pour tout code managé qui s'exécute sur l'ordinateur. En outre, il existe une stratégie de sécurité au niveau de l’hôte qui peut être utilisée par des hôtes tels que SQL Server pour spécifier des restrictions supplémentaires sur le code managé.

Si une API managée dans le .NET Framework expose des opérations sur les ressources protégées par une autorisation d'accès au code, l'API demandera cette autorisation avant d'accéder à la ressource. Cette demande conduit le système de sécurité du CLR à déclencher une vérification complète de chaque unité de code (assembly) dans la pile d'appels. L’accès à la ressource est accordé uniquement si l’ensemble de la chaîne d’appels dispose d’une autorisation.

Notez que la possibilité de générer du code managé dynamiquement, à l’aide de l’API Reflection.Emit, n’est pas prise en charge dans l’environnement hébergé par CLR dans SQL Server. Un tel code n'aurait pas les autorisations de sécurité d'accès du code pour s'exécuter et échouerait par conséquent au moment de l'exécution. Pour plus d’informations, consultez CLR Integration Code Access Security.

Attributs de protection de l'hôte (HPA)

Le CLR fournit un mécanisme pour annoter les API managées qui font partie du .NET Framework avec certains attributs qui peuvent avoir un intérêt pour un hôte du CLR. Voici des exemples de tels attributs :

  • SharedState, qui indique si l'API expose la capacité à créer ou gérer l'état partagé (par exemple, champs de classe statique).

  • Synchronization, qui indique si l'API expose la capacité à réaliser la synchronisation entre des threads.

  • ExternalProcessMgmt, qui indique si l'API expose une méthode pour contrôler le processus hôte.

Étant donné ces attributs, l'hôte peut spécifier une liste de HPA, tels que l'attribut SharedState, qui doivent être rejetés dans l'environnement hébergé. Dans ce cas, le CLR refuse les tentatives d'appel par code utilisateur des API annotées par les HPA dans la liste interdite. Pour plus d’informations, consultez Attributs de protection de l’hôte et programmation d’intégration CLR.

Comment SQL Server et le CLR fonctionnent-ils ensemble ?

Cette section explique comment SQL Server intègre les modèles de gestion des threads, de la planification, de la synchronisation et de la mémoire de SQL Server et du CLR. En particulier, cette section examine l'intégration du point de vue de l'évolutivité, de la fiabilité et des objectifs en matière de sécurité. SQL Server agit essentiellement comme système d’exploitation pour le CLR lorsqu’il est hébergé à l’intérieur de SQL Server. Le CLR appelle des routines de bas niveau implémentées par SQL Server pour la gestion des threads, de la planification, de la synchronisation et de la mémoire. Ces routines sont les mêmes primitives que le reste du moteur SQL Server utilise. Cette approche fournit plusieurs avantages en termes d'évolutivité, de fiabilité et de sécurité.

Évolutivité : threading, planification et synchronisation courants

CLR appelle des API SQL Server pour créer des threads, à la fois pour exécuter du code utilisateur et pour son propre usage interne. Pour synchroniser entre plusieurs threads, le CLR appelle des objets de synchronisation SQL Server. Cette pratique permet au planificateur SQL Server de planifier d’autres tâches lorsqu’un thread attend un objet de synchronisation. Par exemple, lorsque le CLR lance le garbage collection, tous ses threads attendent que le garbage collection se termine. Étant donné que les threads CLR et les objets de synchronisation qu’ils attendent sont connus du planificateur SQL Server, SQL Server peut planifier des threads qui exécutent d’autres tâches de base de données qui n’impliquent pas le CLR. Cela permet également à SQL Server de détecter les interblocages qui impliquent des verrous pris par des objets de synchronisation CLR et utilisent des techniques traditionnelles pour la suppression du blocage.

Le code managé s’exécute préemptivement dans SQL Server. Le planificateur SQL Server a la possibilité de détecter et d’arrêter des threads qui n’ont pas généré de temps considérable. La possibilité de raccorder des threads CLR à des threads SQL Server implique que le planificateur SQL Server peut identifier les threads « runaway » dans le CLR et gérer leur priorité. De tels threads fugitifs sont suspendus et placés en arrière dans la file d'attente. Les threads identifiés à plusieurs reprises comme des threads fugitifs ne sont pas autorisés à s'exécuter pendant un intervalle de temps donné afin que les autres travaux en cours puissent s'exécuter.

Il existe certaines situations où le code managé de longue durée génère automatiquement et certaines situations où ce n’est pas le cas. Dans les situations suivantes, le code managé de longue durée génère automatiquement :

  • Si le code appelle le système d’exploitation SQL (pour interroger des données par exemple)
  • Si suffisamment de mémoire est allouée pour déclencher le garbage collection
  • Si le code entre en mode préemptif en appelant des fonctions de système d’exploitation

Le code qui ne fait aucune des boucles ci-dessus, par exemple des boucles serrées qui contiennent uniquement des calculs, ne génère pas automatiquement le planificateur, ce qui peut entraîner de longues attentes pour d’autres charges de travail dans le système. Dans ces situations, il incombe au développeur de produire explicitement en appelant la fonction System.Thread.Sleep() du .NET Framework, ou en entrant explicitement le mode préemtif avec System.Thread.BeginThreadAffinity(), dans toutes les sections du code qui sont prévues pour être longues. Les exemples de code suivants montrent comment générer manuellement à l’aide de chacune de ces méthodes.

// 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();
Évolutivité : gestion de la mémoire courante

Le CLR appelle des primitives SQL Server pour allouer et déallouer sa mémoire. Étant donné que la mémoire utilisée par le CLR est prise en compte dans l’utilisation totale de la mémoire du système, SQL Server peut rester dans ses limites de mémoire configurées et s’assurer que le CLR et SQL Server ne sont pas en concurrence entre eux pour la mémoire. SQL Server peut également rejeter les demandes de mémoire CLR lorsque la mémoire système est contrainte et demander au CLR de réduire son utilisation de la mémoire lorsque d’autres tâches ont besoin de mémoire.

Fiabilité : domaines d'application et exceptions irrécupérables

Lorsqu'un code managé dans les API du .NET Framework rencontre des exceptions critiques, telles qu'une mémoire insuffisante ou un dépassement de capacité de la pile, il n'est pas toujours possible de récupérer après de tels échecs et de garantir une sémantique cohérente et correcte pour leur implémentation. Ces API déclenchent une exception d'abandon de thread en réponse à ces échecs.

Lorsqu’elles sont hébergées dans SQL Server, ces abandons de thread sont gérés comme suit : le CLR détecte tout état partagé dans le domaine d’application dans lequel se produit l’abandon du thread. Le CLR détecte cela en vérifiant la présence d’objets de synchronisation. S'il existe un état partagé dans le domaine d'application, le domaine d'application lui-même est déchargé. Le déchargement du domaine d'application arrête les transactions de base de données qui sont actuellement en cours d'exécution dans ce domaine d'application. Étant donné que la présence d’un état partagé peut élargir l’impact de telles exceptions critiques aux sessions utilisateur autres que celle qui déclenche l’exception, SQL Server et le CLR ont pris des mesures pour réduire la probabilité d’état partagé. Pour plus d'informations, consultez la documentation sur .NET Framework.

Sécurité : jeux d'autorisations

SQL Server permet aux utilisateurs de spécifier les exigences de fiabilité et de sécurité pour le code déployé dans la base de données. Lorsque des assemblys sont chargés dans la base de données, l’auteur de l’assembly peut spécifier l’un des trois ensembles d’autorisations pour cet assembly : SAFE, EXTERNAL_ACCESS et UNSAFE.

Fonctionnalités SAFE EXTERNAL_ACCESS UNSAFE
Sécurité d'accès du code Exécution uniquement Exécution + accès aux ressources externes Non restreint
Restrictions du modèle de programmation Oui Oui Sans restriction
Vérifiabilité requise Oui Oui Non
Possibilité d'appeler du code natif Non Non Oui

SAFE est le mode le plus fiable et sécurisé avec des restrictions associées quant au modèle de programmation autorisé. Les assemblys SAFE bénéficient d'autorisations suffisantes pour s'exécuter, effectuer des calculs et avoir accès à la base de données locale. Les assemblys SAFE doivent être de type sécurisé vérifié et ne sont pas autorisés à appeler du code non managé.

UNSAFE est réservé à du code hautement approuvé qui peut être créé uniquement par les administrateurs de base de données. Ce code approuvé n'a pas de restrictions de sécurité d'accès du code et il peut appeler du code non managé (natif).

EXTERNAL_ACCESS fournit une option de sécurité intermédiaire. Il permet au code d'accéder à des ressources externes à la base de données, mais possède néanmoins les garanties de fiabilité de SAFE.

SQL Server utilise la couche de stratégie CAS au niveau de l’hôte pour configurer une stratégie hôte qui accorde l’un des trois ensembles d’autorisations en fonction du jeu d’autorisations stocké dans les catalogues SQL Server. Le code managé qui s'exécute au sein de la base de données obtient toujours l'un de ces jeux d'autorisations d'accès du code.

Restrictions du modèle de programmation

Le modèle de programmation pour le code managé dans SQL Server implique l’écriture de fonctions, de procédures et de types qui ne nécessitent généralement pas l’utilisation de l’état détenu sur plusieurs appels ou le partage d’état entre plusieurs sessions utilisateur. Par ailleurs, comme décrit précédemment, la présence d'un état partagé peut provoquer des exceptions critiques qui affectent l'évolutivité et la fiabilité de l'application.

Compte tenu de ces considérations, nous déconseillons l’utilisation de variables statiques et de membres de données statiques des classes utilisées dans SQL Server. Pour les assemblys SAFE et EXTERNAL_ACCESS, SQL Server examine les métadonnées de l’assembly au moment de CREATE ASSEMBLY et échoue la création de ces assemblys s’il trouve l’utilisation de membres et de variables de données statiques.

SQL Server interdit également les appels aux API .NET Framework annotées avec les attributs de protection de l’hôte SharedState, Synchronization et ExternalProcessMgmt . Cela empêche les assemblys SAFE et EXTERNAL_ACCESS d’appeler des API qui activent l’état de partage, effectuent la synchronisation et affectent l’intégrité du processus SQL Server. Pour plus d’informations, consultez restrictions du modèle de programmation d’intégration CLR.

Voir aussi

Sécurité de l’intégration du CLR
Performances de l’intégration du CLR