使用 SysRq 键安全重启挂起的 Linux

最近有台 NFS 服务器挂机,可以 ping 通,但不能 ssh 登陆,也不能通过本地终端登陆,只能重启了。我们一般处理文件服务器这种类型的重启都格外小心,不到迫不得已不会直接硬重启。Linux 运行过程中(为了提高性能)会把大量的数据暂时放在内存缓存中,而不是实时同步写入到磁盘,Linux 根据情况只有在需要(触发某条件)的时候才写入磁盘,所以这个时候挂机,数据还留在内存,没有办法及时写到磁盘,强制断电重启会造成数据不一致、部分数据丢失、文件系统损坏等。

为了在这样的情况下实现安全重启,我们可以利用 SysRq,当然有个条件是,系统虽然罢工停止了对大部分服务的响应,但仍然能处理键盘的中断请求。SysRq (System request) 常被称为 Magic SysRq key,在 Linux 下它被定义为一系列按键组合,之所以说它 magic,是因为它常能在系统挂起、多数服务都无法响应的时候做点事(预定义的操作),而且能在磁盘数据安全的情况下完成重启,除此之外还能捕获一些有用的系统运行信息。

首先确认当前使用的 Linux 内核支持 SysRq:

# grep "CONFIG_MAGIC_SYSRQ" /boot/config-`uname -r`
CONFIG_MAGIC_SYSRQ=y

如果系统默认关闭了 kernel.sysrq 的话,需要打开。为了保证每次系统重启内核参数都生效,建议把配置写到 sysctl.conf 文件里:

# sysctl kernel.sysrq
kernel.sysrq = 0

# sysctl -w kernel.sysrq=1
kernel.sysrq = 1

# vi /etc/sysctl.conf
...
# Controls the System Request debugging functionality of the kernel
kernel.sysrq = 1
...

SysRq 配置好后就可以开始用了。SysRq 安全重启的推荐按键组合是 Alt + SysRq + R-E-I-S-U-B,先按下 Alt 键和 SysRq 键,然后依次按下 R E I S U B 键(不区分大小写)。这个 R E I S U B 序列组合的意思是:

R – 把键盘设置为 ASCII 模式
E – 向除 init 外所有进程发送 SIGTERM 信号
I – 向除 init 外所有进程发送 SIGKILL 信号
S – 磁盘缓冲区同步
U – 重新挂载为只读模式
B – 重启系统

需要注意的是这些按键之间有顺序,而且按键之间有时间间隔(因为要等待前一个操作的完成),推荐的时间间隔是 R – 1 秒 – E – 30 秒 – I – 10 秒 – S – 5 秒 – U – 5 秒 – B.

我们通常只在意数据是否安全的同步到了磁盘,所以我们一般只用 S-B 组合,按下 Alt + SysRq + S 后等待 Emergency Sync complete 提示,同步完成确认后用 Alt + SysRq + B 立刻重启。

Linux 服务器因 CPU 温度过高自动 shutdown

昨天一台 Linux Xen 服务器莫名其妙就不能访问了,开始以为又碰到 server kernel: ip_conntrack: table full, dropping packet. 问题,没仔细看。后来过了2个小时又不能访问了,看了一下日志是服务器自己 shutdown 了,不是网络的问题。再看日志发现错误信息:

Nov 24 05:32:22 ivps kernel: ACPI: Critical trip point
Nov 24 05:32:22 ivps kernel: Critical temperature reached (76 C), shutting down.

原因是 CPU 温度过高超过了警戒温度,查一下系统默认的警戒温度是75度,所以到了76度系统就自动 shutdown 了:

# cat /proc/acpi/thermal_zone/THRM/trip_points 
critical (S5):           75 C

服务器温度有这么高吗?查看一下当前温度吓一跳,刚启动的系统又到了74度,系统马上又要 shutdown 了:

# cat /proc/acpi/thermal_zone/THRM/temperature 
temperature:             74 C

紧急做法是暂时修改默认报警温度到85度:

# echo 85:0:80:60:0 > /proc/acpi/thermal_zone/THRM/trip_points

# cat /proc/acpi/thermal_zone/THRM/trip_points 
critical (S5):           85 C

一般来说 CPU 温度超过70度都是很高的温度了,如果不是系统和程序的原因要赶紧检查服务器周围的环境,检查机房和机柜温度情况、服务器风扇、内部积灰等,让 CPU 和主板长时间工作在高温下可不是好事情。当然不同 CPU 所能耐的住的温度也不同,VPSee 推荐 Intel Core 2 Quad CPU 保持在70度以下,Intel Core i7 CPU 保持在80度以下,这样 CPU 和系统能全速工作发挥最大效率而温度又不至于损坏 CPU.

Nginx 使用 Linux-native aio 需要 Linux 内核支持

Nginx 性能优异在于善于利用操作系统内核的各种特性,比如 aio/epoll/sendfile (Linux), kqueue (FreeBSD) 等。对于使用 VPS 做图片站的站长来说,使用 nginx 的 aio 特性会大大提高性能,图片站的特点是大量的读 io 操作,nginx aio 不用等待每次 io 的结果有助于并发处理大量 io 和提高 nginx 处理效率。

前段时间有位客户要用 nginx aio,他用 rpm 升级安装 nginx 到 0.8.5 版本时候发现 rpm 包里的 nginx 没有包含 Linux-native aio (asynchronous I/O) 的支持,所以需要下载 nginx 源代码并带上参数 –with-file-aio 编译。除了要带参数外,Linux 内核还必须有支持这一特性的 api,eventfd(),否则在 nginx 错误日志(/var/log/nginx/error.log)里会看到类似的报错:

eventfd() failed (38: Function not implemented)
worker process 1858 exited with fatal code 2 and can not be respawn

要让 nginx 使用 aio 特性还需要修改 nginx 配置文件:

# vi /etc/nginx/nginx.conf
...
location / {
aio on;
directio 1;
output_buffers 1 128k;
}
...

昨天另一客户遇到 nginx 启动配置都正确却无法看到网页的情况,VPSee 刚开始怀疑是他自己的 nginx 配置有误或者防火墙屏蔽了80端口,后来登录到他的 VPS 上发现 nginx 配置的确没问题,能正常启动,可以 telnet 80 端口,但是不能 get 到网页。打开 nginx 日志也发现 eventfd() failed 的错误提示。后来检查他的 Linux VPS 内核版本是 2.6.18,查了一下 man 帮助发现只有 2.6.22 以后版本才支持 eventfd,解决方法很简单,升级内核就可以了。

Linux-native aio 比传统的 POSIX aio 功能更丰富一些,重要的一点是能通过内核加速提供高性能。直接用 Linux-native aio API 比较晦涩,为了方便使用和开发 Linux-native aio 应用程序我们可以用 libaio/libaio-devel 库。不过 nginx 的作者没有用这些库,因为 nginx 需要 eventfd(),而 libaio 库里只有 0.3.107 版本起才支持 eventfd;nginx 也没有用 glibc,因为 glibc 要到 2.8 版本才支持 eventfd(),为了减少对库的依赖性,nginx 干脆直接用 Linux-native aio API (system calls).

$ vi nginx-0.9.1/src/event/modules/ngx_epoll_module.c
...
#if (NGX_HAVE_FILE_AIO)

/*
 * We call io_setup(), io_destroy() io_submit(), and io_getevents() directly
 * as syscalls instead of libaio usage, because the library header file
 * supports eventfd() since 0.3.107 version only.
 *
 * Also we do not use eventfd() in glibc, because glibc supports it
 * since 2.8 version and glibc maps two syscalls eventfd() and eventfd2()
 * into single eventfd() function with different number of parameters.
 */
...

这里说到了 eventfd(),eventfd 是 Linux-native aio 其中的一个 API,用来生成 file descriptors,这些 file descriptors 可为应用程序提供更高效 “等待/通知” 的事件机制。和 pipe 作用相似,但比 pipe 更好,一方面它只用到一个 file descriptor(pipe 要用两个),节省了内核资源;另一方面,eventfd 的缓冲区管理要简单得多,pipe 需要不定长的缓冲区,而 eventfd 全部缓冲只有定长 8 bytes.

/proc/cpuinfo 里的 CPU flags

Linux 上的 /proc是一个虚拟文件系统,在系统启动后挂载在 /proc 上,/proc 包含了很多内核和系统信息用来展示 Linux 内核是如何展示硬件的,比如在 /proc/cpuinfo 里可以看到一些关于 CPU 的信息,其中的 flags 包含了很多用来表示 CPU 特征的参数:

$ cat /proc/cpuinfo | grep flags
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
xtopology nonstop_tsc aperfmperf pni dtes64 monitor ds_cpl vmx est tm2 ssse3 cx16 xtpr pdcm sse4_1 
sse4_2 popcnt lahf_lm arat tpr_shadow vnmi flexpriority ept vpid

具体每个 flag 缩写代表什么意思呢?关于 Linux 的最权威答案永远来自源代码,VPSee 在内核源代码里找到了每个 flag 的相关注释,通过这些注释可以很方便我们理解这些 flags 缩写:

# vi /home/vpsee/linux-2.6.31.8/arch/x86/include/asm/cpufeature.h
...
/* Intel-defined CPU features, CPUID level 0x00000001 (edx), word 0 */
#define X86_FEATURE_FPU         (0*32+ 0) /* Onboard FPU */
#define X86_FEATURE_VME         (0*32+ 1) /* Virtual Mode Extensions */
#define X86_FEATURE_DE          (0*32+ 2) /* Debugging Extensions */
#define X86_FEATURE_PSE         (0*32+ 3) /* Page Size Extensions */
#define X86_FEATURE_TSC         (0*32+ 4) /* Time Stamp Counter */
#define X86_FEATURE_MSR         (0*32+ 5) /* Model-Specific Registers */
#define X86_FEATURE_PAE         (0*32+ 6) /* Physical Address Extensions */
#define X86_FEATURE_MCE         (0*32+ 7) /* Machine Check Exception */
#define X86_FEATURE_CX8         (0*32+ 8) /* CMPXCHG8 instruction */
#define X86_FEATURE_APIC        (0*32+ 9) /* Onboard APIC */
#define X86_FEATURE_SEP         (0*32+11) /* SYSENTER/SYSEXIT */
#define X86_FEATURE_MTRR        (0*32+12) /* Memory Type Range Registers */
#define X86_FEATURE_PGE         (0*32+13) /* Page Global Enable */
#define X86_FEATURE_MCA         (0*32+14) /* Machine Check Architecture */
#define X86_FEATURE_CMOV        (0*32+15) /* CMOV instructions */
                                          /* (plus FCMOVcc, FCOMI with FPU) */
#define X86_FEATURE_PAT         (0*32+16) /* Page Attribute Table */
#define X86_FEATURE_PSE36       (0*32+17) /* 36-bit PSEs */
#define X86_FEATURE_PN          (0*32+18) /* Processor serial number */
#define X86_FEATURE_CLFLSH      (0*32+19) /* "clflush" CLFLUSH instruction */
#define X86_FEATURE_DS          (0*32+21) /* "dts" Debug Store */
#define X86_FEATURE_ACPI        (0*32+22) /* ACPI via MSR */
#define X86_FEATURE_MMX         (0*32+23) /* Multimedia Extensions */
#define X86_FEATURE_FXSR        (0*32+24) /* FXSAVE/FXRSTOR, CR4.OSFXSR */
#define X86_FEATURE_XMM         (0*32+25) /* "sse" */
#define X86_FEATURE_XMM2        (0*32+26) /* "sse2" */
#define X86_FEATURE_SELFSNOOP   (0*32+27) /* "ss" CPU self snoop */
#define X86_FEATURE_HT          (0*32+28) /* Hyper-Threading */
#define X86_FEATURE_ACC         (0*32+29) /* "tm" Automatic clock control */
#define X86_FEATURE_IA64        (0*32+30) /* IA-64 processor */
#define X86_FEATURE_PBE         (0*32+31) /* Pending Break Enable */

/* AMD-defined CPU features, CPUID level 0x80000001, word 1 */
/* Don't duplicate feature flags which are redundant with Intel! */
#define X86_FEATURE_SYSCALL     (1*32+11) /* SYSCALL/SYSRET */
#define X86_FEATURE_MP          (1*32+19) /* MP Capable. */
#define X86_FEATURE_NX          (1*32+20) /* Execute Disable */
#define X86_FEATURE_MMXEXT      (1*32+22) /* AMD MMX extensions */
#define X86_FEATURE_FXSR_OPT    (1*32+25) /* FXSAVE/FXRSTOR optimizations */
#define X86_FEATURE_GBPAGES     (1*32+26) /* "pdpe1gb" GB pages */
#define X86_FEATURE_RDTSCP      (1*32+27) /* RDTSCP */
#define X86_FEATURE_LM          (1*32+29) /* Long Mode (x86-64) */
#define X86_FEATURE_3DNOWEXT    (1*32+30) /* AMD 3DNow! extensions */
#define X86_FEATURE_3DNOW       (1*32+31) /* 3DNow! */
...

/* Intel-defined CPU features, CPUID level 0x00000001 (ecx), word 4 */
...
/* VIA/Cyrix/Centaur-defined CPU features, CPUID level 0xC0000001, word 5 */
...
/* More extended AMD flags: CPUID level 0x80000001, ecx, word 6 */
...

仅有 flags 缩写和注释不足以理解 flag 的含义,但是足够发挥个人的 googleability 了,比如直接搜索 msr 不会得到任何有用信息,但是利用上面的注释搜索 Model-Specific Registers(google “Model-Specific Registers”)就会得到很多信息,学习的过程就是不断提高 googleability 的过程:)

1GB 的 VPS 只有 727MB?

上周六我们一位客户报告了一个奇怪的问题,他在我们这里购买的 1024MB 内存的 VPS 只有 727MB,我们立即检查了 Xen 服务器上的配置和他的 VPS 运行情况,没有发现问题,他的 Xen VPS 配置文件以及 Xen 工具都是显示的是他有 1024MB 内存,客户发来了截图,并且给了我们 root 密码,我们登录进去看到客户的 VPS 确实只有 727MB,更奇怪的是我们可以通过动态调整来减少这个 VPS 的内存到 512MB,但是不能加到 1024MB,总是在 727MB 这个地方就加不上去了。我们服务器上有足够的内存,我们检查了各种 Xen 配置文件和 Xen 的日志,没有发现任何异常,Xen 技术已经相当成熟,如果是 bug 的话,应该早就有人遇到和解决了,所以我们估计和客户的系统有关。

这是一个很有意思的问题,VPSee 很想弄明白怎么回事,但是又不能不停的重启客户的 VPS 来测试,也没办法在自己机器上重现。这个时候客户给了我们一条重要的提示信息,他自己编译过 Linux 内核。和其他 VPS 服务商采用公共的内核不同,我们的 VPS 支持用户自己定义和编译内核。我们开始怀疑客户编译内核的时候某些选项弄错了,我们让客户发来他编译内核时用到的配置选项,他自己编译和使用的是64位的内核,而我们提供的 Linux VPS 是32位的,刚开始简单怀疑是32位程序使用64位内核的问题,可能部分32位程序调用64位内核提供的64位系统调用的时候出的问题。不过这个说法说不过去,因为64位是向下兼容的,运行大部分32位程序应该没问题(反过来32位内核运行64位程序就不一定了,因为64位程序指针会被32位内核截断成32位可能造成程序错误)。

周日下午连上服务器新建了一个 1024MB Arch Linux VPS,VPS 正常显示的是 1024MB,没有问题,按照客户的说明和这篇:在 ArchLinux VPS 上编译内核 提供的默认内核配置文件编译后,问题来了,启动系统后果然内存只有 727MB,打印内核信息只看到 727MB 内存:

# dmesg | more
...
last_pfn = 0x40000 max_arch_pfn = 0x1000000
Warning only 727MB will be used.
Use a HIGHMEM enabled kernel.
...

估计就是内核配置低端内存 LOWMEM 限制的问题,找到问题就好办了,检查默认的内核配置文件发现没有启用 HIGHMEM(CONFIG_NOHIGHMEM=y):

# vi kernel26-xen/config
...
CONFIG_NOHIGHMEM=y
# CONFIG_HIGHMEM4G is not set
# CONFIG_HIGHMEM64G is not set
CONFIG_PAGE_OFFSET=0xC0000000
CONFIG_X86_PAE=y
...

把上面的相关部分改成下面的配置就可以了:

# vi kernel26-xen/config
...
# CONFIG_NOHIGHMEM is not set
# CONFIG_HIGHMEM4G is not set
CONFIG_HIGHMEM64G=y
CONFIG_PAGE_OFFSET=0xC0000000
CONFIG_HIGHMEM=y
CONFIG_X86_PAE=y
...

重新编译安装内核并加入到 grub,最后重启系统登录后就会看到 1024MB 内存回来了:

# makepkg --asroot
# pacman -U kernel26-xen-2.6.34.1-1-i686.pkg.tar.xz
# pacman -U kernel26-xen-headers-2.6.34.1-1-i686.pkg.tar.xz

# vi /boot/grub/menu.lst
...
timeout 5
default 0

title Xen for ArchLinux (VPSee)
root (hd0,0)
kernel /boot/vmlinuz26-xen root=/dev/xvda1 ro console=/dev/xvc0
initrd /boot/kernel26-xen.img
...

# reboot

谢谢我们的客户给我们提供充分的信息和截图帮助我们找到和解决问题,也很感谢客户对我们的信任、遇到问题及时向我们反映而不是猜测和抱怨,不了解的人还以为我们在忽悠人呢。

监测 Linux 进程的实时 IO 情况

作为系统管理员和 VPS 服务商,经常会碰到服务器或者 VPS 磁盘 IO 繁忙的时候,VPSee 通常都会用一些工具来检测,其中一个常用的工具就是自己写的 iotop 脚本,可以很方便看到哪个进程在频繁 IO. 上周五收到一位网友的邮件和留言,问到这篇文章:如何查看进程 IO 读写情况?里的 WRITE 为什么会出现是 0 的情况,这是个好问题,VPSee 在这里好好解释一下。首先看看我们怎么样才能实时监测不同进程的 IO 活动状况。

block_dump

Linux 内核里提供了一个 block_dump 参数用来把 block 读写(WRITE/READ)状况 dump 到日志里,这样可以通过 dmesg 命令来查看,具体操作步骤是:

# sysctl vm.block_dump=1
or
# echo 1 > /proc/sys/vm/block_dump

然后就可以通过 dmesg 就可以观察到各个进程 IO 活动的状况了:

# dmesg -c
kjournald(542): WRITE block 222528 on dm-0
kjournald(542): WRITE block 222552 on dm-0
bash(18498): dirtied inode 5892488 (ld-linux-x86-64.so.2) on dm-0
bash(18498): dirtied inode 5892482 (ld-2.5.so) on dm-0
dmesg(18498): dirtied inode 11262038 (ld.so.cache) on dm-0
dmesg(18498): dirtied inode 5892496 (libc.so.6) on dm-0
dmesg(18498): dirtied inode 5892489 (libc-2.5.so) on dm-0

问题

一位细心的网友提到这样一个问题:为什么会有 WRITE block 0 的情况出现呢?VPSee 跟踪了一段时间,发现确实有 WRITE 0 的情况出现,比如:

# dmesg -c
...
pdflush(23123): WRITE block 0 on sdb1
pdflush(23123): WRITE block 16 on sdb1
pdflush(23123): WRITE block 104 on sdb1
pdflush(23123): WRITE block 40884480 on sdb1
...

答案

原来我们把 WRITE block 0,WRITE block 16, WRITE block 104 这里面包含的数字理解错了,这些数字不是代表写了多少 blocks,是代表写到哪个 block,为了寻找真相,VPSee 追到 Linux 2.6.18 内核代码里,在 ll_rw_blk.c 里找到了答案:

$ vi linux-2.6.18/block/ll_rw_blk.c

void submit_bio(int rw, struct bio *bio)
{
        int count = bio_sectors(bio);

        BIO_BUG_ON(!bio->bi_size);
        BIO_BUG_ON(!bio->bi_io_vec);
        bio->bi_rw |= rw;
        if (rw & WRITE)
                count_vm_events(PGPGOUT, count);
        else
                count_vm_events(PGPGIN, count);

        if (unlikely(block_dump)) {
                char b[BDEVNAME_SIZE];
                printk(KERN_DEBUG "%s(%d): %s block %Lu on %s\n",
                        current->comm, current->pid,
                        (rw & WRITE) ? "WRITE" : "READ",
                        (unsigned long long)bio->bi_sector,
                        bdevname(bio->bi_bdev,b));
        }

        generic_make_request(bio);
}

很明显从上面代码可以看出 WRITE block 0 on sdb1,这里的 0 是 bio->bi_sector,是写到哪个 sector,不是 WRITE 了多少 blocks 的意思。还有,如果 block 设备被分成多个区的话,这个 bi_sector(sector number)是从这个分区开始计数,比如 block 0 on sdb1 就是 sdb1 分区上的第0个 sector 开始。

在 ArchLinux VPS 上编译内核

昨天我们有位 ArchLinux 用户遇到一个 OpenVPN 的问题,在他的 VPS 上安装和配置好 OpenVPN 后可以从自己电脑连上 VPN,但是不能用 VPN 出去(访问外部网站),VPSee 和用户一起排错发现配置 OpenVPN 时候忘了用 iptable 做 SNAT 或 MASQUERADE,进一步运行 iptables 报错,内核缺少 iptables 模块:

# iptables -t nat -A POSTROUTING -s 1.1.1.1 -j SNAT --to-source 2.2.2.2
can't initialize iptables table `nat': Table does not exist (do you need to insmod?)
Perhaps iptables or your kernel needs to be upgraded.

找到问题就好办了,解决的办法也很容易,下载内核代码、配置 iptable 内核选项、重新编译内核。(本文已经发给客户,我们的客户可以按照这里的步骤自己编译和替换内核,如果怕麻烦的话也可以找我们要已经编译好的内核。我们争取对我们的客户遇到的问题都能写出文章和流程来帮助理解和解决,这样不但我们自己客户受益,对其他 VPS 用户也有所帮助。)

安装必要工具

编译内核需要一些工具,比如 gcc 编译器什么的:

# pacman -S make gcc patch xmlto docbook-xsl

有一点要注意的就是不是每个内核都可以在 Xen 上运行的,因为是半虚拟化所以必须使用针对 Xen 的 Linux 内核版本,还好,ArchLinux 已经为我们准备好了官方的 Xen 版本内核:

# wget http://aur.archlinux.org/packages/kernel26-xen/kernel26-xen.tar.gz
# tar -xf kernel26-xen.tar.gz
# cd kernel26-xen

修改 kernel26-xen/PKGBUILD 文件,去掉下面这行的注释:

# vi PKGBUILD
pkgname=('kernel26-xen' 'kernel26-xen-headers') # Build kernel with a different name

编译和安装内核

如果打开 kernel26-xen/config 文件就会发现内核已经是为 Xen 配置好的,并且 iptable 相关选项已经是 m 状态(以内核模块的方式运行),如果不放心的话可以再次检查 CONFIG_IP_NF_IPTABLES=m 和 CONFIG_NF_NAT=m 等选项看看是否已经是 m 或 y 状态。在 ArchLinux 下编译和安装内核:

# makepkg --asroot
...
==> Creating package...
  -> Generating .PKGINFO file...
  -> Compressing package...
==> Finished making: kernel26-xen 2.6.34.1-1 (Mon Jul 19 15:15:05 EDT 2010)

# pacman -U kernel26-xen-2.6.34.1-1-i686.pkg.tar.xz
# pacman -U kernel26-xen-headers-2.6.34.1-1-i686.pkg.tar.xz

配置 grub 以启动新内核:

# vi /boot/grub/menu.lst
...
timeout 5
default 0

title Xen for ArchLinux (VPSee)
root (hd0,0)
kernel /boot/vmlinuz26-xen root=/dev/xvda1 ro console=/dev/xvc0
initrd /boot/kernel26-xen.img
...

测试

重启系统后检查和测试新装的 Linux 内核是否有了 iptables 内核模块并安装 iptables 用户端工具:

# uname -r
2.6.34-xen

# modprobe ip_tables

# lsmod
Module                  Size  Used by
ip_tables               9099  0 
x_tables               10364  1 ip_tables
evdev                   6810  0 
pcspkr                  1383  0 
rtc_core               11631  0 
rtc_lib                 1454  1 rtc_core
ext3                  108916  1 
jbd                    35426  1 ext3
mbcache                 4250  1 ext3

# pacman -Syy iptables