Introdução aos Sistemas Operacionais/Exemplo de técnica de Debug: Debug com proc

Dados gerais

editar

Estudo de caso baseado em um driver de dispositivo que conta quantas vezes um processo tenta utilizar um arquivo de dispositivo. Código fonte utilizado no estudo de caso está disponível em [3].

Autoria

editar

João Paulo Ozório
Leandro Pereira Freitas
Paulo César Ferreira Melo

Licenciamento

editar

GPL

Créditos

editar

Objetivo e descrição

editar

O módulo escolhido pelo grupo foi um módulo que após ser inserido na tabela de processos do linux (diretório /proc), conta o número de vezes que algum processo ou arquivo tenta abrir o arquivo de dispositivo (driver). O driver colocará no arquivo o número de vezes que esse arquivo foi lido.

Debug usando Sistema de Arquivos /proc:

O /proc é basicamente um sistema de arquivos. Este diretório virtual é mantido pelo kernel e disponibiliza uma grande quantidade de informações com relação aos processos que estão rodando. Embora seja virtual e não represente nenhum dispositivo físico, ele pode ser montado e desmontado , e a cada vez que o seu Linux é reiniciado, um novo /proc é criado.

Dito de outra forma, os "arquivos" que você vê sob / proc não são realmente "arquivos", em vez disso, os seus conteúdos são tipicamente gerados dinamicamente sempre que você acessá-los. Logo, fica fácil debugar os módulos visto que o arquivo não é real, seu "conteúdo" é gerado por um código que implementa esse arquivo que é acessado (No caso do nosso módulo, gera-se o número de vezes que o arquivo foi lido), e por questões de fácil acesso: a qualquer momento, podemos listar os processos do \proc e examiná-los.

O que fizemos foi:

Criar um proc. ou seja, declarar: proc_create(DEVICE_NAME, 0, NULL, &fops). Tal declaração cria o arquivo proc com o nome de "chardev", o argumento "0" representa a permissão de arquivo padrão de 0444, o "NULL" significa que o arquivo deve ser criado sob o diretório /proc e o argumento final indica a estrutura de operações necessárias para associar o arquivo. Em outras palavras, a criação de arquivos proc e sua associação com a chamada a rotinas de entrada e saida é feito em uma única chamada.

Remover o arquivo proc: remove_proc_entry(DEVICE_NAME, NULL). No nosso caso, remove o arquivo /proc/chardev.

Código-fonte

editar
/*
 *  chardev.c:  Dispositivo que conta o número de vezes que algum processo ou arquivo 
 *				tenta acessar o arquivo de dispositivo (driver).
 */
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <asm/uaccess.h>	/* for put_user */

/*  
 *  Protótipos
 */
/*funcao que e executada ao inserir o modulo*/
int init_module(void);

/*funcao que e executada ao remover o modulo*/
void cleanup_module(void);
static int device_open(struct inode *, struct file *);
static int device_release(struct inode *, struct file *);
static ssize_t device_read(struct file *, char *, size_t, loff_t *);
static ssize_t device_write(struct file *, const char *, size_t, loff_t *);

#define SUCCESS 0 /*constante de retorno*/

#define DEVICE_NAME "chardev"	/* nome do dispositivo que vai ser colocado em /proc/devices   */
#define BUF_LEN 80		/*tamanho maximo da mensagem de saida*/

/* 
 *Variáveis globais são declaradas como estáticas, dessa forma serão globais dentro do arquivo.
 */

static int Major;		/* Numero maximo de acessos ao dispositivo */

static int Device_Open = 0;	/* Is device open?  
				 * Used to prevent multiple access to device */

static char msg[BUF_LEN];	/* The msg the device will give when asked */

static char *msg_Ptr;  /*mensagem de saida*/

/*define qual funcao vai realizar qual atividade*/
static struct file_operations fops = {
	.read = device_read,
	.write = device_write,
	.open = device_open,
	.release = device_release
};


/*
 * Esta funcao é chamada quando o modulo e carregado
 */
int init_module(void)
{
	/*cria o dispositivo*/
    Major = register_chrdev(0, DEVICE_NAME, &fops);

 	/* cria um arquivo de debug em no diretorio proc*/
	proc_create(DEVICE_NAME, 0, NULL, &fops);

	/*verifica a ocorrencia de algum erro na criacao do dispositivo*/
	if (Major < 0) {
	  printk(KERN_ALERT "Registering char device failed with %d\n", Major);
	  return Major;
	}


/*imprime mensagens de utilizacao do dispositivo*/
	printk(KERN_INFO "I was assigned major number %d. To talk to\n", Major);
	printk(KERN_INFO "the driver, create a dev file with\n");
	printk(KERN_INFO "'mknod /dev/%s c %d 0'.\n", DEVICE_NAME, Major);
	printk(KERN_INFO "Try various minor numbers. Try to cat and echo to\n");
	printk(KERN_INFO "the device file.\n");
	printk(KERN_INFO "Remove the device file and module when done.\n");

	return SUCCESS;
}

/*
 * Esta funcao e chamada quando o modulo e removido
 */
void cleanup_module(void)
{
	/*remove o arrquivo de debug do diretorio proc*/
	remove_proc_entry(DEVICE_NAME, NULL);

	/* 
	 * remove o dispositivo
	*/
	unregister_chrdev(Major, DEVICE_NAME);
}

/* 
 * Chamada quando o processo tenta abrir o arquivo do dispositivo como:
 * "cat /dev/mycharfile"
 */
static int device_open(struct inode *inode, struct file *file)
{	
	/*numero acessos o dispositivo*/
	static int counter = 0;

	/*verifica se ja tem alguem acessando o dispositivo*/
	if (Device_Open)
		return -EBUSY; /*retorna antes de completar, apenas um processo pode utilizar o dispositivo de cada vez*/

	/*bloqueia o acesso ao dispositivo por outros processos*/
	Device_Open++;

	/*cria a mensagem de saida e incrementa a qtde de utilizacoes*/
	sprintf(msg, "I already told you %d times Hello world!\n", counter++);

	/*faz ponteiro da msg de saida apontar para a msg criada*/
	msg_Ptr = msg;

	
	try_module_get(THIS_MODULE);


	return SUCCESS;

}

/* 
  * Chamada quando um processo fecha o arquivo.
 */
static int device_release(struct inode *inode, struct file *file)
{	
	/*libera o acesso ao dispositivo (cmo se fosse um semaforo mutex)*/
	Device_Open--;		/* We're now ready for our next caller */

	/* 
	 * Decrement the usage count, or else once you opened the file, you'll
	 * never get get rid of the module. 
	 */
	module_put(THIS_MODULE);

	return 0;
}

/* 
 * Chamada quando um processo tenta ler o arquivo aberto
 */
static ssize_t device_read(struct file *filp,	/* see include/linux/fs.h   */
			   char *buffer,	/* buffer de entrada */
			   size_t length,	/* tamanho do buffer */
			   loff_t * offset)
{
	/*
	 * Numero de bytes lidos do buffer 
	 */
	int bytes_read = 0;

	/*
	 * Se está no fim da mensagem
	 * return 0 significa fim do arquivo
	 */
	if (*msg_Ptr == 0)
		return 0;


	/* 
	 * Coloca os dados do buffer de entrada no de saída
	 */
	while (length && *msg_Ptr) {

		/* 
		 * The buffer is in the user data segment, not the kernel 
		 * segment so "*" assignment won't work.  We have to use 
		 * put_user which copies data from the kernel data segment to
		 * the user data segment. 
		 */
		put_user(*(msg_Ptr++), buffer++);

		length--;
		bytes_read++;
	}

	/* 
	 * retorna o numero de bytes lidos do buffer
	 */
	return bytes_read;
}

/*  
 * Chamada quando um processo tenta escrever no arquivo: echo "hi" > /dev/hello 
 */
static ssize_t
device_write(struct file *filp, const char *buff, size_t len, loff_t * off)
{	
	/*imprime uma msg de erro, pois este dispositio nao realiza operacoes de escrita, apenas leitura*/
	printk(KERN_ALERT "Sorry, this operation isn't supported.\n");
	return -EINVAL;
}

Procedimentos para testes

editar

Para executar o programa siga os passos à seguir:

  1. Compile o código utilizando o comando make
  2. Insira o módulo no sistema
  3. Crie um dispositivo no diretório /dev com o comando mknod /dev/chardev c 251 0
  4. Agora tente ler o arquivo deste dispositivo com o comando cat /dev/chardev, ou escrever algo nele echo "aaa" > /dev/chardev

Entradas de debug foram adicionadas em /proc/chardev

Tentativas:

Tentar criar uma rotina: (int *chardev_show(struct seq_file *m, void *v)) para imprimir na saída padrão quando nosso arquivo fosse listado através de uma chamada, exemplo: cat /proc/chardev, usando a chamada ao método: seq_printf(struct seq_file *, "%d\n", HZ). Onde HZ é a taxa de frequencia do timer. .

Usar a chamada ao método single_open(struct file *file,int *chardev_show ,void *data), a qual é uma interface que mostra como resultado no arquivo virtual o produzido pela função chardev_show.


Obs.: Código fonte do Makefile que deve ser utilizado

obj-m += chardev.o
all:
<tab>make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
<tab>make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

Considerações finais

editar

Inicialmente, aprendemos o funcionamento do módulo hello.c, o qual apenas mostrava na tela uma mensagem de boas vindas quando era iniciado e uma mensagem de despedida quando era finalizado. Após isso, veio a escolha pelo módulo "chardev" e o entendimento de seu funcionamento e os possíveis melhoramentos a serem realizados no código. A escolha do /proc como debug veio do fato do dinamismo ao acesso, visto que como este sempre gera novos resultados ao compilar módulos, poderíamos testar a persistência do nosso driver.

Referências

editar

[1] Linux Device Drivers, Third Edition: http://lwn.net/Kernel/LDD3/
[2] Linux.com: http://www.linux.com/learn/linux-training/39972-kernel-debugging-with-proc-qsequenceq-files-part-2-of-3
[3] The Linux Kernel Module Programming Guide: http://tldp.org/LDP/lkmpg/2.6/html/index.html