📄 6.html
字号:
·arg_start、arg_end:调用参数区的起始地址和结束地址。<br>·env_start、env_end:进程环境区的起始地址和结束地址。<br>·rss:进程内容驻留在物理内存的页面总数。<p><br>2. 虚存段(vma)数据结构:vm_area_atruct<br> 虚存段vma由数据结构vm_area_atruct(include/linux/mm.h)描述:<p>struct vm_area_struct {<br> struct mm_struct * vm_mm; /* VM area parameters */<br> unsigned long vm_start;<br> unsigned long vm_end;<br> pgprot_t vm_page_prot;<br> unsigned short vm_flags;<br>/* AVL tree of VM areas per task, sorted by address */<br> short vm_avl_height;<br> struct vm_area_struct * vm_avl_left;<br> struct vm_area_struct * vm_avl_right;<br>/* linked list of VM areas per task, sorted by address */<br> struct vm_area_struct * vm_next;<br>/* for areas with inode, the circular list inode->i_mmap */<br>/* for shm areas, the circular list of attaches */<br>/* otherwise unused */<br> struct vm_area_struct * vm_next_share;<br> struct vm_area_struct * vm_prev_share;<br>/* more */<br> struct vm_operations_struct * vm_ops;<br> unsigned long vm_offset;<br> struct inode * vm_inode;<br> unsigned long vm_pte; /* shared mem */<br>};<p>vm_start;//所对应内存区域的开始地址<br>vm_end; //所对应内存区域的结束地址<br>vm_flags; //进程对所对应内存区域的访问权限<br>vm_avl_height;//avl树的高度<br>vm_avl_left; //avl树的左儿子<br>vm_avl_right; //avl树的右儿子<br>vm_next;// 进程所使用的按地址排序的vm_area链表指针<br>vm_ops;//一组对内存的操作<br> 这些对内存的操作是当对虚存进行操作的时候Linux系统必须使用的一组方法。比如说,当进程准备访问某一虚存区域但是发现此区域在物理内存不存在时(缺页中断),就激发某种对内存的操作执行正确的行为。这种操作是空页(nopage)操作。当Linux系统按需调度可执行的页面映象进入内存时就使用这种空页(nopage)操作。<br> 当一个可执行的页面映象映射到进程的虚存地址时,一组vm_area_struct结构的数据结构(vma)就会生成。每一个vm_area_struct的数据结构(vma)代表可执行的页面映象的一部分:可执行代码,初始化数据(变量),非初始化数据等等。Linux系统可以支持大量的标准虚存操作,当vm_area_struct数据结构(vma)一被创建,它就对应于一组正确的虚存操作。<br> 属于同一进程的vma段通过vm_next指针连接,组成链表。如图2-3所示,struct mm_struct结构的成员struct vm_area_struct * mmap 表示进程的vma链表的表头。<br> 为了提高对vma段 查询、插入、删除操作的速度,LINUX同时维护了一个AVL(Adelson-Velskii and Landis)树。在树中,所有的vm_area_struct虚存段均有左指针vm_avl_left指向相邻的低地址虚存段,右指针vm_avl_right指向相邻的高地址虚存段,如图2-5。struct mm_struct结构的成员struct vm_area_struct * mmap_avl表示进程的AVL树的根,vm_avl_height表示AVL树的高度。<br> 对平衡树mmap_avl的任何操作必须满足平衡树的一些规则:<br>Consistency and balancing rulesJ(一致性和平衡规则):<p>tree->vm_avl_height==1+max(heightof(tree->vm_avl_left),heightof(<br>tree->vm_avl_right))<br>abs( heightof(tree->vm_avl_left) - heightof(tree->vm_avl_right) ) <= 1<br>foreach node in tree->vm_avl_left: node->vm_avl_key <= tree->vm_avl_key, foreach node in tree->vm_avl_right: node->vm_avl_key >= tree->vm_avl_key.<br> 注:其中node->vm_avl_key= node->vm_end<p>对vma可以进行加锁、加保护、共享和动态扩展等操作。<p><br><center><A HREF="#Content">[目录]</A></center><hr><br><A NAME="I457" ID="I457"></A><center><b><font size=+2>重要常量</font></b></center><br> mlock系统调用所用到的重要常量有:PAGE_MASK、PAGE_SIZE、PAGE_SHIFT、RLIMIT_MEMLOCK、VM_LOCKED、 PF_SUPERPRIV等。它们的值分别如下:<p> PAGE_SHIFT 12 // PAGE_SHIFT determines the page size<br> PAGE_SIZE 0x1000 //1UL<<PAGE_SHIFT<br> PAGE_MASK ~(PAGE_SIZE-1) //a very useful constant variable<br> RLIMIT_MEMLOCK 8 //max locked-in-memory address space<br> VM_LOCKED 0x2000 //8*1024=8192, vm_flags的标志之一。<br> PF_SUPERPRIV 0x00000100 //512<p><center><A HREF="#Content">[目录]</A></center><hr><br><A NAME="I458" ID="I458"></A><center><b><font size=+2>代码函数功能分析</font></b></center><br>mlock系统调用代码函数功能分析<p> 下面对各个函数的功能作详细的分析((1)和(2)在前面简介mlock时已介绍过,并在后面有详细的程序流程):<p>suser():如果用户有效(即current->euid == 0 ),则设置进程标志为root优先权(current->flags |= PF_SUPERPRIV),并返回1;否则返回0。<p>find_vma(struct mm_struct * mm, unsigned long addr):输入参数为当前进程的mm、需要加锁的开始内存地址addr。find_vma的功能是在mm的mmap_avl树中寻找第一个满足mm->mmap_avl->vm_start<=addr< mm->mmap_avl->vm_end的vma,如果成功则返回此vma;否则返回空null。<p>mlock_fixup(struct vm_area_struct * vma, unsigned long start, unsigned long end, unsigned int newflags):输入参数为vm_mmap链中的某个vma、需要加锁内存区域起始地址和结束地址、需要修改的标志(0:加锁,1:释放锁)。<p>merge_segments(struct mm_struct * mm, unsigned long start_addr, unsigned long end_addr):输入参数为当前进程的mm、需要加锁的开始内存地址start_addr和结束地址end_addr。merge_segments的功能的是尽最大可能归并相邻(即内存地址偏移量连续)并有相同属性(包括vm_inode,vm_pte,vm_ops,vm_flags)的内存段,在这过程中冗余的vm_area_structs被释放,这就要求vm_mmap链按地址大小排序(我们不需要遍历整个表,而只需要遍历那些交叉或者相隔一定连续区域的邻接vm_area_structs)。当然在缺省的情况下,merge_segments是对vm_mmap_avl树进行循环处理,有多少可以合并的段就合并多少。<p>mlock_fixup_all(struct vm_area_struct * vma, int newflags):输入参数为vm_mmap链中的某个vma、需要修改的标志(0:加锁,1:释放锁)。mlock_fixup_all的功能是根据输入参数newflags修改此vma的vm_flags。<p>mlock_fixup_start(struct vm_area_struct * vma,unsigned long end, int newflags):输入参数为vm_mmap链中的某个vma、需要加锁内存区域结束地址、需要修改的标志(0:加锁,1:释放锁)。mlock_fixup_start的功能是根据输入参数end,在内存中分配一个新的new_vma,把原来的vma分成两个部分: new_vma和vma,其中new_vma的vm_flags被设置成输入参数newflags;并且按地址(new_vma->start和new_vma->end)大小序列把新生成的new->vma插入到当前进程mm的mmap链或mmap_avl树中(缺省情况下是插入到mmap_avl树中)。<br> 注:vma->vm_offset+= vma->vm_start-new_vma->vm_start;<br>mlock_fixup_end(struct vm_area_struct * vma,unsigned long start, int newflags):输入参数为vm_mmap链中的某个vma、需要加锁内存区域起始地址、需要修改的标志(0:加锁,1:释放锁)。mlock_fixup_end的功能是根据输入参数start,在内存中分配一个新的new_vma,把原来的vma分成两个部分:vma和new_vma,其中new_vma的vm_flags被设置成输入参数newflags;并且按地址大小序列把new->vma插入到当前进程mm的mmap链或mmap_avl树中。<br> 注:new_vma->vm_offset= vma->vm_offset+(new_vma->vm_start-vma->vm_start);<br>mlock_fixup_middle(struct vm_area_struct * vma,unsigned long start, unsigned long end, int newflags):输入参数为vm_mmap链中的某个vma、需要加锁内存区域起始地址和结束地址、需要修改的标志(0:加锁,1:释放锁)。mlock_fixup_middle的功能是根据输入参数start、end,在内存中分配两个新vma,把原来的vma分成三个部分:left_vma、vma和right_vma,其中vma的vm_flags被设置成输入参数newflags;并且按地址大小序列把left->vma和right->vma插入到当前进程mm的mmap链或mmap_avl树中。<br> 注:vma->vm_offset += vma->vm_start-left_vma->vm_start;<br> right_vma->vm_offset += right_vma->vm_start-left_vma->vm_start;<p>kmalloc():常用的一个内核函数<p>insert_vm_struct(struct mm_struct *mm, struct vm_area_struct *vmp):输入参数为当前进程的mm、需要插入的vmp。insert_vm_struct的功能是按地址大小序列把vmp插入到当前进程mm的mmap链或mmap_avl树中,并且把vmp插入到vmp->inode的i_mmap环(循环共享链)中。<p>avl_insert_neighbours(struct vm_area_struct * new_node,** ptree,** to_the_left,** to_the_right):输入参数为当前需要插入的新vma结点new_node、目标mmap_avl树ptree、新结点插入ptree后它左边的结点以及它右边的结点(左右边结点按mmap_avl中各vma->vma_end大小排序)。avl_insert_neighbours的功能是插入新vma结点new_node到目标mmap_avl树ptree中,并且调用avl_rebalance以保持ptree的平衡树特性,最后返回new_node左边的结点以及它右边的结点。<p>avl_rebalance(struct vm_area_struct *** nodeplaces_ptr, int count):输入参数为指向vm_area_struct指针结构的指针数据nodeplaces_ptr[](每个元素表示需要平衡的mmap_avl子树)、数据元素个数count。avl_rebalance的功能是从nodeplaces_ptr[--count]开始直到nodeplaces_ptr[0]循环平衡各个mmap_avl子树,最终使整个mmap_avl树平衡。<p>down(struct semaphore * sem):输入参数为同步(进入临界区)信号量sem。down的功能根据当前信号量的设置情况加锁(阻止别的进程进入临界区)并继续执行或进入等待状态(等待别的进程执行完成退出临界区并释放锁)。<br> down定义在/include/linux/sched.h中:<br>extern inline void down(struct semaphore * sem)<br>{<br> if (sem->count <= 0)<br> __down(sem);<br> sem->count--;<br>}<p>up(struct semaphore * sem)输入参数为同步(进入临界区)信号量sem。up的功能根据当前信号量的设置情况(当信号量的值为负数:表示有某个进程在等待使用此临界区 )释放锁。<br> up定义在/include/linux/sched.h中:<br>extern inline void up(struct semaphore * sem)<br>{<br> sem->count++;<br> wake_up(&sem->wait);<br> }<p>kfree_s(a,b):kfree_s定义在/include/linux/malloc.h中:#define kfree_s(a,b) kfree(a)。而kfree()将在后面3.3中详细讨论。<p>avl_neighbours(struct vm_area_struct * node,* tree,** to_the_left,** to_the_right):输入参数为作为查找条件的vma结点node、目标mmap_avl树tree、node左边的结点以及它右边的结点(左右边结点按mmap_avl中各vma->vma_end大小排序)。avl_ neighbours的功能是根据查找条件node在目标mmap_avl树ptree中找到node左边的结点以及它右边的结点,并返回。<p>avl_remove(struct vm_area_struct * node_to_delete, ** ptree):输入参数为需要删除的结点node_to_delete和目标mmap_avl树ptree。avl_remove的功能是在目标mmap_avl树ptree中找到结点node_to_delete并把它从平衡树中删除,并且调用avl_rebalance以保持ptree的平衡树特性。<p>remove_shared_vm_struct(struct vm_area_struct *mpnt):输入参数为需要从inode->immap环中删除的vma结点mpnt。remove_shared_vm_struct的功能是从拥有vma结点mpnt 的inode->immap环中删除的该结点。<p><br><center><A HREF="#Content">[目录]</A></center><hr><br><A NAME="I459" ID="I459"></A><center><b><font size=+2>添加新调用</font></b></center><br><center><A HREF="#Content">[目录]</A></center><hr><br><A NAME="I460" ID="I460"></A><center><b><font size=+2>例子一</font></b></center><br>深入LINUX内核:为你的LINUX增加一条系统调用<br> 充分利用LINUX开放源码的特性,我们可以轻易地对它进行修改,使我们能够随心所欲驾驭LINUX,完成一个真正属于自己的操作系统,这种感觉使无与伦比的,下面通过为LINUX增加一个系统调用来展示LINUX作为一个开放源码操作系统的强大魅力。<br> 首先,让我们简单地分析一下LINUX中与系统调用的相关的部分:<br> LINUX的系统调用的总控程序是system_call,它是LINUX系统中所有系统调用的总入口,这个system_call是作为一个中断服务程序挂在中断0x80上,系统初始化时通过void init trap_init(void)调用一个宏set_system_ gate(SYSCALL_VERCTOR,&system_call)来对IDT表进行初始化,在0x80对应的中断描述符处填入system_call函数的地址,其中宏SYSCALL_VERCTOR就是0x80。<br> 当发生一条系统调用时,由中断总控程序保存处理机状态,检查调用参数的合法性,然后根据系统调用向量在sys_call_table中找到相应的系统服务例程的地址,然后执行该服务例程,完成后恢复中断总控程序所保存的处理机状态,返回用户程序。<br> 系统服务例程一般定义于kernel/sys.c中,系统调用向量定义在include/asm-386/unistd.h中,而sys_call _table表则定义在arch/i386/kernel/entry.S文件里。<br> 现在我们知道增加一条系统调用我们首先要添加服务例程实现代码,然后在进行对应向量的申明,最后当然还要在sys_call_table表中增加一项以指明服务例程的入口地址。<br> OK,有了以上简单的分析,现在我们可以开始进行源码的修改,假设我们需要添加一条系统调用计算两个整数的平方和,系统调用名为add2,我们需要修改三个文件:kernel/sys.c , arch/i386/kernel/entry.S 和 include/asm-386/unistd.h。<br> 1、修改kernel/sys.c ,增加服务例程代码:<br> asmlinkage int sys_add2(int a , int b)<br> {<br> int c=0;<br> c=a*a+b*b;<br> return c;<br> }<br> 2、修改include/asm-386/unistd.h ,对我们刚才增加的系统调用申明向量,以使用户或系统进程能够找到这条系统调用,修改后文件如下所示:<br> .... .....<br> #define _NR_sendfile 187<br> #define _NR_getpmsg 188<br> #define _NR_putmsg 189<br> #define _NR_vfork 190<br> #define _NR_add2 191 /* 这是我们添加的部分,191即向量 */<br> 3、修改include/asm-386/unistd.h , 将服务函数入口地址加入 sys_call_table,首先找到这么一段:<br> .... .....<br> .long SYMBOL_NAME(sys_sendfile)<br> .long SYMBOL_NAME(sys_ni_syscall) /* streams 1 */<br> .long SYMBOL_NAME(sys_ni_syscall) /* streams 2 */<br>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -