Linux内核upload.ppt_第1页
Linux内核upload.ppt_第2页
Linux内核upload.ppt_第3页
Linux内核upload.ppt_第4页
Linux内核upload.ppt_第5页
已阅读5页,还剩154页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、Linux内核分析,Linux内核结构,Linux内核结构,Linux内核主要由五个子系统组成: 进程调度 内存管理 虚拟文件系统 网络接口 进程间通信,五个子系统组成,进程调度(SCHED) 控制进程对CPU的访问。当需要选择下一个进程运行时,由调度程序选择最值得运行的进程。可运行进程实际上是仅等待CPU资源的进程,如果某个进程在等待其它资源,则该进程是不可运行进程。Linux使用了比较简单的基于优先级的进程调度算法选择新的进程。,五个子系统组成,内存管理(MM) 允许多个进程安全的共享主内存区域。Linux 的内存管理支持虚拟内存。操作系统负责在磁盘和内存间交换程序块。内存管理从逻辑上分为

2、硬件无关部分和硬件有关部分。硬件无关部分提供了进程的映射和逻辑内存的对换;硬件相关的部分为内存管理硬件提供了虚拟接口。,五个子系统组成,虚拟文件系统(Virtual File System)隐藏了各种硬件的具体细节,为所有的设备提供了统一的接口,VFS提供了多达数十种不同的文件系统。虚拟文件系统可以分为逻辑文件系统和设备驱动程序。逻辑文件系统指Linux所支持的文件系统,如ext2,fat等,设备驱动程序指为每一种硬件控制器所编写的设备驱动程序模块。,五个子系统组成,网络接口(NET) 提供了对各种网络标准的存取和各种网络硬件的支持。网络接口可分为网络协议和网络驱动程序。网络协议部分负责实现每

3、一种可能的网络传输协议。网络设备驱动程序负责与硬件设备通讯,每一种可能的硬件设备都有相应的设备驱动程序。,五个子系统组成,进程间通讯(IPC) : 支持进程间各种通信机制。,Linux 内核源代码的结构,Linux 内核源代码的结构,Linux内核源代码位于/usr/src/linux目录下。 /include 子目录包含了建立内核代码时所需的大部分包含文件,这个模块利用其他模块重建内核。/init 子目录包含了内核的初始化代码,这是内核工作的开始的起点。/arch 子目录包含了所有硬件结构特定的内核代码。如:i386,alpha/drivers 子目录包含了内核中所有的设备驱动程序,如块设备

4、和SCSI设备。,Linux 内核源代码的结构,/fs 子目录包含了所有的文件系统的代码。如:ext2,vfat等。 /net 子目录包含了内核的连网代码。/mm 子目录包含了所有内存管理代码。/ipc 子目录包含了进程间通信代码。/kernel 子目录包含了主内核代码。,从何处开始阅读源代码 ?,从何处开始阅读源代码 ?,在Internet,有人制作了源代码导航器,为阅读源代码提供了良好的条件,站点为lxr.linux.no/source。 下面给出阅读源代码的线索:,从何处开始阅读源代码 ?,系统的启动和初始化:在基于Intel的系统上,当loadlin.exe或LILO把内核装入到内存并

5、把控制权传递给内核时,内核开始启动。关于这一部分请看,arch/i386/kernel/head.S head.S进行特定结构的设置,然后跳转到 init/main.c 的 main() 例程。,从何处开始阅读源代码 ?,内存管理:内存管理的代码主要在/mm,但是特定结构的代码在arch/*/mm。 缺页中断处理的代码在/mm/memory.c ,而内存映射和页高速缓存器的代码在/mm/filemap.c 。 缓冲器高速缓存是在/mm/buffer.c 中实现,而交换高速缓存是在mm/swap_state.c和mm/swapfile.c,从何处开始阅读源代码 ?,内核: 内核中,特定结构的代码

6、在arch/*/kernel, 调度程序在kernel/sched.c, fork的代码在kernel/fork.c, 内核例程处理程序在include/linux/interrupt.h task_struct数据结构在inlucde/linux/sched.h中。,从何处开始阅读源代码 ?,PCI:PCI伪驱动程序在drivers/pci/pci.c,其定义在inclulde/linux/pci.h。 每一种结构都有一些特定的PCI BIOS代码,Intel的在arch/alpha/kernel/bios32.c中。,从何处开始阅读源代码 ?,进程间通信:所有的SystemV IPC对象权

7、限都包含在ipc_perm数据结构中,这可以在include/linux/ipc.h中找到。 SystemV消息是在ipc/msg.c中实现。共享内存在ipc/shm.c中实现。信号量在ipc/sem.c中,管道在/ipc/pipe.c中实现。,从何处开始阅读源代码 ?,中断处理:内核的中断处理代码几乎所有的微处理器特有的。中断处理代码在arch/i386/kernel/irq.c中,其定义在include/asm-i386/irq.h中。,内核开发特点,内核开发的特点,没有libc库: stdio.h: printf(“Hello world!”); linux/string.h: prin

8、tk (“Hello world!”);,内核开发的特点,必须使用GNU C: a. 内联函数(inline) static inline void dog(unsigned long tail_size) b. 内联汇编 c. 分支声明,内核开发的特点,必须使用GNU C: c. 分支声明: if (foo) if ( unlikely(foo) ) /foo绝大多数时候为0 if ( likely(foo) ) /foo通常不为0,内核开发的特点,没有内存保护机制。 另外内核中的内存不分页 不要轻易再内核中使用浮点数。 内核会有从整数模式到浮点模式转换的开销 容积小而固定的栈。 内核栈不能

9、动态增长,32位机的内核栈仅8k,内核开发的特点,同步和并发: 多处理器的并发处理 可抢占的内核 可移植性的重要,进程管理,进程管理,进程表示 进程管理 进程创建 进程销毁,进程表示,在 Linux 内核内,进程是由相当大的一个称为 task_struct 的结构表示的。此结构包含所有表示此进程所必需的数据,此外,还包含了大量的其他数据用来统计(accounting)和维护与其他进程的关系(父和子)。下面给出 task_struct 的一小部分。这些代码包含了本文所要探索的这些特定元素。task_struct 位于 ./linux/include/linux/sched.h。,struct t

10、ask_struct volatile long state; void *stack; unsigned int flags; int prio, static_prio; struct list_head tasks; struct mm_struct *mm, *active_mm; pid_t pid; pid_t tgid; struct task_struct *real_parent; char commTASK_COMM_LEN; struct thread_struct thread; struct files_struct *files; . ;,state :是一些表明任

11、务状态的比特位 flags :定义了很多指示符,表明进程是否正在被创建(PF_STARTING)或退出(PF_EXITING),或是进程当前是否在分配内存(PF_MEMALLOC) tasks:提供了链接列表的能力。它包含一个 prev 指针(指向前一个任务)和一个 next 指针(指向下一个任务) thread_struct :用来标识进程的存储状态,最大进程数,在 Linux 内虽然进程都是动态分配的,但还是需要考虑最大进程数。在内核内最大进程数是由一个称为 max_threads 的符号表示的,它可以在 ./linux/kernel/fork.c 内找到。,进程管理,在很多情况下,进程都

12、是动态创建并由一个动态分配的 task_struct 表示。一个例外是 init 进程本身,它总是存在并由一个静态分配的 task_struct 表示,进程的分配方式,哈希表(pidhash):由 PID 值进行哈希计算得到 双链循环表 循环表非常适合于对任务列表进行迭代。由于列表是循环的,没有头或尾 但是由于init_task总是存在,所以可以将其用作继续向前迭代的一个锚点,哈希表pidhash在include/linux/sched.h中定义,#define PIDHASH_SZ (4096 2) extern struct task_struct *pidhashPIDHASH_SZ;

13、#define pid_hashfn(x) (x) 8) (x) ; #define LIST_HEAD_INIT(name) pos = pos-next),使用include/linux/sched.h中的find_task_pid() 函数,可以用给定的PID值快速查找任务,static inline struct task_struct *find_task_by_pid(int pid) struct task_struct *p, *htable = ,结构体:task_struct,在include/linux/sched.h中定义 状态字段:,volatile long sta

14、te; /* -1 unrunnable, 0 runnable, 0 stopped */ #define TASK_RUNNING 0 #define TASK_INTERRUPTIBLE 1 #define TASK_UNINTERRUPTIBLE 2 #define TASK_ZOMBIE 4 #define TASK_STOPPED 8 #define TASK_EXCLUSIVE 32,p-state中定义的volatile表明变量是否能异步修改 TASK_RUNNING: 任务可能要进入运行队列 TASK_INTERRUPTIBLE: 任务正处于睡眠但是可以通过中断信号或计时器到

15、时而唤醒 TASK_UNINTERRUPTIBLE: 与TASK_INTERRUPTIBLE相同,但不能被唤醒 TASK_ZOMBIE: 任务被终止,但是没有收集状态信息 TASK_STOPPED: 任务由控制信号或ptrace(2)终止 TASK_EXCLUSIVE: 可以是TASK_INTERRUPTIBLE or TASK_UNINTERRUPTIBLE中的一个或两个. 也就是说,如果任务在一个有很多其他任务的队列中并处于睡眠状态,这个变量会防止“thundering herd ”问题,防止其他等待中的任务都被唤醒。,标志位,unsigned long flags; /* per pro

16、cess flags, defined below */ /* * 每个进程的标志位 */ #define PF_STARTING 0 x00000002 /* 正在创建进程*/ #define PF_EXITING 0 x00000004 /* 正在推出进程*/ #define PF_FORKNOEXEC 0 x00000040 /* 已经取出,但未运行*/ #define PF_SUPERPRIV 0 x00000100 /* 使用超级用户权限*/ #define PF_DUMPCORE 0 x00000200 /* dumped core */ #define PF_SIGNALED 0

17、 x00000400 /* killed by a signal */ #define PF_MEMALLOC 0 x00000800 /* 分配内存*/ #define PF_VFORK 0 x00001000 /* 在mm_release时释放父进程 */ #define PF_USEDFPU 0 x00100000 /* task used FPU this quantum (SMP) */,进程创建,用户空间任务和内核任务的底层机制是一致的,因为二者最终都会依赖于一个名为 do_fork 的函数来创建新进程。 在创建内核线程时,内核会调用一个名为 kernel_thread 的函数(.

18、/linux/arch/i386/kernel/process.c),此函数执行某些初始化后会调用 do_fork 创建用户空间进程的情况与此类似。在用户空间,一个程序会调用fork,这会导致对名为sys_fork的内核函数的系统调用( ./linux/arch/i386/kernel/process.c),负责创建进程的函数的层次结构,do_fork 是进程创建的基础。可以在 ./linux/kernel/fork.c 内找到 do_fork 函数(以及合作函数 copy_process) do_fork函数首先调用alloc_pidmap,该调用会分配一个新的 PID do_fork检查调

19、试器是否在跟踪父进程。如果是,在clone_flags内设置CLONE_PTRACE标志以做好执行 fork 操作的准备 do_fork函数还会调用copy_process,向其传递这些标志、堆栈、注册表、父进程以及最新分配的 PID,新的进程在 copy_process 函数内作为父进程的一个副本创建 调用dup_task_struct函数(在 ./linux/kernel/fork.c 内),这会分配一个新task_struct并将当前进程的描述符复制到其内。在新的线程堆栈设置好后,一些状态信息也会被初始化,并且会将控制返回给copy_process 控制回到copy_process后,除

20、了其他几个限制和安全检查之外,还会执行一些常规管理,包括在新task_struct上的各种初始化。 调用一系列复制函数来复制此进程的各个方面,比如复制开放文件描述符(copy_files)、复制符号信息(copy_sighand和copy_signal)、复制进程内存(copy_mm)以及最终复制线程(copy_thread),这个新任务会被指定给一个处理程序,同时对允许执行进程的处理程序进行额外的检查(cpus_allowed) 新进程的优先级从父进程的优先级继承后,执行一小部分额外的常规管理,而且控制也会被返回给do_fork。在此时,新进程存在但尚未运行 do_fork函数通过调用wak

21、e_up_new_task来修复此问题。此函数(可在 ./linux/kernel/sched.c 内找到)初始化某些调度程序的常规管理信息,将新进程放置在运行队列之内,然后将其唤醒以便执行 一旦返回至do_fork,此 PID 值即被返回给调用程序,进程完成,进程销毁,程销毁可以通过几个事件驱动 通过正常的进程结束、通过信号或是通过对 exit 函数的调用。不管进程如何退出,进程的结束都要借助对内核函数 do_exit( ./linux/kernel/exit.c )的调用,实现进程销毁的函数的层次结构,do_exit的目的是将所有对当前进程的引用从操作系统删除(针对所有没有共享的资源) 通

22、过设置PF_EXITING标志来表明进程正在退出 内核的其他方面会利用它来避免在进程被删除时还试图处理此进程。将进程从它在其生命期间获得的各种资源分离开来是通过一系列调用实现的 do_exit函数执行释放进程所需的各种统计 通过调用exit_notify执行一系列通知 进程状态被更改为PF_DEAD,并且还会调用schedule函数来选择一个将要执行的新进程,虚拟文件系统(VFS),虚拟文件系统,文件系统在整个 Linux 的内核中具有举足轻重的地位,代码量也很复杂繁琐。但是因为其重要的地位,要想对 Linux 的内核有比较深入的理解,必须要能越过文件系统这一关,VFS 概述,Linux 下的

23、文件系统为三大块: 一是上层的文件系统的系统调用, 二是虚拟文件系统 VFS(Virtual Filesystem System), 三是挂载到 VFS 中的各实际文件系统,例如 ext2,jffs,VFS 概述,VFS 概述,每次系统初始化期间,Linux 都首先要在内存当中构造一棵 VFS 的目录树(在 Linux 的源代码里称之为 namespace),实际上便是在内存中建立相应的数据结构。 VFS 目录树在 Linux 的文件系统模块中是个很重要的概念,希望读者不要将其与实际文件系统目录树混淆,,VFS 概述,图 1:VFS 目录树结构,文件系统的注册,这里的文件系统是指可能会被挂载到

24、目录树中的各个实际文件系统 每种文件系统用以下的数据结构表示: (见文件include/linux/fs.h),文件系统的注册,注册过程实际上将表示各实际文件系统的 struct file_system_type 数据结构的实例化,然后形成一个链表,内核中用一个名为 file_systems 的全局变量来指向该链表的表头。 下面介绍rootfs文件系统的注册,注册 rootfs 文件系统,如果说 ext2/ext3 是 Linux 的本土文件系统,那么 rootfs 文件系统则是 VFS 存在的基础 一般文件系统的注册都是通过 module_init 宏(include/linux/init.

25、h)以及 do_initcalls() 函数(init/main.c) rootfs 的注册却是通过 init_rootfs() 这一初始化函数来完成(linux/fs/ramfs/inode.c) ,这意味着 rootfs 的注册过程是 Linux 内核初始化阶段不可分割的一部分。,注册 rootfs 文件系统,init_rootfs() 通过调用 register_filesystem( 对应于刚才的例子,即使用:sys_mount(“hda2”,“/dev ”,“ext2”,);,在 VFS 树中挂载文件系统,sys_mount在将这些来自用户内存空间(user space)的参数拷贝到

26、内核空间后,便调用 do_mount() 函数开始真正的安装文件系统的工作 do_mount() 函数会根据调用参数 flags 来决定调用以下四个函数之一(include/linux/fs.h):do_remount()、 do_loopback()、do_move_mount()、do_add_mount()。,在 VFS 树中挂载文件系统,调用 do_add_mount() 函数来向 VFS 树中安装点 安装一个实际的文件系统 调用do_move_mount()函数把一个已安装的设备可以移到另一个安装点 调用do_loopback()来实现回接设备的安装 改变一个原已安装设备的安装方式,

27、例如从“只读“安装方式改为“可写”安装方式,这是通过调用do_remount()函数完成的。,在 VFS 树中挂载文件系统,umount()系统调用使用服务routing sys_umount()来卸载一个文件系统.其核心函数是do_umount().,索引节点(inode)缓存及其与数据缓存的关系,为了支持多种文件系统,Linux包含了一种特殊等级的内核接口VFS,这和SVR4中的vnode/vfsIn类似 Linux 索引节点缓存由fs/inode.c实现,这个文件直到现在都没有发生多大变化,Linux inode 缓存的结构:,struct inode /索引节点结构 struct in

28、ode_operations *i_op; /*索引节点操作表*/ struct file_operations *i_fop; /*该索引节点对应文件的文件操作集*/ struct super_block *i_sb; /*相关的超级块*/ ; struct inode_operations /索引节点方法 /该函数为dentry对象所对应的文件创建一个新的索引节点,主要是由open()系统调用来调用 int (*create) (struct inode *,struct dentry *,int, struct nameidata *); /在特定目录中寻找dentry对象所对应的索引节

29、点 struct dentry * (*lookup) (struct inode *,struct dentry *, struct nameidata *); ;,内存管理,内存管理,动态存储器 Slab算法 非连续存储区,内存管理,RAM的某些部分永久地分配给内核,用以存放内核代码以及静态数据 RAM的其余部分称为动态存储器(dynamic memory),动态存储器,进程和内核都需要动态存储器 属于稀缺资源 整个系统的性能取决于如何有效地管理动态存储器 对于动态存储器要尽可能做到: 按需分配,不需要时释放,页框管理,Linux采用页作为内存管理的基本单位 Linux采用的标准的页框大小

30、为4KB 4KB是大多数磁盘块大小的倍数 传输效率高 管理方便 不考虑PSE,PAE 例如:512M的物理内存对应于128K个页框 算法:伙伴算法,请求页框,内核实现了一种底层的内存分配机制,并提供了几个接口供其他内核函数调用。 分配: alloc_pages/alloc_page _get_free_pages/_get_free_page/_get_dma_pages/get_zeroed_page 释放 free_pages/_free_pages/free_page_free_page,页框数据结构,内核必须记录每个页框当前的状态 哪些属于进程,哪些存放了内核代码/数据 是否空闲,即是

31、否可用 如果不可用,内核需要知道是谁在用这个页框 这个页框可能的使用者有用户态进程、动态分配的内核数据结构、静态的内核代码、页面cache、设备驱动程序缓冲的数据等等,页描述符,页描述符:struct page 每个物理页框都用一个页描述符表示 count:页的使用引用计数器 0:空闲 0:页已经分配给一个或多个进程或用户某些内核数据结构 flags:页框状态,最多可以有32个,每个使用一个位表示 参见枚举类型pageflags,当内核调用一个页框分配函数时,必须指明请求页框所在的区。这个一般是通过一些flag标志来指定的,GFP_XXX,关于NUMA,不考虑 物理内存被划分为若干个node

32、存取时间不等 考虑CPU局部性 Node使用数据结构pg_data_t描述 每个node被划分成若干个zone,存储区(Memory Zones),在一个理想的体系结构中,一个页框就是一个物理存储单元,可以用于任何事情,例如 存放内核数据/用户数据/缓存磁盘数据等 实际上存在硬件制约:一些页框由于自身的物理地址的原因不能被一些任务所使用,例如 ISA总线的DMA控制器只能对ram的前16M寻址 在一些具有大容量ram的32位计算机中,CPU不能直接访问所有的物理存储器,因为线性地址空间不够,zone,为了应付这种限制,Linux把具有同样性质的物理内存划分成区(zones) Linux把物理存

33、储器划分为4个区 ZONE_DMA ZONE_DMA32 (未见用) ZONE_NORMAL ZONE_HIGHMEM 参见枚举类型zone_type,ZONE_DMA 和ZONE_NORMAL区包含存储器的“常规”页,通过把它们映射到线性地址空间的3GB以上,内核就可直接访问 而ZONE_HIGHMEN区中包含的存储器页面不能由内核直接访问 每个zone使用struct zone表示 空闲内存管理的关键:free_area,mem_map数组,所有物理页框的描述符,组织在mem_map的数组中,mem_map的定义和初始化 start_kernelsetup_archsetup_memory

34、setup_bootmem_allocatorinit_bootmeminit_bootmem_core 页描述符将会占用很大的一段空间 Mem_map、node、zone之间的关系,请求页框,内核实现了一种底层的内存分配机制,并提供了几个接口供其他内核函数调用。 分配: alloc_pages/alloc_page/alloc_pages_node/alloc_pages_current/ _get_free_pages/_get_free_page/_get_dma_pages/get_zeroed_page 释放 free_pages/_free_pages/free_page_free

35、_page,关于unsigned int gfp_mask,指明可在何处并以何种方式查找空闲的页框 GFP_ATOMIC,这种分配是高优先级的并且不能睡眠。一般在中断处理程序,下半部分和其他不能睡眠的场合下使用 GFP_KERNEL,这是普通的分配模式,允许睡眠。一般在用户进程可能调用到的内核函数中使用,这个时候进程是可以安全的睡眠的 GFP_DMA,设备驱动程序需要DMA内存时使用,在内核中释放页框时要非常小心,必须确保只释放了所请求的页框,否则内核可能会崩溃,page = _get_free_page(GFP_KERNEL,3); If (!page) /*如果内存不足,分配失败,必须在这

36、里处理这个失败*/ /*现在page变量指向了8个连续页框的起始线性地址*/ free_pages(page,3); /*现在页框被释放,不应该再对page中存放的线性地址进行操作*/,页框管理算法,内核要为分配一组连续的页框建立一种稳定、高效的分配策略 这种策略要解决碎片问题:即频繁的请求和释放不同大小的一组连续页框,必然导致在物理页框中分散许多小块的空闲页框 这样,即使有足够的空闲页框页框满足请求,但要分配一个大块的连续页框可能就无法满足了,有两种办法可以避免这样的碎片 利用MMU把一组非连续的物理空闲页框映射到连续的线性地址空间 使用一种适当的技术来记录现存的空闲连续页框的情况,以尽量避

37、免为满足对小块的请求而把大块的空闲块进行分割 基于下面的原因,Linux内核首选第二种方法 在某些情况下,必须使用连续的页框,如DMA 尽量少的修改内核页表,buddy算法(伙伴算法),Linux使用著名的伙伴算法来解决碎片问题。 把所有空闲页框分组为11个块链表,每个块链表分别包含大小为1,2,4,8,16,32,64,128,256,512,1024个连续的页框 每个块的第一个页框的物理地址是该块大小的整数倍 例如:大小为16个页框的块,其起址是164KB的倍数,伙伴的定义,例如:0和1是伙伴,1和2不是伙伴 两个伙伴的大小必须相同,物理地址必须连续 假定伙伴的大小为b 那么第一个伙伴的物

38、理地址必须是2b4KB对齐 事实上伙伴是通过对大块的物理内存划分获得的 假如从第0个页面开始到第3个页面结束的内存 每次都对半划分,那么第一次划分获得大小为2页的伙伴 进一步划分,可以获得大小为1页的伙伴,例如0和1,2和3,0,1,2,3,0,1,2,3,数据结构,Linux为每个zone使用各自独立的伙伴系统 每个伙伴系统使用的主要数据结构为: 空闲内存管理数组free_area,mem_map数组 前面介绍过的页描述符数组 每个页描述符描述一个物理页框 整个mem_map数组描述整个zone中的所有的物理内存 空闲内存管理数组 空闲内存按照伙伴管理的方法进行组织 使用free_area结

39、构,伙伴,当两个伙伴都为空闲的时候,就合并成一个更大的块 该过程将一直进行,直到找不到可以合并的伙伴为止 寻找伙伴 给定一个要释放的空闲块 找到其伙伴 查看其状态:合并 or 不合并,举例,假设有128MB的ram。 128MB最多可以分成215=32768个页框,214=16384个8KB(2页)的块或213=8192个16KB(4页)的块,直至64个大小为512个页的块 假设要请求一个大小为128个页框的块(0.5MB)。 算法先free_area7中检查是否有空闲块(块大小为128个页框) 若没有,就到free_area8中找一个空闲块(块大小为256个页框) 若存在这样的块,内核就把2

40、56个页框分成两等份,一半用作满足请求,另一半插入free_area7中 如果在free_area8中也没有空闲块,就继续找free_area9中是否有空闲块。,若有,先将512分成伙伴,一个插入free_area8中,另一个进一步划分成伙伴,取其一插入free_area7中,另一个分配出去 如果free_area9也没有空闲块,内存不够,返回一个错误信号,内存的分配与回收,阅读相关代码 关键:nr_free _free_one_page _rmqueue,buffered_rmqueue,get_page_from_freelist,_alloc_pages_internal,_alloc_

41、pages,free_one_page,_free_pages_ok,_free_pages,页框管理小结,Mem_map Zone Free_area 伙伴算法,内存区的管理(memory area),内核中大量使用各种数据结构,大小从几个字节到几十上百k不等,都取整到2的幂次个页面那是完全不现实的 早期内核的解决方法是提供几何分布的(小)内存区域,大小为32,.,131056字节的内存区域 需要新的内存区域时,内核从伙伴系统申请页框,把它们划分成一个个区域,取一个来满足需求 如果某个页框中的内存区域都释放了,页框就交回到伙伴系统,但这种分配方法有许多值得改进的地方: 不同的数据类型用不同的

42、方法分配内存可能提高效率。比如需要初始化的数据结构,释放后可以暂存着,再分配时就不必初始化了 内核的函数常常重复地使用同一类型的内存区,缓存最近释放的对象可以加速分配和释放 对内存的请求可以按照请求频率来分类,频繁使用的类型使用专门的缓存,很少使用的可以使用通用缓存 使用2的幂次大小的内存区域时硬件高速缓存冲突的概率较大,有可能通过仔细安排内存区域的起始地址来减少硬件高速缓存冲突 缓存一定数量的对象可以减少对buddy系统的调用,从而节省时间并减少由此引起的硬件高速缓存污染,Linux2.6.26中的内存区管理,SLOB Allocator: Simple List Of Blocks NUM

43、A Slab Slub:slab的一个变种 Kmalloc/kfree kmem_cache_create/kmem_cache_destroy/kmem_cache_alloc/kmem_cache_free 本课介绍基本的slab算法,slab分配器,最早用于Sun公司的Solaris 2.4 slab分配器体现了这些改进思想 slab分配器把内存区看成对象 slab分配器把对象分组放进高速缓存。每个高速缓存都是同种类型内存对象的一种“储备” 例如当一个文件被打开时,存放相应“打开文件”对象所需的内存是从一个叫做filp(file pointer)的slab分配器的高速缓存中得到的 也就是

44、说每种对象类型对应一个高速缓存,Slab分配器的组成,每个高速缓存被分成多个slabs,每个slab由一个或多个连续的页框组成,其中包含一定数目的对象(空闲的/已分配出去的),数据结构:slab,高速缓存描述符和slab描述符之间的关系,参见数据结构:kmem_list3,每个slab有三种状态:全满,半满,全空 全满意味着slab中的对象全部已被分配出去 全空意味着slab中的对象全部是可用的 半满介于两者之间 当内核函数需要一个新的对象时, 优先从半满的slab满足这个请求 否则从全空的slab中取一个对象满足请求 如果没有空的slab则向buddy系统申请页面生成一个新的slab,关于外

45、置/内置slab描述符,一个slab的slab描述符,可能是外置/内置的 根据slab中缓存的大小来决定。 一个slab描述符的初始化参见alloc_slabmgmt,参见kmem_cache_create,Slab描述区的大小,Slab描述区的大小 slab描述符N个缓冲区描述符,slab中空闲队列的组织,缓冲区描述符列表主要用来维护空闲队列。 缓冲区对象描述符只有在对应缓冲区对象空闲时才有效 表示下一个空闲的缓冲区对象的序号。通过这种方法形成一个空闲链表。 最后一个空闲对象对应的描述符中填写BUFCTL_END。 从slab中获得一个空闲的缓冲区对象 slab_get_obj 释放一个缓冲

46、区对象到slab中: slab_put_obj,给高速缓存分配slab,一个新创建的高速缓存没有包含任何slab,也没有空闲对象。 当下列两个条件都为真时,才给高速缓存分配slab 已发出一个分配新对象的请求 高速缓存中不包含任何空闲对象 cache_grow,从高速缓存中释放slab,在两种条件下才能撤销slab Slab高速缓存中有太多的对象 被周期性调用的定时器函数确定是否有完全未使用的slab能被释放 slab_destroy,普通和专用高速缓存,第一个普通高速缓存:cache_cache,名称为“kmem_cache” 普通高速缓存根据大小分配内存,“几何”高速缓存 26个,2组(一

47、组用于DMA分配,另一组用于常规分配) 每组13个,大小从25=32个字节,到217=132017个字节 数据结构cache_sizes;数组:malloc_sizes 函数kmem_find_general_cachep用来根据大小来找到合适的普通几何高速缓存 专用高速缓存根据数据类型分配,用户直接调用函数kmem_cache_create创建,关于13是一个大概的数目, 实际上根据配置的不同而有所不同,slab分配器和伙伴系统的接口,slab分配器调用kmem_getpages()来获取一组连续的空闲页框 static void *kmem_getpages(struct kmem_cac

48、he *cachep, gfp_t flags, int nodeid) 相应的,有kmem_freepages()来释放分配给slab分配器的页框 static void kmem_freepages(struct kmem_cache *cachep, void *addr),页框中的cache和slab指针,当一个页框属于一个slab的时候,这个页框的lru的prev指向的就是这个页框所属的slab。 page_set_slab和page_get_slab 当一个页框属于一个cache的时候,这个页框的lru的next指向的就是这个页框所属的slab。 page_set_cache和pa

49、ge_get_cache Slab中页框块中页框信息的初始化: slab_map_pages,空闲slab对象的本地高速缓存,为避免处理器在分配和释放slab缓冲区对象时对相关数据结构的竞争 Slab缓冲区对象的大多数分配和释放仅仅影响本地数组,只有在本地数组下溢或上溢时,才涉及slab数据结构 kmem_cache中的array数组和数据结构array_cache 从本地高速缓存中分配_cache_alloc 填充本地高速缓存并分配:cache_alloc_refill 释放缓冲区对象到本地高速缓存:_cache_free,slab分配器提供的接口,专用高速缓存 创建专用高速缓存:kmem_

50、cache_create 撤销专用高速缓存:kmem_cache_destroy 一般内核撤销一个模块时会调用这个函数撤销属于那个模块的cache类型 从专用高速缓冲中分配和释放 从高速缓存中分配/释放一个内存对象kmem_cache_alloc/kmem_cache_free 从普通几何高速缓存中分配和释放 kmalloc/kfree 举例说明使用情况,如果编写的内核模块有许多创建和释放数据结构的操作,可以考虑调用前面所述的slab分配器的接口创建一个高速缓存 这样可以大大减少内存的访问时间,Slab高速缓存机制的初始化,kmem_cache_init 首先初始化cache_cache 然后

51、调用kmem_cache_create初始化两个特殊的几何高速缓存 适合数据结构arraycache_init和数据结构kmem_list3的几何高速缓存 此时slab_early_init结束,该值被设置为0,表示以后要根据实际情况来判断是否要外置slab描述区 接下来初始化剩余的几何高速缓存 并且清除并重置一些早期信息,内存区管理:slab分配器小结,kmem_cache:高速缓存 array_cache:本地高速缓存 kmem_list3:slab链表 slab:slab buffer&kmem_bufctl_t,非连续存储区管理,把线性空间映射到一组连续的页框是很好的选择 有时候不得不

52、将线性空间映射到一组不连续的页框 优点:避免碎片,为非连续内存区保留的线性地址空间,VMALLOC_STARTVMALLOC_END,3G以上的线性地址空间有4种用途: 1)直接映射物理内存区域 2)非连续内存区域 3)用于高端内存的永久映射区域 4)固定映射区域,关于VMALLOC_START和VMALLOC_END定义,关于high_memory 的定义,非连续存储区的描述符vm_struct 非连续内存区的标志flags用来标识一个非连续内存区的类型: VM_IOREMAP表示使用ioremap()映射的硬件设备的共享内存 VM_ALLOC表示使用vmalloc()分配得到 VM_MAP

53、表示使用vmap()映射得到 VM_USERMAP标识使用vmalloc_user分配得到的。 VM_VPAGES表示用来存放物理页框数组的内存区间也是一个vm_struct,全局变量vmlist 在vmlist上找到给定地址所在的vm_struct:_find_vm_area 查找一个可用的非连续区内的线性地址区间:get_vm_area 映射/取消映射物理页map_vm_area/unmap_vm_area 为一个非连续区内的地址找到对应的物理页框:vmalloc_to_page Vmalloc等分配一个非连续存储区 Vfree释放非连续线性区间,非连续区小结,非连续区的定义 3G以上线性

54、空间的4种用途 数据结构、分配以及映射的进行,页框管理、内存区管理、非连续存储区管理之间的关系,物理内存:页框管理,以页框为单位进行管理,使用slab算法,以若干 个字节为单位进行管理,分配出 一些页框,直接使用分配到的 页框,例如进程描述符,使用分配到缓冲区,分配出 一些页框,非连续存储区,映射 到非连续的物理页框上,使用分配到线性区,进程间通信,Linux下进程间通信的主要手段,Linux下的进程通信手段基本上是从Unix平台上的进程通信手段继承而来的。而AT&T的贝尔实验室及BSD(加州大学伯克利分校的伯克利软件发布中心)在进程间通信方面的侧重点有所不同。前者对Unix早期的进程间通信手

55、段进行了系统的改进和扩充,形成了“system V IPC”,通信进程局限在单个计算机内;后者则跳过了该限制,形成了基于套接字(socket)的进程间通信机制。Linux则把两者都继承了下来。 Linux下进程间通信的几种主要手段包括: (1)管道(pipe)及有名管道(named pipe):管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信;,(2)信号(signal):信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身;Linux除了支持Unix早期

56、信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数sigaction(实际上,该函数是基于BSD的,BSD为了实现可靠信号机制,又能够统一对外接口,用sigaction函数重新实现了signal函数); (3)报文(message)队列(消息队列):消息队列是消息的链接表,包括Posix消息队列system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。 (4)共享内存:使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运

57、行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。,(5)信号量(semaphore):主要作为进程间以及同一进程不同线程之间的同步手段。 (6)套接字(socket):更为一般的进程间通信机制,可用于不同机器之间的进程间通信。起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其它类Unix系统上:Linux和System V的变种都支持套接字。,1 信号,信号是Unix 系统中最古老的进程间通讯机制之一,它主要用来向进程发送异步的事件信号。键盘中断可能产生信号,而浮点运算溢出或者内存访问错误等也可产生信号。shell 通常利用信号向子进程发送作

58、业控制命令。 在 Linux 中,信号种类的数目和具体的平台有关,因为内核用一个字代表所有的信号,因此字的位数就是信号种类的最多数目。对 32 位的 i386 平台而言,一个字为 32 位,因此信号有 32 种。Linux 内核定义的最常见的信号、C 语言宏名及其用途如表5.5 所示:,进程可以选择对某种信号所采取的特定操作,这些操作包括: (1) 忽略信号。进程可忽略产生的信号,但 SIGKILL 和 SIGSTOP 信号不能被忽略。 (2) 阻塞信号。进程可选择阻塞某些信号。 (3) 由进程处理该信号。进程本身可在系统中注册处理信号的处理程序地址,当发出该信号时,由注册的处理程序处理信号。

59、,(4) 由内核进行默认处理。信号由内核的默认处理程序处理。大多数情况下,信号由内核处理。 需要注意的是,Linux 内核中不存在任何机制用来区分不同信号的优先级。也就是说,当同时有多个信号发出时,进程可能会以任意顺序接收到信号并进行处理。另外,如果进程在处理某个信号之前,又有相同的信号发出,则进程只能接收到一个信号。产生上述现象的原因与内核对信号的实现有关,将在下面解释。,系统在 task_struct 结构中利用两个字分别记录当前挂起的信号(signal)以及当前阻塞的信号(blocked)。挂起的信号指尚未进行处理的信号。阻塞的信号指进程当前不处理的信号,如果产生了某个当前被阻塞的信号,则该信号会一直保持挂起,直到该信号不再被阻塞为止。除了 SIGKILL 和 SIGSTOP 信号外,所有的信号均可以被阻塞,信号的阻塞可通过系统调用实现。每个进程的 task_struct 结构中还包含了一个指向 sigaction 结构数组的指针,该结构数组中的信息实际指定了进程处理所有信号的方式。如果某个 sigaction 结构中包含有处理信

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论