getcpu 的最小可运行示例
为了让事情更具体,我们可以看看getcpu 系统调用,它非常易于理解,并且显示了相同的 EFAULT 行为。
从man getcpu我们看到签名是:
int getcpu(unsigned *cpu, unsigned *node, struct getcpu_cache *tcache);
cpu 指向的内存将包含系统调用后进程正在运行的当前 CPU 的 ID,唯一可能的错误是:
ERRORS
EFAULT Arguments point outside the calling process's address space.
所以我们可以测试一下:
main.c
#define _GNU_SOURCE
#include <assert.h>
#include <errno.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
int main(void) {
int err, ret;
unsigned cpu;
/* Correct operation. */
assert(syscall(SYS_getcpu, &cpu, NULL, NULL) == 0);
printf("%u\n", cpu);
/* Bad trash address == 1. */
ret = syscall(SYS_getcpu, 1, NULL, NULL);
err = errno;
assert(ret == -1);
printf("%d\n", err);
perror("getcpu");
return EXIT_SUCCESS;
}
编译运行:
gcc -ggdb3 -O0 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
./main.out
样本输出:
cpu 3
errno 14
getcpu: Bad address
所以我们看到垃圾地址为1 的错误调用返回了14,从内核代码中可以看出这是EFAULT:https://stackoverflow.com/a/53958705/895245
请记住,系统调用本身返回-14,然后syscall C 包装器检测到由于为负数而导致错误,返回-1,并将errno 设置为实际的精确错误代码。
由于系统调用非常简单,我们也可以从内核 5.4 实现中确认这一点,kernel/sys.c:
SYSCALL_DEFINE3(getcpu, unsigned __user *, cpup, unsigned __user *, nodep,
struct getcpu_cache __user *, unused)
{
int err = 0;
int cpu = raw_smp_processor_id();
if (cpup)
err |= put_user(cpu, cpup);
if (nodep)
err |= put_user(cpu_to_node(cpu), nodep);
return err ? -EFAULT : 0;
}
很清楚,如果put_user 有问题,我们会返回-EFAULT。
值得一提的是,我的 glibc 在 sched.h 中也有一个 getcpu 包装器,但是在地址错误的情况下,该实现会出现段错误,这有点令人困惑:How do I include Linux header files like linux/getcpu.h? 但实际情况并非如此syscall 对进程所做的事情,就像 glibc 对该地址所做的事情一样。
在 Ubuntu 20.04、Linux 5.4 上测试。