Process Management baseado na versão 2.6 do kernel do Linux PESC / COPPE / UFRJ Sistemas Operacionais Prof. Vitor Santos Costa, Ph.D. Jorge Luiz Silva Peixoto [email protected] jorgepeixoto@{cos.ufrj.br, gmail.com} Process Management 1 Introdução e Motivação O que é um processo? Um programa em execução. É composto de: Estado Sinais pendentes Arquivos abertos Kernel data Espaço de endereçamento Threads Pilha PC Process Management Registradores 2 Introdução e Motivação Ciclo de vida do processo: Nasce: fork() ou exec() Reclicla: wait() Slab allocator Cresce: faz_algo() Reproduz (assexuadamente): exec() ou fork() Morre: exit() ou Sinal Process Management 3 Programação Descritor de Processos Estados de Processos Contexto de Processo vs de Sistema Espaço de Usuário vs de Sistema Hierarquia de Processos Criação de Processos Copy-on-Write fork() copy_process() Process Management vfork() Threads Threads – Implementação Kernel Threads Finalização de Processos do_exit() Remoção do Descritor de Processos release_task() Processos Órfãos 4 Descritor de Processos Em Linux: task = process O kernel mantém os processos numa lista circular duplamente encadeada chamada task list. Cada elemento da lista é um process descriptor do tipo struct task_struct Process Management 5 Descritor de Processos struct task_struct é alocado dinamicamente via slab allocator que provê reuso de objetos e cache coloring. [cap. 11] Antes do kernel 2.6, task_struct era alocado estaticamente no final da pilha de kernel de cada processo, dessa forma era possível calcular a localização da estrutura através do ponteira da pilha. O objetivo era economizar registradores. Atualmente, um ponteiro é armazenado em struct thread_info que fica localizado na final da pilha. Process Management 6 Descritor de Processos PID identifica unicamente um processo no sistema. PID é do tipo pid_t (tipicamente um int). Por questões de compatibilidade o valor máximo é 32.768 (short int). O administrador por alterar esse valor em /proc/sys/kernel/pid_max Processos são tipicamente referenciados por um ponteiro para seu task_struct, conseqüentemente, é interessante que o acesso seja rápido (implementado pelo macro current). Sua implementação é dependente da arquitetura. No x86, o endereço da pilha de kernel do processo é usado calcular o endereço de thread_info que contém task_struct. No PowerPC, o valor é armazenado diretamente num registrador. Process Management 7 Estados de Processos TASK_RUNNING: processo ou está rodando, ou está na fila esperando para rodar. TASK_INTERRUPTIBLE: processo está dormindo (bloqueado) esperando por algum recurso. Muda para TASK_RUNNING, se for liberado o recurso ou receber um sinal. TASK_UNINTERRUPTIBLE: idêntico ao anterior, exceto que o processo não acorda se receber um sinal. TASK_ZOMBIE: o processo finalizou, mas seu pai ainda não chamou a system call wait(). TASK_STOPPED: a execução do processo está congelada; o processo não está executando nem é executável. Process Management 8 Contexto de Processo vs de Sistema Espaço de Usuário vs de Sistema Programas “normais” executam em process context e em user mode. Quando um programa “normal” chama uma syscall ou dispara uma exceção, ele entra em kernel mode. Em system context, o kernel não está representando um processo, mas executando um interrupt handler. Process Management 9 Hierarquia de Processos Todos os processos são filhos de init (PID 1). Na inicialização, o último passo do kernel é chamar o init que chama o script inittab que conseqüentemente chama outros programas. Todo processo tem apenas um pai, mas um pai pode ter zero ou mais processos filhos. O relacionamento entre processos está registrado no descritor de processos. É possível seguir na hierarquia de processos de qualquer processo para qualquer outro. Process Management 10 Criação de Processos A maioria dos sistemas operacionais usa um mecanismo de spawn para criar um novo processo a partir de um outro executável. No Unix, são usadas duas funções distintas: fork() e exec(). fork() cria um processo filho idêntico ao pai, exceto pelo PID, PPID, e alguns recursos, como: estatísticas do processo e sinais pendentes. exec()carrega e executa um novo programa. Outros SOs: fork() + if((result = fork()) == 0) { /* child code */ if(execve("new_program", ...) < 0) perror {"execve failed"); exit (1); } else if(result < 0) { perror("fork"); /* fork failed */ ) /* parent continues here */ exec() Process Management 11 Copy-on-Write Como alternativa a significante ineficiência do fork(), no Linux, o fork() é implementado usando uma técnica chamada copy-on-write (COW). Essa técnica atrasa ou evita a cópia dos dados. Ao invés de copiar o espaço de endereçamento do processo pai, ambos podem compartilha uma única cópia somente leitura. Se uma escrita é feita, uma duplicação é feita e cada processo recebe uma cópia. Conseqüentemente, a duplicação é feita apenas quando necessário, economizando tempo e espaço. O único overhead realmente necessário do fork() é a duplicação da tabela de páginas do processo pai e a criação de um novo PID para o filho. Process Management 12 fork() O Linux implementa fork() através da syscall clone() que recebe como entrada várias flags que especificam que recursos devem ser compartilhados. fork() chama do_fork() que chama copy_process(), onde é feito a maior parte do trabalho. Process Management 13 copy_process() Chama dup_task_struct() que cria uma nova pilha de kernel, as estruturas thread_info e task_struct. Os valores são iguais ao do processo pai. Checa se o novo filho não irá exceder os limites de recursos do usuário. Vários campos do descritor do processo são zerados ou atribuídos valores iniciais. Dados estatísticos do processo não são herdados. A parte principal dos dados do descritor do processo é compartilhada. Ao estado do processo filho é atribuído TASK_UNINTERRUPTIBLE. copy_process() chama copy_flags() para atualizar os flags de task_struct. Chama get_pid() para atribuir o novo PID do processo filho. Dependendo dos flags passado à syscall clone(), copy_process() ou duplica ou compartilha arquivos abertos, informações de sistema de arquivo, signal handlers, espaço de endereçamento e namespace. Esses recursos são tipicamente compartilhados entre threads. Então, o restante de time slice é dividido entre o processo pai e o filho. Finalmente, copy_process() retorna para um ponteiro para o novo processo filho. Process Management 14 vfork() Mesmo efeito do fork(), exceto por não copiar a tabela de páginas do processo pai. Filho executa diretamente no espaço de endereçamento do pai. Pai fica bloqueado até o filho chamar exec() ou sair. Não é permitido ao filho escrever no espaço de endereçamento do processo pai. Otimização sugerida nos tempos de 3BSD. Hoje, com o copy-on-write, o único benefício é a não cópia da tabela de páginas do processo pai. Process Management 15 Threads Em Linux, threads = processos Thread é meramente um processo que compartilha recursos com outros processos. Abordagem diferente do Microsoft Windows e do Sun Solaris que explicitamente têm suporte do kernel para threads (lightweight processes). Exemplo: em Solares: existem 2 processos que consistem de 3 e 2 threads cada. Existirá um descritor de processos que aponta para cada conjunto de threads descrevendo os recursos compartilhados, como, o espaço de endereçamento e arquivos aberto. Cada thread então descreve os recursos que cada uma possui. Em Linux, simplesmente existe 5 processos e 5 estruturas task_struct. Os processos estão configurados para compartilhar certos recursos. Process Management 16 Threads – Implementação Criados pela syscall clone(). Exemplo de chamada: clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0); Essa chamada criará uma thread que compartilha o espaço de endereçamento, recursos dos sistema de arquivos, descritores de arquivos e signal handlers. Os flags passados para a syscall clone() descrevem o comportamento do processo filho e detalha os recursos compartilhados. Outros exemplo: fork() = clone(SIGHLD, 0); vfork() = clone(CLONE_VFORK | CLONE_VM | SIGHLD, 0); Process Management 17 Kernel Threads Kernel threads são processos que rodam apenas no espaço do kernel, não há mudança de contexto para o espaço de usuário. Kernel threads não possuem espaço de endereçamento (ponteiro para mm é NULL). São preemptivas e escalonáveis como qualquer outro processo. São criadas por apenas outras kernel threads. Assim como os processos normais, são criados através da syscall clone() com o uso de flags especiais. Exemplos: pdflush, ksoftirqd, nfsd (equivalente). Process Management 18 Finalização de Processos O kernel libera os recursos e notifica o processo pai. A finalização do processo pode ocorrer: – Voluntariamente e explicitamente, através da chamada a syscall exit(); – Voluntariamente e implicitamente, com o retorno da função main() de qualquer programa; – Involuntariamente, quando o processo recebe um sinal ou quando ocorre um exceção que não pode tratar ou ignorar. Process Management 19 do_exit() Ativa o flag PF_EXITING em task_struct. Invoca del_timer_sync() para remover qualquer timer de kernel. Após o retorno, é garantido que nenhum timer estará enfileirado e nenhum timer handler estará rodando. Se BSD process accounting estiver ativo, chama acct_process(). Chama __exit_mm() para liberar mm_struct, se não estiver compartilhado, desaloca. Chama exit_sem() para liberar semáforos. Chama __exit_files(), __exit_fs(), exit_namespace(), and exit_sighand(). Atribuí o código de saída do processo (variável exit_code de task_struct) para posterior análise pelo processo pai. Chama exit_notify() e atribui o estado TASK_ZOMBIE. Chama schedule(). Process Management 20 Remoção do Descritor de Procesos Finalizada a syscall exit(), o processo ainda existe! Somente após chamar a syscall wait4(), o processo é liberado (descritor de processos é desalocado). Os únicos objetos associados ao processo são a sua pilha de kernel, thread_info e task_struct. O funcionamento padrão do wait() é suspender o processor chamador até que um filho finalize. Retorna o PID do filho. Process Management 21 release_task() Sua função é liberar o descritor de processos. Chama free_uid() para decrementar o contador de uso de processos do usuário. Unhash_process(), remove o processo da tabela hash de PID e remove o processo da task list. Se ptrace foi usado, repatriar o processo para o pai original e remove da ptrace list. Chama put_task_struct() para liberar as páginas contendo a pilha de kernel do processo e a estrutura thread_info e desalocar o slab cache contendo task_struct. Process Management 22 Processos Órfãos Reparent o processo órfão a um processo do grupo que pertence, se falha, ao processo init. Evitar o acumulo de processo zumbis, desperdiçando memória. Process Management 23 Revisão Processo é uma das abstrações fundamentais do Linux Relação entre processos e threads Como o processo é representado no Linux kernel: task_struct e thread_info Como é criado: fork() e clone() Como novos imagens de executáveis são carregados: exec() Como o pai coleta informações dos seus “falecidos” processos filhos: wait() Como um processo morre: exit() Process Management 24