2020-2021-1 20209323《Linux内核原理与分析》第五周作业
一、系统调用的三层机制
1、用户态、内核态和中断
- 用户态:较低的执行级别,只能访问一部分内存,只能执行一部分指令。
- 内核态:高级执行级别,可以访问任意物理内存,可以执行特权指令。
- 中 断:系统从用户态进入内核态的主要方式。有硬件中断和软中断两种,系统调用就是通过软中断进入内核态。
2、系统调用
操作系统为用户态进程与硬件设备进行交互提供了一组接口——系统调用
- 把用户从底层的硬件编程中解放出来
- 极大的提高了系统的安全性
- 使用户程序具有可移植性
3、API和系统调用
1)应用编程接口(application program interface, API) 和系统调用是不同的
- API只是一个函数定义,系统调用通过软中断向内核发出一个明确的请求
- Libc库定义的一些API引用了封装例程,一般每个系统调用对应一个封装例程,库再用这些封装例程定义出给用户的API
2)不是每个API都对应一个特定的系统调用。
- API可能直接提供用户态的服务
- 一个单独的API可能调用几个系统调用
- 不同的API可能调用了同一个系统调用
3)返回值
- 大部分封装例程返回一个整数,其值的含义依赖于相应的系统调用
- -1在多数情况下表示内核不能满足进程的请求
- Libc中定义的errno变量包含特定的出错码
4、应用程序、封装例程、系统调用处理程序及系统调用服务例程之间的关系
二、实验(使用库函数API和C代码嵌入汇编代码触发同一个系统调用)
1、使用库函数API触发一个系统调用
//time.c
#include<stdio.h> #include<time.h> int main()
{ time_t tt; struct tm *t; tt = time(NULL); t = localtime(&tt); printf("time:%d:%d:%d:%d:%d:%d:\n",t->tm_year+1900,t->tm_mon,t->tm_mday,t->tm_hour,t->tm_min,t->tm_sec); return 0; }
虚拟机使用的是64位系统,gcc编译时需要加一个m32的参数输出32位的机器码, gcc -m32 -o time time.c
从运行结果可以看出,当前时间的月份提前了一个月,这是因为:结构体tm中定义的月份是从0开始计数的,所以为了得到当前时间,需要将mon+1,结构体tm如下:
struct tm { int tm_sec; /*秒,正常范围0-59, 但允许至61*/ int tm_min; /*分钟,0-59*/ int tm_hour; /*小时, 0-23*/ int tm_mday; /*日,即一个月中的第几天,1-31*/ int tm_mon; /*月, 从一月算起,0-11 1+p->tm_mon; */ int tm_year; /*年, 从1900至今已经多少年 1900+ p->tm_year; */ int tm_wday; /*星期,一周中的第几天, 从星期日算起,0-6*/ int tm_yday; /*从今年1月1日到目前的天数,范围0-365*/ int tm_isdst; /*日光节约时间的旗标*/ };
2、C代码中嵌入汇编代码触发一个系统调用
用汇编方式触发系统调用获取系统当前时间,time_asm.c源代码如下, 可见,除了"tt = time(NULL);"一句用内嵌汇编代替外,与前述的“使用库函数API触发一个系统调用”中的代码完全一致。
#include<stdio.h> #include<time.h> int main(){ time_t tt; struct tm *t; asm volatile( "movl $0,%%ebx;\n\t" //把ebx寄存器清0 "movl $0xd,%%eax;\n\t" //eax用于传递系统调用号 "int $0x80;\n\t" //触发系统调用陷入内核执行系统调用内核处理函数 "movl %%eax,%0;\n\t" //系统调用的返回值通过eax返回 :"=m"(tt) : :"eax","ebx" ); t = localtime(&tt); printf("time:%d:%d:%d:%d:%d:%d:\n",t->tm_year+1900,t->tm_mon+1,t->tm_mday,t->tm_hour,t->tm_min,t->tm_sec); return 0; }