惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

G
Google Developers Blog
S
Schneier on Security
The Hacker News
The Hacker News
P
Proofpoint News Feed
Spread Privacy
Spread Privacy
L
LINUX DO - 热门话题
L
Lohrmann on Cybersecurity
I
Intezer
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Schneier on Security
Schneier on Security
Security Latest
Security Latest
AWS News Blog
AWS News Blog
B
Blog RSS Feed
Microsoft Security Blog
Microsoft Security Blog
有赞技术团队
有赞技术团队
博客园 - 叶小钗
The Last Watchdog
The Last Watchdog
O
OpenAI News
月光博客
月光博客
Hacker News: Ask HN
Hacker News: Ask HN
阮一峰的网络日志
阮一峰的网络日志
S
Security @ Cisco Blogs
Google Online Security Blog
Google Online Security Blog
云风的 BLOG
云风的 BLOG
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Latest news
Latest news
P
Palo Alto Networks Blog
Last Week in AI
Last Week in AI
M
MIT News - Artificial intelligence
Google DeepMind News
Google DeepMind News
P
Proofpoint News Feed
C
CERT Recently Published Vulnerability Notes
Apple Machine Learning Research
Apple Machine Learning Research
U
Unit 42
PCI Perspectives
PCI Perspectives
博客园 - 聂微东
SecWiki News
SecWiki News
宝玉的分享
宝玉的分享
Forbes - Security
Forbes - Security
H
Heimdal Security Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Hugging Face - Blog
Hugging Face - Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
T
Troy Hunt's Blog
博客园 - 三生石上(FineUI控件)
Application and Cybersecurity Blog
Application and Cybersecurity Blog
罗磊的独立博客
WordPress大学
WordPress大学
D
Darknet – Hacking Tools, Hacker News & Cyber Security

Posts on Changkun's Blog

Why High-Output Systems Are Often the First to Stop Growing Dark Forest Theory: A Formal Derivation Agents (or Humans) in Goal-Directed and Goalless Environments: On Pipelines, Priors, and the Rhythm Between Exploration and Exploitation At the Boundary of Self-Reference: From Stable Structures in Artificial Intelligence to the Self as a Recursive Model in an Open Dissipative System 2023 Reading List 2022 Reading List 2021 Reading List Are PSS/USS and RSS Actually the Same Thing? Performance Differences from Page Faults vs. Prefetching 2020 Year-End Review Migration with Zero Downtime 2020 Reading List The All in Go Stack Pointers Might Not Be Ideal for Parameters Eliminating A Source of Measurement Errors in Benchmarks Setup Wordpress in 10 Minutes 我为什么不再写博客了? 2019 年终总结 2018-2019 读书清单 Ten years of blogging Rethinking the Reflections on Communications and Trusts 2018 年终总结 Go source code study is open source Go source study: unsafe Pattern Go source study: sync.Pool Go runtime programming A Million WebSocket and Go Designing Asynchronous RESTful APIs 分布式杂谈01:CAP 理论的误解 Issues of Human-Bot Interaction 压缩法与深度网络的泛化性 Go in 1 Hour UMSLT04: The Past and Present of SGD UMSLT03: A Gentle Start of Learning Theory UMSLT02: A Breif History of Neural Networks UMSLT01: A Breif History of Regularization 不笑不足以为道 论文笔记:Generalization in Deep Learning 2017 年终总结 2017 读书清单 深度学习的泛化理论简介 删除 GitHub 上已经提交的敏感信息 硕士生涯的第一年就这样告一段落了 人肉计算(10): 系统参与激励 人肉计算(9): 陷阱的解法 别聊,一聊你就暴露 人肉计算(8): 人肉计算与数据科学中的陷阱 人肉计算(7): 社会行为分析 Hexo + GitHub + Travis CI + VPS 自动部署 人肉计算(6): 预测市场 人肉计算(5): 信用风险评级模型 读书与回报 瞎扯: 对现代企业理论与当下IT企业的商业模式和信息产业链的规律性的思考 人肉计算(4): 输入数据聚合与PageRank 又一次打整了一下博客 人肉计算(3): 输入数据聚合与链路预测 人肉计算(2): 意图博弈 GWAPs 人肉计算(1): 众包与群众智慧 对后辈同学在计算机专业上的答疑与解惑 在德国的医疗及住院体验 这可能不是一个技术博客了 实验楼楼赛第3期-Python-题解 迅速更换了 DISQUS Electron 深度实践总结 良好的编码体验的三个方面 2016 年终总结 2016 读书清单 最近在着手写的文章 微信小程序文档极致总结 谈谈过去三个月在实验楼的实习经历 Built a Desktop Client for My Blog Guacamole 源码分析与 VNC 中 RFB 协议的坑 《高速上手 C++11/14》正式发布 Docker 极速入门教程02 - 镜像与容器管理 Docker 极速入门教程01 - 基本概念和操作 阶段性沉默 ELK+Redis 最佳实践 终于全面启用了 HTTPS 苹果开源了LZFSE无损压缩 Hash 碰撞的一种思路 记一次完整的 Kaldi-TIMIT 示例运行 Kaldi 上的 TIMIT 例子 Kaldi 安装与部署 从科研写作谈起 Swift API 设计指南 有趣的人类 所以其实论文并没有什么鬼用 Githug 通关记录及指南 小结一下这学期的收获 2015 读书清单 2015 年终总结 负能量爆表 转眼就快两个月了 博客迁移记录 大三总结 这个世界,终究不会是我们的。 Linux 内核分析 之六:Linux 内核创建进程的过程 小说「泽缘」 Linux 内核分析 之五:system_call中断处理过程的简要分析 大创项目的标题真是每年都在考验同学们的想象力啊
Linux 内核分析 之四:使用库函数API和嵌入汇编两种方式使用同一个系统调用
Changkun Ou · 2015-03-29 · via Posts on Changkun's Blog

Published at发布于:   |   PV/UV: /   |   Reading阅读: 3 min

说明

欧长坤 原创作品转载请注明出处 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 这学期学校恰好有操作系统的课程,上个学习就开始寻思研究研究Linux内核代码,恰好MOOC有这个课程,遂选了此课。

一、准备工作

本周的实验比起前三周的实验稍微容易得多。我们可以在http://codelab.shiyanlou.com/xref/linux-3.18.6/arch/x86/syscalls/syscall_32.tbl中查看系统调用号。

二、分析

在这里,我们决定使用sysinfo这个库函数API,首先,我们得会使用这个API。

在 Linux 中,sysinfo可以用来获取系统相关信息的结构体。 函数声明和原型如下所示:

1
2
#include <sys/sysinfo.h>
int sysinfo(struct sysinfo *info);

那么,这个sysinfo的结构体长什么样?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
struct sysinfo {                  
    long uptime;
    unsigned long loads[3];  // 启动到现在经过的时间             
    unsigned long totalram;  // 总的可用的内存大小
    unsigned long freeram;   // 还未被使用的内存大小
    unsigned long sharedram; // 共享的存储器的大小
    unsigned long bufferram; // 缓冲区大小                  
    unsigned long totalswap; // 交换区大小              
    unsigned long freeswap;  // 还可用的交换区大小
    unsigned short procs;    // 当前进程数目
    char _f[22];             // 64字节的补丁结构
};

其实我们都并不关心这个sysinfo的结构到底长什么样,我们目前所关心的是如何能够成功的调用。 实际上看到这里,我们已经能够完成使用库函数调用的C代码了。

那么怎么用汇编来实现呢? 我们得知道sysinfo的系统调用号是多少,容易知道sysinfo的系统调用号是116。所以,嵌入汇编时的值应该为0x74。

三、实验过程

我们先写好使用库函数API调用的版本,使用vi来编辑一个syscall.c的代码:

1
ouchangkun@ubuntu:~/Works/syscall$ vi syscall.c

代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
#include <sys/sysinfo.h>

int main() {
    struct sysinfo sys_info;
    int error;
    error = sysinfo(&amp;sys_info); // 在这里,我们完成了对sysinfo这个库函数API的调用
    printf("code error=%d\n",error);

    printf("Uptime = %lds\n"
           "Load: 1 min%ld / 5 min %ld / 15 min %ld\n"
           "RAM: total %ld / free %ld / shared%ld\n"
           "Memory in buffers = %ld\n"
           "Swap: total%ld / free%ld\n"
           "Number of processes = %d\n",
           sys_info.uptime,
           sys_info.loads[0], sys_info.loads[1],  sys_info.loads[2],
           sys_info.totalram,  sys_info.freeram,  sys_info.sharedram,
           sys_info.bufferram,
           sys_info.totalswap, sys_info.freeswap,
           sys_info.procs);
    return 0;
}

接下来编译和执行都不用说,然后就是输出结果:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
ouchangkun@ubuntu:~/Works/syscall$ gcc syscall.c -o syscall -m32
ouchangkun@ubuntu:~/Works/syscall$ ./syscall 
code error=0
Uptime = 1020s
Load: 1 min2336 / 5 min 2432 / 15 min 3008
RAM: total 1038680064 / free 74907648 / shared0
Memory in buffers = 31862784
Swap: total1071640576 / free1070592000
Number of processes = 425
ouchangkun@ubuntu:~/Works/syscall$ 

至此,我们完成了一个使用库函数调用的版本。

好,那么我们现在要来编写嵌入汇编的版本。

正常情况下,我们一般会对老师编写time函数的汇编版本产生下面的疑惑: 在老师的代码中,

1
2
3
4
5
asm volatile(
"mov $0,%%ebx\n\t"
"mov $0x80,%%eax\n\t"
...
);

最开始这一行: mov $0,%%ebx 为什么要将%%ebx清零呢?

事实上在系统调用时,system_call是linux系统调用的入口点。每个系统调用至少有一个参数,那就是eax,它负责传递系统调用号,同时获取返回值。 除了eax外,还允许至多6个参数,分别是ebx,ecx,edx,esi,edi,ebp。 另一方面,容易观察到,实际上time()函数除了自身的传入系统调用号(同时接收返回值)外,还传入了一个参数NULL。 结合上面的叙述,应该可以猜到,其实代码mov $0,%%ebx\n\t是相当于向ebx传入了一个参数NULL,也就是0

同理,对于sysinfo这个库函数API,它也有一个返回值,表示是否成功,并且传递进来一个参数sys_info来接收系统的相关信息。因此,我们可以编写下面的代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// syscall_asm.c
#include <stdio.h>
#include <sys/sysinfo.h>

int main() {
    struct sysinfo sys_info;
    int error;
    //error = sysinfo(&amp;sys_info);
    asm volatile(
        "movl %1, %%ebx\n\t"
        "movl $0x74, %%eax\n\t"  // sysinfo 的系统调用号是 116 所以十六进制为 0x74
        "int $0x80\n\t"
        "movl %%eax, %0"
        : "=m" (error)           // eax来负责传递返回值,我们同样用error来接收
        : "b" (&amp;sys_info)        // sysinfo的地址作为参数,传递进ebx中作为参数被修改接收系统信息
    );

    printf("code error=%d\n",error);

    printf("Uptime = %lds\n"
           "Load: 1 min%ld / 5 min %ld / 15 min %ld\n"
           "RAM: total %ld / free %ld / shared%ld\n"
           "Memory in buffers = %ld\n"
           "Swap: total%ld / free%ld\n"
           "Number of processes = %d\n",
           sys_info.uptime,
           sys_info.loads[0], sys_info.loads[1],  sys_info.loads[2],
           sys_info.totalram,  sys_info.freeram,  sys_info.sharedram,
           sys_info.bufferram,
           sys_info.totalswap, sys_info.freeswap,
           sys_info.procs);
    return 0;
}

所以,最后的执行结果我们可以看到,是一样的:

1
2
3
4
5
6
7
8
9
ouchangkun@ubuntu:~/Works/syscall$ gcc syscall_asm.c -o syscall_asm -m32
ouchangkun@ubuntu:~/Works/syscall$ ./syscall_asm 
code error=0
Uptime = 3227s
Load: 1 min288 / 5 min 2176 / 15 min 2976
RAM: total 1038680064 / free 111718400 / shared0
Memory in buffers = 86638592
Swap: total1071640576 / free1047654400
Number of processes = 422

四、总结

我们来总结一下: 一般的,进程是不能访问内核的。它不能访问内核所占内存空间也不能调用内核函数。CPU硬件决定了这些(这就是为什么它被称作"保护模式")。系统调用是这些规则的一个唯一例外。其原理是进程先用适当的值填充寄存器,然后调用一个特殊的指令(系统调用号),这个指令会跳到一个事先定义的内核中的一个位置(显然,这个位置是用户进程可读但是不可写的)。在Intel CPU中,这个由中断0x80实现。硬件知道一旦你跳到这个位置,你就不是在限制模式下运行的用户,而是作为操作系统的内核,所以你就可以为所欲为了。 进程可以跳转到的内核位置叫做sysem_call。这个过程检查系统调用号,这个号码告诉内核进程请求哪种服务。然后,它查看系统调用表(sys_call_table)找到所调用的内核函数入口地址。接着,就调用函数,等返回后,做一些系统检查,最后返回到进程(或到其他进程,如果这个进程时间用尽)。 调用内核的参数传递最多涉及七个寄存器:eax,ebx,ecx,edx,esi,edi,ebp。其中,eax用来传入系统调用号,并用来接收返回值。其他六个参数作为函数的输入或输出参数被传递进去。