系统调用
简介几种系统调用函数:write、read、open、close、ioctl

简介⼏种系统调⽤函数:write、read、open、close、ioctl 在 Linux 中,⼀切(或⼏乎⼀切)都是⽂件,因此,⽂件操作在 Linux 中是⼗分重要的,为此,Linux 系统直接提供了⼀些函数⽤于对⽂件和设备进⾏访问和控制,这些函数被称为系统调⽤(syscall),它们也是通向操作系统本⾝的接⼝。
⼀、系统调⽤ 系统调⽤就是 Linux 内核提供的⼀组⽤户进程与内核进⾏交互的接⼝。
这些接⼝让应⽤程序受限的访问硬件设备,提供了创建新进程并与已有进程进⾏通信的机制,也提供了申请操作系统其他资源的能⼒。
系统调⽤⼯作在内核态,实际上,系统调⽤是⽤户空间访问内核空间的唯⼀⼿段(除异常和陷⼊外,它们是内核唯⼀的合法⼊⼝)。
系统调⽤的主要作⽤如下:1)系统调⽤为⽤户空间提供了⼀种硬件的抽象接⼝,这样,当需要读写⽂件时,应⽤程序就可以不⽤管磁盘类型和介质,甚⾄不⽤去管⽂件所在的⽂件系统到底是哪种类型;2)系统调⽤保证了系统的稳定和安全。
作为硬件设备和应⽤程序之间的中间⼈,内核可以基于权限、⽤户类型和其他⼀些规则对需要进⾏的访问进⾏判断;3)系统调⽤是实现多任务和虚拟内存的前提。
要访问系统调⽤,通常通过 C 库中定义的函数调⽤来进⾏。
它们通常都需要定义零个、⼀个或⼏个参数(输⼊),⽽且可能产⽣⼀些副作⽤(会使系统的状态发⽣某种变化)。
系统调⽤还会通过⼀个 long 类型的返回值来表⽰成功或者错误。
通常,⽤⼀个负的值来表明错误,0表⽰成功。
系统调⽤出现错误时,C 库会把错误码写⼊ errno 全局变量,通过调⽤ perror() 库函数,可以把该变量翻译成⽤户可理解的错误字符串。
⼆、⼏种常⽤的系统调⽤函数2.1 write 系统调⽤ 系统调⽤ write 的作⽤是把缓冲区 buf 的前 nbytes 个字节写⼊与⽂件描述符 fildes 关联的⽂件中。
它返回实际写⼊的字节数。
如果⽂件描述符有错或者底层的设备驱动程序对数据块长度⽐较敏感,该返回值可能会⼩于 nbytes。
什么是系统调用

xx年xx月xx日
目录
• 系统调用的定义和作用 • 系统调用的基本类别 • 系统调用的实现方式 • 系统调用的优缺点 • 系统调用技术的发展趋势
01
系统调用的定义和作用
什么是系统调用
系统调用是一种API,它允许应 用程序访问操作系统提供的核
心服务。
系统调用是操作系统提供给应 用程序的接口,用于实现操作
系统调用技术的应用前景
云计算
在云计算中,通过系统调用技术可以实现高效的资源管理 和调度。
物联网
在物联网中,系统调用技术可以用于实现各种设备的远程 管理和控制。
人工智能
人工智能需要大量的计算和存储资源,系统调用技术可以 用于实现高效的资源调度和管理。
安全领域
在安全领域,系统调用技术可以用于实现更加严格的安全 策略和防护机制,保障系统的安全性和可靠性。
系统调用可以实现获取系统时间、获取系统 负载、获取磁盘空间等操作,从而方便用户 对系统状态进行监控和管理。
系统调用可以实现启动和关闭外部设备、对 外部设备进行读写操作等操作,从而实现对 外部设备的控制和管理。
系统调用的基本原理
系统调用使用软件中断实现,应用程序通过系统调用请求操 作系统服务,操作系统通过中断处理程序将控制权转移到内 核,内核执行相应的服务后将结果返回给应用程序,应用程 序继续执行。
THANKS
谢谢您的观看
系统调用的接口
系统调用接口是操作系统提供给应用 程序使用的函数集合,用于向操作系 统请求服务。
系统调用接口通常包括文件操作、进 程控制、内存管理、网络通信等功能 的函数集合。
系统调用接口是操作系统提供的一种 标准化的服务,应用程序使用系统调 用接口来完成对系统资源的访问和管 理。
操作系统中系统调用实例

操作系统中系统调用实例
系统调用是操作系统内核提供给应用程序的接口,应用程序通过系统调用来访问操作系统内核提供的服务和资源,如文件、网络、内存、外设等。
下面是一个C语言中系统调用的实例:
```c
int read(int fd, void *buf, int count); //读文件数据
int write(int fd, const void *buf, int count); //写文件数据
int open(const char *pathname, int flags, mode_t mode); //打开文件
```
在这个例子中,`read`、`write`和`open`是系统调用的函数名称。
`fd`是文件描述符,`buf`是指向缓冲区的指针,`count`是要读取或写入的字节数。
`pathname`是文件的路径名,`flags`是打开文件的选项,`mode`是文件的访问模式。
系统调用的执行过程可以分为三个步骤:
1. 执行前的准备工作:包括模式切换和栈切换。
2. 执行处理程序(处理函数):这是系统调用的主要工作,根据系统调用的不同而有所差异。
3. 执行后的善后工作:包括模式切换和栈切换的回退。
不同的操作系统提供了各自的系统调用,但C语言标准库提供了一种通用的方式,使得C代码可以在不同的操作系统上运行,前提是经过不同操作系统编译器的编译。
Linux内核中系统调用详解

Linux内核中系统调用详解什么是系统调用?(Linux)内核中设置了一组用于实现各种系统功能的子程序,称为系统调用。
用户可以通过系统调用命令在自己的应用程序中调用它们。
从某种角度来看,系统调用和普通的函数调用非常相似。
区别仅仅在于,系统调用由(操作系统)核心提供,运行于核心态;而普通的函数调用由函数库或用户自己提供,运行于用户态。
随Linux核心还提供了一些(C语言)函数库,这些库对系统调用进行了一些包装和扩展,因为这些库函数与系统调用的关系非常紧密,所以习惯上把这些函数也称为系统调用。
为什么要用系统调用?实际上,很多已经被我们习以为常的C语言标准函数,在Linux 平台上的实现都是靠系统调用完成的,所以如果想对系统底层的原理作深入的了解,掌握各种系统调用是初步的要求。
进一步,若想成为一名Linux下(编程)高手,也就是我们常说的Hacker,其标志之一也是能对各种系统调用有透彻的了解。
即使除去上面的原因,在平常的编程中你也会发现,在很多情况下,系统调用是实现你的想法的简洁有效的途径,所以有可能的话应该尽量多掌握一些系统调用,这会对你的程序设计过程带来意想不到的帮助。
系统调用是怎么工作的?一般的,进程是不能访问内核的。
它不能访问内核所占内存空间也不能调用内核函数。
(CPU)(硬件)决定了这些(这就是为什么它被称作"保护模式")。
系统调用是这些规则的一个例外。
其原理是进程先用适当的值填充(寄存器),然后调用一个特殊的指令,这个指令会跳到一个事先定义的内核中的一个位置(当然,这个位置是用户进程可读但是不可写的)。
在(Intel)CPU中,这个由中断0x80实现。
硬件知道一旦你跳到这个位置,你就不是在限制模式下运行的用户,而是作为操作系统的内核--所以你就可以为所欲为。
进程可以跳转到的内核位置叫做sysem_call。
这个过程检查系统调用号,这个号码告诉内核进程请求哪种服务。
然后,它查看系统调用表(sys_call_table)找到所调用的内核函数入口地址。
计算机操作系统实验指导计算机系统调用

使用内核编译法添加系统调用
为了验证系统调用是否成功,编写验证代码如下。 #include <stdio.h> #include <linux/kernel.h> #include <sys/syscall.h> #include <unistd.h> int main() { long int a = syscall(三三三); printf("System call sys_helloworld reutrn %ld\n", a); return 0; }
如图地执行结果,我们得到sys_call_table地址:ffffffffabe00一a0 三. 编写Makefile文件,可参考实验指导书地内容。 四. 编译并装入模块 # sudo make //编译 # sudo insmod hello.ko //装入模块 # lsmod //该命令查看所有模块,用以检查hello是否被装入系统 # sudo rmmod hello.ko //卸载模块
三三三 六四 helloworld
sys_helloworld
使用内核编译法添加系统调用
六. 配置内核 # cd /usr/src/linux-四.一六.一0 # sudo make mrproper # sudo make clean # sudo make menuconfig 七. 编译与安装内核(与第七章类似) # sudo make -j八 # sudo make modules -j八 # sudo make modules_install # sudo make install 八. 重启系统 # uname -r 查看此时地内核版本
编译验证代码: # gcc hello.c
系统调用 调用门 原理-概述说明以及解释

系统调用调用门原理-概述说明以及解释1.引言1.1 概述系统调用是操作系统提供给应用程序使用的一种接口,它允许应用程序请求操作系统执行特定的功能或操作。
系统调用提供了应用程序与底层硬件和系统资源的交互方式,使得应用程序能够进行文件读写、网络通信、进程管理等操作。
调用门是一种机制,它可以让应用程序在用户态和内核态之间进行切换,从而实现对操作系统功能的访问。
调用门提供了一条特殊的指令,应用程序通过调用这条指令来进入内核态执行系统调用,完成需要操作系统权限才能进行的操作。
系统调用和调用门是操作系统中非常重要的概念和机制。
系统调用允许应用程序使用操作系统提供的功能,使得应用程序可以以一种安全又可控的方式访问系统资源。
调用门则是系统调用的一种实现方式,它提供了一种高效、可靠的切换机制,使得应用程序可以方便地进行系统调用,从而完成需要操作系统权限的操作。
在本文中,我们将详细介绍系统调用和调用门的原理和工作过程,探讨它们的应用场景和优势。
我们还将深入分析调用门的结构和运行机制,了解它在操作系统中的实现和使用。
最后,我们将对系统调用和调用门的重要性进行总结,并展望它们在未来的发展前景。
通过阅读本文,读者将能够更好地理解系统调用和调用门的作用和原理,为深入研究操作系统提供理论和实践基础。
【1.2 文章结构】本篇文章将从以下几个方面进行讲述系统调用和调用门的原理和应用。
首先,在引言中会概述整篇文章的主要内容和目的。
接下来,在正文部分,会详细介绍系统调用的定义和作用,包括其实现方式和分类,并对其优缺点进行探讨。
同时,还会对调用门进行概述,阐述其原理和工作过程,并介绍其在实际应用中的场景和优势。
最后,将重点解释调用门的原理,探讨引入调用门的背景,分析调用门的结构和运行机制,并讨论调用门的实现和使用。
在结论部分,会总结系统调用和调用门的重要性,并对其未来发展进行展望。
最后,以简短的结束语作为结尾,对文章内容进行总结。
通过以上的结构安排,本文将全面而系统地介绍系统调用和调用门的原理和应用,读者能够明确了解系统调用和调用门的概念、工作原理、应用场景及其未来发展前景。
用户程序系统调用的实现

系统调用实现过程系统调用是让用户态进入内核态的一种方法,系统调用的实现分为四部分:系统调用注册,系统调用触发,系统调用执行,系统调用返回。
1.系统调用注册在每种平台上,都有特定的指令可以使进程执行由用户态转换为内核态,这种指令称为操作系统陷入。
在Linux中是通过软中断来实现这种陷入的,在X86平台上,这条指令是int 0x80。
也就是说在linux中,系统调用的接口是一个中断处理函数的特例。
在linux启动过程中,对INT80进行一定的初始化:1.使用汇编子程序setup_idt(linux/arch/i386/kernel/head.S)初始化idt表(中断描述符表),这时所有的入口函数偏移地址都被设为ignore_int( setup_idt:lea ignore_int,%edxmovl $(__KERNEL_CS << 16),%eaxmovw %dx,%ax /* selector = 0x0010 = cs */movw $0x8E00,%dx /* interrupt gate - dpl=0, present */lea SYMBOL_NAME(idt_table),%edimov $256,%ecxrp_sidt:movl %eax,(%edi)movl %edx,4(%edi)addl $8,%edidec %ecxjne rp_sidtretselector = __KERNEL_CS, DPL = 0, TYPE = E, P = 1);2.Start_kernel()(linux/init/main.c)调用trap_init()(linux/arch/i386/kernel/trap.c)函数设置中断描述符表。
在该函数里,实际上是通过调用函数set_system_gate(SYSCALL_VECTOR,&system_call)来完成该项的设置的。
System-call系统调用

System-call系统调⽤⼀、系统调⽤过程1. ⽤户在进⾏系统调⽤时,通过传递⼀个系统调⽤编号,来告知内核,它所请求的系统调⽤,内核通过这个编号进⽽找到对应的处理系统调⽤的C函数。
这个系统编号,在 x86 架构上,是通过 eax 寄存器传递的。
2. 系统调⽤的过程跟其他的异常处理流程⼀样,包含下⾯⼏个步骤:(1) 将当前的寄存器上下⽂保存在内核 stack 中(这部分处理都在汇编代码中)(2) 调⽤对应的C函数去处理系统调⽤(3) 从系统调⽤处理函数返回,恢复之前保存在 stack 中的寄存器,CPU 从内核态切换到⽤户态3. 在内核中⽤于处理系统调⽤的C函数⼊⼝名称是 sys_xxx() ,xxx() 就是对应的系统调⽤,实际上会有宏在xxx()前⾯加上⼀个函数头。
在Linux 内核的代码中,这样的系统调⽤函数命名则是通过宏定义 SYSCALL_DEFINEx 来实现的,其中的 x 表⽰这个系统调⽤处理函数的输⼊参数个数。
(不同的架构会复写这个宏定义,以实现不同的调⽤规则,其中 ARM64 的宏定义在arch/arm64/include/asm/syscall_wrapper.h ⽂件中)4. 将系统调⽤编号与这些实际处理C函数联系起来的是⼀张系统调⽤表 sys_call_table 这个表具有 __NR_syscalls 个元素(⽬前kernel-5.10这个值是440)。
表中对应的 n 号元素所存储的就是 n 号系统调⽤对应的处理函数指针。
__NR_syscalls 这个宏只是表⽰这个表的⼤⼩,并不是真正的系统调⽤个数,如果对应序号的系统调⽤不存在,那么就会⽤ sys_ni_syscall 填充,这是⼀个表⽰没有实现的系统调⽤,它直接返回错误码 -ENOSYS。
//arch/arm64/kernel/sys.c#undef __SYSCALL#define __SYSCALL(nr, sym) asmlinkage long __arm64_##sym(const struct pt_regs *);#include <asm/unistd.h> //<1>#undef __SYSCALL#define __SYSCALL(nr, sym) [nr] = __arm64_##sym,typedef long (*syscall_fn_t)(const struct pt_regs *regs);const syscall_fn_t sys_call_table[__NR_syscalls] = {[0 ... __NR_syscalls - 1] = __arm64_sys_ni_syscall, //这个函数是防⽌没有实现的,直接return -ENOSYS;#include <asm/unistd.h> //<2>};<asm/unistd.h> 最终使⽤的是 <uapi/asm-generic/unistd.h> 它⾥⾯定义了 NR_xxx 和相关函数,以 getpriority 系统调⽤的实现为例://include/uapi/asm-generic/unistd.h#define __NR_getpriority 141__SYSCALL(__NR_getpriority, sys_getpriority)在位置<1>,展开为:asmlinkage long __arm64_sys_getpriority(const struct pt_regs *);在位置<2>,展开为:[141] = __arm64_sys_getpriority,最终 sys_call_table[] 下标为 141 的位置指向的函数为 __arm64_sys_getpriority⼆、系统调⽤的进⼊和退出1. 在 x86 的架构上,⽀持2种⽅式进⼊和退出系统调⽤:(1) 通过 int $0x80 触发软件中断进⼊,iret 指令退出(2) 通过 sysenter 指令进⼊,sysexit指令退出2. 在 ARM 架构上,则是通过 svc 指令进⼊系统调⽤。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
当用户态进程发出 int $0x80 指令时,CPU切换 到内核态并从地址system_call处开始执行指令 回忆进入中断和异常的那一系列步骤 跳到system_call处,此时内核态堆栈:
ss esp eflags cs eip esp 从用户态进 入中断/异常
thread info
对于第二种方法:
高效 在后续的执行过程中,会自然的捕获到出错情况(配合缺页异常)
从linux2.2开始执行第二种检查
29/37 Linux
对用户地址参数的粗略验证
在内核中,可以访问到所有的内存 要防止用户将一个内核地址作为参数传递给内核,这将 导致它借用内核代码来读写任意内存 检查方法:
通过宏access_ok( )实现,有addr和size两个参数 int access_ok(const void* addr, unsigned long size) { unsigned long a = (unsigned long) addr; if (a + size < a || a + size > current_thread_info()->addr_limit.seg) return 0; return 1; }
system_call是linux中所有系统调用的公共入口点,因此 每个系统调用至少有一个参数,即由eax传递的系统调用 eax 号
例如: 一个应用程序调用fork( )封装例程,那么在执行int $0x80之前就 把eax寄存器的值置为2(即__NR_fork)。 这个寄存器的设置是libc库中的封装例程进行的,因此程序员一 般不关心系统调用号
Linux内核源代码导读 内核源代码导读
哈尔滨工业大学(威海) 哈尔滨工业大学(威海)
嵌入式系统实验室 Fall 2011
Linux
第五讲 系统调用
Linux
Linux操作系统的结构 操作系统的结构
3/37
Linux
系统调用的意义
Unix/Linux操作系统为用户态进程与硬件设备进 行交互提供了一组接口——系统调用
这个表存放在sys_call_table数组中 有NR_syscalls个表项:第n个表项包含了系统调用号 为n的服务例程的入口地址 每个表项是个函数指针
12/37
Linux
进入和退出系统调用
两种发出系统调用的方式
执行 int $0x80汇编语言指令 执行 sysenter 汇编语言指令。在Intel Pentium II芯片中 引入了这条指令
2011-11-14 17/35 Linux
观察SAVE_ALL 观察
SAVE_ALL宏展开:
ss esp eflags cs eip ORIG_EAX es ds eax ebp edi esi edx ecx ebx 由硬件自动 压入堆栈
系统调用 号,eax
由SAVE_ALL 宏压入堆栈
18/37
在int $0x80汇编指令之前,系统调用的参数被写入 CPU的寄存器。然后,在进入内核态调用系统调用服 务例程之前,内核再把存放在CPU寄存器中的参数拷 贝到内核态堆栈中。因为毕竟服务例程是C函数,它 还是要到堆栈中去寻找参数的
也不直接用内 核态堆栈传参 用户态堆栈 不使用用户 态堆栈传参 内核态堆栈
9/37
Linux
系统调用处理程序也和其他异常处理程序的结构 类似
在进程的内核态堆栈中保存大多数寄存器的内容 (即保存 保存恢复进程到用户态执行所需要的上下文 上下文) 保存 上下文 调用相应的系统调用服务例程 系统调用服务例程的相应C函数处理系统 调用 系统调用服务例程 调用
xyz( )系统调用对应的服务例程名字通常是sys_xyz( )
28/37
Linux
只要一个参数指定的是地址,那么内核必须检查它是否在 这个进程的地址空间之内,有两种验证方法: 验证这个线性地址是否属于进程的地址空间,并且是否有 合理的访问权限 仅仅验证这个线性地址小于PAGE_OFFSET(即没有落在 内核的线性地址空间内) 对于第一种方法:
费时 大多数情况下,不必要
cmpl $NR_syscalls, %eax jb nobadsys movl $(-ENOSYS), 24(%esp) jmp resume_userspace nobadsys:
#曾保存eax的栈单元
5. 最后,调用与系统调用号对应的特定服务例程
call *sys_call_table(0, %eax, 4) #因为分派表每表项4字节,所以系统调用号乘以4,再加上 分派表起始地址,再从这个地址单元获取服务例程指针
使用eax寄存器
8/37
Linux
系统调用的返回值
所有的系统调用返回一个整数值。
正数或0表示系统调用成功结束 负数表示一个出错条件
这里的返回值与封装例程返回值的约定不同
内核没有设置或使用errno变量 封装例程在系统调用返回取得返回值之后设置errno
当系统调用出错时,返回的那个负值将要存放在errno变量中 返回给应用程序
15/37
Linux
system_call()函数 函数
系统调用公共入口system_call( )函数的流程
1. 在内核栈中保存除eflags,cs,eip,ss和esp以外所有寄 存器
system_call: pushl %eax #系统调用号 SAVE_ALL #SAVE_ALL宏,寄存器内容压栈 movl $0xfffffe00, %ebx #存放thread_info地址 andl %esp, %ebx
20/37
Linux
很多系统调用需要不止一个参数
普通C函数的参数传递是通过把参数值写入堆栈(用户 态堆栈或内核态堆栈)来实现的。但因为系统调用是 一种横跨用户和内核两大陆地的特殊函数,所以既不 能使用用户态的堆栈也不能直接使用内核态堆栈
用户态C函数
用户态堆栈
内核态C函数 内核态堆栈
?
21/37
Linux
2. 在ebx寄存器中存放当前进程的thread_info结构的地 址(通过将内核栈指针esp取整到8KB倍数获得)
16/37 Linux
3. 检查thread_info结构flag字段的TIF_SYCALL_TRACE和 TIF_SYSCALL_AUDIT标志位 4. 检查用户态进程传递过来的系统调用号的有效性
对应地,两种方式从系统调用退出,CPU切回到 用户态
执行 iret汇编语言指令 执行 sysexit 汇编语言指令
13/37
Linux
通过int 通过 $0x80指令发出系统调用 指令发出系统调用
内核初始化期间调用trap_init()函数建立IDT表中向量128 对应的表项,语句如下:
其中,SYSCALL_VECTOR被定义为0x80 该调用把下列值存入这个系统门描述符的相应字段: 段选择符:内核代码段__KERNEL_CS的段选择符 段内偏移:指向system_call()异常处理程序的入口地址 类型:置为15。表示这个异常是一个陷阱,相应的处理 程序不禁止可屏蔽中断 DPL(描述符特权级):置为3。这就允许用户态进程访问 这个门,即在用户程序中使用int $0x80是合法的
退出:用保存在内核栈中的值加载寄存器,CPU从内 核态切换回用户态
10/37
Linux
应用程序、封装例程、 应用程序、封装例程、系统调用处理程序及系统调用服务例程之间的关系
11/37
Linux
为了把系统调用号与相应的服务例程关联起来, 内核利用了一个系统调用分派表(dispatch table) 。
把用户从底层的硬件编程中解放出来 极大的提高了系统的安全性 使用户程序具有可移植性,便于统一内核接口
4/37
Linux
API和系统调用 和系统调用
应用编程接口(Application Program Interface, API) 和系统调用是不同的
API只是一个函数定义 系统调用通过“软”中断向内核发出一个明确的请求
24/37 Linux
SAVE_ALL后的堆栈情况 后的堆栈情况
SAVE_ALL宏展开:
ss esp eflags cs eip ORIG_EAX es ds eax ebp edi esi edx ecx ebx
25/37
thread info
Linux
在有些系统调用中,C库接口中尽管没有提 供参数,但是在内核中却会依赖保存的 pt_regs信息,例如 fork() sys_clone() do_frok()
相同点
都是从用户空间进入系统空间的手段
7/35
Linux
系统调用程序及服务例程
当用户态进程调用一个系统调用时,CPU切换到 内核态并开始执行一个内核函数。
在Linux中是通过执行int $0x80来执行系统调用的, 这条汇编指令产生向量为128的编程异常
传参:内核实现了很多不同的系统调用,进程必 须指明需要哪个系统调用,这需要传递一个名为 系统调用号的参数 系统调用号
26/37
Linux
传递返回值
服务例程的返回值是将会被写入eax寄存器中 这个是在执行“return n”指令时,由C编译器自动完 成的
27/37
Linux
验证参数
在内核打算满足用户的请求之前,必须仔细的检查所有 的系统调用参数
比如前面的write()系统调用,fd参数是一个文件描述符, sys_write()必须检查这个fd是否确实是以前已打开文件的 一个文件描述符,进程是否有向fd指向的文件的写权限, 如果有条件不成立,那这个处理程序必须返回一个负数
cli movl 8(%ebp), %ecx testw $oxffff,%cx #恢复保存在内核栈中寄存器, je restore_all 并执行iret指令 19/35 Linux