📄 6.html
字号:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"><HTML><HEAD> <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=gb2312"> <META NAME="GENERATOR" CONTENT="《良友》v2.1, 作者:安富国,http://winking.126.com"> <TITLE>系统调用</TITLE></HEAD><BODY style="font-family: 宋体; font-size: 9pt"> <CENTER><TABLE CELLSPACING=10 CELLPADDING=10 WIDTH="60%" BGCOLOR="#FFB693" ><TR><TD ALIGN=CENTER><FONT SIZE=+2><!--标题由此开始-->系统调用</TD></TR></TABLE></CENTER><p><h3>目 录</h3><!--目录由此开始--><A NAME="Content" ID="Content"></A><OL><LI><A HREF="#I443">系统调用</A></LI><OL><LI><A HREF="#I444">系统调用简述</A></LI><OL><LI><A HREF="#I445">宏</A></LI><LI><A HREF="#I446">系统调用表</A></LI><LI><A HREF="#I447">系统调用入口函数</A></LI></OL><LI><A HREF="#I448">系统调用实现过程</A></LI><OL><LI><A HREF="#I449">函数名约定</A></LI><LI><A HREF="#I450">系统调用号</A></LI><LI><A HREF="#I451">系统调用表</A></LI><LI><A HREF="#I452">从ptrace系统调用命令到INT 0X80中断请求的转换</A></LI><LI><A HREF="#I453">系统调用功能模块的初始化</A></LI><LI><A HREF="#I454">内核服务</A></LI></OL><LI><A HREF="#I455">代码分析:mlock()</A></LI><OL><LI><A HREF="#I456">主要数据结构</A></LI><LI><A HREF="#I457">重要常量</A></LI><LI><A HREF="#I458">代码函数功能分析</A></LI></OL><LI><A HREF="#I459">添加新调用</A></LI><OL><LI><A HREF="#I460">例子一</A></LI><LI><A HREF="#I461">例子二</A></LI></OL></OL></OL><hr><br><A NAME="I443" ID="I443"></A><center><b><font size=+2>系统调用</font></b></center><br> 在系统中真正被所有进程都使用的内核通信方式是系统调用。例如当进程请求内核服务时,就使用的是系统调用。一般情况下,进程是不能够存取系统内核的。它不能存取内核使用的内存段,也不能调用内核函数,CPU的硬件结构保证了这一点。只有系统调用是一个例外。进程使用寄存器中适当的值跳转到内核中事先定义好的代码中执行,(当然,这些代码是只读的)。在Intel结构的计算机中,这是由中断0x80实现的。<p> 进程可以跳转到的内核中的位置叫做system_call。在此位置的过程检查系统调用号,它将告诉内核进程请求的服务是什么。然后,它再查找系统调用表sys_call_table,找到希望调用的内核函数的地址,并调用此函数,最后返回。<p> 所以,如果希望改变一个系统调用的函数,需要做的是编写一个自己的函数,然后改变sys_call_table中的指针指向该函数,最后再使用cleanup_module将系统调用表恢复到原来的状态<p><center><A HREF="#Content">[目录]</A></center><hr><br><A NAME="I444" ID="I444"></A><center><b><font size=+2>系统调用简述</font></b></center><br> linux里面的每个系统调用是靠一些宏,,一张系统调用表,一个系统调用入口来完成的。<br><center><A HREF="#Content">[目录]</A></center><hr><br><A NAME="I445" ID="I445"></A><center><b><font size=+2>宏</font></b></center><br> 宏就是_syscallN(type,name,x...),N是系统调用所需的参数数目,type是返回类型,name即面向用户的系统调用函数名,x...是调用参数,个数即为N。<br> 例如:<br>#define _syscall3(type,name,type1,arg1,type2,arg2,type3,arg3) \<br>type name(type1 arg1,type2 arg2,type3 arg3) \<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))); \<br>if (__res>=0) \<br> return (type) __res; \<br>errno=-__res; \<br>return -1; \<br>}<br>(这是2.0.33版本)<br> 这些宏定义于include\asm\Unistd.h,这就是为什么你在程序中要包含这个头文件的原因。该文件中还以__NR_name的形式定义了164个常数,这些常数就是系统调用函数name的函数指针在系统调用表中的偏移量。<p><center><A HREF="#Content">[目录]</A></center><hr><br><A NAME="I446" ID="I446"></A><center><b><font size=+2>系统调用表</font></b></center><br> 系统调用表定义于entry.s的最后。<br> 这个表按系统调用号(即前面提到的__NR_name)排列了所有系统调用函数的指针,以供系统调用入口函数查找。从这张表看得出,linux给它所支持的系统调用函数取名叫sys_name。<p><br><center><A HREF="#Content">[目录]</A></center><hr><br><A NAME="I447" ID="I447"></A><center><b><font size=+2>系统调用入口函数</font></b></center><br> 系统调用入口函数定义于entry.s:<p>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)<br> movl %db6,%edx<br> movl %edx,dbgreg6(%ebx)<br> testb $0x20,flags(%ebx)<br> jne 1f<br> call *%eax<br> movl %eax,EAX(%esp)<br> jmp ret_from_sys_call<br> 这段代码现保存所有的寄存器值,然后检查调用号(__NR_name)是否合法(在系统调用表中查找),找到正确的函数指针后,就调用该函数(即你真正希望内核帮你运行的函数)。运行返回后,将调用ret_from_sys_call,这里就是著名的进程调度时机之一。<br> 当在程序代码中用到系统调用时,编译器会将上面提到的宏展开,展开后的代码实际上是将系统调用号放入ax后移用int 0x80使处理器转向系统调用入口,然后查找系统调用表,进而由内核调用真正的功能函数。<br> 自己添加过系统调用的人可能知道,要在程序中使用自己的系统调用,必须显示地应用宏_syscallN。<br> 而对于linux预定义的系统调用,编译器在预处理时自动加入宏_syscall3(int,ioctl,arg1,arg2,arg3)并将其展开。所以,并不是ioctl本身是宏替换符,而是编译器自动用宏声明了ioctl这个函数。<p><br><center><A HREF="#Content">[目录]</A></center><hr><br><A NAME="I448" ID="I448"></A><center><b><font size=+2>系统调用实现过程</font></b></center><br><center><A HREF="#Content">[目录]</A></center><hr><br><A NAME="I449" ID="I449"></A><center><b><font size=+2>函数名约定</font></b></center><br>系统调用响应函数的函数名约定<br> 函数名以“sys_”开头,后跟该系统调用的名字,由此构成164个形似sys_name()的函数名。因此,系统调用ptrace()的响应函数是sys_ptrace() (kernel/ptrace.c)。<p><br><center><A HREF="#Content">[目录]</A></center><hr><br><A NAME="I450" ID="I450"></A><center><b><font size=+2>系统调用号</font></b></center><br>系统调用号<br> 文件include/asm/unistd.h为每个系统调用规定了唯一的编号:<p>#define __NR_setup 0<br>#define __NR_exit 1<br>#define __NR_fork 2<br>… …<br>#define __NR_ptrace 26<p> 以系统调用号__NR_name作为下标,找出系统调用表sys_call_table (arch/i386/kernel/entry.S)中对应表项的内容,正好就是该系统调用的响应函数sys_name的入口地址。<p><p>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -