Saudações.
Nesse artigo vou ensinar o conceito de disco em RAM e suas aplicações no kernel. Em seguida vou ensinar como usá-lo para conseguir I/O infinito (ou quase) para suas aplicações.
O objetivo é criar uma maneira de trabalhar I/O intenso em RAM sem onerar o disco, principalmente discos que perdem vida útil e ambientes cloud que cobram por I/O.
Pré-requisitos (constam em outros artigos aqui do blog):
- Instalação do Linux (Debian);
1 – Conceito de RamDisk (RD)
O sistema operacional (Linux nesse caso) precisa de arquivos para ler e executar, a parte mais crítica do boot e da inicialização do Linux é saber onde estão os arquivos vitais para execução do kernel:
- Os drivers (módulos do kernel, /lib/modules/);
- O INIT (/sbin/init): o processo responsável por iniciar todos os programas (SystemD no caso do Debian).
O processo de boot do Linux tem a seguinte ordem:
- O arquivo vmlinuz (kernel) é copiado para a RAM pelo bootloader;
- O arquivo initrd.gz é copiado para a RAM pelo bootloader;
- O bootloader passa a execução para o kernel;
- O kernel descompacta o arquivo initrd.gz em um espaço de RAM – isso cria um RAMDISK (initrd = initramdisk = Initial RAM Disk = disco inicial na RAM);
- O diretório / (rootfs) é montado nesse espaço da RAM do initrd;
- O kernel procura o binário ELF /init ou /sbin/init e o executa, tornando-o responsável pelo PID 1;
- O init presente dentro do initrd não é o processo 1 do SystemD, ele é responsável por garantir um ambiente mínimo para que o disco principal seja reconhecido. O disco principal pode estar num simples disco SATA ou sobre várias outras camadas de softwares, como:
- LVM2 (comum em Ubuntu, Proxmox PVE);
- RAID;
- DM-Crypt (disco criptografado);
- iSCSI (disco na rede);
- Caberá ao init do initrd resolver todos os problemas para alcançar e montar o disco oficial do sistema e remontar a pasta / (rootfs) no disco do sistema, o que libera o disco inicial na RAM (initrd é apagado);
- Com o disco do sistema pronto, o processo init antigo roda um exec sobre o software init presente no disco do sistema, que no caso do Debian é o SystemD.
- Desse ponto em diante o SystemD assume e sobe todos os serviços;
- Seu Linux estará rodando.
Esse processo moderno substituiu o antigo método de kernel inflado, onde para rodar o kernel linux o arquivo vmlinuz precisava ter todos os drivers para reconhecer o hardware.
Agora, o arquivo vmlinuz pode ser pequeno, ultra minimalista e deixar todos os drivers na pasta /lib/modules do sistema.
Para que seu Linux reconheça os hardware durante o boot, todos os drivers precisarão ser copiados para o arquivo initrd.gz, normalmente hospedado em /boot ou /boot/efi.
Destruir o initrd.gz ou deixar um driver de fora é a forma mais eficiente de impedir que o Linux seja carregado durante o boot!
2 – Motivação para uso de RamDisk
Embora o uso do RD no INIT seja temporário e efêmero para o Linux, ele pode ser usado intencionalmente como uma forma de trabalhar arquivos com alta intensidade de leitura e escrita sem sair do bloco CPU/Memória, onde a largura de banda é superior a 60 GB por segundo e a latência é de nano segundos.
Eu descobri esse recurso lendo a documentação do Kernel a 20 anos atras e passei a usá-lo de maneira simples com o RAMFS (sistema de arquivos na RAM).
Quando surgiu a necessidade de criar um FS na RAM com suporte a overlay, o RAMFS não suportou e migrei para o RAMDISK (dispositivo de blocos na RAM).
Minha principal surpresa foi ao usá-lo para compilação de programas: ao compilar o kernel ou sistemas em nodeJS (dependências infinitas) sobre um disco SATA, SAS ou NVME a demora de I/O para cada arquivo pequeno que precisava ser lido e escrito era muito grande.
Ao mover essa demanda de I/O temporário para o RAMDISK o sistema compilou 8x mais rápido. O gargalo passou a ser na CPU e na rede!
Uso comum do RD:
- Treinamento: aprender e praticar tecnologias de disco e sistema de arquivos (LVM2, RAID, DM-CRYPT, SDS em geral, etc);
- Criação de discos template para máquinas virtuais;
- Rodar sistemas que exigem I/O rápido para performar bem (PostgreSQL, etc);
- Compilar sistemas com grande quantidade de arquivos;
- Armazenar e criar sistemas de containers;
É importante tomar cuidado, o RD não persiste nada. Ao desligar ou reiniciar o Linux TUDO É PERDIDO.
Se quiser persistir você deverá usar um sistema de sincronia e backup.
3 – Parâmetros e configurações de RamDisk
O RD é provido pelo módulo “brd” (Ram backed block device driver) do kernel Linux.
# Informações sobre o "brd":
modinfo brd;
# filename: /lib/modules/.../kernel/drivers/block/brd.ko.xz
# alias: rd
# alias: block-major-1-*
# license: GPL
# description: Ram backed block device driver
# depends:
# intree: Y
# name: brd
# retpoline: Y
# vermagic: 6.12.57+deb13-amd64 SMP preempt mod_unload modversions
# parm: rd_nr:Maximum number of brd devices (int)
# parm: rd_size:Size of each RAM disk in kbytes. (ulong)
# parm: max_part:Num Minors to reserve between devices (int)
Podemos ver os seguintes detalhes no comando acima:
- Nome do módulo: brd ou rd, para uso no modprobe;
- Parâmetros aceitos no setup do módulo:
- rd_nr: número de discos de RAM a inicializar, padrão: 16 discos;
- rd_size: tamanho em kbytes do disco de RAM, padrão 4 MB (4096 KB);
- Esse valor pode ser guiado pela variável ramdisk_size do cmdline do kernel durante o boot;
- max_part: quantidade de partições por RD, padrão 1 (não é um parâmetro relevante).
Características do RamDISK:
- BlockDevice: dispositivos de blocos são criados nos endereços /dev/ramN, onde N é o numero de discos desejados ao subir o módulo (parâmetro rd_nr, padrão 16). Apenas subir o módulo resulta na criação de 16 discos RD: /dev/ram0 até /dev/ram15;
- Static: o módulo brd não aceita reconfiguração a quente. Se você criou o RD com 1G e deseja aumentar para 2G, terá que descarregar o modulo apagando todos os RDs para então subir novamente o módulo com os novos parâmetros;
- Overcommit: integra a natureza overcommit de RAM do Linux (Lazy/on-demand), ou seja, ao criar um RD de 2G isso não resulta em uso imediato desses 2G. Zero bytes serão alocados inicialmente e a medida que você grava blocos a memória é realmente alocada para eles.
- OOM: erros de out-of-memory (acabou a memória) são resolvidos negando a escrita em novos blocos com o erro input/output error;
- SWAP: o RD faz uso da memória virtual, o que significa que todo o pool de memória real (DDR) e memória virtual (swap) estarão disponíveis para alocação de blocos;
- Configure o swappness para zero ou próximo de zero;
- Overhead: infelizmente o Linux aplica o buffer de escrita (escrita atrasada) e o cache de leitura para cache de blocos mesmo em operações no RD, o que significa um uso duplo de memória (ele grava na memória do pipeline do buffer para então escrever no RD, que também está na memória, fazendo a escrita duas vezes).
- Isso é possível de reduzir orientando softwares e sistemas de arquivos a fazer uso do parâmetro de I/O chamado odirect, que envia os bytes direto para o dispositivo de blocos sem passar pelo buffer;
4 – Criando um disco na RAM
4.1 – Instalar programas
Alguns comandos necessários para operar discos e partições:
# Instalar ferramentas:
apt -y install e2fsprogs;
apt -y install debugfs;
apt -y install util-linux;
apt -y install fdisk;
apt -y install fio;
apt -y install hdparm;
apt -y install ioping;
4.2 – Suporte no kernel e driver
É necessário carregar o módulo brd.
# Colocar "brd" nos modulos iniciais:
echo 'brd' > /etc/modules-load.d/brd.conf;
# Caso deseje carregar o brd ainda na fase de initrd:
mkdir -p /etc/initramfs;
echo 'brd rd_nr=1 rd_size=8388608' > /etc/initramfs/modules;
# Configurando os parâmetros iniciais do "brd" manualmente:
# - Tamanhos (especificar em KB):
# Gigas | Kbytes
#------------------------
# 1 G | 1048576
# 2 G | 2097152
# 4 G | 4194304
# 8 G | 8388608
# 16 G | 16777216
#------------------------
# Criar apenas 1 disco de 16G:
(
echo 'options brd rd_nr=1 max_part=3 rd_size=16777216';
) > /etc/modprobe.d/brd.conf;
# Carregar módulo:
modprobe brd;
Conferindo ambiente e criação do RamDISK:
# Verificar informacoes no kernel:
grep ram /proc/devices;
grep ram /proc/diskstats;
grep brd /proc/modules;
grep ram /proc/mounts;
grep ram /proc/partitions;
# Conferir dispositivo de blocos /dev/ram0
fdisk -l /dev/ram0;
# Disk /dev/ram0: 16 GiB, 17179869184 bytes, 33554432 sectors
# Units: sectors of 1 * 512 = 512 bytes
# Sector size (logical/physical): 512 bytes / 4096 bytes
# I/O size (minimum/optimal): 4096 bytes / 4096 bytes
4.3 – Testar velocidade de I/O na RAM
Testando capacidade de escrita
# Escrita com zeros:
# - Escrever 512 blocos de 4M (2Gbytes)
dd if=/dev/zero of=/dev/ram0 count=512 bs=4194304 status=progress;
# 2130706432 bytes (2.1 GB, 2.0 GiB) copied, 1 s, 2.1 GB/s
# 512+0 records in
# 512+0 records out
# 2147483648 bytes (2.1 GB, 2.0 GiB) copied, 2.76339 s, 777 MB/s
# - Escrever 1024 blocos de 2M (2Gbytes)
dd if=/dev/zero of=/dev/ram0 count=1024 bs=2097152 status=progress;
# 1759510528 bytes (1.8 GB, 1.6 GiB) copied, 1 s, 1.8 GB/s
# 1024+0 records in
# 1024+0 records out
# 2147483648 bytes (2.1 GB, 2.0 GiB) copied, 2.85374 s, 753 MB/s
# - Escrever 2048 blocos de 1M (2Gbytes)
dd if=/dev/zero of=/dev/ram0 count=2048 bs=1048576 status=progress;
# 2107637760 bytes (2.1 GB, 2.0 GiB) copied, 1 s, 2.1 GB/s
# 2048+0 records in
# 2048+0 records out
# 2147483648 bytes (2.1 GB, 2.0 GiB) copied, 2.64519 s, 812 MB/s
Testar capacidade de escrita sem buffer do kernel
# Escrita sem buffer do kernel (mais rápido):
# - Escrever 512 blocos de 4M (2Gbytes)
dd if=/dev/zero of=/dev/ram0 count=512 bs=4194304 oflag=direct status=progress;
# 512+0 records in
# 512+0 records out
# 2147483648 bytes (2.1 GB, 2.0 GiB) copied, 0.574772 s, 3.7 GB/s
# - Escrever 1024 blocos de 2M (2Gbytes)
dd if=/dev/zero of=/dev/ram0 count=1024 bs=2097152 oflag=direct status=progress;
# 1024+0 records in
# 1024+0 records out
# 2147483648 bytes (2.1 GB, 2.0 GiB) copied, 0.560641 s, 3.8 GB/s
# - Escrever 2048 blocos de 1M (2Gbytes)
dd if=/dev/zero of=/dev/ram0 count=2048 bs=1048576 oflag=direct status=progress;
# 2048+0 records in
# 2048+0 records out
# 2147483648 bytes (2.1 GB, 2.0 GiB) copied, 0.558049 s, 3.8 GB/s
Podemos observar velocidades muito superiores quando o buffer de escrita do kernel é ignorado (oflag=direct) e os blocos vão direto para o dispositivos de blocos /dev/ram0.
Teste de capacidade de leitura
# Teste de leitura
# - Ler 512 blocos de 4M (2Gbytes)
dd if=/dev/ram0 of=/dev/null count=512 bs=4194304 status=progress;
# 2134900736 bytes (2.1 GB, 2.0 GiB) copied, 1 s, 2.1 GB/s
# 512+0 records in
# 512+0 records out
# 2147483648 bytes (2.1 GB, 2.0 GiB) copied, 1.19463 s, 1.8 GB/s
# - Ler 1024 blocos de 2M (2Gbytes)
dd if=/dev/ram0 of=/dev/null count=1024 bs=2097152 status=progress;
# 1024+0 records in
# 1024+0 records out
# 2147483648 bytes (2.1 GB, 2.0 GiB) copied, 1.1758 s, 1.8 GB/s
# - Ler 2048 blocos de 1M (2Gbytes)
dd if=/dev/ram0 of=/dev/null count=2048 bs=1048576 status=progress;
# 2126512128 bytes (2.1 GB, 2.0 GiB) copied, 1 s, 2.1 GB/s
# 2048+0 records in
# 2048+0 records out
# 2147483648 bytes (2.1 GB, 2.0 GiB) copied, 1.19769 s, 1.8 GB/s
Teste de IOPs
Os testes de IOPs ajudam a medir quantas operações de IO (leitura+escrita) o dispositivo é capaz de suportar.
# Teste sequencial de leitura
fio \
--name=seq-read \
--rw=read \
--bs=1M \
--size=1G \
--direct=1 \
--filename=/dev/ram0;
# Teste sequencial de escrita
fio \
--name=seq-write \
--rw=write \
--bs=1M \
--size=1G \
--direct=1 \
--filename=/dev/ram0;
# Teste aleatório 4K (IOPS)
fio \
--name=rand-4k \
--rw=randread \
--bs=4K \
--size=1G \
--direct=1 \
--filename=/dev/ram0;
# Teste misto leitura/escrita
fio \
--name=mixed \
--rw=randrw \
--rwmixread=70 \
--bs=4K \
--size=1G \
--direct=1 \
--filename=/dev/ram0;
# Teste completo com múltiplos parâmetros
fio \
--name=ramdisk-test \
--filename=/dev/ram0 \
--size=2G \
--direct=1 \
--rw=randrw \
--bs=4k \
--ioengine=libaio \
--iodepth=32 \
--numjobs=4 \
--runtime=60 \
--group_reporting;
Testes com HDPARM
# Teste de leitura
hdparm -t /dev/ram0;
# /dev/ram0:
# Timing buffered disk reads: 6184 MB in 3.00 seconds = 2061.00 MB/sec
hdparm -T /dev/ram0;
# /dev/ram0:
# Timing cached reads: 17848 MB in 1.98 seconds = 8994.84 MB/sec
# Teste com cache
hdparm --direct -t /dev/ram0;
# /dev/ram0:
# Timing O_DIRECT disk reads: 16384 MB in 1.44 seconds = 11367.67 MB/sec
Testes com IOPING
# Teste de latência
ioping -c 10 /dev/ram0;
# 4 KiB <<< /dev/ram0 (block device 16 GiB): request=1 time=17.7 us (warmup)
# 4 KiB <<< /dev/ram0 (block device 16 GiB): request=2 time=33.6 us
# 4 KiB <<< /dev/ram0 (block device 16 GiB): request=3 time=27.9 us
# 4 KiB <<< /dev/ram0 (block device 16 GiB): request=4 time=31.7 us
# 4 KiB <<< /dev/ram0 (block device 16 GiB): request=5 time=31.8 us
# 4 KiB <<< /dev/ram0 (block device 16 GiB): request=6 time=28.8 us
# 4 KiB <<< /dev/ram0 (block device 16 GiB): request=7 time=31.9 us
# 4 KiB <<< /dev/ram0 (block device 16 GiB): request=8 time=31.9 us
# 4 KiB <<< /dev/ram0 (block device 16 GiB): request=9 time=29.9 us
# 4 KiB <<< /dev/ram0 (block device 16 GiB): request=10 time=31.5 us
# Teste de taxa de transferência
ioping -R /dev/ram0;
# --- /dev/ram0 (block device 16 GiB) ioping statistics ---
# 689.6 k requests completed in 2.02 s, 2.63 GiB read, 341.6 k iops, 1.30 GiB/s
# generated 689.6 k requests in 3.00 s, 2.63 GiB, 229.9 k iops, 897.9 MiB/s
# min/avg/max/mdev = 2.43 us / 2.93 us / 1.19 ms / 4.94 us
# Teste com I/O direto
ioping -c 10 -D /dev/ram0;
# 4 KiB <<< /dev/ram0 (block device 16 GiB): request=1 time=13.0 us (warmup)
# 4 KiB <<< /dev/ram0 (block device 16 GiB): request=2 time=21.1 us
# 4 KiB <<< /dev/ram0 (block device 16 GiB): request=3 time=21.3 us
# 4 KiB <<< /dev/ram0 (block device 16 GiB): request=4 time=25.7 us
# 4 KiB <<< /dev/ram0 (block device 16 GiB): request=5 time=21.3 us
# 4 KiB <<< /dev/ram0 (block device 16 GiB): request=6 time=20.4 us
# 4 KiB <<< /dev/ram0 (block device 16 GiB): request=7 time=20.9 us
# 4 KiB <<< /dev/ram0 (block device 16 GiB): request=8 time=18.5 us
# 4 KiB <<< /dev/ram0 (block device 16 GiB): request=9 time=21.2 us
# 4 KiB <<< /dev/ram0 (block device 16 GiB): request=10 time=25.9 us
#
# --- /dev/ram0 (block device 16 GiB) ioping statistics ---
# 9 requests completed in 196.3 us, 36 KiB read, 45.8 k iops, 179.1 MiB/s
# generated 10 requests in 9.00 s, 40 KiB, 1 iops, 4.44 KiB/s
# min/avg/max/mdev = 18.5 us / 21.8 us / 25.9 us / 2.28 us
4.4 – Particionando o RamDISK
Não é necessário particioná-lo, embora recomendável. O bd /dev/ram0 pode ser formatado diretamente e montado sem problemas.
Particionar é o comportamento padrão para que o RD possa ser convertido em disco para máquinas virtuais (RAW, QCOW2, VMDK). Nada impede o uso para qualquer outra finalidade e ainda deixa o disco de RAM em estado de arte:
# Criar partição padrão GPT no RAMDISK
# - Tipos de particoes
GUID_BIOS="21686148-6449-6E6F-744E-656564454649"; # bios boot
GUID_ESP="C12A7328-F81F-11D2-BA4B-00A0C93EC93B"; # esp efi/uefi
GUID_LINUX="0FC63DAF-8483-4772-8E79-3D69D8477DE4"; # linux (root)
# Apagar primeiros setores (capricho):
dd if=/dev/zero of=/dev/ram0 bs=512 count=2048;
# 1. Particionar GPT:
parted /dev/ram0 --script -- mklabel gpt;
# 2. Criar partição 1 - BIOS boot (setores 34 a 2047)
parted /dev/ram0 --script -- mkpart primary 34s 2047s;
parted /dev/ram0 --script -- type 1 "$GUID_BIOS";
# 3. Criar partição 2 - EFI System (setores 2048 a 2099199 = 1GB)
parted /dev/ram0 --script -- mkpart primary fat32 2048s 2099199s;
parted /dev/ram0 --script -- type 2 "$GUID_ESP";
# 4. Criar partição 3 - Linux LVM (setores 2099200 a 20971486 = 9.5GB)
parted /dev/ram0 --script -- mkpart primary 2099200s '100%';
parted /dev/ram0 --script -- type 3 "$GUID_LINUX";
# 5. Verificar o resultado
parted /dev/ram0 --script -- print;
# Model: RAM Drive (brd)
# Disk /dev/ram0: 17.2GB
# Sector size (logical/physical): 512B/4096B
# Partition Table: gpt
# Disk Flags:
#
# Number Start End Size File system Name Flags
# 1 17.4kB 1049kB 1031kB primary bios_grub
# 2 1049kB 1075MB 1074MB primary boot, esp
# 3 1075MB 17.2GB 16.1GB primary
Com esse esquema de partição, vamos operar no /dev/ram0p3 (tipo Linux):
4.5 – Formatando partição na RAM
O melhor sistema de arquivos para uso em blocos na RAM é o Ext2 pois ele não tem journal (estágio de escrita em área de trabalho do disco para em seguida gravar na localização final, dupla escrita).
Como ele é um sistema muito antigo, a melhor opção é o Ext4 sem journal, motivos:
- Ext4 sem journal se comporta como o ext2;
- É compatível com tudo que é moderno;
- O Ext4 pode ser convertido para suportar journal caso deseje exportar o RD para uso externo (disco de VM);
Recursos dispensáveis no ext4 pois são desnecessários sobre o RD: has_journal, metadata_csum, resize_inode, 64bit, huge_file, dir_nlink, bigalloc, quota, encrypt.
Assim, para RD que será exportado para disco virtual, apenas alternar o journal, já para RD temporário podemos depenar o Ext4 em busca da performance máxima!
Criando sistema de arquivos Ext4 exportável:
# Formatar com ext4 sem journal:
echo 'y' | mkfs.ext4 -O '^has_journal' /dev/ram0p3;
# mke2fs 1.47.2 (1-Jan-2025)
# Discarding device blocks: done
# Creating filesystem with 3931648 4k blocks and 983040 inodes
# Filesystem UUID: 4caf42a2-016c-4666-b66f-d1dd92858c70
# Superblock backups stored on blocks:
# 32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208
#
# Allocating group tables: done
# Writing inode tables: done
# Writing superblocks and filesystem accounting information: done
# Ativar journal:
tune2fs -O 'has_journal' /dev/ram0p3;
# Desativar journal:
tune2fs -O '^has_journal' /dev/ram0p3;
# Conferindo features do ext4:
tune2fs -l /dev/ram0p3 | grep features;
tune2fs -l /dev/ram0p3 | grep has_journal && echo "# Journal ATIVADO";
tune2fs -l /dev/ram0p3 | grep has_journal || echo "# Journal DESATIVADO";
4.6 – Montando sistema de arquivos
Ao montar um FS sobre a RAM não precisamos nos preocupar com muito detalhes dado a natureza fulgaz e dispensável dos dados. Opções de montagem para máxima performance:
- noatime e relatime: não atualizar inode quando o arquivo for acessado;
- nodiratime: não atualizar inode quando o diretório for acessado;
- nobarrier: destivar barreiras de escrita;
- commit=3600: fazer commit longo (sync), em vez do padrão 5s;
- data=writeback: modo journal rápido, caso tenha jornal;
- dioread_nolock: desativar looks de paralelismo;
- nodiscard: não fazer trim (esse recurso só é util em SSD);
- nodelalloc: desativa delayed allocation (menos overhead);
- noinit_itable: não inicializa tabela de inodes em background;
- dax: Direct Access Mode, desativa completamente o page cache mas pode não estar disponível em todos os sistemas;
Montando:
# Pasta para montagem: /ram
RAMDIR="/ram";
mkdir -p $RAMDIR;
# Montar em modo ultra-rapido:
RAMDEV="/dev/ram0p3";
# Desmontar caso esteja montado:
umount /ram 2>/dev/null;
# Montar modo simples:
mount \
-t ext4 \
-o 'noatime,nodiratime,relatime,nodiscard' \
$RAMDEV \
$RAMDIR;
# Tentar ativar opcoes na montagem:
mount -o remount,rw,noatime $RAMDIR;
mount -o remount,rw,nodiratime $RAMDIR;
mount -o remount,rw,relatime $RAMDIR;
mount -o remount,rw,nobarrier $RAMDIR;
mount -o remount,rw,commit=3600 $RAMDIR;
mount -o remount,rw,dioread_nolock $RAMDIR;
mount -o remount,rw,nodelalloc $RAMDIR;
mount -o remount,rw,noinit_itable $RAMDIR;
mount -o remount,rw,nodiscard $RAMDIR;
mount -o remount,rw,dax $RAMDIR;
# Conferindo atributos de montagem:
grep /ram /proc/mounts;
Montagem concluída. A pasta /ram já pode ser usada para trabalhar com arquivos.
4.6 – Testando velocidade do sistema de arquivos na RAM
Vamos verificar qual a capacidade de escrita e leitura de arquivos. É bom comparar os testes no RD e comparar com os testes no disco para ver se há vantagem significante para seu ambiente (se você tem NVME de ultima geração).
# Escrita sem buffer do kernel (mais rápido):
# - Escrever 512 blocos de 4M (2Gbytes)
dd if=/dev/zero of=/ram/dd.bin count=512 bs=4194304 oflag=direct status=progress;
# 512+0 records in
# 512+0 records out
# 2147483648 bytes (2.1 GB, 2.0 GiB) copied, 0.656324 s, 3.3 GB/s
# - Escrever 1024 blocos de 2M (2Gbytes)
dd if=/dev/zero of=/ram/dd.bin count=1024 bs=2097152 oflag=direct status=progress;
# 1024+0 records in
# 1024+0 records out
# 2147483648 bytes (2.1 GB, 2.0 GiB) copied, 0.659488 s, 3.3 GB/s
# - Escrever 2048 blocos de 1M (2Gbytes)
dd if=/dev/zero of=/ram/dd.bin count=2048 bs=1048576 oflag=direct status=progress;
# 2048+0 records in
# 2048+0 records out
# 2147483648 bytes (2.1 GB, 2.0 GiB) copied, 0.639682 s, 3.4 GB/s
# - Apagar arquivo gerado no teste:
rm -f /ram/dd.bin;
# Teste sequencial de leitura
fio \
--name=seq-read \
--rw=read \
--bs=1M \
--size=1G \
--direct=1 \
--filename=/ram/fio-test.bin;
# Teste sequencial de escrita
fio \
--name=seq-write \
--rw=write \
--bs=1M \
--size=1G \
--direct=1 \
--filename=/ram/fio-test.bin;
# Teste aleatório 4K (IOPS)
fio \
--name=rand-4k \
--rw=randread \
--bs=4K \
--size=1G \
--direct=1 \
--filename=/ram/fio-test.bin;
# Teste misto leitura/escrita
fio \
--name=mixed \
--rw=randrw \
--rwmixread=70 \
--bs=4K \
--size=1G \
--direct=1 \
--filename=/ram/fio-test.bin;
# Teste completo com múltiplos parâmetros
fio \
--name=ramdisk-test \
--filename=/ram/fio-test.bin \
--size=2G \
--direct=1 \
--rw=randrw \
--bs=4k \
--ioengine=libaio \
--iodepth=32 \
--numjobs=4 \
--runtime=60 \
--group_reporting;
# - Apagar arquivo gerado no teste:
rm -f /ram/fio-test.bin;
5 – Serviço RamDISK
Agora vem a parte boa: transformar a montagem do RamDisk em um serviço a ser executado durante o boot do Debian e criar scripts para gerir eventos para que outros sistemas possam colocar, tirar e fazer backup de arquivos sendo trabalhados na RAM.
Variáveis definidas em /etc/default/ramdisk:
- RAMDISK_SIZE: tamanho em gigabytes do RD;
- RAMDISK_MOUNT: montar RD em diretório? Valores: yes (padrão) ou no;
- RAMDISK_FS: tipo de sistema de arquivos a formatar, padrão ext4;
- RAMDISK_DIR: diretório para montar RD, se montagem ativa, padrão /ram;
- RAMDISK_OPTIONS: opções do ponto de montagem, por padrão tentar todas as opções aceleradas de montagem;
# Criar arquivos com variáveis de ambiente padrão:
(
echo 'RAMDISK_SIZE=8';
echo 'RAMDISK_MOUNT=yes';
echo 'RAMDISK_FS=ext4';
echo 'RAMDISK_DIR=/ram';
echo 'RAMDISK_OPTIONS=""';
) > /etc/default/ramdisk;
Arquivos e diretórios do serviço RamDisk:
- /etc/systemd/system/ramdisk.service: configuração do serviço no SystemD;
- /etc/default/ramdisk: manifesto de variáveis de ambiente;
- /usr/local/sbin/ramdisk-env: script para gerar variáveis de ambiente;
- /usr/local/sbin/ramdisk-run-scripts: script para rodar eventos;
- /usr/local/sbin/ramdisk-part-gpt: script para particionar RD;
- /usr/local/sbin/ramdisk-fs-setup: script para formatar e montar FS;
- /usr/local/sbin/ramdisk-start: script para iniciar RD;
- /usr/local/sbin/ramdisk-stop: script para desligar RD;
Diretórios para scripts que desejam se integrar ao uso do RamDisk:
- /etc/ramdisk-pre-up.d: Pasta com scripts a executar antes do RD ser construído (/dev/ram0 já existe nesse ponto mas não foi particionado);
- /etc/ramdisk-up.d: Pasta com scripts a executar quando o RD estiver pronto para uso (/dev/ram0 montado no diretório). Momento ideal para instalar arquivos e programas no RD (SETUP);
- /etc/ramdisk-pre-down.d: Pasta com scripts a executar antes do RD ser desativado (/dev/ram0 ainda montado no diretório). Momento ideal para BACKUP;
- /etc/ramdisk-down.d: Pasta com scripts a serem executados após o RD ser desativado;
Criando serviço:
# Criar diretorios de scripts:
mkdir -p /etc/ramdisk-pre-up.d; chmod 755 /etc/ramdisk-pre-up.d;
mkdir -p /etc/ramdisk-up.d; chmod 755 /etc/ramdisk-up.d;
mkdir -p /etc/ramdisk-pre-down.d; chmod 755 /etc/ramdisk-pre-down.d;
mkdir -p /etc/ramdisk-down.d; chmod 755 /etc/ramdisk-down.d;
# Script para obter variáveis de ambiente com valores default
(
echo '#!/bin/bash';
echo;
echo '[ -f /etc/default/ramdisk ] && . /etc/default/ramdisk;';
echo;
echo '# Carregar defautls';
echo '[ "x$RAMDISK_SIZE" = "x" ] && RAMDISK_SIZE=8;';
echo '[ "x$RAMDISK_MOUNT" = "x" ] && RAMDISK_MOUNT=yes;';
echo '[ "x$RAMDISK_FS" = "x" ] && RAMDISK_FS=ext4;';
echo '[ "x$RAMDISK_DIR" = "x" ] && RAMDISK_DIR="/ram";';
echo '[ "x$RAMDISK_OPTIONS" = "x" ] && RAMDISK_OPTIONS="fast";';
echo 'RAMDISK_BSIZE=$((RAMDISK_SIZE * 1024 * 1024));';
echo 'RAMDISK_KARG="options brd rd_nr=1 max_part=3 rd_size=$RAMDISK_BSIZE";';
echo;
echo '# Exportar para ambiente';
echo 'export RAMDISK_SIZE="$RAMDISK_SIZE";';
echo 'export RAMDISK_MOUNT="$RAMDISK_MOUNT";';
echo 'export RAMDISK_DIR="$RAMDISK_DIR";';
echo 'export RAMDISK_FS="$RAMDISK_FS";';
echo 'export RAMDISK_OPTIONS="$RAMDISK_OPTIONS";';
echo 'export RAMDISK_BSIZE="$RAMDISK_BSIZE";';
echo 'export RAMDISK_KARG="$RAMDISK_KARG";';
echo;
) > /usr/local/sbin/ramdisk-env;
chmod +x /usr/local/sbin/ramdisk-env;
# Script para executar os scripts do evento
(
echo '#!/bin/bash';
echo;
echo '. /usr/local/sbin/ramdisk-env;';
echo;
echo 'EVDIR="/etc/ramdisk-$1.d";';
echo '[ -d "$EVDIR" ] || exit 1;';
echo 'cd "$EVDIR" || exit 2;';
echo;
echo 'for script in $EVDIR/*; do';
echo ' if [ -x "$script" ]; then';
echo ' "$script" || true;';
echo ' fi;';
echo 'done;';
echo;
) > /usr/local/sbin/ramdisk-run-scripts;
chmod +x /usr/local/sbin/ramdisk-run-scripts;
# Script para particionar ramdisk
(
echo '#!/bin/bash';
echo;
echo '# Criar partição padrão GPT no RAMDISK';
echo '# - Tipos de particoes';
echo 'GUID_BIOS="21686148-6449-6E6F-744E-656564454649"; # bios boot';
echo 'GUID_ESP="C12A7328-F81F-11D2-BA4B-00A0C93EC93B"; # esp efi/uefi';
echo 'GUID_LINUX="0FC63DAF-8483-4772-8E79-3D69D8477DE4"; # linux (root)';
echo;
echo '# Apagar primeiros setores (capricho):';
echo 'dd if=/dev/zero of=/dev/ram0 bs=512 count=2048 oflag=direct 2>/dev/null;';
echo;
echo '# 1. Particionar GPT:';
echo 'parted /dev/ram0 --script -- mklabel gpt 2>/dev/null;';
echo;
echo '# 2. Criar partição 1 - BIOS boot (setores 34 a 2047)';
echo 'parted /dev/ram0 --script -- mkpart primary 34s 2047s 2>/dev/null;';
echo 'parted /dev/ram0 --script -- type 1 "$GUID_BIOS";';
echo;
echo '# 3. Criar partição 2 - EFI System (setores 2048 a 2099199 = 1GB)';
echo 'parted /dev/ram0 --script -- mkpart primary fat32 2048s 2099199s;';
echo 'parted /dev/ram0 --script -- type 2 "$GUID_ESP";';
echo;
echo '# 4. Criar partição 3 - Linux LVM (setores 2099200 a 20971486 = 9.5GB)';
echo 'parted /dev/ram0 --script -- mkpart primary 2099200s '100%';';
echo 'parted /dev/ram0 --script -- type 3 "$GUID_LINUX";';
echo;
) > /usr/local/sbin/ramdisk-part-gpt;
chmod +x /usr/local/sbin/ramdisk-part-gpt;
# Fazer setup do sistema de arquivos no ramdisk
(
echo '#!/bin/bash';
echo;
echo '. /usr/local/sbin/ramdisk-env;';
echo;
echo '# Dispositivo de blocos alvo';
echo 'BDEV="/dev/ram0p3";';
echo '[ -e "$BDEV" ] || { echo "$BDEV not found"; BDEV="/dev/ram0"; };';
echo '[ -e "$BDEV" ] || { echo "$BDEV not found"; exit 11; };';
echo;
echo '# Montagem desativa, deixar normal';
echo '[ "$RAMDISK_MOUNT" = "yes" ] || { echo "skip mount"; exit 0; };';
echo;
echo '# Programa formatador:';
echo '[ -x "/usr/sbin/mkfs.$RAMDISK_FS" ] || {';
echo ' echo "/usr/sbin/mkfs.$RAMDISK_FS not found";';
echo ' exit 12;';
echo '};';
echo;
echo '# Opcoes de formatacao';
echo 'MKFS_OPTION="";';
echo 'if [ "$RAMDISK_OPTIONS" = "fast" -a "$RAMDISK_FS" = "ext4" ]; then';
echo ' MKFS_OPTION="-O ^has_journal";';
echo 'fi;';
echo;
echo '# Criar diretorio';
echo '[ -d "$RAMDISK_DIR" ] || mkdir -p "$RAMDISK_DIR";';
echo;
echo '# Se ja estiver montado, ignorar, nao montar overlay';
echo 'egrep -q "$RAMDISK_DIR" /proc/mounts && {';
echo ' echo "pre-mounted $RAMDISK_DIR";';
echo ' exit 0;';
echo '};';
echo;
echo '# Formatar:';
echo 'echo "Format: mkfs.$RAMDISK_FS $MKFS_OPTION $BDEV";';
echo 'echo "y" | /usr/sbin/mkfs.$RAMDISK_FS $MKFS_OPTION $BDEV || {';
echo ' echo "Format: failure mkfs.$RAMDISK_FS $MKFS_OPTION $BDEV";';
echo ' # Tentar formatar novamente:';
echo ' echo "Format: mkfs.$RAMDISK_FS $BDEV";';
echo ' echo "y" | /usr/sbin/mkfs.$RAMDISK_FS $BDEV || {';
echo ' echo "Format: failure mkfs.$RAMDISK_FS $BDEV";';
echo ' exit 13;';
echo ' };';
echo '};';
echo;
echo '# Montar RD no diretorio';
echo 'WORKS=0;';
echo 'MOUNT="mount -t ext4 $BDEV $RAMDISK_DIR";';
echo 'echo "Mounting: $BDEV on $RAMDISK_DIR";';
echo 'if [ "$RAMDISK_OPTIONS" = "fast" -a "$RAMDISK_FS" = "ext4" ]; then';
echo ' # Modo optimizado';
echo ' OPT_BASIC="noatime,nodiratime,relatime";';
echo ' OPT_TRY01="nodiscard,noinit_itable,commit=3600,nobarrier";';
echo ' OPT_TRY02="nodiscard,noinit_itable,commit=3600";';
echo ' OPT_TRY03="nodiscard,noinit_itable,nobarrier";';
echo ' OPT_TRY04="nodiscard,noinit_itable,nodelalloc";';
echo ' OPT_TRY05="nodiscard,nodelalloc";';
echo ' OPT_TRY06="nodiscard,noinit_itable";';
echo ' OPT_TRY07="nodiscard,nobarrier";';
echo ' OPT_TRY08="nodiscard";';
echo ' # Tentar opcoes de montagem';
echo ' [ "$WORKS" = "0" ] && $MOUNT -o $OPT_BASIC,$OPT_TRY01 2>/dev/null;';
echo ' [ "$?" = "0" ] && WORKS="1";';
echo ' [ "$WORKS" = "0" ] && $MOUNT -o $OPT_BASIC,$OPT_TRY01 2>/dev/null;';
echo ' [ "$?" = "0" ] && WORKS="2";';
echo ' [ "$WORKS" = "0" ] && $MOUNT -o $OPT_BASIC,$OPT_TRY02 2>/dev/null;';
echo ' [ "$?" = "0" ] && WORKS="3";';
echo ' [ "$WORKS" = "0" ] && $MOUNT -o $OPT_BASIC,$OPT_TRY03 2>/dev/null;';
echo ' [ "$?" = "0" ] && WORKS="4";';
echo ' [ "$WORKS" = "0" ] && $MOUNT -o $OPT_BASIC,$OPT_TRY04 2>/dev/null;';
echo ' [ "$?" = "0" ] && WORKS="5";';
echo ' [ "$WORKS" = "0" ] && $MOUNT -o $OPT_BASIC,$OPT_TRY05 2>/dev/null;';
echo ' [ "$?" = "0" ] && WORKS="6";';
echo ' [ "$WORKS" = "0" ] && $MOUNT -o $OPT_BASIC,$OPT_TRY06 2>/dev/null;';
echo ' [ "$?" = "0" ] && WORKS="7";';
echo ' [ "$WORKS" = "0" ] && $MOUNT -o $OPT_BASIC,$OPT_TRY07 2>/dev/null;';
echo ' [ "$?" = "0" ] && WORKS="8";';
echo ' [ "$WORKS" = "0" ] && $MOUNT -o $OPT_BASIC,$OPT_TRY08 2>/dev/null;';
echo ' [ "$?" = "0" ] && WORKS="9";';
echo ' [ "$WORKS" = "0" ] && $MOUNT -o $OPT_BASIC 2>/dev/null;';
echo ' [ "$?" = "0" ] && WORKS="10";';
echo ' [ "$WORKS" = "0" ] && $MOUNT 2>/dev/null;';
echo ' [ "$?" = "0" ] && WORKS="11";';
echo;
echo 'else';
echo ' # Modo normal';
echo ' [ "$WORKS" = "0" ] && $MOUNT 2>/dev/null;';
echo ' [ "$?" = "0" ] && WORKS="11";';
echo 'fi;';
echo;
echo '# Ajuste de permissao';
echo 'chmod 1777 "$RAMDISK_DIR"';
echo;
echo '# Testar se a montagem base funcionou';
echo 'if [ "$WORKS" = "0" ]; then';
echo ' echo "Mounting: failure on $MOUNT";';
echo ' exit 14;';
echo 'fi;';
echo;
) > /usr/local/sbin/ramdisk-fs-setup;
chmod +x /usr/local/sbin/ramdisk-fs-setup;
# Script para iniciar ramdisk
(
echo '#!/bin/bash';
echo;
echo '. /usr/local/sbin/ramdisk-env;';
echo;
echo '# Setup';
echo '[ -f /etc/modules-load.d/brd.conf ] || {';
echo ' echo "brd" > /etc/modules-load.d/brd.conf;';
echo '};';
echo 'CURRENT_KARG=$(head -1 /etc/modprobe.d/brd.conf);';
echo '[ "$RAMDISK_KARG" = "$CURRENT_KARG" ] || {';
echo ' echo "$RAMDISK_KARG" > /etc/modprobe.d/brd.conf;';
echo '};';
echo;
echo '# Carregar modulo';
echo '_modprobe(){';
echo ' echo "kernel: load brd";';
echo ' modprobe brd;';
echo '};';
echo 'egrep -q '^brd' /proc/modules || _modprobe;';
echo;
echo '# Detectar RD';
echo 'BDFOUND=0;';
echo 'for i in 1 2 3 4 5 6 7 8 9 10; do';
echo ' [ -e /dev/ram0 ] && { BDFOUND=1; break; };';
echo ' echo "kernel: wait brd";';
echo ' sleep 0.1;';
echo 'done;';
echo 'if [ "$BDFOUND" = "0" ]; then';
echo ' echo "kernel: RamDisk /dev/ram0 not found";';
echo ' exit 11;';
echo 'fi;';
echo;
echo '# Executar pre-up scripts';
echo '/usr/local/sbin/ramdisk-run-scripts "pre-up";';
echo;
echo '# Particionar ramdisk';
echo '/usr/local/sbin/ramdisk-part-gpt;';
echo;
echo '# Formatar e montar';
echo '/usr/local/sbin/ramdisk-fs-setup || exit 11;';
echo;
echo '# Executar up scripts';
echo '/usr/local/sbin/ramdisk-run-scripts "up";';
echo;
) > /usr/local/sbin/ramdisk-start;
chmod +x /usr/local/sbin/ramdisk-start;
# Script para parar ramdisk
(
echo '#!/bin/bash';
echo;
echo '. /usr/local/sbin/ramdisk-env;';
echo;
echo '# Desmontar apenas se estiver montado';
echo 'egrep -q "$RAMDISK_DIR" /proc/mounts && {';
echo ' # Executar pre-down scripts';
echo ' echo "scripts: pre-down";';
echo ' /usr/local/sbin/ramdisk-run-scripts "pre-down";';
echo;
echo ' # Desmontar';
echo ' echo "umount $RAMDISK_DIR";';
echo ' sync;';
echo ' umount /dev/ram0p3 2>/dev/null; sn="$?";';
echo ' [ "$sn" = "0" ] || {';
echo ' for t in 1 2 3; do';
echo ' echo "umount $RAMDISK_DIR - try $t";';
echo ' umount /dev/ram0p3 2>/dev/null && break;';
echo ' umount -f /dev/ram0p3 2>/dev/null && break;';
echo ' sleep 0.1;';
echo ' done;';
echo ' };';
echo;
echo ' # Executar down scripts';
echo ' echo "scripts: down";';
echo ' /usr/local/sbin/ramdisk-run-scripts "down";';
echo;
echo '};';
echo;
echo '# Descarregar ramdisk do kernel para novos parametros';
echo 'CURRENT_KARG=$(head -1 /etc/modprobe.d/brd.conf);';
echo '[ "$RAMDISK_KARG" = "$CURRENT_KARG" ] || {';
echo ' egrep -q "brd" /proc/modules && {';
echo ' echo "kernel: unload brd";';
echo ' rmmod brd 2>/dev/null;';
echo ' };';
echo '};';
echo;
) > /usr/local/sbin/ramdisk-stop;
chmod +x /usr/local/sbin/ramdisk-stop;
# Unity de servico no SystemD
(
echo '[Unit]';
echo 'Description=RAM Disk Service';
echo 'DefaultDependencies=no';
echo 'After=local-fs-pre.target';
echo 'Before=local-fs.target';
echo 'Before=NetworkManager.service';
echo 'Before=network-pre.target';
echo '';
echo '[Service]';
echo 'Type=oneshot';
echo 'RemainAfterExit=yes';
echo 'EnvironmentFile=/etc/default/ramdisk';
echo 'ExecStart=/usr/local/sbin/ramdisk-start';
echo 'ExecStop=/usr/local/sbin/ramdisk-stop';
echo 'TimeoutSec=60';
echo '';
echo '[Install]';
echo 'WantedBy=local-fs.target';
) > /etc/systemd/system/ramdisk.service;
# Atualizar systemd:
systemctl daemon-reload;
Após instalar com o script acima, edite o /etc/default/ramdisk e preencha suas opções personalizadas, se desejar.
Iniciando RamDisk:
# Ativar o servico:
systemctl enable ramdisk.service;
systemctl start ramdisk.service;
systemctl status ramdisk.service;
# Parar ramdisk:
#- systemctl stop ramdisk.service;
Tudo pronto, agora você pode abusar de operações de alto I/O, e sincronizar o backup com outros sistemas para não perder dados!
Terminamos por hoje!
Patrick Brandão, patrickbrandao@gmail.com
