Trabalho de Sistemas Operacionais Prof. Fernando Luís Dotti IMPLEMENTAÇÃO DE UMA NOVA CHAMADA DE SISTEMA NO LINUX Alunos: Andrio Spich Hugo A. W. Schmitt Introdução A proposta deste trabalho de sistemas operacionais foi trabalhar com um sistema operacional real, no caso, o Linux. A idéia foi de aprender sobre o funcionamento interno do Linux, para que depois pudéssemos alterar o seu código fonte de modo a adicionar uma chamada de sistema simples e colocá-la em funcionamento. As chamadas de sistema são o modo padrão para que um programa de usuário possa utilizar os serviços do Sistema Operacional. Ambiente para o trabalho Nos laboratórios de rede possuímos acesso em modo de superusuário ao Linux, no caso a distribuição Kurumin. Infelizmente estes não possuíam o código fonte do sistema, necessário para a recompilação do mesmo, e o laboratório de rede não possui acesso à Internet para que pudéssemos realizar o download do mesmo. A solução encontrada foi trabalharmos em casa. Utilizamos a distribuição Red Hat 9, com versão do kernel 2.4.20-8, arquitetura i386 (PC). Recompilando o kernel Antes mesmo de implementarmos a chamada, preferimos recompilar o kernel sem nenhuma modificação para nos assegurarmos de que quaisquer problemas que apareçam durante ao trabalho sejam relacionados realmente ao trabalho. O kernel do linux (quando estiver instalado) se encontra no diretório /usr/src. Utilizando a ferramenta de montagem make, efetuamos a compilação do kernel nas seguintes etapas: ¾ make clean para remover quaisquer arquivos .o antigos. ¾ make config para kernel.(Poderíamos configurar executar as opções para o nosso make menuconfig e realizar a configuração toda (ou xconfig para um modo gráfico), mas como já possuímos um arquivo de configuração que funcionava, utilizamos oldconfig para que se realize a configuração a partir do arquivo .config. ¾ make dep para verificar as dependências corretamente. Aqui começa a compilação propriamente dita: ¾ make bzImage Temos agora a imagem do kernel nova – bzImage – no diretório ./arch/i386/boot/ Para acabarmos a instalação devemos compilar os módulos e instalá-los: ¾ make modules ¾ make modules_install Por segurança, iremos deixar duas versões do kernel no sistema, a antiga e a nova, uma vez que uma compilação bem sucedida não significa que o kernel irá realmente funcionar. Copiamos a imagem do kernel da pasta local para /boot/ (devemos estar em modo superusuário) ¾ cp ./arch/i386/boot/bzImage /boot Neste caso, a nova imagem se chama bzImage No gerenciador de boot criaremos uma nova entrada correspondente ao kernel recompilado; se utilizamos o Grub devemos alterar o arquivo /boot/grub/menu.lst de modo que teremos algo como: #boot=/dev/hda default=2 timeout=10 splashimage=(hd0,1)/grub/splash.xpm.gz title Linux (2.4.20-8) root (hd0,1) kernel /vmlinuz-2.4.20-8 ro root=LABEL=/ hdc=ide-scsi initrd /initrd-2.4.20-8.img title Testing linux (new system call) root(hd0,1) kernel /bzImage ro root=/dev/hda3 Basta reiniciar agora o computador: ¾ reboot Preparando para uma nova chamada de sistema As chamadas de sistema são chamadas indiretamente via interrupções. Temos uma tabela de interrupções que será acessada pelos processos de usuário para que encontrem a interrupção desejada. Temos que inserir uma nova entrada nesta tabela que se encontra no arquivo linux/arch/i386/kernel/entry.S. O nosso arquivo ficou assim: .long SYMBOL_NAME(sys_ni_syscall) /* 250 sys_alloc_hugepages */ .long SYMBOL_NAME(sys_ni_syscall) /* sys_free_hugepages */ .long SYMBOL_NAME(sys_exit_group) .long SYMBOL_NAME(sys_lookup_dcookie) .long SYMBOL_NAME(sys_userproc) /* Nossa syscall */ .long SYMBOL_NAME(sys_ni_syscall) /* 255 sys_epoll_ctl */ .long SYMBOL_NAME(sys_ni_syscall) /* sys_epoll_wait */ .long SYMBOL_NAME(sys_ni_syscall) /* sys_remap_file_pages */ Normalmente se insere uma nova chamada de sistema na última posição, mas nós inserimos perto do fim, pois ali haviam algumas posições inutilizadas. Também temos que alterar o arquivo linux/include/asm-i386/unistd.h: #define __NR_io_submit 248 #define __NR_io_cancel 249 #define __NR_alloc_hugepages 250 #define __NR_free_hugepages 251 #define __NR_exit_group 252 #define __NR_lookup_dcookie 253 #define __NR_userproc 254 #define __NR_set_tid_address 258 Inserindo a nova chamada de sistema Há várias maneiras de criarmos a chamada de sistema; nós optamos pelo modo mais simples e utilizamos apenas um arquivo userproc.c que colocamos, arbitrariamente, na pasta kernel. Devemos então modificar a Makefile desta pasta para que o código seja compilado e linkado corretamente. A modificação está em negrito: #Começo do arquivo O_TARGET := kernel.o export-objs = signal.o sys.o kmod.o context.o ksyms.o pm.o exec_domain.o printk.o cpufreq.o profile.o obj-y = sched.o dma.o fork.o exec_domain.o panic.o printk.o lowlat.o profile.o module.o exit.o itimer.o info.o time.o softirq.o resource.o sysctl.o acct.o capability.o ptrace.o timer.o user.o signal.o sys.o kmod.o context.o kksymoops.o futex.o pid.o userproc.o (...) O formato de uma chamada de sistema é o seguinte ( userproc.c ): #include #include #include #include #include <linux/unistd.h> <linux/linkage.h> <linux/errno.h> <linux/sched.h> <linux/kernel.h> asmlinkage int sys_userproc(int user_uid) { struct task_struct *pts; (1) //for_each_process(pts) for (pts = &init_task ; (pts = next_task(pts)) != &init_task; ) (2) { if ( pts-> uid == user_uid){ printk("User %d eh dono do processo %d\n",pts->uid,pts->pid); (3) } } return 0; } A biblioteca linkage.h é necessário para que o compilador reconheça a palavra “asmlinkage”. Esta por sua vez é necessária para que as chamadas de sistema sejam linkadas corretamente. Também deve-se colocar “sys_” na frente do header da função. Esta nossa chamada de sistema simplesmente lista os processos de um usuário: - recebe como argumento um inteiro user_id, um ID de usuário( Por exemplo, o root tem UID = 0); - para cada processo existente, se o UID relacionado ao processo for igual a user_id, escreve na tela uma mensagem; ¾ Em (1), declara-se um ponteiro do tipo task_struct que iremos utilizar para acessarmos as estruturas que mantém os processos; ¾ Em (2), temos um for. Observa-se que o endereço init_task nos dá tanto o valor inicial como o limite final do loop. Isto acontece pois todas as tasks do sistema estão relacionadas em uma lista circular duplamente encadeada. Logo o limite final em init_task nós impede de começarmos uma segunda volta e passarmos mais uma vez por todos os processos.(Pts = next_task(pts) seta o ponteiro pts para a próxima posição da lista encadeada) ¾ Em (3): se o UID do processo é o mesmo da entrada, escrevemos, usando printk, uma mensagem para o console. Testando a chamada de sistema Mostramos ainda um exemplo de como usar a chamada de sistema, pois do jeito que a implementamos o código fonte necessita de uma adição ( testeuserproc.c ): #include #include #include #include <linux/unistd.h> <errno.h> //syscalls podem retornar 'error codes' <stdio.h> <stdlib.h> //atoi _syscall1(long, userproc, int, user_uid); main(int argc, char ** argv) { if (argc != 2) { printf("lista os processos de alguem\n"); printf("sintaxe: %s uid\n", argv[0]); exit(1); } my_syscall(atoi(argv[1])); } A única linha interessante aqui é: _syscall1(long, userproc, int, user_uid); Necessitamos desta macro para que um programa de usuário possa usar a chamada de sistema. Nota-se que o número 1 em _syscall1 se refere ao número de argumentos que a função recebe, e há uma limitação máxima de 6 elementos. Esta macro na verdade gera um código assembler que chama a interrupção 80h com a chamada de sistema requerida e seus parâmetros nos registradores. Conclusão Neste trabalho pudemos trabalhar na prática com um sistema operacional moderno e popular. O Linux nunca foi um sistema operacional com “fins educativos” e no seu código as várias partes em programação assembly mostram que o objetivo é sempre o maior desempenho e não clareza ou elegância. Também notamos que a teoria dos livros sobre Linux de alguns anos atrás só vale para os princípios, pois a implementação já está muito alterada e só se consegue aprender na prática, com base nos comentários nos próprios fontes.