Guia de rastreamento dinâmico Solaris

Uniões

As uniões são outra espécie de tipo composto aceito por ANSI-C e D, e estão fortemente relacionadas às structs. Uma união é um tipo composto onde um conjunto de membros de tipos diferentes são definidos e todos os objetos de membro ocupam a mesma região de armazenamento. Uma união é, portanto, um objeto de tipo variado, onde somente um membro é válido a qualquer momento determinado, dependendo de como a união foi atribuída. Geralmente, alguma outra variável ou parte de estado é usada para indicar que o membro da união é válido no momento. O tamanho de uma união é o tamanho do maior membro, e o alinhamento de memória usado para a união é o alinhamento máximo necessário para os membros da união.

A estrutura kstat do Solaris define uma struct contendo uma união que é usada no exemplo seguinte para ilustrar e observar as uniões de C e de D. A estrutura kstat é usada para exportar um conjunto de contadores nomeados representando as estatísticas do kernel, tal como o uso da memória e o throughput de E/S. A estrutura é usada para implementar utilitários como mpstat(1M) e iostat(1M) Esta estrutura usa struct kstat_named para representar um contador nomeado e seu valor, e é definido da seguinte forma:

struct kstat_named {
	char name[KSTAT_STRLEN]; /* name of counter */
	uchar_t data_type;	/* data type */
	union {
		char c[16];
		int32_t i32;
		uint32_t ui32;
		long l;
		ulong_t ul;
		...
	} value;	/* value of counter */
};	

A declaração de inspeção é abreviada para fins ilustrativos. A definição de estrutura completa pode ser encontrada no arquivo de cabeçalho <sys/kstat.h> e é descrita em kstat_named(9S). A declaração acima é válida em ANSI-C e em D, e define uma struct contendo, como um de seus membros, um valor de união com membros de vários tipos, dependendo do tipo do contador. Observe que, já que a própria união é declarada dentro de outro tipo, struct kstat_named, um nome formal do tipo de união é omitido. Este estilo de declaração é conhecido como união anônima. O membro nomeado valor é de um tipo de união descrito pela declaração precedente, mas esse tipo de união não tem nome porque ele não precisa ser usado em nenhum outro local. É atribuído ao membro struct data_type um valor que indica qual membro da união é válido para cada objeto do tipo struct kstat_named. Um conjunto de símbolos de pré-processadores de C são definidos para os valores de data_type. Por exemplo, o símbolo KSTAT_DATA_CHAR é igual a zero e indica que o membro value.c se encontra onde o valor está armazenado no momento.

O Exemplo 7–3 demonstra o acesso à união kstat_named.value rastreando um processo do usuário. Os contadores kstat podem ser exemplificados a partir de um processo do usuário por meio da função kstat_data_lookup(3KSTAT), que retorna um ponteiro para struct kstat_named. O utilitário mpstat(1M) chama esta função repetidamente enquanto é executado, a fim de exemplificar os valores de contador mais recentes. Vá para o seu shell e tente executar mpstat 1 e observe a saída. Pressione Control-C em seu shell para anular mpstat após alguns segundos. Para observar a amostra do contador, gostaríamos de ativar um teste que seja acionado cada vez que o comando mpstat chamar a função kstat_data_lookup(3KSTAT) em libkstat. Para fazê-lo, usaremos um novo provedor do DTrace: pid. O provedor pid permite que você crie os testes dinamicamente nos processos do usuário em locais de símbolo de C, tais como pontos de entrada de função. Você pode pedir ao provedor pid para criar um teste em uma entrada de função do usuário e retornar os sites, escrevendo as descrições do teste no formato:

pidID do processo:nome do objeto:nome da função:entry pidID do processo:nome do objeto:nome da função:return

Por exemplo, se você quisesse criar um teste no ID do processo 3 que é acionado na entrada para kstat_data_lookup(3KSTAT), você escreveria a seguinte descrição de teste:

pid12345:libkstat:kstat_data_lookup:entry

O provedor pid insere a instrumentação dinâmica no processo do usuário especificado no local do programa correspondente à descrição do teste. A implementação do teste força cada segmento do usuário que alcança o local do programa instrumentado a entrar no kernel do sistema operacional e no DTrace, acionando o teste correspondente. Sendo assim, embora o local da instrumentação esteja associado a um processo do usuário, os predicados e as ações do DTrace que você especificar ainda serão executados no contexto do kernel do sistema operacional. O provedor pid é descrito em maiores detalhes no Capítulo 30Provedor pid.

Em vez de ter que editar o fonte do programa em D toda vez que desejar aplicar seu programa a um processo diferente, você pode inserir identificadores chamados variáveis de macro em seu programa que são avaliados no momento em que o programa é compilado e substituído pelos argumentos da linha de comando adicionais do dtrace. As variáveis de macro são especificadas por meio do sinal de dólar $ seguido por um identificador ou dígito. Se você executar o comando dtrace -s script foo bar baz, o compilador de D definirá automaticamente as variáveis de macro $1, $2 e $3 aos símbolos foo, bar e baz, respectivamente. Você pode usar as variáveis de macro nas expressões de programa em D ou em descrições de teste. Por exemplo, as descrições de teste seguintes instrumentam qualquer ID de processo que seja especificado como um argumento adicional para dtrace:

pid$1:libkstat:kstat_data_lookup:entry
{
	self->ksname = arg1;
}

pid$1:libkstat:kstat_data_lookup:return
/self->ksname != NULL && arg1 != NULL/
{
	this->ksp = (kstat_named_t *)copyin(arg1, sizeof (kstat_named_t));
	printf("%s has ui64 value %u\n", copyinstr(self->ksname),
	    this->ksp->value.ui64);
}

pid$1:libkstat:kstat_data_lookup:return
/self->ksname != NULL && arg1 == NULL/
{
	self->ksname = NULL;
}

As variáveis de macro e os scripts reutilizáveis são descritos em maiores detalhes no Capítulo 15Script. Agora que sabemos como instrumentar os processos do usuário usando o ID de processo deles, vamos retornar às uniões de amostra. Vá para o editor e digite o código-fonte do nosso exemplo completo e salve-o em um arquivo chamado kstat.d:


Exemplo 7–3 kstat.d: rastrear chamadas para kstat_data_lookup(3KSTAT )

pid$1:libkstat:kstat_data_lookup:entry
{
	self->ksname = arg1;
}

pid$1:libkstat:kstat_data_lookup:return
/self->ksname != NULL && arg1 != NULL/
{
	this->ksp = (kstat_named_t *) copyin(arg1, sizeof (kstat_named_t));
	printf("%s has ui64 value %u\n",
	    copyinstr(self->ksname), this->ksp->value.ui64);
}

pid$1:libkstat:kstat_data_lookup:return
/self->ksname != NULL && arg1 == NULL/
{
	self->ksname = NULL;
}

Agora vá para um dos seus shells e execute o comando mpstat 1 para iniciar o mpstat(1M) sendo executado em um modo em que ele faz amostra de estatísticas e informa-as uma por segundo. Quando o mpstat estiver em execução, execute o comando dtrace -q -s kstat.d `pgrep mpstat` em seu outro shell. Você verá a saída correspondente às estatísticas que estão sendo acessadas. Pressione Control-C para anular dtrace e retornar para o prompt do shell.


# dtrace -q -s kstat.d `pgrep mpstat`
cpu_ticks_idle has ui64 value 41154176
cpu_ticks_user has ui64 value 1137
cpu_ticks_kernel has ui64 value 12310
cpu_ticks_wait has ui64 value 903
hat_fault has ui64 value 0
as_fault has ui64 value 48053
maj_fault has ui64 value 1144
xcalls has ui64 value 123832170
intr has ui64 value 165264090
intrthread has ui64 value 124094974
pswitch has ui64 value 840625
inv_swtch has ui64 value 1484
cpumigrate has ui64 value 36284
mutex_adenters has ui64 value 35574
rw_rdfails has ui64 value 2
rw_wrfails has ui64 value 2
...
^C
#

Se capturar a saída em cada janela do terminal e subtrair cada valor do valor informado pela iteração anterior através das estatísticas, você deve ser capaz de correlacionar a saída do dtrace com a saída do mpstat. O programa de exemplo registra o ponteiro do nome do contador na entrada para a função de consulta e, em seguida, realiza a maior parte do trabalho de rastreio no retorno de kstat_data_lookup(3KSTAT). As funções incorporadas de D copyinstr() e copyin() copiam os resultados da função do processo do usuário para o DTrace quando arg1 (o valor de retorno) não é NULL. Quando os dados de kstat tiverem sido copiados, o exemplo informará o valor do contador ui64 a partir da união. Este exemplo simplificado assume que mpstat faz amostras de contadores que usam o membro value.ui64. Como um exercício, tente registrar kstat.d para usar vários predicados e imprimir a união correspondente ao membro data_type. Você também pode tentar criar uma versão de kstat.d que calcule a diferença entre valores de dados sucessivos e produza realmente uma saída semelhante a mpstat.