linux用户空间与内核空间
1. 深入User space(用户空间) 与 Kernel space(内核空间)
深入探索linux世界中的神秘双面:User space与Kernel space
在Linux的广阔舞台中,两种独特的运行空间——User space(用户程序的领地)与Kernel space(内核的核心地带)如同两股平行的力量,各自承担着至关重要的角色。Kernel space,作为系统的守护者,以隔离和保护的姿态,拥有执行所有命令和操控系统资源的特权。相比之下,User space受限于权限,它通过system call(系统调用)与内核进行微妙的沟通,确保数据安全与流畅的进程交互。
让我们通过实例来窥探这两大领域的互动。想象你正在运行的top命令,它如一面镜子,反映出CPU时间的分配:user部分代表User space的运行时间,sys部分则象征着Kernel space的职责。niceness、idle和wait等指标,就像财务报告中的细节,揭示着系统的运行状态。
time命令,如同时间侦探,记录下程序执行的点点滴滴。内核空间和用户空间的关系,可以比喻为银行和储户,内核扮演着守门人,管理权限,用户空间则像储户,按需获取服务。在普通IO操作中,进程将控制权暂时交给内核,内核负责处理,甚至预读取,再将数据安全地传递给用户空间的缓冲区,就像银行转账一样,需经过严格的验证和权限控制。
硬盘数据的处理同样遵循这个逻辑。内核负责处理非对齐的数据块,虚拟内存机制则提升了I/O效率,通过MMU(内存管理单元)实现多地址指向同一物理内存,扩展了存储空间。当内存访问出现问题时,内核作为幕后决策者,介入处理内存调入和可能的页面调出。
Linux的系统结构就像一座三重塔:硬件-内核空间-用户空间,进程在执行系统调用时,会从用户态转变为内核态,拥有0级特权,每个进程拥有独立的内核栈。而在用户态,进程执行用户代码,拥有3级特权,中断处理程序则依赖于进程的内核栈,体现了权限和控制的微妙平衡。
逻辑地址、线性地址和物理地址,构成了内存管理的三维世界。虚拟内存扩展了可用内存,逻辑地址,也就是虚拟地址,与物理地址之间存在着固定映射,就像银行账户和实际存款的关系。
在32位传统Linux系统中,物理地址与逻辑地址之间的差异体现在0xC0000000的偏移。内核模块巧妙地利用高端逻辑地址,映射到低端物理内存,解决大内存访问的问题。
内存映射与高端内存的交互,是Linux内核技术的精华。通过ZONE_DMA、ZONE_NORMAL和ZONE_HIGHMEM的划分,内核解决了超过1GB内存的访问难题。高端内存被用于临时大容量存储,使用完毕后会自动释放。mmap函数则像一座桥梁,连接文件和进程的地址空间,使得文件操作和进程间共享变得轻而易举。
vm_area_struct,这个内存管理的关键结构,就像内存地图,用链表或树的形式管理不同的区域。它包含了地址信息和系统调用函数指针,mmap函数就是通过它创建和管理虚拟映射区域。
当程序试图访问映射空间时,可能会触发缺页异常,此时内核会介入,通过页表加载缺失页面,必要时从磁盘获取数据。写操作可能延迟回写到文件,但可以通过msync()函数确保数据的一致性。
总结来说,User space与Kernel space的协作是Linux内存管理的核心,它们各自负责不同的职责,共同构建起系统的稳健运行。理解这两个空间的工作原理,就像理解银行和储户之间的交易规则,是深入掌握Linux系统的关键。
2. linux进程地址空间划分
Linux 64位系统在理论上拥有16位十六进制表示的巨大内存地址空间,即从0x0000000000000000到0xFFFFFFFFFFFFFFFF。然而,Linux仅实际使用了其中的256T空间,其余部分未被使用。
在Linux 64位操作中,实际使用的是低47位地址,高17位用于扩展,只能取全0或全1值。这样,可用的地址空间被分为两部分:用户空间(0x0000000000000000至0x00007FFFFFFFFFFF)和内核空间(0xFFFF800000000000至0xFFFFFFFFFFFFFFFF),剩余部分未被利用。
用户空间主要包含以下部分:代码段、数据段、BSS段、堆和栈。
代码段用于存放程序执行代码,即CPU执行的机器指令。
数据段存放已初始化且初值不为0的全局变量和静态局部变量,属于静态内存分配,可读可写。
BSS段包括未初始化全局变量和静态局部变量的空间。
堆(heap)是动态分配内存的区域,当进程读取文件时,若文件未在内存中,会通过缺页中断获取物理内存,通过磁盘调页将文件数据读入内存,实现文件的读取。
文件在两个进程间共享时,即使它们映射到同一文件,虚拟地址空间也可能不同。若进程A先读取文件,则会获取物理内存,通过磁盘调页将文件数据读入内存。进程B在访问文件时,若文件数据不在内存中,则会查找缓存区,如果缓存中有文件数据,则建立映射关系,实现进程间通信。
栈(stack)用于存储函数调用时的局部变量。
以数组s和指针p3为例,数组s的内容是在运行时赋值,而指针p3指向的常量区字符串内容在编译时已赋值。
使用malloc函数分配内存时,虚拟内存的分配情况如下:
当malloc分配的内存小于128k时,使用brk分配内存,将_edata向高地址移动,只分配虚拟空间,不对应物理内存。第一次读/写数据时,会触发内核缺页中断,内核分配物理内存,建立虚拟地址空间映射关系。若分配的内存不被访问,对应的物理内存不会被分配。
brk分配的内存需要等待高地址内存释放后才能释放,可能导致内存碎片。
当malloc分配的内存大于128k时,使用mmap分配内存,在堆和栈之间寻找空闲内存分配,对应独立内存且初始化为0。mmap分配的内存可以直接通过free释放。
当最高地址空间的空闲内存超过128k时,Linux执行内存紧缩操作,释放部分内存。
当进程访问未建立映射关系的虚拟内存时,逻辑地址转换为物理地址,发现当前页不在内存中,处理器自动触发缺页异常。
3. Linux的内核空间和用户空间是如何划分的(以32位系统为例)
通常32位Linux内核地址空间划分0~3G为用户空间,3~4G为内核空间。地址分配如下图所示
直接映射区:线性空间中从3G开始最大896M的区间,为直接内存映射区,该区域的线性地址和物理地址存在线性转换关系:线性地址=3G+物理地址。
动态内存映射区:该区域由内核函数vmalloc来分配,特点是:线性空间连续,但是对应的物理空间不一定连续。vmalloc分配的线性地址所对应的物理页可能处于低端内存,也可能处于高端内存。
永久内存映射区:该区域可访问高端内存。访问方法是使用alloc_page(_GFP_HIGHMEM)分配高端内存页或者使用kmap函数将分配到的高端内存映射到该区域。
固定映射区:该区域和4G的顶端只有4k的隔离带,其每个地址项都服务于特定的用途,如ACPI_BASE等。