(五):系统调用的实现
1:实现系统调用
实现一个系统调用就是考虑他的用途,每一个系统调用都有一个确定的用途,在Linux中不提倡采用多用途的系统调用(一个系统调用通过传递不同的参数值来选择完成不同的工作)。
2:参数验证
系统调用必须仔细检查他们所有的参数是否合法有效。最重要的一项检查就是检查用户提供的指针是否有效。
在接收一个用户空间的指针之前,内核必须保证:
1:指针指向的内存区域属于用户空间,进程决不能洪骗内核去读内核空间的数据
2:指针指向的内存区域在进程的地址空间里。进程决不能哄骗内核去读其他进程的数据
3:如果是读,该内存应被标记为可读,如果是写,该内存应被标记为可写,如果是可执行,该内存应被标记为可执行。进程决不能绕过内存访问限制。
内核提供了两种方法来完成必须的检查和内核空间与用户空间之间数据的来回拷贝。
为了向用户空间写入数据,内核提供了copy_to_user(),他需要三个参数,第一个参数是进程空间中的目的内存地址,第二个是内核空间内的源地址,第三个参数是需要拷贝的数据的长度(字节数)。
为了从用户空间读取数据,内核提供了copy_from_user(),他和copy_to_user()相似,该函数把第二个参数指定位置上的数据拷贝到第一个参数指定的位置上,拷贝的数据的长度由第三个参数指定。
如果执行失败,这两个函数返回的都是没能完成拷贝的数据的字节数。如果成功,则返回0。当出现上述错误的时候,系统调用返回标准-EFAULT。
下面我们看一个例子,silly_copy()函数。
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;
if(copy_to_user(dst,&buf,len))
return -EFAULT;
return len;
}
注意,copy_to_user()和copy_from_user()函数都有可能引起阻塞。当包含用户数据的页被换出到硬盘上而不是在物理内存上的时候,这种情况就会发生,此时,进程就会休眠,知道缺页处理程序将该页从硬盘换回到物理内存。
最后一项是检查针对是否有合法权限。在现在linux系统中,可以使用capable()函数来检查是否有权限对指定的资源进程操作。如果返回非0值,调用者就有权进程操作,返回0表示无权操作。
下面我们来看一下在reboot系统调用中capality()函数的使用。
SYSCALL_DEFINE4(reboot, int, magic1, int, magic2, unsigned int, cmd,
void __user *, arg)
{
char buffer[256];
int ret = 0;
if (!capable(CAP_SYS_BOOT))
return -EPERM;
if (magic1 != LINUX_REBOOT_MAGIC1 ||
(magic2 != LINUX_REBOOT_MAGIC2 &&
magic2 != LINUX_REBOOT_MAGIC2A &&
magic2 != LINUX_REBOOT_MAGIC2B &&
magic2 != LINUX_REBOOT_MAGIC2C))
return -EINVAL;
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. */
/* Override all DAC access, including ACL execute access if
[_POSIX_ACL] is defined. Excluding DAC access covered by
CAP_LINUX_IMMUTABLE. */
/* 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. */
/* 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
.long sys_exit
.long ptregs_fork
.long sys_read
.long sys_write
.long sys_open
.long sys_close
.long sys_waitpid
.long sys_creat
.long sys_link
.long sys_unlink
.long sys_rt_tgsigqueueinfo
.long sys_perf_event_open
.long sys_recvmmsg
.long sys_foo
很明显,我们的系统调用的系统调用号是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>
asmlinkage long sys_foo(void)
{
return THREAD_SIZE;
}
这样就可以启动内核,并在用户空间调用foo()系统调用了。
2:从用户空间访问系统调用
linux内核提供了一个宏来在用户空间调用系统调用,下面我们通过这种方法来测试前面的foo()系统调用
__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:系统调用创建容易,并且使用方便
2:Linux系统调用的高性能显而易见
系统调用的问题:
1:需要一个系统调用号,这需要在内核开发过程中有官方分配
2:系统调用被加入稳定内核后被固化了,为了避免程序v崩溃,他的接口不允许做改动
3:每个需要支持的体系结构都需要注册该系统调用
4:在脚本中不容易调用系统调用,也不能从文件系统直接访问系统调用
5:由于系统调用号的存在,在主内核之外很难维护和使用系统调用
6:如果仅仅是信息交换的话,系统调用有些大才小用
<script type="text/javascript">
$(function () {
$('pre.prettyprint code').each(function () {
var lines = $(this).text().split('\n').length;
var $numbering = $('<ul/>').addClass('pre-numbering').hide();
$(this).addClass('has-numbering').parent().append($numbering);
for (i = 1; i <= lines; i++) {
$numbering.append($('<li/>').text(i));
};
$numbering.fadeIn(1700);
});
});
</script>
相关推荐
哈工大的操作系统实验二,今年老师要求制作PPT,于是良心制作,详细记录了系统调用的相关操作及实验结论,希望对有心人有用,大家共同进步
操作系统的第二个实验,系统调用,调用三个新建的函数
实验二-系统调用,一、问题回答 1.从 Linux 0.11 现在的机制看,它的系统调用最多能传递几个参数?你能想出办法来扩大这个限制吗? 答:Linux-0.11的系统调用通过寄存器ebx、ecx、edx传递参数,最多能传递3个参数。...
一、理解Nachos系统调用 二、文件系统相关的系统调用 三、执行用户程序相关的系统调用
19——Linux的系统调用与文件IO(二)
系统调用及进程控制综合设计实验 设计简单的命令行 shell
系统调用手册.doc 一、进程控制 二、文件系统控制 三、系统控制 四、内存管理 五、网络管理 六、socket控制 七、用户管理 八、进程间通信
二种不同主板的BIOS启动操作系统调用方法区别源码
编制一段程序,实现进程的管道通信,使用系统调用pipe()建立一个管道文件;两个子进程P1和P2 分别向管道各写一句话: Child1 is sending a message! Child2 is sending a message! 而父进程则从管道中读出来自于...
用友U9 第三方系统调用接口培训视频,FBR 格式 ,声音清楚,界面清晰
一、 系统调用概念 二、 系统调用实现原理 三、 系统调用参数传递 四、 系统调用分类 一、系统调用概念
用户通过本文件系统所提供的系统调用实现对文件的操作。最基本文件操作用:创建文件、上出文件、读文件、写文件。本文件系统可分为三个层次,其最底层是对象及其属性;中间层是对对象进行操纵和管理的软件集合;最...
一、 Linux系统调用概述 二、 Linux系统调用流程 三、 系统调用参数传递 四、 典型Linux系统调用 一、Linux系统调用概述
" " "系统调用实习报告 " " " "善良的大姐姐 " "2015.5.3 " 目录 一:总体概述 3 二:任务完成情况 3 任务完成列表(Y/N) 3 具体Exercise的完成情况 3 三:遇到的困难以及解决方法 18 四:收获及感想 19 五:对课程...
熟悉和掌握Unix/Linux环境下的常用文件/目录的常用System Call; 了解Unix/Linux文件系统结构,特殊文件的作用和操作方法; 加深对《文件系统》一章内容的理解;
Ubuntu/Debian man帮助页扩展3.01-1 第二部分---系统调用介绍 第三部分---C函数介绍
开发API接口调用管理系统网站源码2024全新接口平台多用户管理系统 api接口调用教程2024全新开发API接口调用管理系统网站源码 附教程 用layui框架写的 个人感觉很简洁 方便使用和二次开发
Linux下增加系统调用的二种方法.pdf
使用系统调用pipe()建立一条管道线;两个子进程P1和P2分别向管道各写一句话: Message from Child l! Message from Child 2! 父进程从管道中读出来自于两个子进程的信息,显示在屏幕上。 要求父进程先接收子...
有些时候需要调用顶级栏目以及下面的二级栏目,例如再做下拉菜单时。最简单的调用二级栏目的办法