《Linux内核分析》 第四节 扒开系统调用的三层皮(上)
《Linux内核分析》 第四节 扒开系统调用的三层皮(上)
范闻泽 原创作品转载请注明出处 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
一、用户态、内核态和中断处理过程
- 用户态:当进程在执行用户自己的代码时,则称其处于用户态,即此时处理器在特权级最低的(3级)用户代码中运行。
- 内核态:当一个进程执行系统调用而陷入内核代码中执行时,我们就称进程处于内核态,此时处理器处于特权级最高的(0级)内核代码中执行。
- PS:CPU指令执行级别
中断:中断处理是从用户态进入内核态主要的方式。
用户态和内核态的转换:
中断处理的完整过程
进入中断程序,保存寄存器数据 SAVE_ALL -..//内核代码,完成中断服务,发生进程调度 RESTORE_ALL//退出中断程序,恢复寄存器数据 iret(pop cs:eip/ss:esp/eflags from kernel stack)//对应着中断信号或int指令,与发生时CPU动作相反
二、系统调用概述
1.系统调用:系统调用只是一种特殊的中断。
这是用户态进程主动要求切换到内核态的一种方式,用户态进程通过系统调用申请使用操作系统提供的服务程序完成工作。而系统调用的机制其核心还是使用了操作系统为用户特别开放的一个中断来实现。
系统调用的意义:
把用户从底层的硬件编程中解放出来 极大的提高了系统的安全性 使用户程序具有可移植性
API和系统调用:
应用编程接口(application program interface,API)和系统调用是不同的
API只是一个函数定义 系统调用通过软中断向内核发出一个明确求
Libc库定义的一些API引用了封装例程:
一般每个系统调用对应一个封装例程 库再用这些封装例程定义出给用户的API
不是每个API都对应一个特定的系统调用:
API可能直接提供用户态的服务(如一些数学函数) 一个单独的API可能调用几个系统调用 不同的API可能调用了同一个系统调用
返回值
大部分封装例程返回一个整数,其值的含义依赖于相应的系统调用 -1在多数情况下表示内核不能满足进程的请求 Libc中定义的errno变量包含特定的出错码
应用程序、封装例程、系统调用处理程序、系统调用服务例程之间的关系
2.系统调用的三层皮
•API(xyz) •中断向量(system_call) •中断服务程序(sys_xyz)
-
当用户态进程调用一个系统调用时,CPU切换到内核态并开始执行一个内核函数。
-
Linux中是通过执行int $0x80来执行系统调用,这条汇编指令产生向量为128的编程异常 —— 即中断向量0x80与System_call绑定起来。
-
系统调用号将函数
xyz()
和中断服务程序sys_xyz
关联起来。
3. 参数传递
-
内核实现了很多不同的系统调用,进程用系统调用号这个参数指明需要哪个系统调用。
-
system_call是linux中所有系统调用的入口点,每个系统调用至少有一个参数,使用eax寄存器传递系统调用号。
-
寄存器传递参数的限制
- 每个参数的长度不能超过寄存器的长度,即32位 - 在系统调用号(eax)之外,参数的个数不能超过6个(ebx,ecx,edx,esi,edi,ebp) - 超过6个的情况下,使用某一个寄存器作为指针,进入内核态之后可以访问所有的地址空间,通过某一片区域传递参数。
三、实验:使用库函数API和c代码中嵌入汇编代码触发同一系统调用
-
实验报告
-
选择一个系统调用(13号系统调用time除外),系统调用列表参见http://codelab.shiyanlou.com/xref/linux-3.18.6/arch/x86/syscalls/syscall_32.tbl 参考视频中的方式使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用
-
博客内容的具体要求如下:
-
题目自拟,内容围绕系统调用的工作机制进行,博客中需要使用实验截图
-
博客内容中需要仔细分析汇编代码调用系统调用的工作过程,特别是参数的传递的方式等。
- 总结部分需要阐明自己对“系统调用的工作机制”的理解。
-
- 本次实验选择了2号调用fork调用来做实验:fork函数执行完毕后,如果创建新进程成功,则出现两个进程,一个是子进程,一个是父进程。在子进程中,fork函数返回0,在父进程中,fork返回新创建子进程的进程ID
用实验楼的虚拟机打开shell
Cd Code Vi forktest.c Gcc forktest.c -o forktest.o -m32 ./forktest.o
fork.c代码如下
#include <unistd.h> #include <stdio.h> int main () { pid_t fpid; int count = 0; fpid = fork(); if (fpid < 0) printf("error in fork!"); else if (fpid == 0) { printf("i am the child process, my process id is %d\n",getpid()); count++; } else { printf("i am the parent process, my process id is %d\n",getpid()); count++; } printf("count: %d\n",count); return 0; }
- 嵌入式汇编代码的执行,fork-asm.c源代码如下(参数的传递方式见注释):
#include <unistd.h> #include <stdio.h> int main () { pid_t fpid; int count = 0;
asm volatile (
"mov $0, %%ebx\n\t"
"mov $0x2, %%eax\n\t" // 将fork的系统调用号0x2赋值给eax
"int $0x80\n\t" // 通过0x80中断向量,执行系统调用
"mov %%eax, %0\n\t" // 系统返回的pid号默认储存在eax中
: "=m" (fpid) // 输出操作数0为内存中的fpid。
);
if (fpid < 0) printf("error in fork!"); else if (fpid == 0) { printf("i am the child process, my process id is %d\n",getpid()); count++; } else { printf("i am the parent process, my process id is %d\n",getpid()); count++; } printf("count: %d\n",count); return 0; }