详解linux系统调用原理

操作系统通过系统调用为运行于其上的进程提供服务。

当用户态进程发起一个系统调用, CPU 将切换到 内核态 并开始执行一个 内核函数 。 内核函数负责响应应用程序的要求,例如操作文件、进行网络通讯或者申请内存资源等。

举一个最简单的例子,应用进程需要输出一行文字,需要调用 write 这个系统调用:

hello_world.c

#include <string.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
 char *msg = "Hello, world!\n";
 write(1, msg, strlen(msg));

 return 0;
}

注解

读者可能会有些疑问——输出文本不是用 printf 等函数吗?

确实是。 printf 是更高层次的库函数,建立在系统调用之上,实现数据格式化等功能。 因此,本质上还是系统调用起决定性作用。

调用流程

那么,在应用程序内,调用一个系统调用的流程是怎样的呢?

我们以一个假设的系统调用 xyz 为例,介绍一次系统调用的所有环节。

如上图,系统调用执行的流程如下:

  • 应用程序 代码调用系统调用( xyz ),该函数是一个包装系统调用的 库函数 ;
  • 库函数 ( xyz )负责准备向内核传递的参数,并触发 软中断 以切换到内核;
  • CPU 被 软中断 打断后,执行 中断处理函数 ,即 系统调用处理函数 ( system_call );
  • 系统调用处理函数 调用 系统调用服务例程 ( sys_xyz ),真正开始处理该系统调用;

执行态切换

应用程序 ( application program )与 库函数 ( libc )之间, 系统调用处理函数 ( system call handler )与 系统调用服务例程 ( system call service routine )之间, 均是普通函数调用,应该不难理解。 而 库函数 与 系统调用处理函数 之间,由于涉及用户态与内核态的切换,要复杂一些。

Linux 通过 软中断 实现从 用户态 到 内核态 的切换。 用户态 与 内核态 是独立的执行流,因此在切换时,需要准备 执行栈 并保存 寄存器 。

内核实现了很多不同的系统调用(提供不同功能),而 系统调用处理函数 只有一个。 因此,用户进程必须传递一个参数用于区分,这便是 系统调用号 ( system call number )。 在 Linux 中, 系统调用号 一般通过 eax 寄存器 来传递。

总结起来, 执行态切换 过程如下:

  • 应用程序 在 用户态 准备好调用参数,执行 int 指令触发 软中断 ,中断号为 0x80 ;
  • CPU 被软中断打断后,执行对应的 中断处理函数 ,这时便已进入 内核态 ;
  • 系统调用处理函数 准备 内核执行栈 ,并保存所有 寄存器 (一般用汇编语言实现);
  • 系统调用处理函数 根据 系统调用号 调用对应的 C 函数—— 系统调用服务例程 ;
  • 系统调用处理函数 准备 返回值 并从 内核栈 中恢复 寄存器 ;
  • 系统调用处理函数 执行 ret 指令切换回 用户态 ;

编程实践

下面,通过一个简单的程序,看看应用程序如何在 用户态 准备参数并通过 int 指令触发 软中断 以陷入 内核态 执行 系统调用 :

hello_world-int.S

.section .rodata

msg:
 .ascii "Hello, world!\n"

.section .text

.global _start

_start:
 # call SYS_WRITE
 movl $4, %eax
 # push arguments
 movl $1, %ebx
 movl $msg, %ecx
 movl $14, %edx
 int $0x80

 # Call SYS_EXIT
 movl $1, %eax
 # push arguments
 movl $0, %ebx
 # initiate
 int $0x80

这是一个汇编语言程序,程序入口在 _start 标签之后。

第 12 行,准备 系统调用号 :将常数 4 放进 寄存器 eax 。 系统调用号 4 代表 系统调用 SYS_write , 我们将通过该系统调用向标准输出写入一个字符串。

第 14-16 行, 准备系统调用参数:第一个参数放进 寄存器 ebx ,第二个参数放进 ecx , 以此类推。

write 系统调用需要 3 个参数:

  • 文件描述符 ,标准输出文件描述符为 1 ;
  • 写入内容(缓冲区)地址;
  • 写入内容长度(字节数);

第 17 行,执行 int 指令触发软中断 0x80 ,程序将陷入内核态并由内核执行系统调用。 系统调用执行完毕后,内核将负责切换回用户态,应用程序继续执行之后的指令( 从 20 行开始 )。

第 20-24 行,调用 exit 系统调用,以便退出程序。

注解
注意到,这里必须显式调用 exit 系统调用退出程序。 否则,程序将继续往下执行,最终遇到段错误( segmentation fault )!

读者可能很好奇——我在写 C 语言或者其他程序时,这个调用并不是必须的!

这是因为 C 库( libc )已经帮你把脏活累活都干了。

接下来,我们编译并执行这个汇编语言程序:

$ ls
hello_world-int.S
$ as -o hello_world-int.o hello_world-int.S
$ ls
hello_world-int.o hello_world-int.S
$ ld -o hello_world-int hello_world-int.o
$ ls
hello_world-int hello_world-int.o hello_world-int.S
$ ./hello_world-int
Hello, world!

其实,将 系统调用号 和 调用参数 放进正确的 寄存器 并触发正确的 软中断 是个重复的麻烦事。 C 库已经把这脏累活给干了——试试 syscall 函数吧!

hello_world-syscall.c

#include <string.h>
#include <sys/syscall.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
 char *msg = "Hello, world!\n";
 syscall(SYS_write, 1, msg, strlen(msg));

 return 0;
}
(0)

相关推荐

  • 基于Linux系统调用--getrlimit()与setrlimit()函数的方法

    功能描述:获取或设定资源使用限制.每种资源都有相关的软硬限制,软限制是内核强加给相应资源的限制值,硬限制是软限制的最大值.非授权调用进程只可以将其软限制指定为0~硬限制范围中的某个值,同时能不可逆转地降低其硬限制.授权进程可以任意改变其软硬限制.RLIM_INFINITY的值表示不对资源限制.用法: 复制代码 代码如下: #include <sys/resource.h>int getrlimit(int resource, struct rlimit *rlim);int setrlimit

  • linux 系统调用与标准库调用的区别详细解析

    1.系统调用和库函数的关系 系统调用通过软中断int 0x80从用户态进入内核态. 函数库中的某些函数调用了系统调用. 函数库中的函数可以没有调用系统调用,也可以调用多个系统调用. 编程人员可以通过函数库调用系统调用. 高级编程也可以直接采用int 0x80进入系统调用,而不必通过函数库作为中介. 如果是在核心编程,也可以通过int 0x80进入系统调用,此时不能使用函数库.因为函数库中的函数是内核访问不到的. 2.从用户调用库函数到系统调用执行的流程. 1) 假设用户调用ssize_t wri

  • Linux中获取某个进程的系统调用以及参数(故障排查案例)

    当一个程序发生故障时,有时候想通过了解该进程正在执行的系统调用来排查问题.通常可以用 strace 来跟踪.但是当进程已经处于 D 状态(uninterruptible sleep)时,strace 也帮不上忙.这时候可以通过 复制代码 代码如下: cat /proc/<PID>/syscall 来获取当前的系统调用以及参数. 这里用最近排查的一个问题为例.碰到的问题是,发现一台服务器在执行 pvcreate 创建物理卷的时候卡死,进程状态为 D 复制代码 代码如下: # ps aux|gre

  • Linux C中库函数与系统调用的区别详细解析

    从程序完成的功能来看,函数库提供的函数通常是不需要操作系统的服务,函数是在用户空间内执行的,除非函数涉及到I/O操作等,一般是不会切到核心态的.系统调用是要求操作系统为用户提供进程,提供某种服务,通常是涉及系统的硬件资源和一些敏感的软件资源等. 函数库的函数,尤其与输入输出相关的函数,大多必须通过Linux的系统调用来完成.因此我们可以将函数库的函数当成应用程序设计人员与系统调用程序之间的一个中间层,通过这个中间层,我们可以用一致的接口来安全的调用系统调用.这样程序员可以只要写一次代码就能够在不

  • 三种方法实现Linux系统调用

    系统调用(System Call)是操作系统为在用户态运行的进程与硬件设备(如CPU.磁盘.打印机等)进行交互提供的一组接口.当用户进程需要发生系统调用时,CPU 通过软中断切换到内核态开始执行内核系统调用函数.下面介绍Linux 下三种发生系统调用的方法: 一.通过 glibc 提供的库函数 glibc 是 Linux 下使用的开源的标准 C 库,它是 GNU 发布的 libc 库,即运行时库.glibc 为程序员提供丰富的 API(Application Programming Interf

  • Linux内核设备驱动之系统调用笔记整理

    /**************************** * 系统调用 ****************************/ (1)什么是系统调用 系统调用是内核和应用程序间的接口,应用程序要访问硬件设备和其他操作系统资源,必须通过系统调用来完成. 在linux中,系统调用是用户空间访问内核的唯一手段,除异常和中断外,他们是内核唯一的合法入口.系统调用的数量很少,在i386上只有大概300个左右. (2)c库和系统调用的关系 应用程序员通过C库中的应用程序接口(API)而不是直接通过系统

  • 详解linux系统调用原理

    操作系统通过系统调用为运行于其上的进程提供服务. 当用户态进程发起一个系统调用, CPU 将切换到 内核态 并开始执行一个 内核函数 . 内核函数负责响应应用程序的要求,例如操作文件.进行网络通讯或者申请内存资源等. 举一个最简单的例子,应用进程需要输出一行文字,需要调用 write 这个系统调用: hello_world.c #include <string.h> #include <unistd.h> int main(int argc, char *argv[]) { cha

  • 详解Linux命令iostat

    Linux系统出现了性能问题,一般我们可以通过top.iostat.free.vmstat等命令来查看初步定位问题.在一个以前看到系统监控工具,总在想那些监控工具的代理,如何收集系统性能信息,io性能,cpu使用,带宽使用等信息,偶然发现,不同系统均提供有性能分析工具的,代理可通过这些命令获取系统性能信息,个人猜测,不知道具体是不是这样的.其中iostat可以给我们提供丰富的IO状态数据,下边就来看一下iostat如何使用,命令能够输出那些信息. 简述 Linux系统中通过iostat我们能查看

  • 详解Linux获取线程的PID(TID、LWP)的几种方式

    在 Linux C/C++ 中通常是通过 pthread 库进行线程级别的操作. 在 pthread 库中有函数: pthread_t pthread_self(void); 它返回一个 pthread_t 类型的变量,指代的是调用 pthread_self 函数的线程的 "ID". 怎么理解这个"ID"呢? 这个"ID"是 pthread 库给每个线程定义的进程内唯一标识,是 pthread 库维持的. 由于每个进程有自己独立的内存空间,故此&

  • 详解Linux下调试器GDB的基本使用方法

    一.概述 GDB调试的三种方式: 1. 目标板直接使用GDB进行调试. 2. 目标板使用gdbserver,主机使用xxx-linux-gdb作为客户端. 3. 目标板使用ulimit -c unlimited,生成core文件:然后主机使用xxx-linux-gdb ./test ./core. 二.gdb调试 构造测试程序如下main.c和sum.c如下: main.c:#include <stdio.h> #include <stdlib.h> extern int sum(

  • 详解Linux 主机网络接入配置

    详解Linux 主机网络接入配置 前言: 网络配置是我们在安装好操作系统之后,需要解决的第一步.现时代没有接入网络的主机已然等同于一堆废铁.在网络配置的过程中,通常我们需要配置本机IP地址,缺省网关,DNS,主机名等等.本文主要描述在Linux环境下,主要以传统命令行方式讲解如何将主机接入网络.对于网路配置的新命令如ip,nmcli等等在以后的文章中描述. 一.网络配置概述 主机接入互联网前提:遵循TCP/IP协议栈 配置主机接入TCP/IP网络需要配置的内容:   IP/Netmask   路

  • 详解Linux命令中的正则表达式

    命令中的正则表达式 如果要在命令输出或文本中筛选内容时使用模糊查找,就需要使用正则表达式.正则表达式是一套由多个元字符组成的模糊查找模式,使用正则表达式可以快速查找和定位文本中指定的内容. 1.单字符匹配符.  正则表达式主要由一些元字符和匹配模式组成 单字符匹配符可以匹配任意单个字符,这个字符的功能和文件名匹配符中的?功能相同 使用正则表达式查找文本,首先需要使用元字符组成一个查找模式 (1)使用查找模式时,通常将其放入两个斜杠//中,然后再放入命令,例如要在一个文本中查找匹配模式/.i...

  • 详解Linux使用shell+expect远程登录主机

    详解Linux使用shell+expect远程登录主机 最近使用Ubuntu系统想远程登录自己的vps主机,但是感觉自带的远程登录软件不太好使.于是乎想着怎么使用shell脚本来登录,于是Google之后整理了一下,记录一下,方便以后用到. 1.准备工作:安装expect for Ubuntu sudo apt-get update sudo apt-get install expect for Centos yum install expect 2.新建脚本粘贴以下内容 #!/usr/bin/

  • 详解Linux 下开发微信小程序安装开发工具

    详解Linux 下开发微信小程序安装开发工具 1. git clone https://github.com/yuan1994/wechat_web_devtools 然后创建一个文件夹 mkdir /opt/tencent/ 移动文件 mv ./wechat_web_devtools /opt/tencent 修改用户组 chown -R root:root /opt/tencent/wechat_web_devtools 启动测试工具 /opt/tencent/wechat_web_devt

  • 详解 linux mysqldump 导出数据库、数据、表结构

    详解 linux mysqldump 导出数据库.数据.表结构 导出完整的数据库备份: mysqldump -h127.0.0.1 -P3306 -uroot -ppassword --add-locks -q dbname > dbname.sql 说明:--add-locks:导出过程中锁定表,完成后回解锁.-q:不缓冲查询,直接导出至标准输出 导出完整的数据库表结构 : mysqldump -h127.0.0.1 -P3306 -uroot -ppassword --add-locks -

  • 详解Linux上svn命令行批量操作

    详解Linux上svn命令行批量操作 虽然说git很好,大多数时候我也是使用git,但是有时候因为一些原因,不得不使用svn,而在linux上使用svn是没有像windows上的tortoisesvn的软件的(网上有说有类似的,但是折腾了很久仍然没有成功),所以直接来命令行吧. 我们直接安装svn就好,然后文件修改之后使用命令 svn status 查看文件的跟踪信息,这里会使用一些代号,对应的大概是 " " 无修改 "A" 新增 "C" 冲突

随机推荐