杨明辉 + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 ”
一、实验过程
1.进入实验楼,打开终端输入命令cd Code进入Code目录,然后输入>open.c新建文件,并将C语言的源码输入文件中(源码下面给出),然后输入命令gcc main.c -o main -m32对源文件进行编译,最后输入命令./main运行程序。实验结果如图1所示:
图1
2. 再次输入命令>openms.c新建一个c程序文件,然后将利用嵌入式汇编编写的c语言源程序输入文件中;输入命令gcc openms.c -o openms -m32编译c程序,最后输入./openms运行程序,实验结果如图2所示。
图2
二、源码分析
1.首先利用c语言库函数的API来编写程序进行系统调用,本次实验使用的是open(sys_open)系统调用,系统号为5。程序源码及源码分析如下所示:
程序源码
源码分析#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <fcntl.h> int main(int argc, char *argv[]) { int fd; char str[100]; fd = open("text.txt",O_RDONLY); if(fd < 0) { perror("open"); } while(read(fd,str,sizeof(str)) > 0) { printf("%s",str); } close(fd); return 0; }
1. 首先程序开头的#include<fcntl.h>导入了open函数所在的文件,只有导入该文件后,程序才可以调用open函数。
2. open函数的定义为: int open(const char * pathname, int flags);
3. 参数pathname指向文件的目录,flags为文件打开方式。
4. flags在头文件fcntl.h定义,其中的含义为:
1. O_RDONLY 以只读方式打开文件
2. O_WRONLY 以只写方式打开文件
3. O_RDWR 以可读写方式打开文件。上述三种旗标是互斥的,也就是不可同时使用,但可与下列的旗标利用OR(|)运算符组合。
4. O_CREAT 若欲打开的文件不存在则自动建立该文件。
5. O_EXCL 如果O_CREAT 也被设置,此指令会去检查文件是否存在。文件若不存在则建立该文件,否则将导致打开文件错误。此外,若O_CREAT与O_EXCL同时设置,并且欲打开的文件为符号连接,则会打开文件失败。
6. O_NOCTTY 如果欲打开的文件为终端机设备时,则不会将该终端机当成进程控制终端机。
7. O_TRUNC 若文件存在并且以可写的方式打开时,此旗标会令文件长度清为0,而原来存于该文件的资料也会消失。
8. O_APPEND 当读写文件时会从文件尾开始移动,也就是所写入的数据会以附加的方式加入到文件后面。
9. O_NONBLOCK 以不可阻断的方式打开文件,也就是无论有无数据读取或等待,都会立即返回进程之中。
10. O_NDELAY 同O_NONBLOCK。
11. O_SYNC 以同步的方式打开文件。
12. O_NOFOLLOW 如果参数pathname 所指的文件为一符号连接,则会令打开文件失败。
13. O_DIRECTORY 如果参数pathname 所指的文件并非为一目录,则会令打开文件失败。
5. 当文件成功打开后函数会返还一个正整数的文件描述符,附值给fd,利用该文件描述符可以对文件进行读写及其它相关操作,本次程序读利用read()取文件中的数据,然后输出,最后调用close()函数关闭文件。
2. 利用嵌入式汇编来进行来进行系统调用,程序源码及源码分析如下所示:
源码分析:#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <fcntl.h> int main() { int fd; char path[]="text.txt"; char context[100]; int re=O_RDONLY; asm volatile( "mov %2,%%ecx\n\t" "mov %1,%%ebx\n\t" "mov $0x5,%%eax\n\t" "int $0x80\n\t" "mov %%eax,%0\n\t" : "=m" (fd) :"b"(path),"c"(re) ); if(fd < 0) { perror("open"); } while(read(fd,context,sizeof(context)) > 0) { printf("%s",context); } close(fd); return 0; }
1. 本程序除了打开文件的方式是利用嵌入式汇编语言外,其它的程序与上面的完全一样。
2. 在Linux中的嵌入式汇编是通过执行int $0x80来执行系统调用的,这条汇编指令产生向量为128的编程异常,达到进行系统调用的目的。
3. 内核实现了很多不同的系统调用,进程必须指明需要哪个系统调用,这需要传递一个名为系统调用号的参数,这个参数使用eax寄存器来传递。
4. 系统调用也需要输入输出参数,比如:实际的值、用户态进程地址空间的变量的地址、甚至是包含指向用户态函数的指针的数据结构的地址等参数。
5. 在系统调用号(eax)之外,还可以利用ebx,ecx,edx,esi,edi,ebp来传递参数,但每个参数的长度不能超过寄存器的长度,即32位。
6. 本次实验通过ecx和ebx来传递参数,系统调用后的返回结果保存在eax中。
三、 总结
1. 操作系统为用户态进程与硬件设备进行交互提供了一组接口——系统调用有如下好处:
把用户从底层的硬件编程中解放出来极大的提高了系统的安全性使用户程序具有可移植性
2. 操作系统中的状态分为管态(核心态)和目态(用户态)。
3. 特权指令:一类只能在核心态下运行而不能在用户态下运行的特殊指令。不同的操作系统特权指令会有所差异,但是一般来说主要是和硬件相关的一些指令。
4. 用户程序只在用户态下运行,有时需要访问系统核心功能,这时通过系统调用接口使用系统调用。
5. 由于操作系统提供了系统调用,才使得应用程序编写更加简单,程序运行更加流畅,系统运行更加安全。
6. 系统调用通过软中断向内核发出一个明确的请求,是操作系统为用户态进程与硬件设备进行交互提供的一组接口,系统调用正是通过中断来完成的。