系统调用处理程序 系统调用服务例程 关系 区别

来源:互联网 发布:知乎怎么删除匿名回答 编辑:程序博客网 时间:2024/05/18 03:36

还是菜鸟,可能表述有误,仅供参考。

名字看起来很接近,先看一下调用的流程图:

系统调用的处理过程

用户程序通过int 0x80软中断陷入内核态,这个陷入(trap)具体意思不明,int 0x80软中断的调用一般交给库来封装了,不同体系定义的具体数字可能不同,我们一般都是调用库而不是调用0x80这样的数字。这样,我们就不用去查找那些不统一的软中断定义了。

进入system_call(系统调用处理程序,或名为系统调用例程), 是用汇编代码定义的,如下面的代码,通过这个函数,可以从用户态转换到内核态,然后在内核态中通过“调用号”调用不同的“服务例程”。

这些定义让人很容易混淆,其实“服务”例程才是实际上处理数据的程序。而系统调用处理程序,只是实现了从用户态到内核态转换后的一些必要处理而已。这些必要处理可以参照代码进行研究。


参考代码如下(linux-2.36):

在 arch/x86/kernel/entry_32.S 文件中使用汇编语言编写的 system_call 函数。

/* * syscall stub including irq exit should be protected against kprobes */        .pushsection .kprobes.text, "ax"        # system call handler stubENTRY(system_call)        RING0_INT_FRAME                 # can't unwind into user space anyway        pushl %eax                      # save orig_eax        CFI_ADJUST_CFA_OFFSET 4        SAVE_ALL        GET_THREAD_INFO(%ebp)                                        # system call tracing in operation / emulation        testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp)        jnz syscall_trace_entry        cmpl $(nr_syscalls), %eax        jae syscall_badsyssyscall_call:        call *sys_call_table(,%eax,4)        movl %eax,PT_EAX(%esp)          # store the return valuesyscall_exit:        LOCKDEP_SYS_EXIT        DISABLE_INTERRUPTS(CLBR_ANY)    # make sure we don't miss an interrupt                                        # setting need_resched or sigpending                                        # between sampling and the iret        TRACE_IRQS_OFF        movl TI_flags(%ebp), %ecx        testl $_TIF_ALLWORK_MASK, %ecx  # current->work        jne syscall_exit_work


更详细的描述

http://blog.csdn.net/zhangxinrun/article/details/5838787
                                      
linux内核修炼描述

(1)用户空间到内核空间。
如图 5.3 所示,系统调用的执行需要一个用户空间到内核空间的状态转换,不同的平台具有不同的指
令可以完成这种转换,这种指令也被称作操作系统陷入(operating system trap)指令。
Linux 通过软中断来实现这种陷入,具体对于 X86 架构来说,是软中断 0x80,也即 int $0x80 汇编指
令。软中断和我们常说的中断(硬件中断)不同之处在于—它由软件指令触发而并非由硬件外设引发。
int 0x80 指令被封装在 C 库中,对于用户应用来说,基于可移植性的考虑,不应该直接调用 int $0x80
指令。陷入指令的平台依赖性,也正是系统调用需要在 C 库进行封装的原因之一。
通过软中断 0x80,系统会跳转到一个预设的内核空间地址,它指向了系统调用处理程序(不要和系统
调用服务例程相混淆)
,即在 arch/i386/kernel/entry.S 文件中使用汇编语言编写的 system_call 函数。
(2)system_call 函数到系统调用服务例程。
很显然,所有的系统调用都会统一跳转到这个地址进而执行 system_call 函数,但正如前面所述,到
2.6.23 版为止,内核提供的系统调用已经达到了 325 个,那么 system_call 函数又该如何派发它们到各自的
服务例程呢?
软中断指令 int 0x80 执行时,系统调用号会被放入 eax 寄存器,同时,sys_call_table 每一项占用 4 个
字节。这样,如图 5.5 所示,system_call 函数可以读取 eax 寄存器获得当前系统调用的系统调用号,将其
乘以 4 生成偏移地址,然后以 sys_call_table 为基址,基址加上偏移地址所指向的内容即是应该执行的系统
调用服务例程的地址。
另外,除了传递系统调用号到 eax 寄存器,如果需要,还会传递一些参数到内核,比如 write 系统调
用的服务例程原型为:

sys_write(unsigned int fd, const char * buf, size_t count);

调用 write 系统调用时就需要传递文件描述符 fd、
要写入的内容 buf 以及写入字节数 count 等几个内容
到内核。ebx、ecx、edx、esi 以及 edi 寄存器可以用于传递这些额外的参数。
正如之前所述,系统调用服务例程定义中的 asmlinkage 标记表示,编译器仅从堆栈中获取该函数的参
数,而不需要从寄存器中获得任何参数。进入 system_call 函数前,用户应用将参数存放到对应寄存器中,
system_call 函数执行时会首先将这些寄存器压入堆栈。
对于系统调用服务例程,可以直接从 system_call 函数压入的堆栈中获得参数,对参数的修改也可以一
直在堆栈中进行。在 system_call 函数退出后,用户应用可以直接从寄存器中获得被修改过的参数。
并不是所有的系统调用服务例程都有实际的内容,有一个服务例程 sys_ni_syscall 除了返回-ENOSYS
外不做任何其他工作,在 kernel/sys_ni.c 文件中定义。

10 asmlinkage long sys_ni_syscall(void)11 {12return -ENOSYS;13 }
sys_ni_syscall 的确是最简单的系统调用服务例程,表面上看,它可能并没有什么用处,但是,它在
sys_call_table 中占据了很多位置。
多数位置上的 sys_ni_syscal 都代表了那些已经被内核中淘汰的系统调用,
比如:

.long sys_ni_syscall /* old stty syscall holder */.long sys_ni_syscall /* old gtty syscall holder */

就分别代替了已经废弃的 stty 和 gtty 系统调用。如果一个系统调用被淘汰,它所对应的服务例程就要
被指定为 sys_ni_syscall。
我们并不能将它们的位置分配给其他的系统调用,因为一些老的代码可能还会使用到它们。否则,如
果某个用户应用试图调用这些已经被淘汰的系统调用,所得到的结果,比如打开了一个文件,就会与预期
完全不同,这将令人感到非常奇怪。
其实,sys_ni_syscall 中的“ni”即表示“not implemented(没有实现)。

系统调用通过软中断 0x80 陷入内核,跳转到系统调用处理程序 system_call 函数,并执行相应的服务
例程,但由于是代表用户进程,所以这个执行过程并不属于中断上下文,而是处于进程上下文。

因此,系统调用执行过程中,可以访问用户进程的许多信息,可以被其他进程抢占(因为新的进程可
能使用相同的系统调用,所以必须保证系统调用可重入)
,可以休眠(比如在系统调用阻塞时或显式调用
schedule 函数时)

这些特点涉及进程调度的问题,在此不做深究,读者只需要理解当系统调用完成后,把控制权交回到
发起调用的用户进程前,内核会有一次调度。如果发现有优先级更高的进程或当前进程的时间片用完,那
么就会选择高优先级的进程或重新选择进程运行。

0 0