📄 系统调用.txt
字号:
系统调用功能模块的初始化
对系统调用的初始化也即对INT
0X80的初始化。系统启动时,汇编子程序setup_idt(arch/i386/kernel/head.S)准备了张256项的idt
表,由start_kernel()(init/main.c)、trap_init()(arch/i386/kernel/traps.c)调用的C语言宏定义set_system_gate(0x80,
&system_call)(include/asm/system.h)设置0X80号软中断的服务程序为system_call。system_call(arch/i386/kernel/entry.S)就是所有系统调用的总入口。
[目录]
内核服务
LINUX内部是如何分别为各种系统调用服务的
当进程需要进行系统调用时,必须以C语言函数的形式写一句系统调用命令。当进程执行到用户程序的系统调用命令时,实际上执行了由宏命令_syscallN()展开的函数。系统调用的参数由各通用寄存器传递。然后执行INT
0X80,以核心态进入入口地址system_call。
ENTRY(system_call)
pushl %eax # save orig_eax
SAVE_ALL
#ifdef __SMP__
ENTER_KERNEL
#endif
movl $-ENOSYS,EAX(%esp)
cmpl $(NR_syscalls),%eax
jae ret_from_sys_call
movl SYMBOL_NAME(sys_call_table)(,%eax,4),%eax
testl %eax,%eax
je ret_from_sys_call
#ifdef __SMP__
GET_PROCESSOR_OFFSET(%edx)
movl SYMBOL_NAME(current_set)(,%edx),%ebx
#else
movl SYMBOL_NAME(current_set),%ebx
#endif
andl $~CF_MASK,EFLAGS(%esp) # clear carry - assume no errors
movl %db6,%edx
movl %edx,dbgreg6(%ebx) # save current hardware debugging status
testb $0x20,flags(%ebx) # PF_TRACESYS
jne 1f
call *%eax
movl %eax,EAX(%esp) # save the return value
jmp ret_from_sys_call
从system_call入口的汇编程序的主要功能是:
·保存寄存器当前值(SAVE_ALL);
·检验是否为合法的系统调用;
·根据系统调用表_sys_call_table和EAX持有的系统调用号找出并转入系统调用响应函数;
·从该响应函数返回后,让EAX寄存器保存函数返回值,跳转至ret_from_sys_call(arch/i386/kernel/entry.S)。
·最后,在执行位于用户程序中系统调用命令后面余下的指令之前,若INT 0X80的返回值非负,则直接按类型type返回;否则,将INT
0X80的返回值取绝对值,保留在errno变量中,返回-1。
[目录]
代码分析:mlock()
系统调用mlock的作用是屏蔽内存中某些用户进程所要求的页。
mlock调用的语法为:
int sys_mlock(unsigned long start, size_t len);
初始化为:
len=(len+(start &~PAGE_MASK)+ ~PAGE_MASK)&PAGE_MASK;
start &=PAGE_MASK;
其中mlock又调用do_mlock(),语法为:
int do_mlock(unsigned long start, size_t len,int on);
初始化为:
len=(len+~PAGE_MASK)&PAGE_MASK;
由mlock的参数可看出,mlock对由start所在页的起始地址开始,长度为len(注:len=(len+(start&~PAGE_MASK)+
~PAGE_MASK)&PAGE_MASK)的内存区域的页进行加锁。
sys_mlock如果调用成功返回,这其中所有的包含具体内存区域的页必须是常驻内存的,或者说在调用munlock 或
munlockall之前这部分被锁住的页面必须保留在内存。当然,如果调用mlock的进程终止或者调用exec执行其他程序,则这部分被锁住的页面被释放。通过fork()调用所创建的子进程不能够继承由父进程调用mlock锁住的页面。
内存屏蔽主要有两个方面的应用:实时算法和高度机密数据的处理。实时应用要求严格的分时,比如调度,调度页面是程序执行延时的一个主要因素。保密安全软件经常处理关键字节,比如密码或者密钥等数据结构。页面调度的结果是有可能将这些重要字节写到外存(如硬盘)中去。这样一些黑客就有可能在这些安全软件删除这些在内存中的数据后还能访问部分在硬盘中的数据。
而对内存进行加锁完全可以解决上述难题。
内存加锁不使用压栈技术,即那些通过调用mlock或者mlockall被锁住多次的页面可以通过调用一次munlock或者munlockall释放相应的页面
mlock的返回值分析:若调用mlock成功,则返回0;若不成功,则返回-1,并且errno被置位,进程的地址空间保持原来的状态。返回错误代码分析如下:
·ENOMEM:部分具体地址区域没有相应的进程地址空间与之对应或者超出了进程所允许的最大可锁页面。
·EPERM:调用mlock的进程没有正确的优先权。只有root进程才允许锁住要求的页面。
·EINVAL:输入参数len不是个合法的正数。
[目录]
主要数据结构
1.mm_struct
struct mm_struct {
int count;
pgd_t * pgd; /* 进程页目录的起始地址*/
unsigned long context;
unsigned long start_code, end_code, start_data, end_data;
unsigned long start_brk, brk, start_stack, start_mmap;
unsigned long arg_start, arg_end, env_start, env_end;
unsigned long rss, total_vm, locked_vm;
unsigned long def_flags;
struct vm_area_struct * mmap; /* 指向vma双向链表的指针 */
struct vm_area_struct * mmap_avl; /* 指向vma AVL树的指针 */
struct semaphore mmap_sem;
}
·start_code、end_code:进程代码段的起始地址和结束地址。
·start_data、end_data:进程数据段的起始地址和结束地址。
·arg_start、arg_end:调用参数区的起始地址和结束地址。
·env_start、env_end:进程环境区的起始地址和结束地址。
·rss:进程内容驻留在物理内存的页面总数。
2. 虚存段(vma)数据结构:vm_area_atruct
虚存段vma由数据结构vm_area_atruct(include/linux/mm.h)描述:
struct vm_area_struct {
struct mm_struct * vm_mm; /* VM area parameters */
unsigned long vm_start;
unsigned long vm_end;
pgprot_t vm_page_prot;
unsigned short vm_flags;
/* AVL tree of VM areas per task, sorted by address */
short vm_avl_height;
struct vm_area_struct * vm_avl_left;
struct vm_area_struct * vm_avl_right;
/* linked list of VM areas per task, sorted by address */
struct vm_area_struct * vm_next;
/* for areas with inode, the circular list inode->i_mmap */
/* for shm areas, the circular list of attaches */
/* otherwise unused */
struct vm_area_struct * vm_next_share;
struct vm_area_struct * vm_prev_share;
/* more */
struct vm_operations_struct * vm_ops;
unsigned long vm_offset;
struct inode * vm_inode;
unsigned long vm_pte; /* shared mem */
};
vm_start;//所对应内存区域的开始地址
vm_end; //所对应内存区域的结束地址
vm_flags; //进程对所对应内存区域的访问权限
vm_avl_height;//avl树的高度
vm_avl_left; //avl树的左儿子
vm_avl_right; //avl树的右儿子
vm_next;// 进程所使用的按地址排序的vm_area链表指针
vm_ops;//一组对内存的操作
这些对内存的操作是当对虚存进行操作的时候Linux系统必须使用的一组方法。比如说,当进程准备访问某一虚存区域但是发现此区域在物理内存不存在时(缺页中断),就激发某种对内存的操作执行正确的行为。这种操作是空页(nopage)操作。当Linux系统按需调度可执行的页面映象进入内存时就使用这种空页(nopage)操作。
当一个可执行的页面映象映射到进程的虚存地址时,一组vm_area_struct结构的数据结构(vma)就会生成。每一个vm_area_struct的数据结构(vma)代表可执行的页面映象的一部分:可执行代码,初始化数据(变量),非初始化数据等等。Linux系统可以支持大量的标准虚存操作,当vm_area_struct数据结构(vma)一被创建,它就对应于一组正确的虚存操作。
属于同一进程的vma段通过vm_next指针连接,组成链表。如图2-3所示,struct mm_struct结构的成员struct
vm_area_struct * mmap 表示进程的vma链表的表头。
为了提高对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树的高度。
对平衡树mmap_avl的任何操作必须满足平衡树的一些规则:
Consistency and balancing rulesJ(一致性和平衡规则):
tree->vm_avl_height==1+max(heightof(tree->vm_avl_left),heightof(
tree->vm_avl_right))
abs( heightof(tree->vm_avl_left) - heightof(tree->vm_avl_right) ) <= 1
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.
注:其中node->vm_avl_key= node->vm_end
对vma可以进行加锁、加保护、共享和动态扩展等操作。
[目录]
重要常量
mlock系统调用所用到的重要常量有:PAGE_MASK、PAGE_SIZE、PAGE_SHIFT、RLIMIT_MEMLOCK、VM_LOCKED、
PF_SUPERPRIV等。它们的值分别如下:
PAGE_SHIFT 12 //
PAGE_SHIFT determines the page size
PAGE_SIZE 0x1000
//1UL<<PAGE_SHIFT
PAGE_MASK ~(PAGE_SIZE-1) //a very useful
constant variable
RLIMIT_MEMLOCK 8 //max
locked-in-memory address space
VM_LOCKED 0x2000
//8*1024=8192, vm_flags的标志之一。
PF_SUPERPRIV 0x00000100 //512
[目录]
代码函数功能分析
mlock系统调用代码函数功能分析
下面对各个函数的功能作详细的分析((1)和(2)在前面简介mlock时已介绍过,并在后面有详细的程序流程):
suser():如果用户有效(即current->euid == 0 ),则设置进程标志为root优先权(current->flags |=
PF_SUPERPRIV),并返回1;否则返回0。
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。
mlock_fixup(struct vm_area_struct * vma, unsigned long start, unsigned long end,
unsigned int
newflags):输入参数为vm_mmap链中的某个vma、需要加锁内存区域起始地址和结束地址、需要修改的标志(0:加锁,1:释放锁)。
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树进行循环处理,有多少可以合并的段就合并多少。
mlock_fixup_all(struct vm_area_struct * vma, int
newflags):输入参数为vm_mmap链中的某个vma、需要修改的标志(0:加锁,1:释放锁)。mlock_fixup_all的功能是根据输入参数newflags修改此vma的vm_flags。
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树中)。
注:vma->vm_offset+= vma->vm_start-new_vma->vm_start;
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树中。
注:new_vma->vm_offset= vma->vm_offset+(new_vma->vm_start-vma->vm_start);
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树中。
注:vma->vm_offset += vma->vm_start-left_vma->vm_start;
right_vma->vm_offset += right_vma->vm_start-left_vma->vm_start;
kmalloc():常用的一个内核函数
insert_vm_struct(struct mm_struct *mm, struct vm_area_struct
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -