寇亚飞 + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
实验内容
使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用
- 选择一个系统调用(13号系统调用time除外),系统调用列表参见系统调用
- 参考视频中的方式使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用
实验过程及结果
参见实验报告
实验分析
- 系统调用的意义
操作系统为用户态进程与硬件设备进行交互提供了一组接口——系统调用
- 把用户从底层的硬件编程中解放出来
- 极大的提高了系统的安全性
- 使用户程序具有可移植性
API和系统调用
- 应用编程接口(Application program interface,API)和系统调用是不同的
- API只是一个函数定义
- 系统调用通过软中断向内核发出一个明确的请求
- Libc库定义的一些API引用了封装例程(wrapper routine,唯一目的就是发布系统调用)
- 一般每个系统调用对应一个封装例程
- 库再用这些封装例程定义出给用户的API
- 不是每个API都对应一个特定的系统调用
- API可能直接提供用户态的服务
- 如,一些数学函数
- 一个单独的API可能调用了几个系用调用
- 不同的API可能调用同一个系统调用
- API可能直接提供用户态的服务
- 返回值
- 大部分封装例程返回一个整数,其值的含义依赖于相应的系统调用
- -1在多数情况下表示内核不能满足进程的请求
- Libc中定义的errno变量包含特定的出错码
- 应用编程接口(Application program interface,API)和系统调用是不同的
用调用程序及服务例程
- 当用户态进程调用一个系统调用时,CPU切换到内核态并开始执行一个内核函数
- 在Linux中使用过执行int $0x80来执行系统调用的,这条汇编指令产生向量为128的变成异常
- 传参
内核实现了很多不同的系统调用,进程必须指明需要哪个系统调用,这需要传递一个名为系统调用号的参数
- 使用eax寄存器来接收系统调用号
- 系统调用所需的参数可以用寄存器传参,但有如下限制:
- 每个参数的长度不能超过寄存器的长度
- 在系统调用号(eax)之外,参数的个数不能超过6个(ebx,ecx,edx,esi,edi,ebp)
- 超过6个则寄存器中可存入指针,相应的内存地址中存放参数
- 当用户态进程调用一个系统调用时,CPU切换到内核态并开始执行一个内核函数
具体实践
使用8号系统调用(creat)来演示说明。
- 使用库函数API创建文件
#include<stdio.h>
#include<fcntl.h>
int main()
{
int ret =0;
char* filename = "createfile1";
mode_t mode = 0755;
ret = creat(filename,mode);
printf("file %d create success\n",ret);
return 0;
}
库函数int creat(const char *filename, mode_t mode)有两个参数,第一个为要创建文件名,第二个为创建文件的模式/访问权限,0755表示文件所有者可读可写可执行,文件所有者所在组他行,其用户可读可执行。
- 使用C语言内嵌汇编带码实现
#include<stdio.h>
#include<fcntl.h>
int main()
{
int ret =0;
char *filename = "createfile2";
mode_t mode = 0755;
asm volatile(
"movl $8,%%eax\n\t"
"int $0x80\n\t"
"movl %%eax,%0\n\t"
:"=m"(ret)
:"b"(filename),"c"(mode)
);
printf("file %d create success\n",ret);
return 0;
}
在上面代码中,首先将系统调用号8传给寄存器eax,然后执行指令int $0x80执行数调用;creat函数的两个参数分别存入寄存器ebx和ecx中。
实验总结
系统调用不但把用户从底层的硬件编程中解放出来,提高了编程的效率,也极大的提高了系统的安全性。