Guia de rastreamento dinâmico Solaris

Capítulo 40 Tradutores

No Capítulo 39Estabilidade, aprendemos sobre como o DTrace computa e relata os atributos de estabilidade do programa. Idealmente, gostaríamos de construir nossos programas do DTrace consumindo apenas as interfaces Estável ou Desenvolvendo. Infelizmente, ao depurar um problema de baixo nível ou medir o desempenho do sistema, talvez seja necessário ativar testes que estejam associados a rotinas internas do sistema operacional, tais como as funções do kernel, em vez de testes associados a interfaces mais estáveis, tais como as chamadas do sistema. Os dados disponíveis nos locais de teste profundos na pilha do software geralmente são uma coleção de artefatos de implementação em vez de estruturas de dados mais estáveis, tais como aquelas associadas às interfaces de chamada do sistema do Solaris. Para ajudá-lo a escrever programas em D estáveis, o DTrace fornece um recurso para traduzir os artefatos de implementação em estruturas de dados estáveis acessíveis a partir das declarações do seu programa em D.

Declarações do tradutor

Um tradutor é uma coleção de declarações de atribuições em D oferecidas pelo fornecedor de uma interface que pode ser usada para traduzir uma expressão de entrada em um objeto do tipo struct. Para entender a necessidade e o uso de tradutores, consideraremos como um exemplo as rotinas da biblioteca padrão ANSI-C definidas em stdio.h. Essas rotinas operam em estruturas de dados chamadas FILE, cujos artefatos de implementação são abstraídos dos programadores de C. Uma técnica padrão para criar uma abstração de estrutura de dados é fornecer somente uma declaração de reenvio de uma estrutura de dados em arquivos de cabeçalho públicos, enquanto mantém a definição de struct correspondente em um arquivo de cabeçalho privado separado.

Se você estiver escrevendo um programa em C e deseja saber o descritor do arquivo correspondente a uma struct FILE, use a função fileno(3C) para obter o descritor em vez de cancelar diretamente a referência a um membro da struct FILE . Os arquivo de cabeçalho do Solaris reforçam essa regra, definindo a struct FILE como uma marca de declaração de reenvio opaca para que ela não tenha a sua referência cancelada diretamente pelos programas em C que incluem <stdio.h>. Dentro da biblioteca libc.so.1, imagine que fileno() é implementado em C, da seguinte forma:

int
fileno(FILE *fp)
{
	struct file_impl *ip = (struct file_impl *)fp;

	return (ip->fd);
}

Nossa fileno() hipotética usa um ponteiro FILE como um argumento e o intercala com um ponteiro para uma estrutura libc interna correspondente, struct file_impl, em seguida, retorna o valor do membro fd da estrutura da implementação. Por que o Solaris implementa interfaces dessa forma? Abstraindo os detalhes da implementação libc atual dos programas clientes, a Sun é capaz de manter um compromisso com uma compatibilidade binária forte enquanto continua a desenvolver e alterar os detalhes da implementação interna de libc. Em nosso exemplo, o membro fd poderia mudar de tamanho ou de posição na struct file_impl, mesmo em um patch, e os binários existentes que chamam fileno(3C) não seriam afetados por essa mudança porque eles não dependem desses artefatos.

Infelizmente, um software de observação como o DTrace tem a necessidade de analisar a implementação para fornecer resultados úteis, e não tem o luxo de chamar funções arbitrárias de C definidas nas bibliotecas ou no kernel do Solaris. Você poderia declarar uma cópia de struct file_impl em seu programa em D para instrumentar as rotinas declaradas em stdio.h, mas, então, o seu programa em D confiaria em artefatos de implementação Privada da biblioteca que talvez quebrem em uma versão futura micro ou secundária, ou mesmo em um patch. Idealmente, desejamos fornecer uma construção a ser usada em programas em D que seja vinculada à implementação da biblioteca e seja atualizada de acordo, mas que ainda forneça uma camada adicional de abstração associada a uma estabilidade maior.

Um novo tradutor é criado através da uma declaração no formato:

translator output-type < input-type input-identifier > {
	member-name = expression ;
	member-name = expression ;
	...
};	

O tipo de saída nomeia uma struct que será o tipo de resultado da tradução. O tipo de entrada especifica o tipo da expressão de entrada, e é colocado entre colchetes angulares < > e seguido por um identificador de entrada que pode ser usado nas expressões do tradutor como um alias da expressão de entrada. O corpo do tradutor é colocado entre chaves { } e terminado com um ponto-e-vírgula (;), e consiste em uma lista de nomes de membro e identificadores correspondentes às expressões de tradução. Cada declaração de membro deve nomear um membro exclusivo do tipo de saída e deve ser ter atribuída uma expressão de um tipo compatível ao tipo do membro, de acordo com as regras do operador (=) da atribuição de D.

Por exemplo, poderíamos definir uma struct de informações estáveis sobre os arquivos stdio com base em algumas interfaces libc disponíveis:

struct file_info {
	int file_fd;   /* file descriptor from fileno(3C) */
	int file_eof;  /* eof flag from feof(3C) */
};

Um tradutor de D hipotético de FILE para file_info poderia ser declarado em D da seguinte forma:

translator struct file_info < FILE *F > {
	file_fd = ((struct file_impl *)F)->fd;
	file_eof = ((struct file_impl *)F)->eof;
};

Em nosso tradutor hipotético, a expressão de entrada é do tipo FILE * e tem atribuído o identificador de entrada F. O identificador F pode ser usado nas expressões do membro do tradutor como uma variável do tipo FILE * que só esteja visível no corpo da declaração do tradutor. Para determinar o valor do membro file_fd da saída, o tradutor realiza uma transmissão e cancela a referência semelhante à implementação hipotética de fileno(3C), mostrada acima. Uma tradução semelhante é realizada para obter o valor do indicador EOF.

A Sun fornece um conjunto de tradutores a serem usados com as interfaces do Solaris que você chama a partir dos seus programas em D, e promete manter esses tradutores de acordo com as regras de estabilidade da interface definidas anteriormente quando a implementação da interface correspondente for alterada. Aprenderemos sobre esses tradutores mais tarde neste capítulo, depois que aprendermos como chamar os tradutores a partir de D. O recurso do tradutor em si também é fornecido para ser usado pelos desenvolvedores do aplicativo e da biblioteca que desejam oferecer seus próprios tradutores para que os programadores de D possam usar a fim de observar o estado de seus pacotes de software.

Operador Translate

O operador xlate de D é usado para realizar uma tradução de uma expressão de entrada para uma das estruturas de saída de tradução definidas. O operador xlate é usado em uma expressão no formato:

xlate < output-type > ( input-expression )

Por exemplo, para chamar o tradutor hipotético das structs FILE definidas acima e acessar o membro file_fd, você escreveria a expressão:

xlate <struct file_info *>(f)->file_fd;

onde f é uma variável de D do tipo FILE *. À própria expressão xlate é atribuído o tipo definido pelo tipo de saída. Ao ser definido, um tradutor pode ser usado para traduzir expressões de entrada para o tipo de struct da saída do tradutor, ou para um ponteiro para essa struct.

Se você traduzir uma expressão de entrada para uma struct, poderá cancelar a referência de um membro específico da saída imediatamente usando operador “. ”, ou pode atribuir a struct traduzida inteira para outra variável de D a fim de fazer uma cópia dos valores de todos os membros. Se você cancelar a referência de um único membro, o compilador de D irá gerar somente o código correspondente à expressão desse membro. Você não pode aplicar o operador & a uma struct traduzida para obter seu endereço, já que o próprio objeto de dados não existe até que seja copiado ou que um dos seus membros seja referenciado.

Se você traduzir uma expressão de entrada em um ponteiro para uma struct, poderá cancelar a referência de um membro específico da saída imediatamente usando operador ->, ou poderá cancelar a referência do ponteiro usando o operador unário *, nesse caso, o resultado se comportará como se você traduzisse a expressão em uma struct. Se você cancelar a referência de um único membro, o compilador de D irá gerar somente o código correspondente à expressão desse membro. Você não pode atribuir um ponteiro traduzido para outra variável de D, já que o próprio objeto de dados não existe até que seja copiado ou um dos seus membros seja referenciado e, portanto, não pode ser endereçado.

Uma declaração do tradutor não pode omitir expressões de um ou mais membros do tipo de saída. Se uma expressão xlate for usada para acessar um membro para o qual não haja expressão de tradução definida, o compilador de D produzirá uma mensagem de erro apropriada e anulará a compilação do programa. Se o tipo de saída inteiro for copiado através de uma atribuição de estrutura, quaisquer membros para os quais não há expressões de tradução definidas serão preenchidos com zeros.

Para localizar um tradutor correspondente em uma operação xlate, o compilador de D examina o conjunto de tradutores disponíveis na seguinte ordem:

Se nenhum tradutor correspondente for encontrado de acordo com essas regras, o compilador de D produzirá uma mensagem de erro apropriada e a compilação do programa falhará.

Tradutores de modelo do processo

O arquivo de bibliotecas do DTrace /usr/lib/dtrace/procfs.dfornece um conjunto de tradutores a serem usados em seus programas em D para traduzir as estruturas de implementação do kernel do sistema operacional em processos e segmentos para as estruturas estáveis psinfo e lwpsinfo de proc(4). Essas estruturas também são usadas nos arquivos do sistema de arquivos do Solaris /proc /proc/pid/psinfo e /proc/pid/lwps/lwpid/lwpsinfo e são definidas no arquivo de cabeçalho /usr/include/sys/procfs.h. Essas estruturas definem informações sobre Estável úteis sobre processos e segmentos, tais como o ID, o LWP ID do processo, os argumentos iniciais e outros dados exibidos pelo comando ps(1). Consulte proc(4) para obter uma descrição completa dos membros e semânticas de struct.

Tabela 40–1 Tradutores procfs.d

Tipo de entrada 

Atributos de tipo de entrada 

Tipo de saída 

Atributos de tipo de saída 

proc_t *

Privado/Privado/Comum 

psinfo_t *

Estável/Estável/Comum 

kthread_t *

Privado/Privado/Comum 

lwpsinfo_t *

Estável/Estável/Comum 

Traduções de Estável

Embora um tradutor forneça a habilidade de converter informações em uma estrutura de dados estável, ele não necessariamente resolve todos os problemas de estabilidade que podem surgir na tradução de dados. Por exemplo, se a expressão de entrada em uma operação xlate fizer referência a dados de Instável, o programa em D resultante também será Instável porque a estabilidade do programa sempre é calculada como a estabilidade mínima das declarações e expressões acumuladas dos programas em D. Portanto, às vezes é necessário definir uma expressão de entrada estável específica em um tradutor a fim de permitir que sejam construídos programas estáveis. O mecanismo in-line de D pode ser usado para facilitar essas traduções estáveis.

A biblioteca procfs.d do DTrace fornece as variáveis curlwpsinfo e curpsinfo descritas anteriormente como traduções estáveis. Por exemplo, a variável curlwpsinfo é realmente uma inline declarada da seguinte forma:

inline lwpsinfo_t *curlwpsinfo = xlate <lwpsinfo_t *> (curthread);
#pragma D attributes Stable/Stable/Common curlwpsinfo

A variável curlwpsinfo é definida como uma tradução in-line da variável curthread, um ponteiro para a estrutura de dados Privada do kernel que representa um segmento, para o tipo Estável lwpsinfo_t. O compilador de D processa este arquivo de biblioteca e armazena em cache a declaração inline, fazendo com que curlwpsinfo apareça como qualquer outra variável de D. A instrução #pragma que segue a declaração é usada para redefinir explicitamente os atributos do identificador curlwpsinfo como Estável/Estável/Comum, mascarando a referência a curthread na expressão in-line. Esta combinação de recursos de D permite que os programadores de D usem curthread como a origem de uma tradução de uma forma segura, que pode ser atualizada pela Sun, coincidente com as alterações correspondentes na implementação do Solaris.