Partilhar via


Arquitetura de integração CLR - Ambiente hospedado CLR

Aplica-se a:SQL ServerAzure SQL Managed Instance

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 de negócios que os programadores podem escrever com essas linguagens.

O CLR apresenta memória coletada por lixo, threading preventivo, serviços de metadados (reflexão de tipo), verificabilidade de código e segurança de acesso ao código. O CLR usa metadados para localizar e carregar classes, dispor instâncias na memória, resolver invocações de método, gerar código nativo, impor 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 são gerenciados uniformemente. Este artigo também aborda a maneira como a segurança de acesso ao código CLR (CAS) 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 de Arquitetura CLR

No .NET Framework, um programador escreve em uma linguagem de alto nível que implementa uma classe definindo sua estrutura (por exemplo, os campos ou propriedades da classe) e 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 linguagem intermediária da Microsoft (MSIL) 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 do código do aplicativo no .NET Framework. Usando assemblies, você pode implantar o código do aplicativo dentro do banco de dados e fornecer uma maneira uniforme de administrar, fazer backup e restaurar aplicativos de banco de dados completos.

O manifesto do assembly contém metadados sobre o assembly, descrevendo todas as estruturas, campos, propriedades, classes, relações 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 implementação do assembly, especifica os tipos e recursos que compõem o assembly, discrimina as dependências em tempo de compilação em outros assemblies e especifica o conjunto de permissões necessárias para que o assembly seja executado corretamente. Essas informações são usadas em tempo de execução para resolver referências, impor a política de vinculação de versão e validar a integridade dos assemblies carregados.

O .NET Framework oferece suporte a atributos personalizados para anotar classes, propriedades, funções e métodos com informações adicionais que o aplicativo pode capturar em metadados. Todos os compiladores do .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. Os aplicativos de código gerenciado adquirem serviços CLR, como coleta automática de lixo, verificação de tipo em tempo de execução e suporte de segurança. Esses serviços ajudam a fornecer um comportamento uniforme independente de plataforma e linguagem de aplicativos de código gerenciado.

Objetivos de design da integração 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:

Fiabilidade (Segurança)

O código do usuário não deve ter permissão para executar operações que comprometam a integridade do processo do Mecanismo de Banco de Dados, como abrir uma caixa de mensagem solicitando uma resposta do usuário ou sair do processo. O código do usuário não deve ser capaz de substituir buffers de memória do Mecanismo de Banco de Dados ou estruturas de dados internas.

Escalabilidade

O SQL Server e o CLR têm modelos internos diferentes para agendamento e gerenciamento de memória. O SQL Server dá suporte a um modelo de threading cooperativo e não preventivo no qual os threads produzem voluntariamente a execução periodicamente ou quando estão aguardando bloqueios ou E/S. O CLR suporta um modelo de threading preventivo. Se o código de usuário em execução dentro do SQL Server puder chamar diretamente as primitivas 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 usar memória física dentro de um limite configurável.

Os diferentes modelos de threading, agendamento e gerenciamento de memória apresentam um desafio de integração para um sistema de gerenciamento de banco de dados relacional (RDBMS) que é dimensionado para suportar 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 do usuário chamando interfaces de programação de aplicativos (APIs) para threading, memória e primitivas de sincronização diretamente.

Segurança

O código de 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 banco de dados devem ser capazes de controlar o acesso aos recursos do sistema operacional, como arquivos e acesso à rede, a partir do código do usuário em execução no banco de dados. Essa prática se torna importante à medida que linguagens de programação gerenciadas (ao contrário de 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 recursos da máquina fora do processo do Mecanismo de Banco de Dados. Para obter mais informações, consulte de Segurança de Integração CLR .

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 Performance of CLR Integration.

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 do tipo

Código tipo-seguro é o código que acessa estruturas de memória apenas de maneiras bem definidas. Por exemplo, dada uma referência de objeto válida, o código type-safe pode acessar a memória em deslocamentos fixos correspondentes aos membros reais do campo. No entanto, se o código acessa a memória em deslocamentos arbitrários dentro ou fora do intervalo de memória que pertence ao objeto, então ele não é seguro para digitação. Quando os assemblies são carregados no CLR, antes do MSIL ser compilado usando a compilação just-in-time (JIT), o tempo de execução executa uma fase de verificação que examina o código para determinar sua segurança de tipo. O código que passa com êxito nessa verificação é chamado de código seguro de tipo verificável.

Domínios de aplicação

O CLR suporta a 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 a capacidade de chamar código dinamicamente. Os domínios de aplicação também são o mecanismo para carregar e descarregar código. O código pode ser descarregado da memória somente descarregando o domínio do aplicativo. Para obter mais informações, consulte Application Domains e CLR Integration Security.

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

O sistema de segurança CLR fornece uma maneira de controlar que tipos de operações o código gerenciado pode executar 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 diretiva para todo o computador que pode ser definida pelo administrador do computador. Esta 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 diretiva 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 expõe operações em recursos protegidos por uma permissão de acesso a código, a API exigirá essa permissão antes de acessar o recurso. Essa demanda faz com que o sistema de segurança CLR acione uma verificação abrangente de cada unidade de código (assembly) na pilha de chamadas. O acesso ao recurso só será concedido 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 é suportada dentro do ambiente hospedado pelo CLR no SQL Server. Esse código não teria as permissões CAS para ser executado e, portanto, falharia em tempo de execução. Para obter mais informações, consulte de Segurança de Acesso ao Código de Integração CLR .

Atributos de proteção do host (HPAs)

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

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

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

  • ExternalProcessMgmt, que indica se a API expõe uma maneira de controlar o processo do host.

Considerando esses atributos, o host pode especificar uma lista de HPAs, como o atributo SharedState, que não devem ser permitidos no ambiente hospedado. Nesse caso, o CLR nega tentativas por código de usuário de chamar APIs que são anotadas pelos HPAs na lista proibida. Para obter mais informações, consulte Host Protection Attributes e CLR Integration Programming.

Como o SQL Server e o CLR funcionam juntos

Esta seção discute como o SQL Server integra os modelos de gerenciamento de threading, agendamento, sincronização e memória do SQL Server e do CLR. Em particular, esta seção examina a integração à luz das metas de escalabilidade, confiabilidade e segurança. O SQL Server atua essencialmente como o sistema operacional para o CLR quando ele é hospedado dentro do 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. Essa abordagem oferece 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 código de 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 em um objeto de sincronização. Por exemplo, quando o CLR inicia a coleta de lixo, todos os seus threads aguardam a conclusão da coleta de lixo. Como os threads CLR e os objetos de sincronização em que 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 detete 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 no SQL Server. O agendador do SQL Server tem a capacidade de detetar e parar threads que não renderam 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 "fugitivos" no CLR e gerenciar sua prioridade. Esses fios fugitivos são suspensos e colocados de volta na fila. Threads que são repetidamente identificados como threads fugitivos não podem ser executados por um determinado período de tempo para que outros trabalhadores em execução possam ser executados.

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

  • Se o código chamar o sistema operacional SQL (para consultar dados, por exemplo)
  • Se houver memória suficiente alocada para acionar a coleta de lixo
  • Se o código entrar no modo de preempção chamando funções do SO

O código que não faz nada do 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 produzir explicitamente chamando a função System.Thread.Sleep() do .NET Framework ou entrando explicitamente no modo preemptivo com System.Thread.BeginThreadAffinity(), em qualquer seção de código que se preveja 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 comum de memória

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 pela memória. O SQL Server também pode rejeitar solicitações de memória CLR quando a memória do sistema está restrita e pedir ao CLR para reduzir seu uso de memória quando outras tarefas precisarem de memória.

Fiabilidade: Domínios de aplicação e exceções irrecuperáveis

Quando o código gerenciado nas APIs do .NET Framework encontra exceções críticas, como falta de memória ou estouro de pilha, nem sempre é possível recuperar de tais falhas e garantir semânticas consistentes e corretas para sua implementação. Essas 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 deteta qualquer estado compartilhado no domínio do aplicativo no qual ocorre a interrupção de thread. O CLR deteta isso verificando a presença de objetos de sincronização. Se houver um estado compartilhado no domínio do aplicativo, o próprio domínio do aplicativo será descarregado. O descarregamento do domínio do aplicativo interrompe as transações de banco de dados que estão atualmente em execução nesse domínio do aplicativo. Como a presença de estado compartilhado pode ampliar o impacto de tais exceções críticas para sessões de usuário diferentes daquela que aciona 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 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 SEGURO EXTERNAL_ACCESS INSEGURO
Segurança de acesso ao código Executar apenas Executar + acesso a recursos externos Sem restrições
Restrições do modelo de programação Sim Sim Sem restrições
Requisito de verificabilidade Sim Sim Não
Capacidade de chamar código nativo Não Não Sim

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

UNSAFE é para código altamente confiável que só pode ser criado por administradores de banco de dados. Esse código confiável não tem restrições de segurança de acesso ao 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 tendo as garantias de confiabilidade do 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. O código gerenciado em execução dentro do banco de dados sempre obtém um desses conjuntos de permissões de acesso ao código.

Restrições do modelo de programação

O modelo de programação para código gerenciado no SQL Server envolve a escrita 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 de 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 dados estáticos membros de classes usadas no SQL Server. Para assemblies SAFE e EXTERNAL_ACCESS, o SQL Server examina os metadados do assembly no tempo CREATE ASSEMBLY e falha na criação desses assemblies se encontrar o uso de membros e variáveis de dados estáticos.

O SQL Server também não permite chamadas para APIs do .NET Framework que são anotadas com os atributos de proteção de host SharedState, Synchronizatione ExternalProcessMgmt proteção. Isso impede que assemblies SAFE e EXTERNAL_ACCESS chamem quaisquer APIs que permitam o estado de compartilhamento, executem a sincronização e afetem a integridade do processo do SQL Server. Para obter mais informações, consulte CLR Integration Programming Model Restrictions.

Ver também

de Segurança de Integração CLR
Desempenho do de Integração CLR