zoukankan      html  css  js  c++  java
  • 20135220谈愈敏Blog4_系统调用(上)

    系统调用(上)

    谈愈敏 原创作品转载请注明出处 《Linux内核分析》MOOC课程 http://mooc.study.163.com/course/USTC-1000029000

    用户态、内核态和中断

    系统调用是用户通过库函数方式:库函数帮我们把系统调用封装起来。

    用户态&内核态(CPU执行级别)
    • 内核态:高级别执行,可以使用特权指令,访问任意的物理地址。对应x86 0级
    • 用户态:低级别执行,代码范围受到限制。对应x86 3级(x86CPU有0-3四个级别)

    这种权限级别划分让系统更稳定。

    如何区分用户态&内核态?

    地址空间是显著标识,cs:eip[代码段选择寄存器:偏移量寄存器](指令地址)

    • 0xc0000000以上的地址只能在内核态下访问
    • 0x00000000-0xbfffffff两种状态都行

    注意:这里指逻辑地址(进程地址空间)而不是物理地址

    另:cs寄存器的最低两位,表示当前代码的特权级

    中断处理是从用户态进入内核态主要的方式
    • 从用户态进入内核态:必须保存用户态的寄存器上下文
    • 中断/int指令在堆栈上保存寄存器的值:用户态/内核态栈顶地址(ss:esp)、状态字(eflags)、cs:eip值(内核态时指向中断服务程序入口)

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

    中断发生后第一件事就是保存现场:

    进入中断程序,保存寄存器数据
    SAVE_ALL
    

    中断结束前最后一件事就是恢复现场:

    退出中断程序,恢复寄存器数据
    RESTORE_ALL
    iret(pop cs:eip/ss:esp/eflags from kernel stack)
    #对应着中断信号或int指令,与发生时CPU动作相反
    

    系统调用概述

    系统调用的意义

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

    • 用户不管硬件编程
    • 提高系统安全性
    • 用户程序可移植
    API和系统调用
    • API:应用编程接口,是一个函数定义
    • 系统调用:通过软中断向内核发出明确请求

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

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

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

    • API可直接提供用户态服务,如数学函数
    • 一个API可调用几个系统调用
    • 不同API可调用同一系统调用

    返回值

    • 封装例程返回一个整数,含义依赖与相应系统调用
    • -1表示内核不能满足进程的要求
    • Libc定义的errno变量包含特定出错码
    应用程序、封装例程、系统调用处理程序、系统调用服务例程之间的关系

    xyz是一个函数,即API
    应用程序编程接口里封装了一个系统调用,触发一个中断:中断向量0x80对应内核代码系统调用起点system_call
    中断服务程序(sys_xyz)
    

    系统调用三层皮:

    • API(xyz)
    • 中断向量(system_call)
    • 中断服务程序(sys_xyz)

    用户态进程调用系统调用时,CPU切换到内核态执行内核函数(Linux中通过执行int $128来执行系统调用,产生向量为128的编程异常)

    传参:进程指明需要哪个系统调用,传递系统调用号,使用eax传递

    • 系统调用号将xyz与sys_xyz关联起来
    • system_call是linux中所有系统调用的入口点,每个系统调用至少有一个参数,即系统调用号。

    寄存器传递参数的限制:

    • 每个参数长度不能超过寄存器长度即32位
    • 在系统调用号eax之外,个数不能超过6个

    超过6的话:某个寄存器中存储指针,指针会指向一个地址空间,存储参数。

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

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

    使用time():

    #include<stdio.h>
    #include<time.h>
    
    int  main() 
    {
    	time_t tt;  
    	struct tm *t;//构造一个结构体,将tt转换为这个方便读取时间的
    	tt = time(NULL);//time系统调用
    	t = localtime(&tt); //类型装换为我们熟知的时间格式 
    	printf("time:%d:%d:%d:%d:%d:%d
    ", t->tm_year+1900, t->tm_mon, t->tm_mday,  t->tm_hour, t->tm_min, t->tm_sec);
    	return 0; 
    } 
    

    编译:gcc time.c -o time -m32

    使用C代码中嵌入汇编代码触发系统调用获取系统当前时间
    #include<stdio.h>
    #include<time.h>
    
    int  main() 
    {
    	time_t tt;  
    	struct tm *t;
    	asm volatile(  
        	"mov $0,%%ebx
    	"  //把ebx清零,系统调用传递第一个参数用ebx,这里是NULL
        	"mov $0xd,%%eax
    	"//把0xd放入eax中,即eax传递系统调用号13——time 
        	"int $0x80
    	"     //内核处理系统调用
        	"mov %%eax,%0
    	"  //系统调用返回值是在eax中,%0指tt,把返回值放到tt中去。
        	: "=m" (tt)   
    	);  
    	t = localtime(&tt);  
    	printf("time:%d:%d:%d:%d:%d:%d
    ", t->tm_year+1900, t->tm_mon, t->tm_mday,  t->tm_hour, t->tm_min, t->tm_sec);
    	return 0; 
    } 
    

    这段代码让我们更清楚的知道用户态进程对内核态做了什么

    • 传递了一个系统调用号 - eax
    • 传递了参数 - ebx

    实验

    选择20号系统调用getpid来获取进程ID

    使用库函数API获取系统当前进程ID

    代码:

    运行:

    使用C代码中嵌入汇编代码触发系统调用获取系统当前进程ID

    思想:和视频示例中类似,只是传递给eax的系统调用号为0x14,即20号系统调用getpid。

    代码:

    运行:

  • 相关阅读:
    c# 查找进程
    第三方打包工具
    WebClient 上传和下载
    .net 读取文件
    winfrom 圆角panel
    窗体或控件的两种拖动方式
    winfrom 获取当前屏幕尺寸
    动态修改配置文件web服务地址
    将字符串编码成 GBK
    .net(C#) 读取配置文件
  • 原文地址:https://www.cnblogs.com/tymjava/p/5287833.html
Copyright © 2011-2022 走看看