[Linux]学习笔记系列 -- 内存管理与访问


title: 内存管理与访问

categories:

  • linux
  • include
    tags:
  • linux
  • include
    abbrlink: 85edd85d
    date: 2025-10-03 09:01:49

https://github.com/wdfk-prog/linux-study

文章目录

  • [include/linux/cache.h: 提供与CPU缓存行对齐相关的宏,如 ____cacheline_aligned](#include/linux/cache.h: 提供与CPU缓存行对齐相关的宏,如 ____cacheline_aligned)
    • [__cacheline_aligned 缓存行对齐](#__cacheline_aligned 缓存行对齐)
    • [__ro_after_init 只读数据](#__ro_after_init 只读数据)
    • [____cacheline_internodealigned_in_smp 在多处理器(SMP)系统中对数据结构进行缓存线对齐](#____cacheline_internodealigned_in_smp 在多处理器(SMP)系统中对数据结构进行缓存线对齐)
      • [4. **非 SMP 系统**](#4. 非 SMP 系统)
      • [5. **用途**](#5. 用途)
      • [6. **总结**](#6. 总结)
  • [include/linux/prefetch.h: 提供 prefetch() 宏,用于向CPU发出预取数据到缓存的指令](#include/linux/prefetch.h: 提供 prefetch() 宏,用于向CPU发出预取数据到缓存的指令)
    • [prefetch 预取 "x" 处的 cacheline 进行读取](#prefetch 预取 “x” 处的 cacheline 进行读取)
    • [prefetchw 预取 "x" 处的 cacheline 进行写入](#prefetchw 预取 “x” 处的 cacheline 进行写入)
  • [include/linux/poison.h: 为调试目的,定义用于填充已释放内存的"毒药"值](#include/linux/poison.h: 为调试目的,定义用于填充已释放内存的“毒药”值)
  • [include/linux/uaccess.h: (内存标记) 提供内核空间与用户空间之间安全拷贝数据的核心函数,如 copy_to_user()](#include/linux/uaccess.h: (内存标记) 提供内核空间与用户空间之间安全拷贝数据的核心函数,如 copy_to_user())
    • [user_access_save user_access_restore](#user_access_save user_access_restore)
  • [include/linux/mmdebug.h: 提供内存管理子系统的调试宏和断言](#include/linux/mmdebug.h: 提供内存管理子系统的调试宏和断言)

include/linux/cache.h: 提供与CPU缓存行对齐相关的宏,如 ____cacheline_aligned

__cacheline_aligned 缓存行对齐

  • 这个修饰符确保变量在内存中对齐到缓存行的边界上。缓存行对齐可以减少缓存行冲突,提高缓存命中率,从而提高系统性能。在多处理器系统中,缓存行对齐尤为重要,因为多个处理器可能同时访问相同的内存区域。通过对齐,可以减少缓存一致性协议带来的开销。

    • 在多处理器系统中,缓存行对齐可以减少缓存一致性协议带来的开销,主要原因如下:
    • 缓存一致性协议:在多处理器系统中,每个处理器都有自己的缓存,用于存储频繁访问的数据。为了确保所有处理器看到的数据是一致的,系统使用缓存一致性协议(如MESI协议)。当一个处理器修改了某个缓存行中的数据,其他处理器必须更新或失效其缓存中的相应数据。这种一致性维护会带来额外的开销。
    • 缓存行对齐的好处
    1. 减少伪共享:伪共享(False Sharing)是指多个处理器访问同一个缓存行中的不同数据项,导致频繁的缓存一致性操作。通过缓存行对齐,可以确保每个处理器访问的数据项位于不同的缓存行中,从而减少伪共享的发生。例如,如果两个处理器分别访问两个变量,而这两个变量恰好位于同一个缓存行中,那么每次一个处理器修改其中一个变量时,另一个处理器的缓存行也会失效,导致频繁的缓存一致性操作。通过对齐,可以将这两个变量放在不同的缓存行中,避免这种情况。
    2. 提高缓存命中率:缓存行对齐可以确保数据项在缓存中的位置是有序和连续的,从而提高缓存命中率。高缓存命中率意味着处理器可以更频繁地从缓存中读取数据,而不是从主内存中读取,从而提高系统性能。当数据项对齐到缓存行边界时,处理器可以更高效地预取和访问这些数据,减少缓存未命中的情况。
    3. 优化内存访问:缓存行对齐可以优化内存访问模式,使得内存访问更加高效。处理器可以更高效地加载和存储对齐的数据,从而减少内存访问的延迟。对齐的数据可以更好地利用处理器的缓存预取机制,减少内存访问的延迟。
c 复制代码
#ifndef __cacheline_aligned
#define __cacheline_aligned					\
  __attribute__((__aligned__(SMP_CACHE_BYTES),			\
		 __section__(".data..cacheline_aligned")))  
         //这个属性将变量放置在名为 .data..cacheline_aligned 的特定内存段中。这有助于将对齐的变量集中在一起,进一步优化内存访问
#endif /* __cacheline_aligned */

#ifndef __cacheline_aligned_in_smp
#ifdef CONFIG_SMP
#define __cacheline_aligned_in_smp __cacheline_aligned
#else
#define __cacheline_aligned_in_smp
#endif /* CONFIG_SMP */
#endif

__ro_after_init 只读数据

c 复制代码
/*
 * __ro_after_init 用于标记 init 之后的只读内容(即
 * 在调用 mark_rodata_ro() 之后)。这些实际上是只读的,
 * 但可能会在 INIT 期间写入,因此不能存在于 .rodata 中(通过"const")。
 */
#ifndef __ro_after_init
#define __ro_after_init __section(".data..ro_after_init")
#endif

____cacheline_internodealigned_in_smp 在多处理器(SMP)系统中对数据结构进行缓存线对齐

这段代码定义了一个宏 ____cacheline_internodealigned_in_smp,用于在多处理器(SMP)系统中对数据结构进行缓存线对齐,以优化性能。以下是对这段代码的详细解释:

c 复制代码
__attribute__((__aligned__(1 << (INTERNODE_CACHE_SHIFT))))
  • __attribute__((__aligned__(...))) 是 GCC 提供的一个扩展,用于指定变量或结构的对齐方式。
  • (1 << (INTERNODE_CACHE_SHIFT)) 计算对齐的字节数,通常与缓存线的大小相关。INTERNODE_CACHE_SHIFT 是一个与缓存线大小相关的常量,表示对齐的位移量。
  • 这种对齐方式确保数据结构在内存中与缓存线对齐,从而减少跨缓存线访问的开销,提高多核系统中的性能。

4. 非 SMP 系统

  • 如果未启用 CONFIG_SMP,宏 ____cacheline_internodealigned_in_smp 被定义为空:

    c 复制代码
    #define ____cacheline_internodealigned_in_smp
    • 在单处理器系统中,缓存线对齐通常不是必要的,因为不存在多个 CPU 核心竞争缓存资源。

5. 用途

  • 这个宏通常用于定义需要在多核系统中共享的全局变量或数据结构,例如锁、队列或统计信息。
  • 在多核系统中,缓存线对齐可以减少伪共享(false sharing)的发生。伪共享是指多个 CPU 核心访问同一个缓存线中的不同数据时,导致不必要的缓存一致性操作,从而降低性能。

6. 总结

这段代码通过条件编译,根据系统是否支持 SMP 来决定是否对数据结构进行缓存线对齐。在多核系统中,这种对齐方式可以显著提高性能,而在单核系统中则避免了不必要的内存开销。这种设计兼顾了性能优化和资源节约,是内核开发中常见的模式。

include/linux/prefetch.h: 提供 prefetch() 宏,用于向CPU发出预取数据到缓存的指令

c 复制代码
/*
	prefetch(x) 尝试抢先获取指向的内存
	通过地址 "x" 进入 CPU L1 高速缓存。 
	prefetch(x) 不应导致任何类型的异常,prefetch(0) 是
	具体来说 OK。

	prefetch() 应该由架构定义,如果不是,则使用 
	下面的 #define 提供了 no-op 定义。	
	
	有 2 个 prefetch() 宏:
	
	prefetch(x) - 预取 "x" 处的 cacheline 进行读取
	prefetchw(x) - 预取 "x" 处的 cacheline 进行写入
	
	还有 PREFETCH_STRIDE 是 ArchiteCure 的首选 
	"lookahead" 大小来预取流式作。
	
*/

prefetch 预取 "x" 处的 cacheline 进行读取

c 复制代码
#ifndef ARCH_HAS_PREFETCH
#define prefetch(x) __builtin_prefetch(x)	//使用编译器的预取指令
#endif

arch/arm/include/asm/processor.h

  1. pld 是 ARM 汇编指令,用于预取数据到缓存中。它的作用是将指定地址的数据预取到 CPU 的 L1 缓存中,以提高后续访问的速度。
  2. pldw 是 ARM 汇编指令,用于预取数据到缓存中,并且它会将数据标记为"写入"。这意味着预取的数据可以在后续操作中被修改。
  3. __ALT_SMP_ASM 是一个宏,用于在单处理器和多处理器系统之间切换不同的汇编指令。它的作用是根据系统的配置选择适当的汇编指令。
  4. pld\t%a0 是 ARM 架构的预取指令,其中 pld 表示预取数据(Preload Data)。%a0 是一个占位符,表示将寄存器或内存地址替换为 ptr 的值。
  5. "p" (ptr) 告诉编译器将 ptr 作为一个指针操作数传递给汇编代码。编译器会根据需要将其转换为适当的格式。
c 复制代码
/*
 * Prefetching support - only ARMv5.
 */
#if __LINUX_ARM_ARCH__ >= 5

#define ARCH_HAS_PREFETCH
static inline void prefetch(const void *ptr)
{
	__asm__ __volatile__(
		"pld\t%a0"
		:: "p" (ptr));
}

#if __LINUX_ARM_ARCH__ >= 7 && defined(CONFIG_SMP)
#define ARCH_HAS_PREFETCHW
static inline void prefetchw(const void *ptr)
{
	__asm__ __volatile__(
		".arch_extension	mp\n"
		__ALT_SMP_ASM(
			"pldw\t%a0",
			"pld\t%a0"
		)
		:: "p" (ptr));
}
#endif
#endif
  • 为什么只有SMP才能支持这个函数prefetchw
  • 因为在多处理器系统中,缓存一致性是一个重要问题。pldw 指令不仅预取数据,还可以帮助处理器在写入数据时更高效地处理缓存一致性问题。
    如果系统是单处理器(非 SMP),则没有必要使用 pldw 指令,因为缓存一致性问题在单核环境中并不存在。因此,在非 SMP 环境中,prefetchw 函数会降级为普通的 pld 指令(只读预取)。

prefetchw 预取 "x" 处的 cacheline 进行写入

c 复制代码
#ifndef ARCH_HAS_PREFETCHW
#define prefetchw(x) __builtin_prefetch(x,1) //使用编译器的预取指令
#endif

include/linux/poison.h: 为调试目的,定义用于填充已释放内存的"毒药"值

  • 安全性目的:
    • 通过将毒指针指向不可映射的内存区域,可以防止用户空间的恶意代码利用这些指针进行攻击。
    • 这种设计增强了系统的安全性,特别是在防止用户空间漏洞方面。
c 复制代码
/*
 * Architectures might want to move the poison pointer offset
 * into some well-recognized area such as 0xdead000000000000,
 * that is also not mappable by user-space exploits:
 */
#ifdef CONFIG_ILLEGAL_POINTER_VALUE
# define POISON_POINTER_DELTA _AC(CONFIG_ILLEGAL_POINTER_VALUE, UL)
#else
# define POISON_POINTER_DELTA 0
#endif

/*
 * These are non-NULL pointers that will result in page faults
 * under normal circumstances, used to verify that nobody uses
 * non-initialized list entries.
 */
#define LIST_POISON1  ((void *) 0x100 + POISON_POINTER_DELTA)
#define LIST_POISON2  ((void *) 0x122 + POISON_POINTER_DELTA)

/********** include/linux/timer.h **********/
#define TIMER_ENTRY_STATIC	((void *) 0x300 + POISON_POINTER_DELTA)

/********** mm/page_poison.c **********/
#define PAGE_POISON 0xaa

/********** mm/page_alloc.c ************/

#define TAIL_MAPPING	((void *) 0x400 + POISON_POINTER_DELTA)

/********** mm/slab.c **********/
/*
 * Magic nums for obj red zoning.
 * Placed in the first word before and the first word after an obj.
 */
#define SLUB_RED_INACTIVE	0xbb	/* when obj is inactive */
#define SLUB_RED_ACTIVE		0xcc	/* when obj is active */

/* ...and for poisoning */
#define	POISON_INUSE	0x5a	/* for use-uninitialised poisoning */
#define POISON_FREE	0x6b	/* for use-after-free poisoning */
#define	POISON_END	0xa5	/* end-byte of poisoning */

/********** arch/$ARCH/mm/init.c **********/
#define POISON_FREE_INITMEM	0xcc

/********** fs/jbd/journal.c **********/
#define JBD_POISON_FREE		0x5b
#define JBD2_POISON_FREE	0x5c

/********** drivers/base/dmapool.c **********/
#define	POOL_POISON_FREED	0xa7	/* !inuse */
#define	POOL_POISON_ALLOCATED	0xa9	/* !initted */

/********** drivers/atm/ **********/
#define ATM_POISON_FREE		0x12
#define ATM_POISON		0xdeadbeef

/********** kernel/mutexes **********/
#define MUTEX_DEBUG_INIT	0x11
#define MUTEX_DEBUG_FREE	0x22
#define MUTEX_POISON_WW_CTX	((void *) 0x500 + POISON_POINTER_DELTA)

/********** security/ **********/
#define KEY_DESTROY		0xbd

/********** net/core/page_pool.c **********/
#define PP_SIGNATURE		(0x40 + POISON_POINTER_DELTA)

/********** net/core/skbuff.c **********/
#define SKB_LIST_POISON_NEXT	((void *)(0x800 + POISON_POINTER_DELTA))
/********** net/ **********/
#define NET_PTR_POISON		((void *)(0x801 + POISON_POINTER_DELTA))

/********** kernel/bpf/ **********/
#define BPF_PTR_POISON ((void *)(0xeB9FUL + POISON_POINTER_DELTA))

/********** VFS **********/
#define VFS_PTR_POISON ((void *)(0xF5 + POISON_POINTER_DELTA))

/********** lib/stackdepot.c **********/
#define STACK_DEPOT_POISON ((void *)(0xD390 + POISON_POINTER_DELTA))

include/linux/uaccess.h: (内存标记) 提供内核空间与用户空间之间安全拷贝数据的核心函数,如 copy_to_user()

user_access_save user_access_restore

  • user_access_saveuser_access_restore 是两个宏,用于在内核代码中处理用户空间访问的上下文保存和恢复。这些宏通常用于在内核中进行用户空间访问时,确保正确的上下文切换和权限检查。仅有特定的架构支持这些宏。
  • user_access_save 用于保存当前的用户空间访问状态,以便在稍后恢复。它通常在内核代码中用于保护对用户空间内存的访问。
  • user_access_restore 用于恢复之前保存的用户空间访问状态。这通常在完成对用户空间内存的访问后调用,以确保内核能够正确地恢复到之前的状态。
c 复制代码
static inline unsigned long user_access_save(void) { return 0UL; }
static inline void user_access_restore(unsigned long flags) { }

include/linux/mmdebug.h: 提供内存管理子系统的调试宏和断言

c 复制代码
#ifdef CONFIG_DEBUG_VM
#define VM_BUG_ON(cond) BUG_ON(cond)
#else
#define VM_BUG_ON(cond) BUILD_BUG_ON_INVALID(cond)
#endif /* CONFIG_DEBUG_VM */
相关推荐
超级大只老咪7 小时前
快速进制转换
笔记·算法
嵩山小老虎8 小时前
Windows 10/11 安装 WSL2 并配置 VSCode 开发环境(C 语言 / Linux API 适用)
linux·windows·vscode
Fleshy数模8 小时前
CentOS7 安装配置 MySQL5.7 完整教程(本地虚拟机学习版)
linux·mysql·centos
a41324478 小时前
ubuntu 25 安装vllm
linux·服务器·ubuntu·vllm
Fᴏʀ ʏ꯭ᴏ꯭ᴜ꯭.9 小时前
Keepalived VIP迁移邮件告警配置指南
运维·服务器·笔记
一只自律的鸡10 小时前
【Linux驱动】bug处理 ens33找不到IP
linux·运维·bug
17(无规则自律)10 小时前
【CSAPP 读书笔记】第二章:信息的表示和处理
linux·嵌入式硬件·考研·高考
!chen10 小时前
linux服务器静默安装Oracle26ai
linux·运维·服务器
ling___xi10 小时前
《计算机网络》计网3小时期末速成课各版本教程都可用谢稀仁湖科大版都可用_哔哩哔哩_bilibili(笔记)
网络·笔记·计算机网络