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