⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 index.html

📁 这是一个介绍 linux 编程知识的文章。
💻 HTML
📖 第 1 页 / 共 5 页
字号:
<p><font size=+0><font face="宋体">&nbsp;&nbsp;&nbsp; 若干个库函数可以映射到同一个系统调用入口点。系统调用入口点对每个系统调用定义其真正的语法和语义,但库函数通常提供一个更方便的接口。如系统调用</font>exec<font face="宋体">有集中不同的调用方式:</font>execl,
execle,<font face="宋体">等,它们实际上只是同一系统调用的不同接口而已。对于这些调用,它们的库函数对它们各自的参数加以处理,来实现各自的特点,但是最终都被映射到同一个核心入口点。</font></font>
<p><font face="宋体"><font size=+0> <b>D.系统调用陷入内核后作何初始化处理</b></font></font></ol>
</ol>

<dir>
<dir><font size=+0><font face="宋体">&nbsp;&nbsp;&nbsp; 当进程执行系统调用时,先调用系统调用库中定义某个函数,该函数通常被展开成前面提到的</font>_syscallN<font face="宋体">的形式通过</font>INT
0x80<font face="宋体">来陷入核心,其参数也将被通过寄存器传往核心。</font></font>
<p><font size=+0><font face="宋体">&nbsp;&nbsp;&nbsp; 在这一部分,我们将介绍</font>INT
0x80<font face="宋体">的处理函数</font>system_call<font face="宋体">。</font></font>
<p><font face="宋体"><font size=+0>&nbsp;&nbsp;&nbsp; 思考一下就会发现,在调用前和调用后执行态完全不相同:前者是在用户栈上执行用户态程序,后者在核心栈上执行核心态代码。那么,为了保证在核心内部执行完系统调用后能够返回调用点继续执行用户代码,必须在进入核心态时保存时往核心中压入一个上下文层;在从核心返回时会弹出一个上下文层,这样用户进程就可以继续运行。</font></font>
<p><font size=+0><font face="宋体">&nbsp;&nbsp;&nbsp; 那么,这些上下文信息是怎样被保存的,被保存的又是那些上下文信息呢?这里仍以</font>x86<font face="宋体">为例说明。</font></font>
<p><font size=+0><font face="宋体">&nbsp;&nbsp;&nbsp; 在执行</font>INT<font face="宋体">指令时,实际完成了以下几条操作:</font></font>
<p><font size=+0><font face="宋体">1.由于</font>INT<font face="宋体">指令发生了不同优先级之间的控制转移,所以首先从</font>TSS<font face="宋体">(任务状态段)中获取高优先级的核心堆栈信息(</font>SS<font face="宋体">和</font>ESP<font face="宋体">);2.把低优先级堆栈信息(</font>SS<font face="宋体">和</font>ESP<font face="宋体">)保留到高优先级堆栈(即核心栈)中;</font></font>
<br><font size=+0><font face="宋体">3.把</font>EFLAGS<font face="宋体">,外层</font>CS<font face="宋体">,</font>EIP<font face="宋体">推入高优先级堆栈(核心栈)中。</font></font>
<br><font size=+0><font face="宋体">4.通过</font>IDT<font face="宋体">加载</font>CS<font face="宋体">,</font>EIP<font face="宋体">(控制转移至中断处理函数)</font></font></dir>
</dir>

<dir>
<dir><font size=+0><font face="宋体">然后就进入了中断</font>0x80<font face="宋体">的处理函数</font>system_call<font face="宋体">了,在该函数中首先使用了一个宏</font>SAVE_ALL<font face="宋体">,该宏的定义如下所示:</font></font>
<p><font size=+0>#define SAVE_ALL \</font>
<dir>
<dir>
<dir><font size=+0>cld; \</font>
<p><font size=+0>pushl %es; \</font>
<p><font size=+0>pushl %ds; \</font>
<p><font size=+0>pushl %eax; \</font>
<p><font size=+0>pushl %ebp; \</font>
<p><font size=+0>pushl %edi; \</font>
<p><font size=+0>pushl %esi; \</font>
<p><font size=+0>pushl %edx; \</font>
<p><font size=+0>pushl %ecx; \</font>
<p><font size=+0>pushl %ebx; \</font>
<p><font size=+0>movl $(__KERNEL_DS),%edx; \</font>
<p><font size=+0>movl %edx,%ds; \</font>
<p><font size=+0>movl %edx,%es;</font></dir>
</dir>
</dir>
<font size=+0><font face="宋体">&nbsp;&nbsp;&nbsp; 该宏的功能一方面是将寄存器上下文压入到核心栈中,对于系统调用,同时也是系统调用参数的传入过程,因为在不同特权级之间控制转换时,</font>INT<font face="宋体">指令不同于</font>CALL<font face="宋体">指令,它不会将外层堆栈的参数自动拷贝到内层堆栈中。所以在调用系统调用时,必须先象前面的例子里提到的那样,把参数指定到各个寄存器中,然后在陷入核心之后使用</font>SAVE_ALL<font face="宋体">把这些保存在寄存器中的参数依次压入核心栈,这样核心才能使用用户传入的参数。下面给出</font>system_call<font face="宋体">的源代码:</font></font>
<p><font size=+0>ENTRY(system_call)</font>
<dir>
<dir><font size=+0>pushl %eax # save orig_eax</font>
<p><font size=+0>SAVE_ALL</font>
<p><font size=+0>GET_CURRENT(%ebx)</font>
<p><font size=+0>cmpl $(NR_syscalls),%eax</font>
<p><font size=+0>jae badsys</font>
<p><font size=+0>testb $0x20,flags(%ebx) # PF_TRACESYS</font>
<p><font size=+0>jne tracesys</font>
<p><font size=+0>call *SYMBOL_NAME(sys_call_table)(,%eax,4)</font></dir>
</dir>
</dir>
</dir>
<font size=+0>. . . . . .</font>
<p><font face="宋体"><font size=+0>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
在这里所做的所有工作是:</font></font>
<br><font size=+0><font face="宋体">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
1.保存</font>EAX<font face="宋体">寄存器,因为在</font>SAVE_ALL<font face="宋体">中保存的</font>EAX<font face="宋体">寄存器会被调用的返回值所覆盖;</font></font>
<br><font size=+0><font face="宋体">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
2.调用</font>SAVE_ALL<font face="宋体">保存寄存器上下文;</font></font>
<br><font size=+0><font face="宋体">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
3.判断当前调用是否是合法系统调用(</font>EAX<font face="宋体">是系统调用号,它应该小于</font>NR_syscalls<font face="宋体">);</font></font>
<br><font size=+0><font face="宋体">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
4.如果设置了</font>PF_TRACESYS<font face="宋体">标志,则跳转到</font>syscall_trace<font face="宋体">,在那里将会把当前进程挂起并向其父进程发送</font>SIGTRAP<font face="宋体">,这主要是为了设&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
置调试断点而设计的;</font></font>
<br><font size=+0><font face="宋体">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
5.如果没有设置</font>PF_TRACESYS<font face="宋体">标志,则跳转到该系统调用的处理函数入口。这里是以</font>EAX<font face="宋体">(即前面提到的系统调用号)作为偏移,在系&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
统调用表</font>sys_call_table<font face="宋体">中查找处理函数入口地址,并跳转到该入口地址。</font></font>
<br><font face="宋体"><font size=+0> </font></font>
<dir>
<dir>
<dir>
<dir><font face="宋体"><font size=+0>(</font>补充说明:</font>
<br>1.GET_CURRENT<font face="宋体">宏</font></dir>
</dir>
</dir>
</dir>

<ol>
<dir>
<dir>
<ol>&nbsp;
<br>#define GET_CURRENT(reg) \
<p>movl %esp, reg; \
<p>andl $-8192, reg;
<p><font face="宋体">&nbsp;&nbsp;&nbsp; 其作用是取得当前进程的</font>task_struct<font face="宋体">结构的指针返回到</font>reg<font face="宋体">中,因为在</font>Linux<font face="宋体">中核心栈的位置是</font>task_struct<font face="宋体">之后的两个页面处(</font>8192bytes<font face="宋体">),所以此处把栈指针与</font>-8192<font face="宋体">则得到的是</font>task_struct<font face="宋体">结构指针,而</font>task_struct<font face="宋体">中偏移为</font>4<font face="宋体">的位置是成员</font>flags<font face="宋体">,在这里指令</font>testb
$0x20,flags(%ebx)<font face="宋体">检测的就是</font>task_struct->flags<font face="宋体">。</font>
<p><img SRC="a3.gif" height=394 width=552>
<br><font face="宋体">2.堆栈中的参数</font>
<p><font face="宋体">&nbsp;&nbsp;&nbsp; 正如前面提到的,</font>SAVE_ALL<font face="宋体">是系统调用参数的传入过程,当执行完</font>SAVE_ALL<font face="宋体">并且再由</font>CALL<font face="宋体">指令调用其处理函数时,堆栈的结构应该如上图所示。这时的堆栈结构看起来和执行一个普通带参数的函数调用是一样的,参数在堆栈中对应的顺序是(</font>arg1<font face="宋体">,</font>
ebx<font face="宋体">),(</font>arg2, ecx<font face="宋体">)</font>,<font face="宋体">(</font>arg3,
edx<font face="宋体">)</font>. . . . . .<font face="宋体">,这正是</font>SAVE_ALL<font face="宋体">压栈的反顺序,这些参数正是用户在使用系统调用时试图传送给核心的参数。下面是在核心的调用处理函数中使用参数的两种典型方法:</font>
<p>asmlinkage int sys_fork(struct pt_regs regs)<font face="宋体">;</font>
<p>asmlinkage int sys_open(const char * filename, int flags, int mode)<font face="宋体">;</font>
<p><font face="宋体">&nbsp;&nbsp;&nbsp; 在</font>sys_fork<font face="宋体">中,把整个堆栈中的内容视为一个</font>struct
pt_regs<font face="宋体">类型的参数,该参数的结构和堆栈的结构是一致的,所以可以使用堆栈中的全部信息。而在</font>sys_open<font face="宋体">中参数</font>filename,
flags, mode<font face="宋体">正好对应与堆栈中的</font>ebx, ecx, edx<font face="宋体">的位置,而这些寄存器正是用户在通过</font>C<font face="宋体">库调用系统调用时给这些参数指定的寄存器。</font>
<p><font size=+0>__asm__ __volatile__(\</font>
<p><font size=+0>"int $0x80\n\t"\</font>
<p><font size=+0>:"=a"(retval)\</font>
<p><font size=+0>:"0"(__NR_open),\</font>
<p><font size=+0>"b"(filename),\</font>
<p><font size=+0>"c"(flags),\</font>
<p><font size=+0>"d"(mode));</font>
<p><font face="宋体"><font size=-2> </font></font>
<br><font face="宋体">3.核心如何使用用户空间的参数</font></ol>
</dir>
</dir>
</ol>

<dir>
<dir>
<dir>
<dir>
<dir><font face="宋体">在使用系统调用时,有些参数是指针,这些指针所指向的是用户空间</font>DS<font face="宋体">寄存器的段选择子所描述段中的地址,而在</font>2.2<font face="宋体">之前的版本中,核心态的</font>DS<font face="宋体">段寄存器的中的段选择子和用户态的段选择子描述的段地址不同(前者为</font>0xC0000000,
<font face="宋体">后者为</font>0x00000000<font face="宋体">),这样在使用这些参数时就不能读取到正确的位置。所以需要通过特殊的核心函数(如:</font>memcpy_fromfs,
mencpy_tofs<font face="宋体">)来从用户空间数据段读取参数,在这些函数中,是使用</font>FS<font face="宋体">寄存器来作为读取参数的段寄存器的,</font>FS<font face="宋体">寄存器在系统调用进入核心态时被设成了</font>USER_DS<font face="宋体">(</font>DS<font face="宋体">被设成了</font>KERNEL_DS<font face="宋体">)。在</font>2.2<font face="宋体">之后的版本用户态和核心态使用的</font>DS<font face="宋体">中段选择子描述的段地址是一样的(都是</font>0x00000000<font face="宋体">),所以不需要再经过上面那样烦琐的过程而直接使用参数了。</font></dir>
2.2<font face="宋体">及以后的版本</font>linux/arch/i386/head.S

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -