Linux内核云课堂作业第四章(上)

使用库函数API和嵌入汇编两种方式使用同一个系统调用
1. 说明
刘玉龙
原创作品转载请注明出处
《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
2. 分析
在这里,我们决定使用sysinfo这个库函数API,首先,我们得会使用这个API。
在 Linux 中,sysinfo可以用来获取系统相关信息的结构体。使用C和内嵌汇编方式完成系统调用 使用了39号系统调用sys_mkdir来进行实验。

<number> <abi>  <name>           <entry point> 
39                     i386       mkdir             sys_mkdir

函数声明和原型如下所示

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

sysinfo的结构体:::

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。

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

#include <stdio.h>
#include <sys/sysinfo.h>

int main() {
    struct sysinfo sys_info;
    int error;
    error = sysinfo(&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;
}

调用完后的情况

Liuyulong@ubuntu:~/Works/syscall$ gcc syscall.c -o syscall -m32
Liuyulong@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
Liuyulong@ubuntu:~/Works/syscall$

4.比较一下

asm volatile(
"mov $0,%%ebx\n\t"
"mov $0x80,%%eax\n\t"
...
);

系统调用时,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来接收系统的相关信息。
再看如下代码::看完结果便知一切

#include <stdio.h>
#include <sys/sysinfo.h>

int main() {
    struct sysinfo sys_info;
    int error;
    //error = sysinfo(&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" (&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;
}

结果许下:

Liuyulong@ubuntu:~/Works/syscall$ gcc syscall_asm.c -o syscall_asm -m32
Liuyulong@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

结果一养的。。

5.两次查看效果
通过进行实验,并且对系统调用过程的分析,了解了系统调用是用户操作系统硬件等内核态操作的桥梁以及了解了系统调用的工作过程。
API与系统调用
API只是一个函数定义,系统调用是通过软中断向内核发出一个明确的请求。
但是有的API是封装了软中断的一个函数,便于编程使用。
例如上面的mkdir函数,便是封装了39号系统调用,当调用mkdir函数时,会产生一个39号的系统调用。
系统调用的过程
上面的mkdir系统调用,可以使用下面的图来表示:
(1) 首先用户调用mkdir API
(2) 在API的具体实现里面会调用39号系统调用,使用int $80软中断进入内核态
(3) 此时会压入用户的栈顶地址,当前状态字,和CS:EIP。
(4) 然后使用SAVE ALL保存现场,内核进程根据用户的系统调用号,查找系统调用表(sys_call_table),找到中断处理子程序的地址,并且进入内核函数
(5) 完成调用中断处理子程序后,使用RESTORE ALL还原现场,返回调用的用户态空间

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值