Entorno alojado CLR

Common Language Runtime (CLR) de Microsoft .NET Framework es un entorno que ejecuta muchos lenguajes de programación modernos, incluidos Microsoft Visual C#, Microsoft Visual Basic y Microsoft Visual C++. CLR presenta memoria de recolección de elementos no utilizados, subprocesamiento preferente, servicios de metadatos (reflexión de tipos), capacidad de comprobar el código y seguridad de acceso a 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, como entornos de tiempo de ejecución, se diferencian en el modo en que administran la memoria, los subprocesos y la sincronización. En este tema se describe el modo en que estos dos tiempos de ejecución se integran para que todos los recursos del sistema se administren uniformemente. En este tema también se describe el modo en que la seguridad de acceso a código (CAS) de CLR y la seguridad de SQL Server se integran 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 de Microsoft (MSIL) 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 de .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 puede 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.

MSIL ejecuta el código administrado en CLR, en lugar ejecutarlo directamente 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.

Diseñar objetivos de integración CLR

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

Confiabilidad (seguridad)

El código de usuario no debería tener permiso para llevar a cabo operaciones que pongan en peligro la integridad del proceso del motor de base de datos, como mostrar un cuadro de mensaje que solicite una respuesta por parte del usuario o salir del proceso. El código de usuario no debería poder sobrescribir los búferes de memoria del motor de base de datos o las estructuras de datos internas.

Escalabilidad

SQL Server y CLR tienen distintos modelos internos de programación y administración de memoria. SQL Server admite un modelo de subprocesos cooperativo, no preferente, en el que los subprocesos se ejecutan de forma voluntaria y periódica 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 tipos primitivos de subprocesamiento del sistema operativo, significa que no se integra bien en el programador de tareas de SQL Server y que puede degradar la escalabilidad del sistema. CLR no distingue entre la memoria virtual y física, pero SQL Server administra la memoria física directamente y se le exige que use 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 garantizar que la escalabilidad del sistema no se vea en peligro por el hecho de que el código de usuario llame directamente a las interfaces de programación de aplicaciones (API) para los tipos primitivos de subprocesamiento, memoria y sincronización.

Seguridad

El código de usuario que se ejecuta en la base de datos debe cumplir las normas de autenticación y autorización de SQL Server al obtener acceso 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. Esto adquiere importancia cuando los lenguajes de programación administrados (a diferencia de los lenguajes no administrados, como Transact-SQL) proporcionan distintas API para obtener acceso a dichos recursos. El sistema debe proporcionar un modo seguro para que el código de usuario obtenga acceso a los recursos del equipo fuera del proceso de Database Engine (Motor de base de datos). Para obtener más información, vea Seguridad de la integración CLR.

Rendimiento

El código de usuario administrado que se ejecuta en Database Engine (Motor de base de datos) debe tener un rendimiento de cálculo comparable a ese mismo código ejecutado fuera del servidor. El acceso a la base de datos desde el código de usuario administrado no es tan rápido como el Transact-SQL nativo. Para obtener más información, vea Rendimiento de la integración CLR.

Servicios CLR

CLR ofrece varios servicios para ayudar a lograr los objetivos de diseño de la integración 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 obtiene acceso a la memoria en desplazamientos arbitrarios que se encuentran dentro o fuera del intervalo de memoria perteneciente al objeto, significa que no tiene seguridad de tipos. Cuando los ensamblados se cargan en CLR, antes de que MSIL se compile 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, vea Dominios de aplicación y seguridad de la integración CLR.

Seguridad de acceso a 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 puedan 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 a código, la API solicitará ese permiso antes de obtener acceso 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 si la cadena de llamadas completa tiene permiso obtendrá acceso al recurso.

Tenga en cuenta que la capacidad de generar código administrado de forma dinámica, mediante la API Reflection.Emit, no se admite dentro del entorno CLR hospedado en SQL Server. Dicho código no tendría los permisos CAS necesarios para ejecutarse y, por lo tanto, generaría un error en tiempo de ejecución. Para obtener más información, vea Seguridad de acceso del código de integración CLR.

Atributos de protección del host (HPA)

CLR proporciona un mecanismo para anotar API administradas que forman parte de .NET Framework con determinados atributos que pueden 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 un estado compartido (por ejemplo, campos de clase estática).

  • Synchronization, que indica si la API expone la capacidad de llevar a cabo una sincronización entre los subprocesos.

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

Dados estos atributos, el host puede especificar una lista de atributos HPA, como el atributo SharedState, que no deberían permitirse 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, vea Atributos de protección del host y programación de la integración CLR.

Cómo trabajan juntos SQL Server y CLR

En esta sección se describe el modo en que SQL Server integra los modelos de subprocesamiento, 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 esencialmente como sistema operativo para CLR cuando se hospeda en SQL Server. CLR llama a las rutinas de bajo nivel implementadas por SQL Server para el subprocesamiento, la programación, la sincronización y la administración de memoria. Éstos son los mismos tipos primitivos 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 la API de SQL Server a fin de crear subprocesos para ejecutar código de usuario y para su propio uso interno. Para realizar una sincronización entre varios subprocesos, CLR llama a los objetos de sincronización de SQL Server. Esto permite al programador de SQL Server programar otras tareas cuando un subproceso espera en 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 recolección de elementos no utilizados. Puesto que el programador de SQL Server conoce los subprocesos CLR y los objetos de sincronización que están esperando, SQL Server puede programar los subprocesos que están ejecutando otras tareas de base de datos no relacionadas con CLR. Esto también permite a SQL Server detectar interbloqueos que implican bloqueos tomados por los objetos de sincronización CLR y emplear técnicas tradicionales para la eliminación de los interbloqueos.

El código administrado se ejecuta de forma preferente en SQL Server. El programador de SQL Server tiene capacidad para detectar y detener subprocesos que no se han producido durante mucho tiempo. La capacidad de enlazar subprocesos CLR con subprocesos de SQL Server implica que el programador de SQL Server puede identificar subprocesos consecutivos 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 consecutivos no tienen permiso para ejecutarse durante un período de tiempo determinado de manera que puedan ejecutarse otros subprocesos de trabajo en ejecución.

[!NOTA]

El código administrado de ejecución prolongada que obtiene acceso a datos o asigna suficiente memoria para desencadenar la recolección de elementos no utilizados se producirá automáticamente. El código administrado de ejecución prolongada que no obtiene acceso a datos o no asigna suficiente memoria administrada para desencadenar la recolección de elementos no utilizados debe producirse explícitamente llamando a la función System.Thread.Sleep() de .NET Framework.

Escalabilidad: administración de memoria común

CLR llama a los tipos primitivos de SQL Server para asignar y anular la asignación de su memoria. Dado que la memoria usada por CLR se tiene en cuenta a efectos del uso de memoria total del sistema, SQL Server puede permanecer dentro de sus límites de memoria configurados y asegurarse de que CLR y SQL Server no compitan entre sí por obtener más memoria. SQL Server también puede rechazar las solicitudes de memoria de CLR cuando la memoria del sistema está restringida y solicitar 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 detecta excepciones críticas, como excepciones de memoria insuficiente o desbordamiento de pila, no siempre puede recuperarse de dichos 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 hospedan en SQL Server, dichas anulaciones de subprocesos se controlan de la siguiente forma: CLR detecta cualquier estado compartido en el dominio de aplicación en el que se produce la anulación del subproceso. Para ello, CLR comprueba 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 aumentar el impacto de dichas excepciones críticas en las sesiones de usuario distintas de la que desencadena la excepción, SQL Server y CLR han dado algunos pasos para reducir la probabilidad del estado compartido. Para obtener más información, vea la documentación de .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 dicho ensamblado: SAFE, EXTERNAL_ACCESS y UNSAFE.

Conjunto de permisos

SAFE

EXTERNAL_ACCESS

UNSAFE

Seguridad de acceso a código

Solo ejecución

Ejecución + acceso a recursos externos

Sin restringir

Restricciones del modelo de programación

Sin restricciones

Requisito de capacidad de comprobación

No

Capacidad de llamar a código nativo

No

No

SAFE es el modo más confiable y seguro, con restricciones asociadas relativas al modelo de programación permitido. Los ensamblados SAFE tienen permisos suficientes para la ejecución, realización de cálculos y obtención de acceso a la base de datos local. Los ensamblados SAFE deben tener capacidad para comprobar la seguridad de los tipos y no pueden llamar a código no administrado.

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

EXTERNAL_ACCESS proporciona una opción de seguridad intermedia, que permite al código tener acceso a los recursos externos a la base de datos, pero manteniendo 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 concede uno de los tres conjuntos de permisos basados en el conjunto de permisos almacenado en los 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 el código administrado de SQL Server implica la escritura de funciones, procedimientos y tipos que normalmente no necesitan usar el estado mantenido a lo largo de varias invocaciones o compartir el estado entre varias sesiones de usuario. Además, como se explicó antes, la presencia de estado compartido puede producir excepciones críticas que tienen un impacto en la escalabilidad y la confiabilidad de la aplicación.

A la vista de estas consideraciones, se desaconseja el uso de variables estáticas y miembros de datos estáticos de las clases que se utilizan en SQL Server. En el caso de ensamblados SAFE y EXTERNAL_ACCESS, SQL Server examina los metadatos del ensamblado en el momento de creación del ensamblado (CREATE ASSEMBLY) y no puede crear dichos ensamblados si detecta el uso de variables y miembros de datos estáticos.

SQL Server tampoco permite las llamadas a las API de .NET Framework anotadas con los atributos de protección del host SharedState, Synchronization y ExternalProcessMgmt. Esto impide que los ensamblados EXTERNAL_ACCESS y SAFE llamen a cualquiera de las API que habilitan el estado compartido, ejecuten la sincronización y puedan afectar a la integridad del proceso de SQL Server. Para obtener más información, vea Restricciones del modelo de programación de la integración CLR.