(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 指令进⼊系统调⽤。
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。