转自:http://os.51cto.com/art/200512/13510.htm

现在,您或许正在查看设备驱动程序,并感到奇怪:“函数 foo_read() 是如何被调用的?”或者可能疑惑: “当我输入 cat /proc/cpuinfo 时,cpuinfo() 函数是如何被调用的?”内核完成引导后,控制流就从相对直观的“接下来调用哪个函数?”改变为取决于系统调用、异常和中断。

什么是系统调用?

字面上讲,系统调用(也称为“syscall”)就是一条类似于“add”或者“jump”的指令。从更高的层面上讲,系统调用是用户级程序要求操作系统为它做某些事情的途径。如果您正在编写程序,需要读取某个文件,那么要使用一个系统调用来要求操作系统为您读取那个文件。

系统调用详述

这里是系统调用的工作原理:首先,用户程序为系统调用设置参数。其中一个参数是系统调用编号(稍后对此进行详述)。注意,所有这些都是由库函数自动完成的,除非您是使用汇编编程。参数设置完成后,程序执行“系统调用”指令。这个指令会导致一个异常:产生一个事件,这个事件会致使处理器跳转到一个新的地址,并开始执行那里的代码。新地址的指令会保存程序的状态,计算出应该调用哪个系统调用,调用内核中实现那个系统调用的函数,恢复用户程序状态,然后将控制权返还给用户程序。系统调用是设备驱动程序中定义的函数最终被调用的一种方式。这就是系统调用如何工作的一个简短说明。

接下来,我们将为那些对内核事实上如何完成感到好奇的这些人提供详尽的细节。不要担心您是否完全理解所有细节 —— 只需要记住这是内核中的函数最终被调用的一个途径 —— 没有任何神秘之处。您可以追踪控制流在内核中的全部历程 —— 有时会有些困难,但是您可以做得到。

系统调用示例:1

这里非常适合于开始根据理论展示一些代码。我们将研究 read() 系统调用的过程,首先从系统调用指令被执行的时候开始。使用 PowerPC 体系结构作为代码体系结构相关部分的示例。在 PowerPC 上,当执行一个系统调用时,处理器跳转到地址 0xc00。那个位置的代码是在文件 arch/ppc/kernel/head.S 中定义的。类似如下:


/* System call */
. = 0xc00
SystemCall:
EXCEPTION_PROLOG
EXC_XFER_EE_LITE(0xc00, DoSyscall) /* Single step - not used on 601 */
EXCEPTION(0xd00, SingleStep, SingleStepException, EXC_XFER_STD)
EXCEPTION(0xe00, Trap_0e, UnknownException, EXC_XFER_EE)

这段代码所做的事情是,保存一些状态,然后调用另一个名为 DoSyscall 的函数。

EXCEPTION_PROLOG 是一个宏,负责从用户空间到内核空间的切换,这需要保存用户进程的寄存器状态。使用此例程的地址和函数 DoSyscall 的地址来调用 EXC_XFER_EE_LITE。最后,某些状态将会被保存,DoSyscall 将会被调用。后面的两行在地址 0xd00 和 0xe00 保存两个异常向量。

EXC_XFER_EE_LITE 类似如下:


#define EXC_XFER_EE_LITE(n, hdlr) \
EXC_XFER_TEMPLATE(n, hdlr, n+1, COPY_EE, transfer_to_handler, \
ret_from_except)

EXC_XFER_TEMPLATE 是另一个宏,代码类似如下:


#define EXC_XFER_TEMPLATE(n, hdlr, trap, copyee, tfer, ret) \
li r10,trap; \
stw r10,TRAP(r11); \
li r10,MSR_KERNEL; \
copyee(r10, r9); \
bl tfer; \
i##n: \
.long hdlr; \
.long ret

li 表示 立即加载(load immediate),即某个在编译时已知的常量保存在某个寄存器中。首先,trap 加载到寄存器 r10 中。在接下来的一行中,那个值存储在由 TRAP(r11) 给出的地址中。TRAP(r11) 以及接下来两行去做一些硬件相关的位操作。然后,我们调用 tfer 函数(transfer_to_handler 函数),它会处理更多内部事务并将控制转交给 hdlrDoSyscall)。注意, transfer_to_handler 通过链接寄存器加载处理程序的地址,因此您看到的是 .long DoSyscall,而不是 bl DoSyscall

系统调用示例:2

现在我们来研究 DoSyscall。它位于 arch/ppc/kernel/entry.S 文件中。这个函数最终使用系统调用编号将系统调用表的地址和索引加载到它。操作系统使用系统调用表将系统调用编号翻译为特定的系统调用。

系统调用表名为 sys_call_table,在 arch/ppc/kernel/misc.S 中定义。系统调用表包含有实现每个系统调用的函数的地址。例如,read() 系统调用函数名为 sys_read。 read() 系统调用编号是 3,所以 sys_read() 位于系统调用表的第四个条目中(因为系统调用起始编号为 0)。从地址 sys_call_table + (3 * word_size) 读取数据,得到 sys_read() 的地址。

DoSyscall 找到正确的系统调用地址后,它将控制权转交给那个系统调用。我们来看定义 sys_read() 的位置,即 fs/read_write.c 文件。这个函数会找到关联到 fd 编号(传递给 read() 函数的)的文件结构体。那个结构体包含指向用来读取特定类型文件数据的函数的指针。进行一些检查后,它调用与文件相关的 read() 函数,来真正从文件中读取数据并返回。与文件相关的函数是在其他地方定义的 —— 比如套接字代码、文件系统代码,或者设备驱动程序代码。这是特定内核子系统最终与内核其他部分协作的一个方面。

读取函数结束后,从 sys_read() 返回到 DoSyscall(),它将控制权切换给 ret_from_except(在 arch/ppc/kernel/entry.S 中定义)。它会去检查那些在切换回用户空间之前需要完成的任务。如果没有需要做的事情,那么就通过 restore 函数恢复用户进程的状态,并将控制权交还给用户程序。

就是这样!read() 调用就完成了!幸运的话,您会得到数据。

在关键的位置加入 printk,可以更深入地研究 syscalls。一定要限制这些 printk 的输出的数量。例如,如果向 sys_read() syscall 添加 printk,应该像这样去做:


static int mycount = 0; if (mycount < 10) {
printk ("sys_read called\n");
mycount++;
}

[Kernel]理解System call系统调用的更多相关文章

  1. 深入理解Solaris X64系统调用

    理解系统调用的关键在于洞悉系统调用号是联系用户模式与内核模式的纽带.而在Solaris x64平台上,系统调用号被保存在寄存器RAX中,从用户模式传递到内核模式.一旦进入内核模式,内核的sys_sys ...

  2. 深入理解Linux的系统调用【转】

    一. 什么是系统调用 在Linux的世界里,我们经常会遇到系统调用这一术语,所谓系统调用,就是内核提供的.功能十分强大的一系列的函数.这些系统调用是在内核中实现的,再通过一定的方式把系统调用给用户,一 ...

  3. [kernel]----理解kswapd的低水位min_free_kbytes

    1. min_free_kbytes 先看官方解释: This is used to force the Linux VM to keep a minimum number of kilobytes ...

  4. 灵魂拷问:你真的理解System.out.println()执行原理吗?

    原创/朱季谦 灵魂拷问,这位独秀同学,你会这道题吗?  请说说,"System.out.println()"原理...... 这应该是刚开始学习Java时用到最多一段代码,迄今为止 ...

  5. JVM相关 - 深入理解 System.gc()

    本文基于 Java 17-ea,但是相关设计在 Java 11 之后是大致一样的 我们经常在面试中询问 System.gc() 究竟会不会立刻触发 Full GC,网上也有很多人给出了答案,但是这些答 ...

  6. 【Linux】【Basis】【Kernel】Linux常见系统调用

    一,进程控制 1)getpid,getppid--获取进程识别号 #include <sys/types.h> #include <unistd.h> pid_t getpid ...

  7. 对于linux下system()函数的深度理解(整理)

    原谅: http://blog.sina.com.cn/s/blog_8043547601017qk0.html 这几天调程序(嵌入式linux),发现程序有时就莫名其妙的死掉,每次都定位在程序中不同 ...

  8. 转:对于linux下system()函数的深度理解(整理)

    这几天调程序(嵌入式linux),发现程序有时就莫名其妙的死掉,每次都定位在程序中不同的system()函数,直接在shell下输入system()函数中调用的命令也都一切正常.就没理这个bug,以为 ...

  9. (笔记)Linux下system()函数的深度理解(整理)

    注:从其它地方转的非常好的一篇文章,值得深究! 这几天调程序(嵌入式linux),发现程序有时就莫名其妙的死掉,每次都定位在程序中不同的system()函数,直接在shell下输入system()函数 ...

随机推荐

  1. HTTP客户端都应该支持的五个特性

    在我看来,HTTP客户端必须要支持如下五个特性. 缓存 最后修改时间的检查 ETags 压缩 重定向 缓存返回的头如下: Cache-Control: max-age=, public Expires ...

  2. linux free命令详解和使用实例(查看内存使用率)

    转载:http://www.jb51.net/LINUXjishu/152017.html 1.命令格式: free [参数] 2.命令功能: free 命令显示系统使用和空闲的内存情况,包括物理内存 ...

  3. poj 3237(树链剖分+线段树)

    题意:给一棵树,三种操作.将第i条边的权值改为v,将a到b的路径上的边的权值全部取反,求a到b路径上边的权值的最大值. 思路:明显的树链剖分,加上线段树的操作.因为有取反的操作所以每个区间要记录最大值 ...

  4. TCP客户/服务器程序实例——回射服务器

    目录 客户/服务器程序源码 POSIX信号处理 POSIX信号语义 处理SIGCHLD信号 处理僵死进程 处理被中断的系统调用 wait和waitpid函数 wait和waitpid函数的区别 网络编 ...

  5. Oracle学习(五):多表查询

    1.知识点:能够对比以下的录屏进行阅读 SQL> --等值连接 SQL> --查询员工信息: 员工号 姓名 月薪 部门名称 SQL> select empno,ename,sal,d ...

  6. mybatis开始

    1,安装mysql. 2,安装nvicat. 云盘上已经上传,另外也可看这里: http://www.cnblogs.com/alsf/diary/2017/12/26/8117263.html 3, ...

  7. (数据库)DBCP连接池配置参数说明

    <!-- 数据源1 --> <bean id="dataSource" class="org.apache.commons.dbcp.BasicData ...

  8. 使用RMAN方式清除

    使用RMAN方式清除 RMAN清除方式会自动清除磁盘上的归档日志文件,同时会释放控制文件中对应的归档日志的归档信息. 可以基于不同的条件来清除归档日志,如基于SCN,基于SEQUENCE,基于TIME ...

  9. [精]Oracle APEX 5.0 入门教程(一) Form表单

    Oracle APEX Tutorial for Beginners (APEX 5.0) 1- Introduction 2- Create Workspace 3- Work with Works ...

  10. Android开发之帐户管理

    android.accounts主要包括了集中式的帐户管理API, AccountManagerCallback, AccountManagerFuture, OnAccountsUpdateList ...