Arquitetura de integração CLR – Ambiente hospedado de CLR

Aplica-se a:SQL ServerInstância Gerenciada de SQL do Azure

SQL Server integração com o CLR (Common Language Runtime) .NET Framework permite que os programadores de banco de dados usem linguagens como Visual C#, Visual Basic .NET e Visual C++. Funções, procedimentos armazenados, gatilhos, tipos de dados e agregações estão entre os tipos de lógica corporativa que os programadores podem escrever usando essas linguagens.

O CLR apresenta memória coletada por lixo, threading preemptivo, serviços de metadados (reflexão de tipo), verificabilidade de código e segurança de acesso ao código. Ele usa metadados para localizar e carregar classes, distribuir as instâncias na memória, resolver invocações de métodos, gerar código nativo, impor a segurança e definir limites de contexto em tempo de execução.

O CLR e SQL Server diferem como ambientes de tempo de execução na maneira como lidam com memória, threads e sincronização. Este artigo descreve a maneira como esses dois tempos de execução são integrados para que todos os recursos do sistema sejam gerenciados uniformemente. Este artigo também aborda a maneira como a CAS (segurança de acesso ao código) CLR e a segurança SQL Server são integradas para fornecer um ambiente de execução confiável e seguro para o código do usuário.

Conceitos básicos da arquitetura do CLR

No .NET Framework, um programador escreve o código em uma linguagem de alto nível que implementa uma classe definindo sua estrutura (por exemplo, os campos das propriedades da classe) e seus métodos. Alguns desses métodos podem ser funções estáticas. A compilação do programa produz um arquivo chamado assembly que contém o código compilado na MSIL (linguagem intermediária da Microsoft) e um manifesto que contém todas as referências a assemblies dependentes.

Observação

Os assemblies são um elemento vital na arquitetura do CLR. Eles são as unidades de empacotamento, implantação e controle de versão de código de aplicativo no .NET Framework. Usando assemblies, você pode implantar código de aplicativo no banco de dados e fornecer um modo uniforme de administrar, fazer backup e restaurar aplicativos de bancos de dados completos.

O manifesto do assembly contém metadados sobre o assembly, descrevendo todas as estruturas, campos, propriedades, classes, relacionamentos de herança, funções e métodos definidos no programa. O manifesto estabelece a identidade do assembly, especifica os arquivos que compõem a sua implementação, especifica os tipos e recursos que o compõem, mantém uma lista das dependências de tempo de compilação em outros assemblies e especifica o conjunto de permissões necessárias para que seja executado adequadamente. Estas informações são usadas em tempo de execução para resolver referências, impor a política de ligação da versão e validar a integridade dos assemblies carregados.

O .NET Framework dá suporte a atributos personalizados para anotar classes, propriedades, funções e métodos com informações adicionais que o aplicativo possa capturar em metadados. Todos os compiladores .NET Framework consomem essas anotações sem interpretação e as armazenam como metadados de assembly. Estas anotações podem ser examinadas da mesma forma que quaisquer outros metadados.

O código gerenciado é MSIL executado no CLR, em vez de diretamente pelo sistema operacional. Aplicativos de código gerenciado adquirem serviços de CLR, como coleta de lixo automática, verificação de tipo de tempo de execução e suporte de segurança. Estes serviços ajudam a fornecer uma plataforma uniforme, bem como comportamento independente de linguagem de aplicativos de código gerenciado.

Metas de design da integração de CLR

Quando o código do usuário é executado dentro do ambiente hospedado por CLR no SQL Server (chamado de integração CLR), as seguintes metas de design se aplicam:

Confiabilidade (segurança)

O código do usuário não deve ter permissão para realizar operações que comprometam a integridade do processo do Mecanismo de banco de dados, como exibir uma mensagem em pop-up solicitando resposta do usuário ou sair do processo. Também não deve ser capaz substituir os buffers de memória ou as estruturas de dados internas do Mecanismo de banco de dados.

Escalabilidade

SQL Server e o CLR têm modelos internos diferentes para agendamento e gerenciamento de memória. SQL Server dá suporte a um modelo de threading cooperativo e não preemptivo no qual os threads geram execução voluntariamente periodicamente ou quando estão aguardando bloqueios ou E/S. O CLR dá suporte um modelo de threading preemptivo. Se o código do usuário em execução dentro de SQL Server puder chamar diretamente os primitivos de threading do sistema operacional, ele não se integrará bem ao agendador de tarefas SQL Server e poderá degradar a escalabilidade do sistema. O CLR não distingue entre memória virtual e física, mas SQL Server gerencia diretamente a memória física e é necessário usar memória física dentro de um limite configurável.

Os diferentes modelos de threading, agendamento e gerenciamento de memória são um desafio de integração para um RDBMS (sistema de gerenciamento de banco de dados relacional) que se dimensiona para dar suporte a milhares de sessões de usuário simultâneas. A arquitetura deve garantir que a escalabilidade do sistema não seja comprometida pelo código de usuário que chame diretamente APIs (application programming interfaces) para primitivas de threading, memória e sincronização.

Segurança

O código do usuário em execução no banco de dados deve seguir SQL Server regras de autenticação e autorização ao acessar objetos de banco de dados, como tabelas e colunas. Além disso, os administradores de bancos de dados devem ter a capacidade de controlar o acesso aos recursos do sistema operacional, como arquivos e acesso à rede, do código de usuário em execução no banco de dados. Essa prática torna-se importante, pois as linguagens de programação gerenciadas (ao contrário de linguagens não gerenciadas, como o Transact-SQL) fornecem APIs para acessar esses recursos. O sistema deve fornecer uma maneira segura para que o código do usuário acesse recursos de computador fora do processo do Mecanismo de Banco de Dados. Para obter mais informações, consulte CLR Integration Security.

Desempenho

O código do usuário gerenciado em execução no Mecanismo de Banco de Dados deve ter um desempenho computacional comparável ao mesmo código executado fora do servidor. O acesso de banco de dados do código do usuário gerenciado não é tão rápido quanto o Transact-SQL nativo. Para obter mais informações, consulte Desempenho da integração clr.

Serviços CLR

O CLR fornece uma série de serviços para ajudar a alcançar as metas de design da integração clr com SQL Server.

Verificação de segurança de tipo

Código fortemente tipado é um código que acessa as estruturas de memória somente de modos bem definidos. Por exemplo, dada uma referência de objeto válida, o código fortemente tipado pode acessar memória em offsets fixos, correspondentes a membros de campo reais. Entretanto, se o código acessar a memória em offsets arbitrários dentro ou fora do intervalo de memória que pertence ao objeto, então ele não será fortemente tipado. Quando os assemblies são carregados no CLR, antes de a MSIL ser compilada usando compilação JIT (just-in-time), o runtime executa uma fase de verificação que examina o código antes de determinar sua segurança de tipos. O código aprovado com êxito nesta verificação é chamado de código fortemente tipado verificável.

Domínios de aplicativo

O CLR dá suporte à noção de domínios de aplicativo como zonas de execução dentro de um processo de host, onde assemblies de código gerenciado podem ser carregados e executados. O limite do domínio do aplicativo fornece isolamento entre assemblies. Os assemblies são isolados em termos de visibilidade de variáveis estáticas e membros de dados e de capacidade para chamar código dinamicamente. Domínios de aplicativo também são o mecanismo para carregar e descarregar código. O código só pode ser descarregado da memória através do descarregamento do domínio de aplicativo. Para obter mais informações, consulte Domínios de aplicativo e Segurança de Integração clr.

CAS (segurança de acesso ao código)

O sistema de segurança CLR sistema fornece um modo de controlar quais os tipos de código gerenciado de operações que podem ser executados, atribuindo permissões ao código. As permissões de acesso ao código são atribuídas com base na identidade do código (por exemplo, a assinatura do assembly ou a origem do código).

O CLR fornece uma política para todo o computador que pode ser definida pelo administrador do computador. Essa política define as concessões de permissão para qualquer código gerenciado em execução na máquina. Além disso, há uma política de segurança no nível do host que pode ser usada por hosts como SQL Server para especificar restrições adicionais no código gerenciado.

Se uma API gerenciada no .NET Framework expuser operações sobre recursos que são protegidas por uma permissão de acesso a código, a API solicitará essa permissão antes de acessar o recurso. Essa solicitação faz o sistema de segurança CLR ativar uma verificação abrangente de cada unidade de código (assembly) na pilha de chamadas. O acesso ao recurso será concedido somente se toda a cadeia de chamadas tiver permissão.

Observe que a capacidade de gerar código gerenciado dinamicamente, usando a API Reflection.Emit, não tem suporte dentro do ambiente hospedado por CLR no SQL Server. Esse código não teria as permissões de execução da CAS e, portanto, falharia em tempo de execução. Para obter mais informações, consulte Segurança de acesso ao código de integração clr.

HPAs (atributos de proteção de host)

O CLR fornece um mecanismo para anotar APIs gerenciadas que fazem parte do .NET Framework com determinados atributos que podem ser de interesse de um host do CLR. Exemplos de tais atributos incluem:

  • SharedState, que indica se a API expõe a capacidade de criar ou gerenciar estado compartilhado (por exemplo, campos de classe estáticos).

  • Synchronization, que indica se a API expõe a capacidade para executar sincronização entre threads.

  • ExternalProcessMgmt, que indica se a API expõe um modo de controlar o processo de host.

Dados esses atributos, o host pode especificar uma lista de HPAs, como o atributo SharedState, que devem ser desabilitados no ambiente hospedado. Nesse caso, o CLR nega as tentativas do código de usuário de chamar APIs que são anotadas pelo HPAs na lista proibida. Para obter mais informações, consulte Atributos de proteção de host e Programação de integração clr.

Como o SQL Server e o CLR trabalham juntos

Esta seção discute como SQL Server integra os modelos de threading, agendamento, sincronização e gerenciamento de memória do SQL Server e do CLR. Em particular, esta seção examina a integração sob o ponto de vista das metas de escalabilidade, confiabilidade e segurança. SQL Server atua essencialmente como o sistema operacional para o CLR quando ele é hospedado dentro de SQL Server. O CLR chama rotinas de baixo nível implementadas por SQL Server para threading, agendamento, sincronização e gerenciamento de memória. Essas rotinas são as mesmas primitivas que o resto do mecanismo de SQL Server usa. Esta abordagem fornece vários benefícios de escalabilidade, confiabilidade e segurança.

Escalabilidade: Threading, agendamento e sincronização comuns

O CLR chama SQL Server APIs para criar threads, tanto para executar o código do usuário quanto para seu próprio uso interno. Para sincronizar entre vários threads, o CLR chama SQL Server objetos de sincronização. Essa prática permite que o agendador SQL Server agende outras tarefas quando um thread está aguardando em um objeto de sincronização. Por exemplo, quando o CLR iniciar a coleta de lixo, todos os seus threads esperam a coleta de lixo terminar. Como os threads CLR e os objetos de sincronização em que estão aguardando são conhecidos pelo agendador SQL Server, SQL Server pode agendar threads que estão executando outras tarefas de banco de dados que não envolvem o CLR. Isso também permite que SQL Server detectem deadlocks que envolvem bloqueios feitos por objetos de sincronização CLR e empregue técnicas tradicionais para remoção de deadlock.

O código gerenciado é executado preventivamente em SQL Server. O agendador de SQL Server tem a capacidade de detectar e parar threads que não produziram por um período significativo de tempo. A capacidade de conectar threads CLR a threads SQL Server implica que o agendador de SQL Server pode identificar threads "descontrolados" no CLR e gerenciar sua prioridade. Tais threads fugitivos são suspensos e devolvidos à fila. Os threads que são identificados repetidamente como fugitivos não recebem permissão de execução por um determinado período de tempo, para que outros trabalhos possam ser executados.

Há algumas situações em que o código gerenciado de execução longa produzirá automaticamente e algumas situações em que ele não irá. Nas seguintes situações, o código gerenciado de execução longa produzirá automaticamente:

  • Se o código chamar o SISTEMA OPERACIONAL SQL (para consultar dados, por exemplo)
  • Se memória suficiente for alocada para disparar a coleta de lixo
  • Se o código entrar no modo preemptivo chamando funções do sistema operacional

O código que não faz nenhuma das opções acima, por exemplo, loops rígidos que contêm apenas computação, não produzirá automaticamente o agendador, o que pode levar a longas esperas por outras cargas de trabalho no sistema. Nessas situações, cabe ao desenvolvedor ceder explicitamente chamando a função System.Thread.Sleep() do .NET Framework ou entrando explicitamente no modo preemtivo com System.Thread.BeginThreadAffinity(), em todas as seções de código previstas para execução prolongada. Os exemplos de código a seguir mostram como gerar manualmente usando cada um desses métodos.

// 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();
Escalabilidade: Gerenciamento de memória comum

O CLR chama SQL Server primitivos para alocar e desalocar sua memória. Como a memória usada pelo CLR é contabilizado no uso total de memória do sistema, SQL Server pode permanecer dentro de seus limites de memória configurados e garantir que o CLR e SQL Server não estejam competindo entre si pela memória. SQL Server também pode rejeitar solicitações de memória CLR quando a memória do sistema é restrita e pedir ao CLR para reduzir o uso de memória quando outras tarefas precisarem de memória.

Confiabilidade: Domínios de aplicativo e exceções irrecuperáveis

Quando o código gerenciado nas APIs do .NET Framework encontra exceções críticas, como memória insuficiente ou estouro da pilha, nem sempre é possível recuperar-se de tais falhas e garantir a semântica consistente e correta para sua implementação. Estas APIs geram uma exceção de anulação de thread em resposta a essas falhas.

Quando hospedadas em SQL Server, essas anulações de thread são tratadas da seguinte maneira: o CLR detecta qualquer estado compartilhado no domínio do aplicativo no qual ocorre a anulação do thread. O CLR detecta isso verificando a presença de objetos de sincronização. Se houver um estado compartilhado no domínio do aplicativo, então o próprio domínio do aplicativo será descarregado. A descarga do domínio do aplicativo para transações de banco de dados que estão em execução no momento, naquele domínio de aplicativo. Como a presença de estado compartilhado pode ampliar o impacto dessas exceções críticas para sessões de usuário diferentes daquela que dispara a exceção, SQL Server e o CLR tomaram medidas para reduzir a probabilidade de estado compartilhado. Para obter mais informações, consulte a documentação do .NET Framework.

Segurança: conjuntos de permissões

SQL Server permite que os usuários especifiquem os requisitos de confiabilidade e segurança para o código implantado no banco de dados. Quando os assemblies são carregados no banco de dados, o autor do assembly pode especificar um dos três conjuntos de permissões para esse assembly: SAFE, EXTERNAL_ACCESS e UNSAFE.

Funcionalidade SAFE EXTERNAL_ACCESS UNSAFE
Segurança de Acesso do Código Somente execução Execução + acesso a recursos externos Irrestrito
Restrições do modelo de programação Sim Sim Sem restrições
Requisito de verificabilidade Sim Sim No
Capacidade de chamar código nativo No No Sim

SAFE é o modo mais confiável e seguro, com restrições associadas ao modelo de programação permitido. Assemblies SAFE recebem permissão suficiente para executar, realizar cálculos e ter acesso ao banco de dados local. Assemblies SAFE precisam ser seguros do tipo verificável e não têm permissão para chamar código não gerenciado.

UNSAFE é para código altamente confiável que pode ser criado somente por administradores de bancos de dados. Este código confiável não tem nenhuma restrição de segurança de acesso a código e pode chamar código não gerenciado (nativo).

EXTERNAL_ACCESS fornece uma opção de segurança intermediária, permitindo que o código acesse recursos externos ao banco de dados, mas ainda tenha as garantias de confiabilidade de SAFE.

SQL Server usa a camada de política CAS no nível do host para configurar uma política de host que concede um dos três conjuntos de permissões com base no conjunto de permissões armazenado em catálogos SQL Server. Código gerenciado em execução dentro do banco de dados sempre obtém um desses conjuntos de permissão de acesso de código.

Restrições do Modelo de Programação

O modelo de programação para código gerenciado em SQL Server envolve escrever funções, procedimentos e tipos que normalmente não exigem o uso do estado mantido em várias invocações ou o compartilhamento de estado em várias sessões de usuário. Além disso, conforme descrito anteriormente, a presença do estado compartilhado pode causar exceções críticas que afetam a escalabilidade e a confiabilidade do aplicativo.

Considerando essas considerações, desencorajamos o uso de variáveis estáticas e membros de dados estáticos de classes usadas em SQL Server. Para assemblies SAFE e EXTERNAL_ACCESS, SQL Server examina os metadados do assembly no momento CREATE ASSEMBLY e falha na criação desses assemblies se encontrar o uso de membros e variáveis de dados estáticos.

SQL Server também não permite chamadas para APIs .NET Framework anotadas com os atributos de proteção de host SharedState, Synchronization e ExternalProcessMgmt. Isso impede que assemblies SAFE e EXTERNAL_ACCESS chamem as APIs que habilitam o estado de compartilhamento, executem a sincronização e afetem a integridade do processo de SQL Server. Para obter mais informações, consulte Restrições de modelo de programação de integração CLR.

Consulte Também

Segurança da integração CLR
Desempenho da integração CLR