精华内容
下载资源
问答
  • 1.4 x86 CPU地址空间分配和寄存器访问

    千次阅读 2017-10-12 11:05:15
    1、基本概念cpu地址空间和pci地址空间是两个常用的比较容易混淆的概念,特别是其中不同系列的cpu的实现还各不相同:x86系列cpu地址空间和pci地址空间是重合的,即为同一空间;而非x86 cpu的cpu地址空间和pci地址空间...

    1、基本概念

    cpu地址空间和pci地址空间是两个常用的比较容易混淆的概念,特别是其中不同系列的cpu的实现还各不相同:x86系列cpu地址空间和pci地址空间是重合的,即为同一空间;而非x86 cpu的cpu地址空间和pci地址空间为两个独立的空间。
    也许是因为pci总线是intel发明的,所以x86内部总线和pci总线实现是一致的,但非x86系列的cpu实现pci总线需要用到总线转换。

    1.1、非x86系列的CPU空间和PCI空间互访(以PowerPC为例):

    这里写图片描述
    这里写图片描述

    因为PCI地址空间和CPU地址空间是独立的,所以PCI设备和CPU之间的互访需要用到地址转换。涉及到两个概念,inbound和outbound。
    Inbound窗口:即将CPU的一段地址空间映射到PCI地址空间上,供PCI设备访问CPU空间所用,即inbound窗口。因为对cpu来说pci访问就是inbound访问。
    Outbound窗口:即将PCI的一段地址空间映射到CPU地址空间上,供CPU访问PCI空间所用,即Outbound窗口。因为对cpu来说访问pci空间就是outbound访问。

    1.2、x86系列的CPU空间和PCI空间互访:

    这里写图片描述

    对x86 cpu来说,就没有了cpu地址空间和pci地址空间之分,这两个空间是重合的。所以pci外设和cpu之间的相互访问就不需要设置地址转换窗口:
    Inbound访问:pci设备访问cpu空间,这个不需要设置inbound窗口,对外设来说所有的地址空间都是可以访问的。
    Outbound访问:cpu访问pci外设,这个也还需要保留一段cpu地址给外设,让外设的窗口能映射到这段空间来,即MMIO空间。有点类似于Outbound窗口,但是不需要地址转换。

    1.3、x86系列的IO空间和CFG空间:

    这里写图片描述

    由于x86 cpu对pci标准的完美支持,所以x86 cpu还支持pci的io空间访问和pci的配置空间访问:
    io空间:pci标准规定的io地址空间最大可以有4G,但是x86只实现了64k的大小。io空间用来实现早期兼容的外设寄存器的访问(IO端口),和用来映射pci外设的io空间。
    配置空间:用来访问pci总线设备的配置空间,而x86也把内部的寄存器组织成虚拟的pci设备,使用访问pci配置寄存器的方式来访问内部寄存器。
    访问配置空间的方法有两种:一是通过CF8/CFC io端口的间接访问来访问配置空间;二是通过mmcfg方式,把配置空间映射到memory空间来访问。对每个配置空间来说,CF8/CFC方式只能访问传统的pci配置空间256字节,而mmcfg访问方式,能访问pcie的整个配置空间4k。

    2、x86 CPU地址空间分配

    本节以系统的桥片为例,来说明x86系列cpu的地址空间分配。x86 其他系列cpu的地址空间分配类似。
    x86 xeon系列cpu 在32位系统下面,通过PAE(Physical address Extension)机制可以访问到36位的地址,即最大64G的空间。

    这里写图片描述

    2.1、0-1M 兼容空间:

    0-FFFFF 0-640k常规内存(MS-DOS Area) 这一段区域就是ram。 其中有功能划分的区域是:起始位置的1 KB被用做BIOS中断向量表,随后的1 KB被用做BIOS数据区
    A0000-BFFFF 640 – 768 kB Video Buffer Area 1、这一段区域是显卡的显示RAM区域,老式的VGA显示模式直接往这段显存写数据,就可以显示。现在估计只有bios阶段使用这种显示方式,系统起来后会开启更高级的显卡显示模式。 2、被显存地址覆盖的这一块128K大小的内存,可以被利用起来当做SMM内存。SMM是CPU一种等级最高的管理模式,所以它的内存在常规下不可以被访问。 1、PCI在支持VGA显示时,有个VGAEN功能,比较特殊,值得关注。VGA显卡设备不需要配置常规的pci bar寄存器地址,而只需要使能显卡所挂在PCI-PCI桥设备的配置寄存器0x3E bit 3(VGA Enable),显卡就会响应专为VGA保留的固定pci memory地址(A0000-BFFFF)和pci io地址(03c0-03df)。 2、什么是SMM模式? SMM是System Management Mode系统管理模式的缩写。从Intel 386SL开始,此后的x86架构微处理器中都开始支持这个模式。在这个模式中,所有正常执行的软件,包括操作系统都已经暂停运行。只有特别的单独软件,具备高特权模式的软件才能运行。通常这些软件都是一些固件程序或者是硬件辅助调试器。 x86 处理器的模式Mode模式 起始支持的处理器 Real mode Intel 8086 Protected mode Intel 80286 Virtual 8086 mode Intel 80386 Unreal mode Intel 80386 System Management Mode Intel 386SL Long mode AMD Opteron
    C0000-CFFFF 768 - 832 kB VGA Video BIOS ROM IDE Hard Disk BIOS ROM Optional Adapter ROM BIOS or RAM UMBs 1、这一段区域存放显卡的Option Rom还有其他设备的OptionRom(如硬盘、网卡..)。 这一段区域,是OptionRom和BIOS区域覆盖了原RAM区域。由于RAM的访问速度远远快于这些固件的访问速度,所以通常的做法是把固件中的内容拷贝到相同地址的RAM中,然后再使能RAM而屏蔽原有的固件映射。 访问BIOS和OptionRom内容和地址都没有改变,但是速度却加快了。这种做法就叫ROM Shadowing
    D0000-DFFFF 832 - 896 kB Optional Adapter ROM BIOS or RAM UMBs 这一段区域也是来存放设备的OptionRom。如果没有OptionRom覆盖,那就是常规内存
    E0000-EFFFF 896 - 960 kB System BIOS Plug and Play Extended Information 扩展BIOS区域。
    F0000-FFFFF 960 kB–1 MB System BIOS ROM 常规BIOS区域,映射到BIOS芯片。CPU的第一句指令0xFFFF0就跳到该区域

    2.2、1M以上的memory地址空间:

               
    1M-TOLM 低于4G的常规内存 这一段的内存就是低于4G的可用的内存,其中有两段区域比较特殊。 1、15 MB - 16 MB Window (ISA Hole)传统的ISA黑洞,现在基本不支持。 2、Extended SMRAM Space (TSEG)扩展SMM内存。前面已经有VGA RAM覆盖的128k内存可做SMM内存使用,系统还允许分配更多的SMM内存。 “Coherency Protocol”Intel 桥片通过同步协议保证所有对内存访问的一致性。
    HECBASE-(HECBASE+256M) MMCFG(Memory Mapped Configuration) 映射到memory空间的pci配置寄存器 256M的计算方法: 256bus x 32device x 8function x 4k bytes register = 256M bytes mmcfg、mmio这两段区域覆盖的相同地址的常规内存比较大,怎么样能使用到这段被覆盖的内存? 芯片组和内存控制其有一种叫做Main Memory Reclaim AddressRange。通过这个技术可将重叠得部分的内存地址印射到4g以上的地址上去,这个功能是由硬件决定的。
    (HECBASE+256M) - (4G-32M) MMIO(Memory Mapped I/O) 可分配给外设使用的pci memory空间 为什么要在4G以下设置Low MMIO区域,造成内存被分割成两块,为什么不能把MMIO都放在4G以上?主要还是为了照顾pci 32/64bit的兼容,32bit的pci地址需要放在4G以下的空间。
    (4G-32M) - 4G CPU-spec 4G以下的32M区域是CPU的一些特殊区域,主要由以下几部分组成: 1、16M fireware地址; 2、一些直接访问的cpu寄存器; 3、Interrupt、I/O APIC区域;
    4G - ram_size 高于4G的常规内存 Coherency Protocol”Intel 桥片通过同步协议保证所有对内存访问的一致性。
    ram_size - high MMIO 在高端内存之上一直到CPU支持的最大空间,都可以用来映射64bit的pci memory外设地址

    2.3、x86 io地址空间:

    这里写图片描述

    x86只实现64k大小的io空间,其中低4k是兼容的io空间用做专门用途,4k以上的io地址空间可以分配给外部设备使用。

    2.4、命令操作:

    在linux下通过以下命令查看cpu的地址空间分配:

    • cat /proc/iomem 查看cpu的pci memory空间分配
    • cat /proc/ioports 查看cpu的pci io空间分配

    3、x86内部寄存器的访问

    除了一些是需要直接在memory空间访问的寄存器。x86把大部分内部的寄存器组织成虚拟的pci设备,使用访问pci配置寄存器的方式来访问内部寄存器。

    访问配置空间的方法有两种:

    • 一是通过CF8/CFC io端口的间接访问来访问配置空间;
    • 二是通过mmcfg方式,把配置空间映射到memory空间来访问。对每个配置空间来说,CF8/CFC方式只能访问传统的pci配置空间256字节,而mmcfg访问方式,能访问pcie的整个配置空间4k。

    linux内核态实现pci配置空间访问的函数有以下:

    static inline int pci_read_config_byte(struct pci_dev *dev, int where, u8 *val)
    {
        return pci_bus_read_config_byte (dev->bus, dev->devfn, where, val);
    }
    static inline int pci_read_config_word(struct pci_dev *dev, int where, u16 *val)
    {
        return pci_bus_read_config_word (dev->bus, dev->devfn, where, val);
    }
    static inline int pci_read_config_dword(struct pci_dev *dev, int where, u32 *val)
    {
        return pci_bus_read_config_dword (dev->bus, dev->devfn, where, val);
    }
    static inline int pci_write_config_byte(struct pci_dev *dev, int where, u8 val)
    {
        return pci_bus_write_config_byte (dev->bus, dev->devfn, where, val);
    }
    static inline int pci_write_config_word(struct pci_dev *dev, int where, u16 val)
    {
        return pci_bus_write_config_word (dev->bus, dev->devfn, where, val);
    }
    static inline int pci_write_config_dword(struct pci_dev *dev, int where, u32 val)
    {
        return pci_bus_write_config_dword (dev->bus, dev->devfn, where, val);
    }

    这些函数的内部实现,会判断cpu是否进行了mmcfg映射。如果已经进行了mmcfg映射,则使用mmcfg模式访问;如果没有实现mmcfg,则使用CF8/CFC方式访问。
    具体的实现过程如下:

    • 1、这些函数的定义在driver/pci/access.c,可以看到其中的关键是bus->ops指针:
    #define PCI_OP_READ(size,type,len) \
    int pci_bus_read_config_##size \
        (struct pci_bus *bus, unsigned int devfn, int pos, type *value) \
    {                                   \
        int res;                            \
        unsigned long flags;                        \
        u32 data = 0;                           \
        if (PCI_##size##_BAD) return PCIBIOS_BAD_REGISTER_NUMBER;   \
        spin_lock_irqsave(&pci_lock, flags);                \
        res = bus->ops->read(bus, devfn, pos, len, &data);      \
        *value = (type)data;                        \
        spin_unlock_irqrestore(&pci_lock, flags);           \
        return res;                         \
    }
    
    #define PCI_OP_WRITE(size,type,len) \
    int pci_bus_write_config_##size \
        (struct pci_bus *bus, unsigned int devfn, int pos, type value)  \
    {                                   \
        int res;                            \
        unsigned long flags;                        \
        if (PCI_##size##_BAD) return PCIBIOS_BAD_REGISTER_NUMBER;   \
        spin_lock_irqsave(&pci_lock, flags);                \
        res = bus->ops->write(bus, devfn, pos, len, value);     \
        spin_unlock_irqrestore(&pci_lock, flags);           \
        return res;                         \
    }
    
    PCI_OP_READ(byte, u8, 1)
    PCI_OP_READ(word, u16, 2)
    PCI_OP_READ(dword, u32, 4)
    PCI_OP_WRITE(byte, u8, 1)
    PCI_OP_WRITE(word, u16, 2)
    PCI_OP_WRITE(dword, u32, 4)
    • 2、bus->ops的实现由arch/i386/pci/common.c中的pci_root_ops结构提供,pci_root_ops结构实际调用的是raw_pci_ops结构:
    static int pci_read(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 *value)
    {
        return raw_pci_ops->read(0, bus->number, devfn, where, size, value);
    }
    
    static int pci_write(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 value)
    {
        return raw_pci_ops->write(0, bus->number, devfn, where, size, value);
    }
    
    struct pci_ops pci_root_ops = {
        .read = pci_read,
        .write = pci_write,
    };
    • 3、raw_pci_ops的mmcfg模式实现在arch/i386/pci/mmconfig.c中定义:
    static int pci_mmcfg_read(unsigned int seg, unsigned int bus,
                  unsigned int devfn, int reg, int len, u32 *value)
    {
        unsigned long flags;
        u32 base;
    
        if (!value || (bus > 255) || (devfn > 255) || (reg > 4095))
            return -EINVAL;
    
        base = get_base_addr(seg, bus, devfn);
        if (!base)
            return pci_conf1_read(seg,bus,devfn,reg,len,value);
    
        spin_lock_irqsave(&pci_config_lock, flags);
    
        pci_exp_set_dev_base(base, bus, devfn);
    
        switch (len) {
        case 1:
            *value = readb(mmcfg_virt_addr + reg);
            break;
        case 2:
            *value = readw(mmcfg_virt_addr + reg);
            break;
        case 4:
            *value = readl(mmcfg_virt_addr + reg);
            break;
        }
    
        spin_unlock_irqrestore(&pci_config_lock, flags);
    
        return 0;
    }
    
    static int pci_mmcfg_write(unsigned int seg, unsigned int bus,
                   unsigned int devfn, int reg, int len, u32 value)
    {
        unsigned long flags;
        u32 base;
    
        if ((bus > 255) || (devfn > 255) || (reg > 4095)) 
            return -EINVAL;
    
        base = get_base_addr(seg, bus, devfn);
        if (!base)
            return pci_conf1_write(seg,bus,devfn,reg,len,value);
    
        spin_lock_irqsave(&pci_config_lock, flags);
    
        pci_exp_set_dev_base(base, bus, devfn);
    
        switch (len) {
        case 1:
            writeb(value, mmcfg_virt_addr + reg);
            break;
        case 2:
            writew(value, mmcfg_virt_addr + reg);
            break;
        case 4:
            writel(value, mmcfg_virt_addr + reg);
            break;
        }
    
        spin_unlock_irqrestore(&pci_config_lock, flags);
    
        return 0;
    }
    • 4、raw_pci_ops的CF8/CFC方式访问模式实现在arch/i386/pci/mmconfig.c中定义:
    int pci_conf1_read(unsigned int seg, unsigned int bus,
                  unsigned int devfn, int reg, int len, u32 *value)
    {
        unsigned long flags;
    
        if (!value || (bus > 255) || (devfn > 255) || (reg > 255))
            return -EINVAL;
    
        spin_lock_irqsave(&pci_config_lock, flags);
    
        outl(PCI_CONF1_ADDRESS(bus, devfn, reg), 0xCF8);
    
        switch (len) {
        case 1:
            *value = inb(0xCFC + (reg & 3));
            break;
        case 2:
            *value = inw(0xCFC + (reg & 2));
            break;
        case 4:
            *value = inl(0xCFC);
            break;
        }
    
        spin_unlock_irqrestore(&pci_config_lock, flags);
    
        return 0;
    }
    
    int pci_conf1_write(unsigned int seg, unsigned int bus,
                   unsigned int devfn, int reg, int len, u32 value)
    {
        unsigned long flags;
    
        if ((bus > 255) || (devfn > 255) || (reg > 255)) 
            return -EINVAL;
    
        spin_lock_irqsave(&pci_config_lock, flags);
    
        outl(PCI_CONF1_ADDRESS(bus, devfn, reg), 0xCF8);
    
        switch (len) {
        case 1:
            outb((u8)value, 0xCFC + (reg & 3));
            break;
        case 2:
            outw((u16)value, 0xCFC + (reg & 2));
            break;
        case 4:
            outl((u32)value, 0xCFC);
            break;
        }
    
        spin_unlock_irqrestore(&pci_config_lock, flags);
    
        return 0;
    }
    展开全文
  • X86的IO地址空间

    2008-12-05 17:15:21
    详细介绍了X86结构的IO地址空间分配!!! X86的IO地址空间
  • x86处理器系统中,内存地址空间是CPU地址空间的一部分。但是在32位x86系统中,并不是所有的内存地址空间都能被系统使用,如下图: 如图所示,左侧0~4G为CPU所能寻址的地址空间,红框内的空间是能被CPU识别的内存...

    1.CPU地址空间
    CPU地址空间是指CPU所能寻址的空间大小,比如对于32位CPU来说,其所能寻址的空间大小为0~4G。这是由CPU自身的地址总线数目决定的。这段空间也被称作CPU物理地址空间。

    2.内存地址空间
    内存地址空间就是指内存控制器所能寻址的空间大小。在x86处理器系统中,内存地址空间是CPU地址空间的一部分。但是在32位x86系统中,并不是所有的内存地址空间都能被系统使用,如下图:
    在这里插入图片描述

    如图所示,左侧0~4G为CPU所能寻址的地址空间,红框内的空间是能被CPU识别的内存地址空间。右侧为内存控制器所能寻址的空间大小为4GB,但是只有一部分空间能被CPU所识别。这就是为什么4GB内存在32位x86系统只能被识别为3G多的原因,因为剩下的CPU地址空间被其他设备占用了,比如PCI设备、Local APIC或者IO APIC等,这些CPU地址空间对应的是这些设备的寄存器区域。

    3.设备的内存映射空间
    注意,这里的设备内存映射空间是指硬件机制上实现的设备寄存器访问方式映射,而不是指MMU中虚拟地址到物理地址的映射。在上图中,我们可以得知,CPU通过CPU地址空间中的内存地址空间能访问内存设备,大概的访问流程如下:
    在这里插入图片描述
    CPU发送相关信号给北桥,告知其要访问内存,然后北桥通过内存控制器来访问内存设备。在汇编指令级别,这是通过MOV指令实现的,比如MOV eax,[mem addr],就能触发北桥通过内存控制器访问内存。这是CPU访问内存设备的情况,那么CPU如果要访问别的设备呢,比如要访问PCI设备,这是如何实现的?
    从第2小节的图中可以看出,在CPU地址空间中有一段空间叫做PCI Memory Range,这段空间也叫做PCI设备寄存器映射区。CPU通过访问这段空间,从而达到访问PCI设备的目的访问,意思就是CPU能通过像访问内存那样的方式来访问这些设备。注意,CPU访问CPU地址空间上的设备都是通过MOV指令来实现的。CPU访问PCI设备的大概流程如下:
    在这里插入图片描述

    CPU发送相关信号给北桥,告知其要访问PCI设备,然后北桥通过PCI控制器来访问PCI设备中的寄存器。

    4.端口地址空间
    端口地址访问是x86处理器系统中另外一种访问设备的方式,不同于CPU地址空间,端口地址空间只有64KB大小,为0~65535,这是因为访问端口时必须将端口地址放入dx寄存器,dx寄存器是16位的,故端口地址空间只有64KB大小。如下图:
    在这里插入图片描述
    端口其实就是设备中的寄存器,只不过访问这些寄存器并不是通过MOV指令来访问,而是通过IN/OUT指令来访问。比如在北桥中有一个端口,其地址为0xCF8,访问这个端口流程如下:
    在这里插入图片描述
    CPU发送相关信号给北桥,告知其要访问0xCF8端口,然后北桥发现这个端口是属于自己的,就直接访问。

    5.x86处理器系统中设备驱动编写注意
    在PPC和ARM处理器系统中,外设的寄存器都是被映射到CPU地址空间上的,所以访问这些外设寄存器通过readl、writel等系统接口就可以,因为readl、writel这些系统接口最终都会被翻译为MOV指令。但是在x86处理器系统中,还存在着通过端口地址空间访问设备的情况,如果一个设备的寄存器是通过端口地址来访问的,那么就需要用in8、out8等这些系统接口,因为这些系统接口最终会被翻译为IN/OUT指令。所以在为x86处理器系统下的设备编写驱动时,一定要搞清楚设备的寄存器是通过CPU地址空间来访问的还是通过端口地址来访问的。比如显卡设备,有的寄存器是通过CPU地址空间来访问的,有的寄存器是通过端口地址来访问的,这时候访问这些不同方式的寄存器就需要使用不同的系统接口。

    最后感谢蛋哥的文章分项。

    展开全文
  • Windows虚拟地址空间分配问题

    千次阅读 2016-10-03 23:26:54
    虚拟地址空间,即从0x00000000~0X7FFFFFFF的2GB为用户空间,而高地址0x80000000~0xFFFFFFFF被分配给了系统内核。高地址空间2GB内存是提供系统内核使用的。在这高地址空间中安排了操作系统的系统代码和数据,用户...

    一般情况下,32位的机器上,地址空间从0x000000~0xFFFFFFFF,总大小为4GB。一般而言,虚拟地址空间分为两个区,即为用户空间和系统空间。虚拟地址低空间,即从0x00000000~0X7FFFFFFF的2GB为用户空间,而高地址0x80000000~0xFFFFFFFF被分配给了系统内核。高地址空间2GB内存是提供系统内核使用的。在这高地址空间中安排了操作系统的系统代码和数据,用户一般无法访问到这个地址空间。用户地址空间使用的是低地址2GB内存,其中包含了用户应用程序、调用的动态链接库、用户栈、用户可使用的空闲内存空间等。从整体上看,Windows虚拟地址空间分配如下表:

    虚拟内存地址范围功能描述
    0x00000000~0x0000FFFF这段内存是空指针区,同时肯定是不能访问的
    0x00010000~0x7FFFFFFF这段提供给进程使用,包括所有的数据,静态或动态加载的exe和dll模块,以及内存映射文件
    0x7FFF0000~0x7FFFFFFF此64KB的区域是禁止访问的,因为紧挨着它就是内核地址,如果中间密友这个阻拦的话,你可以用一个很长的数据信息进行覆盖操作,从而破坏内核的完整性和正确性—-这是不允许的
    0x80000000~0xFFFFFFFF内核区域,用于线程调度、内存管理、文件系统支持、网络支持、所有设备驱动的代码全部在这个分区加载。驻留在这个分区中的一切均可被所有进程共享

    用户态低2GB空间分配表

    虚拟内存地址范围功能
    0x0~0xFFFF拒绝访问区域,用于帮助程序员避免引用错误的指针,试图访问这个区域地址的操作将会导致访问越权
    0x10000~0x7EFDEFFF专用进程地址空间
    0x7EFDF000~0x7FFDFFFF进程环境快(PEB)
    0x7FFE0000~0x7FFE0FFF共享的用户数据页,这个只读方式的页面被映射到系统空间中包含系统时间、时钟计数和版本号信息的一个页面。这个页面的存在使数据在用户态下可以直接读取而不必请求核心态的转换。
    0x7FFE1000~0x7FFEFFFF拒绝访问区域(共享用户数据页面以后剩余的64KB)
    0x7FFF0000~0x7FFFFFFF拒绝访问区域,用于防止线程跨越用户/系统空间边界传送缓存区,在变量MmUserProbeAddress中包含此页的起始地址。

    核心态高2GB空间分配表

    虚拟内存地址范围功能说明
    0x80000000~0xC0000000内核执行体,HAL和硬件驱动程序
    0xC0000000~0xC0800000进程页表和超空间
    0xC0800000~0xCFFBE000系统高速缓存、分页缓冲池、非分页缓冲池
    0xFFBE0000~0xFFC00000崩溃转储驱动程序区域
    0xFFC00000~0xFFFFFFFF保留给HAL使用

    alphaOS
    alphaOS is Lightweight, portable and flexible operating system

    Description of alphaOS

    alphaOS is a simple and minimalistic Linux distribution for the x86-64 architecture, built using Linux Live Kit set of scripts developed by Tomáš Matějíček, the developer of the popular Slax Linux distribution. But, unlike Slax, alphaOS is based on Arch Linux and uses pacman as the default package manager. This operating system features highly configurable and lightweight Openbox window manager. Modular by design, alphaOS makes it easy to add desired functionality.


    Intel x86架构的cpu指令一共有0~4四个特权级,0级最高,3级最低,硬件上在执行每条指令时都会对指令所具有的特权级做相应的检查。硬件已经提供了一套特权级使用的相关机制,软件自然要好好利用,这属于操作系统要做的事情,对于UNIX/LINUX来说,只使用了0级特权级别和3级特权级。也就是说在UNIX/LINUX系统中,一条工作在0级特权级的指令具有了CPU能提供的最高权力,而一条工作在3级特权的指令具有CPU提供的最低或者说最基本权力。

    在Windows操作系统下面,操作系统运行于0级,而应用程序运行于3级。这是因为Alpha计算机只能够支持两个优先级的指令,windows为了能够便于将编写的应用程序移植到Alpha计算机上面运行。故,Windows操作系统不使用1级和2级这两个优先级。


    这里写图片描述


    用户态到和核心态的转换是由什么完成的?


    计算机通过硬件中断机制完成由用户态到核心态的转换。


    REFERENCE
    1. Linux 内核态与用户态
    2. linux用户态和内核态理解
    3. 内核态(Kernel Mode)与用户态(User Mode)
    4. 用户态到和核心态的转换是由什么完成的?
    5. Linux内核态和用户态的区别
    6. Ring3/Ring0的四种通信方式
    7. Windows进程间各种通信方式浅谈
    8. Windows XP 进程分类(必要,需要,不要)windows xp 必要进程
    9. alphaOS

    展开全文
  • 这里有一点不明白,假设预订地址空间,系统为了确保起始地址是64KB的整数倍,取地址空间为128KB处开始,然后按照4KB大小分配内存,这样虚拟地址128~132KB处为已预订空间,假如下一次再预订地址空间,系统再次按照...
  • Linux下x86_64进程地址空间布局

    千次阅读 2015-03-08 23:33:26
    关于Linux 32位内存下的内存空间布局,可以参考这篇博文Linux下C程序进程地址空间局关于源代码中各种数据类型/代码在elf格式文件以及进程空间中所处的段,在x86_64下和i386下是类似的,本文主要关注vm.legacy_va_...

    关于Linux 32位内存下的内存空间布局,可以参考这篇博文Linux下C程序进程地址空间局关于源代码中各种数据类型/代码在elf格式文件以及进程空间中所处的段,在x86_64下和i386下是类似的,本文主要关注vm.legacy_va_layout以及kernel.randomize_va_space参数影响下的进程空间内存宏观布局,以及vDSO和多线程下的堆和栈分布。


    情形一:

    • vm_legacy_va_layout=1
    • kernel.randomize_va_space=0
      此种情况下采用传统内存布局方式,不开启随机化
      cat 程序的内存布局

      可以看出:
      代码段:0x400000–>
      数据段
      堆:向上增长 2aaaaaaab000–>
      栈:7ffffffde000<–7ffffffff000
      系统调用:ffffffffff600000-ffffffffff601000
      你可以试一下其他程序,在kernel.randomize_va_space=0时堆起点是不变的

    情形二:

    • vm_legacy_va_layout=0
    • kernel.randomize_va_space=0
      现在默认内存布局,不随机化

      可以看出:
      代码段:0x400000–>
      数据段
      堆:向下增长 <–7ffff7fff000
      栈:7ffffffde000<–7ffffffff000
      系统调用:ffffffffff600000-ffffffffff601000

    情形三:

    • vm_legacy_va_layout=0
    • kernel.randomize_va_space=2 //ubuntu 14.04默认值
      使用现在默认布局,随机化


      对比两次启动的cat程序,其内存布局堆的起点是变化的,这从一定程度上防止了缓冲区溢出攻击。

    情形四:

    • vm_legacy_va_layout=1
    • kernel.randomize_va_space=2 //ubuntu 14.04默认值
      与情形三类似,不再赘述

    vDSO

    在前面谈了两个不同参数下的进程运行时内存空间宏观的分布。也许你会注意到这样一个细节,在每个进程的stack以上的地址中,有一段动态变化的映射地址段,比如下面这个进程,映射到vdso。

    cat

    如果我们用ldd看相应的程序,会发现vdso在磁盘上没有对应的so文件。
    不记得曾经在哪里看到大概这样一个问题:

    getpid,gettimeofday是不是系统调用?

    其实这个问题的答案就和vDSO有关,杂x86_64和i386上,getpid是系统调用,而gettimeofday不是。

    vDSO全称是virtual dynamic shared object,是一种内核将一些本身应该是系统调用的直接映射到用户空间,这样对于一些使用比较频繁的系统调用,直接在用户空间调用可以节省开销。如果想详细了解,可以参考这篇文档

    下面我们用一段程序验证下:

    #include <stdio.h>
    #include <sys/time.h>
    #include <sys/syscall.h>
    #include <unistd.h>
    
    int main(int argc, char **argv)
    {
        struct timeval tv;
        int ret;
        if ((ret=gettimeofday(&tv, NULL))<0) {
            fprintf(stderr, "gettimeofday call failed\n");
        }else{
            fprintf(stdout, "seconds:%ld\n", (long int)tv.tv_sec);
        }
    
        fprintf(stdout, "pid:%d\n", (int)getpid());
        fprintf(stdout, "thread id:%d\n", (int)syscall(SYS_gettid));
        return 0;
    }

    编译为可执行文件后,我们可以用strace来验证:

    strace -o temp ./vdso
    grep getpid temp
    grep gettimeofday temp

    多线程的堆栈

    • 三个线程的进程:
      这里写图片描述
    • 主线程:
      这里写图片描述
    • 子线程1:
      这里写图片描述
    • 子线程2:
      这里写图片描述

    • 测试代码1:

    #include <pthread.h>
    #include <unistd.h>
    #include <stdio.h>
    
    void *routine(void *args)
    {
      fprintf(stdout, "========\n");
      char arr[10000];
      fprintf(stdout, "temp var arr address in child thread : %p\n", arr);
      char arr1[10000];
      fprintf(stdout, "temp var arr1 address in child thread : %p\n", arr1);
    
      fprintf(stdout, "delta : %ld\n", arr1 - arr);
    
      for(;;) {
        sleep(5);
      }
    }
    
    int main(int argc, char *argv[])
    {
      // argc 4
      // argv ?
      pthread_t pt; // 4
      pthread_t pt1; // 4
      int ret;  // 4
      // pthread max stack size(can be changed): 0x800000 = 8M
      // char bigArr[0x800000 - 10000]; // SEGMENT FAULT
      //char arr1[144000];
      char arr1[144];
      arr1[0] = 'a';
      fprintf(stdout, "temp var arr1 address in main thread lower than 139 K : %p\n", arr1);
      //char arr2[100];
      char arr2[1];
      fprintf(stdout, "temp var arr2 address in main thread lower than 139 K : %p\n", arr2);
      fprintf(stdout, "delta : %ld\n", arr2 - arr1);
      //char arr3[100];
      char arr3[10];
      fprintf(stdout, "temp var arr3 address in main thread lower than 139 K : %p\n", arr3);
      fprintf(stdout, "delta : %ld\n", arr3 - arr2);
      ret = pthread_create(&pt, NULL, routine, NULL);
      ret = pthread_create(&pt1, NULL, routine, NULL);
      pthread_join(pt, NULL); 
      pthread_join(pt1, NULL); 
      return 0;
    }
    • 测试代码2:打印内核栈地址
    #include <linux/module.h>
    #include <linux/errno.h>
    #include <linux/sched.h>
    #include <asm/thread_info.h>
    
    static int test_param = 10;
    module_param(test_param, int, S_IRUGO | S_IWUSR);
    MODULE_PARM_DESC(test_param, "a test parameter");
    
    
    static int print_all_processes_init(void)
    {
      struct task_struct *p;
      for_each_process(p) {
        if (p->pid == 1) {
          printk(KERN_INFO "stack : %p\n", p->stack);
        }
      };
      return 0;
    }
    
    static void print_all_processes_exit(void)
    {
      printk(KERN_INFO "unload module print_all_processes\n");
    }
    
    module_init(print_all_processes_init);
    module_exit(print_all_processes_exit);
    
    MODULE_AUTHOR("FEILENGCUI");
    MODULE_LICENSE("GPL");
    MODULE_DESCRIPTION("A MODULE PRINT ALL PROCESSES");
    
    • 对应init进程的内核栈stack起始地址
      这里写图片描述

    • 用户态线程栈在同一进程空间的堆起始部分分配,x86_64默认是8M,可以通过ulimit等方法设置

    • 用户态线程栈的增长是从低的线性地址往高增长
    • 内核栈位于高地址
    • 主线程的栈(姑且称为进程栈吧)行为比较怪异,后面会详细分析glibc的ptmalloc下多线程程序malloc和线程栈的内存分配行为
    展开全文
  • PCIe扫盲——PCI总线的地址空间分配

    千次阅读 2019-06-28 13:32:13
    PCI总线具有32位数据/地址复用总线,所以其存储地址空间为2的32次方=4GB。也就是PCI上的所有设备共同映射到这4GB上,每个PCI设备占用唯一的一段PCI地址,以便于PCI总线统一寻址。每个PCI设备通过PCI寄存器中的基地址...
  • Linux虚拟地址布局x64 layout在x86_64下面,其实虚拟地址只使用了48位。所以C语言里,输出地址都是12位16进制的地址。48位地址长度也就是对应了256TB的地址空间。而在Linux下有效的地址区间是从0x0 ~ 0x00007FFF ...
  • PCI总线具有32位数据/地址复用总线,所以其存储地址空间为2的32次方=4GB。也就是PCI上的所有设备共同映射到这4GB上,每个PCI设备占用唯一的一段PCI地址,以便于PCI总线统一寻址。每个PCI设备通过PCI寄存器中的基地址...
  • x86 32bit Linux下,我们的系统会给每个进程分配一个4G大小的虚拟地址空间。你可能会说,我电脑总共就4g内存这每个进程都给4g怎么可能啊。注意虚拟两个字。那么这块空间里都装了什么东西呢? 其中前3G内存是...
  • 四、地址空间与内存分配

    千次阅读 2018-04-07 16:05:44
    物理地址空间和硬件直接对应,如内存条代表的主存,硬盘代表的磁盘 ,都是物理内存,其管理由硬件完成逻辑地址空间是运行的程序看到的地址空间,是一维的线性的地址空间。逻辑地址空间依赖物理地址空间而存在。2....
  • Linux x86_64/arm 分配大块物理连续内存

    千次阅读 2018-04-18 20:08:42
    Linux x86 分配大于4M空间的内存 实现一个PCIe设备驱动,需要分配几个128M的物理连续内存作为DM缓冲区,受制于伙伴系统最多分配4M,所以需要使用到CMA机制,来分配大块的物理连续内存: 硬件平台:x86_64 软件...
  • 原文网址:http://blog.nlogn.cn/why-does-x86_64-not-have-zone_highmem/ 如果你稍微了解过Linux内核的内存管理,那么对内存分区的概念一定不陌生,Linux内核把物理内存分成了3个区, 0 – 16M 为ZONE_DMA区, 16M ...
  • Windows(x86)下大内存的分配

    千次阅读 2011-03-02 11:36:00
     Windows(x86)下,由于进程的地址空间只有4G,而实际上可以使用的虚拟地址空间也就2G左右。如果有需求,需要分配较大的内存,比如需要分配16G的内存空间,以作特殊用途,如做大缓存,则可以使用AWE(地址窗口...
  • x86下 线性地址到物理地址映射图示

    千次阅读 2011-09-18 21:25:15
    1.进程线性地址空间:用户进程的页面优先从ZONE_HIGTMEM分配,但也可以从其他 2.直接映射区:最大896MB,动态映射区最小120MB 有下面几个用于获取页框并返回一个位于直接映射区的线性地址的函数(④返回p
  • PCIe的内存地址空间、I/O地址空间和配置地址空间

    千次阅读 多人点赞 2018-08-12 18:07:14
    PCIe的内存地址空间、I/O地址空间和配置地址空间 pci设备与其它接口的设备(如i2c设备)最大的不同是存在内存地址空间和配置地址空间,本文分析一下它们的用途。 首先区分一下IO空间和内存空间 cpu会访问的设备...
  • 本贴涉及的硬件平台是X86,如果是其它平台,不保证能一一对号入座,但是举一反三,我想是完全可行的。 一、概念 物理地址(physical address) 用于内存芯片级的单元寻址,与处理器和CPU连接的地址总线相对应。...
  • PCIe的内存地址空间、I/O地址空间和配置地址空间 pci设备与其它接口的设备(如i2c设备)最大的不同是存在内存地址空间和配置地址空间,本文分析一下它们的用途。 首先区分一下IO空间和内存空间 cpu会访问的设备一般...
  • x86虚拟地址到物理地址的映射学习

    千次阅读 2014-09-27 11:08:54
    另外,并不需要为不存在的或线性地址空间未使用部分分配二级页表。虽然目录表页面必须总是存在于物理内存中,但是二级页表可以在需要时再分配。这使得页表结构的大小对应于实际使用的线性地址空间大小。 页目录...
  • centos7系统安装与磁盘空间分配

    万次阅读 2018-09-18 23:04:47
    系统安装与磁盘空间分配 系统安装 安装系统 将U盘插入主机,在启动选项处选择你的U盘(具体方式每个品牌的主机都不同,请自行查阅相关资料)。出现如下图所示引导安装界面,选择第一项,Enter键进入安装,等待。 ...
  • 3-2地址空间地址生成 3-3连续内存分配:内存碎片与分区的动态分配 3-4 连续内存分配:压缩式/交换式碎片整理4-1 非连续内存分配:分段 页表-概述,TLB,二级页表,多级也比哦啊,反向页表
  • Linux虚拟地址空间

    千次阅读 2018-11-02 21:18:47
    这个沙盘就是虚拟地址空间(Virtual Address Space),在32位模式下它是一个4GB的内存地址块,这篇博客均是X86架构的 1. 地址空间分布 2. 内核地址空间 其中高1G为内核空间,所有进程共享内核地址空间,内核空间分三...
  • x86系统下的c语言内存分配解析

    千次阅读 2010-10-23 18:13:00
    注意:a的值在[ebp-8]所指向的空间,及在刚才分配空间中,因为栈是向小地址x86为小端处理器)延伸 0040107B push eax ;在调用者(这里是主函数)中用栈空间保存传递的参数,注意:传递参数的顺序  0040107C ...
  • 对于x86的pc机和单片机等嵌入式开发系统程序的存储是截然相反的,  即:  x86的pc机cpu在运行的时候程序是存储在RAM中的,而单片机等嵌入式系统则是存于flash中   x86cpu和单片机读取程序的具体途径  pc机在...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 62,185
精华内容 24,874
关键字:

x86地址空间分配