每CPU变量

最好的同步技术是把设计不需要同步的临界资源放在首位,这是一种思维方法,因为每一种显式的同步原语都有不容忽视的性能开销。

最简单也是最重要的同步技术包括把内核变量或数据结构声明为每CPU变量(per-cpu variable)。每CPU变量主要是数据结构的数组,系统的每个CPU对应数组的一个元素。

一个CPU不应该访问与其他CPU对应的数组元素,另外,它可以随意读或修改它自己的元素而不用担心出现竞争条件,因为它是唯一有资格这么做的CPU。但是,这也意味着每CPU变量基本上只能在特殊情况下使用,也就是当它确定在系统的CPU上的数据在逻辑上是独立的时候。

每CPU的数组元素在主存中被排列以使每个数据结构存放在硬件高速缓存的不同行,因此,对每CPU数组的并发访问不会导致高速缓存行的窃用和失效(这种操作会带来昂贵的系统开销)。

虽然每CPU变量为来自不同CPU的并发访问提供保护,但对来自异步函数(中断处理程序和可延迟函数)的访问不提供保护,在这种情况下需要另外的同步技术。

此外,在单处理器和多处理器系统中,内核抢占都可能使每CPU变量产生竞争条件。总的原则是内核控制路径应该在禁用抢占的情况下访问每CPU变量。因为当一个内核控制路径获得了它的每CPU变量本地副本的地址,然后它因被抢占而转移到另外一个CPU上,但仍然引用原来CPU元素的地址,这是非常危险的。

本博,我们介绍一些每CPU方面有用的宏。

1 DEFINE_PER_CPU(type, name)


静态分配一个每CPU数组,数组名为name,结构类型为type:
#define DECLARE_PER_CPU(type, name) extern __typeof__(type) per_cpu__##name

2 per_cpu(name, cpu)


获得一个为用DEFINE_PER_CPU宏为CPU选择的一个每CPU数组元素,CPU由cpu指定,数组名称为name:
#define per_cpu(var, cpu)     (*((void)(cpu), &per_cpu__##var))

3 __get_cpu_var(name)


选择每CPU数组name的本地CPU元素:
#define __get_cpu_var(var)            per_cpu__##var

4 get_cpu_var(name)


先禁用内核抢占,然后在每CPU数组name中,为本地CPU选择元素:
#define get_cpu_var(var) (*({ preempt_disable(); &__get_cpu_var(var); }))

5 put_cpu_var(name)


启用内核抢占(不使用name):
#define put_cpu_var(var) preempt_enable()

6 alloc_percpu(type)


动态分配type类型数据结构的每CPU数组,并返回它的地址:
#define alloc_percpu(type)    ((type *)(__alloc_percpu(sizeof(type))))
static inline void *__alloc_percpu(size_t size)
{
    void *ret = kmalloc(size, GFP_KERNEL);
    if (ret)
        memset(ret, 0, size);
    return ret;
}

7 free_percpu(pointer)


释放动态分配的每CPU数组,pointer指示其地址:
static inline void free_percpu(const void *ptr)
{   
    kfree(ptr);
}

8 per_cpu_ptr(pointer, cpu)


返回每CPU数组中与cpu对应CPU元素地址,pointer给出数组地址:
#define per_cpu_ptr(ptr, cpu) ({ (void)(cpu); (ptr); })

在Linux内核中,`errno` 是用于表示系统调用或库函数执行失败时的错误代码。虽然 `errno` 本身是一个全局变量,但在多处理器(SMP)系统中,为了提高性能和减少锁竞争,某些与 CPU 相关的状态信息会被存储为 **每CPU变量**(per-CPU variables)。然而,`errno` 并不是一个 per-CPU 变量。 Linux 内核通过 `<asm/errno.h>` 和 `<linux/err.h>` 等头文件定义了标准的错误码,并确保它们可以在不同架构下统一使用。每个错误码都有一个宏定义,例如: - `EAGAIN` (Resource temporarily unavailable) - `ENOMEM` (Cannot allocate memory) - `EFAULT` (Bad address) - `EINVAL` (Invalid argument) 这些错误码是全局共享的整数值,并不绑定到特定的 CPU 上[^1]。因此,在 Linux 内核中并不存在所谓的 "per-cpu errno definitions" 的概念。 尽管如此,Linux 内核确实存在一些 per-CPU 变量用于调度、中断处理等底层机制,比如 `this_rq()` 获取当前 CPU 的运行队列,或者 `nr_running` 和 `nr_uninterruptible` 被用于计算系统负载(load average)中的活跃进程数[^1]。但这些与 `errno` 没有直接关系。 ### 错误码的获取与设置 在用户空间程序中,`errno` 是线程局部变量(Thread-local storage),这意味着每个线程拥有自己的 `errno` 值。而在内核空间中,错误码通常通过函数返回值传递给用户空间,例如系统调用返回 `-EFAULT` 来表示地址无效错误。 例如,在内核模块中,可以通过如下方式返回错误码: ```c #include <linux/errno.h> static ssize_t my_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { if (!buf || !count) return -EFAULT; // 返回错误码 ... } ``` 上述方式将错误码以负值形式返回,用户空间可通过 `strerror(errno)` 或 `perror()` 函数将其转换为可读字符串。 ### 小结 Linux 内核中并没有专门针对每个 CPU 定义不同的 `errno` 值。所有错误码都是全局定义的标准常量,用于描述系统调用或内核操作的失败原因。虽然内核广泛使用 per-CPU 变量来优化性能,如 `nr_running` 和 `nr_uninterruptible` 用于系统负载计算,但这与 `errno` 的定义和使用机制无关。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值