Trabalho de Sistemas Operacionais Prof. Fernando Luís Dotti

Propaganda
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.
Download