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
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.
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:
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.
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
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 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 gerenciamento de recursos inclui a multiplexação (compartilhamento) de recursos de duas maneiras diferentes: no tempo e no espaço.
Multiplexado no tempo
Multiplexado no espaço
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.
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.
É 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).
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.
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.
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).
total used free shared buff/cache available
Mem: 13290480 480388 10480984 1252 2329108 12527384
Swap: 0 0 0
Já vimos que os sistemas operacionais apresentam duas funções: abstrações para os usuários e gerenciamento de recursos.
open()open() é executada em modo usuárioopen()open() é executado e retorna o resultado para o sistema operacional e sequencialmente para o usuárioAPI: 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.
Aqui uma abstração da complexidade!
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
#define __NR_mkdirat 34
__SYSCALL(__NR_mkdirat, sys_mkdirat)
Uma chamada de sistem pode necessitar de outras chamadas serem realizadas
% 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
man 2 mkdir
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.
Abaixo a chamada de sistema write sendo utilizada
write(1, "Ola Mundo!", 10) = 10
exit_group(0) = ?
+++ exited with 0 +++
No fim das contas um printf realiza a mesma chamada de sistema write
write?C++
| 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 |
forkfork_exemplo.py
Hello world!, process_id(pid) = 7534
Hello world!, process_id(pid) = 7536
waitpidwaitpid_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;
}execve
| 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 |
| 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 |
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
Os sistemas operacionais podem ser organizados de várias maneiras, dependendo de sua estrutura interna e de como eles gerenciam os recursos do computador.
Desse modo, um erro no driver de áudio fará que o som fique truncado ou pare, mas não derrubará o computador.
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
O conceito mais central em qualquer sistema operacional é o processo: uma abstração de um programa em execução.
Início da execução do programa:
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.
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).
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?
Usando:
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:
Exemplos:
ctrl + c para interromper programas ou processos no primeiro plano!g++ foo.cpp e foo.cpp não existe no diretóriokill <pid>$
./grandparent.sh
ps -efj | egrep "PGID|children|parent"
kill -9 -<pgid>
grandparent.sh
parent.sh
$
curl -s https://openbible.com/textfiles/kjv.txt | \
grep "Exodus\s2" | \
head -4
São 3 Estados:
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.
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.
topCom 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
topNa 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
topSignificado dos status:
top
top -c Mostra a linha de comando ao invés do nome do programatop -d <num> Atualiza a tela após num segundostop -p <pid> Mostra apenas o processo com o PID especificadotop -u <user> Monitora os processos em execução de um determinado usuário
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>
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.
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.
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.
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
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
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.
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 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.
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.
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.
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.
Quatro condições são estabelecidas para se chegar a uma boa solução de corridas:
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.
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.
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.
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.
#!/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/
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:
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.
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
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).
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 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.
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.
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.
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:
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)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.
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.
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.
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..
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
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.
Decisões de scheduling da CPU podem ser tomadas sob as seguintes circunstâncias:
wait para o encerramento de um processo-filho
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.
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:
O tempo que o despachante leva para interromper um processo e iniciar a execução de outro é conhecido como latência do despacho.
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.
O algoritmo mais simples de scheduling da CPU é o algoritmo “primeiro-a-chegar, primeiro-a-ser-atendido” (FCFS – first-come, first-served).
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
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
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
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
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.
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.
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).
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.
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.
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:
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.
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.
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 é 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 objetivo da tabela de páginas é mapear as páginas virtuais em quadros de páginas.
Pequeno dispositivo de hardware para mapear endereços virtuais para endereços físicos sem ter de passar pela tabela 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.
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.
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.
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 geralmente são arquivos ASCII ou arquivos binários. Arquivos ASCII consistem de linhas de texto.
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.
touch ou echo 'algum texto' > arquivo.txtrm arquivo.txtecho 'algum outro texto' >> arquivo.txtchmod 640 arquivo.txtmv antigo novo
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.
mkdir pastarmdir pastamv antigo novolink arquivo nome_linkunlink nome_link
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.
Feita a opção de armazenar arquivos em blocos de tamanho fixo, devemos decidir qual tamanho o bloco terá.
Uma lista encadeada de blocos de disco, com cada bloco contendo tantos números de blocos livres de disco quantos couberem nele (a).
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.
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.
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.
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.
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.
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.
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.
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.
Abra o PowerShell e execute
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
No PowerShell execute o comando para poder acessar o usuário root:
Por exemplo caso tenha instalado o Debian, será -d Debian
No terminal do linux digitar o comando
Nas distribuições derivadas do Debian o gerenciador de pacotes é o apt
cmatrixPara executar escreva cmatrix no terminal
Comandos que podem ser executados para solucionar erros de instalação
neovim editor de códigog++ compilador de código c++tmux permite que várias sessões de terminal sejam acessadas simultaneamente em uma única janelawget permite fazer download de conteúdos da webPara instalar todos eles de uma vez
sudo apt install neovimnvim
Para iniciar no modo de -- INSERÇÃO -- ou -- INSERT --, clique na letra i
Assim será possível digitar
– 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
sudo apt install tmux e para executar tmux.tmux.conf pode ser alterado e salvo em /home/usuarioctrl + actrl + b e -; a divisão horizontal ctrl + b e \
– Para dividir a janela horizontalmente com ctrl + a \
– Para dividir a janela verticalmente com ctrl + a -
– Para criar nova janela com ctrl + a c
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.
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.
Para instalação do docker usando apt
https://docs.docker.com/engine/install/debian/#install-using-the-repository
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.
Para processos interativos (como um shell), você deve usar os parâmetros -i -t juntos para alocar um tty (terminal) para o container.
Site oficial da distribuição Alpine Linux: https://www.alpinelinux.org/
apk)apk add cmatrixPara puxar a imagem
Para executar a imagem
Para abrir o shell deste container seguir estes passos
docker run -it python:3.12.3-slim-bullseyeID do container que está em execução: docker container lsdocker exec -it <CONTAINER ID> bash
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
Dockerfile
Dockerfilenada mais é do que um meio que utilizamos para criar nossas próprias imagens.
Dockerfile
Outros comandos
COPY src src compiar uma pasta local de nome src para dentro da imagemUsar o comando: docker build -t alpine-new para recriar a imagem a partir das alterações no Dockerfile
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>.