Do ponto de vista do kernel, um processo é uma entrada na tabela de processos. Nada mais. A tabela de processos, então, é uma das mais importantes estruturas de dados no sistema, conjuntamente com a tabela de gerenciamento de memória e o buffer cache.
O item individual na tabela de processos é a estrutura task_struct, definida em include/linux/sched.h.
Com a task_struct, tanto informações de baixo quanto de alto nível, são mantidas – variando da cópia de alguns registradores de hardware até o inode do diretório de trabalho para o processo.
A tabela de processos é tanto um array quanto uma lista duplamente ligada, como uma árvore. A implementação física é um array estático de ponteiros, cujo tamanho é NR_TASKS, uma constante definida em include/linux/tasks.h, e cada estrutura reside em uma pagina de memória reservada.
A estrutura da lista está entre os ponteiros next_task e prev_task, a estrutura em arvore é um tanto complexa, e não será descrita aqui. Voce pode desejar mudar NR_TASKS do seu valor default (que é 128), mas esteja certo de que há dependências, e será necessário recompilar todos os arquivos fonte envolvidos.
Depois do boot, o kernel está sempre trabalhando em um dos processos, e a variável global "current", um ponteiro para um item da task_struct, é usado para guardar o processo que está rodando.
A variável "current" só é mudada pelo scheduler, em kernel/sched.c. Quando, porém, todos os processos necessitarem estar looked, a macro for_each_task é usada.
Isto é consideravelmente mais rápido que uma procura seqüencial no array. Um processo está sempre rodando em ou em "modo usuário" ou em "modo kernel".
O corpo principal de um programa de usuário é executado em modo usuário e chamadas a sistema são executados em modo kernel.
A pilha usada pelos processos netes dois modos de execução são diferentes -um seguimento de pilha convencional é usado para o modo usuário, enquanto uma pilha de tamanho fixo (uma página, cujo processo é dono) é usada no modo kernel.
A página de pilha para o modo kernel nunca é swapped out, porque ela pode estar disponível sempre que um system call é introduzido.
Chamadas a sistema (System calls), no kernel do Linux, são como funções da linguagem C, seu nome "oficial" esta prefixado por "sys_".
Uma chamada a sistema de nome, por exemplo, burnout invoca a função de kernel sys_burnout(). mais o mecanismo de chamadas a sistema (System calls) está descrito no capítulo 3 do Linux Kernel Hackers' Guide (http://ftp.utcluj.ro/pub/docs/ldp/bible-src/khg-0.6/khg/khg.html).
Uma olhada em for_each_task e SET_LINKS, em include/linux/sched.h pode ajudar a entender a lista e a estrutura de árvore da tabela de processos.
3.3 – Criando e destruindo processos
Um sistema UNIX cria um processo através da chamada a sistema fork(), e o seu término é executado por exit(). A implementação do Linux para eles reside em kernel/fork.c e kernel/exit.c.
Executar o "Forking" é fácil, fork.c é curto e de fácil leitura. Sua principal tarefa é suprir a estrutura de dados para o novo processo. Passos relevantes nesse processo são:
• Criar uma página livre para dar suporte à task_struct
• Encontrar um process slot livre (find_empty_process())
• Criar uma outra página livre para o kernel_stack_page
• Copiar a LTD do processo pai para o processo filho
• Duplicar o mmap (Memory map – memoria virtual) do processo pai sys_fork() também gerencia descritores de arquivos e inodes.
A versão 1.0 do kernel possui algum vestígio de suporte ao "threading" (trabalho ou processo em paralelo), e a chamada a sistema fork() apresenta algumas alusões à ele.
A morte de um processo é difícil, porque o processo pai necessita ser notificado sobre qualquer filhos que existam (ou deixem de existir).
Além disso, um processo pode ser morto (kill()) por outro processo (isto é um aspecto do UNIX). O arquivo exit.c é, portanto, a casa do sys_kill() e de variados aspectos de sys_wait(), em acréscimo à sys_exit().
O código pertencente à exit.c não é descrito aqui – ele não é tão interessante. Ele trabalha com uma quantidade de detalhes para manter o sistema em um estado consistente.
O POSIX "standard", por conseguinte, é dependente de sinais (flags), e tinha que trabalhar com eles.
3.4 – Executando Processos