linux内存管理一

磁盘

  文件存储在硬盘上,硬盘的最小存储单位叫做”扇区”(Sector)。每个扇区储存512字节(磁盘不能定位到每一个地址,职能定位到扇区,所以读写操作常常是以一整个扇区为单位,例如要变某一个地方的值,是将整个扇区读出来,然后更改完后在写回去)。
  操作系统读取硬盘的时候,不会一个个扇区地读取,这样效率太低,而是一次性连续读取多个扇区,即一次性读取一个”块”(block)。这种由多个扇区组成的”块”,是文件存取的最小单位。”块”的大小,最常见的是4KB,即连续八个 sector组成一个 block。

文件描述符fd和文件指针(FILE结构体)

文件描述符fd

  linux中一切接文件,而所有文件都通过文件描述符来操作,文件描述符是一个非负的整数,每当打开现有文件或者创建新文件,内核会像进程都会返回一个文件描述符,一个新创建的进程中,默认有三个文件描述符,0、1、2,如果创建或者打开新的文件,则会返回3,即没增加一个文件,则该进程返回的文件描述符+1。
  每个进程都有一个文件描述符的表,用来管理文件描述符,父进程如果fork子进程的话,子进程会继承这个文件描述符的表。相同的文件可以有不同的进程打开,一个文件也能被一个进程打开多次,会生成多个文件描述符,但是都是指向的同一个文件。
  进程打开文件过程:
  执行open通过系统调用打开文件,该进程获得文件描述符之后能操作文件。并且进程会为该文件创建file对象,将file对象的指针存入进程描述符表。即文件描述符fd是一个整数,他对应着文件描述符表中的key,根据这个key能找的val,即文件指针。

struct files_struct {
	atomic_t count; /* 共享该表的进程数 */
	rwlock_t file_lock; /* 保护以下的所有域,以免在tsk->alloc_lock中的嵌套*/
	int max_fds; /*当前文件对象的最大数*/
	int max_fdset; /*当前文件描述符的最大数*/
	int next_fd; /*已分配的文件描述符加1*/
	struct file ** fd; /* 指向文件对象指针数组的指针 */
	fd_set *close_on_exec; /*指向执行exec( )时需要关闭的文件描述符*/
	fd_set *open_fds; /*指向打开文件描述符的指针*/
	fd_set close_on_exec_init;/* 执行exec( )时需要关闭的文件描述符的初 值集合*/
	fd_set open_fds_init; /*文件描述符的初值集合*/
	struct file * fd_array[32];/* 文件对象指针的初始化数组*/
};

FILE结构体

//C语言文件指针域文件描述符之间可以相互转换
int fileno(FILE * stream)
FILE * fdopen(int fd, const char * mode)

struct _iobuf {
    char *_ptr;          //缓冲区当前指针
    int   _cnt;
    char *_base;         //缓冲区基址
    int   _flag;         //文件读写模式
    int   _file;         //文件描述符
    int   _charbuf;      //缓冲区剩余自己个数
    int   _bufsiz;       //缓冲区大小
    char *_tmpfname;
};
typedef struct _iobuf FILE;

  FILE结构体中包含文件描述符和缓冲区。
  注意:文件描述符和文件指针的区别

int fd = open("");

  这里的fd代表的是文件描述符,每个进程的PCB中保存着一份文件描述符表,这个fd表示已打开的文件在这个表中的索引。每个表项都有一个指针指向打开的文件

FILE *fd;代表的是文本文件,使用fopen、fread、fwrite进行读写

  这里的fd表示文件指针,通过FILE结构体来构建。FILE包括一个IO缓冲区和文件描述符,可以理解为FILE包含了文件描述符
  文件描述符和文件指针的转换:

int fileno(FILE *stream);
FILE *fdopen(int fd, const char *mode);

与文件描述符相关的三张表

  1. 进程中的文件描述符表
struct files_struct {
	atomic_t count; /* 共享该表的进程数 */
	rwlock_t file_lock; /* 保护以下的所有域,以免在tsk->alloc_lock中的嵌套*/
	int max_fds; /*当前文件对象的最大数*/
	int max_fdset; /*当前文件描述符的最大数*/
	int next_fd; /*已分配的文件描述符加1*/
	struct file ** fd; /* 指向文件对象指针数组的指针 */
	fd_set *close_on_exec; /*指向执行exec( )时需要关闭的文件描述符*/
	fd_set *open_fds; /*指向打开文件描述符的指针*/
	fd_set close_on_exec_init;/* 执行exec( )时需要关闭的文件描述符的初 值集合*/
	fd_set open_fds_init; /*文件描述符的初值集合*/
	struct file * fd_array[32];/* 文件对象指针的初始化数组*/
};
struct task_struct {
  //...
    struct files_struct *files // 进程级别的文件描述符表
  //...
};
  1. linux内核维护的文件描述符表
struct file
{
	struct list_head f_list; /*所有打开的文件形成一个链表*/
	struct dentry *f_dentry; /*指向相关目录项的指针*/
	struct vfsmount *f_vfsmnt; /*指向VFS安装点的指针*/
	struct file_operations *f_op; /*指向文件操作表的指针*/
	mode_t f_mode; /*文件的打开模式*/
	loff_t f_pos; /*文件的当前位置*/
	unsigned short f_flags; /*打开文件时所指定的标志*/
	unsigned short f_count; /*使用该结构的进程数*/
	unsigned long f_reada, f_ramax, f_raend, f_ralen, f_rawin;/*预读标志、要预读的最多页面数、上次预读后的文件指针、预读的字节数以及预读的页面数*/
	int f_owner; /* 通过信号进行异步I/O数据的传送*/
	unsigned int f_uid, f_gid; /*用户的UID和GID*/
	int f_error; /*网络写操作的错误码*/
	unsigned long f_version; /*版本号*/
	void *private_data; /* tty驱动程序所需 */
};

  内核对所有打开的文件通过一个链表建立链接。并将链表中的每个节点称为文件句柄,一个打开文件的句柄存储了打开文件的全部信息,包括:当前操作文件的偏移、标识状态(open时的flag)、文件权限、文件属性、对inode的引用等。
2. 文件系统的inode表

struct inode {
	struct hlist_node    i_hash;
	struct list_head    i_list;
	struct list_head    i_sb_list;
	struct list_head    i_dentry;
	unsigned long        i_ino;
	atomic_t        i_count;
	unsigned int        i_nlink;
	uid_t            i_uid;
	gid_t            i_gid;
	dev_t            i_rdev;   //该成员表示设备文件的inode结构,它包含了真正的设备编号。
	u64            i_version;
	loff_t            i_size;
	#ifdef __NEED_I_SIZE_ORDERED
	seqcount_t        i_size_seqcount;
}

  将硬盘格式化为ext文件系统是,硬盘会被分数据区(block)和inode区,block存放数据,inode存放文件包含的信息。包括文件字节数、占用了那几个磁盘块、文件属性和权限、文件时间戳、链接树(内核的文件描述符表中有多少文件指向这个inode)、文件数据block的位置,以及inode编号。(通过df -i指令可以看到inode信息,通过ls -il最前面的一列就是inode编号)。

虚拟文件系统(VFS)

  进程所有的文件操作都通过VFS,由VFS来适配各种底层不同的文件系统,完成实际的文件操作。
  VFS是内核的一个子系统,VFS向上提供统一的操作接口。
  一个具体的文件系统想要被Linux支持,必须按照VFS的规范编写自己的操作函数,同时将自己的操作细节对内核的其他子系统隐藏
  通俗的说,VFS就是定义了一个通用文件系统的接口层和适配层,一方面为用户进程提供了一组统一的访问文件,目录和其他对象的统一方法,另一方面又要和不同的底层文件系统进行适配。具体应用:例如mount、umount、sysfs、chown、mkdir等,特别,异步IO的select和poll是VFS。

主要作用

主要作用:

  1. 支持多种具体文件系统直接的相互访问(例如ext的文件系统通过mount指令在某个目录下挂在Fat文件系统的U盘,粘贴复制移动等操作都不被影响是因为VFS的原因)
  2. 接受系统的调用,read、write、open
  3. 对具体文件系统数据结构进行抽象,以统一的数据结构进行管理
  4. 接受其他子系统的操作

Linux中文件系统逻辑关系架构

在这里插入图片描述

VFS的核心数据结构

  1. 超级块(superblock)模块:用于保存一个文件系统的所有元数据,相当于这个文件系统的信息库,为其他的模块提供信息。因此一个超级块可代表一个文件系统。文件系统的任意元数据修改都要修改超级块。超级块对象是常驻内存并被缓存的。
  2. 索引节点(inode)模块:管理一个具体的文件,是文件的唯一标识,一个文件对应一个inode。通过inode可以方便的找到文件在磁盘扇区的位置。同时inode模块可链接到address_space模块,方便查找自身文件数据是否已经缓存。
  3. 目录项(dentry)模块:管理路径的目录项。比如一个路径 /home/foo/hello.txt,那么目录项有home, foo, hello.txt。目录项的块,存储的是这个目录下的所有的文件的inode号和文件名等信息。其内部是树形结构,操作系统检索一个文件,都是从根目录开始,按层次解析路径中的所有目录,直到定位到文件。
  4. 文件(file)模块:包含所有内核已经打开的文件。已经打开的文件对象由open系统调用在内核中创建,也叫文件句柄。打开文件列表模块中包含一个列表,每个列表表项是一个结构体struct file,结构体中的信息用来表示打开的一个文件的各种状态参数。
  5. 文件操作(file_operations)模块。这个模块中维护一个数据结构,是一系列函数指针的集合,其中包含所有可以使用的系统调用函数,例如open、read、write、mmap等。每个打开文件(打开文件列表模块的一个表项)都可以连接到file_operations模块,从而对任何已打开的文件,通过系统调用函数,实现各种操作。
  6. address_space模块,它表示一个文件在页缓存中已经缓存了的物理页。它是页缓存和外部设备中文件系统的桥梁。如果将文件系统可以理解成数据源,那么address_space可以说关联了内存系统和文件系统。

文件读写过程

  linux系统采用虚拟内存机制后,页是虚拟内存管理的最小单位。有两个概念需要区分一下:buffer chahe和page cache,buffer是面向块设备的,而page(页)是面向虚拟内存的。文件io之和page缓存交互,不和内存交互。

读过程

  1. 通过read函数进程系统调用发起读请求
  2. 内核检查进程的文件描述符表定位到文件指针
  3. 根据文件指针找到目录项和inode等信息
  4. 根据inode计算出要读取的地址和页的信息
  5. 通过inode找到文件对应的address_space
  6. 在address_space中访问该文件的页缓存树,查找对应的页缓存结点:如果页缓存命中,那么直接返回文件内容;如果页缓存缺失,那么产生一个页缺失异常,创建一个页缓存页,同时通过inode找到文件该页的磁盘地址,读取相应的页填充该缓存页;重新进行第6步查找页缓存;

写过程

  1. 前5步和读文件一致,在address_space中查询对应页的页缓存是否存在:
  2. 如果页缓存命中,直接把文件内容修改更新在页缓存的页中。写文件就结束了。这时候文件修改位于页缓存,并没有写回到磁盘文件中去。
  3. 如果页缓存缺失,那么产生一个页缺失异常,创建一个页缓存页,同时通过inode找到文件该页的磁盘地址,读取相应的页填充该缓存页。此时缓存页命中,进行第6步。
  4. 一个页缓存中的页如果被修改,那么会被标记成脏页。脏页需要写回到磁盘中的文件块。有两种方式可以把脏页写回磁盘:手动调用sync()或者fsync()系统调用把脏页写回,pdflush进程会定时把脏页写回到磁盘。同时注意,脏页不能被置换出内存,如果脏页正在被写回,那么会被设置写回标记,这时候该页就被上锁,其他写请求被阻塞直到锁释放。

参考博客

  • https://blog.csdn.net/qq_20817327/article/details/107093167?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165371513316782246462636%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=165371513316782246462636&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2blogfirst_rank_ecpm_v1~rank_v31_ecpm-8-107093167-null-null.nonecase&utm_term=%E5%AD%98%E5%82%A8&spm=1018.2226.3001.4450
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux 内存管理主要包括内存节点、分区、页框和虚拟内存等概念。 1. 内存节点 Linux 根据 CPU 访问代价的不同将内存划分为不同的分区,即内存节点。内核以 struct zone 来描述内存分区。通常一个节点分为 DMA、Normal 和 High Memory 内存区。其中,DMA 内存区为直接内存访问分区,通常为物理内存的起始16M,供外设使用,外设和内存直接访问数据而无需 CPU 参与;Normal 内存区为从 16M 到 896M 的内存区;HighMemory 内存区为 896M 以后的内存区。 2. 分区 内存节点中的分区是内存管理的基本单位,每个分区都有自己的页框列表和空闲页框列表。页框是内存管理的最小单位,通常为 4KB。内核通过页框来管理内存,将内存分为多个页框,每个页框都有自己的状态,包括已分配、未分配、已使用等。 3. 页框 页框是内存管理的最小单位,通常为 4KB。内核通过页框来管理内存,将内存分为多个页框,每个页框都有自己的状态,包括已分配、未分配、已使用等。内核通过页表来映射虚拟地址和物理地址,将虚拟地址转换为物理地址。 4. 虚拟内存 虚拟内存是一种将硬盘中划出一段 swap 分区当作虚拟的内存,用来存放内存中暂时用不到的内存页,等到需要的时候再从 swap 分区中将对应的内存页调入到内存中的技术。硬盘此时相当于一个虚拟的内存。Linux 通过虚拟内存技术来扩展内存,使得进程可以使用比物理内存更大的内存空间。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值