Processos de Boot no UNIX e Linux

Processos de Boot no UNIX e Linux

Fala pessoal tudo bom?

Tem tempo que eu não escrevo nada, não é verdade? Então deixa eu me apresentar novamente como se fosse a primeira vez...Eu sou o Aprigio Simões e durante anos, basicamente entre 1998 a 2016 eu tive um blog hospedado em aprigiosimoes.com.br. Eu escrevia diversas matérias sobre o mundo UNIX, Linux e tudo que sobre Free Software e Opensource. Eu gostava muito daquele blog, principalmente pela famosa frase: "Linux é o poder, não trava, não da tela azul e se vc jogar pro espaço vira satélite" rsrs. Enfim, recebia bastante mensagens e meu twitter era sempre cheio. Ai virei papai, trabalho aumentou, fui morar no exterior e entao ficou muito dificil de manter ele e então tirei do ar.. mas sabe como é rsrs, meu amor por esses sistemas maravilhosos é grande demais e graças a Deus estou aqui de novo para ajudar quem busca informações sobre o mundo UNIX e Linux. E como sempre eu disse: vamos falar sobre o PODER !!! ;-)

Hoje eu gostaria de trazer um assunto bem legal no mundo Unix e Linux, que é sobre o processo de boot e do seu controle no sistema.

Para não ficar muito grande, vamos iniciar com os "processos de inicialização e desligamento" ou "gerenciamento de boot".

Quando iniciamos a máquina onde esta instalado o sistema operacional, independente da sua arquitetura, existem algumas etapas que o processo de inicialização do sistema do hardware fará, até alcançar o sistema operacional pelo seu gerenciamento de boot. Um deles é o famoso POST ou Power-On Self-Test que é um conjunto de testes que o hardware faz para saber se esta tudo ok. Após isso, dependendo da sua arquitetura utilizada, inicializará o processo de "carga" do OS (sistema operacional). No caso de um servidor Power/IBM e até semelhantemente em hardware x86, ele inicializará o IPL ou "Initial Program Load" que após o POST que se encarregará de verificar a existência de discos e/ou dispositivos de armazenamentos, onde está o OS (operating system).

Após isso, inicializará o seu "bootloader", que é o software que carrega o sistema operacional, ou seja, em uma plataforma x86 ele é dividido em pelo menos em dois estágios, ou seja, um pequeno binário inicial no MBR que se ocupa de localizar todo o setor de inicialização do sistema operacional e seu setor de boot, seja ele em disco interno, externo ou qualquer outra coisa. Para quem gosta de assunto, por favor, não tente limpar sua faixa de 640 a 1024k, a sua RAM agradece ;)

O bootloader vai depender muito do sistema operacional e da sua arquitetura. Por exemplo, em sistemas Linux instalado em hardware padrão PC/x86 se usa o GRUB (atualmente na versao 2), antes era o LILO ( era lindo isso ), porem se vc instalar Linux em um hardware PPC (PowerPC), vc vai precisar de usar o Yaboot cujo o seu arquivo de configuração é o /etc/yaboot.conf (isso tb é lindo), porem isso nao é uma regra, apesar do GRUB suportar diversas arquiteturas. Nele vc encontra a opção --target que permite definir o destino para a instalação dele, dependendo da arquitetura utilizada. Por exemplo, para instalar o grub em uma arquitetura padrão PC se usa --target=x86_64-efi (que é a opção default), para instalar em sistemas Sun Sparc, utilizasse sparc64-ieee1275, ou arm-efi para ARM, sendo aarch64-efi para ARM/UEFI, ia64-efi para Itanium e i386-pc para o bom e velho PC. Porem as opções de target variam muito e em muitos casos na ultima versão do GRUB passam despercebidas, hoje ta tudo muito fácil. Se vc é das antigas como eu, deve lembrar o maravilhoso syslinux que se utiliza em dispositivos de armazenamento externos e independentes, tal como o LOADLIN. Imagine vc carregar o sistema através de um floppy disk (disquete), facilitando o boot em sistemas como MSDOS. Um exemplo do seu arquivo de configuração syslinux.cfg. Ta bom, eu confesso... adoro coisa retro :-)

No UNIX/Linux esse processo de inicialização é constituído do famoso bootstrapping que consiste em carregar o kernel na memoria e inicializar todos os processos para disponibilizar o sistema operacional para o usuário/administrador. Uma importante observação é que quando o kernel é iniciado, com ele são carregados um conjunto muito grande de tarefas que investigará o seu hardware e concluindo com o carregamento de diversos drivers que contem essas "regras" e que vão "ligar" esses dispositivos.

Um exemplo de configuração do GRUB:

title Red Hat Enterprise Linux 8 (4.18.0-80.el8.x86_64
  root (hd0)
  kernel /boot/vmlinuz-4.18.0-80.el8.x86_64 ro root=UUID=58013e4a-11c0-4195-8fd8-e4b33e5b17d6 console=hvc0 LANG=en_US.UTF-8
  initrd /boot/initramfs-4.18.0-80.el8.x86_64.img)

Neste exemplo podemos ver claramente o boot do RHEL8 sendo gerenciado pelo GRUB, onde ele carrega a imagem do kernel vmlinuz. Hoje por questões de padrnizacao essa opção "kernel" foi renomeada no arquivo de configuração do grub para "linux", que tornou as opções mais coerentes com o "standard". Quando se vê a opção linux16, siginifica que para carregar a imagem do kernel instalada o sistema precisa fazer uma inicialização do kernel em 16 bits. Isso é necessário apenas em sistemas muito antigos que usam o bom e velho BIOS em vez do EFI. Além disso vc encontra a imagem do kernel initrd e initramfs "Initial RAM Disk" que contem um pseudo sistema de arquivos temporário que será utilizado durante o processo de inicialização do kernel Linux. Essa imagem é carregada e descompactada para proceder com a "carga" desses módulos continuando o processo de boot e através dele serão carregados e "linkados" enquanto o kernel estiver em execução. Ele tambem possibilita que a imagem do kernel durante o boot seja bem menor, pois os "drivers" no kernel serão carregados apenas se forem necessários. O que vc poderá fazer depois da carga completa do kernel com os comandos insmod e modprobe.

Se deu tudo certo e nada de Kernel Panic, levante aos mãos para o alto, glorifique e seja feliz :)

Não foi fornecido texto alternativo para esta imagem

Processo de inicialização do Kernel Linux com SysVinit

O processo de inicialização do Linux vai depender muito da sua distribuição e da versão dela, no caso vc pode encontrar distribuições com o bom e velho SysVinit (init), Upstart e o atual Systemd, existem diversos outros como o RuInit, OpenRC, Shepherd e Nosh.

O openrc é um caso muito interessante e que eu gostava muito, ele foi muito utilzado pelo Gentoo como padrão e durante o procedimento de boot ele carregava os daemons referenciados em /etc/rc.conf cujo suas opções e variáveis eram carregados nos arquivos dentro de /etc/conf.d/, como por exemplo o ssh em /etc/conf.d/sshd. Essa estrutura sempre me lembrou com ótimos olhos o que temos no HP-UX em /etc/rc.config.d. Estes processos no gentoo eram inicializados pelo rc-update.

Ja na maioria, distribuições Linux sempre utilizavam o init do SysvInit. O processo era definido como o "pai" de todos os processos e o seu "PID - Process IDentifier" sempre foi 1. O interessante e curioso é que nessa época as distribuições Linux eram muito descentralizadas e dependiam muito do seu mantedor, por exemplo, o Debian era um belo malcriado e tinha a estrutura de scripts de inicialização muito mal documentado e era muito bagunçado, MAS ... para mim era o mais maneiro de todos.. tudo bem.. que cada um coma a sua própria comida de cachorro, como desabafou bem o Adrian no seu bom e velho BSD-NOW no capitulo 101 (I'll Fix Everything | BSD Now 101 - YouTube)

O Debian tinha um conceito de scripts de inicialização um pouco diferente das outras distros com o init, mas no final das contas o resultado era o mesmo. Todo o processo de boot ocorria dentro dos arquivos preliminares do /etc/rc.S/, tal como o hostname e nomeclaturas de interfaces de rede. Por exemplo, ele tinha um conjunto de variaveis a ser carregados em /lib/init/vars.sh para entao executar os scripts e no caso do simples hostname, ele construía a $HOSTNAME pelo S01hostname.sh em /etc/rcS.d/ fazendo um simples HOSTNAME="$(cat /etc/hostname). Enfim, de todas as distros que usava o SyvInit, o Debian deixava tudo sempre muito mais "emocionante". No Debian todos os daemons eram iniciados na runlevel 2 por padrão e eram atualizados e interrompidos pelo seu "gestor" invoke-rc.d. Eu adorava isso!! Vale lembrar que o "rc" significa "Run Commands" e o "S" significa "Startup".

O bom e velho Slackware, distro que iniciei o meu amor pelo Linux, possui um modo de inicialização um pouco diferente e usa o layout do formato BSD-like para a inicialização dos scripts do sistema. Todo os scripts são carregados pelo diretório /etc/rc.d e atraves dos scripts rc, tal como rc.serial, rc.inetd, rc.inet1, rc.sshd e por ai vai.

O SysVinit era utilizado em praticamente todas as distribuições Linux e o seu conceito de inicialização de scripts era totalmente baseado em BASH SCRIPTS. Assim como diversos sistemas UNIX o Linux possuía um método de ordem de boot baseado em níveis de execução, o que chamamos de RUNLEVEL. Estes níveis de execução em UNIX são representados por números de 0 a 9. Já no caso do Linux e alguns outros sistemas representados de 0 a 6.

Porém, deixa eu te contar um segredinho. Na verdade no Linux também possui 9 níveis de execução, sendo a 7, 8 e 9 personalizadas pelo usuário, mas desde uma determinada versão do kernel com distribuições que vieram com o upstart (scripts em /etc/init), como o caso do Ubuntu, Fedora 9 ao 15 e RHEL6, não podem mais ser executadas.

Não foi fornecido texto alternativo para esta imagem

Um exemplo dos niveis de execução em distribuições LINUX com o SysvInit e sua comparação ao Systemd.

O Linux antes de receber o systemd, utilizava os modos de carga de scripts baseado no processo "rc", ou seja, o modo de operação do sistema operacional era definido pelo numero da runlevel selecionado em /etc/inittab, o arquivo era constituído com a configuração seguinte: <id>:<runlevels>:<action>:<process>

id:3:initdefault:

O inittab alem de definir a runlevel padrão, também definia varias outras funções especiais, como o uso do control+alt+del e da construção dos terminais locais tty pelo getty, mingetty ou mgetty, além da execução do script sulogin que era executado com a runlevel 1 (modo de single-user). Com isso, os scripts shell armazenados em em distribuições baseadas no formato da RedHat eram iniciados em /etc/rc.d/init.d (havia um link simbolico para /etc/init.d) ou o proprio /etc/init.d em distribuições baseadas em Debian. Uma observação interessante sobre o mgetty é que ele suportava o PPP (Protocolo Ponto-a-Ponto), mas ai já é um assunto para um outro tópico retro ;-)

Mas como esses scripts eram iniciados? Eles eram indicados por ordem de prioridade e indicados em /etc/rcX.d, sendo o X o número da runlevel selecionada. Ou seja, algo parecido com isso aqui: rc0.d/ rc1.d/ rc2.d/ rc3.d/ rc4.d/ rc5.d/ rc6.d/.

Mas até isso era relativo, pois dependendo do nível de execução, os scripts eram descarregados em vez de carregados. Ou seja, por padrão em distribuições baseadas em Red Hat (formato rpmdb), iniciavam todos os scripts mencionados em /etc/rc3.d, cujo eles eram referenciados por uma ordem de boot que variava entre 10 a 99, sendo 01 a 10 para os scripts mais importantes. Esses scripts eram "linkados" com o diretório /etc/rc.d/init.d ou /etc/init.d para gerenciar a ordem de boot, sendo S para "start" e K "para kill".

No antigo formato SysVinit no Linux os modos eram constituídos assim:

Nivel 0: Para o desligamento do sistema operacional. Era invocada pelo comando halt.
Nível 1: era dedicada ao modo de usuário único, usado para reparar o sistema sem que sejam carregados os scripts operacionais, como a rede.
Nível 2, que no Debian e Ubuntu era a padrão de uso do usuário. Mas em distribuições como o Suse, não carregavam a rede.
Nível 3, que em distribuições como baseadas em RedHat, Fedora, CentOS era o nível de execução padrão para o uso do usuário sem a utilização da interface gráfica, ou seja, não carregava gerenciadores de login automaticamente como o xdm, gdm e kdm.
Nível 4, que era uma runlevel opcional para uso do usuário. Porem na maravilhosa distribuição Slackware era a padrão, e também podia executar o X11.
Nivel 5, que em distros baseadas em Red Hat carregava o X11 durante o boot, ou seja, o gerenciador de login grafico pelo XDM, ou KDM ou GDM ou LightDM.
Nivel 6, para reboot. Era invocada pelo comando reboot.

Os comandos runlevel ou who -r identificam a runlevel que o administrador esta conectado tal como a ultima modificação, que quando apresentado por "N", é sem modificação anterior.

runlevel
who -r

Gostaria também de dizer que o antigo e maravilhoso Red Hat Linux 4,5,6,7, 8 e 9 (atualmente o RHEL) e o SuSE (atualmente o SLE). Sempre foram muito bem documentados, sendo o SuSE com seus belos arquivos de configuração cheios de comentários, o que ajudava muito na época em vez de abrir livros e consulta ao TLDP The Linux Documentation Project (tldp.org).

Hoje, distribuições Linux são construídas e atualizadas com o maravilhoso Systemd, que lembra muito sistemas como o SMF "Service Management Facility". implementado no Solaris 10, tal como o Launchd, implementado no MacOS na versão 10.4. Ele é sistema muito semelhante a estes na sua maneira de trabalhar, tudo bem... não sejamos radicais ...... mas é verdade..

O Systemd foi desenvolvido para centralizar o processo de inicialização dos scripts do sistema operacional Linux e substituir de vez o init do bom e velho sysvinit, facilitando o gerenciamento de serviços e e processos dos mesmos, tudo de uma maneira bastante centralizada. Ele é totalmente compatível com o formato SysVinit porem possui outro modo de inicialização quando comparado as velhas RUNLEVELs. Ele possui os modos "multi-user" que da no mesmo que a antiga runlevel 3, "graphical-user" que lembra a runlevel 5, além de existir os "modos" dedicados para reparação do sistema e modos de emergência.

O grande diferencial do Systemd é que ele executa os scripts de inicialização sob demanda através de ativações via socket e bus. Ele utiliza o cgroups para gerenciar os recursos do sistema como CPU, MEM, I/O e permite que os processos sejam isolados, limitando, controlando e monitorando esses recursos. Desta forma o systemd reduz o tempo de inicialização do sistema e otimiza o uso dos recursos, centralizado tudo na sua unidade de configuração e gerenciamento do sistema. Cada unidade é representada por arquivos como .service, .target, .mount, .socket e muitos outros tipos. Estes podem ser encontrados no diretório das unidade, instalados em /usr/lib/systemd/system e os arquivos de configuração local em /etc/systemd/system. No SystemD vc pode verificar qual target esta conectado com o comando systemctl get-default

[root@gurunix~]# systemctl get-default
multi-user.targett

Mas e no UNIX? Bom ali era um pouco diferente.

Em sistemas Solaris ou SunOS, o nível 5 que desliga o sistema, assim como a 0. Já os modos de manutenção eram o "s" ou "S", sendo para administração e diferente da runlevel 1. No Solaris a runlevel 2 que era a multi-user. O seu processo de boot é gerenciado pela OpenBoot PROM, sendo necessário entrar na sua administração pela tecla L1/STOP (ai vai depender do seu teclado da Sun Microsystems). O disco de boot /dev/rdsk/c0t0d0s0, se for o primeiro, é então iniciado pela opção do boot, carregando o kernel em /etc/system. Caso fosse necessário carregar um outro kernel fora do diretório padrão, poderia ser feito com o comando boot -a <especificando o caminho>. Ou boot -s para single user diretamente no shell da openboot.

No IBM AIX ainda é bem diferente. Existem diversos níveis de execução, sendo de 0 a 9, também declarados no bom e velho /etc/inittab. Não se espante se vc ver prefixos como a, b, c, m, M, s e S.

No AIX a runlevel 2 é a padrão e todo o seu processo de boot e seus scripts rc são realizados em ordem. Um dos scripts de boot e um dos mais importantes no AIX é o rc.mls.boot, que carrega todo o processo de segurança do sistema. No AIX após o BLV "Boot Logical Volume" ser carregado por completo, o script rc.boot é executado algumas vezes para concluir todo o processo de inicialização. Outro ponto interessante é que o AIX possui um formato de base de dados chamado ODM ou Object Data Manager. Ele é utilizado para armazenar informações sobre o hardware e tudo o que há no OS.

No sistema FreeBSD o processo de inicialização ocorre através do "boot0 boot manager" instalado através do comando boot0cfg na sua área de boot. Ele é realizado através do /boot/boot0 que define o primeiro setor de boot do freebsd em sistemas x86 (boot0).

Em hardwares x86 o FreeBSD assim como todos os outros sistemas operacionais no padão IBM-PC, usa uma área "legacy", chamada MBR, que possui 512 bytes, cujo destes são reservados 446 bytes para o bootloader. E no FreeBSD é exatamente isso que ocorre com a inicialização do boot0. Durante esse processo de inicialização do sistema o arquivo /boot/loader.rc é executado e ele se encarrega de de ler o arquivo /boot/defaults/loader.conf (/boot/loader.conf). Encontrando o setor de boot, então o kernel do FreeBSD se encarrega de todo o seu processo, até fazer a leitura de toda a configuração que se encontra no /etc/defaults/rc.conf e detalhes específicos em /etc/rc.conf entregando o terminal ao administrador. "Semelhantemente" no NetBSD. Já no OpenBSD esse processo é bem similar no /etc/boot.conf.

Enfim, eu gostaria de ficar aqui até amanha escrevendo sobre o processo de boot destes sistemas, mas se transformaria em um livro. Eu apenas resumi ai em cima como funciona o boot process em UNIX e Linux, porém, eu gostaria muito de entrar em detalhes. Eu me lembro de uma matéria que eu escrevi no meu blog sobre o Systemd, assim que ele foi lançado. Eu detalhei muito os arquivos de configuração e os comandos de administração dele e prometo que farei uma segunda versão dele aqui.

Próximo artigo vamos ver o uso de comandos como o service do antigo formato sysvinit, systemctl do systemd tal como ps, kill, top, pgrep, pkill, strace, nice, renice e muitos outros. Mas adoraria falar também do svcadm ;)

é o poder ;)