应用程序、封装例程、系统调用处理程序及 系统调用服务例程之间的关系
深入系统调用的话,就需要我们在C代码中嵌入汇编语言来触发系统调用,也就是说我们需要通过库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用。那么如何做到呢?我的方法是,C 语言的编程代码我们可以顺利地编程得到,但是 C 代码中嵌入汇编的方式可能无法顺利地做到,或者做到的过程中会出现问题,我们就需要一条条语句来做,并且每条语句做好了就立马进行调试,使得最后得到的结果无限接近于用 C 代码写出的程序。
这次实验所编写的C 程序是文件拷贝程序,将当前目录下文件名为 “src_file” 的文件内容拷贝到当前目录的另一个文件 “dest_file” 中去(文件不存在则创建)。
#include
#include
#include
#define BUFFER_SIZE 1024 /* 每次读写缓存大小,影响运行效率 */
#define OFFSET 10240 /* 拷贝的数据大小 */
#define SRC_FILE_NAME "src_file" /* 源文件名 */
#define DEST_FILE_NAME "dest_file" /* 目标文件名 */
int main()
{
int src_fd, dest_fd;
unsigned char buff[BUFFER_SIZE];
int buff_len;
/* 以只读的方式打开源文件 */
src_fd = open(SRC_FILE_NAME, O_RDONLY);
/* 以只读的方式打开目标文件,若此文件不存在则创建,访问权限为644 */
dest_fd = open(DEST_FILE_NAME, O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
if (src_fd < 0 || dest_fd < 0)
{
printf("Open File Error!\n");
exit(1);
}
/* 将源文件的读写指针移到最后10KB的起始位置 */
lseek(src_fd, -OFFSET, SEEK_END);
/* 读取源文件的最后10KB数据并写到目标文件中,每次读写1KB */
while ((buff_len = read(src_fd, buff, sizeof(buff))) > 0)
{
write(dest_fd, buff, buff_len);
}
close(dest_fd);
close(src_fd);
return 0;
}
file.c 文件的编写
编辑 src_file 文件的内容
编译 file.c; 运行 file 可执行文件; 用 cat 命令查看 dest_file 文件内容
从实验代码及截图可以看出程序的效果,成功地实现了预设的目标——“当前目录下文件名为 “src_file” 的文件内容拷贝到当前目录的另一个文件 “dest_file” 中去(文件不存在则创建)”。
#include
#include
#include
#include
#include
#define BUFFER_SIZE 1024
#define OFFSET 10240
int main()
{
int src_fd, dest_fd ;
unsigned char buff[BUFFER_SIZE] ;
int buff_len ;
char *src_file_name = "src-asm_file" ;
char *dest_file_name = "dest-asm_file" ;
//src_file = open(SRC_FILE_NAME, O_RDONLY) ;
asm volatile (
"movl %1, %%ebx\n\t"
"movl $0x0000, %%ecx\n\t" //O_RDONLY
"movl $0x5, %%eax\n\t" //set system call numbers $0x05-->open
"int $0x80\n\t"
"movl %%eax, %0\n\t"
: "=m" (src_fd)
: "p" (src_file_name)
) ;
//dest_file = open(DEST_FILE_NAME, O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
asm volatile (
"movl %1, %%ebx\n\t"
"movl $0x0001, %%ecx\n\t" //O_WRONLY
"or $0x0200, %%ecx\n\t" //O_CREAT
"movl $0000400, %%edx\n\t" //S_IRUSR
"or $0000200, %%edx\n\t" //S_IRUSR|S_IWUSR
"or $0000040, %%edx\n\t" //S_IRGRP
"or $0000004, %%edx\n\t" //S_IROTH
"movl $0x5, %%eax\n\t" //set system call numbers $0x05-->open
"int $0x80\n\t"
"movl %%eax, %0\n\t"
: "=m" (dest_fd)
: "p" (dest_file_name)
) ;
if (src_fd < 0 || dest_fd < 0)
{
printf("Open File Error!\n");
exit(1);
}
//lseek(src_fd, -OFFSET, SEEK_END);
asm volatile (
"movl %0, %%ebx\n\t"
"movl $-1024, %%ecx\n\t" //OFFSET
"movl $2, %%edx\n\t" //SEEL_END
"movl $0x13, %%eax\n\t" //set system call numbers $0x13-->lseek
"int $0x80\n\t"
:
: "m" (src_fd)
) ;
do
{
//read(src_fd, buff, sizeof(buff))
asm volatile (
"movl %1, %%ebx\n\t"
"movl %2, %%ecx\n\t"
"movl %3, %%edx\n\t"
"movl $0x03, %%eax\n\t" //set system call numbers $0x03-->read
"int $0x80\n\t"
"movl %%eax, %0\n\t"
: "=m" (buff_len)
: "m" (src_fd), "p" (buff), "i" (sizeof(buff))
) ;
if (real_real_len > 0)
{
//write(dest_fd, buff, buff_len);
asm volatile (
"movl %0, %%ebx\n\t"
"movl %1, %%ecx\n\t"
"movl %2, %%edx\n\t"
"movl $0x04, %%eax\n\t"//set system call numbers $0x04-->write
"int $0x80\n\t"
:
: "m" (dest_fd), "p" (buff), "m" (buff_len)
) ;
}
else
{
break ;
}
} while (1) ;
close(src_fd) ;
close(dest_fd) ;
return 0 ;
}
在程序编写的时候,首先碰到的难题就是 open 函数。open 函数的原型是
int open(const char *pathname, int flags);
让我感到下不了手的,就是宏定义中的文件名,宏定义的 SRC_FILE_NAME 参数该如何传给 open 函数呢?难!我只好在 main 函数中声明了两个字符串指针,指向两文件的文件名,嵌入式汇编的时候输入参数就是一个指针,指向文件名字符串常量。而 flags 则相对好办多了,那些宏定义在 fcntl.h 文件中有定义,只需要到里面查找就好了,于是乎我就把O_RDONLY, O_WRONLY, ... 都找到然后一一放到寄存器中去。
lseek 函数中的参数,OFFSET 我也直接用立即数的形式存放到寄存器中去了,参数 SEEK_END 也找到相应的值存放到寄存器中去。
而循环则又重新写了一遍,因为先要读取一片段的数据到缓冲区;然后判断读取是否成功;成功了的话,然后再将缓冲区中的数据写到目标文件中去;不成功则退出循环。于是乎,照着这个逻辑写了一遍。
也就是在重写 read 函数的这个时候,我才意识到前面写的代码都可以更好。我在思考怎么把 sizeof(buf) 这个参数传给 read 函数时,我原先想申明个变量 int buf_size = (int)sizeof(buff) ; 然后把 buf_size 以内存值的方式存放到寄存器,但是又觉得麻烦。重新审视 C 语言才明白,buf_size 也只不过是个内存地址,赋值操作只不过是把值/变量中的值存放到 buf_size 地址对应的内存空间中去罢了。而 sizeof(buff) 本身就是个立即数!然后又发现,既然
"m" (buff_size)
"i" (sizeof(buff))
这时候,暮然回首,才发现圆括号里面的 C 语言参数是可以被解析的,是可以识别出来的!那么也就是说,前面的 O_RDONLY, O_WRONLY, OFFSET, SEEK_END, ... 什么乱七八糟的都是可以直接放到括号里面去的!这样程序的可读性,易理解程度不是大大提高了吗?天才!
于是乎,就有了下面的程序,这样子的程序只要有点 C 编码基础和 AT&T 汇编基础的人,读起来都不会太难。同时我想到了,既然已经把 open、read、write、lseek 等系统调用 API 用 C 嵌入式汇编的方式实现了,那为什么不更疯狂一点儿把 exit、close 等都实现呢?
#include
#include
#include
#include
#include
#define BUFFER_SIZE 1024
#define OFFSET 10240
int main()
{
int src_fd, dest_fd ;
unsigned char buff[BUFFER_SIZE] ;
int buff_len ;
char *src_file_name = "src-asm_file" ;
char *dest_file_name = "dest-asm_file" ;
asm volatile (
"movl %1, %%ebx\n\t"
"movl %2, %%ecx\n\t"
"movl $0x5, %%eax\n\t"
"int $0x80\n\t"
"movl %%eax, %0\n\t"
: "=m" (src_fd)
: "p" (src_file_name), "i" (O_RDONLY)
: "eax", "ebx", "ecx"
) ;
asm volatile (
"movl %1, %%ebx\n\t"
"movl %2, %%ecx\n\t"
"movl %3, %%edx\n\t"
"movl $0x5, %%eax\n\t"
"int $0x80\n\t"
"movl %%eax, %0"
: "=m" (dest_fd)
: "p" (dest_file_name), "i" (O_WRONLY|O_CREAT),
"i" (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)
: "eax", "ebx", "ecx", "edx"
) ;
if (src_fd < 0 || dest_fd < 0)
{
printf("Open File Error!\n") ;
asm volatile (
"movl $1, %ebx\n\t"
"movl $0x01, %eax\n\t"
"int $0x80\n\t"
) ;
}
asm volatile (
"movl %0, %%ebx\n\t"
"movl %1, %%ecx\n\t"
"movl %2, %%edx\n\t"
"movl $0x13, %%eax\n\t"
"int $0x80\n\t"
:
: "m" (src_fd), "i" (-OFFSET), "i" (SEEK_END)
: "eax", "ebx", "ecx", "edx"
) ;
do
{
asm volatile (
"movl %1, %%ebx\n\t"
"movl %2, %%ecx\n\t"
"movl %3, %%edx\n\t"
"movl $0x03, %%eax\n\t"
"int $0x80\n\t"
"movl %%eax, %0\n\t"
: "=m" (buff_len)
: "m" (src_fd), "p" (buff), "i" (sizeof(buff))
: "eax", "ebx", "ecx", "edx"
) ;
if (buff_len > 0)
{
asm volatile (
"movl %0, %%ebx\n\t"
"movl %1, %%ecx\n\t"
"movl %2, %%edx\n\t"
"movl $0x04, %%eax\n\t"
"int $0x80\n\t"
:
: "m" (dest_fd), "p" (buff), "m" (buff_len)
: "eax", "ebx", "ecx", "edx"
) ;
}
else
{
break ;
}
} while (1) ;
asm volatile (
"movl %0, %%ebx\n\t"
"movl $0x06, %%eax\n\t"
"int $0x80\n\t"
:
: "m" (src_fd)
: "eax", "ebx"
) ;
asm volatile (
"movl %0, %%ebx\n\t"
"movl $0x06, %%eax\n\t"
"int $0x80\n\t"
:
: "m" (dest_fd)
: "eax", "ebx"
) ;
return 0 ;
}
可以看到,上面的这个程序并没有 C 程序的那么简洁,一个 main 函数那么长,而且都是汇编,简直吓死人!有很多代码片段都是可服用的,比如关闭文件的汇编代码段:
asm volatile (
"movl %0, %%ebx\n\t"
"movl $0x06, %%eax\n\t"
"int $0x80\n\t"
:
: "m" (src_fd)
: "eax", "ebx"
) ;
asm volatile (
"movl %0, %%ebx\n\t"
"movl $0x06, %%eax\n\t"
"int $0x80\n\t"
:
: "m" (dest_fd)
: "eax", "ebx"
) ;
return 0 ;
void xyz(int fd)
{
...
}
编辑 file-asm.c 文件
编辑 src-asm_file 文件内容
编译 file-asm.c; 运行 file-asm 可执行文件; 用 cat 命令查看 dest_file 文件内容
可以看出,程序实现的非常成功,我们用 cat 命令查看“dest-asm_file”时,终端显示出了 Yeates 的诗When You Are Old. 效果跟调用系统调用 API 时效果相同——“将当前目录下文件名为 “src-asm_file” 的文件内容拷贝到当前目录的另一个文件 “des-asmt_file” 中去(文件不存在则创建)”。
陈金雷+原创作品转载请注明出处+《Linux内核分析》MOOC课程 http://mooc.study.163.com/course/USTC-1000029000