weak percpu
今天看内核代码发现有个名为ARCH_NEEDS_WEAK_PER_CPU
的宏,定义了这个宏的架构只有两个:alpha和s390:
如果架构使用这种weak percpu,需要在 asm/percpu.h 中定义ARCH_NEEDS_WEAK_PER_CPU
:
weak percpu 不能定义在函数中,只能全局定义,如:
今天看内核代码发现有个名为ARCH_NEEDS_WEAK_PER_CPU
的宏,定义了这个宏的架构只有两个:alpha和s390:
如果架构使用这种weak percpu,需要在 asm/percpu.h 中定义ARCH_NEEDS_WEAK_PER_CPU
:
weak percpu 不能定义在函数中,只能全局定义,如:
当前节点是 6.13-rc7 版本内核。
mmap_lock
一开始并不叫这个名字,它原名是 mmap_sem
,在 5.8-rc1 版本中合并了一个修改,将其名改为 mmap_lock
:
这里其实不仅仅是更名,而是将之前 mmap_sem
的加解锁封装起来,提供一个新的 API 来加解锁。之前的加锁的做法是直接调用down_write(&mm->mmap_sem)
,而这个讨论提供了新的 API:
这样做的目的有两个:
mmap_sem
做一些事情,譬如发现加锁慢了,那我就可以直接在这个接口中做调试,而不用在读写锁的代码中调试;或者如果我要在加解锁 mmap_sem
之前或之后做一些事情,就会方便很多。mmap_sem
这把锁太大了,做性能优化的朋友可能会经常遇到这把锁竞争耗时严重,导致卡顿/延时等问题,未来这把锁可能会换种实现方式,不一定会使用读写信号量了,所以这里也把 mmap_sem
换成 mmap_lock
,算是优化 mmap_sem
工作的一部分吧。如果测量的事件超过一微妙,可以使用软件的系统定时器,在 Linux 系统上,可以通过clock_gettime
系统调用访问系统定时器。在 C++ 中访问系统定时器的标准方式是使用std::chrono
:
1 | #include <cstdint> |
如果测量的事件是纳秒级到一分钟,可以读取CNTVCT_EL0
寄存器,这是一种硬件定时器,被实现为一个硬件寄存器。时间戳计数器是单调的,并且速率恒定,即它不受频率变化的影响。每个 CPU 都有自己的时间戳计数器,它就是所经过的参考周期数。可以认为是 ARM 上 rdtsc
的实现:
1 | static inline uint64_t |
注:代码来源https://blog.csdn.net/z20230508/article/details/136741584
但是读 CNTVCT_EL0
的方法也不能精确到 cpu cycle,因为其频率通常比 cpu 频率低很多,譬如在天玑9400的机器(我用的是 vivo 的 X200 Pro)上,TSC 的频率仅为13MHZ,但是 CPU 频率都是 GHZ 为单位的。
我查阅资料,看到可以通过读取 PMCCNTR_EL0
寄存器来达到 cpu cycle 精度定时,但是这个寄存器默认用户态不可访问,需要内核打开,在 6.6 内核上,按照https://ilinuxkernel.com/?p=1755目前我没有打开成功。而且在手机上大小核并且频率还会动态变化,各个cpu的频率不一样会导致计数不准确,所以用这个方法需要将测试用例绑核并且固定核的频率。
文章翻译自 http://landley.net/writing/memory-faq.txt ,已有部分译文在 内存那些事儿(上) 和 内存那些事儿(中)。
写时复制如何工作?
如果两个页表项指向同个物理页,那么当读取这两个页表项就会得到同样的内容。物理页有相关的引用计数记录有多少页表项指向了它,每个指向它的页表项只有读权限,没有写权限。
如果写这个页面则会触发 page fault,page fault 处理程序会分配一个新页面,将其内容拷贝到新页面,将页表项更新到这个新页面,设置页表项可写,将旧页面的引用计数减一,最后返回到产生 page fault 的指令处重新执行,这次将写到新的页面中。
写时复制是一种延时分配(lazy allocation)机制,将内存分配推迟到真正需要的时候才进行。
什么是干净页面?
干净页面拥有存储在交换空间或文件中的数据副本,因此可以通过将其从相关的页表项中剥离回收其内存用于其它用途,当再次需要该页面的内容时,再分配一个新页面,然后将其内容从副本中读出来。
什么是活跃页面?
文章翻译自 http://landley.net/writing/memory-faq.txt ,已有部分译文在内存那些事儿(上)。
什么是文件映射?
文件映射是文件内容在内存中的镜像。映射的管理数据有:
当文件映射通过 page fault 分配新的物理页时,页面内容是通过读取磁盘中对应位置的内容来初始化的。内核中会缓存着一些磁盘的文件页,这个缓存称为 page cache。新分配的物理页通常是和 page cache 共享的,在文件被读进内存的时候,其内容通常就会被内核缓存起来,这些缓存的页面可与进程共享,以降低系统内存的使用量。
对使用 MAP_SHARED 标志创建的文件映射进行写操作时会更新 page cache 内容,使更新后的文件内容立即对使用该文件的其他进程可见,并且最终 page cache 会被刷新到磁盘,更新磁盘上文件的内容。
对使用 MAP_PRIVATE 标志创建的文件映射进行写操作时会执行写时复制,即分配一个新的本地页面副本来存储更改。这些更改对其他进程不可见,并且不会更新到磁盘上。
请注意,这意味着对 MAP_SHARED 页面的写操作不会分配额外的物理页面(页面已经通过读取进入了 page cache,如果物理页面在其他地方需要(译注:如内存不足),数据可以刷新回磁盘),但对 MAP_PRIVATE 页面的写操作就需要分配额外的物理页面(page cache 中的副本与程序需要的本地副本会不一致,因此需要两个页面来存储它们,并且将 page cache 中的副本刷新回磁盘不会释放更改内容的本地副本)。
buddy 分配器是 linux 内核中的经典分配器,学习 linux 内存管理的肯定绕不开它,甚至学习其他 linux 子系统的也会学学它,因为实在是太经典了。作者想从根本理解 buddy 分配器的原理,所以找来了 buddy 分配器最初提出的文章:A fast storage allocator 和 knuth 在The Art of Computer Programming, Fundamental Algorithms, Volume 1. 中深入分析 buddy 分配器一节(2.5 节)阅读,下面内容算是这两处内容的笔记了。
因为要用 qemu monitor 的 info tlb 功能,奈何 ubuntu 官方版本太低,不支持,所以只能源码安装了。