共计 3217 个字符,预计需要花费 9 分钟才能阅读完成。
MMU 虚拟地址转为物理地址的硬件(进程内存 3G 超出也会页交换,到文件)
page(4K.8K 内存管理基本单位) 每个物理页一个结构 4G 会占用 20M 被用户空间进程 / 动态分配内核数据 / 静态内核代码 / 页高速缓存等引用
flag 脏 / 锁;count 引用计数,0 空闲;*virtual 虚拟地址
RAM
分区 DDMA normal highem 动态映射的页
分配:
alloc_pages 整个页
kmalloc 字节 连续内存虚拟地址,物理地址也连续
vmalloc 虚拟连续,物理不一定,建立页表项需要一个一个的映射
slab 层,防止每个进程自己单独创建 free 链,不能全局判断
高低度缓冲组。kmem_cache。每个类型比如 task_struct,inode,通用的。3 个链表 empty,full,parti
kmem_Cache_alloc. 内存紧缺 / 显示撤销才释放
高端内存(页并不一定永久映射,比如内核 1G 要访问 8G 物理内存,动态映射)
kmap。可睡眠,永久映射
kmap_atomic 临时,原子,不睡眠,不阻塞,禁止内核抢占
cpu 上数据,alloc_percpu
Linux 内核高端内存的理解
前 面我们解释了高端内存的由来。Linux 将内核地址空间划分为三部分 ZONE_DMA、ZONE_NORMAL 和 ZONE_HIGHMEM,高端内存 HIGH_MEM 地址空间范围为 0xF8000000 ~ 0xFFFFFFFF(896MB~1024MB)。那么如内核是如何借助 128MB 高端内存地址空间是如何实现访问可以所有物理内存?
当内核想访问高于 896MB 物理地址内存时,从 0xF8000000 ~ 0xFFFFFFFF 地址空间范围内找一段相应大小空闲的逻辑地址空间,借用一会。借用这段逻辑地址空间,建立映射到想访问的那段物理内存(即填充内核 PTE 页面表),临时用一会,用完后归还。这样别人也可以借用这段地址空间访问其他物理内存,实现了使用有限的地址空间,访问所有所有物理内存。
1. 无论 CPU 运行于用户态还是核心态,CPU 执行程序所访问的地址都是 虚拟地址 ,MMU 必须通过读取控制寄存器CR3 中的值作为当前页面目录的指针,进而根据分页内存映射机制(参看相关文档)将该虚拟地址转换为真正的物理地址才能让 CPU 真 正的访问到物理地址。
2. 每个进程都有其 自身的页面目录 PGD,Linux 将该目录的指针存放在与进程对应的内存结构 task_struct.(struct mm_struct)mm->pgd 中。每当一个进程被调度(schedule())即将进入运行态时,Linux 内核都要用该进程的 PGD 指针设 置 CR3(switch_mm())
3. 当创建一个新的进程时,都要为新进程创建一个新的页面目录 PGD,并从内核的页面目录 swapper_pg_dir 中复制内核区间页面目录项至新建进程页面目录 PGD 的相应位置
4. 系统调用的具体实现是将系统调用的参数依次存入寄存器 ebx,ecx,edx,esi,edi(最多 5 个参数,该情景有两个 name 和 len),接着将系统调用号存入寄存器 eax,然后通过中断指令“int 80”使进程 A 进入系统空间。由于进程的 CPU 运行级别小于等于为系统调用设置的陷阱门的准入级别 3,所以可以畅通无阻的进入系统空间去执行为 int 80 设置的函数指针 system_call()。由于 system_call() 属于内核空间,其运行级别 DPL 为 0,CPU 要将堆栈切换到内核堆栈,即 进程 A 的系统空间堆栈。我们知道内核为新建进程创建 task_struct 结构时,共分配了两个连续的页面,即 8K 的大小,并将底部约 1k 的大小用于 task_struct(如 #define alloc_task_struct() ((struct task_struct ) __get_free_pages(GFP_KERNEL,1))), 而其余部分内存用于系统空间的堆栈空间,即当从用户空间转入系统空间时,堆栈指针 esp 变成了(alloc_task_struct()+8192),这也是为什么系统空间通常用宏定义 current(参看其实现)获取当前进程的 task_struct 地址的原因。每次在进程从用户空间进入系统空间之初,系统堆栈就已经被依次压入用户堆栈 SS、用户堆栈指针 ESP、EFLAGS、用户空间 CS、EIP,接着 system_call() 将 eax 压入,再接着调用 SAVE_ALL 依次压入 ES、DS、EAX、EBP、EDI、ESI、EDX、ECX、EBX,然后调用 sys_call_table+4%EAX,本情景为 sys_sethostname()
5. 调用 copy_from_user(to,from,n),其中 to 指向内核空间 system_utsname.nodename,譬如 0xE625A000,from 指向用户空间譬如 0x8010FE00。现在进程 A 进入了内核,在 系统空间中运行,MMU 根据其 PGD 将虚拟地址完成到物理地址的映射,最终完成从用户空间到系统空间数据的复制
虚拟内存交换等
VFS 虚拟文件系统
磁盘 分区:文件,数据,交换区域
文件系统:ext2 引导块,超级块(包含块大小等),i 节点(可对应多级块 3IPB,2IPB 等),数据
崩溃恢复,元数据不一致,检查要遍历 =》日志文件 ext3,ext4
write()->sys_write(VFS)->ext2 等的 write-> 物理
文件,目录 (目录项缓存),inode,mount point
file {链表,pathh file_operations}
目录包含文件名,inode 编号。目录自身的 Inode 中的类型不同,rename 仅操作目录条目不动文件。
文件名到 Inode 有多个。硬链接指向一个 Inode。软链接指向文件名
块 IO,硬盘等,随机访问的,区别于字符设备只能顺序访问
块缓冲区,已经合成一个页缓存
块 IO 请求队列(一个块设备一个)
调度:linusx 电梯 合并请求,临近插入
最终期限 超时时间(读 500ms,写 5s)三个队列:读 FIFO/ 写 FIFO/ 排序队列同 linus=> 超时派发队列
预测 IO 超时后等几 ms,相邻合并
完全公正,每个进程一个队列
空 IO,只合并相邻的
进程地址空间 连续虚拟地址
mm_struct 分配,撤销……{内存区域 vm_area_struct 红黑树和链表,pgd, 所有 mm_struct 链表,每段起始地址等}
vm_area_strict 区间地址等。
虚拟地址 =》物理页面 三级页表(pgd,pmd,pte)硬件效率有限,TLB 缓存
在堆上分配,sbrk。改变 programm break 位置,进程可以访问这部分地址,若 RAM 未分配,内核会在进程是首次访问这些虚拟内存地址时自动分配新 RAM 页
malloc,free 内找 / 分割,不够 sbrk
页缓存(磁盘和内存 ms/ns 的差距)
页告诉缓存是 RAM 组成,对应硬盘上的物理块(扇区整数倍但比页小,一般 512B/1kb/4kb),
linux 写缓存,标记脏,脏页链表,回写进程刷会磁盘
缓存回收 LRU 双链(活跃 / 非活跃)
address_space 每页一个
页内搜索 基树。回收时要反向检查引用的项。根据页内偏移起终范围找 vma,起点基树,终点堆,很多个共同引用优先搜索树(堆 + 基树)。
回写线程 flusher 一个设备一个线程,每个收集自己的。以前固定线程数,当一个设备阻塞会阻塞其上个所有线程。因为拥塞发生次数太频繁改一个设备一个线程。
若页属于一个文件(该文件存放在磁盘上文件系统中,如 ext4),那么页的所有者就是文件的 inode;并且对应的 address_space 对象存放在 VFS inode 对象的 i_data 字段中。
fd 与磁盘的关联
fd->file->path->dentry->inode->address_space->page->buffer_head-> 磁盘块号
https://segmentfault.com/a/11…