📄 6.html
字号:
<center><A HREF="#Content">[目录]</A></center>
<hr><br><A NAME="I451" ID="I451"></A><center><b><font size=+2>系统调用表</font></b></center><br>
系统调用表<br>
系统调用表sys_call_table (arch/i386/kernel/entry.S)形如:<p>
ENTRY(sys_call_table)<br>
.long SYMBOL_NAME(sys_setup) /* 0 */<br>
.long SYMBOL_NAME(sys_exit)<br>
.long SYMBOL_NAME(sys_fork)<br>
… …<br>
.long SYMBOL_NAME(sys_stime) /* 25 */<br>
.long SYMBOL_NAME(sys_ptrace)<br>
… …<p>
sys_call_table记录了各sys_name函数(共166项,其中2项无效)在表中的位子。有了这张表,很容易根据特定系统调用在表中的偏移量,找到对应的系统调用响应函数的入口地址。NR_syscalls(即256)表示最多可容纳的系统调用个数。这样,余下的90项就是可供用户自己添加的系统调用空间。<p>
<p>
<center><A HREF="#Content">[目录]</A></center>
<hr><br><A NAME="I452" ID="I452"></A><center><b><font size=+2>从ptrace系统调用命令到INT 0X80中断请求的转换</font></b></center><br>
从ptrace系统调用命令到INT 0X80中断请求的转换<br>
宏定义syscallN()(include/asm/unistd.h)用于系统调用的格式转换和参数的传递。<p>
#define _syscall4(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4) \<br>
type name (type1 arg1, type2 arg2, type3 arg3, type4 arg4) \<br>
{ \<br>
long __res; \<br>
__asm__ volatile ("int $0x80" \<br>
: "=a" (__res) \<br>
: "0" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2)), \<br>
"d" ((long)(arg3)),"S" ((long)(arg4))); \<br>
__syscall_return(type,__res); \<br>
}<p>
N取0与5之间任意整数。参数个数为N的系统调用由syscallN负责格式转换和参数传递。例如,ptrace()有四个参数,它对应的格式转换宏就是syscall4()。<p>
syscallN()第一个参数说明响应函数返回值的类型,第二个参数为系统调用的名称(即name),其余的参数依次为系统调用参数的类型和名称。例如,<p>
_syscall4(int, ptrace, long request, long pid, long addr, long data)<p>
说明了系统调用命令<p>
int sys_ptrace(long request, long pid, long addr, long data)<p>
宏定义的余下部分描述了启动INT 0X80和接收、判断返回值的过程。也就是说,以系统调用号对EAX寄存器赋值,启动INT 0X80。规定返回值送EAX寄存器。函数的参数压栈,压栈顺序见下表:<br>
参数 参数在堆栈的位置 传递参数的寄存器<p>
arg1 00(%esp) ebx<br>
arg2 04(%esp) ecx<br>
arg3 08(%esp) edx<br>
arg4 0c(%esp) esi<br>
arg5 10(%esp) edi<p>
若INT 0X80的返回值非负,则直接按类型type返回;否则,将INT 0X80的返回值取绝对值,保留在errno变量中,返回-1。<p>
<p>
<center><A HREF="#Content">[目录]</A></center>
<hr><br><A NAME="I453" ID="I453"></A><center><b><font size=+2>系统调用功能模块的初始化</font></b></center><br>
系统调用功能模块的初始化<br>
对系统调用的初始化也即对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)就是所有系统调用的总入口。<p>
<br>
<center><A HREF="#Content">[目录]</A></center>
<hr><br><A NAME="I454" ID="I454"></A><center><b><font size=+2>内核服务</font></b></center><br>
LINUX内部是如何分别为各种系统调用服务的<br>
当进程需要进行系统调用时,必须以C语言函数的形式写一句系统调用命令。当进程执行到用户程序的系统调用命令时,实际上执行了由宏命令_syscallN()展开的函数。系统调用的参数由各通用寄存器传递。然后执行INT 0X80,以核心态进入入口地址system_call。<br>
ENTRY(system_call)<br>
pushl %eax # save orig_eax<br>
SAVE_ALL<br>
#ifdef __SMP__<br>
ENTER_KERNEL<br>
#endif<br>
movl $-ENOSYS,EAX(%esp)<br>
cmpl $(NR_syscalls),%eax<br>
jae ret_from_sys_call<br>
movl SYMBOL_NAME(sys_call_table)(,%eax,4),%eax<br>
testl %eax,%eax<br>
je ret_from_sys_call<br>
#ifdef __SMP__<br>
GET_PROCESSOR_OFFSET(%edx)<br>
movl SYMBOL_NAME(current_set)(,%edx),%ebx<br>
#else<br>
movl SYMBOL_NAME(current_set),%ebx<br>
#endif<br>
andl $~CF_MASK,EFLAGS(%esp) # clear carry - assume no errors<br>
movl %db6,%edx<br>
movl %edx,dbgreg6(%ebx) # save current hardware debugging status<br>
testb $0x20,flags(%ebx) # PF_TRACESYS<br>
jne 1f<br>
call *%eax<br>
movl %eax,EAX(%esp) # save the return value<br>
jmp ret_from_sys_call<p>
从system_call入口的汇编程序的主要功能是:<br>
·保存寄存器当前值(SAVE_ALL);<br>
·检验是否为合法的系统调用;<br>
·根据系统调用表_sys_call_table和EAX持有的系统调用号找出并转入系统调用响应函数;<br>
·从该响应函数返回后,让EAX寄存器保存函数返回值,跳转至ret_from_sys_call(arch/i386/kernel/entry.S)。<br>
·最后,在执行位于用户程序中系统调用命令后面余下的指令之前,若INT 0X80的返回值非负,则直接按类型type返回;否则,将INT 0X80的返回值取绝对值,保留在errno变量中,返回-1。<p>
<br>
<center><A HREF="#Content">[目录]</A></center>
<hr><br><A NAME="I455" ID="I455"></A><center><b><font size=+2>代码分析:mlock()</font></b></center><br>
系统调用mlock的作用是屏蔽内存中某些用户进程所要求的页。<p>
mlock调用的语法为:<br>
int sys_mlock(unsigned long start, size_t len);<br>
初始化为:<p>
len=(len+(start &~PAGE_MASK)+ ~PAGE_MASK)&PAGE_MASK;<br>
start &=PAGE_MASK;<p>
其中mlock又调用do_mlock(),语法为:<p>
int do_mlock(unsigned long start, size_t len,int on);<p>
初始化为:<p>
len=(len+~PAGE_MASK)&PAGE_MASK;<p>
由mlock的参数可看出,mlock对由start所在页的起始地址开始,长度为len(注:len=(len+(start&~PAGE_MASK)+ ~PAGE_MASK)&PAGE_MASK)的内存区域的页进行加锁。<br>
sys_mlock如果调用成功返回,这其中所有的包含具体内存区域的页必须是常驻内存的,或者说在调用munlock 或 munlockall之前这部分被锁住的页面必须保留在内存。当然,如果调用mlock的进程终止或者调用exec执行其他程序,则这部分被锁住的页面被释放。通过fork()调用所创建的子进程不能够继承由父进程调用mlock锁住的页面。<br>
内存屏蔽主要有两个方面的应用:实时算法和高度机密数据的处理。实时应用要求严格的分时,比如调度,调度页面是程序执行延时的一个主要因素。保密安全软件经常处理关键字节,比如密码或者密钥等数据结构。页面调度的结果是有可能将这些重要字节写到外存(如硬盘)中去。这样一些黑客就有可能在这些安全软件删除这些在内存中的数据后还能访问部分在硬盘中的数据。 而对内存进行加锁完全可以解决上述难题。<br>
内存加锁不使用压栈技术,即那些通过调用mlock或者mlockall被锁住多次的页面可以通过调用一次munlock或者munlockall释放相应的页面<br>
mlock的返回值分析:若调用mlock成功,则返回0;若不成功,则返回-1,并且errno被置位,进程的地址空间保持原来的状态。返回错误代码分析如下:<br>
·ENOMEM:部分具体地址区域没有相应的进程地址空间与之对应或者超出了进程所允许的最大可锁页面。<br>
·EPERM:调用mlock的进程没有正确的优先权。只有root进程才允许锁住要求的页面。<br>
·EINVAL:输入参数len不是个合法的正数。<p>
<p>
<p>
<br>
<center><A HREF="#Content">[目录]</A></center>
<hr><br><A NAME="I456" ID="I456"></A><center><b><font size=+2>主要数据结构</font></b></center><br>
1.mm_struct<br>
struct mm_struct {<br>
int count;<br>
pgd_t * pgd; /* 进程页目录的起始地址*/<br>
unsigned long context;<br>
unsigned long start_code, end_code, start_data, end_data;<br>
unsigned long start_brk, brk, start_stack, start_mmap;<br>
unsigned long arg_start, arg_end, env_start, env_end;<br>
unsigned long rss, total_vm, locked_vm;<br>
unsigned long def_flags;<br>
struct vm_area_struct * mmap; /* 指向vma双向链表的指针 */<br>
struct vm_area_struct * mmap_avl; /* 指向vma AVL树的指针 */<br>
struct semaphore mmap_sem;<br>
}<br>
·start_code、end_code:进程代码段的起始地址和结束地址。<br>
·start_data、end_data:进程数据段的起始地址和结束地址。<br>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -