O que é o kernel do Linux + exemplos de seu funcionamento



@Oxghh







Introdução





O que exatamente é um kernel?

O kernel, a grosso modo, é a "camada" que se encontra entre o hardware e as aplicações rodando em um computador.

Ele é responsável por atuar como uma interface à qual essas aplicações recorrem quando precisam utilizar os recursos do hardware. Dessa forma, o programador que cria essas aplicações não precisa se preocupar em escrever código diferente para cada hardware; basta ele recorrer ao kernel, de uma forma padronizada, e o kernel faz com que o hardware a execute.





E como isso se encaixa no Linux?

O termo 'Linux', originalmente, se refere apenas ao kernel - que Linus Torvalds escreveu no início dos anos 90, e atualmente é usado para nomear sistemas operacionais que usam esse kernel (porém, se você quiser soar pedante, pode falar GNU/Linux, que engloba tanto o kernel quanto algumas ferramentas que a FSF e o Projeto GNU escreveram e que vêm junto com a maioria dos sistemas operacionais Linux :D).

Existem diversas variantes do sistema operacional que usam o kernel Linux, e as variantes (distros) são iustradas na imagem a seguir:

Distros





Mas você deve estar se perguntando - como as aplicações usam a interface do kernel? Através de uma série de "chamadas de sistema" ou apenas "Syscalls".





Algumas Syscalls e quais componentes de hardware elas representam

O conjunto de Syscalls disponibilizadas pelo kernel é conhecido como "A máquina virtual Linux".

Quando chamada, uma Syscall executa operações com privilégios especiais, conhecidos como "kernel space", ou "espaço do kernel" em uma tradução literal. Já as operações normais, são executadas no "User space".





Além das funções representadas pelas Syscalls, o kernel oferece mais "abstrações", ou seja, "esconde as camadas de baixo", como componentes hardware, para que as aplicações de cima não tenham que de preocupar com elas, e para que exista compatibilidade. Um exemplo disso é o sistema de arquivos/diretórios, que é gerenciado por... Adivinhe... Syscalls.





Exemplos





Exemplo 1: Gerenciando arquivos

Do que se trata:

Um exemplo de um programa em C que usa as syscalls open(), read() e write() para abrir um arquivo e mostrar o seu conteúdo na tela.

Código:

Explicação:





Linha 1: Incluímos a biblioteca que contêm as funções das syscalls open(), read() e write();

Linha 2: Definimos que o máximo que pode ser lido do arquivo do exemplo será 1000 bytes;

Linha 4: Declaramos a função main(), que é o ponto inicial de execução da linguagem C;

Linha 5: Definimos uma array chamada buf (vem de buffer), cujo tamanho é 1000 bytes (MAX_LEN, definido na linha 2). Nela vai ser copiado o conteúdo do arquivo, para posteriormente ser mostrado na tela;

Linha 6: Definimos duas variáveis do tipo int (número INTeiro): fd (vem de file descriptor, um número único que identifica determinado arquivo no sistema e identificará o arquivo "pr1v8.txt"), e size, que vai conter o número de bytes do arquivo que será copiado para a array buf;

Linha 7: A primeira função usada que referencia uma syscall diretamente. A função open() recebe dois argumentos: o primeiro é o arquivo que queremos abrir, no caso "pr1v8.txt", e o segundo é as flags que usaremos. Nesse caso, queremos apenas ler o conteúdo do arquivo, então usamos a flag O_RDONLY (open read-only). A função open() retorna um file descriptor que é uma referência ao arquivo "pr1v8.txt". A partir de agora, pra tudo que formos fazer nesse arquivo, precisaremos usar esse file descriptor, que está sendo armazenado na variável fd.

Linha 8: Agora, usamos a função read() para copiar 1000 bytes (MAX_LEN) do arquivo representado pelo file descriptor fd para a array de tamanho 1000 bytes (MAX_LEN) chamada buf. A função read() retorna a quantidade, em bytes, que de fato foi lida, sendo armazenada em size.

Linha 9: Aqui, é usada a função write() para mostrar na tela o conteúdo armazenado na variável buf (que, por sua vez, contém o que foi lido do arquivo "pr1v8.txt"). A função write(), recebe três argumentos. O primeiro é o file descriptor do arquivo que vai ser escrito. Como queremos escrever na tela, usamos o file descriptor 1, que significa STDOUT (standart output, no nosso caso, a tela). O segundo é a origem do conteúdo a ser escrito. No nosso caso, a variável buf. Já o terceiro argumento, é o quanto vai ser escrito. Queremos que seja escrito exatamente a mesma quantidade de dados que foi lida, então usamos a variável size.

Linha 11: Retornamos o valor 0, que é o padrão quando o programa é executado sem erros.

Curiosidade: Se você digitar

# echo $?













no bash do Linux, vai ver o que o último programa terminado retornou.

Conclusão:

Não se preocupe com a sintaxe por enquanto, o que importa agora é o seguinte: Através dessas syscalls, o Kernel do Linux oferece a "ilusão" de um arquivo - que na realidade, é apenas um conjunto de bytes, e no fim, um conjunto de pequenos - e com pequenos, eu quero dizer microscópicos - fragmentos metálicos alterados por conta de fenômenos magnéticos em um disco rígido. O ponto é que, se não houvesse um kernel, o programa descrito acima seria infinitamente maior, visto que teríamos que nos preocupar com fatores tais como setores, cabeçalhos, offsets, etc para poder acessar o "conjunto de bytes" (arquivo).

Como você pode ver, o Kernel precisa trabalhar duro para manter essa mesma "ilusão" - ou "abstração", como preferir - independentemente se o hardware que de fato está armazenando o arquivo é um CD, um disco rídido, um cartão SD, um USB - Podendo ser até mesmo um sistema de acesso remoto, acessado por um protocolo como NFS.





Exemplo 2 - Gerenciando processos

Do que se trata:

Outro importantíssimo trabalho que o Kernel desempenha é o gerenciamento de processos e a divisão da CPU entre os diversos programas que estão rodando em um computador, a cada instante.

O kernel determina um tempo para cada processo usar a CPU, e passado esse tempo, ele coloca outro processo para usar os recursos de processamento da CPU. Isso é tão rápido, que temos a sensação que várias coisas estão rodando ao mesmo tempo, enquanto na verdade, cada uma está realizada por vez em curtos intervalos de tempo.

Aqui está mais um pequeno programa em C, que usa as syscalls fork() e wait() para gerenciar a execução.

Código:

Explicação:

Linha 1 & 2: Importamos as libs necessárias para usar as syscalls fork() / exit() e o printf(), respectivamente;

Linha 4 & 11: Declaramos a função main e retornamos. Descrito com mais detalhes no exemplo anterior;

Linha 5: fork() é uma função do C baseada na syscall fork, que cria um novo processo que é "filho"; Todo processo é identificado por dois atributos distintos: seu PID (process id, um número que identifica o processo) e o seu PPID (parent-process id, um número que identifica o processo que o originou); A função fork() cria um novo processo que é uma cópia do processo atual, e que executa o código a partir de onde o fork() foi chamado. A função fork() retorna o PID do processo filho, e como é um valor diferente de zero, é considerado pelo C como verdadeiro - e portanto, o processo-pai executa o código dentro do if. Já o processo-filho gerado pelo fork(), como começa a executar a partir desse fork(), não entra na condicional (afinal, se esse filho executasse o fork de novo, iria ficar se auto-replicando). Assim, o processo filho executa apenas a partir do else.

Curiosidade: Se você rodar o comando

# :(){ :|:& };:













No bash do Linux, em poucos segundos o seu computador vai travar. Isso ocorre pelo fato de que ele basicamente cria um processo que fica se auto-replicando exponencialmente. Isso é conhecido como fork bomb.

fork() bomb

Linha 7: Essa função faz com que o processo pai só continue a executar quando o processo filho já tiver retornado. Ou seja, ele vai ficar esperando o processo filho terminar.

Conclusão:

Funções elegantes e simples como fork(), wait() e exit() permitem usufruir de funções extremamente complexas do Kernel sem precisar se preocupar com os detalhes.

abstraction





Outro exemplo de abstração implementada pelo Kernel é o gerenciamento de memória. Todo programa age como se tivesse toda a memória disponível para sí, mas a realidade é muito mais complexa e conta com a segmentação de memória, arquivos de swap, entre outros.





O Kernel, ainda implementa protocolos como o IP, TCP e UDP,e é responsavel por gerenciar permissões, usuários, entre outros. Novamente, tudo é feito com base em ilusões. O TCP, como exemplo, dá a ilusão que duas máquinas estão conectadas permanentemente, como se houvesse fios as ligando.





O Kernel Modular

O Kernel é modificado por uma série de módulos.

Entre eles há os Device Drivers, que determinam as regras para usar componentes de hardware.

O Kernel ser dividido em módulos oferece uma série de vantagens, incluindo o Kernel ser mais leve, mais customizável, o usuário ter maior controle, e haver a possibilidade de mudar os módulos dinamicamente, sem precisar de reiniciar o computador.

Curiosidade: Ao executar

# lsmod





no bash do Linux, você verá uma lista de módulos que estão carregados no Kernel.









//esse artigo foi baseado no http://www.tuxradar.com/content/how-linux-kernel-works, recomendo muito a leitura.