本博客参考爱编程的大丙
博客仅作个人学习使用。
1.虚拟地址空间
- 用来加载程序数据(数据可能被加载到物理内存上,空间不够就加载到虚拟内存中。
- 对应着一段连续的内存地址,其实位置为0
- 其实为0的地址不是物理内存的0地址,是被虚拟出来的
虚拟地址空间的大小也由操作系统决定,32位的操作系统虚拟地址空间的大小为 232字节,也就是4G,64位的操作系统虚拟地址空间大小为264 字节,234G。
当我们运行磁盘上一个可执行程序, 就会得到一个进程,内核会给每一个运行的进程创建一块属于自己的虚拟地址空间,并将应用程序数据装载到虚拟地址空间对应的地址上。
CPU只进行计算,并不存储数据,其实进程中的数据就是通过CPU中的内存管理单元MMU从进程的虚拟地址空间映射过去的。
1.1 存在的意义
假设计算机的物理内存大小为1G, 进程A需要100M内存因此直接在物理内存上从0地址开始分配100M, 进程B启动需要250M内存, 因此继续在物理内存上为其分配250M内存, 并且进程A和进程B占用的内存是连续的。之后再启动其他进程继续按照这种方法进行物理内存的分配。。。
如果不进行虚拟地址空间的管理。可能会导致如下问题:
- 每个进程的地址不隔离,有安全风险
- 内存效率低
- 进程中数据的地址不确定,每次都会发生变化。
虚拟地址空间就是一个中间层,相当于在程序和物理内存之间设置了一个屏障,将二者隔离开来。程序中访问的内存地址不再是实际的物理内存地址,而是一个虚拟地址,然后由操作系统将这个虚拟地址映射到适当的物理内存地址上。
1.2 分区
![](https://img-blog.csdnimg.cn/img_convert/52d30a8f0c319cb556f96cc6038d3ca7.png#pic_center)
- 内核区:不允许应用程序读写该区域的内容或者皆调用内核代码定义的函数;内核总是驻留在内存中,是操作系统的一部分;系统中所有进程对应的虚拟地址空间的内核去都会映射到同一块物理内存上。
- 用户区:存储用户程序运行中的各种数据
每个进程的虚拟地址空间都是从0地址开始的,我们在程序中打印的变量地址也其在虚拟地址空间中的地址,程序是无法直接访问物理内存的。虚拟地址空间中用户区地址范围是 0~3G,里边分为多个区块:
- 保留区: 位于虚拟地址空间的最底部,未赋予物理地址。任何对它的引用都是非法的,程序中的空指针(NULL)指向的就是这块内存地址。
- .text段: 代码段也称正文段或文本段,通常用于存放程序的执行代码(即CPU执行的机器指令),代码段一般情况下是只读的,这是对执行代码的一种保护机制。
- .data段: 数据段通常用于存放程序中已初始化且初值不为0的全局变量和静态变量。数据段属于静态内存分配(静态存储区),可读可写。
- .bss段: 未初始化以及初始为0的全局变量和静态变量,操作系统会将这些未初始化变量初始化为0
- 堆(heap):用于存放进程运行时动态分配的内存。
- 堆中内容是匿名的,不能按名字直接访问,只能通过指针间接访问。
- 堆向高地址扩展(即“向上生长”),是不连续的内存区域。这是由于系统用链表来存储空闲内存地址,自然不连续,而链表从低地址向高地址遍历。
- 内存映射区(mmap):作为内存映射区加载磁盘文件,或者加载程序运作过程中需要调用的动态库。
- 栈(stack): 存储函数内部声明的非静态局部变量,函数参数,函数返回地址等信息,栈内存由编译器自动分配释放。栈和堆相反地址“向下生长”,分配的内存是连续的。
- 命令行参数:存储进程执行的时候传递给main()函数的参数,argc,argv[]
- 环境变量: 存储和进程相关的环境变量, 比如: 工作路径, 进程所有者等信息
2.文件描述符
2.1 文件描述符
Linux中一切皆文件,那么一个打开的文件如何与应用程序对应呢?
解决方案:文件描述符(file descriptor, fd),当在进程中打开一个现有文件或创建一个新文件时,内核向该进程返回一个文件描述符,用于对应这个打开/新建的文件。
2.2 文件描述符表
每启一个进程就会得到一个对应的虚拟地址空间,这个虚拟地址空间氛围两大部分,在内核区有专门用于进程管理的模块。Linux的进程控制块PCB本质是一个叫做task_struct的结构体,里边包括管理进程所需要的各种信息,其中有一个结构提体叫file,我们将它叫做文件描述符表,其是整形索引表。
内核为每一个进程维护了一个文件描述符表,索引表中的值都是从0开始的,所以在不同的进程中你会看到相同的文件描述符,但是它们指向的不一定是同一个磁盘文件。
![](https://img-blog.csdnimg.cn/img_convert/674a5a0f2c76040cf8445f6d56c8cc45.png#pic_center)
在Linux中,甚至每个终端都被视作一个设备文件,当前操作的终端文件可以使用/dev/tty
表示。
正如图中所示,这里说明以下问题:
-
打开最大文件数
默认最大1024 -
默认分配的文件描述符
当一个进程被启动之后,内核PCB的文件描述符表中就已经分配了三个文件描述符,这三个文件描述符对应的都是当前启动这个进程的终端文件(Linux中一切皆文件,终端就是一个设备文件,在 /dev 目录中)。
这三个默认分配的文件描述符是可以通过close()函数关闭掉,但是关闭之后当前进程也就不能和当前终端进行输入或者输出的信息交互了。STDIN_FILENO
:标准输入,宏值为0STDOUT_FILENO
:标准输出,宏值为1STDERR_FILENO
:标准错误,可以通过这个文件描述符讲错误信息通过终端输出出来,宏值为2.
-
给新打开的文件分配文件描述符
上文提到文件描述符表中0,1,2都被分配出去了,所以只能从3开始分配。- 在进程中每打开一个文件,就会给这个文件分配一个新的文件描述符
文件描述符的创建和使用
请直接移步苏丙榅的个人博客:Linux系统文件IO