kk Blog —— 通用基础


date [-d @int|str] [+%s|"+%F %T"]
netstat -ltunp
sar -n DEV 1

Linux中Buffer cache

http://www.linuxidc.com/Linux/2013-01/78140.htm

buffer cache和page cache的区别

Page cache和buffer cache到底有什么区别呢?很多时候我们不知道系统在做IO操作的时候到底是走了page cache还是buffer cache?其实,buffer cache和page cache是Linux中两个比较简单的概念,在此对其总结说明。

Page cache是vfs文件系统层的cache,例如 对于一个ext3文件系统而言,每个文件都会有一棵radix树管理文件的缓存页,这些被管理的缓存页被称之为page cache。所以,page cache是针对文件系统而言的。例如,ext3文件系统的页缓存就是page cache。Buffer cache是针对设备的,每个设备都会有一棵radix树管理数据缓存块,这些缓存块被称之为buffer cache。通常对于ext3文件系统而言,page cache的大小为4KB,所以ext3每次操作的数据块大小都是4KB的整数倍。Buffer cache的缓存块大小通常由块设备的大小来决定,取值范围在512B~4KB之间,取块设备大小的最大公约数。


http://alanwu.blog.51cto.com/3652632/1112079

Linux中Buffer cache性能问题一探究竟

1, Buffer cache的作用

为了提高磁盘设备的IO性能,我们采用内存作为磁盘设备的cache。用户操作磁盘设备的时候,首先将数据写入内存,然后再将内存中的脏数据定时刷新到磁盘。这个用作磁盘数据缓存的内存就是所谓的buffer cache。在以前的Linux系统中,有很完善的buffer cache软件层,专门负责磁盘数据的缓存。在磁盘设备的上层往往会架构文件系统,为了提高文件系统的性能,VFS层同样会提供文件系统级别的page cache。这样就导致系统中存在两个cache,并且重叠在一起,显得没有必要和冗余。为了解决这个问题,在现有的Linux系统中对buffer cache软件层进行了弱化,并且和page cache进行了整合。Buffer cache和page cache都采用radix tree进行维护,只有当访问裸设备的时候才会使用buffer cache,正常走文件系统的IO不会使用buffer cache。

我们知道ext3文件系统的page cache都是以page页大小为单位的,那么buffer cache中缓存块大小究竟是多大呢?其对性能影响如何呢?这两天我在Linux-2.6.23平台上针对这个问题做了很多实验,得到了一些数据结果,并从源代码分析中得到设置缓存块大小的方法。在此对这个buffer cache的性能问题进行分析说明,供大家讨论。

2, Buffer cache的性能问题

2.1 测试实验

首先让我们来做一个实验,在Linux-2.6.23平台上,采用dd工具对一个块设备进行顺序写操作,可以采用如下的命令格式:

1
dd if=/dev/zero of=/dev/sda2 bs=<request_size> count=100

采用该命令在不同buffer cache块(blk_size)大小配置的情况下测试不同请求大小(req_size)的IO性能,可以得到如下表所示的测试数据:

表:不同buffer cache块大小配置下的吞吐量

将表中的数据做成性能对比图,如下图所示:

从图中可以看出,在请求大小小于Cache块大小的时候,Cache块越大,IO性能越高;但是,请求大小大于Cache块大小之后,性能都有明显的飞跃。

例如,当buffer cache块大小被配置成2KB时,小于2KB的块性能基本都在19MB/s左右;当buffer cache块大小被配置成512B时,小于512B的写性能都保持在5MB/s;当buffer cache块大小被配置成1024B时,小于1KB的写性能基本都保持在9.5MB/s上下。这就说明对于小于cache块大小的small_write,buffer cache越大,其性能会越好,反之,性能越差,这就是buffer cache的作用。

观察发现一旦请求大小大于等于cache块大小之后,性能急剧提升,由于测试工具的IO压力足够大,能够一下子将磁盘性能耗尽。这是为什么呢?其实,当请求块比较小时,对于cache块而言是“局部操作”,这种“局部操作”会引入buffer cache的数据读操作,并且数据读操作和用户写操作存在顺序关系,这就极大的影响了IO的写性能。因此,当请求大小大于cache块时,并且能够和Cache块对齐时,就能够充分利用磁盘的IO带宽,所以就产生了上图中所示的性能飞跃。

看到上图中的测试结果之后,我们就会想在实际应用中,我们该如何选择buffer cache的块大小?如果请求大小是512B时,显然将buffer cache块设置成512比较合适;如果请求大小是256B时,显然将buffer cache块设置成2KB比较合适。所以,个人认为块大小的设置还需要根据实际的应用来决定,不同的应用需要设置不同的块大小,这样才能使整体性能达到最佳。

2.2 Buffer cache块大小

Linux系统在创建块设备的时候是如何设置块大小的呢?这里面涉及到Linux针对块大小设置的一个小小算法。在此结合源码对Linux的这个方法加以说明。

总体来说,Linux决定buffer cache块大小采用的是“最大块大小”的设计思想。Linux根据块设备容量决定buffer cache的块大小,并且将值域限定在512B和4KB之间。当然,这个值域内的元素不是连续的,并且都是2的幂。在这个值域的基础上取块设备大小的最大公约数,这个值就是buffer cache的块大小。这种算法的指导思想就是buffer cache的块越大越好,因此,能够取2KB就不会选择512B。Linux中算法实现代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void bd_set_size(struct block_device *bdev, loff_t size)
{
	unsigned bsize = bdev_logical_block_size(bdev);

	bdev->bd_inode->i_size = size;      //size为块设备大小
	while (bsize < PAGE_CACHE_SIZE) {   //bsize不能大于Page size
		if (size & bsize)
			break;
		bsize <<= 1;    //bsize只能取2的幂
	}
	bdev->bd_block_size = bsize;
	/* 设置buffer cache块大小 */
	bdev->bd_inode->i_blkbits = blksize_bits(bsize);
}

3, 小结

本文对buffer cache的性能问题进行了分析,通过实验发现当请求块比较小时,buffer cache块大小对IO性能有很大的影响。Linux根据块设备的容量采用“最大cache块”的思想决定buffer cache的块大小。在实际应用中,我们应该根据应用特征,通过实际测试来决定buffer cache块大小。


通常Linux的“block size”指的是1024 bytes,Linux用1024-byte blocks 作为buffer cache的基本单位。但linux的文件系统的block确不一样。例如ext3系统,block size是4096。使用tune2fs可以查看带文件系统的磁盘分区的相关信息,包括block size。

例如:

1
2
tune2fs -l /dev/sda2 |grep "Block size"
Block size:               4096

另一个工具dumpe2fs也可以。 dumpe2fs /dev/sda2 | grep “Block size”

其实本来这几个概念不是很难,主要是NND他们的名字都一样,都叫“Block Size”。

1.硬件上的 block size, 应该是"sector size",linux的扇区大小是512byte

2.有文件系统的分区的block size, 是"block size",大小不一,可以用工具查看

3.没有文件系统的分区的block size,也叫“block size”,大小指的是1024 byte

4.Kernel buffer cache 的block size, 就是"block size",大部分PC是1024

5.磁盘分区的"cylinder size",用fdisk -l可以查看。

我们来看看fdisk显示的不同的信息,理解一下这几个概念:

1
2
3
4
5
6
7
Disk /dev/hda: 250.0 GB, 250059350016 bytes
255 heads, 63 sectors/track, 30401 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes
   Device Boot    Start       End    Blocks   Id  System
/dev/hda1   *         1      1305  10482381   83  Linux
/dev/hda2          1306      1566   2096482+  82  Linux swap
/dev/hda3          1567     30401 231617137+  83  Linux

8225280就是cylinder size。一共有30401个cylinder。Start和End分别标记的是各个分区的起始cylinder。第4列显示的就是以1024为单位的block(这一列最容易把人搞晕)。为什么“2096482+”有个“+”号呢?因为啊,总size除1024除不尽,是个约数。

Linux内核页回收 swappiness参数

http://www.douban.com/note/349467816/

本文主要尝试解释两个问题:
1. swappiness的确切含义是什么,它对内核进行页回收机制的影响。
2. swappiness设置成0,为什么系统仍然可能会有swap发生。

一. 关于内存分配与页回收(page reclaim)

page reclaim发生的场景主要有两类,一个是kswapd后台线程进行的活动,另一个是direct reclaim,即分配页时没有空闲内存满足,需要立即直接进行的页回收。大体上内存分配的流程会分为两部分,一部分是fast path,另一部分是slow path,通常内存使用非紧张情况下,都会在fast path就可以满足要求。并且fast path下的内存分配不会出现dirty writeback及swap等页回收引起的IO阻塞情况。

fast path大体流程如下:

1.如果系统挂载使用了memory cgroup,则首先检查是否超过cgroup限额,如果超过则进行direct reclaim,通过do_try_to_free_pages完成。如果没超过则进行cgroup的charge工作(charge是通过两阶段提交完成的,这里不展开了)。

2.从本地prefered zone内存节点查找空闲页,需要判断是否满足系统watermark及dirty ratio的要求,如果满足则从buddy system上摘取相应page,否则尝试对本地prefered zone进行页回收,本次fast path下页回收只会回收clean page,即不会考虑dirty page以及mapped page,这样就不会产生任何swap及writeback,即不会引起任何blocking的IO操作,如果这次回收仍然无法满足请求的内存页数目则进入slow path

slow path大体流程如下:

  1. 首先唤醒kswapd进行page reclaim后台操作。

  2. 重新尝试本地prefered zone进行分配内存,如果失败会根据请求的GFP相关参数决定是否尝试忽略watermark, dirty ratio以及本地节点分配等要求进行再次重试,这一步中如果分配页时有指定__GFP_NOFAIL标记,则分配失败会一直等待重试。

  3. 如果没有__GFP_NOFAIL标记,则会需开始进行page compact及page direct reclaim操作,之后如果仍然没有可用内存,则进入OOM流程。

相关内容可以参阅内核代码__alloc_pages函数的逻辑,另外无论page reclaim是由谁发起的,最终都会统一入口到shrink_zone,即针对每个zone独立进行reclaim操作,最终会进入shrink_lruvec函数,进行每个zone相应page lru链表的扫描与回收操作。

二. 关于页回收的一些背景知识

页回收大体流程会先在每个zone上扫描相应的page链表,主要包括inactive anon/active anon(匿名页链表)以及inactive file/active file链表(file cache/映射页链表),一共四条链表,我们所有使用过的page在被回收前基本是保存在这四条链表中的某一条中的(还有一部分在unevictable链表中,忽略),根据其被引用的次数会决定其处于active还是inactive链表中,根据其类型决定处于anon还是file链表中。

页回收总体会扫描逐个内存节点的所有zone,然后先扫描active,将不频繁访问的页挪到inactive链表中,随后扫描inactive链表,会将其中被频繁引用的页重新挪回到active中,确认不频繁的页则最终被回收,如果是file based的页则根据是否clean进行释放或回写(writeback,filecache则直接释放),如果是anon则进行swap,所以本文实际关心的是swappiness参数对anon链表扫描的影响。

另外还需要了解前面描述的四个链表原来是放在zone数据结构上的,后来引入了mem_cgroup则,重新定义了一组mem_cgroup_per_zone/mem_cgroup_per_node的数据结构,这四个链表同时定义在这组数据结构上,如果系统开启了mem cgroup则使用后者,否则用前者。

另外再重点说下swap只是page reclaim的一种处理措施,主要针对anon page,我们最终来看下swappiness的确切含义

三. swappiness对page reclaim的确切影响

page reclaim逻辑中对前面所述四个链表进行扫描的逻辑在vmscan.c中的get_scan_count函数内,该函数大部分逻辑注释写得非常清楚,我们简单梳理下,主要关注scan_balance变量的取值:

  1. 首先如果系统禁用了swap或者没有swap空间,则只扫描file based的链表,即不进行匿名页链表扫描 代码:
1
2
3
4
if (!sc->may_swap || (get_nr_swap_pages() <= 0)) {
	scan_balance = SCAN_FILE;
	goto out;
}
  1. 如果当前进行的不是全局页回收(cgroup资源限额引起的页回收),并且swappiness设为0,则不进行匿名页链表扫描,这个是没得商量,这里swappiness值直接决定了是否有swap发生,设成0则肯定不会发生,另外需要注意,这种情况下需要设置的是cgroup配置文件memory.swappiness,而不是全局的sysctl vm.swappiness 代码:
1
2
3
4
if (!global_reclaim(sc) && !vmscan_swappiness(sc)) {
	scan_balance = SCAN_FILE;
	goto out;
}
  1. 如果进行链表扫描前设置的priority(这个值决定扫描多少分之一的链表元素)为0,且swappiness非0,则可能会进行swap 代码:
1
2
3
4
if (!sc->priority && vmscan_swappiness(sc)) {
	scan_balance = SCAN_EQUAL;
	goto out;
}
  1. 如果是全局页回收,并且当前空闲内存和所有file based链表page数目的加和都小于系统的high watermark,则必须进行匿名页回收,则必然会发生swap,可以看到这里swappiness的值如何设置是完全无关的,这也解释了为什么其为0,系统也会进行swap的原因,另外最后我们会详细解释系统page watermark是如何计算的。 代码:
1
2
3
4
5
6
7
8
9
10
11
12
anon = get_lru_size(lruvec, LRU_ACTIVE_ANON) +
		get_lru_size(lruvec, LRU_INACTIVE_ANON);
file = get_lru_size(lruvec, LRU_ACTIVE_FILE) +
		get_lru_size(lruvec, LRU_INACTIVE_FILE);

if (global_reclaim(sc)) {
	free = zone_page_state(zone, NR_FREE_PAGES);
	if (unlikely(file + free <= high_wmark_pages(zone))) {
		scan_balance = SCAN_ANON;
		goto out;
	}
}
  1. 如果系统inactive file链表比较充足,则不考虑进行匿名页的回收,即不进行swap 代码:
1
2
3
4
if (!inactive_file_is_low(lruvec)) {
	scan_balance = SCAN_FILE;
	goto out;
}
  1. 最后一种情况则要根据swappiness值与之前统计的file与anon哪个更有价值来综合决定file和anon链表扫描的比例,这时如果swappiness设置成0,则也不会扫描anon链表,即不进行swap,代码比较多,不再贴出。

四. 系统内存watermark的计算

前面看到系统内存watermark对页回收机制是有决定影响的,其实在内存分配中也会频繁用到这个值,确切的说它有三个值,分别是low,min和high,根据分配页时来指定用哪个,如果系统空闲内存低于相应watermark则分配会失败,这也是进入slow path或者wakeup kswapd的依据。

实际这个值的计算是通过sysctl里的vm.min_free_kbytes来决定的,大体的计算公式如下:

1
2
3
4
5
6
pages_min = min_free_kbytes >> (PAGE_SHIFT - 10);
tmp = (u64)pages_min * zone->managed_pages;
do_div(tmp, lowmem_pages);
zone->watermark[WMARK_MIN] = tmp;
zone->watermark[WMARK_LOW] = min_wmark_pages(zone) + (tmp >> 2);
zone->watermark[WMARK_HIGH] = min_wmark_pages(zone) + (tmp >> 1);

即根据min_free_kbytes的值按照每个zone管理页面的比例算出zone的min_watermark,然后再加min的1/4就是low,加1/2就是high了

总结:

swappiness的值是个参考值,是否会发生swap跟当前是哪种page reclaim及系统当前状态都有关系,所以设置了swappiness=0并不代表一定没有swap发生,同时设为0也确实会可能发生OOM。

个人仍然认为线上环境设置swappiness=0是没有任何问题的。

Linux swap实现

http://blog.csdn.net/freas_1990/article/details/9090601

swap是现代Unix操作系统一个非常重要的特性。尤其在大型数据库服务器上,swap往往是性能首要查看指标。

通俗的说法,在Unix里,将开辟一个磁盘分区,用作swap,这块磁盘将作为内存的的替代品,在内存不够用的时候,把一部分内存空间交换到磁盘上去。

而Unix的swap功能也成为了Unixer们认为Unix由于windows的一个论据(?)。在Unix里,swap一般被认为设置为内存的2倍大小。这个2倍大小的指标出自哪里,到目前为止我也没有找到(?如果你找到了可以留言或发私信)。

不过,在内存不断掉价的今天,swap的功效已经越来越弱化了——在2013年6月13日23:01,如果一个OLTP系统的swap使用超过了2G以上,基本上可以对这个系统的性能产生怀疑了。swap并不是一种优化机制,而是一种不得已而为之的手段,防止在内存紧张的时刻,操作系统性能骤降以至瞬间崩溃。swap的价值主要体现在可以把这个崩溃的时间提升至几小时到几十个小时不等。

本文主要关注CPU访问一个内存page时,发现该page不在内存中的情况。废话不多说了,先把swap的核心函数调用栈贴一下。

当CPU检查一个页目录项/页表项的Present标志位时,如果发现该标志位为0,则表示相应的物理页面不在内存。此时,CPU会被激发“页面异常”(中断中的fault),而去执行一段代码。

至于到底是这个内存页面需要重新构建、还是页面的内容是存储到磁盘上去了,CPU本身是不关心的,CPU只知道中断条件发生了,要根据中断描述符跳转到另外一段代码去执行,而真正的swap或者是真的缺页的智能判断是在这段中断服务程序里做的——真正的技术是在这段中断服务程序里。(所以我在《中断——一鞭一条痕(下)》里说,作为一个初学者,不必深究中断(interrupt)、异常(exception)、陷阱(trap)这三个概念)

pte_present()函数会检查当前页面的描述entry的present标志位,查看该page是否在内存中。如果不在内存中,调用pte_none()判断是否建立了页目录、页表映射。如果连映射都没建立,说明是“真没在内存中”,需要从头建立映射关系。如果建立了映射关系,说明此时,该页面被暂时存储到磁盘上去了,应该到磁盘上去把该page取回来放到内存里。

如何去取呢?

如何到磁盘取一个page的数据到内存中去,这是一个多么熟悉的概念!思考一下Oracle的内存管理,一个block如何读入到SGA的buffer cache里去吧。其实这几十年来,核心的本源技术无论是在操作系统内核还是在数据库内核里,都是通用的,都是用来极大限度提升CPU任务管理能力、内存管理效率的,所有的理念、技术都是通用的——如果你站在一个系统程序猿的角度来思考,一定能明白的——不要把自己局限在一个产品里,无论这个产品是数据库、CPU、还是操作系统,这些看似绚烂神秘的技术在30年以前,已经被人反复的讨论和意淫过了。

接下来就到了核心部分了——do_swap_page()函数。

源代码如下(linux/mm/memory.c line 2022~1060):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
static int do_swap_page(struct mm_struct * mm,
	struct vm_area_struct * vma, unsigned long address,
	pte_t * page_table, swp_entry_t entry, int write_access)
{ 
	struct page *page = lookup_swap_cache(entry);
	pte_t pte;

	if (!page) {
		lock_kernel();
		swapin_readahead(entry);
		page = read_swap_cache(entry);
		unlock_kernel();
		if (!page)
			return -1;

		flush_page_to_ram(page);
		flush_icache_page(vma, page);
	}

	mm->rss++;

	pte = mk_pte(page, vma->vm_page_prot);

	/*
	 * Freeze the "shared"ness of the page, ie page_count + swap_count.
	 * Must lock page before transferring our swap count to already
	 * obtained page count.
	 */
	lock_page(page);
	swap_free(entry);
	if (write_access && !is_page_shared(page))
		pte = pte_mkwrite(pte_mkdirty(pte));
	UnlockPage(page);

	set_pte(page_table, pte);
	/* No need to invalidate - it was non-present before */
	update_mmu_cache(vma, address, pte);
	return 1;   /* Minor fault */
}

这里有2个参数需要重点关注,一个是(pte_t *)page_table,另外一个是(swp_entry_t*)entry

当一个page在内存中,不需要swap in时,描述该page的entry是pte_t类型的;反之,是swp_entry_t类型。

swap_entry_t(include/linux/shmem_fs.h)定义如下:

1
2
3
typedef struct {
	unsigned long val;
} swp_entry_t;

问题出来了,既然都进入do_swap_page()函数了,说明是需要swap in了,为什么还会传入一个pte_t类型的变量呢?

答案是,当在do_swap_page()之前,page是在磁盘上的,描述类型是swp_entry_t,而do_swap_page()之后,页面已经从磁盘交换到内存了,这个时候描述类型就是pte_t了。

至于lookup_swap_cache、swapin_readahead(预读——read ahead)等函数就不一一分析了,从名字就可以看出其技巧了。都是些在数据库server上的常用技巧。如果你是行家,一眼就能看出来。