Linux | 系统调用System Calls

1 Linux操作系统体系结构

这里写图片描述

可以把这张图想象成一个三维的:

最下面的底盘是硬件平台(Hardware Platform),上面有两大层:内核区(Kernel Space) 和 用户区(User space)

其中内核区还有分层

2 系统调用的“社会”关系

操作系统为用户态进程硬件及内核资源进行 交互提供的一组接口

系统调用可被看成是一个内核用户空间程序 交互的接口

可以说承上启下

【作用】

  • 极大的提高了系统的安全性
  • 使用户程序具有可移植性

系统调用和应用程序编程接口(API)

Linux的应用编程接口(API)遵循 POSIX标准(一种操作系统接口标准)

C Library to System Call

这里写图片描述

系统调用与系统命令

系统命令相对API,更高一层,每个系统命令都是一个可执行程序。

系统命令的实现调用了系统调用

系统调用与内核函数

内核函数在形式上与普通函数一样,但它是在内核实现的,需要满足一些内核编程的要求

系统调用是用户进程进入内核的接口层,由内核函数实现的

进入内核后,不同的系统调用会找到各自对应的内核函数,这些内核函数被称为系统调用的“服务例程”(后面会经常用到这个概念)

3 系统调用时的内核栈

这里写图片描述

在之前日志曾讨论过为什么切换内核态会耗费时间,其中提到程序会有两个栈,栈切换有费时。

为了把系统调用号与相应 的服务例程关联起来,内核利用了一个系统调用表。

这个表存放在 sys_call_table数组中,有 NR_syscalls个表项

Linux/arch/x86/syscalls/syscall_32.tbl
[阅读源码网站]
http://fxr.watson.org/fxr/source/?v=linux-2.6
这里写图片描述

当用户态进程调用一个系统调用时,CPU切换到 内核态并开始执行一个内核函数

内核实现了很多不同的系统调用,进程必须传递 一个名为系统调用号的参数来指明需要调用的系 统调用,eax寄存器就用作这个目的

参数传递
系统调用也需要输入输出参数

  • 实际的值
  • 用户态进程地址空间变量的地址

system_call是linux中所有系统调用的入口点,每个系统调用至少有一个参数,即eax传递的系统调用号

system_call代码
/arch/x86/kernel/entry_32.S ENTRY(system_call)

  • system_call是用汇编语言写的
  • 传递系统调用号和参数(eax , ebx, ecx, edx, esi, edi 存放参数)
  • 如果检验有效,则执行相应的系统调用处理程序 call *sys_call_table(, %eax , 4)

Linux-4.4.39定义了七个从SYSCALL_DEFINE0到 SYSCALL_DEFINE6的一组宏,用于定义系统调用服务例程
<include/linux/syscalls.h>
X是系统调用参数的个数

#define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__) #define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__) #define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__) #define SYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__) #define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__) #define SYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__) 

验证参数
在内核打算满足用户的请求之前,必须仔细的检查所有的系统调用参数
只要一个参数指定的是地址,那么内核必须 检查它是否在这个进程的地址空间之内,有两种验证方法:

  • 验证这个线性地址是否属于这个进程的地址空间
  • 如果是读,该内存应被标记为可读。如果是写, 该内存应被标记为可写。

传递返回值
所有的系统调用返回一个整数值。

  • 正数0表示系统调用成功结束
  • 负数表示一个出错条件
    此时这个负值将要存放在errno变量中返回给应用程 序。内核没有设置或使用errno变量,封装例程在系 统调用返回取得返回值之后设置这个变量

服务例程的返回值是将会被写入eax寄存器

4 系统调用过程

用户态下调用C库的库函数,比如func():

  1. func()先做好参数传递工作,然后利用int 0x80指令产生一次异常
  2. CPU通过0x80号在IDT中找到对应的服务例程 system_call(),并调用之
  3. system_call()根据系统调用号索引系统调用表,找到系统调用程序入口,比如sys_func()
  4. sys_func()执行完后,经过ret_from_sys_call()例程返 回用户程序

Linux只允许系统调用接口使用128这一个软中断向量。
这也意味着所有的系统调用接口必须共享这一中断通道, 并在同一个中断服务例程中调用不同的内核服务例程。
系统调用接口是通过调用软中断指令“int $0x80”使进程 从用户态进入到内核态。

应用程序、封装例程、系统调用中断处理程序及系统调用服务例程之间的 关系
这里写图片描述

猜你喜欢

转载自blog.csdn.net/jh_zhai/article/details/79996246