Fundamentos de Sistemas Operacionais


A função do sistema operacional é fornecer aos programas do usuário um modelo do computador melhor, mais simples e mais limpo, assim como lidar com o gerenciamento de todos os recursos mencionados. A maioria dos computadores tem dois modos de operação: modo núcleo e modo usuário.

Sistemas Operacionais


O sistema operacional opera em modo núcleo (também chamado modo supervisor). Nesse modo, ele tem acesso completo a todo o hardware e pode executar qualquer instrução que a máquina for capaz de executar.


Observe, na figura a seguir, uma visão geral simplificada dos principais componentes:

Fundamentos de Sistemas Operacionais


O resto do software opera em modo usuário, no qual apenas um subconjunto das instruções da máquina está disponível.

A diferença entre os modos exerce papel crucial na maneira como os sistemas operacionais funcionam.

Os sistemas operacionais são enormes, complexos e têm vida longa. O código-fonte do coração de um sistema operacional como Linux ou Windows tem cerca de cinco milhões de linhas.

O que é um sistema operacional?


Os sistemas operacionais realizam duas funções essencialmente não relacionadas: (1) fornecer a programadores de aplicativos (e programas aplicativos, claro) um conjunto de recursos abstratos limpo em vez de recursos confusos de hardware, e (2) gerenciar esses recursos de hardware

O que é um sistema operacional?


Sistemas operacionais transformam o feio em belo, como mostrado na figura:

Usando essa abstração, os programas podem criar, escrever, ler arquivos, sem ter que lidar com detalhes complexos de como o hardware funciona

O que é um sistema operacional?


O conceito de um sistema operacional como fundamentalmente fornecendo abstrações para programas aplicativos é uma visão top-down (abstração de cima para baixo). Uma visão alternativa, bottom-up (abstração de baixo para cima), sustenta que o sistema operacional está ali para gerenciar todas as partes de um sistema complexo.

O que é um sistema operacional?


O gerenciamento de recursos inclui a multiplexação (compartilhamento) de recursos de duas maneiras diferentes: no tempo e no espaço.

  • Quando um recurso é multiplexado no tempo, diferentes programas ou usuários se revezam usando-o.
  • O outro tipo é a multiplexação de espaço. Em vez de os clientes se revezarem, cada um tem direito a uma parte do recurso.

O que é um sistema operacional?


  1. Quando múltiplas saídas de impressão estão na fila para serem impressas em uma única impressora, uma decisão tem de ser tomada sobre qual deve ser impressa em seguida.

Multiplexado no tempo

  1. Em vez de os clientes se revezarem, cada um tem direito a uma parte do recurso. Por exemplo, a memória principal é normalmente dividida entre vários programas sendo executados, de modo que cada um pode ser residente ao mesmo tempo (por exemplo, a fim de se revezar usando a CPU).

Multiplexado no espaço

Revisão sobre hardware de computadores


Um sistema operacional está intimamente ligado ao hardware do computador no qual ele é executado


A CPU, memória e dispositivos de E/S estão todos conectados por um sistema de barramento (bus) e comunicam-se uns com os outros sobre ele.

Revisão sobre hardware de computadores


Processadores

O “cérebro” do computador é a CPU. O ciclo básico de toda CPU é buscar a primeira instrução da memória, decodificá-la para determinar o seu tipo e operandos, executá-la.


Cada CPU tem um conjunto específico de instruções que ela consegue executar. Desse modo, um processador x86 não pode executar programas ARM.

As CPUs têm alguns registradores internos para armazenamento de variáveis e resultados temporários.

Os resgistradores são importantes por causa da multiplexação de tempo da CPU.

Revisão sobre hardware de computadores


Memória

É o segundo principal componente em qualquer computador, o qual deve ser rápido ao extremo (mais rápida do que executar uma instrução, de maneira que a CPU não seja atrasada pela memória).


Memória

A camada superior consiste em registradores internos à CPU. Eles são feitos do mesmo material que a CPU e são, desse modo, tão rápidos quanto ela

Memória cache é uma parte da CPU. Atua como memória temporária para que seja recuperado rapidamente os dados, sem a necessidade de uma busca direta na memória principal

Mémoria principal tem por finalidade o armazenamento de instruções e dados de programas que serão ou estão sendo executados pela CPU.

Discos magnéticos são um tipo de memória não volátil de grande capacidade de armazenamento, usada para guardar informações (instruções e dados de programas) que não serão imediatamente usadas pela CPU.

Exemplo de utilização de cache


DuckDB tem uma forma de consulta (vectorized or just-in-time query execution engines) que são processadas em lotes de dados que consistem em coleções de vetores, cada um contendo uma quantidade fixa de valores das colunas.

O resultado é um uso eficiente das operações no cache, mantendo os dados nas consultas tanto quanto possível no cache L1 e L2 muito rápido.

O zoológico dos sistemas operacionais

  • Sistemas operacionais de computadores de grande porte, de alto desempenho e alta disponibilidade (mainframes)
  • Sistemas operacionais de servidores
  • Sistemas operacionais de computadores pessoais
  • Sistemas operacionais de computadores portáteis
  • Sistemas operacionais embarcados
  • Sistemas operacionais de tempo real

Conceitos de sistemas operacionais

  • Processos: um processo é basicamente um programa em execução e associado a cada processo está um espaço de endereçamento, uma lista de posições de mémoria que vai de 0 a algum máximo, onde o processo pode ler e escrever.
  • Espaços de endereçamento: diz respeito ao gerenciamento e à proteção da memória principal do computador, quando se tem multiplos processos sendo executados.
  • Arquivos: um sistema de arquivos é uma estrutura usada por um sistema operacional para organizar e gerenciar arquivos em um dispositivo de armazenamento. Chamadas do sistemas são necessárias para criar, remover, ler escrever arquivos. Existe o conceito de diretório como uma maneira de agrupar os arquivos

Conceitos de sistemas operacionais

  • Entrada/Saída: o sistema operacional tem um subsistema de E/S para gerenciamento dos dispositivos.
  • Proteção: o sistema operacional gerencia a segurança do sistema de maneira que os arquivos, por exemplo, sejam acessíveis somente por usuários autorizados.
  • Interpretador de comandos (shell): é a principal interface entre um usuário e o sistema operacional.

Conceitos de sistemas operacionais

Memória virtual: A memória virtual proporciona a capacidade de executar programas maiores do que a memória física da máquina, rapidamente movendo pedaços entre a memória RAM e o disco.


Quando se instala o linux é possível definir o tamanho da partição do disco que será utilizado pela memória virtual (swap).

free
              total        used        free      shared  buff/cache   available
Mem:        13290480      480388    10480984        1252     2329108    12527384
Swap:              0           0           0

Chamadas de sistema

Já vimos que os sistemas operacionais apresentam duas funções: abstrações para os usuários e gerenciamento de recursos.

  • A parte do gerenciamento de recursos fica transparente para os usuários e é feita automaticamente.
  • Na sua maior parte a interação entre programas de usuários e o sistema operacional lida com as abstrações.

Chamadas de sistema


A manipulação de uma aplicação de usuário que invoca a chamada de sistema open()
  1. A função open() é executada em modo usuário
  2. No modo núcleo/kernel é feita uma busca em um vetor de endereços a implementação da chamada open()
  3. O open() é executado e retorna o resultado para o sistema operacional e sequencialmente para o usuário

Chamadas de sistema

API: elemento que proporciona uma ligação física ou lógica entre dois sistemas ou partes de um sistema que não poderiam ser conectados diretamente.

  • API define um conjunto de normas que possibilita a comunicação entre plataformas por meio de uma série de padrões e protocolos.
  • Por meio de APIs, desenvolvedores podem criar novos softwares e aplicativos capazes de se comunicar com outras plataformas. Por exemplo: caso um desenvolvedor queira criar um aplicativo de fotos para Android, ele poderá ter acesso à câmera do celular através da API do sistema operacional, sem ter a necessidade de criar uma nova interface de câmera do zero..


Aqui uma abstração da complexidade!

Chamadas de sistema

As chamadas de sistema sempre foram o meio pelo qual os programas de espaço do usuário podem acessar os serviços do kernel

  • Cria um novo diretório (pasta)
mkdir mynewdir
  • Visualizar a interface de chamada
cat /usr/include/asm*/unistd.h | grep -B 1 sys_mkdir
#define __NR_mkdirat 34
__SYSCALL(__NR_mkdirat, sys_mkdirat)
  • Visualizar os registradores onde são armazenados as info da chamada
cpuid -1 -r | head -3
CPU:
   0x00000000 0x00: eax=0x0000000d ebx=0x756e6547 ecx=0x6c65746e edx=0x49656e69
   0x00000001 0x00: eax=0x000406f0 ebx=0x01020800 ecx=0xfefa3203 edx=0x1f8bfbff

Chamadas de sistema

Uma chamada de sistem pode necessitar de outras chamadas serem realizadas

strace -c mkdir mynewdir
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
  0.00    0.000000           0         5           read
  0.00    0.000000           0         8           close
  0.00    0.000000           0        18           mmap
  0.00    0.000000           0         7           mprotect
  0.00    0.000000           0         1           munmap
  0.00    0.000000           0         3           brk
  0.00    0.000000           0         4           pread64
  0.00    0.000000           0         2         2 access
  0.00    0.000000           0         1           execve
  0.00    0.000000           0         1           mkdir
  0.00    0.000000           0         2         2 statfs
  0.00    0.000000           0         2         1 arch_prctl
  0.00    0.000000           0         1           set_tid_address
  0.00    0.000000           0        26        20 openat
  0.00    0.000000           0        26        20 newfstatat
  0.00    0.000000           0         1           set_robust_list
  0.00    0.000000           0         1           prlimit64
  0.00    0.000000           0         1           getrandom
  0.00    0.000000           0         1           rseq
------ ----------- ----------- --------- --------- ----------------
100.00    0.000000           0       111        45 total
  • A lista de chamadas existentes aqui
  • Para consultar no sistema operacional a página da mensagem man 2 mkdir

Chamadas de sistema


Os desenvolvedores de aplicações projetam programas de acordo com uma interface de programação de aplicações (API — application programming interface).

A API especifica um conjunto de funções que estão disponíveis para um programador de aplicações, incluindo os parâmetros que são passados a cada função e os valores de retorno que o programador pode esperar.

Portable Operating System Interface (POSIX) é uma API para o sistema UNIX/Linux.

Se usarmos o comando ps, ele deverá se comportar da mesma forma no OpenBSD, Debian e macOS.

Chamadas de sistema


  • Uso da função write em C++
write.cpp
#include<unistd.h>

int main() 
{
    write(1, "Ola Mundo!", 10);
}
  • Nas linguagens de programação C e C++, unistd.h é o nome do arquivo de cabeçalho que fornece acesso à API do sistema operacional POSIX.
  • Compilar: g++ write.cpp
  • Executar: ./a.out

Chamada de sistema


Abaixo a chamada de sistema write sendo utilizada

strace -o trace.txt ./a.out

cat trace.txt | grep -A 5 write
write(1, "Ola Mundo!", 10)              = 10
exit_group(0)                           = ?
+++ exited with 0 +++

Chamada de sistema


No fim das contas um printf realiza a mesma chamada de sistema write

write.cpp
#include<stdio.h>

int main() 
{
    printf("Ola Mundo!");
}
strace -o trace.txt ./a.out

cat trace.txt | grep -A 5 write
write(1, "Ola Mundo!", 10)              = 10
exit_group(0)                           = ?
+++ exited with 0 +++

Chamada de sistema


strace -o trace.txt python -c 'print("Ola Mundo!")'

cat trace.txt | grep -A 5 write 
  • Será realizada a chamada de sistema write?
  • Comparar com as chamadas de sistema feitas com C++

Chamadas de sistema

  • Chamadas de sistema para gerenciamento de processos
  • Chamadas de sistema para gerenciamento de arquivos
  • Chamadas de sistema para gerenciamento de diretórios
  • Chamadas de sistema diversas

Chamadas de sistema para gerenciamento de processos


Chamada Descrição
pid = fork() Criar um processo filho idêntico ao pai
pid = waitpid(pid, statloc, options) Espera que um processo filho seja concluído
s = execve(name, argv, environp) Substitui a imagem do núcleo de um processo
exit(status) Conclui a execução do processo e devolve status

Chamadas de sistema para gerenciamento de processos

fork

fork_exemplo.py
import os
import sys

pid = os.fork()

if pid < 0:
  sys.exit("Fork fail")

print(f"Hello world!, process_id(pid) = {os.getpid()}\n")
Hello world!, process_id(pid) = 7534
Hello world!, process_id(pid) = 7536
fork_exemplo.cpp
#include <stdio.h>
#include <unistd.h>

int main()
{
    pid = fork();
    if(pid < 0){
    perror("Fork fail");
    _exit(1);
    }
    printf("Hello world!, process_id(pid) = %d \n", getpid());
    return 0;
}
Hello world!, process_id(pid) = 685 
Hello world!, process_id(pid) = 686

Chamadas de sistema para gerenciamento de processos

waitpid

waitpid_exemplo.py
import os

i = 1
waitpid_return = ()
print(f"process_id(pid) parent= {os.getpid()}\n")

pid = os.fork()
if pid > 0:
  for i in range(1, 6):
    waitpid_return = os.waitpid(pid, os.WNOHANG)
    print(f"{i}, process_id(pid) child = {pid}, waitpid = {waitpid_return}\n")
else:
  print(f"{i}, process_id(pid) child = {pid}, waitpid = {waitpid_return}\n")
waitpid_exemplo.cpp
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int i = 1;
int pid;
int waitpid_return;
int main ()
{   
    printf("process_id(pid) parent= %d\n", getpid());
    pid = fork();
    if (pid != 0)
    {   
      
      for (int i = 1; i < 5; i++) {
        waitpid_return = waitpid(pid, NULL, WNOHANG);
        printf("%d, process_id(pid) child = %d, waitpid = %d\n", i, pid, waitpid_return);
      }
    }
    else {
        waitpid_return = waitpid(pid, NULL, WNOHANG);
        printf("%d, process_id(pid) child = %d, waitpid = %d\n", i, pid, waitpid_return);}
        return 0;
    }

Chamadas de sistema para gerenciamento de processos

execve

execve.cpp
#include <unistd.h>
#include <iostream>
#include <string>

std::string c = "/bin/ls";
std::string p = "-lh";
char *args[] = {c.data(), p.data()};
int main() {
  execve(args[0], args, NULL);
}

Chamadas de sistema para gerenciamento de diretórios


Chamada Descrição
s = mkdir(name, mode) Cria um novo diretório
s = rmdir(name) Remove um diretório
s = link(name1, name2) Cria um nova entrada name1 apontando para name2
s = unlink(name) Remove uma entrada de diretório
s = mount(special, name, flag) Monta um sistema de arquivos
s = unmount(special) Desmonta um sistema de arquivos

Chamadas de sistema para gerenciamento de diretórios

Exemplos

# Criar um novo diretório
mkdir mynewdir

# Remover um diretório
rmdir mynewdir

# Criar link entre arquivos
link myfile1 myfile2

# Criar link simbólico (atalho)
ln -s myfile1 myfile2

Alguns comandos úteis


# Acessar o manual de um comando
man mkdir

# Criar um novo diretório
mkdir novapasta

# Criar um novo diretório contendo outro diretório (pasta)
mkdir -p novapasta/outrapasta

# Criar um diretório com alguma permissão definida
mkdir -m 700 novapasta

# Remover um diretório vazio
rmdir novapasta

Alguns comandos úteis


# Criar um arquivo em branco
touch meuarquivo.txt

# Criar um arquivo com algum conteúdo
echo "Ola mundo!" > meuarquivo.txt

# Adicionar uma nova linha a um arquivo existente
echo "Nova linha" >> meuarquivo.txt 

# Remover um arquivo
rm meuarquivo.txt

# Remover um diretório
rm -r novapasta

Alguns comandos úteis


# Mover arquivos
mv meuarquivo.txt /outrapasta

# Renomear arquivos
mv meuarquivo.txt novonome.txt

# Copiar arquivos
cp meuarquivo.txt copiadoarquivo.txt

# Copiar diretórios
cp -r meudiretorio outro/luga

Chamadas de sistema para gerenciamento diversos


Chamada Descrição
s = chdir(dirname) Altera o diretório de trabalho
s = chmod(name, mode) Altera os bits de proteção de um arquivo
s = kill(pid, signal) Envia um sinal para um processo

Chamadas de sistema para gerenciamento diversos

Exemplos

# Alterar um diretório (geralmente se usa cd)
chdir <diretório existente>
cd <diretório existente>

# Define a proteção de um arquivo
chmod 700 myfile

# "Matar" um processo
kill <pid>

Proteção de um arquivo

Devo utilizar um valor octal para usuário/grupo/outros

Octal Binario para Octal Binário Código de Proteção
0 \(0\times2^2 + 0\times2^1 + 0\times2^0\) 000 Nenhuma permissão ---
1 \(0\times2^2 + 0\times2^1 + 1\times2^0\) 001 Permissão de execução --x
2 \(0\times2^2 + 1\times2^1 + 0\times2^0\) 010 Permissão de escrita -w-
3 \(1\times2^2 + 1\times2^1 + 1\times2^0\) 011 Permissão de escrita e execução -wx
4 \(1\times2^2 + 0\times2^1 + 0\times2^0\) 100 Permissão de leitura r--
5 \(1\times2^2 + 0\times2^1 + 1\times2^0\) 101 Permissão de leitura e execução r-x
6 \(1\times2^2 + 1\times2^1 + 0\times2^0\) 110 Permissão de leitura e escrita rw-
7 \(1\times2^2 + 1\times2^1 + 1\times2^0\) 111 Todas as permissões rwx
# Permissão de escrita para o usuário (2) e nenhuma permissão para o grupo (0) e outros (0)
chmod 200 myfile
ls -l myfile
--w-------  1 thop  staff  0 27 Mar 16:18 myfile
# Permissão de leitura/escrita para o usuário (6) e permissão de leitura para o grupo (4)
chmod 640 myfile
ls -l myfile
-rw-r-----  1 thop  staff  0 27 Mar 16:18 myfile

Estruturas do Sistema Operacional

Os sistemas operacionais podem ser organizados de várias maneiras, dependendo de sua estrutura interna e de como eles gerenciam os recursos do computador.

Monolítico


  • Nesta estrutura, todo o sistema operacional é implementado como um único programa de grande porte.
  • Todas as funcionalidades do sistema operacional, como gerenciamento de memória, gerenciamento de processos e sistemas de arquivos, residem no kernel.
  • Exemplos incluem sistemas operacionais mais antigos, como MS-DOS e versões mais antigas do UNIX.

Microkernel


  • Neste modelo, o kernel é mínimo e fornece apenas as funcionalidades básicas, como gerenciamento de memória, comunicação entre processos e escalonamento de CPU.
  • Funcionalidades adicionais, como sistemas de arquivos e drivers de dispositivos, são implementadas como processos de usuário que se comunicam com o kernel.
  • Exemplos incluem MINIX, Symbian e Redox.

Desse modo, um erro no driver de áudio fará que o som fique truncado ou pare, mas não derrubará o computador.

Microkernel

Redox


O Redox OS segue os princípios da filosofia Unix, integrando conceitos de microkernel. Isso significa que o núcleo do sistema apenas gerencia interações entre processos e recursos, enquanto funcionalidades adicionais são movidas para bibliotecas externas, que podem ser usadas tanto pelo kernel quanto por aplicativos de usuários. Um dos grandes diferenciais é que todos os drivers são executados no espaço de usuário, em ambientes isolados, o que aumenta a segurança e a estabilidade do sistema.1

Híbrido


  • Esta abordagem combina elementos do kernel monolítico e do microkernel.
  • O kernel contém algumas funcionalidades essenciais, como gerenciamento de memória e escalonamento de processos, enquanto outras funcionalidades são implementadas como módulos do kernel ou como processos de usuário.
  • Exemplos incluem Linux, Windows NT/2000/XP/Vista/7/8/10.

Sistemas Virtuais


  • Esta estrutura cria uma máquina virtual que simula uma arquitetura de hardware para cada processo.
  • Cada processo tem a ilusão de que possui seu próprio sistema operacional.
  • Exemplos incluem máquinas virtuais como VMware, VirtualBox e Hyper-V.

(a) Um hipervisor de tipo 1. (b) Um hipervisor de tipo 2 na prática. Uma discussão sobre a diferença entre hipervisors 1 e 2: https://aws.amazon.com/pt/compare/the-difference-between-type-1-and-type-2-hypervisors/

Gerenciamento de processos

O conceito mais central em qualquer sistema operacional é o processo: uma abstração de um programa em execução.

Contador de Programa e Registrador de Instruções


  • Contador de Programa (Program Counter), é um registrador de propósito especial usado pelo processador para armazenar o endereço da próxima instrução a ser executada.
  • Registrador de Instruções (Instruction Register) contém a instrução que está sendo executada pela CPU

Ciclo de instrução básico

Início da execução do programa:

  1. O Contador de Programa contém 0x00000000 (digamos que este seja o endereço de início do programa na memória).
  2. A instrução é buscada na memória e colocada no Registrador de Instruções.
  3. A instrução é executada.
  4. Agora é hora de avançar para o próximo endereço de instrução, e seguir o mesmo processo (ciclo de intrução)

Contador de Programa e Registrador de Instruções


Processos

  • Um processo é apenas uma instância de um programa em execução, incluindo os valores atuais do contador do programa, registradores e variáveis.
  • Processos podem ser criados e terminados dinamicamente.
  • Cada processo tem seu próprio espaço de endereçamento.

O modelo de processo

  1. Vemos um computador multiprogramando quatro programas na memória (com contador programa físico).
  2. Vemos quatro processos, cada um com seu próprio fluxo de controle e sendo executado independente dos outros (contador de programa lógico).
  3. Vemos que, analisados durante um intervalo longo o suficiente, todos os processos tiveram progresso, mas a qualquer dado instante apenas um está sendo de fato executado.


Em qualquer sistema de multiprogramação, a CPU muda de um processo para outro rapidamente, executando cada um por dezenas ou centenas de milissegundos, dando a ilusão do paralelismo.

Criação de processos

Sistemas operacionais precisam de alguma maneira criar processos.

Quatro eventos principais fazem com que os processos sejam criados:

Inicialização do sistema. Alguns processos são de (1) primeiro plano, sendo processos que interagem com o usuário; (2) segundo plano, processos que não estão associados a um usuário (daemons)

Execução de uma chamada de sistema de criação de processo por um processo em execução. Um processo em execução emitirá chamadas de sistema para criar processos novos para ajudá-lo em seu trabalho

Solicitação de um usuário para criar um novo processo. Em sistemas interativos, os usuários podem começar um programa digitando um comando ou clicando duas vezes sobre um ícone.

Início de uma tarefa em lote. Pense no gerenciamento de estoque ao fim de um dia em uma cadeia de lojas, nesse caso usuários podem submeter tarefas em lote ao servidor (possivelmente de maneira remota).

Criação de processos

No UNIX, há apenas uma chamada de sistema para criar um novo processo: fork. Essa chamada cria um clone exato do processo que a chamou. Após a fork, os dois processos, o pai e o filho, têm a mesma imagem de memória, as mesmas variáveis de ambiente e os mesmos arquivos abertos. Mas atuam de forma independente.

Como identificar processos filho?

child.sh
#!/bin/bash
sleep infinity
parent.sh
#!/bin/bash
./child.sh &
sleep infinity
$
parent.sh &

Usando:

$
pgrep -P <parent pid>
pgrep -lP <parent pid>
pstree -p <parent pid>
ps --ppid <parent pid>
ls /proc/<parent pid>/task
cat /proc/<parent pid>/task/<parent pid>/children

Término de processos

Após um processo ter sido criado, ele começa a ser executado e realiza qualquer que seja o seu trabalho. No entanto, nada dura para sempre, nem mesmo os processos. Cedo ou tarde, o novo processo terminará, normalmente devido a uma das condições a seguir:

  1. Saída normal (voluntária).
  2. Erro fatal (involuntário).
  3. Saída por erro (voluntária).
  4. Morto por outro processo (involuntário).

Exemplos:

  1. ctrl + c para interromper programas ou processos no primeiro plano
  2. !g++ foo.cpp e foo.cpp não existe no diretório
  3. Um Erro interno do processo que afeta seu funcionamento, então o usuário é avisado e o programa termina
  4. kill <pid>

Hierarquias de processos

  • Em alguns sistemas, quando um processo cria outro, o processo pai e o processo filho continuam a ser associados de certas maneiras.
  • O processo filho pode em si criar mais processos, formando uma hierarquia de processos.
$
./grandparent.sh
ps -efj | egrep "PGID|children|parent"
kill -9 -<pgid>
grandparent.sh
#!/bin/bash
bash parent.sh &
bash parent.sh &
for i in {1..100}; do 
sleep 2; 
echo -n "This is the grandparent process $i"; 
done
parent.sh
#!/bin/bash
bash children.sh &
bash children.sh &
for i in {1..100}; do sleep 2; echo -n "This is the parent process $i"; 
done
children.sh
#!/bin/bash
for i in {1..100}; do 
sleep 2; 
echo -n "This is a test in children process $i"; 
done

Estados de processos


  • Embora cada processo seja uma entidade independente, com seu próprio contador de programa e estado interno, processos muitas vezes precisam interagir entre si.
  • Um processo pode gerar alguma saída que outro processo usa como entrada.


$
curl -s https://openbible.com/textfiles/kjv.txt | \
grep "Exodus\s2" | \
head -4

São 3 Estados:

  • Em execução (realmente usando a CPU naquele instante).
  • Pronto (executável, temporariamente parado para deixar outro processo ser executado).
  • Bloqueado (incapaz de ser executado até que algum evento externo aconteça).

Estados de processos


  1. O processo é bloqueado aguardando uma entrada
  2. O escalonador seleciona outro processo
  3. O escalonador seleciona esse processo
  4. A entrada torna-se disponível

Um processo pode estar nos estados em execução, bloqueado ou pronto. Transições entre esses estados ocorrem como mostrado.

Estados de processos

O nível mais baixo de um sistema operacional estruturado em processos controla interrupções e escalonamento. Acima desse nível estão processos sequenciais.

O escalonamento, isto é, decidir qual processo deve ser executado, quando e por quanto tempo, é um assunto importante; nós o examinaremos mais adiante neste capítulo. Muitos algoritmos foram desenvolvidos para tentar equilibrar as demandas concorrentes de eficiência para o sistema como um todo e justiça para os processos individuais.

Implementação de processos

Para implementar o modelo de processos, o sistema operacional mantém uma tabela (um arranjo de estruturas) chamada de tabela de processos, com uma entrada para cada um deles.

  • Essas entradas contêm informações importantes sobre o estado do processo quando ele é trocado do estado em execução para pronto ou bloqueado
  • Podendo o processo retornar precisamente para o mesmo estado em que se encontrava antes de ser interrompido.

Alguns dos campos de uma entrada típica na tabela de processos

Comando top

Com o comando top podemos ter várias informações sobre os processos:

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                              
 2153 root      20   0  192020  50716  10752 S   1.3  0.2   0:24.52 jupyterhub                           
  350 eee_W_3+  20   0   34760   9992   5504 S   0.3  0.0   0:00.35 vim                                  
 1050 eee_W_3+  20   0  321276  68720  16192 S   0.3  0.2   0:01.28 jupyterhub-sing                      
 1072 eee_W_3+  20   0  856432  67460  14456 S   0.3  0.2   0:00.67 python3.11                           
 1202 mysql     20   0 1944024 172192  19812 S   0.3  0.5   1:11.94 mysqld                               
 1824 root      20   0 1510756  22280  13592 S   0.3  0.1   0:52.81 docker-containe                      
 2403 root      20   0 3286184  53932  19520 S   0.3  0.2   0:17.24 tty.js                               
 2964 root      20   0 1238252  40832  14912 S   0.3  0.1   1:16.43 node                                 
 3238 eee_W_3+  20   0   34736   9992   5484 S   0.3  0.0   0:00.03 vim                                      
  • PID: identificador do processo
  • USER: usuário
  • %CPU: percentual de CPU utilizado
  • %MEM: percentual de memória utilizado
  • COMMAND: comando que deu origem ao processo

Comando top

Na parte do cabeçário da saída do comando são apresentadas estas informações:

top - 13:21:02 up  8:36,  0 users,  load average: 0.21, 0.22, 0.25
Tasks: 583 total,   1 running, 581 sleeping,   0 stopped,   1 zombie
%Cpu(s):  0.4 us,  0.1 sy,  0.0 ni, 99.5 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem : 32127716 total, 22673544 free,  4059152 used,  5395020 buff/cache
KiB Swap:        0 total,        0 free,        0 used. 27328092 avail Mem 
  • Há 583 processos, sendo que 1 está running, 581 sleeping, 0 stopped e 1 zombie
  • Sobre a CPU: 0,4% utilizado na camada de usuário (us), 0,1% na camada do kernel (sy), 99.5 não está sendo feito nada (id) e 0% esperando por I/O (wa)

Comando top

Significado dos status:

  • D (sono ininterrupto): O processo está aguardando um evento, como a conclusão de uma operação de E/S, e não pode ser interrompido.
  • R (em execução): O processo está sendo executado pelo processador.
  • S (sono): O processo está aguardando um evento, mas pode ser interrompido por sinais.
  • T (rastreado ou parado): O processo foi suspenso ou está sendo depurado.
  • Z (zumbi): O processo terminou sua execução, mas ainda não foi removido da tabela de processos pelo sistema operacional.

Comando top


  • top -c Mostra a linha de comando ao invés do nome do programa
  • top -d <num> Atualiza a tela após num segundos
  • top -p <pid> Mostra apenas o processo com o PID especificado
  • top -u <user> Monitora os processos em execução de um determinado usuário

Processo zombie


Um Processo Zombie é criado quando um processo filho termina antes do processo pai e o pai não verifica o status de saída do filho.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main(void)
{
    pid_t pid;
    int status;

    if ((pid = fork()) < 0) {
        perror("fork");
        exit(1);
    }

    /* Child */
    if (pid == 0)
        exit(0);

    /* Parent
     * Gives you time to observe the zombie using ps(1) ... */
    sleep(100);

    /* ... and after that, parent wait(2)s its child's
     * exit status, and prints a relevant message. */
    pid = wait(&status);
    if (WIFEXITED(status))
        fprintf(stderr, "\n\t[%d]\tProcess %d exited with status %d.\n",
                (int) getpid(), pid, WEXITSTATUS(status));

    return 0;
}

Ao lado temos uma rotina para a criação de um zombie.1 Para identificar o processo zombie, podemos utilizar o comando ps <pid do filho> ou o comando top ao clicar na letra o e filtrar com S=Z. No exemplo foi utilizado ps 7246

  PID TTY      STAT   TIME COMMAND
 7246 pts/49   Z      0:00 [a.out] <defunct>

Scheduling de Processos

Scheduling de Processos


O objetivo da multiprogramação é haver sempre algum processo em execução para maximizar a utilização da CPU.

O compartilhamento de tempo é alternar a CPU entre os processos, com tanta frequência, que os usuários possam interagir com cada programa enquanto ele está sendo executado.

Para alcançar esses objetivos, o scheduler de processos seleciona um processo disponível (possivelmente em um conjunto de vários processos disponíveis) para execução na CPU.

Scheduling de Processos

Cada processo é representado, no sistema operacional, por um bloco de controle de processo (PCB - process control block) - também chamado bloco de controle de tarefa.

Estado do processo pode ser novo, pronto, em execução, em espera, parado, e assim por diante.

Contador do programa. indica o endereço da próxima instrução a ser executada para esse processo.

Registradores da CPU incluem acumuladores, registradores índice, ponteiros de pilhas e registradores de uso geral, além de qualquer informação do código de condição.

Informações de scheduling da CPU incluem a prioridade de um processo, ponteiros para filas de scheduling e quaisquer outros parâmetros de scheduling.

Informações de gerenciamento da memória podem incluir itens como o valor dos registradores base e limite e as tabelas de páginas, ou as tabelas de segmentos.

Fila de scheduling

Quando os processos entram no sistema, eles são inseridos em uma fila de jobs que é composta por todos os processos no sistema. Os processos que estão residindo na memória principal e estão prontos e esperando execução são mantidos em uma lista chamada fila de prontos.

Mudança de contexto


A alocação da CPU a outro processo requer a execução do salvamento do estado do processo corrente e a restauração do estado de um processo diferente. Essa tarefa é conhecida como mudança de contexto


  • As interrupções fazem com que o sistema operacional tire a CPU de sua tarefa corrente para executar uma rotina do kernel.
  • Essas operações ocorrem, com frequência, em sistemas de uso geral.
  • O contexto é representado no PCB do processo.

Mudança de contexto


O tempo gasto na mudança de contexto é puro overhead porque o sistema não executa trabalho útil durante a permuta de processos. A velocidade da permuta varia de uma máquina para outra, dependendo da velocidade da memória, do número de registradores a serem copiados

Comunicação Interprocessos


Os processos que são executados concorrentemente no sistema operacional podem ser processos independentes ou processos cooperativos


Um processo é independente quando não pode afetar outros processos em execução no sistema nem ser afetado por eles. Qualquer processo que não compartilhe dados com outros processos é independente.

Um processo é cooperativo quando pode afetar outros processos em execução no sistema ou pode ser afetado por eles. É claro que qualquer processo que compartilhe dados com outros processos é um processo cooperativo.

Comunicação Interprocessos


Razões para o fornecimento de um ambiente que permita a cooperação entre processos:

Compartilhamento de informações. Já que vários usuários podem estar interessados no mesmo bloco de informações (por exemplo, um arquivo compartilhado), devemos fornecer um ambiente que permita o acesso concorrente.

Aumento da velocidade de computação. Se quisermos que uma tarefa em particular seja executada mais rapidamente, devemos dividi-la em subtarefas a serem executadas em paralelo.

Modularidade. Podemos querer construir o sistema de forma modular, dividindo suas funções em processos

Conveniência. Até mesmo um usuário individual pode trabalhar em muitas tarefas ao mesmo tempo. Por exemplo, um usuário pode editar, ouvir música e compilar em paralelo.

Processos cooperativos


Processos cooperativos demandam um mecanismo de comunicação entre processos (IPC) que lhes permita trocar dados e informações. Há dois modelos básicos de comunicação entre processos: memória compartilhada e transmissão de mensagens.

No modelo de memória compartilhada, estabelece-se uma região da memória que é compartilhada por processos cooperativos. Os processos podem, então, trocar informações lendo e gravando dados na região compartilhada.

No modelo de transmissão de mensagens, a comunicação ocorre por meio de mensagens trocadas entre os sistemas cooperativos.

Sistema de memória compartilhada


A comunicação entre processos que usam memória compartilhada requer que os processos em comunicação estabeleçam uma região de memória compartilhada. Normalmente, a região de memória compartilhada reside no espaço de endereçamento do processo que cria o segmento de memória compartilhada. Outros processos que queiram se comunicar usando esse segmento de memória compartilhada devem anexá-lo ao seu espaço de endereçamento.

Sistema de memória compartilhada


Condições de corrida

Em alguns sistemas operacionais, processos que estão trabalhando juntos podem compartilhar de alguma memória comum que cada um pode ler e escrever.

Veja na figura dois processos querem acessar a memória compartilhada ao mesmo tempo.

Sistema de memória compartilhada


Regiões críticas

Como evitar as condições de corrida? A chave para evitar problemas aqui e em muitas outras situações envolvendo memória compartilhada, arquivos compartilhados e tudo o mais compartilhado é encontrar alguma maneira de proibir mais de um processo de ler e escrever os dados compartilhados ao mesmo tempo.

Colocando a questão em outras palavras, o que precisamos é de exclusão mútua, isto é, alguma maneira de se certificar de que se um processo está usando um arquivo ou variável compartilhados, os outros serão impedidos de realizar a mesma coisa.

Regiões críticas


Quatro condições são estabelecidas para se chegar a uma boa solução de corridas:

  1. Dois processos jamais podem estar simultaneamente dentro de suas regiões críticas.
  2. Nenhuma suposição pode ser feita a respeito de velocidades ou do número de CPUs.
  3. Nenhum processo executando fora de sua região crítica pode bloquear qualquer processo.
  4. Nenhum processo deve ser obrigado a esperar eternamente para entrar em sua região crítica.

Exclusão mútua com espera ocupada

Desabilitando interrupções

Com as interrupções desabilitadas, nenhuma interrupção de relógio poderá ocorrer. Afinal de contas, a CPU só é chaveada de processo em processo em consequência de uma interrupção de relógio ou outra, e com as interrupções desligadas, a CPU não será chaveada para outro processo. Então, assim que um processo tiver desabilitado as interrupções, ele poderá examinar e atualizar a memória compartilhada sem medo de que qualquer outro processo interfira.

Contudo, em um sistema multinúcleo (isto é, sistema de multiprocessador) desabilitar as interrupções de uma CPU não evita que outras CPUs interfiram com as operações que a primeira está realizando. Em consequência, esquemas mais sofisticados são necessários.

Exclusão mútua com espera ocupada

Variáveis do tipo trava

Considere ter uma única variável (de trava) compartilhada, inicialmente 0. Quando um processo quer entrar em sua região crítica, ele primeiro testa a trava. Se a trava é 0, o processo a configura para 1 e entra na região crítica. Se a trava já é 1,o processo apenas espera até que ela se torne 0. Desse modo, um 0 significa que nenhum processo está na região crítica, e um 1 significa que algum processo está em sua região crítica.

Infelizmente, essa ideia contém exatamente a mesma falha fatal que vimos no diretório de spool. Suponha que um processo lê a trava e vê que ela é 0. Antes que ele possa configurar a trava para 1, outro processo está escalonado, executa e configura a trava para 1. Quando o primeiro processo executa de novo, ele também configurará a trava para 1, e dois processos estarão nas suas regiões críticas ao mesmo tempo.

Exclusão mútua com espera ocupada

Alternância explícita

A variável do tipo inteiro turn, inicialmente 0, serve para controlar de quem é a vez de entrar na região crítica e examinar ou atualizar a memória compartilhada. Inicialmente, o processo 0 inspeciona turn, descobre que ele é 0 e entra na sua região crítica. O processo 1 também encontra lá o valor 0 e, portanto, espera em um laço fechado testando continuamente turn para ver quando ele vira 1. Testar continuamente uma variável até que algum valor apareça é chamado de espera ocupada.

while True:
  while turn != 1:
    print("processo 0 na região crítica")
    turn = 1
    print("processo 0 na região não crítica")

while True:
  while turn != 0:
    print("processo 1 na região crítica")
    turn = 0
    print("processo 1 na região não crítica")

Digamos que em algum momento os dois processos estão em suas regiões não críticas e o processo 0 colocar turn para 1, mas ele termina o seu processo e precisar retornar para região crítica, mas está impedido.

Exclusão mútua com espera ocupada


Solução de Peterson

Antes de usar as variáveis compartilhadas (isto é, antes de entrar na região crítica), cada processo chama enter_region com seu próprio número de processo, 0 ou 1, como parâmetro. Essa chamada fará que ele espere, se necessário, até que seja seguro entrar. Após haver terminado com as variáveis compartilhadas, o processo chama leave_region para indicar que ele terminou e para permitir que outros processos entrem, se assim desejarem.

Exclusão mútua com espera ocupada


Solução de Peterson

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import threading
import time

cs = 0
flag_0 = False
flag_1 = False
turn = 0

def thread_0():
    global cs, flag_0, flag_1, turn

    flag_0 = True
    turn = 1
    while (flag_1 and turn == 1):
            continue

    for i in range(10):
        cs += 1
        print("Thread 0: cs =", cs)
        time.sleep(0.1)

    flag_0 = False

def thread_1():
    global cs, flag_0, flag_1, turn

    flag_1 = True
    turn = 0
    while (flag_0 and turn == 0):
        continue

    for i in range(10):
        cs += 1000
        print("Thread 1: cs =", cs)
        time.sleep(0.1)

    flag_1 = False

if __name__ == "__main__":
        t0 = threading.Thread(target=thread_0)
        t1 = threading.Thread(target=thread_1)
        t0.start()
        t1.start()

# Reference: https://www.aspires.cc/implementing-peterson-algorithm-with-python/

Sistemas de Transmissão de Mensagens


A transmissão de mensagens fornece um mecanismo para permitir que os processos se comuniquem e sincronizem suas ações sem compartilhar o mesmo espaço de endereçamento. Isso é particularmente útil em um ambiente distribuído em que os processos em comunicação podem residir em diferentes computadores conectados por uma rede. Por exemplo, um programa de bate-papo na Internet poderia ser projetado de modo que os participantes se comuniquem uns com os outros trocando mensagens.

Um recurso de transmissão de mensagens fornece, pelo menos, duas operações:

  • send(message)
  • receive(message)

Sistemas de Transmissão de Mensagens


Sockets

Um socket é definido como uma extremidade de comunicação. Um par de processos comunicando-se por uma rede emprega um par de sockets — um para cada processo. Um socket é identificado por um endereço IP concatenado com um número de porta. Geralmente, os sockets usam uma arquitetura cliente-servidor. O servidor espera solicitações recebidas de clientes ouvindo em uma porta especificada. Uma vez que uma solicitação seja recebida, o servidor aceita uma conexão proveniente do socket do cliente para completá-la.

Sisitemas de Transmissão de Mensagens


server.py
import socket


def run_server():
    # create a socket object
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    server_ip = "127.0.0.1"
    port = 8000

    # bind the socket to a specific address and port
    server.bind((server_ip, port))
    # listen for incoming connections
    server.listen(0)
    print(f"Listening on {server_ip}:{port}")

    # accept incoming connections
    client_socket, client_address = server.accept()
    print(f"Accepted connection from {client_address[0]}:{client_address[1]}")

    # receive data from the client
    while True:
        request = client_socket.recv(1024)
        request = request.decode("utf-8") # convert bytes to string
        
        # if we receive "close" from the client, then we break
        # out of the loop and close the conneciton
        if request.lower() == "close":
            # send response to the client which acknowledges that the
            # connection should be closed and break out of the loop
            client_socket.send("closed".encode("utf-8"))
            break

        print(f"Received: {request}")

        response = "accepted".encode("utf-8") # convert string to bytes
        # convert and send accept response to the client
        client_socket.send(response)

    # close connection socket with the client
    client_socket.close()
    print("Connection to client closed")
    # close server socket
    server.close()


run_server()
client.py
import socket


def run_client():
    # create a socket object
    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    server_ip = "127.0.0.1"  # replace with the server's IP address
    server_port = 8000  # replace with the server's port number
    # establish connection with server
    client.connect((server_ip, server_port))

    while True:
        # input message and send it to the server
        msg = input("Enter message: ")
        client.send(msg.encode("utf-8")[:1024])

        # receive message from the server
        response = client.recv(1024)
        response = response.decode("utf-8")

        # if server sent us "closed" in the payload, we break out of the loop and close our socket
        if response.lower() == "closed":
            break

        print(f"Received: {response}")

    # close client socket (connection to the server)
    client.close()
    print("Connection to server closed")

run_client()


Exemplo extraído daqui

Threads


Cada processo tem um espaço de endereçamento e um único thread de controle. Na realidade, essa é quase a definição de um processo. Não obstante isso, em muitas situações, é desejável ter múltiplos threads de controle no mesmo espaço de endereçamento executando em quase paralelo, como se eles fossem (quase) processos separados (exceto pelo espaço de endereçamento compartilhado).

Threads, miniprocessos


Por que alguém iria querer ter um tipo de processo dentro de um processo? Na realidade, há várias razões para a existência desses miniprocessos, chamados threads

A capacidade para as entidades em paralelo compartilharem um espaço de endereçamento e todos os seus dados entre si.

São mais leves do que os processos, eles são mais fáceis (isto é, mais rápidos) para criar e destruir do que os processos.

Quando há uma computação substancial e também E/S substancial, contar com threads permite que essas atividades se sobreponham.

Threads são úteis em sistemas com múltiplas CPUs, onde o paralelismo real é possível.

Um processador de texto com três threads


Um thread interage com o usuário e o outro lida com a reformatação em segundo plano. Tão logo a frase é apagada da página 1, o thread interativo diz ao de reformatação para reformatar o livro inteiro. Enquanto isso, o thread interativo continua a ouvir o teclado e o mouse e responde a comandos simples como rolar a página 1 enquanto o outro thread está trabalhando com afinco no segundo plano. Um terceiro thread pode fazer backups de disco sem interferir nos outros dois.

Um processador de texto com três threads


Deve ficar claro que ter três processos em separado não funcionaria aqui, pois todos os três threads precisam operar no documento. Ao existirem três threads em vez de três processos, eles compartilham de uma memória comum.

O modelo de thread clássico


  1. vemos três processos tradicionais. Cada processo tem seu próprio espaço de endereçamento e um único thread de controle. Cada um deles opera em um espaço de endereçamento diferente.
  2. vemos um único processo com três threads de controle. Embora em ambos os casos tenhamos três threads. Todos os três compartilham o mesmo espaço de endereçamento.

Todo thread pode acessar todo espaço de endereçamento de memória dentro do espaço de endereçamento do processo, um thread pode ler, escrever, ou mesmo apagar a pilha de outro thread. Não há proteção, porque (1) é impossível e (2) não seria necessário.

Threads POSIX


Para possibilitar que se escrevam programas com threads portáteis, o IEEE definiu um padrão para threads no padrão IEEE 1003.1c. O pacote de threads que ele define é chamado Pthreads.

Todos os threads têm determinadas propriedades. Cada um tem um identificador, um conjunto de registradores (incluindo o contador de programa), e um conjunto de atributos, que são armazenados em uma estrutura.

Algumas das chamadas de função do Pthreads:

Executando Threads


multiple_threads.py

"""
Executando Múltiplas Threads

https://realpython.com/intro-to-python-threading/
"""

import logging
import threading
import time

def thread_function(name):
    logging.info("Thread %s: starting, ID: %s", name, threading.get_native_id())
    time.sleep(2)
    logging.info("Thread %s: finishing", name)

if __name__ == "__main__":
    format = "%(asctime)s: %(message)s"
    logging.basicConfig(format=format, level=logging.INFO,
                        datefmt="%H:%M:%S")

    threads = list()
    for index in range(3):
        logging.info("Main    : create and start thread %d.", index)
        x = threading.Thread(target=thread_function, args=(index,))
        threads.append(x)
        x.start()

    for index, thread in enumerate(threads):
        logging.info("Main    : before joining thread %d.", index)
        thread.join()
        logging.info("Main    : thread %d done", index)

Convertendo de um thread para multithread

Conflitos entre threads sobre o uso de uma variável global

Conflitos entre threads sobre o uso de uma variável global.

Threads podem ter variáveis globais individuais.

Thread 1 executa a chamada de sistema access para descobrir se ela tem permissão. A variável retorna a variável da resposta em errno. Contudo antes da thread ler a variável o escalanador troca para a thread 2. O thread 2 executa uma chamada open que falha, o que faz com que errno seja sobreescrito. Quando thread 1 retorna ele retorna com um valor errado.

Scheduling da CPU

O scheduling da CPU é a base dos sistemas operacionais multiprogramados. Alternando a CPU entre os processos, o sistema operacional pode tornar o computador mais produtivo.

Scheduling ou escalonamento de CPU


Quando um computador é multiprogramado, ele frequentemente tem múltiplos processos ou threads competindo pela CPU ao mesmo tempo. Essa situação ocorre sempre que dois ou mais deles estão simultaneamente no estado de pronto. Se apenas uma CPU está disponível, uma escolha precisa ser feita sobre qual qual processo será executado. A parte do sistema operacional que faz esta escolha é escalonador.

Comportamento do processo

Quase todos os processos alternam entre períodos de computação intensa e solicitações de E/S (disco ou rede). A CPU executa cálculos por um tempo contínuo, até ser necessário realizar uma chamada de sistema para ler ou gravar em um arquivo. Após a conclusão dessa chamada, a CPU retoma os cálculos até que precise de mais dados ou precise gravar mais informações, repetindo o ciclo..

Ciclo de Picos de CPU e I/O

O sucesso do scheduling da CPU depende de uma propriedade observada nos processos: a execução de processos consiste em um ciclo de execução da CPU e espera por I/O. Os processos se alternam entre esses dois estados. A execução do processo começa com um pico de CPU. Esse é seguido por um pico de I/O, que é seguido por outro pico de CPU e, então, outro pico de I/O, e assim por diante. Eventualmente, o último pico de CPU termina com o sistema solicitando o encerramento da execução

Scheduler da CPU


Sempre que a CPU se torna ociosa, o sistema operacional deve selecionar um dos processos na fila de prontos para ser executado. O processo de seleção é realizado pelo scheduler da CPU. O scheduler seleciona um processo entre os processos na memória que estão prontos para execução e aloca a CPU a esse processo.

Scheduling com Preempção (interrupção forçada de um processo)


Decisões de scheduling da CPU podem ser tomadas sob as seguintes circunstâncias:

  1. Quando um processo passa do estado de execução para o estado de espera por exemplo, como resultado de uma solicitação de I/O ou de uma invocação a wait para o encerramento de um processo-filho
  2. Quando um processo passa do estado de execução para o estado de pronto (por exemplo, quando ocorre uma interrupção)
  3. Quando um processo passa do estado de espera para o estado de pronto (por exemplo, na conclusão de I/O)
  4. Quando um processo termina

Scheduling com Preempção


Nas situações 1 e 4, não há alternativa no que diz respeito ao scheduling. Um novo processo (se existir um na fila de prontos) deve ser selecionado para execução. Há uma alternativa, no entanto, nas situações 2 e 3.

 

Quando o scheduling ocorre apenas sob as circunstâncias 1 e 4, dizemos que o esquema de scheduling é sem preempção ou cooperativo. Caso contrário, ele tem preempção.

Despachante


Outro componente envolvido na função de scheduling da CPU é o despachante. O despachante é o módulo que passa o controle da CPU ao processo selecionado pelo scheduler. Essa função envolve o seguinte:

  • Mudança de contexto
  • Mudança para a modalidade de usuário
  • Salto para o local apropriado no programa do usuário para reiniciar o programa


O tempo que o despachante leva para interromper um processo e iniciar a execução de outro é conhecido como latência do despacho.

Critérios de Scheduling


Diferentes algoritmos de scheduling da CPU têm diferentes propriedades, e a escolha de um algoritmo específico pode favorecer uma classe de processos em vez de outra. Seguem alguns critérios para comparação de algorítmos:

Utilização da CPU. Queremos manter a CPU tão ocupada quanto possível. Conceitualmente, a utilização da CPU pode variar de 0 a 100%. Em um sistema real, ela deve variar de 40% (para um sistema pouco carregado) a 90% (para um sistema pesadamente carregado).

Throughput. Se a CPU está ocupada executando processos, trabalho está sendo realizado. Uma medida de trabalho é o número de processos que são concluídos por unidade de tempo, chamado throughput. Para processos longos, essa taxa pode ser de um processo por hora; para transações curtas, ela pode ser de dez processos por segundo.

Tempo de turnaround. Do ponto de vista de um processo específico, o critério importante é quanto tempo ele leva para ser executado. O intervalo entre o momento em que o processo é submetido e o momento de sua conclusão é o tempo de turnaround. O tempo de turnaround é a soma dos períodos gastos em espera para entrar na memória, em espera na fila de prontos, em execução na CPU, e executando I/O.

Tempo de espera. O algoritmo de scheduling da CPU não afeta o período de tempo durante o qual um processo é executado ou faz I/O. Ele afeta somente o período de tempo que um processo gasta esperando na fila de prontos. O tempo de espera é a soma dos períodos gastos em espera na fila de prontos.

Algoritmos de Scheduling

Scheduling “Primeiro-a-Chegar, Primeiro-a-Ser-Atendido”

O algoritmo mais simples de scheduling da CPU é o algoritmo “primeiro-a-chegar, primeiro-a-ser-atendido” (FCFS – first-come, first-served).

  • Nesse esquema, o processo que solicita a CPU primeiro é o primeiro a usá-la.
  • Quando um processo entra na fila de prontos, seu PCB é conectado na cauda da fila. Quando a CPU está livre, ela é alocada ao processo na cabeça da fila.
  • O lado negativo é que o tempo médio de espera na política FCFS geralmente é bem longo.

gantt
    title Primeiro-a-Chegar, Primeiro-a-Ser-Atendido 
    dateFormat  SS
    axisFormat %SS
    tickInterval 1second
    P1: p1, 0, 24s
    P2: p2, after p1, 3s
    P3: after p2, 3s

Algoritmos de Scheduling

Scheduling Menor-Job-Primeiro

Esse algoritmo associa a cada processo a duração do próximo pico de CPU do processo. Quando a CPU está disponível, ela é atribuída ao processo que tem o próximo pico de CPU mais curto

gantt
    title Menor-Job-Primeiro
    dateFormat  SS
    axisFormat %SS
    tickInterval 1second
    P4: p4, 0, 3s
    P1: p1, after p4, 6s
    P3: p3, after p1, 7s
    P2: p2, after p3, 8s

Algoritmos de Scheduling

Scheduling por Prioridades

Uma prioridade é associada a cada processo, e a CPU é alocada ao processo com a prioridade mais alta. Processos com prioridades iguais são organizados no schedule em ordem FCFS. O algoritmo SJF é simplesmente um algoritmo por prioridades em que a prioridade (p) é o inverso do próximo pico de CPU (previsto). Quanto maior o pico de CPU, menor a prioridade, e vice-versa.

gantt
    title Scheduling por Prioridades (p)
    dateFormat  SS
    axisFormat %SS
    tickInterval 1second
    P2 (1): p2, 0, 1s
    P5 (2): p5, after p2, 5s
    P1 (3): p1, after p5, 10s
    P3 (4): p3, after p1, 2s
    P4 (5): p4, after p3, 1s

Algoritmos de Scheduling

Scheduling Round-Robin

O scheduler da CPU seleciona o primeiro processo da fila de prontos, define um timer com interrupção após 1 quantum de tempo e despacha o processo.

Portanto, uma entre duas coisas ocorrerá. O processo pode ter um pico de CPU menor do que 1 quantum de tempo. Nesse caso, o próprio processo liberará a CPU voluntariamente. O scheduler passará então para o próximo processo na fila de prontos. Se o pico de CPU do processo em execução corrente for maior do que 1 quantum de tempo, o timer será desligado e causará uma interrupção para o sistema operacional. Uma mudança de contexto será executada e o processo será inserido na cauda da fila de prontos. O scheduler da CPU selecionará então o próximo processo na fila de prontos.

gantt
    title Round-Robin
    dateFormat  SS
    axisFormat %SS
    tickInterval 1second
    P1: p1, 0, 4s
    P2: p2, after p1, 3s
    P3: p3, after p2, 3s
    P1: p11, after p3, 4s
    P1: p12, after p11, 4s
    P1: p13, after p12, 4s
    P1: p14, after p13, 4s

Gerenciamento de memória

Gerenciamento de memória


O que todo programador gostaria é de uma memória privada, infinitamente grande e rápida, que fosse não volátil também, isto é, não perdesse seus conteúdos quando faltasse energia elétrica. Aproveitando o ensejo, por que não torná-la barata, também? Infelizmente, a tecnologia ainda não produz essas memórias no momento.

Hierarquia de memória


Ao longo dos anos, as pessoas descobriram o conceito de hierarquia de memórias, em que os computadores têm alguns megabytes de memória cache volátil, cara e muito rápida, alguns gigabytes de memória principal volátil de velocidade e custo médios, e alguns terabytes de armazenamento em disco em estado sólido ou magnético não volátil, barato e lento, sem mencionar o armazenamento removível, com DVDs e dispositivos USB. É função do sistema operacional abstrair essa hierarquia em um modelo útil e então gerenciar a abstração.

A parte do sistema operacional que gerencia (parte da) hierarquia de memórias é chamada de gerenciador de memória.

A noção de um espaço de endereçamento


Dois problemas têm de ser solucionados para permitir que múltiplas aplicações estejam na memória ao mesmo tempo sem interferir umas com as outras: proteção e realocação.


Uma solução foi inventar uma nova abstração para a memória: o espaço de endereçamento. Da mesma forma que o conceito de processo cria uma espécie de CPU abstrata para executar os programas, o espaço de endereçamento cria uma espécie de memória abstrata para abrigá-los. Um espaço de endereçamento é o conjunto de endereços que um processo pode usar para endereçar a memória. Cada processo tem seu próprio espaço de endereçamento, independente daqueles pertencentes a outros processos (exceto em algumas circunstâncias especiais onde os processos querem compartilhar seus espaços de endereçamento).

Registradores base e registradores limite


Quando esses registradores são usados, os programas são carregados em posições de memória consecutivas sempre que haja espaço e sem realocação durante o carregamento. Quando um processo é executado, o registrador base é carregado com o endereço físico onde seu programa começa na memória e o registrador limite é carregado com o comprimento do programa.

Usar registradores base e limite é uma maneira fácil de dar a cada processo seu próprio espaço de endereçamento privado, pois cada endereço de memória gerado automaticamente tem o conteúdo do registrador base adicionado a ele antes de ser enviado para a memória.

Desvantagem: necessidade de realizar uma adição e uma comparação em cada referência de memória.

Registradores base e registradores limite


(a) Um programa de 16 KB. (b) Outro programa de 16 KB. (c) Os dois programas carregados consecutivamente na memória.

Registradores base (endereço físico onde seu programa começa) ou limite (comprimento do programa) podem ser usados para dar a cada processo um espaço de endereçamento em separado.

Troca de processos (Swapping)


Estratégia simples para lidar com a sobrecarga de memória que consiste em trazer cada processo em sua totalidade, executá-lo por um tempo e então colocá-lo de volta no disco.

Processos ociosos estão armazenados em disco em sua maior parte, portanto não ocupam qualquer memória quando não estão sendo executados.

Quando as trocas de processos criam múltiplos espaços na memória, é possível combiná-los em um grande espaço movendo todos os processos para baixo, o máximo possível. Essa técnica é conhecida como compactação de memória.

Troca de processos (Swapping)


Mudanças na alocação de memória à medida que processos entram nela e saem dela. As regiões sombreadas são regiões não utilizadas da memória:


Quanta memória deve ser alocada para um processo quando ele é criado ou trocado?

  • Se os processos são criados com um tamanho fixo que nunca muda, então a alocação é simples: o sistema operacional aloca exatamente o que é necessário, nem mais nem menos. No entanto, um problema ocorre sempre que um processo tenta crescer.
  • Se houver um espaço adjacente ao processo, ele poderá ser alocado e o processo será autorizado a crescer naquele espaço.
  • Por outro lado, se o processo for adjacente a outro, aquele que cresce terá de ser movido para um espaço na memória grande o suficiente para ele, ou um ou mais processos terão de ser trocados para criar um espaço grande o suficiente.
  • Se um processo não puder crescer em memória e a área de troca no disco estiver cheia, ele terá de ser suspenso até que algum espaço seja liberado (ou ele pode ser morto).
  • Se o esperado for que a maioria dos processos cresça à medida que são executados, provavelmente seja uma boa ideia alocar um pouco de memória extra sempre que um processo for trocado ou movido, para reduzir a sobrecarga associada com a troca e movimentação dos processos que não cabem mais em sua memória alocada.

(a) Alocação de espaço para um segmento de dados em expansão. (b) Alocação de espaço para uma pilha e um segmento de dados em expansão.

Gerenciamento de memória com mapas de bits ou listas encadeadas


(a) Uma parte da memória com cinco processos e três espaços. As marcas indicam as unidades de alocação de memória. As regiões sombreadas (0 no mapa de bits) estão livres. (b) Mapa de bits correspondente. (c) A mesma informação como lista.

Gerenciamento de memória com listas encadeadas

Uma maneira de controlar o uso da memória é manter uma lista encadeada de espaços livres e de segmentos de memória alocados, onde um segmento contém um processo ou é um espaço vazio entre dois processos.

Algoritmos:

  • first fit (procura fazer a menor busca possível);
  • next fit (uma pequena variação do first fit, exceto por memorizar a posição que se encontra um espaço livre adequado sempre que o encontra);
  • best fit (faz uma busca em toda a lista, do início ao fim, e escolhe o menor espaço livre que seja adequado);
  • worst fit (sempre escolhe o maior espaço livre);
  • quick fit (mantém listas em separado para alguns dos tamanhos mais comuns solicitados).

Memória virtual


Apesar de os tamanhos das memórias aumentarem depressa, os tamanhos dos softwares estão crescendo muito mais rapidamente.


Como consequência desses desenvolvimentos, há uma necessidade de executar programas que são grandes demais para se encaixar na memória e há certamente uma necessidade de ter sistemas que possam dar suporte a múltiplos programas executando em simultâneo, cada um deles encaixando-se na memória, mas com todos coletivamente excedendo-a.

Memória virtual


Método encontrado para passar todo o programa para o computador.

A ideia básica é que cada programa tem seu próprio espaço de endereçamento, o qual é dividido em blocos chamados de páginas.

Funciona bem em um sistema de multiprogramação, com pedaços e partes de muitos programas na memória simultaneamente. Enquanto um programa está esperando que partes de si mesmo sejam lidas, a CPU pode ser dada para outro processo.

Paginação


Paginação é um esquema de gerenciamento de memória pelo qual um computador armazena e recupera dados de um armazenamento secundário para uso na memória principal

  • O espaço de endereçamento virtual consiste em unidades de tamanho fixo chamadas de páginas.
  • As unidades correspondentes na memória física são chamadas de quadros de página.
  • Transferências entre a memória RAM e o disco são sempre em páginas inteiras.
  • A paginação é implementada normalmente por unidades dedicadas de hardware integradas nos processadores (Memory Management Unit). O MMU que irá criar os endereços

A relação entre endereços virtuais e endereços de memória física é dada pela tabela de páginas. Cada página começa com um múltiplo de 4096 e termina 4095 endereços acima; assim, 4K a 8K na verdade significa 4096-8191 e 8K a 12K significa 8192-12287.

Tabelas de página


O objetivo da tabela de páginas é mapear as páginas virtuais em quadros de páginas.

  • Estrutura de uma entrada da tabela de páginas:
  • O campo mais importante é o número do quadro de página. Afinal, a meta do mapeamento de páginas é localizar esse valor.
  • A informação presente/ausente, se for 1 a entrada pode ser usada, se for 0 a pagina virtual não está na memória
  • A proteção diz que tipos de acessos são permitidos
  • Modificada se refere a uma página que foi atualizada e indica se precisa alterar em disco
  • Referenciada é se uma página esta sendo usada ou para leitura ou para escrita

TLB (Translation Lookaside Buffers) ou memória Associativa


Pequeno dispositivo de hardware para mapear endereços virtuais para endereços físicos sem ter de passar pela tabela de páginas.

Substituição de páginas


Quando ocorre uma falta de página, o sistema operacional tem de escolher uma página para remover da memória a fim de abrir espaço para a que está chegando.

  • Se a página a ser removida foi modificada enquanto estava na memória, ela precisa ser reescrita para o disco a fim de atualizar a cópia em disco.

  • Se, no entanto, ela não tiver sido modificada (por exemplo, ela contém uma página de código), a cópia em disco já está atualizada, portanto não é preciso reescrevê-la. Assim a página a ser lida simplesmente sobrescreve a página que está sendo removida.

  • Embora seja possível escolher uma página ao acaso para ser descartada a cada falta de página, o desempenho do sistema será muito melhor se for escolhida uma página que não é intensamente usada.
  • Se uma página intensamente usada for removida, ela provavelmente terá de ser trazida logo de volta, resultando em um custo extra. Muitos trabalhos, tanto teóricos quanto experimentais, têm sido feitos sobre o assunto dos algoritmos de substituição de páginas.

Sistemas de Arquivos

Sistemas de Arquivos


Arquivos são gerenciados pelo sistema operacional. Como um todo, aquela parte do sistema operacional lidando com arquivos é conhecida como sistema de arquivos.


Um arquivo fornece uma maneira para armazenar informações sobre o disco e lê-las depois. Isso deve ser feito de tal modo que isole o usuário dos detalhes de como e onde as informações estão armazenadas.

Nomeação de Arquivos


Quando um processo cria um arquivo, ele lhe dá um nome. Quando o processo é concluído, o arquivo continua a existir e pode ser acessado por outros processos usando o seu nome.

As regras para a nomeação de arquivos variam de sistema para sistema, mas todos os sistemas operacionais atuais permitem cadeias de uma a oito letras como nomes de arquivos legais. Dígitos e caracteres especiais também são frequentemente permitidos.

Alguns sistemas de arquivos distinguem entre letras maiúsculas e minúsculas. O UNIX faz essa distinção; o MS-DOS, não.

Um sistema UNIX pode ter todos os arquivos a seguir como três arquivos distintos: maria, Maria e MARIA. No MS-DOS, todos esses nomes referem-se ao mesmo arquivo.

Muitos sistemas operacionais aceitam nomes de arquivos de duas partes, com as partes separadas por um ponto, como em prog.c. A parte que vem depois do ponto é a extensão do arquivo.

Tipos de arquivos


O UNIX e o Windows, por exemplo, apresentam arquivos regulares e diretórios. O UNIX também tem arquivos especiais de caracteres e blocos.

  • Arquivos regulares são aqueles que contêm informações do usuário.
  • Diretórios são arquivos do sistema para manter a estrutura do sistema de arquivos.
  • Arquivos especiais de caracteres são relacionados com entrada/saída e usados para modelar dispositivos de E/S seriais como terminais, impressoras e redes.
  • Arquivos especiais de blocos são usados para modelar discos.

Tipos de arquivos


Arquivos regulares geralmente são arquivos ASCII ou arquivos binários. Arquivos ASCII consistem de linhas de texto.

  • ASCII: Código Padrão Americano para o Intercâmbio de Informação é um sistema de representação de letras, algarismos e sinais de pontuação e de controle
  • A grande vantagem dos arquivos ASCII é que eles podem ser exibidos e impressos como são e editados com qualquer editor de texto. Além disso, se grandes números de programas usam arquivos ASCII para entrada e saída, é fácil conectar a saída de um programa com a entrada de outro, como em pipelines do interpretador de comandos (shell)
  • Outros arquivos são binários, o que apenas significa que eles não são arquivos ASCII. Listá-los em uma impressora resultaria em algo completamente incompreensível. Em geral, eles têm alguma estrutura interna conhecida pelos programas que os usam.

Atributos de arquivos


Todo arquivo possui um nome e sua data. Além disso, os sistemas operacionais associam outras informações com os arquivos, como data e horário em que foi modificado pela última vez, e tamanho do arquivo. Esses itens extras são atributos do arquivo ou metadados.

Alguns possíveis atributos de arquivos

Operações com arquivos


  1. Create touch ou echo 'algum texto' > arquivo.txt
  2. Delete rm arquivo.txt
  3. Open
  4. Close
  5. Read
  6. Write
  7. Append echo 'algum outro texto' >> arquivo.txt
  8. Get attributes
  9. Set attributes chmod 640 arquivo.txt
  10. Rename mv antigo novo

Diretórios


Para controlar os arquivos, sistemas de arquivos normalmente têm diretórios ou pastas, que são em si arquivos. Nesta seção discutiremos diretórios, sua organização, suas propriedades e as operações que podem ser realizadas por eles.


A capacidade dos usuários de criarem um número arbitrário de subdiretórios proporciona uma ferramenta de estruturação poderosa para eles organizarem o seu trabalho. Por essa razão, quase todos os sistemas de arquivos modernos são organizados dessa maneira.

Operações com diretórios


  1. Create mkdir pasta
  2. Delete rmdir pasta
  3. Opendir
  4. Closedir
  5. Readdir
  6. Rename mv antigo novo
  7. Link link arquivo nome_link
  8. Unlink unlink nome_link

Gerenciamento e otimização de sistemas de arquivos


O gerenciamento de espaço de disco é uma preocupação importante para os projetistas de sistemas de arquivos. Duas estratégias gerais são possíveis para armazenar um arquivo de n bytes: ou são alocados n bytes consecutivos de espaço, ou o arquivo é dividido em uma série de blocos (não necessariamente) contíguos.


Como vimos, armazenar um arquivo como uma sequência contígua de bytes tem o problema óbvio de que se um arquivo crescer, ele talvez tenha de ser movido dentro do disco. O mesmo problema ocorre para segmentos na memória, exceto que mover um segmento na memória é uma operação relativamente rápida em comparação com mover um arquivo de uma posição no disco para outra.

Gerenciamento e otimização de sistemas de arquivos


Feita a opção de armazenar arquivos em blocos de tamanho fixo, devemos decidir qual tamanho o bloco terá.


  • Ter um tamanho de bloco grande significa que todos os arquivos, mesmo um de 1 byte, ocuparão um bloco inteiro. Também significa que arquivos pequenos desperdiçam uma grande quantidade de espaço de disco.
  • Por outro lado, um tamanho de bloco pequeno significa que a maioria dos arquivos ocupará múltiplos blocos e, desse modo, precisará de múltiplas buscas e atrasos rotacionais para lê-los, reduzindo o desempenho.
  • Então, se a unidade de alocação for grande demais, desperdiçamos espaço; se ela for pequena demais, desperdiçamos tempo.

Monitoramento dos blocos livres


Uma lista encadeada de blocos de disco, com cada bloco contendo tantos números de blocos livres de disco quantos couberem nele (a).

(a) Armazenamento da lista de blocos livres em uma lista encadeada. (b) Um mapa de bits.

Sistemas de I/O

Sistemas de I/O


As duas tarefas principais de um computador são I/O e processamento. Em muitos casos, a principal tarefa é o I/O, e o processamento é meramente incidental. Por exemplo, quando navegamos em uma página da web ou editamos um arquivo, nosso interesse imediato é ler ou dar entrada em alguma informação, e não computar uma resposta.

Hardware de I/O


Os computadores operam muitos tipos de dispositivos. A maioria enquadra-se nas categorias gerais de dispositivos de armazenamento (discos, fitas), dispositivos de transmissão (conexões de rede, Bluetooth) e dispositivos de interface humana (tela, teclado, mouse, entrada e saída de áudio). Outros dispositivos são mais especializados, como os envolvidos na pilotagem de um jato.

Nessas aeronaves, uma pessoa fornece entradas para o computador de voo por meio de um joystick e de pedais, e o computador envia comandos de saída que fazem os motores acionarem os lemes, os flaps e o combustível para as máquinas. Apesar da incrível variedade de dispositivos de I/O, no entanto, precisamos apenas de alguns conceitos para entender como eles são conectados e como o software pode controlar o hardware.

Hardware de I/O


Um dispositivo comunica-se com um sistema de computação enviando sinais por um cabo ou até mesmo através do ar. O dispositivo se comunica com a máquina por um ponto de conexão, ou porta — por exemplo, uma porta serial. Quando os dispositivos compartilham um conjunto de fios, a conexão é chamada de bus. Um bus é um conjunto de fios e um protocolo rigidamente definido que especifica um conjunto de mensagens que podem ser enviadas nos fios.

Em termos de eletrônica, as mensagens são transmitidas por padrões de voltagens elétricas aplicados aos fios em intervalos de tempo definidos. Quando o dispositivo A tem um cabo que se conecta ao dispositivo B, o dispositivo B tem um cabo que se conecta ao dispositivo C, e o dispositivo C se conecta a uma porta no computador, essa configuração é chamada de cadeia margarida. Uma cadeia margarida opera usualmente como um bus.

Hardware de I/O


Os buses são amplamente usados na arquitetura de computadores e variam em seus métodos de sinalização, em velocidade, no throughput (número de processos executados por unidade de tempo) e nos métodos de conexão. Uma estrutura típica de bus de PC é mostrada na figura.


bus=barramento

Uma estrutura típica de bus de PC.

Hardware de I/O


Como o processador pode fornecer comandos e dados a um controlador para realizar uma transferência de I/O?

Uma resposta resumida é que o controlador tem um ou mais registradores para dados e sinais de controle. O processador comunica-se com o controlador lendo e gravando padrões de bits nos seus registradores.

Uma forma pela qual essa comunicação pode ocorrer é através do uso de instruções de I/O especiais que determinam a transferência de um byte ou palavra para um endereço de porta de I/O.

A instrução de I/O dispara linhas de bus para selecionar o dispositivo apropriado e mover bits para dentro e para fora de um registrador de dispositivo.

Hardware de I/O


Alternativamente, o controlador do dispositivo pode suportar I/O mapeado para a memória.


Nesse caso, os registradores de controle do dispositivo são mapeados para o espaço de endereçamento do processador. A CPU executa solicitações de I/O usando as instruções-padrão de transferência de dados para ler e gravar os registradores de controle de dispositivos em suas locações mapeadas na memória física.

Interface de I/O da Aplicação


A finalidade da camada do driver de dispositivos é ocultar, do subsistema de I/O do kernel, as diferenças entre controladores de dispositivos, de modo semelhante a como as chamadas de sistema de I/O encapsulam o comportamento de dispositivos em algumas classes genéricas que ocultam, dos aplicativos, as diferenças de hardware.

Uma estrutura de I/O do kernel.


Infelizmente para os fabricantes de hardware de dispositivos, cada tipo de sistema operacional tem seus próprios padrões para a interface dos drivers de dispositivos.

Instalação e Configuração do Linux

Instalação via WSL

Abra o PowerShell e execute

PowerShell
wsl --install

Por padrão será instalada a distribuição Ubuntu.


Caso queira instalar uma outra distribuição adcione o argumento -d e o nome da distribuição

PowerShell
wsl --install -d <nome da distribuição>

Configuração do ambiente


  • Após a instalação será necessário definir um usuário e uma senha
  • Caso você não seja o administrador da máquina, será necessária algumas configurações adicionais:

No PowerShell execute o comando para poder acessar o usuário root:

PowerShell
wsl --install -d <nome da distribuição> -u <nome do usuário>

Por exemplo caso tenha instalado o Debian, será -d Debian

  • Adicionar o seu usuário no sudoers para ter acesso a alguns privilégios

No terminal do linux digitar o comando

$
usermod -aG sudo <nome do usuário>

Instalando programas


Nas distribuições derivadas do Debian o gerenciador de pacotes é o apt

  • Para instalar o programa cmatrix
$
sudo apt install cmatrix

Para executar escreva cmatrix no terminal

$
cmatrix

Erros que podem surgir na instalação


Comandos que podem ser executados para solucionar erros de instalação

sudo apt install <nome do pacote> --fix-missing
sudo apt update
sudo apt --fix-broken install
sudo apt install <nome do pacote>

Alguns programas úteis


  • neovim editor de código
  • g++ compilador de código c++
  • tmux permite que várias sessões de terminal sejam acessadas simultaneamente em uma única janela
  • wget permite fazer download de conteúdos da web

Para instalar todos eles de uma vez

$
sudo apt install neovim g++ tmux wget

Usando vi/vim/neovim

Comandos básicos do neovim


  • Para instalar: sudo apt install neovim
  • Para iniciar digite nvim

Comandos básicos do neovim


Para iniciar no modo de -- INSERÇÃO -- ou -- INSERT --, clique na letra i

Assim será possível digitar

Comandos básicos do neovim


– Após digitar, você deve sair do modo -- INSERÇÃO -- clicando em ESC

– Para salvar o arquivo digite : e em seguida w script.sh

– Clicando na tecla ENTER o arquivo está salvo com o nome script.sh

– Para sair é só digitar :, depois q e então a tecla ENTER

Comandos básicos do neovim


Usando tmux

Comandos básicos do tmux


  • Instalar sudo apt install tmux e para executar tmux
  • Arquivo de configuração .tmux.conf pode ser alterado e salvo em /home/usuario
  • Exemplo de um arquivo de configuração aqui
  • O prefixo foi alterado para ctrl + a
  • A divisão vertical ctrl + b e -; a divisão horizontal ctrl + b e \

Comandos básicos do tmux


– Para dividir a janela horizontalmente com ctrl + a \

– Para dividir a janela verticalmente com ctrl + a -

– Para criar nova janela com ctrl + a c

Containers (Docker)

Um container do Docker é um ambiente de runtime com todos os componentes necessários, como código, dependências e bibliotecas, necessários para executar o código da aplicação sem usar dependências da máquina host.


Runtime: A maioria das linguagens de programação possui algum tipo de sistema de tempo de execução que fornece um ambiente no qual os programas são executados.

Como o Docker funciona?

A tecnologia Docker usa o kernel do Linux e funcionalidades do kernel, como cGroups e namespaces, para segregar processos. Assim, eles podem ser executados de maneira independente. O objetivo dos containers é criar independência: a habilidade de executar diversos processos e apps separadamente para utilizar melhor a infraestrutura e, ao mesmo tempo, manter a segurança que você teria em sistemas separados.

  • cGroups: Você pode usar control groups (cgroups) para estabelecer limites, priorizar ou controlar o acesso aos recursos de hardware para grupos de processos. Isto permite que você controle granularmente o uso de recursos de aplicações para utilizá-los de forma mais eficiente.
  • namespaces: No contexto dos contêineres, isso significa que usuários e grupos podem ter privilégios para determinadas operações dentro do contêiner sem ter esses privilégios fora do contêiner. (Em outras palavras, o conjunto de capacidades de um processo para operações dentro de um namespace de usuário pode ser bem diferente de seu conjunto de capacidades no sistema host.)

Instalação do docker em um ambiente Debian


Para instalação do docker usando apt

https://docs.docker.com/engine/install/debian/#install-using-the-repository

Puxar uma imagem de um registro

Um registro (registry) de contêiner é um repositório — ou coleção de repositórios — usado para armazenar e acessar imagens de container.


Os registros de containeres economizam um tempo valioso dos desenvolvedores na criação e entrega de aplicativos nativos da nuvem, agindo como intermediários para o compartilhamento de imagens de contêineres entre sistemas.

docker pull alpine

Executar um container

Para processos interativos (como um shell), você deve usar os parâmetros -i -t juntos para alocar um tty (terminal) para o container.

docker run -it alpine

Site oficial da distribuição Alpine Linux: https://www.alpinelinux.org/

  • Gerenciador de pacotes (apk)
  • Para instalar apk add cmatrix

Imagens com programas já configurados

Python com um Debian mínimo

Para puxar a imagem

docker pull python:3.12.3-slim-bullseye


Para executar a imagem

docker run -it python:3.12.3-slim-bullseye


Para abrir o shell deste container seguir estes passos

  1. Executar o container: docker run -it python:3.12.3-slim-bullseye
  2. Verificar qual o ID do container que está em execução: docker container ls
  3. Executar o comando para acessar o terminal: docker exec -it <CONTAINER ID> bash

Verificando o tamanho das imagens


docker image ls


REPOSITORY                TAG                   IMAGE ID      CREATED       SIZE
docker.io/library/alpine  latest                442043f030d3  6 days ago    9.12 MB
docker.io/library/python  3.12.3-slim-bullseye  d8ed415581d3  7 weeks ago   127 MB

Criação de um Dockerfile


Dockerfile nada mais é do que um meio que utilizamos para criar nossas próprias imagens.

Dockerfile
FROM alpine # utilização de uma imagem base
RUN apk add cmatrix # execução de comandos na etapa de build
CMD cmatrix # execução de comandos na etapa de inicialização do container

Outros comandos

Usar o comando: docker build -t alpine-new para recriar a imagem a partir das alterações no Dockerfile

Dockerhub


Como mencionado anteriormente, quando baixamos uma image usando pull, estamos obtendo esta imagem de um registry que no caso do docker o padrão é o dockerhub. Para logar neste registro e poder enviar as nossas próprias imagens (push), devemos utilizar o comando docker login -u <user> -p <password> e em seguida docker push <tag>.

AWS

Criação do EC2 e instalação do Docker


Conectar o VSCode no EC2