Linux源代码分析_存储管理

合集下载

Linux 操作系统内核基本实验

Linux 操作系统内核基本实验
1. 以超级用户身份编程,计算某一时段中所有程序平均运行时间。
2. 通过编写 shell 程序,了解子进程的创建和父进程与子进程间的协同,获得 多进程程序的编程经验。
1.2.4 第 4 组 存储管理
实验 4.1 观察实验 1. 在 Linux 下,使用 gdb 程序观察一个程序文件的内容和结构。启动该程序 执行,再用 GDB 观察其内存映象的内容和结构。 2. 在 Linux 下,用 free 和 vmstat 命令观察内存使用情况。 3. 在 Linux 下,查看/proc 与内存管理相关的文件,并解释显示结果。 4. 在 Linux 下,用 malloc()函数实现 cat 或 copy 命令。
2. 系统安装实验
2.1 实验 1.1 Linux 系统安装
1、实验目的
从 CD-ROM 安装 Red Hat Linux 操作系统,如 Red Hat Linux7.2,建立后续各个实验的 运行环境。
2、实验内容(以 Red Hat Linux7.2 为例)
Red Hat Linux7.2 安装光盘共有两张,第一张可直接从光盘启动,包含大部分的软件包 和一些安装工具。第二张光盘包含许多附加软件包。以下为安装过程和注意事项。 (1)启动安装程序。用 Linux 的第一张光盘,从光驱引导启动程序,进入启动界面,显示 提示符 ”boot: ”,选择图形模式进行安装。 (2)选择安装界面的使用语言 (3)选择默认的键盘设置 (4)选择默认的鼠标设置 (5)选择安装类型。Red Hat Linux 提供了个人桌面、工作站、服务器和定制等多种安装类
了解 Linux 的设备驱动程序的组织结构和设备管理机制,编写简单的字符设 备和块设备驱动程序。
1.2.7 第 7 组 文件系统管理

Linux 0.1.1文件系统的源码阅读

Linux 0.1.1文件系统的源码阅读

Linux 0.11文件系统的源码阅读总结1.minix文件系统对于linux 0.11内核的文件系统的开发,Linus主要参考了Andrew S.Tanenbaum 所写的《MINIX操作系统设计与实现》,使用的是其中的1.0版本的MINIX文件系统。

而高速缓冲区的工作原理参见M.J.Bach的《UNIX操作系统设计》第三章内容。

通过对源代码的分析,我们可以将minix文件系统分为四个部分,如下如1-1。

●高速缓冲区的管理程序。

主要实现了对硬盘等块设备进行数据高速存取的函数。

●文件系统的底层通用函数。

包括文件索引节点的管理、磁盘数据块的分配和释放以及文件名与i节点的转换算法。

●有关对文件中的数据进行读写操作的函数。

包括字符设备、块设备、管道、常规文件的读写操作,由read_write.c函数进行总调度。

●涉及到文件的系统调用接口的实现,这里主要涉及文件的打开、关闭、创建以及文件目录等系统调用,分布在namei和inode等文件中。

图1-1 文件系统四部分之间关系图1.1超级块首先我们了解一下MINIX文件系统的组成,主要包括六部分。

对于一个360K软盘,其各部分的分布如下图1-2所示:图 1-2 建有MINIX文件系统的一个360K软盘中文件系统各部分的布局示意图注释1:硬盘的一个扇区是512B,而文件系统的数据块正好是两个扇区。

注释2:引导块是计算机自动加电启动时可由ROM BIOS自动读入得执行代码和数据。

注释3:逻辑块一般是数据块的2幂次方倍数。

MINIX文件系统的逻辑块和数据块同等大小对于硬盘块设备,通常会划分几个分区,每个分区所存放的不同的文件系统。

硬盘的第一个扇区是主引导扇区,其中存放着硬盘引导程序和分区表信息。

分区表中得信息指明了硬盘上每个分区的类型、在硬盘中其实位置参数和结束位置参数以及占用的扇区总数。

其结构如下图1-3所示。

图1-3 硬盘设备上的分区和文件系统对于可以建立不同的多个文件系统的硬盘设备来说,minix文件系统引入超级块进行管理硬盘的文件系统结构信息。

Linux操作系统的内核设计分析

Linux操作系统的内核设计分析

Linux操作系统的内核设计分析Linux操作系统作为开源操作系统的代表,已经在各个领域得到了广泛应用。

而Linux操作系统的内核则是这个系统之所以能够运转的关键所在。

本文将就Linux操作系统的内核设计进行分析,并探讨其优劣之处。

一、Linux内核设计的基础Linux内核的设计基础主要包括以下几个方面:1. 开放源码Linux内核采用的是GPL协议,这意味着它是一个开放源码的项目。

这为世界各地的开发人员提供了极大的便利,方便他们进行开发和修改。

同时,这也确保了Linux内核的透明度,并且鼓励开发者贡献代码的同时,深度参与到Linux开源社区的构建和升级中。

2. 模块化Linux内核的构造采用的是模块化设计。

这种设计方式将内核代码分成独立的模块,每个模块都可以独立编译、加载和卸载。

采用模块化的设计,能够使得开发人员能够更加细致地打包、编译、并部署只包含他们需要的模块的系统。

3. 多任务Linux内核是一个基于多任务设计的系统。

这意味着它能够使得多个程序同时运行,并能够平滑高效地进行任务的切换。

这给开发人员提供了各种各样的自由,使得他们能够更加高效地进行开发。

4. 支持众多处理器架构Linux内核的支持范围非常广泛,它可以适配众多处理器架构。

这意味着一个制造商可以使用不同的处理器架构去生产设备,并且这些设备都能够安装和运行Linux操作系统。

5. 外层调用接口Linux内核支持开放式的外层调用接口。

这使得用户层可以很容易地调用Linux 内核执行某个任务。

这些用户层应用包括网上购物网站、应用程序和各种驱动程序。

6. 子系统Linux内核的子系统主要包括进程管理、内存管理、I/O管理和网络管理等。

二、Linux内核的优点Linux内核具有以下主要优点:1. 开源性Linux内核本身是一个开源的、由社区驱动的项目。

这意味着在它的附加组件和周边产品中,广大的开发者社区都可以为用户提供帮助和支持。

2. 安全性相比其他闭源操作系统,Linux内核在安全性方面更具优势。

linux内核源码分析-nvme设备的初始化

linux内核源码分析-nvme设备的初始化

linux内核源码分析-nvme设备的初始化本⽂基于3.18.3内核的分析,nvme设备为pcie接⼝的ssd,其驱动名称为nvme.ko,驱动代码在drivers/block/nvme-core.c.驱动的加载 驱动加载实际就是module的加载,⽽module加载时会对整个module进⾏初始化,nvme驱动的module初始化函数为nvme_init(),如下:static struct pci_driver nvme_driver = {.name = "nvme",.id_table = nvme_id_table,.probe = nvme_probe,.remove = nvme_remove,.shutdown = nvme_shutdown,.driver = {.pm = &nvme_dev_pm_ops,},.err_handler = &nvme_err_handler,};static int __init nvme_init(void){int result;/* 初始化等待队列nvme_kthread_wait,此等待队列⽤于创建nvme_kthread(只允许单进程创建nvme_kthread) */init_waitqueue_head(&nvme_kthread_wait);/* 创建⼀个workqueue叫nvme */nvme_workq = create_singlethread_workqueue("nvme");if (!nvme_workq)return -ENOMEM;/* 在内核中注册新的⼀类块设备驱动,名字叫nvme,注意这⾥只是注册,表⽰kernel⽀持了nvme类的块设备,返回⼀个major,之后所有的nvme设备的major都是此值 */result = register_blkdev(nvme_major, "nvme");if (result < 0)goto kill_workq;else if (result > 0)nvme_major = result;/* 注册⼀些通知信息 */nvme_nb.notifier_call = &nvme_cpu_notify;result = register_hotcpu_notifier(&nvme_nb);if (result)goto unregister_blkdev;/* 注册pci nvme驱动 */result = pci_register_driver(&nvme_driver);if (result)goto unregister_hotcpu;return0;unregister_hotcpu:unregister_hotcpu_notifier(&nvme_nb);unregister_blkdev:unregister_blkdev(nvme_major, "nvme");kill_workq:destroy_workqueue(nvme_workq);return result;} 这⾥⾯其实最重要的就是做了两件事,⼀件事是register_blkdev,注册nvme这类块设备,返回⼀个major,另⼀件事是注册了nvme_driver,注册了nvme_driver后,当有nvme设备插⼊后系统后,系统会⾃动调⽤nvme_driver->nvme_probe去初始化这个nvme设备.这时候可能会有疑问,系统是如何知道插⼊的设备是nvme设备的呢,注意看struct pci_driver nvme_driver这个结构体,⾥⾯有⼀个nvme_id_table,其内容如下:/* Move to pci_ids.h later */#define PCI_CLASS_STORAGE_EXPRESS 0x010802static const struct pci_device_id nvme_id_table[] = {{ PCI_DEVICE_CLASS(PCI_CLASS_STORAGE_EXPRESS, 0xffffff) },{ 0, }};再看看PCI_DEVICE_CLASS宏是如何定义的#define PCI_DEVICE_CLASS(dev_class,dev_class_mask) \.class = (dev_class), .class_mask = (dev_class_mask), \.vendor = PCI_ANY_ID, .device = PCI_ANY_ID, \.subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID也就是当pci class为PCI_CLASS_STORAGE_EXPRESS时,就表⽰是nvme设备,并且这个是写在设备⾥的,当设备插⼊host时,pci driver(并不是nvme driver)回去读取这个值,然后判断它需要哪个驱动去做处理.nvme数据结构 现在假设nvme.ko已经加载完了(注册了nvme类块设备,并且注册了nvme driver),这时候如果有nvme盘插⼊pcie插槽,pci会⾃动识别到,并交给nvme driver去处理,⽽nvme driver就是调⽤nvme_probe去处理这个新加⼊的设备. 在说nvme_probe之前,先说⼀下nvme设备的数据结构,⾸先,内核使⽤⼀个nvme_dev结构体来描述⼀个nvme设备, ⼀个nvme设备对应⼀个nvme_dev,nvme_dev如下:/* nvme设备描述符,描述⼀个nvme设备 */struct nvme_dev {struct list_head node;/* 设备的queue,⼀个nvme设备⾄少有2个queue,⼀个admin queue,⼀个io queue,实际情况⼀般都是⼀个admin queue,多个io queue,并且io queue会与CPU做绑定 */ struct nvme_queue __rcu **queues;/* unsigned short的数组,每个CPU占⼀个,主要⽤于存放CPU上绑定的io queue的qid,⼀个CPU绑定⼀个queues,⼀个queues绑定到1到多个CPU上 */unsigned short __percpu *io_queue;/* ((void __iomem *)dev->bar) + 4096 */u32 __iomem *dbs;/* 此nvme设备对应的pci dev */struct pci_dev *pci_dev;/* dma池,主要是以4k为⼤⼩的dma块,⽤于dma分配 */struct dma_pool *prp_page_pool;/* 也是dma池,但是不是以4k为⼤⼩的,是⼩于4k时使⽤ */struct dma_pool *prp_small_pool;/* 实例的id,第⼀个加⼊的nvme dev,它的instance为0,第⼆个加⼊的nvme,instance为1,也⽤于做/dev/nvme%d的显⽰,%d实际就是instance的数值 */int instance;/* queue的数量, 等于admin queue + io queue */unsigned queue_count;/* 在线可以使⽤的queue数量,跟online cpu有关 */unsigned online_queues;/* 最⼤的queue id */unsigned max_qid;/* nvme queue⽀持的最⼤cmd数量,为((bar->cap) & 0xffff)或者1024的最⼩值 */int q_depth;/* 1 << (((bar->cap) >> 32) & 0xf),应该是每个io queue占⽤的bar空间 */u32 db_stride;/* 初始化设置的值* dev->ctrl_config = NVME_CC_ENABLE | NVME_CC_CSS_NVM;* dev->ctrl_config |= (PAGE_SHIFT - 12) << NVME_CC_MPS_SHIFT;* dev->ctrl_config |= NVME_CC_ARB_RR | NVME_CC_SHN_NONE;* dev->ctrl_config |= NVME_CC_IOSQES | NVME_CC_IOCQES;*/u32 ctrl_config;/* msix中断所使⽤的entry,指针表⽰会使⽤多个msix中断,使⽤的中断的个数与io queue对等,多少个io queue就会申请多少个中断* 并且让每个io queue的中断尽量分到不同的CPU上运⾏*/struct msix_entry *entry;/* bar的映射地址,默认是映射8192,当io queue过多时,有可能会⼤于8192 */struct nvme_bar __iomem *bar;/* 其实就是块设备,⼀张nvme卡有可能会有多个块设备 */struct list_head namespaces;/* 对应的在/sys下的结构 */struct kref kref;/* 对应的字符设备,⽤于ioctl操作 */struct miscdevice miscdev;/* 2个work,暂时还不知道什么⽤ */work_func_t reset_workfn;struct work_struct reset_work;struct work_struct cpu_work;/* 这个nvme设备的名字,为nvme%d */char name[12];/* SN号 */char serial[20];char model[40];char firmware_rev[8];/* 这些值都是从nvme盘上获取 */u32 max_hw_sectors;u32 stripe_size;u16 oncs;u16 abort_limit;u8 vwc;u8 initialized;}; 在nvme_dev结构中,最最重要的数据就是nvme_queue,struct nvme_queue⽤来表⽰⼀个nvme的queue,每⼀个nvme_queue会申请⾃⼰的中断,也有⾃⼰的中断处理函数,也就是每个nvme_queue在驱动层⾯是完全独⽴的.nvme_queue有两种,⼀种是admin queue,⼀种是io queue,这两种queue都⽤struct nvme_queue来描述,⽽这两种queue的区别如下:admin queue: ⽤于发送控制命令的queue,所有⾮io命令都会通过此queue发送给nvme设备,⼀个nvme设备只有⼀个admin queue,在nvme_dev中,使⽤queues[0]来描述.io queue: ⽤于发送io命令的queue,所有io命令都是通过此queue发送给nvme设备,简单来说读/写操作都是通过io queue发送给nvme设备的,⼀个nvme设备有⼀个或多个io queue,每个io queue的中断会绑定到不同的⼀个或多个CPU上.在nvme_dev中,使⽤queues[1~N]来描述. 以上说的io命令和⾮io命令都是nvme命令,⽐如快层下发⼀个写request,nvme驱动就会根据此request构造出⼀个写命令,将这个写命令放⼊某个io queue中,当controller完成了这个写命令后,会通过此io queue的中断返回完成信息,驱动再将此完成信息返回给块层.明⽩了两种队列的作⽤,我们看看具体的数据结构struct nvme_queue/* nvme的命令队列,其中包括sq和cq。

linux-0.11调试教程,mkfs.c源代码分析

linux-0.11调试教程,mkfs.c源代码分析

linux-0.11调试教程,mkfs.c源代码分析(1)下面是mkfs命令的一个例子mkfs /dev/hd6 60000结果:20000个inodes,60000个blocks,第一个数据块块号为638指导思想:不看源代码的话,格式化一个文件系统,应该改变的是:(1),文件系统的超级块信息,需要用户输入的块的总数算出i节点的个数和i节点位图块的个数和逻辑块位图块的个数及第一个数据块的块号。

setup_tables()函数完成这个任务。

(2),建立根目录,需要申请一个数据块,需要申请一个根目录对应的i节点,是文件系统中的第一个节点。

修改根目录对应的i节点在i节点位图中对应的位和根目录所在的块对应的逻辑块位图中的位。

make_root_inode();函数完成这个任务。

(3)源程序里还有第三个任务,就是统计磁盘分区中坏块的数目,并把所有的坏块看成一个坏块文件,这个坏块文件对应第二个节点。

make_bad_inode()函数完成这个任务。

思路分析:main()函数首先取得用户给出的块数放到BLOCKS里。

然后调用setup_tables()函数,setup_tables()函数的作用是根据块数算出i节点的总数既块数的三分之一。

然后算出i节点位图的块数和逻辑块位图的块数,还有第一个数据块的块号。

然后把逻辑块位图块清零(范围是数据区对应的位图既FIRSTZONE之后的块对应的位)和把i节点位图块清零(第一个位没有清零,第一个位对应根节点)。

然后初始化了i节点缓冲区。

最后打印出超级块信息。

区块的数目ZONES既块数BLOCKS为60000,INODES的数目为块数的三分之一既20000。

i节点位图的块数IMAPS为3。

#define NORM_FIRSTZONE (2+IMAPS+ZMAPS+INODE_BLOCKS)NORM_FIRSTZONE数目既数据区前面的块数。

ZMAPS = 0;while (ZMAPS != UPPER(BLOCKS - NORM_FIRSTZONE,BITS_PER_BLOCK))ZMAPS = UPPER(BLOCKS - NORM_FIRSTZONE,BITS_PER_BLOCK);逻辑块位图的块数ZMAPS为8。

uCLinux开发介绍

uCLinux开发介绍

uCLinux开发介绍严永红Linux是当前一种非常受欢迎的操作系统,它与UNIX系统兼容,并开放源代码。

它包含所有现代操作系统所具有的一切特性,包括多任务,虚拟内存,代码共享,按需载入,内存管理,以及TCP/IP网络。

并且,它遵循POSIX标准,只要是遵循POSIX API的应用程序很容易被移植。

目前,随着嵌入式系统的蓬勃发展。

Linux也已对嵌入式系统的开发产生具大影响。

大多数流行的CPU都被移植上去,ARM, PowerPC , MIPS, 68K, SPARC, Alpha, SH 等等. 这些CPU都含有一种叫做内存管理单元(MMU)的硬件,来支持标准Linux所需要的虚拟内存。

但在嵌入式世界里,还有很多CPU是没有MMU的,象ARM7、68328等等。

uClinux 正是为了解决这种没有MMU的CPU而产生的。

在uCLinux这个英文单词中,u表示Micro,小的意思,C表示Control,控制的意思,连起来就是Micro-Control-Linux, ―运行在微控制器上的Linux.‖针对这种没有MMU的CPU架构,uCLinux采用了一种平板式(Flat)的内存模型来去除对MMU的依赖, 并且改变了用户程序的加载方式,开发了运用于uCLinux的C函数库--uCLibc. 由于这些变化,一般的Linux开发工具(例如GDB)在开发uCLinux时会碰到一些困难,包括内核的移植,驱动程序及应用程序的调试。

针对这样状况。

Hitool System公司开发了Hitool for uClinux开发套件,来帮助用户开发基于uClinux的系统。

Hitool for uClinux与其它的Linux开发工具相比,有几个优点:A.整个开发过程只在Windows环境下完成,包括内核的配臵、编译,应用程序的编译,文件系统的生成,内核的调试,用户程序的调试。

B.可以采用多种调试方式,既可以采用JTAG方式来调试,也可通过网口用Hitool自己的监控程序(MDB)来调试。

用Source Insight打开linux内核源代码

用Source Insight打开linux内核源代码

用Source Insight打开linux内核源代码2008-01-09 19:06Linux的内核源代码可以从很多途径得到。

一般来讲,在安装的linux系统下,/usr/src/linux目录下的东西就是内核源代码。

另外还可以从互连网上下载,解压缩后文件一般也都位于linux目录下。

内核源代码有很多版本,目前最新的稳定版是2.2.14。

许多人对于阅读Linux内核有一种恐惧感,其实大可不必。

当然,象Linux内核这样大而复杂的系统代码,阅读起来确实有很多困难,但是也不象想象的那么高不可攀。

只要有恒心,困难都是可以克服的。

也不用担心水平不够的问题,事实上,有很多事情我们不都是从不会到会,边干边学的吗?任何事情做起来都需要有方法和工具。

正确的方法可以指导工作,良好的工具可以事半功倍。

对于Linux 内核源代码的阅读也同样如此。

下面我就把自己阅读内核源代码的一点经验介绍一下,最后介绍Window平台下的一种阅读工具。

对于源代码的阅读,要想比较顺利,事先最好对源代码的知识背景有一定的了解。

对于linux内核源代码来讲,我认为,基本要求是:1、操作系统的基本知识;2、对C语言比较熟悉,最好要有汇编语言的知识和GNU C对标准C的扩展的知识的了解。

另外在阅读之前,还应该知道Linux内核源代码的整体分布情况。

我们知道现代的操作系统一般由进程管理、内存管理、文件系统、驱动程序、网络等组成。

看一下Linux内核源代码就可看出,各个目录大致对应了这些方面。

Linux内核源代码的组成如下(假设相对于linux目录):arch 这个子目录包含了此核心源代码所支持的硬件体系结构相关的核心代码。

如对于X86平台就是i386。

include 这个目录包括了核心的大多数include文件。

另外对于每种支持的体系结构分别有一个子目录。

init 此目录包含核心启动代码。

mm 此目录包含了所有的内存管理代码。

与具体硬件体系结构相关的内存管理代码位于arch/*/mm目录下,如对应于X86的就是arch/i386/mm/fault.c 。

读书摘要观后感与总结:《Glibc内存管理:ptmalloc2源代码分析》

读书摘要观后感与总结:《Glibc内存管理:ptmalloc2源代码分析》

读书摘要观后感与总结:《Glibc内存管理:ptmalloc2源代码分析》更新中在Linux平台下做漏洞利⽤的时候,针对于Heap部分总是有些不求甚解,下⾯开个博⽂来记录下《Glibc内存管理:ptmalloc2源代码分析》这本书的读后感和收获,⼀些简单的点将不再记录说明,本博⽂中所有的实验均在Linux Ubuntu16.04的环境下进⾏⽬录树:⼀些关于计算size的宏"chunk to mem" and "mem to chunk"about size分箱式内存管理smallbinslargebins⼀些关于计算size的宏Ptmalloc设计的时候很巧妙的⼀点就是利⽤宏来屏蔽不同平台的差异,⼀些简单的细节⽐如chunk的形式在此我就不再赘述,下⾯记录⼀下读后有收获的点"chunk to mem" and "mem to chunk"/* conversion from malloc headers to user pointers, and back */#define chunk2mem(p) ((void*)((char*)(p) + 2*SIZE_SZ))#define mem2chunk(mem) ((mchunkptr)((char*)(mem) - 2*SIZE_SZ))about sizeMIN_CHUNK_SIZE定义了最⼩的chunk⼤⼩,MINSIZE定义了最⼩的分配的内存⼤⼩,是对MIN_CHUNK_SIZE进⾏了2*SIZE_SZ对齐,对齐后与MIN_CHUNK_SIZE的⼤⼩仍然是⼀样的/* The smallest possible chunk */#define MIN_CHUNK_SIZE (offsetof(struct malloc_chunk, fd_nextsize))/* The smallest size we can malloc is an aligned minimal chunk */#define MINSIZE \(unsigned long)(((MIN_CHUNK_SIZE+MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK))下⾯说明⼀下chunk是如何计算其size的/* size field is or'ed with PREV_INUSE when previous adjacent chunk in use */#define PREV_INUSE 0x1/* extract inuse bit of previous chunk */#define prev_inuse(p) ((p)->mchunk_size & PREV_INUSE)/* size field is or'ed with IS_MMAPPED if the chunk was obtained with mmap() */#define IS_MMAPPED 0x2/* check for mmap()'ed chunk */#define chunk_is_mmapped(p) ((p)->mchunk_size & IS_MMAPPED)/* size field is or'ed with NON_MAIN_ARENA if the chunk was obtainedfrom a non-main arena. This is only set immediately before handingthe chunk to the user, if necessary. */#define NON_MAIN_ARENA 0x4#define SIZE_BITS (PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)/* Like chunksize, but do not mask SIZE_BITS. */#define chunksize_nomask(p) ((p)->mchunk_size)/* Get size, ignoring use bits */#define chunksize(p) (chunksize_nomask (p) & ~(SIZE_BITS))/* Ptr to next physical malloc_chunk. */#define next_chunk(p) ((mchunkptr) (((char *) (p)) + chunksize (p)))/* Size of the chunk below P. Only valid if !prev_inuse (P). */#define prev_size(p) ((p)->mchunk_prev_size)⽐如做个实验来验证下,我们的chunksize为0x71,那么它本⾝的真实size是如何计算的?根据宏定义来计算可以看到计算得出的结果显然正确下⾯这⼀组宏定义⽤来check/set/clear当前chunk使⽤标志位,有当前chunk的使⽤标志位存储在下⼀个chunk的size的P位,所以下⾯的宏都要⾸先算出来下⼀个chunk的地址然后再做处理/* extract p's inuse bit */#define inuse(p) \((((mchunkptr) (((char *) (p)) + chunksize (p)))->mchunk_size) & PREV_INUSE)/* set/clear chunk as being inuse without otherwise disturbing */#define set_inuse(p) \((mchunkptr) (((char *) (p)) + chunksize (p)))->mchunk_size |= PREV_INUSE#define clear_inuse(p) \((mchunkptr) (((char *) (p)) + chunksize (p)))->mchunk_size &= ~(PREV_INUSE)我们可以简单来实验⼀下define inuse(p) 定义p的inusedefine set_inuse(p) 设置p的inuse位(p的nextchuhnk来设置)define clear_inuse(p) 清理p的inuse位下⾯三个宏⽤来check/set/clear指定chunk的size域中的使⽤标志位/* check/set/clear inuse bits in known places */#define inuse_bit_at_offset(p, s) \(((mchunkptr) (((char *) (p)) + (s)))->mchunk_size & PREV_INUSE)#define set_inuse_bit_at_offset(p, s) \(((mchunkptr) (((char *) (p)) + (s)))->mchunk_size |= PREV_INUSE)#define clear_inuse_bit_at_offset(p, s) \(((mchunkptr) (((char *) (p)) + (s)))->mchunk_size &= ~(PREV_INUSE))分箱式内存管理smallbinssmallbins有64个bin,实际共62个bin,bin[0]和bin[1]不存在chunk_size = 2 * SIZE_SZ * index范围:16B-504B (32B-1008B)ptmalloc维护了62个双向环形链表,每个链表都有头节点,便于管理,每个链表内各个空闲的chunk的⼤⼩⼀致largebins32:⼤于等于512B64:⼤于等于1024B⼀共63个bins每个bin中的chunk⼤⼩不是⼀个固定公差的等差数列,⽽是分成6组bin,每组bin是⼀个固定公差的等差数列每组的bin数量依次为:32,16, 8, 4, 2, 1公差依次为: 64,512,4096,32768,262144可以⽤数学来描述计算largebins的chunk_size第⼀组:chunksize = 512 + 64 * index第⼆组:chunksize = 512 + 64 * 32 + 512 * index……可以看到,其实smallbins和largebins差不多满⾜同样的规律,所以可以将small bins和large bins放在同⼀个包含128个chunk的数组上,数组前⼀部分为small bins,后⼀部分为large bins。

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
/ / 指向 v ma 段 AVL 树的指针 struct semaphor mmap_sem;
struct vm_area_struct* vm_pr ev_share; struct vm_operation_struct* vm_ops;
/ / vm_o ps 规定 了可 对 V M A 段实 施的操作。
unsigned long vm_offset ; / / VM A 段相对于文件或共享内存 的偏移量。
/ / 进程未初 始化 的数 据段 的起 始地 址 和结束地址
unsig ned lo ng star t_stack, start_mmap; unsig ned lo ng arg_start, arg_end;
/ / 调用参数区的起始地址和 结束地址 unsig ned lo ng env_start, env_end;
/ / 进程环境区的起始地址和 结束地址 unsig ned lo ng rss, total_vm, locked_v m;
/ / rss 是进程内容驻留 在物理 内存的 页 面总数
unsig ned lo ng def_flags; struct v m_ar ea_struct* mmap;
/ / 指向 v ma 段的双向链表的指针 struct v m_ar ea_struct* mmap_av l;
L inux 操作系统 是一种能运行于多种平台、源 代码公开、免费、功能强大、与 Unix 兼容的操 作系 统。自其诞生以来, 发展非常迅速, 在我国也受到政 府、企业、科研单位、大专院校的重视。我们自 2000 年开始对 L inux 源代码( 版本号是 Linux 2 2 16) 进 行分析, 首先剖析了进程管理和存储管理部分, 本文 是有关存储管理的一部分。主要介绍了 Linux 虚存 管理所用到的数据结构及其相互间的关系, 据此可 以更好地理解其存储管理机制, 也可以在此基础上 对其进行改进或在此后的研究中提供借鉴作用。作 为一种功能强大的操作系统, Linux 实现了 以虚拟 内存为主的内存管理机制。即能够克服物理内存的 局限, 使用户进程在透明方式下, 拥有比实际物理内 存大得多的内存。本文主要阐述了 L inux 虚存管理 的基本特点和主要实现技术, 并分析了 L inux 虚存 管理的主要数据结构及其相互关系。
第3期
王艳春: L inux 源代码分析 存储管理
31
程中实现的。进程执行时每用到一个地址, 地址转 换机构都要把虚拟地址转化为内存的实际地址。动
态地址映射使 L inux 可以实现进程在主存中的动态 重定位。虚存段的动态扩展和移动, 也为虚存的实 现提供了基础。
2 Linux 虚存管理数据结构
1) mem_m ap Linux 系统中的物 理内存由 mem_map 表描 述
mm h) , 每一个 mem_map_t 描 述系统的一个 关于
内核态、用户态代码和数据的物理页面, 其定义如 下:
typedef struct pag e {
/ / 在 include/ linux / mm h 中:
struct page* nex t, * prev; / / 由于搜索算法 的约定, 这 两项 必须首先定义
struct inode* v m_inode; / / 指向 VM A 所在文件的 inode 结 构。 若 不 涉 及 文 件, 则 为 N U LL 。
unsigned long vm_pte; / / 用于 共享内 存, 含 SHM_SWP_ T Y PE 和共享内存段 id 号
};
图 4 虚拟内存数据结构示意图
1 Linux 虚存管理概述
Linux 的内存管理采用虚拟页式管理, 使用多 级页表, 动态地址变换。进程在运行过程中可以动 态浮动和扩展, 为用户提供了透明的、灵活有效的 内存使用方式。
1) 32 bit 虚拟地址 在 L inux 中, 进程的 4GB 虚存需通过 32 bit 地 址进行寻址。L inux 中虚拟地址与线性地址为同一 概念, 虚拟地址被分成 3 个子位段, 而大小为 4k, 如图 1 所示。 2) L inux 的多级页表结构
/ / 页帧描述表的首地 址
2) free_area
L inux 采用位示图 ( bitm ap 表) 的方式记录所 有物 理 内 存 的 使 用 状 况。 与 mem_map 一 样,
bit map 表在系统初始化时由 f ree_area_init ( ) 函数
创建 ( 见 mm/ page_alloc. c) 。空闲 的物理页 帧用
4) mm_st ruct 进程的虚拟这间由 mm_st ruct 描述, 该数据结 构包含当前执行程序的映象信息 ( 用户进程中与存 储有关的信息) , 以及一些指向 vm_area_st ruct 结构 的指针, 参见图 5。
图 5 用户进程虚存管理数据结构 struct mm_struct { int count; pg d_t* pgd;
struct inode* ino de;
/ / 若该页帧的内 容是文件, 则 inode 和
unsigned long offset;
struct page* next_hash;
/ / offset 指出文件的 inode 和 偏移 位置
/ / pag e cache 的 hash 表中,
链表 的后继指针
指示地址
Hale Waihona Puke unsig ned lo ng swap_unloch_entry;
unsig ned olng map_nr;
/ / 页 帧 在 mem_map 表 中
的下 标, page map_nr
= = page_mem_map
} mem_map_t;
mem_map_t * mem_map= N U LL ;
/ / 此结构的 nex t、prev 指 针与 struct page 匹配
struct page* prev; unsig ned int * map;
/ / 指向 bitmap } static struct free_ar ea_struct free_area [ N R_M EM_ L IST S] ;
/ / 页帧 的年龄, 越 小越 先换
出 struct wait_queue* wait ;
/ / 等待队列指针 struct page* prev_hash;
/ / page_cach 的 hsh 表 中, 链 表的前向指针
struct buffer_head* buffers;
/ / 若该页帧作为 缓冲区, 则
3) 页表项的格式
图 2 Linux 中页目录项和页表项格式
4) 动态地址映射 L inux 虚存采用动态地址映射方式, 即进程的 地址空间和存储空间的对应关系是在程序的执行过
收稿日期: 2003- 05- 10 作者简介: 王艳春, 女 ( 1964 ) , 副教授, 主要从事操作系统、中文信息处理等方面的研究工作。
Linux 源代码分析 存储管理
王艳春 陈 毓 葛明霞
( 长春理工大学 计算机科学技术学院, 吉林 长春 130022)
摘 要: 本文剖析了 L inux 操作系统的存储管理机制。给出了 L inux 存储管理的特点、虚存的实 现方法, 以及主要数据结构之间的关系。 关键词: L inux 操作系统; 存储管理; 虚拟存储 中图分类号: T P316 81 文献标识码: A
f ree_area 数组记录。该数组由 NR- M EM - LIST S 个 f ree_area_st ruct 结构类型的数组元素构成 ( 见图
3) 。每个元素作为一条空闲链表的表头。
图 3 bitmap 表及其与 free_area 的关系 stuct free_ar ea_struct { struct page* next;
struct vm_area_struct* vm_avl_right ; struct vm_area_struct* vm_nex t;
/ / 链接 每个任 务的 V M A 区, 按 地址分类
struct vm_aea_str uct* vm_nex t_shar ; / / VM A 为共 享区 时所使 用的 前、 后向指针。
/ / 进程页目录的起始地址 unsig ned lo ng star t_code. end_code;
/ / 进程代码段的起始地址和 结束地址 unsig ned lo ng star t_data, end_data;
/ / 进程数据段的起始地址和 结束地址 unsig ned lo ng star t_brk, brk;
struct mm_str uct* vm_mm; / / V M A area parameters
32
长春理工大学学报
2003 年
unsigned long vm_start; / / V M A 描 述的 虚 存 段始 于 vm_ start, 终于 vm_end
unsigned long vm_end; pgprot_t vm_page_rot;
( 见 mm \ memory c) , 是一个 mem_mp_t 类型的队
列, 该队列在系统初始化时由核心 f ree_area_init ( )
创建和初始 化 ( 见 mm \ page_alloc c) , 它本身 是 关于 st ruct page mem_map_t 的 数 组 ( 见 linux \
图 1 32 位虚拟地址
标准的 L inux 的虚存页表为三级页表, 依次为页目 录( Pag e Direct ory PGD) 、中间页目录( Pag e Middle Direct ory P MD ) 、页 表 ( Page T able PT E ) 。 在 i386 机器 上 Linux 的 页表结构实 际为两级, PGD 和 PMD 页表是合二为一的。所有有关 PMD 的操作关际上 是对 PGD 的操作。所以源代码中形如* _pgd_* ( ) 和* _ pmd_* ( ) 函数实现的功能也是一样的。
相关文档
最新文档