[Kernel]理解System call系统调用

转自: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++;
}
时间: 2024-10-06 10:33:09

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

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

对于linux下system()函数的深度理解(整理) (2013-02-07 08:58:54) 这几天调程序(嵌入式linux),发现程序有时就莫名其妙的死掉,每次都定位在程序中不同的system()函数,直接在shell下输入system()函数中调用的命令也都一切正常.就没理这个bug,以为是其他的代码影响到这个,或是内核驱动文件系统什么的异常导致,昨天有出现了这个问题,就随手百了一下度,问题出现了,很多人都说system()函数要慎用要少用要能不用则不用,system()函数不稳定?

实验五:扒开系统调用的三层皮(下)

实验五:扒开系统调用的三层皮(下) 王朝宪20135114 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.给MenuOS增加time和time-asm命令 1. 通过内核的方式(跟踪调试系统调用)来理解并使用系统调用. rm menu -rf //强制删除当前menu git clone http://github.com/mengning/menu.git //重新克隆新版本的m

使用 Linux 系统调用的内核命令【转】

转自:http://www.ibm.com/developerworks/cn/linux/l-system-calls/ 探究 SCI 并添加自己的调用 Linux® 系统调用 —— 我们每天都在使用它们.不过您清楚系统调用是如何在用户空间和内核之间执行的吗?本文将探究 Linux 系统调用接口(SCI),学习如何添加新的系统调用(以及实现这种功能的其他方法),并介绍与 SCI 有关的一些工具. 1 评论: M. Tim Jones ([email protected]), 顾问工程师, Em

[深入理解Android卷二 全文-第六章]深入理解ActivityManagerService

由于<深入理解Android 卷一>和<深入理解Android卷二>不再出版,而知识的传播不应该因为纸质媒介的问题而中断,所以我将在CSDN博客中全文转发这两本书的全部内容 第6章 深入理解ActivityManagerService 本章主要内容: ·  详细分析ActivityManagerService 本章所涉及的源代码文件名及位置: ·  SystemServer.java frameworks/base/services/java/com/android/server/

Linux 库函数与系统调用的关系与区别

上周总结了<C 标准库的基础 IO>,其实这些功能函数通过「系统调用」也能实现相应功能.这次文章并不是要详细介绍各系统调用接口的使用方法,而是要深入理解「库函数」与「系统」调用之间的关系和区别. 一.系统调用 系统调用,我们可以理解是操作系统为用户提供的一系列操作的接口(API),这些接口提供了对系统硬件设备功能的操作.这么说可能会比较抽象,举个例子,我们最熟悉的 hello world 程序会在屏幕上打印出信息.程序中调用了 printf() 函数,而库函数 printf 本质上是调用了系统

linux :vmware kernel update导致vmware无法打开,解决

kernel 4.7 and VMWare Workstation 12.1. # cd /usr/lib/vmware/modules/source # tar xf vmnet.tar # mv vmnet.tar vmnet.old.tar # sed -i -e 's/dev->trans_start = jiffies/netif_trans_update\(dev\)/g' vmnet-only/netif.c # tar cf vmnet.tar vmnet-only # vmwa

Linux的system()和popen()差异

Linux的system()和popen()差异 1. system()和popen()简介 在linux中我们可以通过system()来执行一个shell命令,popen()也是执行shell命令并且通过管道和shell命令进行通信. system().popen()给我们处理了fork.exec.waitpid等一系列的处理流程,让我们只需要关注最后的返回结果(函数的返回值)即可. 2. system().popen()源码 首先我们来看一下这两个函数在源码(伪代码)上面的差异. int s

Linux Operating System

Linux is a Unix-like computer operating system assembled under the model of free and open source software developmentand distribution .The defining component of Linux is the Linux kernel , andoperating system kernel was first released October 5 , 199

Linux snacks from &lt;linux kernel development&gt;

introduction to the Linux kernel 1.operating system 1) considered the parts of the system 2) responsible for basic use and administration. 3) includes the kernel and device drivers, boot loader, command shell or other user interface, and basic file a