Linux内核设计与实现---系统调用

系统调用

  • 1 API、POSIX和C库
  • 2 系统调用
    • 系统调用号
  • 3 系统调用处理程序
    • 指定恰当的系统调用
    • 参数传递
  • 4 系统调用的实现
      • 参数验证
  • 5 系统调用上下文
    • 绑定一个系统调用的最后步骤
    • 从用户空间访问系统调用
    • 为什么不通过系统调用的方式实现

1 API、POSIX和C库

API:应用编程接口。一个API定义了一组应用程序使用的编程接口。它们可以实现成一个系统调用,也可以通过调用多个系统调用来实现,而完全不使用任何系统调用也不存在问题。在Unix世界中,最流行的应用编程接口是基于POSIX标准的。Linux是与POSIX兼容的。

Linux的系统调用像大多数Unix一样,作为C库的一部分。C库实现了Unix系统的主要API,包括标准C库函数和系统调用。

Unix的系统调用抽象出了完成某种确定目的的函数。至于这些函数怎么用完全不需要内核去关心,提供机制(需要提供什么功能)而不是策略(怎样实现这些功能)。

2 系统调用

系统调用通常通过函数进行调用,在Linux中常称作syscalls。系统调用还会通过一个long类型的返回值来表示成功或者失败,使用long类型是为了与64位的硬件体系结构保持兼容。

函数声明中如果用asmlinkage限定词,表示通知编译器仅从栈中提取函数的参数,所有的系统调用都需要这个限定词。Linux所有的系统调用都应该遵守的命名规则:系统调用getpid()在内核中被定义为sys_getpid(),要在系统调用加上sys_。

系统调用号

Linux中,每个系统调用都被赋予一个系统调用号。这样,通过这个独一无二的号就可以关联系统调用。当用户空间的进程执行一个系统调用的时候,这个系统调用号就被用来指明到底是要执行那个系统调用,进程不会提及系统调用的名称。

系统调用号一旦分配就不能再有任何变更,否则编译好的应用程序就会崩溃。此外,如果一个系统调用被删除,它所占的系统调用号也不允许被回收利用,否则,以前编译过的代码再调用这个系统调用,实际上调用的就是另一个系统调用了。Linux有一个系统调用sys_ni_syscall(),它除了返回ENOSYS外不做任何其他的工作,这个错误号就是专门针对无效的系统调用而设的。

内核记录了系统调用表中的所有已被注册过的系统调用的列表,存储在sys_call_table中。它与结构体系有关,需要将系统调用分别注册到每个需要支持的体系结构去,一般定义在entry.s中。2.6.10版本的位置在arch/结构体系/kernel/entry.S。这个表中为每一个有效的系统调用指定了唯一的系统调用号。
m32r体系结构:
在这里插入图片描述

3 系统调用处理程序

用户空间的程序无法直接执行内核代码,他们不能直接调用内核空间中的函数,所以,应用程序应该以某种方式通知系统,告诉内核自己需要执行一个系统调用,希望系统切换到内核态,通知内核的机制是靠软中断实现的:通过一个异常来促使系统切换到内核态去执行异常处理程序。此时的异常处理程序就是系统调用程序。x86系统上的软中断是int 0x80指令产生的。这条指令会触发一个异常导致系统切换到内核态并执行第128号异常处理程序,而该程序正是系统调用处理程序,叫system_call()。x86处理器增加了一条叫做sysenter的指令。与int指令相比,这条指令提供了更快、更专业的陷入内核执行系统调用的方式。

指定恰当的系统调用

因为所有的系统调用陷入内核方式都一样,因此必须把系统调用号一并传给内核,告诉内核去执行什么系统调用。在x86上,系统调用号是通过eax寄存器传递给内核的。在陷入内核之前,用户空间就把相应系统调用所对应的号放入eax中。这样系统调用处理程序一旦运行,就可以从eax中得到数据。

system_call()函数通过将给定的系统调用号与NR_syscalls做比较来检查其有效性。如果大于等于NR_syscalls,该函数返回ENOSYS。否则,就执行相应的系统调用。

call *sys_call_table(,%eax,4)

由于系统调用表中的表项是32位(4字节)类型存放的,所以内核需要将给定的系统调用号乘以4,然后用所得的结构在该表中查询其位置。
在这里插入图片描述

参数传递

除了系统调用号外,大部分系统调用都还需要一些外部的参数输入。所以,在发送异常的时候,应该把这些参数从用户空间传给内核。最简单的办法就是像传递系统调用号一样:把这些参数存放到寄存器里。在x86系统上,ebx、ecx、edx、edi和esi按照顺序存放前五个参数。需要6个或6个以上参数的,应该用一个单独的寄存器存放指向所有这些参数在用户空间地址的指针。

用户空间返回值也通过寄存器传递,在x86系统上,它存放在eax寄存器中。

4 系统调用的实现

实现一个新的系统调用的第一步是决定它的用途。它要做什么?每个系统调用都应该有一个明确的用途。在Linux中不提倡采用多用途的系统调用(一个系统调用根据参数来选择完成不同的工作)。

新系统调用的参数、返回值和错误码又该是什么?系统调用的接口应该简洁,参数尽可能的少。

设计接口的时候要尽量为将来多做考虑。

当写一个系统调用的时候,要时刻注意可移植性和健壮性,不但要考虑当前,还要为将来做打算。

参数验证

系统调用必须仔细检查它们所有的参数是否合法有效。系统调用在内核空间执行,如果任由用户将不合法的输入传递给内核,那么系统的安全和稳定将面临巨大的考验。

内核提供了两个方法来完成必须的检查和内核空间与用户空间之间数据的来回拷贝。为了向用户空间写入数据,内核提供了copy_to_uesr(),它需要三个参数。第一个参数是进程空间中的目的内存地址。第二个是内核空间内的源地址。最后一个参数是需要拷贝的数据长度。为了从用户空间读取数据,内核提供了copy_from_user()。该函数把第二个参数指定的位置上的数据拷贝到第一个参数指定的位置上,拷贝的数据长度由第三个参数决定。

注意,内核无论何时都不能轻率地接受来自用户空间的指针!

5 系统调用上下文

在执行系统调用的时候处于进程上下文。current指针指向当前任务,即引发系统调用的那个进程。
在进程上下文中,内核可以休眠并且可以被抢占。当系统调用返回的时候,控制权仍然在system_call()中,它最终负责切换到用户空间并让用户进程继续执行下去。

绑定一个系统调用的最后步骤

当编写完一个系统调用后,把它注册成一个正式的系统调用是件琐碎的工作:

  • 首先,在系统调用表的最后加入一个表项。每种支持该系统调用的硬件体系都必须做这样的工作。从0开始算起,系统调用在该表中的位置就是它的系统调用号。如第10个系统调用分配到的系统调用号为9。
  • 对于所支持的各种体系结构,系统调用号都必须定义于include/asm/unistd.h中。
  • 系统调用必须被编译进内核映像(不能编译成模块)。这只要把它放进kernel/下的一个相关文件就行了。

我们通过一个虚构的系统调用foo()来仔细观察这些步骤。首先,我们把sys_foo加入到系统调用表中去。对于大多数体系结构中,该表位于entry.s中,形式如下:
请添加图片描述
我们把新的系统调用加到这个表的末尾:

.long sys_foo

虽然没有明确地指定编号,但我们加入这个系统调用被次序分配给了285这个系统调用号。对于每种需要支持的体系结构,我们都必须将自己的系统调用加入到其系统调用表中去。每种体系结构不需要对应相同的系统调用号。系统调用号是专属体系结构ABI(应用程序二进制结构)的部分。

接下来,我们把系统调用号加入到include/asm/unistd.h中,它的格式如下
请添加图片描述
然后,我们在该列表中加入下面这行:

#define _NR_foo 			285

同时#define NR_syscalls 285这个要改成#define NR_syscalls 286。因为系统调用处理程序system_call()函数通过将给定的系统调用号与NR_syscalls做比较来检查其有效性。如果大于等于NR_syscalls,该函数返回ENOSYS。否则,就执行相应的系统调用。

最后,我们来实现foo()系统调用。无论何种配置,该系统调用都必须编译到核心的内核映像中去,所以我们把它放在kernel/sys.c文件中。再次编译内核就可以了。

从用户空间访问系统调用

Linux提供了一组宏,用于直接对系统调用进行访问。它会设置好寄存器并调用陷入指令。这些宏是_syscalln(),其中n的范围是0到6,代表需要传递给系统调用的参数个数。举个例子,open系统调用的 定义为:

long open(const char *filename,int flags,int mode);

直接调用该系统调用的宏形式为:

_syscall3(long,open,const char *,filename,int flags,int mode)

这样程序就可直接使用open。对于每个宏,都有2+2*n个参数,第一个参数是返回值类型。第二个参数是系统调用的名称。

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

建立一个新的系统调用非常容易,但却绝不倡导。

建立一个新的系统调用的好处:

  • 系统调用创建容易且使用方便
  • Linux系统调用的高性能显而易见

问题是:

  • 你需要一个系统调用号,而这需要在一个内核处于开发版本的时候由官方分配给你
  • 系统调用被加入稳定内核后就被固化了
  • 需要将系统调用分别注册的每个需要的体系结构去
  • 在脚本中不容易调用系统调用,也不能从文件系统直接访问系统调用

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/379671.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

内核编译配置选项含义

Linux 2.6.19.x 内核编译配置选项简介 作者:金步国 版权声明 本文作者是一位自由软件爱好者,所以本文虽然不是软件,但是本着 GPL 的精神发布。任何人都可以自由使用、转载、复制和再分发,但必须保留作者署名,亦不得对声…

js编码处理(转)

js编码处理(转) 1. 使用 JS 中的 encodeURIComponent 或 encodeURI 方法。 说明: encodeURIComponent(String) 对传递参数进行设置。不编码字符有 71 个: ! , , ( , ) , * , - &#…

手动去设置HTTP响应行、响应头、响应体

①手动去设置HTTP响应行中的状态码,这里用到了response的setStatus(int sc);这个方法 package com.itheima.line;import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpSer…

Java SecurityManager checkListen()方法与示例

SecurityManager类的checkListen()方法 (SecurityManager Class checkListen() method) checkListen() method is available in java.lang package. checkListen()方法在java.lang包中可用。 checkListen() method invokes checkPermission with the given SocketPermission(&q…

基本的二分查找、寻找第一个和最后一个数的二分查找

二分查找1 二分查找的框架2 寻找一个数(基本的二分搜索)3 寻找左侧边界的二分搜索4 寻找右侧边界的二分查找5 合并二分查找场景:有序数组寻找一个数、寻找左侧边界(有序数组第一个等目标数的下标)、寻找右侧边界&#…

PostgreSQL 中的递归查询 与oracle 的比较

PostgreSQL 中的递归查询,2种方法: 1、用with decursive WITH RECURSIVE d AS (SELECT d1.id,d1.parent_id,d1.caption FROM course_types d1 where d1.dr 0 and d1.idtypeId union ALL SELECT d2.id,d2.parent_id,d2.caption FROM course_types d2, d …

教你如何玩转GitHub

使用GitHub ①目的:借助GitHub托管项目代码 基本概念: ①仓库(Repository): 用来存放项目代码,每个项目对应一个仓库,多个开源项目对应多个仓库 ②收藏(Star): 收藏项目,方便下次查看 ③…

Java SecurityManager checkDelete()方法与示例

SecurityManager类的checkDelete()方法 (SecurityManager Class checkDelete() method) checkDelete() method is available in java.lang package. checkDelete()方法在java.lang包中可用。 checkDelete() method calls checkPermission with FilePermission(filename,"d…

jQuery中的treeview插件

jQuery做树状结构真的很简单,下面做一个最简单的示例: 在html文件中引用: <link rel"stylesheet" href"../jquery.treeview.css" /> <link rel"stylesheet" href"../red-treeview.css" /> <link rel"styles…

Linux内核设计与实现---中断和中断处理程序

中断和中断处理程序1 中断异常2 中断处理程序上半部与下半部的对比3 注册中断处理程序释放中断处理程序4 编写中断处理程序重入和中断处理程序共享的中断处理程序中断处理程序实例5 中断上下文6 中断处理机制的实现7 中断控制禁止和激活中断禁止指定中断线中断系统的状态8 总结…

asp.net中的窗体身份验证(最简单篇)

在创建网站中&#xff0c;常常会使用到身份验证。asp.net中内置了几种身份验证的方式&#xff0c;如Windows、Froms、Passport等。这几种身份验证的方式各有不同。一般来说&#xff0c;网站的身份验证方式都会经过以下几个步骤&#xff1a; 1、输入用户名和密码&#xff0c;单击…

bat文件调用dos命令 (dos淘金)

ECHO命令是大家都熟悉的DOS批处理命令的一条子命令&#xff0c;但它的一些功能和用法也许你并不是全都知道&#xff0c;不信你瞧&#xff1a; 1&#xff0e; 作为控制批处理命令在执行时是否显示命令行自身的开关 格式&#xff1a;ECHO [ON|OFF] 如果想关闭“ECHO OFF”命令…

response细节点

一、 1&#xff09;、response获得的流不需要手动关闭&#xff0c;Tomcat容器会帮你自动关闭 2&#xff09;、getWriter和getOutputStream不能同时调用 //error package com.itheima.content;import java.io.IOException; import javax.servlet.ServletException; import java…

Java RandomAccessFile writeBytes()方法与示例

RandomAccessFile类writeBytes()方法 (RandomAccessFile Class writeBytes() method) writeBytes() method is available in java.io package. writeBytes()方法在java.io包中可用。 writeBytes() method is used to write the sequence of bytes (i.e. string) to the file. E…

linux内核设计与实现---下半部和推后执行的工作

下半部和推后执行的工作1 下半部为什么要用下半部下半部的环境内核定时器2 软中断软中断的实现软中断处理程序执行软中断使用软中断3 tasklettasklet的实现使用taskletksoftirqd4 工作队列工作队列的实现工作、工作队列和工作者线程之间的关系使用工作队列5 下半部机制的选择6 …

Jquery对复选框的操作

<from> 你的爱好是?<br/> <input type"checkbox" name"items" value"篮球" />篮球 <input type"checkbox" name"items" value"乒乓球" />乒乓球 <input type"checkbox" na…

HttpServletRequest(request的一些API)

一、request的运行流程 首先&#xff0c;自己写一个web工程&#xff0c;也就是建一个工程&#xff1b;当把该web工程发布到Tomcat服务器当中&#xff0c;可以让外界访问&#xff0c;这就成了一个web应用。 在客户端输入一个网站&#xff0c;是web应用资源的地址URL&#xff0c…

DCI:James O. Coplien和Trygve Reenskau提出的新架构方法

http://www.infoq.com/cn/news/2009/05/dci-coplien-reenskau 转载于:https://www.cnblogs.com/yelinpalace/archive/2009/06/13/1502573.html

Java ObjectStreamField getOffset()方法与示例

ObjectStreamField类的getOffset()方法 (ObjectStreamField Class getOffset() method) getOffset() method is available in java.io package. getOffset()方法在java.io包中可用。 getOffset() method is used to get the offset of this ObjectStreamField field. getOffse…

Mac VSCode配置C语言环境(可以调试)

Mac VSCode配置C语言环境c_cpp_properties.jsontasks.jsonlaunch.json新建一个文件夹&#xff0c;用vscode&#xff0c;然后再新建一个test.c文件。 #include <stdio.h>int main(void) {int a1,b1;int cab;printf("%d\n",c);return 0; }这篇文章说怎么配置c_c…