E fechando o ano de 2012, um último artigo sobre hibernação no Ubuntu 12.10, que é pra lembrar de entrar 2013 bem descansado :-)
Escrevo um artigo sobre a utilização do laptop sem ficar desligando, a guerra dos 100 dias, e seus benefícios e então me deparo justamente com um upgrade de sistema que não funciona com a hibernação. E em menos de 6 meses.
Tudo começou quando decidi passar o laptop de 32 bits para 64 (processador Intel Core i3). Minha limitação para não realizar tal tarefa antes era o sistema de SSL VPN, da Juniper, usado pela empresa para conectar remotamente. O sistema de VPN inicia um applet java que instala e roda uma biblioteca de 32 bits.
Já fazia algum tempo, eu vinha testando rodar isso em um modo "híbrido", com a funcionalidade de multi-arch ou com chroot de um ambiente de 32 bits. Como tudo estava funcionando nos testes, resolvi mudar o sistema aproveitando as férias de fim de ano.
Eu estava com Ubuntu 12.04 para i386, LTS, e resolvi instalar o 12.10 para amd64. Eu poderia ter escolhido um upgrade do sistema, que aparentemente funcionaria, mas resolvi fazer uma instalação nova, o que acabou me gerando a perda de dados do /home, mas essa é outra história.
Ao finalizar a instalação do 12.10 (e recuperar minha partição perdida - mas não completamente, duh!), eu me deparei com um kernel mais novo: 3.5.0-21-lowlatency. Anteriormente eu estava rodando o 3.2.7-pf (post factum). Não consegui fazer funcionar a hibernação de jeito nenhum. Nem com pm-suspend, nem com pm-hibernate, nem com o novo método, o pm-suspend-hybrid. Em todos os casos o sistema travava logo no início da hibernação e me deixava com uma tela preta, sistema operacional travado, mas máquina ligada. Somente um procedimento de "dedo-off" conseguia desligar o laptop. E sem nenhum log de problema por parte do ACPI.
Tentei recompilar o kernel, instalar outra versão, a versão sem lowlatency, enfim de tudo. Mas sem resultados.
Por um acaso, notei um erro no sistema pelo "dmesg". No começo achei que era problema da minha memória RAM.
[ 5.974345] BUG: unable to handle kernel paging request at 0000000000ff1000 [ 5.974351] IP: [] memcpy+0xd/0x110 [ 5.974359] PGD 1b0dbc067 PUD 1b0db9067 PMD 0 [ 5.974363] Oops: 0000 [#1] SMP [ 5.974366] CPU 2 [ 5.974367] Modules linked in: snd_page_alloc drm_kms_helper serio_raw drm coretemp cfg80211 kvm_intel i2c_algo_bit kvm videobuf2_vmalloc videobuf2_memops mxm_wmi wmi sony_laptop(+) intel_ips microcode mac_hid video mei lpc_ich btusb bluetooth xfs firewire_ohci firewire_core crc_itu_t sdhci_pci sdhci atl1c [ 5.974385] [ 5.974387] Pid: 637, comm: modprobe Not tainted 3.5.0-17-generic #28-Ubuntu Sony Corporation VPCS110GB/VAIO [ 5.974390] RIP: 0010:[] [] memcpy+0xd/0x110 [ 5.974394] RSP: 0018:ffff8801b0d31c40 EFLAGS: 00010246 [ 5.974395] RAX: ffff8801b0d31c90 RBX: ffff8801ae9545c0 RCX: 0000000000000001 [ 5.974397] RDX: 0000000000000000 RSI: 0000000000ff1000 RDI: ffff8801b0d31c90 [ 5.974398] RBP: ffff8801b0d31c58 R08: ffff8801b2218200 R09: 000000018040003e [ 5.974399] R10: 0000000000000000 R11: ffffffff813ad3e5 R12: ffff8801b0d31c90 [ 5.974401] R13: ffff8801b0d31caf R14: ffff8801b22f4800 R15: 0000000000000135 [ 5.974403] FS: 00007fb7feb92700(0000) GS:ffff8801bbc80000(0000) knlGS:0000000000000000 [ 5.974404] CS: 0010 DS: 0000 ES: 0000 CR0: 000000008005003b [ 5.974405] CR2: 0000000000ff1000 CR3: 00000001b271d000 CR4: 00000000000007e0 [ 5.974407] DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000 [ 5.974408] DR3: 0000000000000000 DR6: 00000000ffff0ff0 DR7: 0000000000000400 [ 5.974410] Process modprobe (pid: 637, threadinfo ffff8801b0d30000, task ffff8801b0d8c500) [ 5.974411] Stack: [ 5.974413] ffffffffa01a8893 0000000000000009 0000000000000035 ffff8801b0d31ce8 [ 5.974416] ffffffffa01a9b69 ffff8801b16986a0 ffff8801b0067800 ffff8801b0d31c80 [ 5.974419] ffffffff00000000 0000000000000009 0000000000000000 0000000000000000 [ 5.974422] Call Trace: [ 5.974430] [] ? sony_nc_buffer_call.constprop.12+0x43/0xa0 [sony_laptop] [ 5.974435] [] sony_nc_function_setup+0x2f9/0xab0 [sony_laptop] [ 5.974440] [] sony_nc_add+0x1f8/0x660 [sony_laptop] [ 5.974446] [] ? sysfs_do_create_link+0xeb/0x200 [ 5.974451] [] acpi_device_probe+0x50/0x11d [ 5.974457] [] driver_probe_device+0x7e/0x220 [ 5.974460] [] __driver_attach+0xab/0xb0 [ 5.974462] [] ? driver_probe_device+0x220/0x220 [ 5.974465] [] bus_for_each_dev+0x55/0x90 [ 5.974468] [] ? 0xffffffffa01b4fff [ 5.974470] [] driver_attach+0x1e/0x20 [ 5.974473] [] bus_add_driver+0x198/0x270 [ 5.974475] [] ? 0xffffffffa01b4fff [ 5.974478] [] driver_register+0x77/0x150 [ 5.974483] [] ? dmi_matches+0x53/0xc0 [ 5.974485] [] ? 0xffffffffa01b4fff [ 5.974488] [] acpi_bus_register_driver+0x3e/0x47 [ 5.974492] [] sony_laptop_init+0x57/0x1000 [sony_laptop] [ 5.974498] [] do_one_initcall+0x12a/0x180 [ 5.974502] [] sys_init_module+0xc2/0x230 [ 5.974508] [] system_call_fastpath+0x16/0x1b [ 5.974509] Code: 2b 43 50 88 43 4e 48 83 c4 08 5b 5d c3 90 e8 eb fb ff ff eb e6 90 90 90 90 90 90 90 90 90 48 89 f8 48 89 d1 48 c1 e9 03 83 e2 07 48 a5 89 d1 f3 a4 c3 20 4c 8b 06 4c 8b 4e 08 4c 8b 56 10 4c [ 5.974539] RIP [] memcpy+0xd/0x110 [ 5.974542] RSP [ 5.974543] CR2: 0000000000ff1000 [ 5.974545] ---[ end trace 67f7b54c3f5c5271 ]---
Como a danada da falha mostrava um erro com "memcpy+0xd/0x110", eu imaginei que era falha de cópia de dados pra alguma endereço da memória RAM, ou seja, o pente de memória que comprei no #DX estava com problemas, o que denota a máxima de que, na dúvida, culpe o fornecedor Chinês mais próximo de você.
Passei os restantes dos dias buscando por mais reclamações sobre problemas de hibernação no Ubuntu 12.10 ou algo parecido, e... nada. Comecei a desconfiar mesmo dos produtos chineses, a ponto de passar 2 dias rodando memtest pra verificar o estado da RAM, procedimento aliás que só causa expectativa seguida de frustração, e não tira o produto Chinês da mira de vilão da história.
Hoje, por um acaso muito grande, eu resolvi buscar pelo erro do bug, mas na verdade para buscar alguma ferramenta para bloquear o segmento de memória danificado. Então busquei pela linha:
BUG: unable to handle kernel paging request at
E encontrei umas referências sobre problemas em... Sony Vaio! Justamente a marca do meu laptop. Coincidência?
Então resolvi buscar diretamente o endereço de memória do meu problema:
BUG: unable to handle kernel paging request at 0000000000ff1000
E não é que peguei um problema reportado e bem descrito no Launchpad, o sistema de reporte de bugs do Ubuntu? Eu nunca tinha encontrado referências a esse bug porque a descrição fala de problema de Sony Vaio (outro duh!).
[SONY VAIO VPCS12L9E] Suspend doesn't work after dist-upgrade to Quantal 12.10
Felizmente a pessoa que abriu o bug report fez uma bela descrição do problema e também de uma solução. Apenas apliquei os seguintes passos para ter meu sistema funcionando corretamente:
sudo add-apt-repository ppa:shiba89/vaio-kernel sudo apt-get update sudo apt-get install linux-headers-generic sony-laptop-dkms
Com isso, no boot seguinte tive a comprovação de que meu laptop voltou à hibernar feito um bebê. E com isso fecho 2012 sem pendências, ao menos pessoais, para 2013.
E que venha 2013! Se sobrevivemos ao fim do mundo segundo os Maias, não é um problema de kernel que vai nos segurar!
O calcanhar de Aquiles do Linux sempre foi o gerenciamento de memória. Por mais memória que se tenha disponível, ele vorazmente sempre quer mais. E nunca libera a memória livre.
E meus parcos 6 GB de memória do laptop tem sentido isso nesses últimos dias de 2012. E verificável pelos gráficos do Munin, que mostra o pedido de 10 GB de memória pra usar. Isso me faz pensar que talvez os Maias esteja certos.
Eu ainda não descobri qual programa (ou processo) está causando isso, e o kernel Linux não cuida de matar o processo comilão.
Então criei um pequeno daemon, em perl, para ficar lendo o /proc e matando os processos que mais consomem memória se a carga do sistema aumentar muito (atualmente marquei para load average maior de 10).
#! /usr/bin/perl
use POSIX;
$PROC = "/proc";
$MAXLOAD = 10; #loadaverage
$SLEEPTIME = 1; #seconds
if (getuid() != 0) {
print "Only root can run this program.\n";
exit(1);
}
if (! -d $PROC) {
print "Not possible to run. System not running with $PROC directory\n";
exit(1);
}
sub GetMem() {
$pid_dir = $_[0];
my $dir = $PROC."/".$pid_dir."/statm";
open(MEM, $dir) or die "Error:$dir $!\n";
@params = split(/ /, );
close MEM;
$memory = $params[0];
return $memory;
}
sub GetPID() {
my %MEM;
opendir(PROC,$PROC) or die "Not possible to run. System not running with $PROC directory\n";
foreach $dir (readdir PROC) {
# Just read higher processes
# avoid init (1) or non-pid
next if ($dir !~ m/^\d\d\d+/);
$mem = &GetMem($dir);
if ($mem) {
$MEM{$dir} = $mem;
}
}
closedir PROC;
return %MEM;
}
sub GetLoad() {
open(LOAD,$PROC."/loadavg") or die "Impossible to detect load average by $PROC:$!\n";
my $load = ;
close LOAD;
my @params = split(/ /, $load);
return $params[0];
}
sub GetCommand() {
my $pid = $_[0];
open(CMD, $PROC."/".$pid."/cmdline") or die "Impossible to read the source command: $!\n";
my $cmd = ;
close CMD;
return $cmd;
}
sub DaemonizeMe() {
print "Starting to memory control daemon\n";
POSIX::setsid or die "setsid: $!";
my $pid = fork ();
if ($pid < 0) {
die "fork: $!";
} elsif ($pid) {
exit 0;
}
chdir "/";
umask 0;
foreach (0 .. (POSIX::sysconf (&POSIX::_SC_OPEN_MAX) || 1024)) {
POSIX::close $_;
}
open (STDIN, "/dev/null");
#open (STDERR, ">&STDOUT");
}
&DaemonizeMe();
my $mypid = getpid();
my %DEATHCOUNTER;
while (! $SIG{TERM}) {
sleep($SLEEPTIME);
my $load = &GetLoad();
next if ($load < $MAXLOAD);
my %MEM;
%MEM = &GetPID();
$higher_m = 0;
$higher_pid = 0;
foreach $k (sort keys %MEM) {
next if ($k eq $mypid);
$mem = $MEM{$k};
if ($mem > $higher_m) {
$higher_m = $mem;
$higher_pid = $k;
$DEATHCOUNTER{$higher_pid}++;
}
}
if ($DEATHCOUNTER{$higher_pid} >= 5) {
print "Killing due higher memory usage: ".&GetCommand($higher_pid)." (".$higher_pid.") [".$higher_m." Bytes]\n";
%DEATHCOUNTER;
}
}
print "Exiting...";
exit(0);
Os Maias podiam até estar certos, mas isso não significa que precisamos ficar sentados olhando.
Já faz anos que compilo meus kernels e sistemas com a utilização do parâmetro "-j 8" ou "-j 12". Esse parâmetro, passado ao GCC, faz uso de multithreads em máquinas com mais de um processador ou processador multicore, como esse Intel Core i3 que tenho no laptop.
Mas sempre usei esse parâmetro quase que como dogma, sem muita certeza de sua eficiência. Aliás, com uma pequena idéia de eficiência já que, sem o uso do mesmo, o tempo de compilação era mais demorado. Mas tudo muito de "sentimento", sem nenhuma comprovação.
Então, num desses dias sem muita coisa pra fazer (pequena metira: estava lotado de coisas pra terminar, mas decidi fazer isso pra limpar um pouco a mente), resolvi verificar essa compilação com dados mais concretos e monitoração dos resultados. Fiz o seguinte programa em python pra ficar compilando um kernel que já estava configurado e que eu tinha certeza que compilava sem problemas, com as threads indo de 1 a 20:
#! /usr/bin/python
# make clean; time make-kpkg -j 4 --initrd kernel_image
import os
import time
#print time.time()
OUTPUT = "/tmp/kernel_results.csv"
FD = open(OUTPUT, "w")
for TH in xrange(1,21):
print "Threads:", TH
print "\tlimpando..."
os.system("make clean")
t_0 = time.time()
os.system("make-kpkg -j " + str(TH) + " --initrd kernel_image")
t_1 = time.time()
total_time = t_1 - t_0
msg = "Threads: %d in %0.2f s" %(TH, total_time)
print msg
FD.write(str(TH) + "," + str(total_time) + "\n")
FD.flush()
Então deixei o sistema rodando. Eu costumo usar o "make-kpkg", do pacote kernel package, que já gera para mim o pacote DEB para instalação.
Ao final, os resultados foram os seguintes:
A troca de contextos de processos realmente aumentou com o aumento de threads. Por isso o sistema fica tão lento.
O sistema ficou com uso de CPU intenso, mas sem crescimento gradual (isso eu já achei estranho).
As interrupções de mudança de contexto também ficaram iguais, onde eu esperava um valor em degraus.
Mas a carga do sistema, load average, aumentou realmente em degrau, acompanhando a quantidade definida no "-j". Isso era esperado e é sempre notado, pois o computador fica super lento.
Porém o melhor ficou pro final: a análise do tempo pela quantidade de threads.
O tempo diminuiu conforme a quantidade de threads que aumenta até... 3??? O processador Intel Core i3 é um multicore de 4 cores, eu esperava ao menos um melhor desempenho até 4 threads, mas dá pra ver bem claro que o sistema estabiliza em 3.
Ou seja, usando "-j 8" ou "-j 12" só serve para aumentar a carga da CPU, aumentando as trocas de contextos, mas não significam nem melhora nem otimização da compilação. Pelo contrário. Então o melhor é saber o máximo que a CPU realmente aguenta antes de aplicar cegamente parâmetros para *melhorar* o desempenho do sistema. E monitorar os resultados!
Atualização: Sun Feb 3 20:34:35 BRST 2013
Conforme pedido do Erick Tostes, @ericktostes no twitter, estou incluindo o meu /proc/cpuinfo.
processor : 0
vendor_id : GenuineIntel
cpu family : 6
model : 37
model name : Intel(R) Core(TM) i3 CPU M 330 @ 2.13GHz
stepping : 2
microcode : 0x9
cpu MHz : 933.000
cache size : 3072 KB
physical id : 0
siblings : 4
core id : 0
cpu cores : 2
apicid : 0
initial apicid : 0
fpu : yes
fpu_exception : yes
cpuid level : 11
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36
clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx rdtscp lm constant_tsc
arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf pni dtes64 monitor
ds_cpl vmx est tm2 ssse3 cx16 xtpr pdcm sse4_1 sse4_2 popcnt lahf_lm arat dtherm tpr_shadow
vnmi flexpriority ept vpid
bogomips : 4255.62
clflush size : 64
cache_alignment : 64
address sizes : 36 bits physical, 48 bits virtual
power management:
processor : 1
vendor_id : GenuineIntel
cpu family : 6
model : 37
model name : Intel(R) Core(TM) i3 CPU M 330 @ 2.13GHz
stepping : 2
microcode : 0x9
cpu MHz : 933.000
cache size : 3072 KB
physical id : 0
siblings : 4
core id : 0
cpu cores : 2
apicid : 1
initial apicid : 1
fpu : yes
fpu_exception : yes
cpuid level : 11
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36
clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx rdtscp lm constant_tsc
arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf pni dtes64 monitor
ds_cpl vmx est tm2 ssse3 cx16 xtpr pdcm sse4_1 sse4_2 popcnt lahf_lm arat dtherm tpr_shadow
vnmi flexpriority ept vpid
bogomips : 4255.62
clflush size : 64
cache_alignment : 64
address sizes : 36 bits physical, 48 bits virtual
power management:
processor : 2
vendor_id : GenuineIntel
cpu family : 6
model : 37
model name : Intel(R) Core(TM) i3 CPU M 330 @ 2.13GHz
stepping : 2
microcode : 0x9
cpu MHz : 933.000
cache size : 3072 KB
physical id : 0
siblings : 4
core id : 2
cpu cores : 2
apicid : 4
initial apicid : 4
fpu : yes
fpu_exception : yes
cpuid level : 11
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36
clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx rdtscp lm constant_tsc
arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf pni dtes64 monitor
ds_cpl vmx est tm2 ssse3 cx16 xtpr pdcm sse4_1 sse4_2 popcnt lahf_lm arat dtherm tpr_shadow
vnmi flexpriority ept vpid
bogomips : 4255.62
clflush size : 64
cache_alignment : 64
address sizes : 36 bits physical, 48 bits virtual
power management:
processor : 3
vendor_id : GenuineIntel
cpu family : 6
model : 37
model name : Intel(R) Core(TM) i3 CPU M 330 @ 2.13GHz
stepping : 2
microcode : 0x9
cpu MHz : 933.000
cache size : 3072 KB
physical id : 0
siblings : 4
core id : 2
cpu cores : 2
apicid : 5
initial apicid : 5
fpu : yes
fpu_exception : yes
cpuid level : 11
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36
clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx rdtscp lm constant_tsc
arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf pni dtes64 monitor
ds_cpl vmx est tm2 ssse3 cx16 xtpr pdcm sse4_1 sse4_2 popcnt lahf_lm arat dtherm tpr_shadow
vnmi flexpriority ept vpid
bogomips : 4255.62
clflush size : 64
cache_alignment : 64
address sizes : 36 bits physical, 48 bits virtual
power management:
Já faz tempo que estou querendo escrever um pouco sobre o pf-kernel e acho que finalmente chegou esse momento. Talvez a idéia dramática de fatalidade, derivado do fato de que estou assistindo o filme 2012, tenha me levado a escrever sobre isso logo.
Sempre existiram variações do kernel do Linux, desde seu surgimento. As versões de Andrew Morton eram famosas por incluir algumas vantagens como melhor preempção e gerenciamento de memória se comparado com o kernel padrão, por exemplo. Mas isso era no passado. Nos tempos atuais de Linux, alguns kernels modificados para melhoria de resposta em tempo real são os mais famosos, justamente por existir dentro de distribuições como Debian e Ubuntu, bastando usar o gerenciador de pacotes para instalar e testar. Nesse panorama, sem muito alarde, surgiu um novo fork do kernel Linux: o pf-kernel.
Apesar da associação de pf-kernel com o packetfilter dos BSDs, na verdade o pf é uma referência ao apelido do autor, post-factum. O pf-kernel é um kernel padrão Linux modificado da seguinte forma:
A maioria são patches para melhorar o tempo de resposta do sistema (preempção), mas não somente com alteração no escalonador, mas por melhorias inclusive nas respostas de I/O, além de TuxOnIce, uma forma nova hibernar o Linux, com várias vantagens em relação ao sistema padrão, como o retorno mais rápido. Infelizmente no meu caso tive problemas do TuxOnIce com o meu filesystem, XFS. Mas de resto, gostei de usar o pf-3.2, ficando com uptime além de 100 dias graças a ele. Eu não conhecia o pf-kernel, só tinha ouvido falar por cima, mas uma sugestão no twitter, do @cleitonflima, me fez experimentar e gostar muito dessa árvore de kernel. Posteriormente encontrei um artigo sobre o mesmo:
Fazendo o pinguim voar: o pf-kernel
Apesar da riqueza de dados sobre pf-kernel nesse artigo, não há tanta informação assim sobre o mesmo. Então não consegui confirmar o que o artigo diz sobre os patches, além das informações que cada patch já traz. Aliás as melhorias, no meu processador Intel Core I3, só foram percebidas com carga alta, ou seja, load average acima de 4. Abaixo disso, que é o normal do sistema, não percebi nenhum ganho. Mas com o sistema sobrecarregado, consegui continuar ouvido música sem interrupções, o que não acontece com o kernel padrão.
E fui brindado com um kernel mais estável que o kernel padrão também. Tive problemas com o kernel 3.2.0, e com o pf-kernel-3.2.5, mas a versão pf-kernel-3.2.7 funcionou muito bem (com exceção do problema do filesystem XFS). Agora estou criando coragem pra compilar uma versão mais recente do pf-kernel, que suporte mais de 100 dias de uptime, mas não acho que terei problemas.
Pra quem tem uma máquina mais antiga, da geração Core Duo ou até anterior, é bem provável que os benefícios do pf-kernel sejam mais bem notados. Para agraciados donos de CPUs mais modernas, como a última geração dos Core-i7, talvez nem seja perceptível, mas é sempre interessante testar as alternativa ao kernel padrão e saber que elas existem.
E minha alegria realmente chegou ao fim nos 110 dias de uptime. Dessa vez o culpado foi o filesystem, XFS, que começou a cuspir vários erros como esses: