LINUX内核分析第四周学习总结——扒开应用系统的三层皮(上)

LINUX内核分析第四周学习总结——扒开应用系统的三层皮(上)

张忻(原创作品转载请注明出处)

《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000

一、知识概要

(一)用户态、内核态和中断处理过程

(二)系统调用概述

系统调用概述和系统调用的三层皮

(三)使用库函数API和C代码中嵌入汇编代码触发同一个系统调用

使用库函数API获取系统当前时间

C代码中嵌入汇编代码的方法(复习)

使用C代码中嵌入汇编代码触发系统调用获取系统当前时间

二、学习笔记

(一)用户态、内核态和中断处理过程

  • 一般现代CPU都有几种不同的指令执行级别。
  • 在高执行级别下,代码可以执行特权指令,访问任意的物理地址,这种CPU执行级别就对应着内核态。
  • 而在相应的低级别执行状态下,代码的掌控范围会受到限制。只能在对应级别允许的范围内活动。
  • 举例:Intel x86CPU有四种不同的执行级别0-3,Linux只使用了其中的0 3级分别表示内核态和用户态
  • cs寄存器的最低两位表明了当前代码的特权级。
  • CPU每条指令的读取都是通过cs:eip这两个寄存器:其中cs是代码段选择寄存器,eip是偏移量寄存器。
  • 上述判断由硬件完成。
  • 一般来说在Linux中,地址空间是一个显著地标志:0xc0000000以上的地址空间只能在内核态下访问,都可以访问0x00000000-0xbfffffff的地址空间在两种状态下。注意:这里说的地址空间是逻辑地址而不是物理地址。

中断处理是是从用户态进入内核态的主要方式。

系统调用只是一种特殊的中断。

  • 寄存器上下文 从用户态切换到内核态时,必须要保存用户态的寄存器上下文。
  • 中断/int指令会在堆栈上保存一些寄存器的值。如:用户态栈顶地址、当时的状态字、当时的cs:eip的值。

中断发生后的第一件事就是保存现场,结束前最后一件事是恢复现场。

  • 保护现场就是进入中断程序 保存需要用到的寄存器的数据。
  • 恢复现场就是推出中断程序 恢复保存寄存器的数据。

中断处理的完整过程

  • interrupt(ex:int 0x80)-save
  • SAVE_ALL
  • RESTORE_ALL
  • iret-pop cs:eip/ss:esp/eflags from kernel stack

(二)系统调用概述

系统调用概述和系统调用的三层皮

1.系统调用的意义

操作系统为用户态进程与硬件设备进行交互提供了一组接口——系统调用

  • 把用户从底层的硬件编程中解放出来
  • 极大的提高了系统的安全性
  • 使用户程序具有可移植性

2.API和系统调用

应用编程接口(application program interface, API) 和系统调用是不同的

  • API只是一个函数定义
  • 系统调用通过软中断向内核发出一个明确的请求

Libc库定义的一些API引用了封装例程 (wrapper routine,唯一目的就是发布系统调用)

  • 一般每个系统调用对应一个封装例程
  • 库再用这些封装例程定义出给用户的API

不是每个API都对应一个特定的系统调用。

  • API可能直接提供用户态的服务。如,一些数学函数
  • 一个单独的API可能调用几个系统调用
  • 不同的API可能调用了同一个系统调用

返回值

  • 大部分封装例程返回一个整数,其值的含义依赖于相应的系统调用
  • -1在多数情况下表示内核不能满足进程的请求
  • Libc中定义的errno变量包含特定的出错码

3.应用程序、封装例程、系统调用处理程序及系统调用服务例程之间的关系

 

系统调用的三层皮:xyz、system_call和sys_xyz

(1)系统调用程序及服务例程

当用户态进程调用一个系统调用时,CPU切换到内核态并开始执行一个内核函数。

  • 在Linux中是通过执行int $0x80来执行系统调用的, 这条汇编指令产生向量为128的编程异常
  • Intel Pentium II中引入了sysenter指令(快速系统调 用),2.6已经支持(本课程不考虑这个)

传参:

  • 内核实现了很多不同的系统调用, 进程必须指明需要哪个系统调用,这需要传递一个名为系统调用号的参数(系统调用号将xyz和sys_xyz关联起来了)
  • 使用eax寄存器

 

(2)参数传递
系统调用也需要输入输出参数,例如

  • 实际的值
  • 用户态进程地址空间的变量的地址
  • 甚至是包含指向用户态函数的指针的数据结构的地址

system_call是linux中所有系统调用的入口点,每个系统调用至少有一个参数 ,即由eax传递的系统调用号

  • 一个应用程序调用fork()封装例程,那么在执行int $0x80之前就把eax寄存器的值 置为2(即__NR_fork)。
  • 这个寄存器的设置是libc库中的封装例程进行的,因此用户一般不关心系统调用号
  • 进入sys_call之后,立即将eax的值压入内核堆栈
  • 寄存器传递参数具有如下限制: 1)每个参数的长度不能超过寄存器的长度,即32位 2)在系统调用号(eax)之外,参数的个数不能超过6个(ebx, ecx,edx,esi,edi,ebp) 3)超过6个怎么办?把某一个寄存器作为一个指针,指向某一块内存。

(三)使用库函数API和C代码中嵌入汇编代码触发同一个系统调用

1.使用库函数API获取系统当前时间

2.C代码中嵌入汇编代码的方法(复习)

3.使用C代码中嵌入汇编代码触发系统调用获取系统当前时间

三、作业

1.实验过程

例一

分析汇编代码调用系统调用的工作过程,特别是参数的传递的方式等。

(1)通过系统调用函数chomd函数改变文件的权限为只读

代码如下:

#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdio.h>

int main()
{
        int rc;

        rc = chmod("/etc/passwd", 0444);
        if (rc == -1)
                fprintf(stderr, "chmod failed, errno = %d\n", errno);
        else
                printf("chmod success!\n");
        return 0;
}

在普通用户下编译运用,输出结果为:

上面系统调用返回的值为-1,说明系统调用失败,错误码为1,

即无权限进行该操作,我们以普通用户权限是无法修改 /etc/passwd 文件的属性的,结果正确。

(2)使用C代码中嵌入汇编代码触发系统调用改变文件的权限为只读

代码如下:

#include <stdio.h>
#include <sys/types.h>
#include <sys/syscall.h>
#include <errno.h>

int main()
{
        long rc;
        char *file_name = "/etc/passwd";
        unsigned short mode = 0444;

        asm(
                "int $0x80"
                : "=a" (rc)
                : "0" (SYS_chmod), "b" ((long)file_name), "c" ((long)mode)
        );

        if ((unsigned long)rc >= (unsigned long)-132) {
                errno = -rc;
                rc = -1;
        }

        if (rc == -1)
                fprintf(stderr, "chmode failed, errno = %d\n", errno);
        else
                printf("success!\n");

        return 0;
}

如果 eax 寄存器存放的返回值(存放在变量 rc 中)在 -1~-132 之间,就必须要解释为出错码(在/usr/include/asm-generic/errno.h 文件中定义的最大出错码为 132),这时,将错误码写入 errno 中,置系统调用返回值为 -1;否则返回的是 eax 中的值。

结果如图:

上面程序在 32位Linux下以普通用户权限编译运行结果与前面两个相同。

例二

生成一个与主进程一样的子进程。fork的系统调用号为2

 

(1)通过系统调用函数

 

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>

void main()
{
    printf("Anthony&&Cindy");
    fork();
}

 

 

(2)使用C代码中嵌入汇编代码触发系统调用

 

#include<stdio.h> 
#include<sys/types.h> 
#include<unistd.h> void main()
{ 
    int pid; printf("Anthony&&Cindy"); 
    asm volatile(
    "mov $0x2,%%eax\n\t"    
    "int $0x80\n\t"       
    "mov %%eax,%0\n\t"   
    :"=m"(pid)
    ); 
}

 

 

2.总结

对“系统调用的工作机制”的理解。

可以通过库函数API使用系统调用或者用汇编方式触发系统调用。

 

参考资料:http://www.linuxidc.com/Linux/2014-12/110238.htm

 

posted @ 2016-03-14 20:35  xixixixixi  阅读(347)  评论(0编辑  收藏  举报