Aula #20 - Processos, modos e daemons - Nice/Renice


 
Um processo é a personificação de um aplicativo em execução que pode ou não ser constituído de vários segmentos. Os processos têm atributos e permissões bem definidos. 


Eles devem estar em um dos estados enumerados, os estados mais comuns são rodando (running) ou dormindo (sleeping). É importante saber quando um processo está sendo executado no modo de usuário com privilégios limitados, ou em modo kernel com privilégios avançados, incluindo acesso direto ao hardware. Existem várias maneiras de criar processos filho e de definir e modificar as suas prioridades.
Um processo é um programa em execução com os recursos associados, como arquivos abertos, páginas de memória, etc. O mesmo programa pode ser executado mais de uma vez ao mesmo tempo e, assim, ser responsável por vários processos.

Ao mesmo tempo, duas ou mais tarefas ou threads, podem compartilhar diversos recursos, como os seus espaços de memória (ou apenas áreas de memória particulares), arquivos abertos, etc. Quando há situações em que tudo é compartilhado, fala-se de um processo multi-thread.

Em outros sistemas operacionais, pode haver uma grande diferença entre os processos e threads um processo pode ter várias ou apenas uma thread.

No Linux, a situação é bem diferente. Cada segmento de execução é considerada individualmente, a diferença entre processos e threads é discreta e diz respeito ao compartilhamento de recursos, o que torna o Linux mais eficiente para executar aplicativos muti-thread.

Ao contrário de alguns outros sistemas operacionais, o Linux sempre é excepcionalmente rápido para criar, destruir e alternar entre processos. Assim, o modelo adotado para threads é parecido com o modelo dos processos, ou seja a diferença entre rodar vários processos ou várias threads é pequena; cada segmento está programado individualmente e, normalmente, como se fosse um processo autônomo. Isto é feito ao invés de envolver mais níveis de complicações, como ter um método separado de scheduling para processos e outro para threads.

Ao mesmo tempo o Linux respeita o padrão POSIX e outras normas para processos multi-thread; por exemplo, cada thread retorna o mesmo ID do processo pai (chamado ID do grupo da thread internamente), ao mesmo tempo que atribui um ID diferente para cada thread (chamado ID do processo internamente). Isso pode levar a confusão para os desenvolvedores, mas deve ser invisível para os administradores.


INIT


O primeiro processo da camada do usuário no sistema é o init que tem PID (identificador do processo) = 1.  Este processo é iniciado assim que o kernel concluiu a inicialização e montou a sistema de arquivos root.

O init é executado até que o sistema é desligado; ele será o último processo da camada do usuário a ser morto durante o processo de desligamento.  Ele também serve como processo pai de todos os outros processos, direta e indiretamente.



PROCESSOS


Um processo é uma instância de um programa em execução. Ele pode estar em um número de diferentes estados, como rodando ou dormindo. Cada processo tem um PID (ID do Processo), um ppid (ID do processo pai), e um PGID (ID do grupo do processo). Além disso, cada processo tem código de programa, dados, variáveis, descritores de arquivos, e um ambiente.

O init é geralmente o primeiro processo de usuário executado em um sistema e, assim, torna-se o pai de todos os processos posteriores em execução no sistema, exceto para casos especiais em que kernel inicia alguns processos diretamente (que aparecem entre [] no ps).

Se o processo pai morre antes do processo filho terminar, o ppid do processo filho é definido como 1; ou seja, o processo é adotado pelo init. (Nota: em sistemas Linux recentes usando systemd, o ppid será definido para 2, que corresponde ao KThreadd, que assumiu o papel do adotador de processos órfãos).

Um processo filho que termina (seja normal ou anormal), antes de seu pai, e que não aguarda para liberar os recursos é conhecido como um processo zumbi (ou defunct) processo. Processos zombies liberam quase todos os recursos e permanecer apenas para retornar seu status de saída. Uma das funções do processo init é verificar que seus filhos adotivos tenham terminado graciosamente.

Os processos são controlados por scheduling que normalmente é preemptivo. Apenas o kernel tem o direito de solicitar que um processo seja temporariamente suspenso (que é parte normal da característica de um scheduler preemptivo), processos não podem solicitar esta suspensão de outro processo.

Por razões históricas, o maior PID tem sido limitado a um número de 16 bits, ou 32768. É possível alterar este valor, alterando /proc/sys/kernel/pid_max . A medida que processos são criados, eles podem chegar pid_max, ao ponto em que eles vão começar novamente no PID = 300.


Todos os processos possuem alguns atributos:
  •     O programa em execução
  •     Contexto (estado)
  •     Permissões
  •     Recursos associados

Todo processo está executando algum programa. O contexto do processo é composto de uma série de informações em formato de bits, que normalmente são armazenados nos registradores da CPU (um tipo de memória super rápida, mas de baixa capacidade de armazenamento). Essas informações incluem o ponto de execução atual do programa, e o que está na memória associada ao processo.

Como processos podem ser agendados para executar e para dormir quando compartilhando tempo de CPU com outros processos (ou ter que dormir enquanto aguarda por alguma condição ser resolvida), ser capaz de armazenar e carregar todo o contexto de um processo é essencial para a capacidade do kernel fazer trocas de contexto (context switch).


O ulimit é um comando integrado do bash que exibe ou altera os limites associados a recursos do sistema associados com processos sendo executados sob um shell. Rodar ele com a opção -a exibe:

$ ulimit -a

core file size             (blocks, -c) 0
data seg size              (kbytes, -d) unlimited
scheduling priority                (-e) 0
file size                  (blocks, -f) unlimited
pending signals                    (-i) 31843
max locked memory          (kbytes, -l) 64
max memory size            (kbytes, -m) unlimited
open files                         (-n) 1024
pipe size               (512 bytes, -p) 8
POSIX message queues        (bytes, -q) 819200
real-time priority                 (-r) 0
stack size                (kbytes,  -s) 8192
cpu time                  (seconds, -t) unlimited
max user processes                 (-u) 4096
virtual memory             (kbytes, -v) unlimited
file locks                         (-x) unlimited

Um administrador de sistema pode precisar alterar estes valores em duas direções diferentes:

    Restringir limites para que um usuário e/ou processo não consuma todos os recursos do sistema como memória, cpu ou o número máximo processos que o sistema pode executar ao mesmo tempo.
    
    Expandir limites para que um processo não seja atrapalhado por estes limites; por exemplo, um servidor com muitos clientes pode ter problemas causados pelo limite de 1024 arquivos abertos, que é o valor padrão.

Existem dois tipos de limites:

  •     Hard: O valor máximo, ou teto, que é definido pelo root, e que determina até onde os usuários normais podem configurar seus limites individuais.
    
  •     Soft: O limite em uso no momento, e que o usuário pode alterar até o limite hard.


Para ver ou configurar limites, use:

$ ulimit [opções] [limite]

como em
$ ulimit -n 1600

que aumenta o número máximo de arquivos para 1600.

Esta alteração afeta apenas o shell atual. Para fazer alterações que são efetivas para todos os usuários , altere o arquivo /etc/security/limits.conf, que é um arquivo muito bem documentado, e reinicie o sistema.


 Cada processo tem permissões baseadas no usuário que iniciou o processo. Também é possível dar ao processo permissões baseadas no dono do arquivo executável em disco.

Como falamos anteriormente sobre segurança local, os programas que estão marcados com o bit de execução s são executados com o ID do usuário do arquivo que pode ser diferente do usuário que está iniciando o processo. Estes são referidos como programas setuid. Eles executam com o ID de usuário que possui o arquivo executável do programa em disco; programas sem o setuid executam com as permissões do usuário que iniciou o programa. Programas setuid cujo dono do arquivo em disco é o root são conhecidos por serem um problema de segurança.

O programa passwd é um exemplo de um programa setuid. Qualquer usuário pode executá-lo. Quando um usuário executa este programa, o processo deve ser executado com permissões de root, para atualizar os arquivo com restrições de escrita /etc/passwd e /etc/shadow onde as senhas do usuário são mantidas.


 Os processos podem estar em um dos vários estados possíveis, sendo os principais:

    Rodando (running):
    O processo está atualmente em execução na CPU ou aguardando ansiosamente na fila de execução, por fatia de tempo. Ele vai voltar a rodar quando o scheduler decidir que ele merece tempo da CPU.
    
    Dormindo (sleeping/waiting):
    O processo está aguardando que um pedido (geralmente de I/O) seja atendido e que impede o processo de prosseguir até que a solicitação seja concluída. Quando o pedido for concluído, o kernel vai acordar o processo e colocá-lo de volta na fila de execução.
    
    Parado (stopped):
    O processo foi suspenso. Esse estado é mais usado quando um programador quer examinar a memória do programa em execução. Assim que isso for feito, o processo pode voltar a rodar. Isso geralmente é feito quando o processo está sendo executado em um debuger ou quando o usuário pressiona Ctrl-Z.
    
    Zombie:
    O processo entra neste estado quando ele termina, sem ter um processo pai. Tal processo também é chamado um processo de defunct. Se o pai de qualquer processo em execução morrer, o processo é adotado pelo init (PID = 1) ou pelo kthreadd (PID = 2).
   


Modos de execução


A qualquer momento, um processo (ou qualquer segmento de um processo com várias threads) pode ser executado em modo de usuário ou modo do sistema, que normalmente é chamado de modo kernel por desenvolvedores do kernel.

Quais instruções podem ser executadas depende do modo e é o hardware que decide, não o software.

O modo não é um estado do sistema; é um estado do processador, como em um sistema com mais de uma CPU e/ou com mais de um núcleo de processamento(core) cada unidade pode estar em seu próprio estado individual.

Para os íntimos dos processadores Intel (x86 e similares), o modo do usuário é também conhecido como Ring 3 e modo de sistema é o Ring 0.




Modos de Usuário


Exceto quando executam uma chamada de sistema (ou syscall ou system call), conceitos que serão descritos na próxima seção, os processos executam no modo de usuário, onde eles têm menos privilégios do que no modo kernel.

Quando um processo é iniciado, ele é isolado em seu próprio espaço de memória, isso é feito para protegê-lo de outros processos, e também promove segurança e cria um ambiente mais estável. Isso às vezes é chamado de isolamento de recursos processo.

Cada processo em execução no modo de usuário tem seu próprio espaço de memória, mas partes deste espaço podem ser compartilhadas com outros processos; exceto para os segmentos de memória compartilhada, um processo do usuário não é capaz de ler ou escrever no espaço de memória de outro processo.

Mesmo um processo executado pelo usuário root ou um processo com privilégios escalonados através do setuid continua sendo executado em modo de usuário, e tem uma capacidade limitada para acessar hardware, incluindo a memória.


Modo Kernel


No modo kernel (sistema) a CPU tem pleno acesso a todo o hardware do sistema, incluindo periféricos, memória, discos, etc. Se um aplicativo precisa de acesso a esses recursos, deve emitir uma chamada do sistema (syscall ou system call), o que provoca uma mudança de contexto do modo do usuário para o modo kernel. Este procedimento deve ser feito para ler e escrever de arquivos, criar um processo, etc.

O código do aplicativo nunca é executado em modo kernel, apenas a chamada do sistema, que parte do código do kernel. Quando a chamada de sistema termina, um valor de retorno é produzido e o processo retorna para o modo de usuário com a mudança de contexto inversa.

Há outros momentos em que o sistema está em modo kernel que nada têm a ver com processos, tais como a manipulação interrupções de hardware, executar as rotinas do scheduler, e outras tarefas de gerenciamento para o sistema.


Daemons


Um daemon é um processo que roda no background, com propósito de fornecer um serviço específico para os usuários do sistema.

    Daemons podem ser bastante eficientes, pois eles só funcionam quando necessário.
    Muitos daemons são iniciados quando o sistema é ligado.
    Os nomes dos daemon muitas vezes (mas nem sempre) terminam com d.
    Alguns exemplos incluem httpd e udevd.
    Daemons podem responder a eventos externos (udevd) ou reagir a intervalos de tempo (crond).
    Daemons por rodarem no background não tem interface direta com o usuário, nem interagem com a entrada e saída padrão.
    Daemons podem oferecer melhor controle de segurança.

Ao usar sysvinit, scripts no diretório /etc/init.d iniciam vários daemons do sistema. Esses scripts iniciam daemons usando malabarismos do shell que são definidos no arquivo /etc/init.d/functions .

Para ver um exemplo de como isso é feito veja o script para o serviço httpd na pasta /etc/init.d .

Os sistemas que utilizam o systemd tem soluções semelhantes para daemons.


Processos criados pelo Kernel


Nem todos os processos são criados, ou fazem um fork do processo pai. O kernel do Linux cria diretamente dois tipos de processos :

    Processos internos do kernel:
    Estes cuidam de trabalhos de manutenção, como ter certeza de que os buffers são salvos em disco, que a carga sobre CPUs diferentes é balanceado uniformemente, que drivers de dispositivo lidem com o trabalho que eles devem fazer, etc. Estes processos frequentemente são criados na inicialização do sistema, e dormem, exceto quando têm algo para fazer.
    
    Processos externos de usuários
    Estes são processos que rodam na camada do usuário, como aplicativos normais, mas que são criados pelo kernel. Há poucos deles, e eles geralmente rodam por pouco tempo.

É fácil de ver quais processos são desta natureza rodando:

$ ps -elf

Este comando vai listar todos os processos no sistema enquanto mostra os IDs dos processos pai, todos eles vão ter PPID = 2, que se refere a KThreadd, que é a kernel thread interna, cujo trabalho é criar tais processos, cujos nomes serão mostrados entre colchetes: [Ksoftirqd/0].



Criação e forking de processos


Um sistema Linux está sempre criando novos processos. Isso é muitas vezes chamado fork, já que o processo pai original continua funcionando enquanto o novo processo filho é criado.

Quando a maioria dos computadores tinha apenas um processador eles eram normalmente configurados de modo que o pai faria uma pausa para que o filho fosse criado, e até uma expressão UNIX reflete essa política: "As crianças vêm em primeiro lugar." No entanto, com sistemas multi-core pai e filho tendem a rodar simultaneamente em CPUs diferentes.

Muitas vezes, ao invés de apenas um fork, o próximo passo costuma ser um exec, em que o processo pai termina e o processo filho herda o PID do processo do pai.

Sistemas UNIX antigos frequentemente usam um programa chamado spawn que é semelhante em muitas maneiras ao fork e exec, mas difere em detalhes. Não faz parte do padrão POSIX e não é uma parte normal do Linux.

Para ver como novos processos podem iniciar, considere um servidor web que lida com muitos clientes. Ele pode criar um novo processo cada vez que uma nova conexão é feita com um cliente. Por outro lado ele pode apenas começar uma nova thread. No Linux não há muita diferença a nível técnico entre a criação de um processo e a criação de uma thread.

Outro exemplo é o daemon sshd que é iniciado pelo processo init quando ele executa o script sshd, que por sua vez inicia o daemon sshd. Este processo daemon atende a pedidos ssh de usuários remotos.

Quando um pedido é recebido, o sshd cria uma nova cópia de si mesmo para atender à solicitação. Cada usuário remoto recebe sua própria cópia do daemon sshd executando o serviço da login remoto. O processo sshd irá iniciar o programa de login para validar o usuário remoto. Se a autenticação for bem sucedida, o processo de login vai continuar e um um shell (digamos bash) será iniciado.

O que acontece quando um usuário executa um comando em um shell, como o bash?

    Um novo processo é criado (clonado a partir do shell do usuário.)
    
    Uma chamada de sistema (syscall/system call) wait coloca o processo shell pai para dormir.
    
    O comando é carregado pela syscall exec. Em outras palavras, o programa é copiado para a memória sobrescrevendo a memória do processo filho.
    
    O comando é concluído, e o processo filho morre usando a chamada de sistema exit.
    
    O shell pai é despertado pela morte do processo filho e passa a emitir uma nova linha de comandos. O shell pai aguarda pelo próximo pedido do utilizador, e o ciclo será repetido.

Se um comando é iniciado em background (adicionando um e comercial (&) no final da linha de comando) o shell pai ignora o pedido de espera e fica imediatamente livre para receber novos de comandos, permitindo que o processo em segundo plano execute em paralelo. Caso contrário para os pedidos de primeiro plano, o shell espera até que o processo filho termine.

Alguns comandos do shell (como echo e kill) estão embutidos no próprio shell e não carregam programa externos. Para esses comandos, nem o fork nem o exec são executados.





Redefinindo prioridades com o nice/renice


A prioridade de execução de processos pode ser controlada com as ferramentas nice e renice. Desde os primeiros dias do UNIX a ideia tem sido que um processo nice(ou gentil, legal, bacana, descolado, supimpa) reduz sua prioridade em prol dos outros processos. Logo quanto maior é a niceness (gentileza) menor é a prioridade.


O valor nice vai de -20 (a prioridade mais alta) a +19 (a prioridade mais baixa). A forma normal de rodar o nice é como:


$ nice -n 5 comando [ARGUMENTOS]


que vai aumentar o nice em 5. Isso equivale a:


$ nice -5 comando [ARGUMENTOS]


Se você não informa o valor do nice, o padrão é para aumentar o nível de gentileza em 10. Sem argumentos você recebe o nível de nice atual. Por exemplo:


$ nice
0
$ nice cat &
[1] 24908
$ ps -l
F S UID   PID  PPID C PRI NI ADDR SZ WCHAN  TTY          TIME CMD
0 S 500  4670  4603 0 80   0 - 16618 wait   pts/0    00:00:00 bash
0 S 500 24855  4670 0 80   0 - 16560 wait   pts/0    00:00:00 bash
0 T 500 24908 24855 0 90  10 - 14738 signal pts/0    00:00:00 cat
0 R 500 24909 24855 0 80   0 - 15887 -      pts/0    00:00:00 ps


Lembre-se que aumentar o nice de um processo não significa que ele não será executado, ele pode até ter todo o tempo da CPU se não há outros processos.

Se você tentar alterar para um valor fora do que é permitido, o valor mais próximo do que você solicitou que seja válido será utilizado.

 Por padrão, somente um superusuário pode diminuir o nice de um processo, ou seja, aumentar a prioridade. No entanto, é possível dar aos usuários normais a capacidade de diminuir o nice dentro de um intervalo pré-determinado, editando /etc/security/limits.conf.

É fácil usar o comando renice, para alterar o nice de um processo em execução:

renice +3 13848

que irá aumentar 3 pontos no nice do processo com pid = 13848. Para ver uma lista completa das opções man renice.

Mais vistos no mês:

As melhores distribuições Linux para 2017

Teste de Performance de Rede com Iperf

TuxMath - Tux, do Comando da Matemática. Ensino e diversão a crianças.

Aula #14 - Os sistemas de arquivos ext2/ext3/ext4

Modelo Firewall Completo em Iptables para pequena rede/office

DHCP - Guia Completo

OPNsense - Firewall Open Source

SSD no linux

Administração de sistema e Deploys: Ansible, Chef, Fabric, Puppet ou Salt?

Oracle Linux 7.0 Server com Xfce - Instalação e configurações iniciais