📄 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 + -