本文转自:http://blog.csdn.net/kernel_learner/article/details/7331505

在Linux中,系统调用是用户空间访问内核的唯一手段,它们是内核唯一的合法入口。

一般情况下,应用程序通过应用编程接口(API)而不是直接通过系统调用来编程,而且这种编程接口实际上并不需要和内核提供的系统调用对应。一个API定义了一组应用程序使用的编程接口。它们可以实现成一个系统调用,也可以通过调用多个系统调用来实现,即使不使用任何系统调用也不存在问题。实际上,API可以在各种不同的操作系统上实现,给应用程序提供完全相同的接口,而它们本身在这些系统上的实现却可能迥异。

在Unix世界中,最流行的应用编程接口是基于POSIX标准的,Linux是与POSIX兼容的。

从程序员的角度看,他们只需要给API打交道就可以了,而内核只跟系统调用打交道;库函数及应用程序是怎么使用系统调用不是内核关心的。

系统调用(在linux中常称作syscalls)通常通过函数进行调用。它们通常都需要定义一个或几个参数(输入)而且可能产生一些副作用。这些副作用通过一个long类型的返回值来表示成功(0值)或者错误(负值)。在系统调用出现错误的时候会把错误码写入errno全局变量。通过调用perror()函数,可以把该变量翻译成用户可以理解的错误字符串。

系统调用的实现有两个特别之处:

1)函数声明中都有asmlinkage限定词,用于通知编译器仅从栈中提取该函数的参数。

2)系统调用getXXX()在内核中被定义为sys_getXXX()。这是Linux中所有系统调用都应该遵守的命名规则。

系统调用号:在linux中,每个系统调用都赋予一个系统调用号,通过这个独一无二的号就可以关联系统调用。当用户空间的进程执行一个系统调用的时候,这个系统调用号就被用来指明到底要执行哪个系统调用;进程不会提及系统调用的名称。系统调用号一旦分配就不能再有任何变更(否则编译好的应用程序就会崩溃),如果一个系统调用被删除,它所占用的系统调用号也不允许被回收利用。Linux有一个"未使用"系统调用sys_ni_syscall(),它除了返回-ENOSYS外不做任何其他工作,这个错误号就是专门针对无效的系统调用而设的。虽然很罕见,但如果有一个系统调用被删除,这个函数就要负责“填补空位”。

内核记录了系统调用表中所有已注册过的系统调用的列表,存储在sys_call_table中。它与体系结构有关,一般在entry.s中定义。这个表中为每一个有效的系统调用指定了唯一的系统调用号。

用户空间的程序无法直接执行内核代码。它们不能直接调用内核空间的函数,因为内核驻留在受保护的地址空间上,应用程序应该以某种方式通知系统,告诉内核自己需要执行一个系统调用,系统系统切换到内核态,这样内核就可以代表应用程序来执行该系统调用了。这种通知内核的机制是通过软中断实现的。x86系统上的软中断由int$0x80指令产生。这条指令会触发一个异常导致系统切换到内核态并执行第128号异常处理程序,而该程序正是系统调用处理程序,名字叫system_call().它与硬件体系结构紧密相关,通常在entry.s文件中通过汇编语言编写。

所有的系统调用陷入内核的方式都是一样的,所以仅仅是陷入内核空间是不够的。因此必须把系统调用号一并传给内核。在x86上,这个传递动作是通过在触发软中断前把调用号装入eax寄存器实现的。这样系统调用处理程序一旦运行,就可以从eax中得到数据。上述所说的system_call()通过将给定的系统调用号与NR_syscalls做比较来检查其有效性。如果它大于或者等于NR_syscalls,该函数就返回-ENOSYS.否则,就执行相应的系统调用:call *sys_call_table(, %eax, 4);

由于系统调用表中的表项是以32位(4字节)类型存放的,所以内核需要将给定的系统调用号乘以4,然后用所得到的结果在该表中查询器位置。如图图一所示:

上面已经提到,除了系统调用号以外,还需要一些外部的参数输入。最简单的办法就是像传递系统调用号一样把这些参数也存放在寄存器里。在x86系统上ebx,ecx,edx,esi和edi按照顺序存放前5个参数。需要六个或六个以上参数的情况不多见,此时,应该用一个单独的寄存器存放指向所有这些参数在用户空间地址的指针。给用户空间的返回值也通过寄存器传递。在x86系统上,它存放在eax寄存器中。

系统调用必须仔细检查它们所有的参数是否合法有效。系统调用在内核空间执行。如果任由用户将不合法的输入传递给内核,那么系统的安全和稳定将面临极大的考验。最重要的一种检查就是检查用户提供的指针是否有效,内核在接收一个用户空间的指针之前,内核必须要保证:

1)指针指向的内存区域属于用户空间
2)指针指向的内存区域在进程的地址空间里
3)如果是读,读内存应该标记为可读。如果是写,该内存应该标记为可写。

内核提供了两种方法来完成必须的检查和内核空间与用户空间之间数据的来回拷贝。这两个方法必须有一个被调用。

copy_to_user():向用户空间写入数据,需要3个参数。第一个参数是进程空间中的目的内存地址。第二个是内核空间内的源地址。第三个是需要拷贝的数据长度(字节数)。
copy_from_user():向用户空间读取数据,需要3个参数。第一个参数是进程空间中的目的内存地址。第二个是内核空间内的源地址.第三个是需要拷贝的数据长度(字节数)。
注意:这两个都有可能引起阻塞。当包含用户数据的页被换出到硬盘上而不是在物理内存上的时候,这种情况就会发生。此时,进程就会休眠,直到缺页处理程序将该页从硬盘重新换回到物理内存。

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

给linux添加一个系统调用时间很简单的事情,怎么设计和实现一个系统调用是难题所在。实现系统调用的第一步是决定它的用途,这个用途是明确且唯一的,不要尝试编写多用途的系统调用。ioctl则是一个反面教材。新系统调用的参数,返回值和错误码该是什么,这些都很关键。一旦一个系统调用编写完成后,把它注册成为一个正式的系统调用是件琐碎的工作,一般下面几步:

1)在系统调用表(一般位于entry.s)的最后加入一个表项。从0开始算起,系统表项在该表中的位置就是它的系统调用号。如第10个系统调用分配到系统调用号为9。
2)任何体系结构,系统调用号都必须定义于include/asm/unistd.h中
3)系统调用必须被编译进内核映像(不能编译成模块)。这只要把它放进kernel/下的一个相关文件就可以。

用户的程序无法直接执行内核代码。他们不能直接调用内核的函数,因为内核驻留在受保护的地址空间。所以应用程序应该通过某种方式通知内核,告诉内核自己需要执行一个系统调用,希望系统切换到内核态,这样内核就可以代表应用程序来执行该系统调用了。

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

通常,系统调用靠C库支持,用户程序通过包含标准头文件并和C库链接,就可以使用系统调用(或者使用库函数,再由库函数实际调用)。庆幸的是linux本身提供了一组宏用于直接对系统调用进行访问。它会设置好寄存器并调用int $0x80指令。这些宏是_syscalln(),其中n的范围是从0到6.代表需要传递给系统调用的参数个数。这是由于该宏必须了解到底有多少参数按照什么次序压入寄存器。以open系统调用为例:

open()系统调用定义如下是:
long open(const char *filename, int flags, int mode)
直接调用此系统调用的宏的形式为:
#define NR_open 5
_syscall3(long, open, const char *, filename, int , flags, int, mode)

这样,应用程序就可以直接使用open().调用open()系统调用直接把上面的宏放置在应用程序中就可以了。对于每个宏来说,都有2+2*n个参数。每个参数的意义简单明了,这里就不详细说明了。

本文转自张昺华-sky博客园博客,原文链接:http://www.cnblogs.com/sky-heaven/p/4508065.html,如需转载请自行联系原作者

Linux系统调用的运行过程【转】相关推荐

  1. Linux 系统调用的执行过程

    什么是系统调用 系统调用 (在 Linux 中常称为 syscalls ) 是应用程序访问硬件设备之间的桥梁. 系统调用层为用户空间提供一种硬件的抽象接口,使得用户不用关注设备的具体信息,同时系统调用 ...

  2. 嵌入式Linux——分析u-boot运行过程(3):u-boot第三阶段代码

    简介: 本文主要介绍在u-boot-1.1.6中代码的运行过程,以此来了解在u-boot中如何实现引导并启动内核.这里我们主要介绍u-boot第三阶段的代码.而第三阶段的代码主要讲解的是在u-boot ...

  3. linux C程序运行过程解析

    前言 一般我们在linux操作系统编译运行C程序,都是直接编译成可执行程序直接运行,没有对中间过程进行剖析,过程如下. 一.预处理阶段 预处理阶段主要完成宏替换.头文件展开.条件编译.注释. gcc ...

  4. Linux内核分析(七)系统调用execve处理过程

    本文的内容包括: 1. 用execve系统调用加载和执行一个可执行程序的代码演示 2. 用gdb跟踪系统调用execve的执行过程 3. execve系统调用处理过程分析 一.如何用execve系统调 ...

  5. 详解Linux系统调用过程

    Linux系统调用的层次如下: 用户程序-------->C库(即API):INT 0x80------------>system_call------------>系统调用服务例程 ...

  6. 操作系统原理,系统调用,系统调用与库函数API等函数之间的调用关系,功能与机制设计,系统调用的执行过程与Linux系统调用执行示例,不同操作系统下的PCB

    操作系统原理,系统调用,功能与机制设计,系统调用的执行过程与Linux系统调用执行示例,不同操作系统下的PCB 一.系统调用:操作系统功能调用,用户在编程时可以调用的操作系统功能. 1.系统调用是操作 ...

  7. linux java运行class文件_jvm学习java文件运行过程

    汇编 汇编的目的是把汇编语言转为机器语言. 链接 链接是要解决目标文件之间的互相依赖关系,当a文件中的aa方法中调用了b文件的bb方法时,在汇编完成后,a文件的bb方法并没有准确的内存地址,链接后会转 ...

  8. linux系统调用过程

    在用户程序调用read.write等系统调用时,linux内核发生了哪些动作呢.下面以write为例: 调用write系统调用,进入C库执行write函数,就是产生了一个int 0X80的一个中断,这 ...

  9. 为什么 Linux 系统调用会消耗较多资源

    本文转载自:公众号真没什么逻辑,作者Draveness,特此感谢! 系统调用是计算机程序在执行的过程中向操作系统内核申请服务的方法,这可能包含硬件相关的服务.新进程的创建和执行以及进程调度,对操作系统 ...

最新文章

  1. iOS 关闭页面侧滑手势
  2. 【自动驾驶】Eigen:矩阵Matrix的使用
  3. 监控利器nagios
  4. Form提交前,ajax校验,并阻止提交
  5. [转]收集android上开源的酷炫的交互动画和视觉效果:Interactive-animation
  6. Mysql写入数据时,adapter 日志报ES连接错误
  7. bootstrap悬停下拉导航的实现
  8. leetcode —— 29. 两数相除
  9. 笔记本电脑风扇声音大_笔记本风扇噪音太大?教你怎么降低笔记本风扇噪音
  10. Android FloatingActionButton(圆形按钮)
  11. 藏在兰州拉面里精益管理秘诀
  12. 【干货】32个EMC标准电路分享!
  13. layer使用心得3
  14. Nacos——Distro一致性协议(架构篇)
  15. 苹果wifi网速慢怎么办_家里网速慢如何解决 提高WiFi网速方法【详解】
  16. 搭建普罗米修斯Prometheus监控系统
  17. sdlc 瀑布式 生命周期_管理信息系统中的系统开发生命周期(SDLC)
  18. 关于物流管理的软件测试项目经验,物流软件自动化测试用例管理和执行调度的设计与实现...
  19. 极客时间 算法训练营 毕业总结
  20. html 颜色混搭_混搭油漆调色板,教用户基本的颜色理论

热门文章

  1. 苹果自动关机_零下二十度,登山表爆表,苹果冻关机,电霸手机好
  2. win7怎么查看电脑配置_电脑死机是什么原因?出现问题你会怎么办?
  3. 电脑开机3秒就重启循环_3秒开机不是梦,泰捷WE40旗舰升级版体验
  4. Mac安装code blocks以及解无法打开的问题
  5. 零基础考信息系统项目管理师要怎么准备?
  6. 人力资源管理4个过程及相关重点
  7. linux安装mysql、卸载mysql、设置mysql
  8. 《系统集成项目管理工程师》必背100个知识点-77配置审计的功能
  9. 《系统集成项目管理工程师》必背100个知识点-37项目进度管理的过程
  10. 笔记整理-信息技术服务标准-ITSS生命周期