系统调用

Posted

tags:

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

(五):系统调用的实现

1:实现系统调用

实现一个系统调用就是考虑他的用途,每个系统调用都有一个确定的用途,在Linux中不提倡採用多用途的系统调用(一个系统调用通过传递不同的參数值来选择完毕不同的工作)。

2:參数验证

系统调用必须细致检查他们全部的參数是否合法有效。最重要的一项检查就是检查用户提供的指针是否有效。
在接收一个用户空间的指针之前。内核必须保证:

?1:指针指向的内存区域属于用户空间,进程决不能洪骗内核去读内核空间的数据
?2:指针指向的内存区域在进程的地址空间里。

进程决不能哄骗内核去读其它进程的数据 ?3:假设是读。该内存应被标记为可读,假设是写,该内存应被标记为可写,假设是可运行,该内存应被标记为可运行。进程决不能绕过内存訪问限制。

内核提供了两种方法来完毕必须的检查和内核空间与用户空间之间数据的来回拷贝。

为了向用户空间写入数据,内核提供了copy_to_user(),他须要三个參数,第一个參数是进程空间中的目的内存地址。第二个是内核空间内的源地址,第三个參数是须要拷贝的数据的长度(字节数)。

为了从用户空间读取数据。内核提供了copy_from_user(),他和copy_to_user()类似,该函数把第二个參数指定位置上的数据复制到第一个參数指定的位置上,拷贝的数据的长度由第三个參数指定。

假设运行失败,这两个函数返回的都是没能完毕拷贝的数据的字节数。假设成功。则返回0。当出现上述错误的时候,系统调用返回标准-EFAULT。

以下我们看一个样例,silly_copy()函数。

/* 
 * silly_copy没有实际价值的系统调用。他把len字节的数据从‘src‘拷贝的‘dst‘,
 * 毫无理由的让内核空间作为中转站。

*/ SYSTEMCALL_DEFINE3(silly_copy, unsigned long *src, unsigned long *dst, unsigned long len ) { unsigned long buf; / * 将用户地址中的src拷贝进dst */ if(copy_from_user(&buf,src,len)) return -EFAULT; /* 将buf拷贝进用户地址空间中的dst */ if(copy_to_user(dst,&buf,len)) return -EFAULT; /* 返回拷贝的数据量 */ return len; }

注意,copy_to_user()和copy_from_user()函数都有可能引起堵塞。

当包括用户数据的页被换出到硬盘上而不是在物理内存上的时候。这样的情况就会发生,此时。进程就会休眠。知道缺页处理程序将该页从硬盘换回到物理内存。

最后一项是检查针对是否有合法权限。在如今linux系统中。能够使用capable()函数来检查是否有权限对指定的资源进程操作。假设返回非0值,调用者就有权进程操作。返回0表示无权操作。

以下我们来看一下在reboot系统调用中capality()函数的使用。

/*
 * Reboot system call: for obvious reasons only root may call it,
 * and even root needs to set up some magic numbers in the registers
 * so that some mistake won‘t make this reboot the whole machine.
 * You can also set the meaning of the ctrl-alt-del-key here.
 *
 * reboor系统调用:因为一些显著的原因,只root用户才干调用他。

* 甚至是root用户,也须要在寄存器中设置一些參数。所以一些错误不会使 * 整个机器重新启动。 * * reboot doesn‘t sync: do that yourself before calling this. * * reboot不是协作的:在调用这个之前自己做那些事情。 */ SYSCALL_DEFINE4(reboot, int, magic1, int, magic2, unsigned int, cmd, void __user *, arg) { char buffer[256]; int ret = 0; /* We only trust the superuser with rebooting the system. */ /* 我们只相信启动系统的超级用户 */ if (!capable(CAP_SYS_BOOT)) return -EPERM; /* For safety, we require "magic" arguments. */ /* 为了安全起见,我们须要“magic”參数 */ if (magic1 != LINUX_REBOOT_MAGIC1 || (magic2 != LINUX_REBOOT_MAGIC2 && magic2 != LINUX_REBOOT_MAGIC2A && magic2 != LINUX_REBOOT_MAGIC2B && magic2 != LINUX_REBOOT_MAGIC2C)) return -EINVAL; /* Instead of trying to make the power_off code look like * halt when pm_power_off is not set do it the easy way. * * 当pm_power_off未被设置的时候。请不要尝试让power_off的代码 * 看起来像是能够停机,而应该採用更简单的方式 */ if ((cmd == LINUX_REBOOT_CMD_POWER_OFF) && !pm_power_off) cmd = LINUX_REBOOT_CMD_HALT; mutex_lock(&reboot_mutex); switch (cmd) { case LINUX_REBOOT_CMD_RESTART: kernel_restart(NULL); break; case LINUX_REBOOT_CMD_CAD_ON: C_A_D = 1; break; case LINUX_REBOOT_CMD_CAD_OFF: C_A_D = 0; break; case LINUX_REBOOT_CMD_HALT: kernel_halt(); do_exit(0); panic("cannot halt"); case LINUX_REBOOT_CMD_POWER_OFF: kernel_power_off(); do_exit(0); break; case LINUX_REBOOT_CMD_RESTART2: if (strncpy_from_user(&buffer[0], arg, sizeof(buffer) - 1) < 0) { ret = -EFAULT; break; } buffer[sizeof(buffer) - 1] = ‘‘; kernel_restart(buffer); break; #ifdef CONFIG_KEXEC case LINUX_REBOOT_CMD_KEXEC: ret = kernel_kexec(); break; #endif #ifdef CONFIG_HIBERNATION case LINUX_REBOOT_CMD_SW_SUSPEND: ret = hibernate(); break; #endif default: ret = -EINVAL; break; } mutex_unlock(&reboot_mutex); return ret; }

首先就是确定调用进程是否具有CAP_SYS_REBOOT的权利。在linux/capality.h文件里,包括一份全部这些权限和所相应的权限的列表。我们略微看一下:

/**
 ** POSIX-draft defined capabilities.
 **/
/* In a system with the [_POSIX_CHOWN_RESTRICTED] option defined, this
   overrides the restriction of changing file ownership and group
   ownership. */
#define CAP_CHOWN            0
/* Override all DAC access, including ACL execute access if
   [_POSIX_ACL] is defined. Excluding DAC access covered by
   CAP_LINUX_IMMUTABLE. */
#define CAP_DAC_OVERRIDE     1
/* Overrides all DAC restrictions regarding read and search on files
   and directories, including ACL restrictions if [_POSIX_ACL] is
   defined. Excluding DAC access covered by CAP_LINUX_IMMUTABLE. */
#define CAP_DAC_READ_SEARCH  2
/* Overrides all restrictions about allowed operations on files, where
   file owner ID must be equal to the user ID, except where CAP_FSETID
   is applicable. It doesn‘t override MAC and DAC restrictions. */
#define CAP_FOWNER           3
//........

(六):系统调用上下文

在上文中。我们知道,在进程系统调用的时候,会由sys_call()进程处理,当系统调用并返回之后,控制权仍然在system_call()手中。他终于会负责切换到用户空间,并让用户进程继续运行下去。

1:绑定一个系统调用的最后步骤
当编写玩一个系统调用的时候,把他注冊成一个正式的系统调用是一件繁琐的事情:

?1):首先,在系统调用表的最后增加一个表项。
?2):对于所支持的体系结构,系统调用表必须定义在asm/unistd.h中
?3):系统调用必须被编译进内核映象(不能被编译成模块)。只要放入kernel/下的一个相关文件里就能够。比方sys.c,他包括了各种各样的系统调用

首先我们虚构一个系统调用foo()。来使用一下这些步骤。

首先将sys_foo系统调用增加到调用表的最后一个表项,该表位于kernel/syscall_table_32.S文件里。

ENTRY(sys_call_table)
    .long sys_restart_syscall   /* 0 - old "setup()" system call, used for restarting */
    .long sys_exit
    .long ptregs_fork
    .long sys_read
    .long sys_write
    .long sys_open      /* 5 */
    .long sys_close
    .long sys_waitpid
    .long sys_creat
    .long sys_link
    .long sys_unlink    /* 10 */
// ..............................................
    .long sys_rt_tgsigqueueinfo /* 335 */
    .long sys_perf_event_open
    .long sys_recvmmsg
 .long sys_foo /* 338 */

非常明显,我们的系统调用的系统调用号是338。

这个系统调用是与体系结构相关的。所以须要放在合适的体系结构的文件里。

其次。我们将系统调用号增加到asm/unistd.h文件里。

#define __NR_rt_tgsigqueueinfo 240
__SYSCALL(__NR_rt_tgsigqueueinfo, sys_rt_tgsigqueueinfo)
#define __NR_perf_event_open 241
__SYSCALL(__NR_perf_event_open, sys_perf_event_open)
#define __NR_accept4 242
__SYSCALL(__NR_accept4, sys_accept4)
#define __NR_recvmmsg 243
__SYSCALL(__NR_recvmmsg, sys_recvmmsg)
#undef __NR_syscalls
#define __NR_syscalls 244
#define _NR_foo 338

最后来实现系统调用函数foo()。依据函数的功能。我们能够放入到有关的文件里,这个foo()函数我放入到kernel/sys.c文件里。

#include <asm/page.h>
/* 
 * sys_foo -返回内核栈的大小
 *
 */
asmlinkage long sys_foo(void)
{
    return THREAD_SIZE;
}

这样就能够启动内核,并在用户空间调用foo()系统调用了。

2:从用户空间訪问系统调用

linux内核提供了一个宏来在用户空间调用系统调用,以下我们通过这样的方法来測试前面的foo()系统调用

#define _NR_foo 338
__syscall0(long,foo)
int main()
{
    long stack_size;
    stack_size = foo();
    printf("The kernel stack size is %ld.
",stack_size);
    return 0;
}

当中,#define _NR_foo 338 代表foo系统调用的系统调用号
__syscall0(long,foo) : 当中0表示传递给foo系统调用0个參数,该值表示传递给系统调用的參数的个数。

3:为什么不通过系统调用的方式实现

首先我们先看一下系统调用的优点:

?1:系统调用创建easy。而且使用方便
?2:Linux系统调用的高性能显而易见

系统调用的问题:

?1:须要一个系统调用号,这须要在内核开发过程中有官方分配
?2:系统调用被增加稳定内核后被固化了,为了避免程序v崩溃。他的接口不同意做修改
?3:每个须要支持的体系结构都须要注冊该系统调用
?4:在脚本中不easy调用系统调用。也不能从文件系统直接訪问系统调用
?5:因为系统调用号的存在,在主内核之外非常难维护和使用系统调用
?6:假设不过信息交换的话,系统调用有些大才小用

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

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

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

从片段调用 Google Play 游戏服务

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

应用调试系统调用SWI

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