Compartilhar via


Arquitetura de integração clr – ambiente hospedado por CLR

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

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 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 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 CIL (linguagem intermediária comum) 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. 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 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 pode 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 é CIL 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 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 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 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 do 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 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 por APIs (interfaces de programação) de aplicativo de chamada de código do usuário para threading, memória e primitivos de sincronização diretamente.

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 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 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 arquitetura de integração clr.

Serviços CLR

O CLR fornece vários serviços para ajudar a alcançar as metas de design da integração 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. No entanto, se o código acessar a memória em deslocamentos arbitrários dentro ou fora do intervalo de memória que pertence ao objeto, ele não será type-safe. Quando os assemblies são carregados no CLR, antes do CIL ser compilado usando a compilação just-in-time (JIT), o runtime executa uma fase de verificação que examina o código para determinar sua segurança de tipo. 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 expor operações em recursos protegidos por uma permissão de acesso de código, a API exigirá 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.

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 cas para execução e, portanto, falharia em tempo de execução. Para obter mais informações, consulte clr integration Code Access Security .

Atributos de proteção de 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áticos).

  • Synchronization, 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 de host.

Considerando esses atributos, o host pode especificar uma lista de HPAs, como o atributo SharedState, que deve ser desautorizado 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 funcionam 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 funciona 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. 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 em que estão aguardando são conhecidos pelo agendador do SQL Server, o SQL Server pode agendar threads que executam 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 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 "descontrolados" no CLR e gerenciar sua prioridade. Tais threads fugitivos são suspensos e devolvidos à fila. Threads identificados repetidamente como threads descontrolados não têm permissão para serem executados por um determinado período de tempo para que outros trabalhos em execução possam ser executados.

Há algumas situações em que o código gerenciado de execução longa produz automaticamente e algumas situações em que ele não o faz. Nas seguintes situações, o código gerenciado de execução longa produz 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 dessas ações, como loops apertados que contêm apenas computação, não gera 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 preemptivo com System.Thread.BeginThreadAffinity(), em qualquer seção de código que esteja prevista para ser de execução prolongada. Os exemplos de código a seguir mostram como produzir manualmente usando cada um desses métodos.

Exemplos

Produzir manualmente para o agendador 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);
  }
}

Usar ThreadAffinity para ser executado preventivamente

Neste exemplo, o código CLR é executado no modo preemptivo em BeginThreadAffinity e 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();

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 é contabilizado 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 é 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 falta de memória ou estouro de pilha, nem sempre é possível se recuperar dessas 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, 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 efeito dessas exceções críticas para 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 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_ACCESSe UNSAFE.

Funcionalidade SAFE EXTERNAL_ACCESS UNSAFE
Code Access Security Somente execução Execução + acesso a recursos externos Irrestrito
Programming model restrictions Sim Sim Sem restrições
Verifiability requirement Sim Sim No
Ability to call native code No No Sim

SAFE é o modo mais confiável e seguro com restrições associadas em termos do modelo de programação permitido. SAFE assemblies recebem permissão suficiente para executar, executar cálculos e ter acesso ao banco de dados local. SAFE assemblies devem ser verificados como seguros 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. 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 tendo 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 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 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 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 em CREATE ASSEMBLY momento 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. Isso impede que SAFE e EXTERNAL_ACCESS assemblies chamem qualquer 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 de modelo de programação de integração clr.