Compartir a través de


Arquitectura de integración de CLR: entorno hospedado en CLR

Se aplica a:SQL ServerAzure SQL Managed Instance

La integración de SQL Server con Common Language Runtime (CLR) de .NET Framework permite a los programadores de bases de datos usar lenguajes como C#, Visual Basic .NET y Visual C++. Las funciones, procedimientos almacenados, desencadenadores, tipos de datos y agregados pertenecen a los tipos de lógica de negocios que los programadores pueden escribir con estos lenguajes.

ClR incluye memoria recopilada por elementos no utilizados, subprocesos preferentes, servicios de metadatos (reflexión de tipos), verificabilidad de código y seguridad de acceso al código. CLR usa metadatos para localizar y cargar clases, colocar instancias en memoria, resolver invocaciones a métodos, generar código nativo, exigir mecanismos de seguridad y establecer los límites del contexto en tiempo de ejecución.

CLR y SQL Server difieren como entornos en tiempo de ejecución de la manera en que controlan la memoria, los subprocesos y la sincronización. En este artículo se describe cómo se integran estos dos tiempos de ejecución para que todos los recursos del sistema se administren uniformemente. En este artículo también se describe cómo se integra la seguridad de acceso al código (CAS) clR y la seguridad de SQL Server para proporcionar un entorno de ejecución confiable y seguro para el código de usuario.

Conceptos básicos de la arquitectura CLR

En .NET Framework, un programador escribe un lenguaje de alto nivel que implementa una clase que define su estructura (por ejemplo, los campos o las propiedades de la clase) y sus métodos. Algunos de estos métodos pueden ser funciones estáticas. La compilación del programa genera un archivo denominado ensamblado que contiene el código compilado en el lenguaje intermedio común (CIL) y un manifiesto que contiene todas las referencias a ensamblados dependientes.

Nota:

Los ensamblados constituyen un elemento vital para la arquitectura CLR; Son las unidades de empaquetado, implementación y control de versiones del código de aplicación en .NET Framework. El uso de ensamblados permite implementar el código de aplicación dentro de la base de datos y proporciona un modo uniforme de administrar, realizar copias de seguridad y restaurar aplicaciones de base de datos completas.

El manifiesto de ensamblado contiene metadatos sobre el ensamblado, que describen todas las estructuras, campos, propiedades, clases, relaciones de herencia, funciones y métodos definidos en el programa. El manifiesto establece la identidad del ensamblado, especifica los archivos que componen la implementación del ensamblado, especifica los tipos y los recursos que forman el ensamblado, desglosa en elementos las dependencias en tiempo de compilación de otros ensamblados y especifica el conjunto de permisos necesarios para que el ensamblado se ejecute correctamente. Esta información se usa en tiempo de compilación para resolver referencias, exigir el cumplimiento de las directivas de enlace de versión y validar la integridad de los ensamblados cargados.

.NET Framework admite atributos personalizados para anotar clases, propiedades, funciones y métodos con información adicional que la aplicación podría capturar en metadatos. Todos los compiladores de .NET Framework usan estas anotaciones sin interpretarlas y las almacenan como metadatos de ensamblado. Estas anotaciones pueden examinarse del mismo modo que cualquier otro metadato.

El código administrado es CIL ejecutado en CLR, en lugar de directamente por el sistema operativo. Las aplicaciones de código administrado adquieren servicios de CLR, como la recolección automática de elementos no utilizados, la comprobación de tipos en tiempo de ejecución y la compatibilidad con la seguridad. Estos servicios ayudan a proporcionar un comportamiento uniforme independiente de la plataforma y del lenguaje a las aplicaciones de código administrado.

Objetivos de diseño de la integración de CLR

Cuando el código de usuario se ejecuta dentro del entorno hospedado en CLR en SQL Server (denominado integración CLR), se aplican los siguientes objetivos de diseño:

Confiabilidad (seguridad)

No se debe permitir que el código de usuario realice operaciones que pongan en peligro la integridad del proceso del motor de base de datos, como que aparezca un cuadro de mensaje que solicite una respuesta de usuario o salga del proceso. El código de usuario no debe poder sobrescribir los búferes de memoria del motor de base de datos ni las estructuras de datos internas.

Escalabilidad

SQL Server y CLR tienen diferentes modelos internos para la programación y la administración de memoria. SQL Server admite un modelo de subprocesos cooperativo y no preferente en el que los subprocesos producen voluntariamente la ejecución periódicamente o cuando están esperando bloqueos o E/S. CLR admite un modelo de subprocesos preferente. Si el código de usuario que se ejecuta dentro de SQL Server puede llamar directamente a los primitivos de subproceso del sistema operativo, no se integra bien en el programador de tareas de SQL Server y puede degradar la escalabilidad del sistema. CLR no distingue entre la memoria virtual y física, pero SQL Server administra directamente la memoria física y es necesaria para usar la memoria física dentro de un límite configurable.

Los distintos modelos de subprocesamiento, programación y administración de memoria presentan un desafío de integración para un sistema de administración de bases de datos relacionales (RDBMS) que se escala con objeto de admitir miles de sesiones de usuarios simultáneas. La arquitectura debe asegurarse de que la escalabilidad del sistema no se ve comprometida por el código de usuario que llama a interfaces de programación de aplicaciones (API) para subprocesos, memoria y primitivos de sincronización directamente.

Seguridad

El código de usuario que se ejecuta en la base de datos debe seguir las reglas de autenticación y autorización de SQL Server al acceder a objetos de base de datos como tablas y columnas. Además, los administradores de bases de datos deben ser capaces de controlar el acceso a los recursos del sistema operativo, como el acceso a archivos y a la red, desde el código de usuario que se ejecuta en la base de datos. Esta práctica es importante, ya que los lenguajes de programación administrados (a diferencia de los lenguajes no administrados como Transact-SQL) proporcionan API para acceder a estos recursos. El sistema debe proporcionar una manera segura de que el código de usuario acceda a los recursos de la máquina fuera del proceso de Motor de base de datos. Para obtener más información, consulte seguridad de integración de CLR.

Rendimiento

El código de usuario administrado que se ejecuta en la Motor de base de datos debe tener un rendimiento computacional comparable al mismo código que se ejecuta fuera del servidor. El acceso a la base de datos desde código de usuario administrado no es tan rápido como Transact-SQL nativo. Para obtener más información, consulte Rendimiento de la arquitectura de integración clR.

Servicios CLR

CLR proporciona varios servicios para ayudar a lograr los objetivos de diseño de la integración de CLR con SQL Server.

Comprobación de la seguridad de tipos

El código con seguridad de tipos es código que obtiene acceso a las estructuras de memoria siguiendo métodos perfectamente definidos. Por ejemplo, dada una referencia válida a un objeto, el código con seguridad de tipos puede obtener acceso a la memoria en desplazamientos fijos que se correspondan con miembros de campo reales. Sin embargo, si el código accede a la memoria en desplazamientos arbitrarios dentro o fuera del intervalo de memoria que pertenece al objeto, no es seguro para tipos. Cuando los ensamblados se cargan en CLR, antes de compilar la CIL mediante la compilación Just-In-Time (JIT), el tiempo de ejecución realiza una fase de comprobación que examina el código para determinar su seguridad de tipos. El código que supera correctamente esta comprobación se denomina código con seguridad de tipos comprobable.

Dominios de aplicación

CLR admite la noción de dominios de aplicación como zonas de ejecución dentro de un proceso de host donde los ensamblados de código administrado pueden cargarse y ejecutarse. El límite del dominio de aplicación proporciona aislamiento entre los ensamblados. Los ensamblados se aíslan en lo que se refiere a la visibilidad de variables estáticas y miembros de datos, y a la capacidad de llamar al código de forma dinámica. Los dominios de aplicación también constituyen el mecanismo de carga y descarga de código. Solo es posible descargar código de la memoria descargando el dominio de aplicación. Para obtener más información, consulte Dominios de aplicación y Seguridad de integración clR.

Seguridad de acceso al código (CAS)

El sistema de seguridad de CLR proporciona un modo de controlar qué tipos de operaciones puede llevar a cabo el código administrado mediante la asignación de permisos al código. Los permisos de acceso a código se asignan según la identidad del código (por ejemplo, la firma del ensamblado o el origen del código).

CLR proporciona una directiva de equipos que puede establecer el administrador del equipo. Esta directiva define las concesiones de permisos para cualquier código administrado que se ejecute en el equipo. Además, hay una directiva de seguridad de nivel de host que pueden usar los hosts como SQL Server para especificar restricciones adicionales en el código administrado.

Si una API administrada de .NET Framework expone operaciones en recursos protegidos por un permiso de acceso de código, la API exige ese permiso antes de acceder al recurso. Esta solicitud hace que el sistema de seguridad de CLR active una comprobación completa de cada unidad de código (ensamblado) en la pila de llamadas. Solo se concede acceso al recurso si toda la cadena de llamadas tiene permiso.

La capacidad de generar código administrado dinámicamente, mediante la API de Reflection.Emit, no se admite dentro del entorno hospedado en CLR en SQL Server. Este código no tendría los permisos cas para ejecutarse y, por lo tanto, produciría un error en tiempo de ejecución. Para obtener más información, consulte integración de CLR Code Access Security.

Atributos de protección de host (HPA)

CLR proporciona un mecanismo para anotar las API administradas que forman parte de .NET Framework con determinados atributos que podrían ser de interés para un host de CLR. Algunos ejemplos de estos atributos son los siguientes:

  • SharedState, que indica si la API expone la capacidad de crear o administrar el estado compartido (por ejemplo, campos de clase estática).

  • Synchronization, que indica si la API expone la capacidad de realizar la sincronización entre subprocesos.

  • ExternalProcessMgmt, que indica si la API expone una manera de controlar el proceso de host.

Dados estos atributos, el host puede especificar una lista de HPAs, como el atributo SharedState, que se debe denegar en el entorno hospedado. En este caso, CLR rechaza los intentos del código de usuario de llamar a las API anotadas por los HPA en la lista de atributos prohibidos. Para obtener más información, consulte atributos de protección de host y programación de integración de CLR.

Funcionamiento conjunto de SQL Server y CLR

En esta sección se describe cómo SQL Server integra los modelos de subprocesos, programación, sincronización y administración de memoria de SQL Server y CLR. En concreto, en esta sección se examina la integración a la luz de los objetivos de escalabilidad, confiabilidad y seguridad. SQL Server actúa básicamente como el sistema operativo para CLR cuando se hospeda dentro de SQL Server. CLR llama a rutinas de bajo nivel implementadas por SQL Server para subprocesos, programación, sincronización y administración de memoria. Estas rutinas son las mismas primitivas que usa el resto del motor de SQL Server. Este enfoque proporciona varias ventajas de escalabilidad, confiabilidad y seguridad.

Escalabilidad: subprocesamiento, programación y sincronización comunes

CLR llama a las API de SQL Server para crear subprocesos, tanto para ejecutar código de usuario como para su propio uso interno. Para sincronizar entre varios subprocesos, CLR llama a objetos de sincronización de SQL Server. Esta práctica permite al programador de SQL Server programar otras tareas cuando un subproceso está esperando un objeto de sincronización. Por ejemplo, cuando CLR inicia la recolección de elementos no utilizados, todos sus subprocesos esperan a que finalice dicha recopilación de elementos no utilizados. Dado que los subprocesos clR y los objetos de sincronización en los que están esperando se conocen para el programador de SQL Server, SQL Server puede programar subprocesos que ejecutan otras tareas de base de datos que no implican CLR. Esto también permite a SQL Server detectar interbloqueos que implican bloqueos tomados por objetos de sincronización CLR y emplear técnicas tradicionales para la eliminación de interbloqueos.

El código administrado se ejecuta de forma preventiva en SQL Server. El programador de SQL Server tiene la capacidad de detectar y detener subprocesos que no se han producido durante una cantidad significativa de tiempo. La capacidad de enlazar subprocesos CLR a subprocesos de SQL Server implica que el programador de SQL Server puede identificar subprocesos "descontrolables" en CLR y administrar su prioridad. Dichos subprocesos consecutivos se suspenden y vuelven a colocarse en la cola. Los subprocesos que se identifican repetidamente como subprocesos descontrolado no pueden ejecutarse durante un período de tiempo determinado para que otros trabajos en ejecución puedan ejecutarse.

Hay algunas situaciones en las que el código administrado de ejecución prolongada produce automáticamente y algunas situaciones en las que no lo hace. En las situaciones siguientes, el código administrado de larga duración produce automáticamente:

  • Si el código llama al sistema operativo SQL (para consultar datos por ejemplo)
  • Si se asigna suficiente memoria para desencadenar la recolección de elementos no utilizados
  • Si el código entra en modo preventivo mediante una llamada a funciones del sistema operativo

El código que no realiza ninguna de estas acciones, como bucles ajustados que contienen solo cálculos, no produce automáticamente el programador, lo que puede provocar largas esperas para otras cargas de trabajo del sistema. En estas situaciones, es necesario que el desarrollador produzca explícitamente una llamada a la función System.Thread.Sleep() de .NET Framework, o al especificar explícitamente el modo de preferencia con System.Thread.BeginThreadAffinity(), en cualquier sección de código que se prevé que sea de larga duración. En los ejemplos de código siguientes se muestra cómo producir manualmente mediante cada uno de estos métodos.

Ejemplos

Rendimiento manual del programador de 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);
  }
}

Uso de ThreadAffinity para ejecutarse de forma preventiva

En este ejemplo, el código CLR se ejecuta en modo preventivo dentro de BeginThreadAffinity y 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();

Escalabilidad: administración de memoria común

CLR llama a primitivos de SQL Server para asignar y desasignar su memoria. Dado que la memoria usada por CLR se tiene en cuenta en el uso total de memoria del sistema, SQL Server puede permanecer dentro de sus límites de memoria configurados y asegurarse de que CLR y SQL Server no compiten entre sí para la memoria. SQL Server también puede rechazar solicitudes de memoria CLR cuando la memoria del sistema está restringida y pedir a CLR que reduzca su uso de memoria cuando otras tareas necesiten memoria.

Confiabilidad: dominios de aplicación y excepciones irrecuperables

Cuando el código administrado de las API de .NET Framework encuentra excepciones críticas, como desbordamiento de memoria insuficiente o pila, no siempre es posible recuperarse de estos errores y garantizar una semántica coherente y correcta para su implementación. Estas API generan una excepción de anulación de subprocesos en respuesta a estos errores.

Cuando se hospeda en SQL Server, estas anulaciones de subprocesos se controlan de la siguiente manera: CLR detecta cualquier estado compartido en el dominio de aplicación en el que se produce la anulación del subproceso. CLR detecta esto comprobando la presencia de objetos de sincronización. Si hay un estado compartido en el dominio de aplicación, se descarga el propio dominio de aplicación. La descarga del dominio de aplicación detiene las transacciones de base de datos que se estén ejecutando en esos momentos en dicho dominio de aplicación. Dado que la presencia de estado compartido puede ampliar el efecto de estas excepciones críticas a las sesiones de usuario distintas de las que desencadenan la excepción, SQL Server y CLR han tomado medidas para reducir la probabilidad de estado compartido. Para obtener más información, consulte .NET Framework.

Seguridad: conjuntos de permisos

SQL Server permite a los usuarios especificar los requisitos de confiabilidad y seguridad para el código implementado en la base de datos. Cuando los ensamblados se cargan en la base de datos, el autor del ensamblado puede especificar uno de los tres conjuntos de permisos para ese ensamblado: SAFE, EXTERNAL_ACCESSy UNSAFE.

Funcionalidad SAFE EXTERNAL_ACCESS UNSAFE
Code Access Security Solo ejecución Ejecución + acceso a recursos externos Sin restricciones
Programming model restrictions Sin restricciones
Verifiability requirement No
Ability to call native code No No

SAFE es el modo más confiable y seguro con restricciones asociadas en términos del modelo de programación permitido. SAFE ensamblados tienen permiso suficiente para ejecutar, realizar cálculos y tener acceso a la base de datos local. SAFE ensamblados deben ser seguros para tipos verificables y no pueden llamar al código no administrado.

UNSAFE es para código de alta confianza que solo los administradores de bases de datos pueden crear. Este código de confianza no tiene ninguna restricción de seguridad de acceso del código y puede llamar al código no administrado (nativo).

EXTERNAL_ACCESS proporciona una opción de seguridad intermedia, lo que permite que el código acceda a los recursos externos a la base de datos, pero que sigue teniendo las garantías de confiabilidad de SAFE.

SQL Server usa la capa de directiva CAS de nivel de host para configurar una directiva de host que conceda uno de los tres conjuntos de permisos en función del conjunto de permisos almacenado en catálogos de SQL Server. El código administrado que se ejecuta dentro de la base de datos siempre obtiene uno de estos conjuntos de permisos de acceso a código.

Restricciones del modelo de programación

El modelo de programación para código administrado en SQL Server implica escribir funciones, procedimientos y tipos que normalmente no requieren el uso del estado mantenido en varias invocaciones o el uso compartido de estado en varias sesiones de usuario. Además, como se ha descrito anteriormente, la presencia de estado compartido puede provocar excepciones críticas que afectan a la escalabilidad y la confiabilidad de la aplicación.

Dadas estas consideraciones, se desaconseja el uso de variables estáticas y miembros de datos estáticos de clases usadas en SQL Server. Para los ensamblados SAFE y EXTERNAL_ACCESS, SQL Server examina los metadatos del ensamblado en CREATE ASSEMBLY momento y produce un error en la creación de estos ensamblados si encuentra el uso de miembros y variables de datos estáticos.

SQL Server tampoco permite llamadas a las API de .NET Framework anotadas con los atributos de protección de host de SharedState, Synchronizationy ExternalProcessMgmt host. Esto impide que SAFE y EXTERNAL_ACCESS ensamblados llamen a cualquier API que habilite el estado de uso compartido, realice la sincronización y afecte a la integridad del proceso de SQL Server. Para obtener más información, consulte restricciones del modelo de programación de integración de CLR.