Partager via


Architecture d’intégration CLR - Environnement hébergé CLR

S’applique à :SQL ServerAzure SQL Managed Instance

L’intégration de SQL Server à .NET Framework Common Language Runtime (CLR) permet aux programmeurs de base de données d’utiliser des langages tels que 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 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 CIL (Common 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. Il s’agit des unités d’empaquetage, de déploiement et de contrôle de version du code d’application dans .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.

.NET Framework prend en charge les attributs personnalisés pour annoter des classes, des propriétés, des fonctions et des 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é est exécuté cil 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 le fait de faire apparaître une boîte de message demandant une réponse utilisateur ou de quitter le processus. Le code utilisateur ne doit pas pouvoir remplacer les mémoires tampons de mémoire du moteur de base de données ou les structures de données internes.

É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 threads 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 exécuté à l’intérieur de SQL Server peut appeler directement les primitives de thread de 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 s’assurer que l’extensibilité du système n’est pas compromise par le code utilisateur appelant des interfaces de programmation d’applications (API) pour les primitives de threading, de mémoire et de synchronisation directement.

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 de sécurité de l’intégration 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’architecture d’intégration CLR.

Services CLR

Le CLR fournit plusieurs 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. Toutefois, si le code accède à la mémoire à des décalages arbitraires à l’intérieur ou à l’extérieur 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 la compilation CIL à l’aide de la compilation juste-à-temps (JIT), le runtime effectue une phase de vérification qui examine le code pour déterminer sa sécurité de type. 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é de l’accès au code (CAS)

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 .NET Framework expose des opérations sur les ressources protégées par une autorisation d’accès au code, l’API demande 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 la chaîne d’appels entière a l’autorisation.

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. Ce code n’aurait pas les autorisations d’administration centrale à exécuter et échouerait donc au moment de l’exécution. Pour plus d’informations, consultez sécurité d’accès au code d’accès clR .

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

Le CLR fournit un mécanisme permettant d’annoter les API managées qui font partie du .NET Framework avec certains attributs susceptibles d’intéresser un hôte du CLR. Voici des exemples de tels attributs :

  • SharedState, qui indique si l’API expose la possibilité de créer ou de gérer un état partagé (par exemple, des champs de classe statique).

  • Synchronization, qui indique si l’API expose la possibilité d’effectuer la synchronisation entre les threads.

  • ExternalProcessMgmt, qui indique si l’API expose un moyen de contrôler le processus hôte.

Étant donné ces attributs, l’hôte peut spécifier une liste d’hpAs, telles que l’attribut SharedState, qui doit être interdit 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 la programmation d’intégration CLR.

Fonctionnement de SQL Server et du CLR

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 en tant que 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 été générés pendant un certain temps. 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 qui sont identifiés à plusieurs reprises comme des threads de fuite ne sont pas autorisés à s’exécuter pendant une période donnée afin que d’autres workers en cours d’exécution puissent s’exécuter.

Il existe certaines situations où le code managé de longue durée génère automatiquement et certaines situations où il ne le fait pas. 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 de ces actions, comme 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 appartient au développeur de produire explicitement en appelant la fonction System.Thread.Sleep() du .NET Framework, ou en entrant explicitement le mode préemptif 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.

Exemples

Retourner manuellement au planificateur 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);
  }
}

Utiliser ThreadAffinity pour s’exécuter préemptivement

Dans cet exemple, le code CLR s’exécute en mode préemptif dans BeginThreadAffinity et 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();

É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 les uns avec les autres 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

Lorsque le code managé dans les API .NET Framework rencontre des exceptions critiques, telles que le dépassement de mémoire insuffisante ou de la pile, il n’est pas toujours possible de récupérer à partir de ces défaillances 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’effet de ces 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 .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 jeux d’autorisations pour cet assembly : SAFE, EXTERNAL_ACCESSet UNSAFE.

Fonctionnalités SAFE EXTERNAL_ACCESS UNSAFE
Code Access Security Exécution uniquement Exécution + accès aux ressources externes Non restreint
Programming model restrictions Oui Oui Sans restriction
Verifiability requirement Oui Oui Non
Ability to call native code Non Non Oui

SAFE est le mode le plus fiable et le plus sécurisé avec les restrictions associées en termes de modèle de programmation autorisé. SAFE assemblys disposent d’une autorisation suffisante pour exécuter, effectuer des calculs et avoir accès à la base de données locale. SAFE assemblys doivent être vérifiables de type sécurisé et ne sont pas autorisés à appeler du code non managé.

UNSAFE concerne le code hautement approuvé qui ne peut être créé que 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 offre une option de sécurité intermédiaire, ce qui permet au code d’accéder aux ressources externes à la base de données, mais qui dispose toujours des 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. De plus, comme décrit précédemment, la présence d’un état partagé peut entraîner des exceptions critiques qui affectent l’extensibilité 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 à CREATE ASSEMBLY temps 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 des hôtes SharedState, Synchronizationet ExternalProcessMgmt. Cela empêche les assemblys SAFE et EXTERNAL_ACCESS d’appeler des API qui permettent de partager l’état, d’effectuer la synchronisation et d’affecter l’intégrité du processus SQL Server. Pour plus d’informations, consultez restrictions de modèle de programmation d’intégration CLR.