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

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

A integração do SQL Server com o CLR (Common Language Runtime) do .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 como 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 o 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 do 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 pelo 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

O SQL Server e o CLR têm modelos internos diferentes para agendamento e gerenciamento de memória. O SQL Server oferece suporte a um modelo de threading cooperativo e não preemptivo no qual os threads voluntariamente produzem a execução 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 no SQL Server puder chamar diretamente os primitivos de threading do sistema operacional, ele não se integrará bem ao agendador de tarefas do SQL Server e poderá degradar a escalabilidade do sistema. O CLR não distingue entre memória virtual e física, mas o SQL Server gerencia diretamente a memória física e é necessário para usar a 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 as regras de autenticação e autorização do SQL Server 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 se torna importante à medida que as linguagens de programação gerenciadas (ao contrário das linguagens não gerenciadas, como Transact-SQL) fornecem APIs para acessar esses recursos. O sistema deve fornecer uma maneira segura para o código do usuário acessar os recursos do computador fora do processo do Mecanismo de Banco de Dados. Para obter mais informações, consulte CLR Integration Security.

Desempenho

O código de usuário gerenciado em execução no Mecanismo de Banco de Dados deve ter desempenho computacional comparável ao mesmo código executado fora do servidor. O acesso ao banco de dados a partir do código de 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 vários serviços para ajudar a atingir as metas de design da integração do CLR com o 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 o 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 não há suporte para a capacidade de gerar código gerenciado dinamicamente, usando a API Reflection.Emit, dentro do ambiente hospedado pelo 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 o 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. O SQL Server atua essencialmente como o sistema operacional para o CLR quando ele é hospedado no SQL Server. O CLR chama rotinas de baixo nível implementadas pelo SQL Server para threading, agendamento, sincronização e gerenciamento de memória. Essas rotinas são as mesmas primitivas que o restante do mecanismo do 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 APIs do SQL Server 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 objetos de sincronização do SQL Server. Essa prática permite que o agendador do SQL Server agende outras tarefas quando um thread está aguardando 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 que eles estão aguardando são conhecidos pelo agendador do SQL Server, o 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 o SQL Server detecte deadlocks que envolvem bloqueios obtidos por objetos de sincronização CLR e empregue técnicas tradicionais para remoção de deadlock.

O código gerenciado é executado preventivamente no SQL Server. O agendador do SQL Server tem a capacidade de detectar e interromper threads que não foram gerados por um período significativo de tempo. A capacidade de conectar threads CLR a threads do SQL Server implica que o agendador do 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 longa execução será renderizado automaticamente e algumas situações em que não. Nas seguintes situações, o código gerenciado de longa duração será gerado 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 apertados 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 inserindo explicitamente o modo preemptivo com System.Thread.BeginThreadThreadAffinity(), em todas as seções de código que devem ser de longa execução. Os exemplos de código a seguir mostram como produzir 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 primitivos do SQL Server para alocar e desalocar sua memória. Como a memória usada pelo CLR é contabilizada no uso total de memória do sistema, o SQL Server pode permanecer dentro de seus limites de memória configurados e garantir que o CLR e o SQL Server não estejam competindo entre si por memória. O SQL Server também pode rejeitar solicitações de memória CLR quando a memória do sistema é restrita e solicitar que o CLR reduza seu 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 no 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 de 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 em sessões de usuário diferentes daquela que dispara a exceção, 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

O 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.

O 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 nos catálogos do 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 no SQL Server envolve a gravação de funções, procedimentos e tipos que normalmente não exigem o uso de 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.

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

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

Confira também

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