系统调用

Posted llguanli

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了系统调用相关的知识,希望对你有一定的参考价值。

(一):与内核通信

系统调用在用户空间和硬件设备之间加入了一个中间层。该层主要有三个作用:

?1:他为用户空间提供了一种硬件的抽象接口
?2:系统调用保证了系统的稳定和安全。
?3:每个进程都运行在虚拟系统中,而在用户空间和系统的其余部分提供这样一层公共接口,也是出于这种考虑。

在Linux中,系统调用是用户空间訪问内核的唯一手段。

(二):API,POSIX,C库

普通情况下。应用程序通过在用户空间实现的应用编程接口(API)而不是直接通过系统调用来编程。一个API定义了一组应用程序使用的编程接口。

以下我们看一下POSIX,API。和C库以及系统调用之间的联系。

技术分享图片

(三):系统调用

首先我们先看一下一个比較简单的系统调用的实现:

SYSCALL_DEFINE0(getpid)
{
    ?return task_tgrid_vnr(current);
}

注意,定义中并没有规定他要怎样实现。

当中SYSCALL_DFINE0不过一个宏。他定义一个无參数的系统调用。尾展开后的代码为:

asmlinkage long sys_getpid(void)

那么我们来看一下怎样定义系统调用:
首先,注意函数声明中的asmlinkage限定词。这是一个编译指令,通知编译器只从栈中提取该函数的參数。

全部的系统调用都须要这个限定词。


其次。函数返回long。为了保证32位和64位系统的兼容,系统调用在用户空间和内核空间有不同的返回值类型,在用户空间为int,在内核空间为long。
最后,注意系统调用get_pid()在内核中被定义成sys_getpid()。

这是一个命名规则。

1:系统调用号

在Linux中,每个系统调用被赋予一个系统调用号。这种话,每个系统调用都会关联一个系统调用。

系统调用号很重要,一旦分配就不能再有不论什么改变。否则编译好的应用程序就会崩溃。

此外。假设一个系统调用被删除,他所占用的系统调用号也不同意被回收利用,否则,曾经编译过的代码会调用这个系统调用,但其实却调用的是另外一个系统调用。

在Linux中有一个”未实现“的系统调用sys_ni_syscall()。他除了返回-ENOSYS之外不做不论什么事情,这个系统调用就是专门针对无效的系统调用而设的。

假设一个系统调用被删除或者是变为不可用,这个系统调用就负责”填空补缺“。

在sys_call_table中,是内核记录的全部的已注冊过的系统调用的列表。

在x84-64中,定义在文件arch/i386/kernel/syscall_64.c中。

这个表为没一个有效的系统调用指定了唯一的系统调用号。

如今我们来看一下:

const sys_call_ptr_t sys_call_table[__NR_syscall_max+1] = {
    /*
    *Smells like a like a compiler bug -- it doesn‘t work
    *when the & below is removed.
    *
    * 看上去像是一个编译器bug -- 当以下的&移除之后,他就不能工作了
    */
    [0 ... __NR_syscall_max] = &sys_ni_syscall,
#include <asm/unistd_64.h>
};

2:系统调用的性能

Linux系统调用比其它操作系统运行的要快。

Linux很短的上下文切换时间是一个重要的原因,进出内核都被优化的简单介绍高效。同一时候。系统调用处理函数和每个系统调用本身也都很简洁。

(四):系统调用处理函数
因为用户空间的程序是无法运行内核程序的。全部须要一个机制来通知内核运行某个系统调用。

通知内核的机制是通过软中断来实现的:通过引发一个异常来促使系统切换到内核态去运行异常处理程序。

此时的一场处理程序就是系统调用处理程序。有关于中断,会在后面具体学习。

1:指定恰当的系统调用

因为全部的系统调用陷入内核的方式都是一样的。所以,须要将系统调用号一并传给内核。在x86上,系统调用号是通过eax寄存器来传递给内核的。在陷入内核之前,用户空间就把对应的系统调用所对应的号传入eax中。

system_call()函数通过将给定的系统调用号与NR_syscalls做比較来检查其有效性。

假设大于或等于NR_syscalls。该函数就返回-ENOSYS。

否则,就运行对应的系统调用:

call *sys_call_table(,%rax,8)

因为系统调用表中的表项是以63位类型存放的,所以内核须要将给定的系统调用号乘以4,然后用所得的结果在该表中查询位置。

2:參数传递

同系统调用号一样,进行參数传递的时候。也能够通过寄存器将參数传递到内核中。在x86-32系统上。ebx,ecx,edx,esi,edi依照顺序存放前5个參数,此外,应该用一个单独的寄存器存放指向全部这些參数在用户空间地址的指针。

以下我们看一下系统调用的过程:

技术分享图片

给用户空间的返回值也是通过寄存器传递的。

在x86系统上。他存放在eax寄存器上。





以上是关于系统调用的主要内容,如果未能解决你的问题,请参考以下文章

如何从片段 KOTLIN 中调用意图 [重复]

调用模板化成员函数:帮助我理解另一个 *** 帖子中的代码片段

从片段调用 Google Play 游戏服务

使用意图从另一个片段调用一个片段

应用调试系统调用SWI

Android片段生命周期:onResume调用了两次