内存这个概念,对于现代计算机的重要性,当然是无需多言的,但是内存这个概念并不是计算机一旦诞生就有的。内存概念首次被正式提出,应该归功于第一草稿团队的贡献。第一草稿是计算机业界中里程碑式的白皮书,第一草稿中设计的计算机架构模型一直被沿用到了今天。虽然这些成果已经被一个名为冯·诺伊曼的小偷给偷走了。在今天这个时代,你经常可以听到“冯·诺伊曼架构”这一名词。冯·诺伊曼是匈牙利人,名字中的冯是匈牙利皇家的赐姓,他在二战之间来到了美国,结识了奥本海默等人,并选择计算机科学的研究方向进行研究。第一草稿就可以说是一个非常杰出的研究成果。但是,第一草稿的成果应该是属于团队的,而冯·诺伊曼在发表第一草稿时只填写了自己的名字,并没有添加其他的人员为作者。值得一提的是,第一草稿中将内存的概念提到了与CPU一样的高度。是的,从那个时候开始,计算机领域的先贤们就意识到了内存的重要性,光CPU快是没有用的,内存也要跟上才行。说起Linux的内存分配方式,你一定可以在第一时间想起来很多种,在这众多的内存方式中,有一种特殊的内存分配方式,它将磁盘文件与内存关联了起来。将内存与文件相结合的做法,并不算太罕见,这种方式常常被用在两种场景下,一是内存不够用的时候,这个时候可以从磁盘上取出一段空间作为交换内存使用,二是加速外部设备访问的时候。对于用户态程序来讲,mmap是一种显示且常见的将文件映射到内存中的方法。对于一个正常程序来讲,不管程序主体中有没有使用mmap,在实际情况这个,它也会被动的使用mmap。比如程序在启动过程中,其中一个很重要的操作,就是将程序所依赖的动态链接库加载到内存中。在了解程序是如何显示的将文件映射到内存上之前,我们先来看一下程序是如何被动的使用文件映射的内存缓存。Linux中有一个非常重要的概念,那就是一切皆文件。对于用户态程序来讲,它是不能直接访问CPU信息、内核资源以及外部IO设备的,但是有的时候用户态程序又有访问它们的实际需求。为此Unix提供了虚文件系统,允许CPU信息、内核资源以及外部IO设备通过高特权的驱动建立一个虚文件,用户态程序通过读写虚文件,实现针对敏感设备的数据访问与控制。这样做最大的好处就是统一了用户态程序的访问接口。只需要通过write或read这样的文件读写接口,就可以实现一套接口访问和控制各种设备的资源。至于访问规则,则由设备的驱动负责管理,驱动有必要保证高特权资源的安全性问题,并且自身也不会在性能及安全性上影响其他模块。所以,从用户态程序的视角来看,虚文件系统的好处一共有三处,一是让用户态程序可以使用一套文件读写接口访问各种高特权资源,二是高特权资源的管理驱动,可以灵活的提供资源,而不需要在场景变更时重新编译内核。在上面的描述中,你可能会好奇什么是虚文件,所谓的虚文件和真实的磁盘文件都有什么样的区别,虚文件就一定对应磁盘文件吗?下面就让我们来逐个揭秘!用户态程序通过__NR_write或__NR_read系统调用号发出系统调用后,内核会通过sys_write或sys_read函数进行响应。从代码中可以看出,sys_write和sys_read只是针对系统调用而产生的一个过度层,真正负责处理读写流程的函数应该是vfs_read和vfs_write。不直接使用vfs_read和vfs_write对接系统调用的原因并不复杂,首要的原因是内核需要SYSCALL_DEFINE将系统调用对接函数与系统调用表直接的串联起来(总不能逐个赋值吧)。不管是vfs_read函数,还是vfs_write函数,它们都要求上级函数传递一个类型为struct file的变量,实际的读写操作有该变量中的f_op字段决定。那么struct file是个什么东西,又是从哪里产生的呢?用户态程序读写文件时,有一个前提要求,那就是需要程序先将文件打开,然后获取文件描述符。内核要求文件在读写文件时传递文件描述符,内核会使用fdget_pos接口将文件描述符fd转换成struct fd f。struct fd中的file字段的类型就是struct file。我们暂缺不论struct file是从哪里来的,又是如何与文件描述符进行绑定,在这里,我们只关注一件事情,那就是为什么要单独封装一个struct fd结构体。struct fd的产生由fdget_pos函数负责,该函数的执行流程可以分成__to_fd和__fdget_pos两个部分。__fdget_pos会根据内核第一霸current宏(指向CPU当前允许的进行)取出struct files_struct字段存放到files变量中,当进程只打开一个文件时,会通过files_lookup_fd_raw直接获取文件描述符对应的struct file,反之则通过__fget遍历获取struct file。__fdget_pos根据文件描述符匹配到struct file后,会将数值交给__to_fd,__to_fd函数会将其转换为struct fd。之所以__to_fd函数还要再转换一次,是因为__fdget_pos传回来的数值,不止包含文件描述符对应的struct file,低三个比特位并不包含真实的数值,它们被留作是标志位使用。那么这个文件描述符又是怎么来的呢?对于用户态程序来讲,它需要通过open接口打开文件获取文件描述符。内核会为每个进程维护一套使用文件的列表,这个列表由结构体struct files_struct描述,该结构体中有一个名为next_fd的字段,它就是内核分配文件描述符的关键。每当用户态程序申请打开文件时,内核都会将next_fd返回给程序使用,并会将next_fd加1,让程序下次申请时获取新的文件描述符。对于用户态程序来讲,文件描述符就是标识进程自身已打开文件的唯一标识,而且这个文件描述符对于其他进程来讲是隔离的。如果说,文件描述符的数量是无限的,那么当然不需要再额外考虑什么,但是我们知道文件描述符是属于int类型的,其中负数部分代表文件打开失败,只有文件描述符数值为正数时,才代表文件被正确的打开,int类型一般占用32个比特位,其正数值的上限就是2147483647。如果打开文件的数量超过2147483647了,应该怎么办呢?而且文件既可以打开又可以关闭,已关闭的文件描述符难道就不能使用了吗,那这样太浪费了吧!如果你对上面两点感到好奇,可以重点关注下find_next_fd函数以及expand_files函数。其中find_next_fd函数可以查找未使用的文件描述符,而函数expand_files则可以扩展文件描述符的可获取范围。当然expand_files并不是无限制扩张的,一旦进程打开的文件数量过多,就会返回错误,内核不允许用户态进程无限制的打开文件。对于用户态进程来讲,可以查看nr_open虚文件获取单个进程被允许打开的最大文件数量,file-max则代表整个系统中所有进程被允打开的最大文件数量。file-nr中的信息,代表当前系统中所有进程已打开的文件数量。struct file和文件描述符是操作虚文件的关键所在,文件描述符这个东西内核和用户态程序都会使用,因为它是标识进程打开文件的唯一标识,至于struct file,它只会留给
...(已截断)
---
来源: 看雪论坛
原文链接: https://bbs.kanxue.com/thread-287441.htm
[原创]PWN入门-24-MMAP除妖
294 浏览
0 回复
暂无回复,快来抢沙发吧!