Linux内核分析(四)

开始

用两种方式实现使用同一个系统调用:

  • 库函数API
  • C代码中内嵌汇编

本实验目的是为了熟悉Linux系统调用的相关知识,以及熟悉Linux和嵌入式汇编的写法。

实验内容

选择一个系统调用,列表见这里,我们选择write系统调用。
用C写一个文件test.c,代码如下:

1
2
3
4
5
6
7
#include <stdio.h>
#include <unistd.h>
int main(void)
{
write(1,"hello world!\n",13);
return 0;
}

这里,write函数有三个参数,第一个表示写到终端屏幕上,1可以认为是屏幕的代号,第二个参数是写的内容,第三个参数是写入字符串的长度。
运行结果如下图所示:
1

然后需要把这段代码转化为嵌入式汇编的格式,嵌入式汇编的格式如下:
2

在test_asm.c中,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include<stdio.h>
#include<unistd.h>

int main(void)
{
int a;
char *ch="hello world!\n";

__asm__(
"movl $0x4,%%eax\n\t"//系统调用号存入eax寄存器
"movl $0x1,%%ebx\n\t"//ebx传递参数,第一个参数1
"movl %1,%%ecx\n\t"//ecx存储输出位置,第二个参数字符串ch
"movl $0xd,%%edx\n\t"//edx存储参数长度,第三个参数13
"int $0x80\n\t"//执行系统调用
"movl %%eax,%0\n\t"//系统调用返回值存入eax
:"=m"(a)//输出
:"c"(ch)//输入
);
return 0;
}

执行结果如下所示:
3
这里代码:

1
"movl %1,%%ecx\n\t"//ecx存储输出位置

中的%1,表示传递的第一个参数,它和后面的输入:

1
:"c"(ch)//输入

是相呼应的,c表示ecx,表示把字符串a存入ecx中。
而其他的代码:

1
"movl $0x1,%%ebx\n\t"//ebx传递参数

$0x1表示把立即数1存入ebx中,也是作为参数的。按照顺序传递参数。

总结

系统调用的三个层次如下所示:
4
分别为:xyz函数(API)、system call(中断向量)和 sys xyz(中断服务程序)。

当用户进程需要发生系统调用时,CPU 通过软中断切换到内核态开始执行内核系统调用函数。Linux 下有三种发生系统调用的方法:

  1. 通过 glibc 提供的库函数
  2. 使用 syscall 函数直接调用
  3. 通过 int 0x80指令陷入

总的来说,前两种最终都会通过int 0x80指令陷入进入中断处理程序。而系统调用也需要输入输出参数,例如实际的值,用户态进程地址空间的变量的地址,甚至是包含指向用户态函数的指针的数据结构的地址等。
system_call是linux中所有系统调用的入口点,每个系统调用至少有一个参数,即由eax传递的系统调用号,其他参数依次由ebx,ecx,edx,esi,edi,ebp传入。
寄存器传递参数具有如下限制:

  1. 每个参数的长度不能超过寄存器的长度,即32位
  2. 在系统调用号(eax)之外,参数的个数不能超过6个(ebx,ecx,edx,esi,edi,ebp)

【版权声明】
本文首发于戚名钰的博客,欢迎转载,但是必须保留本文的署名戚名钰(包含链接)。如您有任何商业合作或者授权方面的协商,请给我留言:qimingyu.security@foxmail.com
欢迎关注我的微信公众号:科技锐新

kejiruixin

本文永久链接:http://qimingyu.github.io/2016/04/08/Linux内核分析(四)/

坚持原创技术分享,您的支持将鼓励我继续创作!

热评文章