(1)-系统调用篇
- 格式:doc
- 大小:89.00 KB
- 文档页数:9
系统调用知识点总结一、系统调用的概念系统调用是操作系统内核提供给用户程序的接口,用于访问操作系统内核提供的服务和资源。
操作系统提供了各种系统调用,包括文件操作、进程管理、网络通信、内存管理、设备管理等。
用户程序通过系统调用可以向操作系统请求服务,比如打开文件、创建进程、发送网络数据等。
系统调用是用户程序和操作系统内核之间的桥梁,它为用户程序提供了访问操作系统内核功能的途径。
二、系统调用的实现原理系统调用的实现原理涉及到用户态和内核态的切换。
当用户程序执行系统调用时,会触发处理器从用户态切换到内核态,然后执行相应的内核代码。
在Linux系统中,系统调用的实现原理一般包括以下几个步骤:1. 用户程序通过系统调用指令(比如int 0x80或syscall指令)发起系统调用请求。
2. 处理器从用户态切换到内核态,执行相应的内核代码。
3. 内核根据系统调用号(syscall number)找到相应的系统调用处理函数。
4. 内核执行系统调用处理函数,完成相应的操作。
5. 内核将处理结果返回给用户程序,然后从内核态切换回用户态。
三、系统调用的调用方式系统调用的调用方式包括直接调用、库函数封装和系统命令等。
用户程序可以通过直接调用系统调用指令来执行系统调用,也可以通过库函数封装的方式来调用系统调用,比如C 标准库中的文件操作函数(如open、read、write等)就是封装了系统调用的库函数,用户程序可以直接调用这些库函数来进行文件操作。
此外,用户程序还可以通过系统命令来触发系统调用,比如在命令行中使用cat命令来读取文件就是通过系统命令来触发系统调用。
四、常用系统调用常用系统调用包括文件操作、进程管理、网络通信、内存管理、设备管理等。
在Linux系统中,常见的系统调用包括:1. 文件操作系统调用:open、read、write、close、lseek等。
2. 进程管理系统调用:fork、exec、wait、exit等。
实验报告一、理论分析(分值:20%)【从操作系统原理(理论)的角度阐述系统功能调用的过程】1、函数声明中都有asmlinkage限定词,用于通知编译器仅从栈中提取该函数的参数。
2、系统调用getXXX()在内核中被定义为sys_getXXX()。
系统调用号:在linux中,每个系统调用都赋予一个系统调用号,通过这个独一无二的号就可以关联系统调用。
当用户空间的进程执行一个系统调用的时候,这个系统调用号就被用来指明到底要执行哪个系统调用;进程不会提及系统调用的名称。
系统调用号一旦分配就不能再有任何变更(否则编译好的应用程序就会崩溃),如果一个系统调用被删除,它所占用的系统调用号也不允许被回收利用。
Linux 有一个"未使用"系统调用sys_ni_syscall(),它除了返回-ENOSYS外不做任何其他工作,这个错误号就是专门针对无效的系统调用而设的。
内核记录了系统调用表中所有已注册过的系统调用的列表,存储在sys_call_table中。
它与体系结构有关,一般在entry.s中定义。
这个表中为每一个有效的系统调用指定了唯一的系统调用号。
3、Makefile控制着整个内核的编译,在每个子目录下调用编译.c 文件,生成.o文件,生成新的内核。
会把新编译的sys_hello内核加入到系统调用中。
系统调用表geditsyscall_32.tbl中加入354 i386 hello sys_hello,当系统调用时可以在调用表中找到系统调用的号。
4、在syscalls.h中添加定义的内容的引用函数。
5、编译执行结果。
二、设计与实现(分值:30%)【阐述在Linux中添加系统功能调用的方法】1、在内核目录下创建hello文件夹 mkdir hello2、进入hello文件夹 cd hello3、创建hello.c的文件 vim hello.c4、加入代码#include <linux/kernel.h>asmlinkage long sys_hello(void){printk(“Hello world\n”);return 0;}5、在hello文件夹下添加Makefile文件 vim Makefile在文件里添加 obj-y := hello.o6、返回内核的根目录中,打开Makefile文件,在842行添加代码 vim Makefile 将core-y += kernel/ mm/ fs/ ipc/ security/ crypto/ block/改为core-y += kernel/ mm/ fs/ ipc/ security/ crypto/ block/ hello/ 7、打开系统调用表 cd arch/x86/syscallsVim syscall_32.tbl在文件最后一行添加354 i386 hello sys_hello8、在调用函数名的文件中加入添加的函数cd include/linux/Vim syscalls.hasmlinkage long sys_hello(void);9、进行编译cd /usr/src/linux-3.16/sudo make menuconfigsudo make oldconfigmake -j410、安装编译好的内核sudo make modules_install installshutdown -r now重启后选择新的内核载入测试uname -r11、编写测试文件hello.c#include <stdio.h>#include <linux/kernel.h>#include <sys/syscall.h>#include <unistd.h>int main(){printf("%ld\n", syscall(354));return 0;}12、编译测试gcc -o hello.c hello./hello.outdmesg三、实验结果(分值:10%)【对实验结果进行简要分析和说明】测试文件调用系统调用号354,并且将返回结果0输出,dmesg可以将开机显示结果输出查看。
系统调用原理(最新版)目录1.系统调用的概念与作用2.系统调用的分类3.系统调用的实现原理4.系统调用的应用实例5.系统调用与用户程序的关系正文一、系统调用的概念与作用系统调用(System Call)是操作系统向用户程序提供的一种申请操作系统服务的接口。
用户程序通过系统调用请求操作系统的帮助,完成文件操作、进程管理、内存管理等任务。
系统调用是操作系统与用户程序之间的桥梁,使得用户程序可以更加高效地使用操作系统的功能。
二、系统调用的分类根据系统调用的功能,可以将其分为以下几类:1.文件操作:包括创建文件、打开文件、读取文件、写入文件、关闭文件等。
2.进程管理:包括创建进程、终止进程、切换进程、获取进程信息等。
3.内存管理:包括分配内存、释放内存、复制内存等。
4.设备管理:包括设备分配、设备回收、设备操作等。
5.其他系统服务:包括获取系统时间、随机数生成等。
三、系统调用的实现原理系统调用的实现原理可以分为以下几个步骤:1.用户程序调用库函数:用户程序调用库函数,如 C 语言的标准库函数,来实现文件操作、进程管理等功能。
2.库函数调用系统调用:库函数通过调用系统调用来请求操作系统提供相应的服务。
3.操作系统处理:操作系统根据系统调用的类型,执行相应的操作,如文件操作、进程管理等。
4.返回结果:操作系统将处理结果返回给库函数。
5.库函数返回结果给用户程序:库函数将操作系统返回的结果返回给用户程序。
四、系统调用的应用实例以下是一个简单的系统调用应用实例,使用 C 语言编写,通过系统调用实现文件的创建和写入功能:```c#include <stdio.h>#include <unistd.h>int main() {int fd = open("example.txt", O_CREAT | O_TRUNC | O_WRONLY, 0644);if (fd < 0) {perror("open");return -1;}write(fd, "Hello, system call!", 25);close(fd);return 0;}```五、系统调用与用户程序的关系系统调用是操作系统为用户程序提供的一种服务接口,用户程序通过系统调用来请求操作系统的帮助,实现文件操作、进程管理等功能。
系统调用原理摘要:一、系统调用简介1.系统调用的概念2.系统调用的重要性二、系统调用原理1.系统调用接口2.系统调用处理程序3.系统调用实现原理a.系统调用号b.参数传递c.返回值d.系统调用实现三、系统调用应用1.文件操作2.进程管理3.内存管理4.设备驱动四、系统调用与用户程序的关系1.用户程序调用系统调用2.系统调用将请求传递给内核3.内核处理请求并返回结果正文:一、系统调用简介系统调用是操作系统提供给用户程序的一组应用编程接口,它允许用户程序在需要时请求操作系统内核提供的服务。
系统调用为用户程序和操作系统内核之间提供了一个抽象层,使得用户程序无需关心底层的实现细节,只需通过调用相应的系统调用函数来完成所需功能。
这种设计使得用户程序和操作系统内核的开发与维护变得更加容易。
二、系统调用原理1.系统调用接口系统调用接口是用户程序与操作系统内核之间交互的桥梁,它定义了用户程序如何请求操作系统内核提供的服务。
通常,每种操作系统都有一组标准的系统调用接口,这些接口遵循一定的规范,以便用户程序能够方便地调用。
2.系统调用处理程序当用户程序调用一个系统调用时,操作系统内核会根据系统调用接口找到对应的系统调用处理程序。
系统调用处理程序负责处理用户程序的请求,并将其转换为内核可以理解和执行的指令。
3.系统调用实现原理系统调用的实现原理包括系统调用号、参数传递、返回值和系统调用实现。
a.系统调用号:每种系统调用都有一个唯一的编号,用户程序通过这个编号来请求相应的服务。
b.参数传递:用户程序调用系统调用时,需要传递所需的参数。
这些参数可以通过栈来传递,也可以通过寄存器来传递。
c.返回值:系统调用处理程序执行完成后,会返回一个结果值。
这个结果值可以通过栈来返回,也可以通过寄存器来返回。
d.系统调用实现:系统调用实现是操作系统内核中实际执行处理用户程序请求的部分。
它根据用户程序的请求执行相应的操作,如文件读写、进程创建等。
C语言系统调用详解在计算机编程中,C语言是一种常用的编程语言,它具备底层编程的能力,并且被广泛应用于操作系统和系统编程中。
在C语言中,系统调用是一种与操作系统交互的重要机制,它允许程序访问操作系统提供的各种服务和资源。
本文将详细介绍C语言中的系统调用,包括其概念、基本用法和常见示例。
一、概念解析系统调用是指程序通过调用操作系统提供的函数来访问操作系统功能的过程。
在C语言中,系统调用通常是以函数的形式存在,通过调用这些函数可以实现对底层资源和服务的访问。
系统调用提供了一种独立于具体硬件和操作系统的编程接口,使得程序可以在不同的操作系统和硬件平台上运行。
二、基本用法在C语言中,使用系统调用需要包含相应的头文件,并且通过函数调用来实现。
下面是一些常见的系统调用函数及其用法:1. 文件操作:a. 打开文件:使用open()函数可以打开一个文件,并返回一个文件描述符,用于后续对文件的读写操作。
b. 读取文件:使用read()函数可以从文件中读取数据,并将其存储到指定的缓冲区中。
c. 写入文件:使用write()函数可以将数据写入文件中。
d. 关闭文件:使用close()函数可以关闭已打开的文件。
2. 进程控制:a. 创建新进程:使用fork()函数可以创建一个新的进程,该新进程是父进程的副本。
b. 执行新程序:使用exec()函数族中的某个函数可以执行一个新的程序,替换当前进程的地址空间。
c. 等待子进程结束:使用wait()函数可以让父进程等待子进程的结束,并获取子进程的返回状态。
3. 网络通信:a. 创建套接字:使用socket()函数可以创建一个网络套接字,用于网络通信。
b. 绑定套接字:使用bind()函数可以将套接字与指定的IP地址和端口绑定。
c. 监听连接请求:使用listen()函数可以使套接字处于监听状态,等待其他程序的连接请求。
d. 接受连接:使用accept()函数可以接受其他程序的连接请求。
了解各种系统调用助你成为一名Linux下编程高手什么是系统调用?Linux内核中设置了一组用于实现各种系统功能的子程序,称为系统调用。
用户可以通过系统调用命令在自己的应用程序中调用它们。
从某种角度来看,系统调用和普通的函数调用非常相似。
区别仅仅在于,系统调用由操作系统核心提供,运行于核心态;而普通的函数调用由函数库或用户自己提供,运行于用户态。
随Linux核心还提供了一些C语言函数库,这些库对系统调用进行了一些包装和扩展,因为这些库函数与系统调用的关系非常紧密,所以习惯上把这些函数也称为系统调用。
为什么要用系统调用?实际上,很多已经被我们习以为常的C语言标准函数,在Linux平台上的实现都是靠系统调用完成的,所以如果想对系统底层的原理作深入的了解,掌握各种系统调用是初步的要求。
进一步,若想成为一名Linux下编程高手,也就是我们常说的Hacker,其标志之一也是能对各种系统调用有透彻的了解。
即使除去上面的原因,在平常的编程中你也会发现,在很多情况下,系统调用是实现你的想法的简洁有效的途径,所以有可能的话应该尽量多掌握一些系统调用,这会对你的程序设计过程带来意想不到的帮助。
系统调用是怎么工作的?一般的,进程是不能访问内核的。
它不能访问内核所占内存空间也不能调用内核函数。
CPU 硬件决定了这些(这就是为什么它被称作"保护模式")。
系统调用是这些规则的一个例外。
其原理是进程先用适当的值填充寄存器,然后调用一个特殊的指令,这个指令会跳到一个事先定义的内核中的一个位置(当然,这个位置是用户进程可读但是不可写的)。
在Intel CPU中,这个由中断0x80实现。
硬件知道一旦你跳到这个位置,你就不是在限制模式下运行的用户,而是作为操作系统的内核--所以你就可以为所欲为。
进程可以跳转到的内核位置叫做sysem_call。
这个过程检查系统调用号,这个号码告诉内核进程请求哪种服务。
然后,它查看系统调用表(sys_call_table)找到所调用的内核函数入。
操作系统中的系统调用在操作系统中,系统调用是一种重要的机制,它允许用户程序与操作系统内核交互,获取系统资源和执行特权操作。
系统调用提供了一组函数接口,通过这些接口,用户程序可以请求操作系统完成特定的任务或操作。
系统调用的作用主要有以下几个方面:1. 资源管理:操作系统维护着各种资源,如内存、文件、设备等。
用户程序通过系统调用可以申请和释放这些资源。
例如,用户程序可以通过系统调用申请内存空间或打开文件。
2. 进程控制:系统调用支持创建、销毁和管理进程。
用户程序可以通过系统调用请求操作系统创建新的进程或终止已有的进程。
通过系统调用,用户程序可以获取进程的状态信息,如进程ID、父进程ID等。
3. 文件操作:操作系统提供了一组函数接口,使用户程序可以读取和写入文件。
用户程序可以通过系统调用打开、关闭、读取和写入文件。
系统调用还支持文件的定位操作,如查找、移动文件指针等。
4. 设备控制:系统调用提供了对设备的操作接口。
用户程序可以通过系统调用请求操作系统打开、关闭设备,进行设备的读写操作。
例如,用户程序可以通过系统调用向打印机发送数据。
5. 通信与同步:系统调用支持进程间的通信和同步操作。
用户程序可以通过系统调用创建进程间的管道或共享内存,实现进程间的数据传输和共享。
系统调用还支持进程的同步操作,如信号量、互斥锁等。
在使用系统调用时,用户程序需要通过指定系统调用号来调用具体的系统调用函数。
系统调用号是一个唯一的标识符,用于指明用户程序要调用的系统调用。
操作系统根据系统调用号来确定用户程序要执行的系统操作,并相应地进行处理。
系统调用的具体实现需要操作系统内核提供相应的功能函数。
操作系统内核会根据系统调用号来调用相应的功能函数,完成用户程序的请求操作,并返回结果给用户程序。
总之,系统调用是操作系统中一种重要的机制,它允许用户程序与操作系统内核进行交互,获取系统资源和执行特权操作。
通过系统调用,用户程序可以实现资源管理、进程控制、文件操作、设备控制以及进程间通信和同步等功能。
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.系统调用的实现方式4.系统调用的应用实例5.系统调用的发展趋势正文一、系统调用的定义与作用系统调用(System Call)是操作系统提供给应用程序的一组访问操作系统服务的接口。
通过系统调用,应用程序可以请求操作系统完成某些特定的任务,例如文件操作、进程管理等。
这些任务通常涉及到底层硬件资源或者操作系统内部的核心功能,因此需要通过系统调用来实现,以确保应用程序与操作系统之间的隔离和安全性。
二、系统调用的分类系统调用可以根据其功能分为以下几类:1.文件操作:包括创建、删除、读取、写入文件等。
2.进程管理:包括创建、撤销、切换进程等。
3.存储管理:包括内存分配、内存释放等。
4.设备管理:包括设备创建、设备删除、设备操作等。
5.通信与网络:包括套接字操作、进程间通信等。
6.系统安全:包括身份验证、权限设置等。
三、系统调用的实现方式系统调用通常通过函数库的形式提供给应用程序,应用程序通过调用这些函数来实现对应的功能。
在实现上,系统调用可以分为以下两种方式:1.系统调用中断:应用程序在执行过程中,遇到需要操作系统完成的任务时,会通过陷入(trap)指令产生一个中断,跳转到操作系统内核中执行相应的系统调用处理函数。
处理完毕后,操作系统会将控制权返回给应用程序。
2.系统调用陷阱:与系统调用中断类似,不同之处在于应用程序主动请求陷入操作系统内核,执行相应的系统调用处理函数。
在处理完毕后,操作系统会返回控制权给应用程序。
四、系统调用的应用实例以 Linux 系统为例,其系统调用包括如下几个常见函数:1.open():打开文件。
2.read():读取文件内容。
3.write():写入文件内容。
4.close():关闭文件。
5.fork():创建子进程。
6.exec():执行一个可执行文件。
五、系统调用的发展趋势随着计算机技术的发展,系统调用的功能和数量在不断增加,以满足日益复杂的应用程序需求。
操作系统中系统调用实例-回复“操作系统中系统调用实例”操作系统中的系统调用对于程序的执行是至关重要的。
在本文中,我们将探讨什么是系统调用,为什么它们是必不可少的,以及一些在实际中常见的系统调用示例。
系统调用可以被视为操作系统提供给应用程序的接口。
通过系统调用,应用程序可以请求操作系统执行某些特权操作,例如文件操作、进程管理和网络通信。
系统调用提供了应用程序与硬件和操作系统内核之间的桥梁,允许应用程序直接访问操作系统所提供的服务和资源。
系统调用是操作系统与应用程序之间的必备通信机制。
在应用程序执行期间,系统调用允许应用程序通过操作系统接收输入和发送输出。
系统调用还允许应用程序访问操作系统的完整功能,例如内存管理、进程调度和设备驱动程序。
下面是一些常见的系统调用示例:1. 文件操作系统调用:- 打开文件(open):应用程序使用此调用请求操作系统打开一个文件,并返回一个文件描述符(file descriptor),以供应用程序读取和写入文件。
- 读取文件(read):应用程序使用此调用从打开的文件中读取数据。
- 写入文件(write):应用程序使用此调用将数据写入打开的文件。
2. 进程管理系统调用:- 创建进程(fork):应用程序使用此调用请求操作系统创建一个新的子进程,以便并行执行其他任务。
- 终止进程(exit):应用程序使用此调用请求操作系统终止当前进程的执行。
3. 网络通信系统调用:- 建立网络连接(socket):应用程序使用此调用请求操作系统建立一条与远程主机的网络连接。
- 发送数据(send):应用程序使用此调用将数据发送到已建立的网络连接。
- 接收数据(receive):应用程序使用此调用从已建立的网络连接接收数据。
系统调用的实现方式因操作系统而异。
通常,操作系统提供一组由操作系统内核实现的函数,这些函数被应用程序调用以执行特定任务。
在内核中,系统调用被用作被应用程序触发的中断或异常的处理程序。
1、什么是系统调用答:系统调用是指操作系统提供给用户程序调用的一组“特殊”接口,用户程序可以通过这组“特殊”接口来获得操作系统内核提供的服务。
例如用户可以通过进程控制相关的系统调用来创建进程、实现进程调度、进程管理等。
7、进程∙执行态:该进程正在运行,即进程正在占用CPU。
∙就绪态:进程已经具备执行的一切条件,正在等待分配CPU的处理时间片。
∙等待态:进程不能使用CPU,若等待事件发生(等待的资源分配到)则可将其唤醒。
11、什么是嵌入式系统?其特点有些什么?P94-96答:嵌入式系统是指以应用为中心,以计算机技术为基础,软件硬件可剪裁,适应应用系统对功能、可靠性、成本、体积、功耗严格要求的专用计算机系统。
将一套计算机控制系统嵌入到已具有某种完整的特定功能的系统内,以实现对原有系统的计算机控制,此时将这个新系统叫做嵌入式系统。
它通常由特定功能模块和计算机控制模块组成,主要由嵌入式微处理器、外围硬件设备、嵌入式操作系统以及用户应用软件组成。
它具有“嵌入性”、“专用性”与“计算机系统”的三个基本要素。
特点: 1、面向特定应用的特点。
2、嵌入式系统的硬件和软件都必须进行高效地设计,量体裁衣、去除冗余,力争在同样的硅片面积上实现更高的性能。
3、嵌入式系统是将先进的计算机技术、半导体技术和电子技术与各个行业的具体应用相结合后的产物。
4、为了提高执行速度和系统可靠性,嵌入式系统中的软件一般都固化在存储器芯片中或单片机本身,而不是存储于磁盘中。
5、嵌入式开发的软件代码尤其要求高质量、高可靠性。
6、嵌入式系统本身不具备二次开发能力。
13、嵌入式系统的BooTLoader的功能是什么?写出三种你所熟悉的引导程序BootLoader。
功能: Bootloader就是在操作系统内核运行之前运行的一段程序,它类似于PC机中的BIOS 程序。
通过这段程序,可以完成硬件设备的初始化,并建立内存空间的映射关系,从而将系统的软硬件环境带到一个合适的状态,为最终加载系统内核做好准备。
系统调⽤的实现原理【转】在看《unix/linux编程实践教程》时,忽然意识到,系统调⽤是如何实现的?在实际编程中,往往是调⽤相关的函数,⽐如open(),read()等等。
但是调⽤这些函数怎么可能让程序的运⾏在⽤户空间和内核空间切换呢?看了下⾯的⽂章,才知道怎么回事。
让我想到了《计算机组成原理》中讲到的东西。
原⽂地址:系统调⽤1什么是系统调⽤系统调⽤,顾名思义,说的是操作系统提供给⽤户程序调⽤的⼀组“特殊”接⼝。
⽤户程序可以通过这组“特殊”接⼝来获得操作系统内核提供的服务,⽐如⽤户可以通过⽂件系统相关的调⽤请求系统打开⽂件、关闭⽂件或读写⽂件,可以通过时钟相关的系统调⽤获得系统时间或设置定时器等。
从逻辑上来说,系统调⽤可被看成是⼀个内核与⽤户空间程序交互的接⼝——它好⽐⼀个中间⼈,把⽤户进程的请求传达给内核,待内核把请求处理完毕后再将处理结果送回给⽤户空间。
系统服务之所以需要通过系统调⽤来提供给⽤户空间的根本原因是为了对系统进⾏“保护”,因为我们知道Linux的运⾏空间分为内核空间与⽤户空间,它们各⾃运⾏在不同的级别中,逻辑上相互隔离。
所以⽤户进程在通常情况下不允许访问内核数据,也⽆法使⽤内核函数,它们只能在⽤户空间操作⽤户数据,调⽤⽤户空间函数。
⽐如我们熟悉的“hello world”程序(执⾏时)就是标准的⽤户空间进程,它使⽤的打印函数printf就属于⽤户空间函数,打印的字符“hello word”字符串也属于⽤户空间数据。
但是很多情况下,⽤户进程需要获得系统服务(调⽤系统程序),这时就必须利⽤系统提供给⽤户的“特殊接⼝”——系统调⽤了,它的特殊性主要在于规定了⽤户进程进⼊内核的具体位置;换句话说,⽤户访问内核的路径是事先规定好的,只能从规定位置进⼊内核,⽽不准许肆意跳⼊内核。
有了这样的陷⼊内核的统⼀访问路径限制才能保证内核安全⽆虞。
我们可以形象地描述这种机制:作为⼀个游客,你可以买票要求进⼊野⽣动物园,但你必须⽼⽼实实地坐在观光车上,按照规定的路线观光游览。
xv6的作业翻译——作业1-shell和系统调⽤Xv6的lectureLEC 1 Operating systemsL1: O/S overviewL1:O/S概述* 6.828 goals6.828的⽬标Understand operating systems in detail by designing and implementing a small O/S通过设计并实现⼀个⼩型的系统在细节上理解操作系统。
Hands-on experience with building systems("Applying 6.033")构建系统来完成练习。
* What do applications want from an O/S?从程序⾓度看操作系统需要提供什么?* Abstract the hardware for convenience and portability对硬件进⾏抽象使其⽅便使⽤和移植* Multiplex the hardware among multiple applications在多个程序间复⽤硬件资源* Isolate applications to contain bugs隔离程序阻⽌错误蔓延* Allow sharing among applications允许程序间共享资源* What is an OS?什么是操作系统?* e.g. OSX, Windows, Linux例如OSX、Windows和linux* the small view: a h/w management library狭义:硬件的管理库* the big view: physical machine -> abstract one w/ better properties⼴义:物理机器 -> 具有更好特性的抽象机器* Organization: layered picture结构:分层图像h/w: CPU, mem, disk硬件:CPU、内存、磁盘kernel: [various services]内核:多种服务user: applications, e.g. vi and gcc⽤户:应⽤程序,如vi和gccwe care a lot about the interfaces and internel kernel structure 我们更关⼼接⼝和内核的内部结构* What services does an O/S kernel typically provide?⼀个典型的操作系统内核需要提供什么服务?* processes进程* memory内存管理* file contents⽂件内容* directories and file names⽬录和⽂件名称* security安全* many others: users, IPC, network, time, terminals其他:⽤户、IPC、⽹络、时间和终端* What does an O/S abstraction look like?操作系统的抽象看来什么样?* Applications only see them via system calls应⽤程序仅能通过系统调⽤看到它们。
系统调⽤(⼀):重写WriteProcessMemory BOOL WriteProcessMemory(HANDLE hProcess,LPVOID lpBaseAddress,LPCVOID lpBuffer,SIZE_T nSize,SIZE_T *lpNumberOfBytesWritten);⾸先分析⼀下kernel32中的WriteProcessMemory,这⾥不管检查部分push eax ; NumberOfBytesWrittenpush ebx ; NumberOfBytesToWritepush [ebp+Buffer] ; Bufferpush [ebp+lpBaseAddress] ; BaseAddresspush edi ; ProcessHandlecall ds:NtWriteVirtualMemory分析ntdll中的NtWriteVirtualMemoryZwWriteVirtualMemory proc nearmov eax, 115h ; NtWriteVirtualMemorymov edx, 7FFE0300hcall dword ptr [edx]retn 14h这⾥⽤到了_KUSER_SHARED_DATA结构kd> dt _KUSER_SHARED_DATAnt!_KUSER_SHARED_DATA+0x000 TickCountLow : Uint4B+0x004 TickCountMultiplier : Uint4B+0x008 InterruptTime : _KSYSTEM_TIME+0x014 SystemTime : _KSYSTEM_TIME+0x020 TimeZoneBias : _KSYSTEM_TIME+0x02c ImageNumberLow : Uint2B+0x02e ImageNumberHigh : Uint2B+0x030 NtSystemRoot : [260] Uint2B+0x238 MaxStackTraceDepth : Uint4B+0x23c CryptoExponent : Uint4B+0x240 TimeZoneId : Uint4B+0x244 Reserved2 : [8] Uint4B+0x264 NtProductType : _NT_PRODUCT_TYPE+0x268 ProductTypeIsValid : UChar+0x26c NtMajorVersion : Uint4B+0x270 NtMinorVersion : Uint4B+0x274 ProcessorFeatures : [64] UChar+0x2b4 Reserved1 : Uint4B+0x2b8 Reserved3 : Uint4B+0x2bc TimeSlip : Uint4B+0x2c0 AlternativeArchitecture : _ALTERNATIVE_ARCHITECTURE_TYPE+0x2c8 SystemExpirationDate : _LARGE_INTEGER+0x2d0 SuiteMask : Uint4B+0x2d4 KdDebuggerEnabled : UChar+0x2d5 NXSupportPolicy : UChar+0x2d8 ActiveConsoleId : Uint4B+0x2dc DismountCount : Uint4B+0x2e0 ComPlusPackage : Uint4B+0x2e4 LastSystemRITEventTickCount : Uint4B+0x2e8 NumberOfPhysicalPages : Uint4B+0x2ec SafeBootMode : UChar+0x2f0 TraceLogging : Uint4B+0x2f8 TestRetInstruction : Uint8B+0x300 SystemCall : Uint4B+0x304 SystemCallReturn : Uint4B+0x308 SystemCallPad : [3] Uint8B+0x320 TickCount : _KSYSTEM_TIME+0x320 TickCountQuad : Uint8B+0x330 Cookie : Uint4B+300处为系统调⽤,有两种⽅式KiFastSystemCallKiIntSystemCallKiIntSystemCall proc neararg_4= byte ptr 8lea edx, [esp+arg_4]int 2EhretnKiFastSystemCall proc nearmov edx, espsysenter到这栈为分析结束,重写WriteProcessMemory的三环部分#include "stdafx.h"#include <windows.h>void __declspec(naked) MyIntWriteProcessMemory(HANDLE hProcess, LPVOID lpBaseAddress,LPCVOID lpBuffer,SIZE_T nSize,SIZE_T *lpNumberOfBytesWritten){__asm{mov eax,0x115lea edx,[esp+4]int 0x2eret}}void __declspec(naked) MyFastWriteProcessMemory(HANDLE hProcess, LPVOID lpBaseAddress,LPCVOID lpBuffer,SIZE_T nSize,SIZE_T *lpNumberOfBytesWritten){__asm{push retaddrmov eax,0x115mov edx,esp_emit 0x0f_emit 0x34retaddr:ret}}int main(int argc, char* argv[]){DWORD X=0xabcd;DWORD addr=0;DWORD dwProcessId = 0;DWORD check=0;printf("input Pid:");scanf("%x",&dwProcessId);printf("input addr:");scanf("%x",&addr);HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS,FALSE,dwProcessId);//MyIntWriteProcessMemory(hProcess,(char*)addr,(char*)&X,(SIZE_T)4,(SIZE_T*)NULL); MyFastWriteProcessMemory(hProcess,(char*)addr,(char*)&X,(SIZE_T)4,(SIZE_T*)NULL); return 0;}测试代码#include "stdafx.h"#include <windows.h>int main(int argc, char* argv[]){DWORD X=0x1234;DWORD PID=GetCurrentProcessId();printf("PID = %x\n",PID);printf("X = %x\n",X);printf("Xaddr = %x\n",&X);getchar();printf("X = %x\n",X);getchar();return 0;}有两点要注意1,在fast时要注意栈的结构2,vc6脑瘫编译器把push retaddr编译成了push [retaddr]。
Linux系统调⽤所谓系统调⽤是指操作系统提供给⽤户程序调⽤的⼀组“特殊”接⼝,⽤户程序可以通过这组“特殊”接⼝来获得操作系统内核提供的服务。
例如⽤户可以通过进程控制相关的系统调⽤来创建进程、实现进程调度、进程管理等。
在这⾥,为什么⽤户程序不能直接访问系统内核提供的服务呢?这是由于在 Linux 中,为了更好地保护内核空间,将程序的运⾏空间分为内核空间和⽤户空间(也就是常称的内核态和⽤户态),它们分别运⾏在不同的级别上,在逻辑上是相互隔离的。
因此,⽤户进程在通常情况下不允许访问内核数据,也⽆法使⽤内核函数,它们只能在⽤户空间操作⽤户数据,调⽤⽤户空间的函数。
但是,在有些情况下,⽤户空间的进程需要获得⼀定的系统服务(调⽤内核空间程序),这时操作系统就必须利⽤系统提供给⽤户的“特殊接⼝”——系统调⽤规定⽤户进程进⼊内核空间的具体位置。
进⾏系统调⽤时,程序运⾏空间需要从⽤户空间进⼊内核空间,处理完后再返回到⽤户空间。
Linux 系统调⽤部分是⾮常精简的系统调⽤(只有 250 个左右),它继承了 UNIX 系统调⽤中最基本和最有⽤的部分。
这些系统调⽤按照功能逻辑⼤致可分为进程控制、进程间通信、⽂件系统控制、系统控制、存储管理、⽹络管理、socket 控制、⽤户管理等⼏类。
在 Linux 中对⽬录和设备的操作都等同于⽂件的操作,因此,⼤⼤简化了系统对不同设备的处理,提⾼了效率。
Linux 中的⽂件主要分为 4种:普通⽂件、⽬录⽂件、链接⽂件和设备⽂件。
那么,内核如何区分和引⽤特定的⽂件呢?这⾥⽤到的就是⼀个重要的概念——⽂件描述符。
对于 Linux ⽽⾔,所有对设备和⽂件的操作都使⽤⽂件描述符来进⾏的。
⽂件描述符是⼀个⾮负的整数,它是⼀个索引值,并指向内核中每个进程打开⽂件的记录表。
当打开⼀个现存⽂件或创建⼀个新⽂件时,内核就向进程返回⼀个⽂件描述符;当需要读写⽂件时,也需要把⽂件描述符作为参数传递给相应的函数。
Windows的地址空间分用户模式与内核模式,低2GB的部分叫用户模式,高2G的部分叫内核模式,位于用户空间的代码不能访问内核空间,位于内核空间的代码却可以访问用户空间一个线程的运行状态分内核态与用户态,当指令位于用户空间时,就表示当前处于内核态,当指令位于内核空间时,就处于内核态.一个线程由用户态进入内核态的途径有3种典型的方式:1、主动通过int 2e(软中断自陷方式)或sysenter指令(快速系统调用方式)调用系统服务函数,主动进入内核2、发生异常,被迫进入内核3、发生硬件中断,被迫进入内核现在讨论第一种进入内核的方式:(又分为两种方式)1、通过老式的int 2e指令方式调用系统服务(因为老式cpu没提供sysenter指令)如ReadFile函数调用系统服务函数NtReadFileKernel32.ReadFile() //点号前面表示该函数的所在模块{//所有Win32 API通过NTDLL中的系统服务存根函数调用系统服务进入内核NTDLL.NtReadFile();}NTDLL.NtReadFile(){Mov eax,152 //我们要调用的系统服务函数号,也即SSDT表中的索引,记录在eax中If(cpu不支持sysenter指令){Lea edx,[esp+4] //用户空间中的参数区基地址,记录在edx中Int 2e //通过该自陷指令方式进入KiSystemService,‘调用’对应的系统服务}Else{Lea edx,[esp +4] //用户空间中的参数区基地址,记录在edx中Sysenter //通过sysenter方式进入KiFastCallEntry,‘调用’对应的系统服务}Ret 36 //不管是从int 2e方式还是sysenter方式,系统调用都会返回到此条指令处}Int 2e的内部实现原理:该指令是一条自陷指令,执行该条指令后,cpu会自动将当前线程的当前栈切换为本线程的内核栈(栈分用户栈、内核栈),保存中断现场,也即那5个寄存器。
然后从该cpu的中断描述符表(简称IDT)中找到这个2e中断号对应的函数(也即中断服务例程,简称ISR),jmp 到对应的isr处继续执行,此时这个ISR 本身就处于内核空间了,当前线程就进入内核空间了Int 2e指令可以把它理解为intel提供的一个内部函数,它内部所做的工作如下Int 2e{Cli //cpu一中断,立马自动关中断Mov esp, TSS.内核栈地址 //切换为内核栈,TSS中记录了当前线程的内核栈地址Push SSPush espPush eflagsPush csPush eip //这5项工作保存了中断现场【标志、ip、esp】Jmp IDT[中断号] //跳转到对应本中断号的isr}IDT的整体布局:【异常->空白->5系->硬】(推荐采用7字口诀的方式重点记忆)异常:前20个表项存放着各个异常的描述符(IDT表不仅可以放中断描述符,还放置了所有异常的异常处理描述符,0x00-0x13)保留:0x14-0x1F,忽略这块号段空白:接下来存放一组空闲的保留项(0x20-0x29),供系统和程序员自己分配注册使用5系:然后是系统自己注册的5个预定义的软中断向量(软中断指手动的INT指令)(0x2A-0x2E 5个系统预注册的中断向量,0x2A:KiGetTickCount, 0x2B:KiCallbaclReturn0x2C:KiRaiseAssertion, 0x2D:KiDebugService, 0x2E:KiSystemService)硬:最后的表项供驱动程序注册硬件中断使用和自定义注册其他软中断使用(0x30-0xFF)下面是中断号的具体的分配情况:0x00-0x13固定分配给异常:0x00: Divide error(故障)0x01: Debug (故障或陷阱)0x02: 保留未用(为非屏蔽中断保留的,NMI)0x03: breakpoint(陷阱)0x04: Overflow(陷阱)0x05: Bounds check(故障)0x06: Invalid Opcode(故障)0x07: Device not available(故障)0x08: Double fault(异常中止)0x09: Coprocessor segment overrun(异常中止)0x0A: Invalid TSS(故障)0x0B: Segment not present(故障)0x0C: Stack segment(故障)0x0D: General protection(故障)0x0E: Page fault(故障)0x0F: Intel保留0x10: Floating point error(故障)0x11: Alignment check(故障)0x12: Machine check(异常中止)0x13: SIMD floating point(故障)0x14-0x1f:Intel保留给他公司将来自己使用(OS和用户都不要试图去使用这个号段,不安全)----------------------以下的号段可用于自由分配给OS、硬件、用户使用----------------------- linux等其他系统是怎么划分这块号段的,不管,我们只看Windows的情况0x20-0x29:Windows没占用,因此这块号段我们也可以自由使用0x2A-0x2E:Windows自己本身使用的5个中断号0x30-0xFF:Windows决定把这块剩余的号段让给硬件和用户使用参见《寒江独钓》一书P93页注册键盘中断时,搜索空闲未用表项是从0x20开始,到0x29结束的,就知道为什么寒江独钓是在这段范围内搜索空白表项了(其实我们也完全可以从0x14开始搜索)Windows系统中,0x30-0xFF这块号段让给了硬件和用户自己使用。
事实上,这块号段的开头部分默认都是让给硬件IRQ使用的,也即是分配给硬件IRQ的。
IRQ N默认映射到中断号0x30+N,如IRQ0用于系统时钟,系统时钟中断号默认对应就是0x30。
当然程序员也可以修改APIC(可编程中断控制器)将IRQ映射到自定义的中断号。
IRQ对外部设备分配,但IRQ0,IRQ2,IRQ13必须如下分配:IRQ0 ---->间隔定时设备IRQ2 ---->8259A芯片IRQ13 ---->外部数学协处理器其余的IRQ可以任意分配给外部设备。
虽然一个IRQ只对应一个中断号,但是由于IRQ数量有限,而设备种类成千上万,因此多个设备可以使用同一个IRQ,进而,多个设备可以分配同一个中断号。
因此,一个中断号可以共享给多个设备同时使用。
明白了IDT,就可以看到0x2e号中断的isr为KiSystemService,顾名思义,这个中断号专用于提供系统服务。
在正式分析KiSystemService,前,先看下几个辅助函数SaveTrap() //这个函数用来保存寄存器现场和其他状态信息{Push 0 //LastErrorPush ebpPush ebxPush esiPush ediPush fs //此时的fs若是从用户空间自陷进来的就指着TEB,反之指着kpcrPush kpcr.ExceptionListPush kthread.PreviousModeSub esp,0x48 //腾给调式寄存器保存用-----------至此,上面的这些语句连同int 2e中的语句在栈上构造了一个trap帧----------------- Mov CurTrapFrame,esp //当前Trap帧的地址Mov CurTrapFrame.edx, kthread.TrapFrame //将上次的trap帧地址记录到edx成员中Mov kthread.TrapFrame, CurTrapFrame, //修改本线程当前trap帧的地址Mov kthread.PreviousMode,GetMode(进入内核前的CS) //根据CS自动确定上次模式Mov kpcr.ExceptionList,-1 //表示刚进入内核时,尚未安装sehMov fs,kpcr //一进入内核就让fs改指向当前cpu的描述符kpcr,不再指向TEBIf(当前线程处于调试状态)保存DR0-DR7到trap帧中}FindTableCall() //这个函数用来查表,拷贝参数,调用系统服务{Mov edi,eax //系统函数号,低12位为索引,第13为表示是哪张系统服务表中的索引Mov eax, edi.低12位 //eax=真正的服务号If(edi.第13位=1) //if这是shadow SSDT中的系统函数号{If(当前线程.服务描述符表!=shadow)当前线程.服务描述符表=shadow //换用另外一张描述符表}服务表描述符=当前线程.服务描述符表[edi.第13位]Mod edi=服务表描述符.base //这个系统服务表的地址Mov ebx,[edi+eax*4] //查表获得这个函数的地址Mov ecx=服务表描述符.Number[eax] //查表获得的这个系统函数的参数大小Mov esi,edx //esi=用户空间中的参数地址Mov edi,esp //esp已经为内核栈的栈顶地址Rep movsb //将所有参数从用户空间复制到内核空间,相当于N个连续push压参Call ebx //调用对应的系统服务函数}KiSystemService()//int 2e的isr,内核服务函数总入口,注意这个函数可以嵌套、递归!!!{SaveTrap();Sti //开中断---------------上面保存完寄存器等现场后,开始查SSDT表调用系统服务------------------ FindTableCall();---------------------------------调用完系统服务函数后------------------------------ Move esp,kthread.TrapFrame; //将栈顶回到trap帧结构体处Cli //关中断If(上次模式==UserMode){Call KiDeliverApc //遍历执行本线程的内核APC和用户APC队列中的所有APC函数清理Trap帧,恢复寄存器现场Iret //返回用户空间}Else{返回到原call处后面的那条指令处}}上面所说的trap帧(TrapFrame)是指一个结构体,用来保存系统调用、中断、异常发生时的寄存器现场,方便以后回到用户空间/回到中断处时,恢复那些寄存器的值,继续执行Trap帧中除了保存了所有寄存器现场外,还附带保存了一些其他信息,如seh链表的地址等必须说一下trap帧的结构体布局定义:typedef struct _KTRAP_FRAME //Trap现场帧{------------------这些是KiSystemService保存的---------------------------ULONG DbgEbp;ULONG DbgEip;ULONG DbgArgMark;ULONG DbgArgPointer;ULONG TempSegCs;ULONG TempEsp;ULONG Dr0;ULONG Dr1;ULONG Dr2;ULONG Dr3;ULONG Dr6;ULONG Dr7;ULONG SegGs;ULONG SegEs;ULONG SegDs;ULONG Edx;//xy 这个位置不是用来保存edx的,而是用来保存上个Trap帧,因为Trap帧是可以嵌套的 ULONG Ecx; //中断和异常引起的自陷要保存eax,系统调用则不需保存ecxULONG Eax;//中断和异常引起的自陷要保存eax,系统调用则不需保存eaxULONG PreviousPreviousMode;struct _EXCEPTION_REGISTRATION_RECORD FAR *ExceptionList;//上次seh链表的开头地址ULONG SegFs;ULONG Edi;ULONG Esi;ULONG Ebx;ULONG Ebp;---------------------------------------------------------------------------------------- ULONG ErrCode;//发生的不是中断,而是异常时,cpu还会自动在栈中压入对应的具体异常码在这儿-----------下面5个寄存器是由int 2e内部本身保存的或KiFastCallEntry模拟保存的现场--------- ULONG Eip;ULONG SegCs;ULONG EFlags;ULONG HardwareEsp;ULONG HardwareSegSs;---------------以下用于用于保存V86模式的4个寄存器也是cpu自动压入的-------------------ULONG V86Es;ULONG V86Ds;ULONG V86Fs;ULONG V86Gs;} KTRAP_FRAME, *PKTRAP_FRAME;KPCR与KPRCB结构,都是用来描述处理器的,前者叫处理器描述符,后者叫处理器控制块Struct KPCR{KPCR_TIB Tib;//类似于TEB.TIB,内部第一个字段都是ExceptionListKPCR* self;//自身结构体的地址,方便直接寻址KPRCB* kprcb;//处理器控制块的地址、KIRQL irql;//当前cpu的irqlUSHORT* IDT;//本cpu的IDT地址,一有中断/异常就去这个表找isr、eprUSHORT* GDT;//全局描述符表地址KTSS* TSS;//记录了本cpu上当前运行线程的状态信息,重要字段有内核栈地址,IO权限位图……}Struct KPRCB{KTHREAD* CurrentThread;//本cpu上当前运行的线程KTHREAD* NextThread;//本cpu上将抢占当前线程的下个线程(抢占式调度核心)BYTE CpuID;//不多说ULONG KernelTime,UserTime;//本cpu的累计运行时间统计信息……}系统中有两张“系统服务表”,即SSDT和shadow SSDT。