月度归档:2014年10月

从free命令看Linux内存管理

free命令是Linux系统下用来查看内存使用情况的,例如:

$ free -h
             total       used       free     shared    buffers     cached
Mem:          7.8G       6.6G       1.3G         0B       600M       1.9G
-/+ buffers/cache:       4.1G       3.7G
Swap:         2.0G         0B       2.0G

第一行Mem是从操作系统层面看到的内存使用情况,total表示物理内存总量,used表示当前已使用量,free表示当前未分配内存,shared表示共享内存大小,可能已废弃,固定为0,buffers和cached表示被操作系统用来当做磁盘高速缓存的内存大小。这里total=used+free,并且buffers和cached包含在used里,因为在操作系统看来,用于缓存的内存也是属于被用掉的内存。

第二行-/+ buffers/cache从程序角度看到的内存使用情况,used表示被当前系统中所有进程用掉的物理内存大小,free表示当前程序可用的物理内存大小。从数值上看,used=Mem:used-buffers-cached,free=Mem:free+buffers+cached。这里涉及到Linux对内存的管理方式,buffers和cached是两种不同类型的磁盘高速缓存,分别称为块缓存(buffer cache)和页缓存(page cache)。当物理内存剩余充分的时候,操作系统可能会将非常多的空闲内存用作缓存,借以提升磁盘IO性能,这种机制造成的特有现象就是很多时候Mem:free值很低,而buffers和cached的值很高,这个时候并不意味着内存吃紧。当有应用程序需要分配内存时,操作系统会逐渐回收buffers和cached占用的内存空间,这个过程中,Mem:used和Mem:free值不会有大的变动,但是-/+ buffers/cache used和free会体现出变化,具体表现为free值减少,used值增加。所以,第二行的-/+ buffers/cache往往比第一行的Mem统计数据更有参考意义,因为第二行显示的才是当前操作系统真正可用的内存使用量和剩余量。

第三行Swap表示交换空间的使用情况,Swap存在于磁盘中,可以认为是内存的扩展,当应用程序继续吃进内存,操作系统已经没有多余buffers和cached可供回收时,就会将一部分内存页交换到Swap区,以便腾出可用的物理内存。假如此时某个进程关闭腾出了大块物理内存(used值降低,free值上升),操作系统不会把原先置换到Swap区的内存页交换回物理内存,空出来的物理内存会被用来做缓存(buffers+cached)。

为了有个更加直观的解释,特地从网上盗图一张,加深理解:
free

上文的描述都未对页缓存和块缓存进行具体区分,实际上,两者还是有一定区别的。简单来说,页缓存(page cache)为了提升文件内容的读写效率,属于文件系统级别的缓存;块缓存(buffer cache)为了提升块设备的读写效率,属于磁盘级别的缓存。

Linux下文件系统中的文件是由文件内容和元信息(inode)两部分组成的,其中文件内容是分散存储在磁盘的不同块中,通过元信息来组织和描述,这些磁盘上的块数据形成了一个逻辑意义上的文件。因此,磁盘高速缓存机制分成两个层次。当操作系统通过文件系统接口读出文件时,文件内容会被缓存在页缓存里;当文件系统继而将文件内容从磁盘中读出时,文件内容会被缓存在块缓存里。很显然,一次读文件操作会导致文件内容被页缓存和块缓存分别缓存一次,Linux 2.4版本之前就是这么设计的。2.6版本以后,这部分功能进行了优化,对于文件内容的读写,块缓存中的地址被指向到页缓存中,从此,内存中只有一份文件内容的缓存。但是,这并不意味着块缓存可以去除,因为有些场景是需要内核直接读写块设备的,这时候块缓存仍旧有着用武之地,比如读写inode,或者一些跳过文件系统直接操作块设备的命令,如dd等。

References:
[1] Linux Kernel: What is the major difference between the buffer cache and the page cache?
[2] linux cache and buffer

--EOF--

多级反向代理下的维护页面配置

Nginx的error_page指令可以用来在服务器临时维护时呈现静态维护页面,增强用户体验,但是在稍微复杂的反向代理部署架构中,只有error_page还是不够的。比如:
proxy
Tomcat前端接Apache,Apache前端接Nginx,流量入口在Nginx。这样一个两级的反向代理结构下,如果服务器程序升级,Tomcat重启,Apache检测到Tomcat服务不可用时,向上级返回503,那么用户通过Nginx看到的,也是丑陋的503 Service Temporarily Unavailable页面,试图在Nginx配置中加上error_page 503 /50x.html页面也不会起作用。探其原因,在于error_page指令是用来处理由Nginx产生的错误,在Nginx看来,Apache工作正常,且能返回响应码和正确页面,只不过返回的页面对用户来说是个服务不可用页面。如果是Apache停掉,那么Nginx是能够正确呈现error_page指定的维护页面的。

所以,需要另外在Nginx配置中加入proxy_intercept_errors指令,官方说法是:

Determines whether proxied responses with codes greater than or equal to 300 
should be passed to a client or be redirected to nginx for processing with the 
error_page directive.

设置proxy_intercept_errors为on后,Nginx会解析被代理服务器的响应码,如果出现大于300的响应码,就会调用error_page指令处理,如果error_page指令中没有指定返回的错误码,那么Nginx仍会依样返回被代理服务器的响应。

以下为简化的配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
server {
 server_name domain.com;
 location / {
  ...
  proxy_pass ...;
  proxy_intercept_errors on;
 } 
 
 error_page 503 /50x.html;
 location = /50x.html {
  root /etc/nginx/maintein;
 }
 #50x.html中引入了同级目录下的static目录中的图片文件。
 location ~ /static {
  root /etc/nginx/maintein;
  rewrite ^(.*)/(static/.*)$ /$2 break;
 }
}

--EOF--

Linux文件系统的琐碎知识

作为Java服务器端开发人员,平时容易忽略掉文件系统相关的知识储备,抽空把所了解的整理成文,作为备忘。

文件系统是Linux内核的五大核心子系统之一,我理解的文件系统的作用就是用来组织磁盘或其他存储设备上的数据,使得这些数据可被应用程序访问和存取。有一个不大恰当的比喻,文件系统也是一种关于数据传输的协议,如果说网络协议(IP、TCP)约定了数据流在网络上的传输,那么文件系统就是约定了数据在存储设备中的组织形式,以及定义了数据在应用程序和存储设备间如何交换。

不同的文件系统有不同的方式来组织存储设备上的数据,Linux内核的虚拟文件系统(VFS)提供了一层抽象,将用户态应用程序和不同的文件系统实现解耦开来。VFS对应用程序暴露一些高度抽象的文件访问接口(open, read, write...),任何实现了这些接口的具体文件系统类型(ext3,ext4,xfs...)都可以以内核模块(或其他)的方式接入。VFS的设计使得Linux系统下的文件系统极具扩展性。

从一个高层次的角度看,Linux文件系统分层架构可以表示如下:
VFS

Linux文件系统组件(来源)

其中,蓝色部分用户态应用程序或者C运行时库(glibc)的文件IO请求通过系统调用进入内核态,黄色部分属于Linux内核文件系统相关的组件,虚拟文件系统(VFS)通过一些数据结构(超级块、inode、数据块、dentry等)来屏蔽底层具体文件系统访问存储设备的细节,这里会有个inode和dentry缓存,用于加快文件读取速度。另外具体文件系统通过存储设备驱动来操作设备,具体文件系统和设备之间还有块缓存,用于加快磁盘块的读取速度。

从操作层面上看,一块硬盘插入主板接口,通过文件系统这座桥梁,到可以被应用程序访问、读写,大致可以分为三个步骤:分区、格式化、挂载(mount)。

1. 分区

硬盘中数据存储和寻道相关的概念有以下几个:

a. 盘片(Platter): 盘片绕中心轴转动,一块硬盘有多个盘片组成,数据都是存储在盘片上。
b. 磁头(Head): 读写数据,沿着盘片半径方向移动。一个盘片有上下两面,各对应一个磁头。
c. 磁道(Track): 盘片上划分出来的一系列同心圆,通过0、1、2……数字编号。
d. 扇区(Sector): 磁道上划分出来的一系列扇形区域,是磁头读写数据的最小单位,一般为512字节。
e. 柱面(Cylinder): 所有盘片中相同数字编号磁道组成的一个逻辑上的圆柱面。

通过盘片的圆周旋转和磁头的半径方向移动,可以实现读写任意一个扇区中的数据。一图胜千言,上述概念的示意图如下:
disk

硬盘要分区(Partition)了才能使用。所谓分区,就是将硬盘以不同的柱面为界线,划分成相对独立的一块块连续区域。由于一些约定俗成的设计,一块硬盘的分区个数是有限制的,限制的原因在于整块硬盘中只用了一个扇区(0号扇区)来存储主引导记录和分区相关的信息。其中,主引导记录(MBR)占了446个字节,分区表占了64个字节。

64个字节的分区表最多能存储4个分区信息,所谓的分区信息其实很简单,就是该分区的起始柱面号和结束柱面号。如果需要将一块盘划为超过4个分区,需要使用扩展分区,通过在扩展分区里使用额外的扇区来存储分区信息,一个扩展分区可以划分成一个或多个逻辑分区。总之, 默认分区表只能存储4个分区信息,可以是下列组合:

a. 4个主分区(P)。
b. 3个主分区(P)和1个扩展分区(E),这个扩展分区可以继续分成1个或多个逻辑分区(L),逻辑分区的设备名称号码统一从5开始(比如sda5, hda5...)。
c. 2个主分区(P)和1个扩展分区(E)。
d. 1个主分区(P)和1个扩展分区(E)。

更多磁盘和分区相关的信息可以移步鸟哥私房菜『磁盘分区』,解释得相当详细。

Linux中可以通过fdiskparted命令对磁盘进行分区操作。

2. 格式化

分区是针对磁盘的操作,与具体操作系统无关;而格式化则是针对磁盘元信息的初始化操作,与操作系统具有相关性,只有当分区被格式化成具体文件系统类型后,操作系统才能在这个分区上进行文件的存取操作。早期分区和文件系统是一对一的关系,但是随着一些存储技术的发展,两者的对应关系已经模糊,比如通过RAID可以将多个分区合为一个文件系统,通过LVM可以将一个分区格式化为多个文件系统。

Linux把一个文件(包括目录)的内容和其元信息(权限、所属用户、修改/创建时间等属性)分开存储,文件内容存储在数据块(data block)中,元信息存储在inode(索引节点,index node)里,一个文件对应一个inode,inode是一个数据结构,由一个唯一的inode number标记,它包含了除文件名和文件内容以外的所有文件元信息。除此之外,超级块(super block)也是Linux文件系统中的重要概念,它记录了当前文件系统中的inode和数据块的使用/剩余量,以及其他一些文件系统相关的全局信息。

当我们所讲到inode,在VFS和具体文件系统中含义还略有不同,虽然它们大部分属性和值都是相同的。在VFS中,inode是内存中的一个数据结构,在打开一个文件时创建,各个属性的值由具体文件系统的inode填充;与此同时,还会创建一个dentry内存对象,用来维护文件名和inode number的映射关系,以及此文件父目录的dentry结构体指针。在具体文件系统中,inode结构体是实实在在存储在磁盘中的。可以认为具体文件系统中的inode信息是静态的,持久存在的,而VFS inode是临时的,存储在内存中的。在具体文件系统的实现中,以ext4为例,当一个分区被格式化为具体的文件系统时,整个分区被划为多个块组(block group),每个块组都至少包括以下几块区域:

a. 数据块位图(block bitmap):存储在一个块里。每一位表示一个数据块是否已存储着数据。
b. inode位图(inode bitmap):存储在一个块里。每一位表示一个inode是否已被分配给某个文件。
c. inode表: 存储在多个连续的块里。一个inode占用128个字节,用多级索引的方式解决大文件索引问题,如下图所示的Inode结构(图示为ext2的inode结构,虽然ext2已经过时,但是inode结构体在ext3和ext4中基本一致)。
d. 数据块:具体存储文件内容的地方。

这几块区域在磁盘中的布局如下:
Ext4 Layout

Ext4的磁盘布局(来源)

inode

Inode结构(来源)

关于VFS inode,可以参考这篇『Anatomy of the Linux file system』,关于具体文件系统的inode介绍可以参考『Linux 磁盘与文件系统管理』和『理解inode』,都是非常地浅显易懂。关于两者更细致的区别,可以参考『dentry与inode』和『inode』。

Linux中可以通过mkfs -t {fstype} {partition}命令或mkfs.{fstype} {partition}命令来格式化分区,通过dumpe2fs {patition}命令可以查询超级块和块组相关的信息,很多命令(比如ls, df等)支持-i参数显示inode相关信息。

3. 挂载

对硬盘进行分区、格式化成文件系统后,还要要将文件系统挂载到Linux的目录树上去,才能被应用程序访问。被挂载的目录称为挂载点。挂载和卸载可以通过mountumount命令执行。默认情况下,执行mount命令挂载的文件系统在系统重启后不会自动挂载,要想随系统启动自动挂载,可以编辑/etc/fstab,将文件系统、挂载点、挂载参数等信息持久化到此文件。实际上,当mount -a命令就是从/etc/fstab文件中读取相关挂载信息,将未挂载的文件系统都挂载上去。

Linux文件系统相关的知识实在太庞大了,本想着多记一些,实在是水平有限,无法深入,暂且就记这么多吧。

--EOF--