`

Linux系统调用及其实验(二)——内核态、用户态【转】

阅读更多

Linux系统调用

转自http://www.tinylab.org/linux-system-calls/

by Pingbo Wen of TinyLab.org
2013/09/12

系统调用是系统内核提供给用户态程序的一系列API,这样应用程序就可以通过系统调用来请求操作系统内核管理的资源[1]。本文尝试分析在Linux下是如何使用linux内核给我们提供的API,并分析其实现过程。

一、用户态

不管我们是打开一个文件,接收一个socket包,还是获取当前进程信息,都需要调用内核给我们提供的API。这里,我们可以通过strace这个工具,来跟踪一个程序调用的系统函数。比如下面是命令”strace whoami”的输出结果:

 

我们发现whoami首先调用getuid来获取当前有效用户ID,然后打开”/etc/passwd”文件,利用之前获取到的用户ID来获取对应的用户名,最后打印到当前终端上。

在这个过程中,就调用了execve, open, geteuid, read, write等系统调用。一个系统调用就意味着一次用户态到内核态的切换,并且每一个系统调用都会和一个内核中的函数相对应。这里我们就以geteuid这个系统调用为例,来跟踪整个系统调用关系。

首先,我们先自己实现一个调用geteuid的小程序,代码如下:

编译执行后,我这里得到的结果是1000,也就是当前用户id为1000。

然后,通过查看/usr/include/unistd.h头文件,我们知道geteuid的实现在libc中。那么我们可以先反汇编一下libc这个库,这里写了一个脚本来从一个共享库中反汇编指定的函数,脚本如下:

 

运行这个脚本:

 

我们发现这个函数的反汇编指令很简单:

先把0x6b放到寄存器eax中,然后就执行一个syscall的指令。最后是返回指令。

syscall是什么指令?这里的syscall指令就是在x86架构下,专门为系统调用准备的指令(SYSCALL/SYSENTER and SYSRET/SYSEXIT)。

那0x6b又是什么?这是系统调用号,用来区分其他的系统调用。现在我们只是看到了反汇编的代码,而这些系统调用的真真实现可以在glibc的源码中找到[2]。其实在glibc中并没有去实现系统调用,而是对不同系统内核的系统调用的wrapper。在sysdeps/unix/sysv/linux/syscalls.list下,就列出了linux下面所有的系统调用,部分如下:

 

这个文件指定了每一个系统调用对应的内部实现函数名,以及对应的文件名。在编译glibc的时候,syscalls.list文件会被sysdep/unix/make-syscalls.sh脚本处理,这个脚本会利用sysdeps/unix/syscall-template.S这个模板文件,来生成每一个系统调用wrapper的汇编代码,最后生成我们刚才反汇编的那样的代码[3]。

现在我们知道geteuid()函数最后调用的是一个syscall指令。那么我们能不能跳过glibc的wrapper,直接调用linux内核的系统调用呢?在以前的老版本内核中,确实提供了_syscall,_syscall0等宏,但是现在已经没有了。只保留了glibc中给我们提供的syscall函数,其函数声明在/usr/include/unistd.h文件中,原型如下:

 

我们可以通过syscall来实现和之前一样的效果,代码如下:

 

这里,107就是我们刚才在反汇编代码中看到的0x6b。而这些系统调用也在头文件/usr/include/asm/unistd_64.h中找到,如果我们包含文件,我们可以通过”syscall(__NR_geteuid)”来实现和之前一样的效果,但是更直观一点。

如果我们想完全跳过glibc,我们可以写一个汇编代码:

 

我们可以通过如下命令,来编译执行:

 

由于并没有调用输出函数,所以我们只能通过strace来跟踪具体的系统调用,运行后,输出如下:

可以看到,通过syscall调用了geteuid系统调用,并返回了正确的结果。

要注意的是,这里的实现是64位的版本,32位的linux系统调用号和64位的是不一样的,具体可以通过/usr/include/asm/unistd_32.h来获取具体的系统调用号。并且32位下面是用int 0×80软件中断来实现系统调用的分发,而64位是通过syscall指令来实现的。

二、内核态

现在,我们已经知道了用户态程序是如何调用linux内核提供的系统调用,但是真真的实现却是在kernel中。所以现在我们要进入kernel源码,来分析具体系统调用实现,最后我们还要往kernel中添加我们自己的系统调用。

这里还是以geteuid为例。

以前的linux kernel的系统调用是在arch/$(arch)/kernel/syscall_table.S文件定义的,但是在3.2以后,就已经改变了,相关patch可以到lkml.org中找到[4]。x86下最新的syscall定义在arch/x86/syscalls中。其中的syscall_64.tbl就是64位下所有的系统调用表,部分如下:

 

我们可以发现geteuid的系统调用号和之前是一致的。而在编译的时候,就会通过syscallhdr.sh和syscalltbl.sh两个脚本读入对应的系统调用表,来生成unistd_64.h和其他头文件。而这些文件,就是我们刚才在系统里看到的。

通过syscall_64.tbl文件,我们发现geteuid对应的内核函数是sys_geteuid。而这个函数的实现是在kernel/sys.c文件中,源码如下:

其中SYSCALL_DEFINE0是一个宏,0代表这个函数不带参数。这些宏的定义在include/linux/syscalls.h文件中。

知道了一个系统调用的实现,我们就可以利用kernel给我们提供的SYSCALL_DEFINEn宏来添加我们自己的函数,并把我们自定义的函数添加到syscall_64.tbl文件中就可以了。这里,实现了一个很简单的函数,每次调用,都会返回一个字符串。源码如下:

这里函数mysyscall带一个参数,注意参数的声明,中间有一个逗号。

然后在syscall_64.tbl中添加自己的函数:

 

现在,我们可以编译我们定制的内核,并加载这个内核。然后我们可以在系统中写一个程序,来调用我们自己写的系统调用。源码如下:

运行这个程序,你应该会看到”str: strings from kernel”。

三、系统调用加速

现在,我们应该很清楚一个系统调用是怎样从用户程序传递到内核中的。但是,我们知道,从用户态陷入到内核态是一个比较昂贵的切换,如果一个系统中,同时有很多系统调用,这将会严重拖慢整个系统。系统调用的设计初衷就是做为一个系统门卫,只让用户态程序访问它应该访问的资源。但是,有一些系统调用是无害的(比如,获取时间),如果能够让这些系统调用存在于用户态,那就会极大的减少用户态到内核态的切换,从而提高系统性能。

Linux kernel中,有vdso和vsyscall的机制,用来加速特定的系统调用。两者的基本原理都是把一些特定的系统调用放到一个专门的page中,然后把这个page映射到用户程序空间,这样用户态程序就可以不用切换到内核态就可以调用这些函数。

如果你用ldd查看任意一个动态链接程序的库依赖,你将会发发现每一个程序都会依赖一个linux-{vdso, gate}.so.1的库,但是这个库却没有任何文件与之想关联。比如,下面是”ldd /bin/true”的输出:

 

这里的linux-vdso.so.1就是之前所说的vdso机制。这个库是内核虚拟的,然后映射到所有用户态进程。你也可以通过查看/proc/self/maps查看具体的内存映射,下面是”cat /proc/self/maps”的输出:

 

这里,你可以看到vdso和vsyscall的内存映射。需要指出的是vdso和vsyscall的最大的区别是vdso映射到用户态的内存地址是随机的,而vsyscall确实固定的。你可以通过运行多次”cat /proc/self/maps”来比较它们的地址。

由于vsyscall的地址是固定的,这就给内核留下一个巨大的内存溢出漏洞。所以在最新的内核,vsyscall已经逐渐废除,但是你还是能在很多系统中,看到两者的共存,这只是为了向后兼容罢了。并且最新的内核中,vsyscall中已经没有任何指令了,取代的是内核的一个trap,当以前的老程序调用vsyscall里的内容时,会被导向到正常的系统调用。这也是为什么在读取vsyscall的时候,发现里面是空的[5]。

vdso的具体实现在arch/x86/vdso中,其中的vdso.lds.S就定义了具体加速的系统调用。你甚至可以往vdso添加自定义的函数,具体添加方法见这里[6]。

REFERENCE

  • [1]. System Call: http://en.wikipedia.org/wiki/System_calls
  • [2]. Glibc Wrapper: https://sourceware.org/glibc/wiki/SyscallWrappers
  • [3]. Syscall: http://www.ibm.com/developerworks/library/l-system-calls/
  • [4]. Kernel Syscalltbl: https://lkml.org/lkml/2011/11/17/388
  • [5]. vsyscall vs vdso: http://lwn.net/Articles/446528/
  • [6]. Customize vdso: http://www.linuxjournal.com/content/creating-vdso-colonels-other-chicken
分享到:
评论

相关推荐

    Linux内核系统调用开发指南

    Linux® 系统调用 —— ...不过您清楚系统调用是如何在用户空间和内核之间执行的吗?本文将探究 Linux 系统调用接口(SCI),学习如何添加新的系统调用(以及实现这种功能的其他方法),并介绍与 SCI 有关的一些工具。

    边干边学——LINUX内核指导

    第1章 了解Linux内核 1. 1 Linux内核 1. 2 查看Linux内核状况 1. 3 编程序检查系统状况 1. 4 Linux编程环境 第2章 shell 2. 1 she11 2. 2 实现一个简单的shell程序 2. 3 shell编程 第3章 内核时钟 3. 1 关于时钟和...

    西南交通大学2020届操作系统实验1——7

    通过本实验,了解Linux系统的组织和行为,观察各种存储系统状态信息的内核变量;熟悉这些结构与信息。 实验2 软中断通信实验 ①本实验要求学生了解什么是信号,掌握软中断的基本原理;掌握中断信号的使用、进程的创建...

    疯狂内核之——进程管理子系统

    1.8.1 拷贝用户态参数 57 1.8.2 重要的数据结构 61 1.8.3 search_binary_handler函数 66 1.8.4 目标文件的装载和投入运行 69 1.8.5 库函数 92 2 中断控制 94 2.1 中断的分类 94 2.2 中断的硬件环境 95 2.2.1 外部...

    华中科技大学操作系统课程设计——1.系统调用实现文件拷贝

    2 自己的系统调用,只有系统调用实现的代码,系统调用需要自己编译内核 3 字符设备驱动的编写(含代码和makefile) 4 GTK编写系统监视器,可以监测系统很多方面(含代码和makefile) 5 虚拟文件系统(实现的比较简单...

    操作系统课程设计高分大作业(97分),共25页word版本

    文档部分实验结果进行了适当涂鸦以遮挡个人信息,详细介绍请参考...实验8:系统调用实验 —— 创建系统调用 实验9:终端编写实验 —— 模拟终端shell

    Linux 内核-中断和系统调用.pdf

    北大计算机系考研资料 增补资料3:北大操作系统大题最长考内容——之中断和系统调用,一般的教材不包括,但重要且常考

    疯狂内核之——Linux虚拟内存

    第二章 内核级内存管理系统 58 2.1 Linux页面管理 58 2.1.1 NUMA架构 61 2.1.2 内存管理区 62 2.2 伙伴系统算法 65 2.2.1 数据结构 66 2.2.2 块分配 67 2.2.3 块释放 69 2.3 Linux页面级内存管理 72 2.3.1 分配一组...

    Linux内核源代码情景分析 (上下册 高清非扫描 )

    本PDF电子书包含上下两册,共1576页,带目录,高清非扫描版本。 作者: 毛德操 胡希明 丛书名: Linux内核源代码情景分析 ...10.3系统初始化(第二阶段) 10.4系统初始化(第三阶段) 10.5系统的关闭和重引导

    Linux2.6内核标准教程(共计8-- 第1个)

    《Linux2.6内核标准教程》适合Linux内核爱好者、Linux驱动开发人员、Linux系统工程师参考使用,也可以作为计算机及相关专业学生深入学 习操作系统的参考书。 引用: 目录 第1章 Linux内核学习基础 1 1.1 为什么...

    Linux 2.6内核标准教程(部分)

    Linux内核是Linux操作系统中最核心的部分,用于实现对硬件部件的编程控制和接口操作。本书深入、系统地讲解了Linux内核的工作原理,对Linux内核的核心组件逐一进行深入讲解。 全书共8章,首先讲解Linux系统的引导...

    论文研究-基于系统调用的Linux系统入侵检测技术研究.pdf

    提出了一种基于系统调用、面向进程的Linux系统入侵检测方法:利用LKM(Loadable Kernel Modules)技术在Linux内核空间获取检测源数据——所考察进程的系统调用,使用基于极大似然系统调用短序列的Markov模型提取进程...

    Linux编程从入门到精通.rar

    第二部分 Linux内核模块编程指南 第1章 Hello, World 第2章 字符设备文件 第3章 /proc文件系统 158 第4章 把/proc用于输入 162 第5章 把设备文件用于输入 170 第6章 启动参数 182 第7章 系统调用 185 第8章 阻塞...

    Linux内核阅读——感悟

    读核感悟.......................................................................2 读核感悟-Linux内核启动-内核的生成..............读核感悟-伪装现场-系统调用参数.............................................

    linux 2.6内核标准教程

    Linux内核是Linux操作系统中最核心的部分,用于实现对硬件部件的编程控制和接口操作。本书深入、系统地讲解了Linux内核的工作原理,对Linux内核的核心组件逐一进行深入讲解。 全书共8章,首先讲解Linux系统的引导...

    LINUX内核修炼之道

    第二个层次讨论了内核中系统初始化、系统调用、中断处理、进程管理及调度、内存管理、文件系统以及设备驱动等主要部分,目的是希望读者以兴趣为导向,寻找一个子系统或模块,对其代码深入钻研和分析。第三个层次介绍...

    Linux2.6内核标准教程(共计8--第6个)

    《Linux2.6内核标准教程》适合Linux内核爱好者、Linux驱动开发人员、Linux系统工程师参考使用,也可以作为计算机及相关专业学生深入学 习操作系统的参考书。 引用: 目录 第1章 Linux内核学习基础 1 1.1 为什么...

    华清远见嵌入式linux应用程序开发技术详解下载(内部资料).rar

     6.1 Linux系统调用及用户编程接口(API)  6.2 Linux中文件及文件描述符概述   6.3 不带缓存的文件I/O操作   6.4 嵌入式Linux串口应用开发  6.5 标准I/O开发   6.6 实验内容   本章小结   思考...

    Linux2.6内核标准教程(共计8--第8个)

    《Linux2.6内核标准教程》适合Linux内核爱好者、Linux驱动开发人员、Linux系统工程师参考使用,也可以作为计算机及相关专业学生深入学 习操作系统的参考书。 引用: 目录 第1章 Linux内核学习基础 1 1.1 为什么...

    疯狂内核之——虚拟文件系统

    1.2 VFS所处理的系统调用 9 2 虚拟文件系统架构 11 2.1 VFS对象数据结构 11 2.1.1 超级块对象 11 2.1.2 索引节点对象 15 2.1.3 文件对象 18 2.1.4 目录项对象 22 2.2 把Linux中的VFS对象串联起来 24 2.2.1 与进程...

Global site tag (gtag.js) - Google Analytics