用户进程的有效地址空间是多少? (OS X 和 Linux)

发布于 2024-10-20 19:59:58 字数 237 浏览 1 评论 0原文

mmap 系统调用文档指出,如果出现以下情况,该函数将失败:

指定了 MAP_FIXED 并且地址 参数未对齐页面或部分 所需地址空间的驻留 超出了有效地址空间 用户进程。

我无法在任何地方找到说明什么是有效映射地址的文档。 (我有兴趣在 OS X 和 Linux 上执行此操作,理想情况下相同的地址对两者都有效......)。

The mmap system call documentation says that the function will fail if:

MAP_FIXED was specified and the addr
argument was not page aligned, or part
of the desired address space resides
out of the valid address space for a
user process.

I can't find documentation anywhere saying what would be a valid address to map. (I'm interested in doing this on OS X and linux, ideally the same address would be valid for both...).

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

酷到爆炸 2024-10-27 19:59:58

Linux 内核为自己保留了部分虚拟地址空间,用户空间(几乎)无法访问并且无法映射任何内容。您正在寻找所谓的“用户空间/内核空间分割”。

在 i386 架构上,默认值为 3G/1G 之一——用户空间获得较低的 3 GB 虚拟地址空间,内核获得较高的 1 GB,此外还有 2G/2G 和 1G/3G 分割:

config PAGE_OFFSET
        hex
        default 0xB0000000 if VMSPLIT_3G_OPT
        default 0x80000000 if VMSPLIT_2G
        default 0x78000000 if VMSPLIT_2G_OPT
        default 0x40000000 if VMSPLIT_1G
        default 0xC0000000
        depends on X86_32

在 x86_64 上,用户空间位于虚拟地址空间的下半部分(目前) 48位虚拟地址空间:

/*
 * User space process size. 47bits minus one guard page.
 */
#define TASK_SIZE_MAX   ((1UL << 47) - PAGE_SIZE)

Linux kernel reserves part of virtual address space for itself to where userspace have (almost) no access and can't map anything. You're looking for what's called "userspace/kernelspace split".

On i386 arch default is 3G/1G one -- userspace gets lower 3 GB of virtual address space, kernel gets upper 1 GB, additionally there are 2G/2G and 1G/3G splits:

config PAGE_OFFSET
        hex
        default 0xB0000000 if VMSPLIT_3G_OPT
        default 0x80000000 if VMSPLIT_2G
        default 0x78000000 if VMSPLIT_2G_OPT
        default 0x40000000 if VMSPLIT_1G
        default 0xC0000000
        depends on X86_32

On x86_64, userspace lives in lower half of (currently) 48-bit of virtual address space:

/*
 * User space process size. 47bits minus one guard page.
 */
#define TASK_SIZE_MAX   ((1UL << 47) - PAGE_SIZE)
菊凝晚露 2024-10-27 19:59:58

这取决于许多因素,其中许多因素不受您的控制。正如 adobriyan 提到的,根据操作系统的不同,您有各种固定的上限,超出该上限的内核代码和数据。通常,在 32 位操作系统上,此上限至少为 2GB;某些操作系统提供额外的地址空间。 64 位操作系统通常提供由 CPU 支持的虚拟地址位数控制的上限(通常至少有 40 位地址空间)。然而,还有其他因素是您无法控制的:

  • 在最新版本的 Linux 上,/proc/sys/vm/mmap_min_addr 中配置的地址下方的 mmap 映射将被拒绝。
  • 您不能创建与任何现有映射重叠的映射。由于动态链接器可以自由映射与可执行文件的固定部分不重叠的任何位置,这意味着任何地址都可能被拒绝。
  • 内核可能会注入其他附加映射,例如系统调用门
  • malloc 可以自行执行 mmap,这些映射被放置在任意位置

因此,无法绝对保证 MAP_FIXED 会成功,因此通常应该应避免。

我见过的唯一需要 MAP_FIXED 的地方是在 wine 启动代码中,它保留(使用 MAP_FIXED)所有 2G 以上的地址,以避免混淆 Windows 代码它假设任何映射都不会显示为负地址。当然,这是旗帜的高度专业化用途。

如果您尝试这样做是为了避免处理共享内存中的偏移量,一种选择是将指针包装在类中以自动处理偏移量:

template<typename T>
class offset_pointer {
    private:
       ptrdiff_t offset;
    public:
        typedef T value_type, *ptr_type;
        typedef const T const_type, *const_ptr_type;

        offset_ptr(T *p) { set(p); }
        offset_ptr() { set(NULL); }

        void set(T *p) {
            if (p == NULL)
                offset = 1;
            else
                offset = (char *)p - (char *)this;
        }
        T *get() {
            if (offset == 1) return NULL;
            return (T*)( (char *)this + offset );
        }

        const T *get() const { return const_cast<offset_pointer>(this)->get(); }

        T &operator*() { return *get(); }
        const T &operator*() const { return *get(); }
        T *operator->() { return get(); }
        const T *operator->() const { return get(); }

        operator T*() { return get(); }
        operator const T*() const { return get(); }

        offset_pointer operator=(T *p) { set(p); return *this; }
        offset_pointer operator=(const offset_pointer &other) {
            offset = other.offset;
            return *this;
        }
};

注意:这是未经测试的代码,但应该为您提供基本概念。

This varies based on a number of factors, many of which aren't under your control. As adobriyan mentioned, depending on the OS, you have various fixed upper limits, beyond which kernel code and data lies. Usually this upper limit is at least 2GB on 32-bit OSes; some OSes provide additional address space. 64-bit OSes generally provide an upper limit controlled by the number of virtual address bits supported by your CPU (usually at least 40 bits worth of address space). However there are yet other factors beyond your control:

  • On recent version of linux, mmap mappings below the address configured in /proc/sys/vm/mmap_min_addr will be denied.
  • You cannot make mappings which overlap with any existing mappings. Since the dynamic linker is free to map anywhere that does not overlap with your executable's fixed sections, this means potentially any address may be denied.
  • The kernel may inject other additional mappings, such as the system call gate.
  • malloc may perform mmaps on its own, which are placed in a somewhat arbitrary location

As such, there is no way to absolutely guarentee that MAP_FIXED will succeed, and so it should normally be avoided.

The only place I've seen where MAP_FIXED is necessary is in the wine startup code, which reserves (using MAP_FIXED) all addresses above 2G, in order to avoid confusing windows code which assumes no mappings will ever show up with a negative address. This is, of course, a highly specialized use of the flag.

If you're trying to do this in order to avoid having to deal with offsets in shared memory, one option would be to wrap pointers in a class to automatically handle offsets:

template<typename T>
class offset_pointer {
    private:
       ptrdiff_t offset;
    public:
        typedef T value_type, *ptr_type;
        typedef const T const_type, *const_ptr_type;

        offset_ptr(T *p) { set(p); }
        offset_ptr() { set(NULL); }

        void set(T *p) {
            if (p == NULL)
                offset = 1;
            else
                offset = (char *)p - (char *)this;
        }
        T *get() {
            if (offset == 1) return NULL;
            return (T*)( (char *)this + offset );
        }

        const T *get() const { return const_cast<offset_pointer>(this)->get(); }

        T &operator*() { return *get(); }
        const T &operator*() const { return *get(); }
        T *operator->() { return get(); }
        const T *operator->() const { return get(); }

        operator T*() { return get(); }
        operator const T*() const { return get(); }

        offset_pointer operator=(T *p) { set(p); return *this; }
        offset_pointer operator=(const offset_pointer &other) {
            offset = other.offset;
            return *this;
        }
};

Note: This is untested code, but should give you the basic idea.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文