# 统计数据

当进程执行内存相关操作时，如分配大页，内核会记录下相关动作。这些数据作为进程生命周期的一部分存在。

## mm->rss\_stat\[]

### 位置

首先我们来看看mm->rss\_stat这个统计数据，在mm\_struct中有一个rss\_stat\[]数组。

### 类型

这个数组一共就这么些类型。

```
enum {
	MM_FILEPAGES,	/* Resident file mapping pages */
	MM_ANONPAGES,	/* Resident anonymous pages */
	MM_SWAPENTS,	/* Anonymous swap entries */
	MM_SHMEMPAGES,	/* Resident shared memory pages */
	NR_MM_COUNTERS
};
```

### 操作

对操作的方法是

修改类:

* add\_mm\_counter()
* inc\_mm\_counter()
* dec\_mm\_counter()

读取类:

* get\_mm\_counter\_sum()

这个看上去是统计进程中各种类型内存的数量，以base page为单位。

比如在do\_anonymous\_page()中，如果分配成功了匿名页，则会 add\_mm\_counter(vma->vm\_mm, MM\_ANONPAGES, nr\_pages);来增加对应的计数。

而在do\_swap\_page()中，如果是交换进页面，则会

```
	add_mm_counter(vma->vm_mm, MM_ANONPAGES, nr_pages);
	add_mm_counter(vma->vm_mm, MM_SWAPENTS, -nr_pages);
```

增加匿名页计数，同时减少swap页计数。

### 展示

这里统计的值会在文件[/proc/self/status](https://richardweiyang-2.gitbook.io/kernel-exploring/nei-cun-guan-li/00-index-3/02-per_process/06-status)中展示。

其中的RssAnon, RssFile, RssShmem和VmSwap。

## vm\_event\_state

### 位置

这是一个全局的per\_cpu数组。

```
struct vm_event_state {
	unsigned long event[NR_VM_EVENT_ITEMS];
};

DECLARE_PER_CPU(struct vm_event_state, vm_event_states);
```

此外，这个还是一个内核配置项。只有配置了CONFIG\_VM\_EVENT\_COUNTERS才会记录相关的事件信息。

### 类型

这个种类就多很多了， 详见vm\_event\_item，目测有120+种。

### 操作

这个事件计数的操作比较简单：

变更：

* \_count\_vm\_event()
* count\_vm\_event()
* \_count\_vm\_events()
* count\_vm\_events()

读取：

* all\_vm\_enents()

### 观察

这个计数是可以观察的，也就是在我们熟知的[vmstat](https://richardweiyang-2.gitbook.io/kernel-exploring/nei-cun-guan-li/00-index-3/01-global/08-vmstat)文件。

不过vmstat文件中，不仅包含了vm\_event\_states的信息。所以事件记录是从pgpgin字段开始的。

## pgdat->vm\_stat\[]/vm\_node\_stat\[]

### 位置

看了下，发现一个有意思的东四，有两个一模一样的统计数组。

* pgdat->vm\_stat\[]
* vm\_node\_stat\[]

两个都是原子变量，只不过一个在pgdat中，一个是全局的。

```
atomic_long_t vm_node_stat[NR_VM_NODE_STAT_ITEMS] __cacheline_aligned_in_smp;
```

所以可以看到，最核心的变量更新函数 node\_page\_state\_add()是对这两个变量同时更新的。

```
static inline void node_page_state_add(long x, struct pglist_data *pgdat,
				 enum node_stat_item item)
{
	atomic_long_add(x, &pgdat->vm_stat[item]);
	atomic_long_add(x, &vm_node_stat[item]);
}
```

而且这个XXX\_add()名字好迷惑，实际上只要传入的参数是负数，就是减法操作。

### 类型

一个枚举型数组，里面约有四十左右类型。

包括了active\_anon, active\_file等。

### 操作

目前看到操作这个数组的方式，除了直接通过原子操作atomic\_long\_read()之外，有下面几个封装。

修改类:

* 以pgdate为参数,最核心的API，其余都是调用这个
  * node\_page\_state\_add(pgdat, )
  * \_\_inc\_node\_state(pgdat, )
  * \_\_dec\_node\_state(pgdat, )
  * \_\_mod\_node\_page\_state(pgdat, )
  * mod\_node\_page\_state(pgdat, ), 可能依赖mod\_node\_state()
* 以page为参数
  * \_\_inc\_node\_page\_state(page, )
  * inc\_node\_page\_state(page, )
  * \_\_dec\_node\_page\_state(page, )
  * dec\_node\_page\_state(page, )
* 以folio为参数
  * \_\_node\_stat\_mod\_folio(folio, )
  * node\_stat\_mod\_folio(folio, )
  * \_\_node\_stat\_add\_folio(folio, )
  * node\_stat\_add\_folio(folio, )
  * \_\_node\_stat\_sub\_folio(folio, )
  * node\_stat\_sub\_folio(folio, )
  * \_\_folio\_mod\_stat(folio, )

大致看了下，有下划线前缀的是不关中断的版本。偷懒的讲，不确定的时候就有没有下划线的版本比较保险。

基本上以page为参数的API应该是要退出历史舞台了，取而代之的是以folio为参数的。

但是统计数据中有些是和内存页无关的，比如PGPROMOTE\_CANDIDATE，就还是用以pgdat为参数的API。

读取类:

* global\_node\_page\_state()
* global\_node\_page\_state\_pages()

这两个蛮奇怪的，global\_node\_page\_state()调用的是global\_node\_page\_state\_pages()。而前者会在某条件下warn一次。

### 展示

在[/proc/meminfo](https://richardweiyang-2.gitbook.io/kernel-exploring/nei-cun-guan-li/00-index-3/01-global/07-meminfo)中会读取global\_node\_page\_state()来返回当前系统相关信息。

## zone->vm\_stat\[]/vm\_zone\_stat\[]

### 位置

和vm\_node\_stat\[]一样，有两个一模一样的数组：一个是全局原子变量，一个是在zone里的变量

```
atomic_long_t vm_zone_stat[NR_VM_ZONE_STAT_ITEMS] __cacheline_aligned_in_smp;
```

修改的时候，两者会同时修改。

### 类型

一个枚举型数组，里面约有四十左右类型。

### 操作

修改类:

* 以zone为参数
  * zone\_page\_state\_add(zone, x)
  * \_\_inc\_zone\_state(zone, )
  * \_\_dec\_zone\_state(zone, )
  * mod\_zone\_page\_state(), 可能依赖mod\_zone\_state(zone, )
* 以page为参数
  * \_\_inc\_zone\_page\_state(page, )
  * inc\_zone\_page\_state(page, )
  * \_\_dec\_zone\_page\_state(page, )
  * dec\_zone\_page\_state(page, )
* 以folio为参数
  * \_\_zone\_stat\_mod\_folio(folio, )
  * zone\_stat\_mod\_folio(folio, )
  * \_\_zone\_stat\_add\_folio(folio, )
  * zone\_stat\_add\_folio(folio, )
  * \_\_zone\_stat\_sub\_folio(folio, )
  * zone\_stat\_sub\_folio(folio, )

有意思的是，folio的这些api中，目前只用了zone\_stat\_mod\_folio()。

而page的api，目前也只有zsmalloc在用了。

读取类:

* global\_zone\_page\_state()

### 展示

在[/proc/vmstat](https://richardweiyang-2.gitbook.io/kernel-exploring/nei-cun-guan-li/00-index-3/01-global/08-vmstat)中会读取global\_zone\_page\_state()来返回当前系统相关信息。
