您的位置:新葡亰496net > 电脑系统 > 新葡亰496net:编程中的API函数和系统调用的关系

新葡亰496net:编程中的API函数和系统调用的关系

发布时间:2019-07-14 22:05编辑:电脑系统浏览(108)

    操作系统通过系统调用为运营于其上的历程提供劳务。

    操作系统通过系统调用为运转于其上的经过提供劳务。

    具有的程序猿在写程序的时候都离不开通过库函数的法子和连串调用打交道

    安徽大学大 原创作品转发请注脚出处 《Linux操作系统一分配析》MOOC课程

    转自:

    当用户态进度发起二个系统调用, CPU 将切换成 内核态 并开头施行一个 内核函数 。 内核函数担当响应应用程序的须求,比方操作文件、实行网络通信或然申请内部存款和储蓄器能源等。

    当用户态进度发起三个体系调用, CPU 将切换来 内核态 并开端实践一个内核函数 。 内核函数担负响应应用程序的渴求,比如操作文件、举办互连网通信也许申请内部存款和储蓄器财富等。

    怎么是用户态和内核态?(从CPU指令等第的角度)

    用户态、内核态和间断管理进程

    程序员通过库函数的方法和种类调用打交道,库函数把系统调用给封装起来了。
    一般当代CPU都有二种差异的指令推行等第
    ♦ 在高实行等级下,代码能够实践特权指令,访问任性的物理地址,这种CPU试行品级就对应着内核态
    ♦ 而在相应的低档别推行情形下,代码的掌握控制范围会受到限制。只好在对应等级允许的范围内活动
    ♦ 举个例子:intel x86 CPU有各个不一样的试行等第0-3,Linux只使用了内部的0级和3级分别来代表内核态和用户态


    初稿地址:Linux 编制程序中的API函数和种类调用的涉及 作者:up哥小号

    原稿地址:https://learn-linux.readthedocs.io
    玩转Linux旧群已满,请加新群:278378501。
    款待关心大家的民众号:小菜学编制程序 (coding-fan)

    举多个最简便易行的例子,应用进度须求输出一行文字,需求调用 write 这些系统调用:

    一般今世CPU都有二种差异的通令执行品级,什么样的先后能够举行如何的授命
    在高施行等第下,代码能够实践特权指令,访问随意的大要地址,这时CPU推行等级就对应着内核态
    而在相应的低端别推行景况下,代码的掌握控制范围会遭到限制。只好在对应等第允许的范围内移动
    比如:intel x86 CPU有七种分化的进行等级0-3,Linux只行使了内部的0级3级分别来代表内核态用户态

    何以有权力等第的细分

    防御技士违法访谈系统大概是别的能源而使得系统崩溃

     

    举二个最简便的事例,应用进度供给输出一行文字,须要调用 write 这一个系统调用:

    hello_world.c

    如何区分用户态和内核态?(从进度地址空间的角度)

    Linux中怎么差别用户态和内核态:

    ♦ cs存放器的最低两位评释了当下代码的特权级
    ♦ CPU每条指令的读取都以通过cs:eip这多少个贮存器:
    中间cs是代码段选用寄放器,eip是偏移量寄放器。
    ♦ 上述推断由硬件完毕
    ♦ 一般的话在Linux中,地址空间是多个深入人心的标识:
    0xc0000000上述的地点空间只好在内核态下访谈,0x00000000-0xbfffffff的地点空间在二种情状下都得以访谈
    留心:这里所说的地址空间是逻辑地址并不是概况地址

    在内核态时,cs和eip能够是随便的地方


    API:(Application Programming Interface,应用程序编制程序接口)
      指的是我们用户程序编制程序调用的如read(),write(),malloc(),free()之类的调用的是glibc库提供的库函数。API直接提要求用户编制程序使用,运转在用户态。
      大家平时说起的POSIX(Portable Operating System Interface of Unix)是对准API的正儿八经,即针对API的函数名,再次来到值,参数类型等。POSIX兼容也就内定这个接口函数兼容,可是并不管API具体哪些促成。

    #include <string.h>
    #include <unistd.h>
    
    int main(int argc, char *argv[])
    {
        char *msg = "Hello, world!n";
        write(1, msg, strlen(msg));
    
        return 0;
    }
    
    #include <string.h>
    #include <unistd.h>
    
    int main(int argc, char *argv[])
    {
     char *msg = "Hello, world!n";
     write(1, msg, strlen(msg));
    
     return 0;
    }
    

    cs贮存器的最低两位评释了当下代码的特权级
    CPU每条指令的读取都以透过cs:eip那四个存放器:
          个中  cs是代码段接纳存放器,eip是偏移量存放器
    上述决断由硬件完毕

    暂停管理是从用户态进入内核态主要的点子

    用户态进入内核态一般的话都以用中断来触发的,也许是硬件中断。也也许是用户态程序运营个中调用了系统调用步向了内核态(trap)。系统调用是一种奇特的中断。

    ♦ 存放器上下文
    – 从用户态切换成内核态时
    • 必须保留用户态的寄放器上下文,同一时间内核态相应的值放到CPU中
    • 要保存哪些?
    • 保存在哪个地方?
    ♦ 中断/int指令会在酒店上保存一些寄放器的值
    – 如:用户态栈顶地址、当时的状态字、当时的cs:eip的值

    系统调用
      通过软中断或系统调用指令向基础发出二个显眼的伸手,内核将调用内核相关函数来落成(如sys_read(),sys_write(),sys_fork())。用户程序无法平昔调用这么些Sys_read,sys_write等函数。这个函数运营在内核态。

    注解

    读者恐怕会有些难题——输出文本不是用 printf 等函数吗?

    确实是。 printf 是更加高档期的顺序的库函数,创建在系统调用之上,完结数量格式化等效率。 因而,本质上依旧系统调用起决定性功效。

    注解

    在叁九位x86的机械上,有4G的长河地址空间(逻辑地址),在内核态的时候全都能够访谈,在用户态的时候,只可以访问0x00000000-0xbfffffff的地点空间。也便是说0xc0000000以上的地方空间只可以在内核态下访问

    暂停爆发后先是件事正是保存现场 SAVE_ALL

    保卫安全现场 正是跻身暂停程序,保存需求利用的存放器的数额
    复原现场 正是脱离中断程序,恢复生机保存寄放器的数据

    相互关系:

    调用流程

    那么,在应用程序内,调用一个连串调用的流程是什么样的呢?

    咱俩以三个例如的系统调用 xyz 为例,介绍二次系统调用的兼具环节。

    新葡亰496net 1

    如上航海用教室,系统调用实践的流程如下:

    1. 应用程序 代码调用系统调用( xyz ),该函数是一个包装系统调用的 库函数 ;
    2. 库函数 ( xyz )担任筹划向基础传递的参数,并触发 软中断 以切换成根本;
    3. CPU 被 软中断 打断后,执行 暂停管理函数 ,即 系统调用管理函数 ( system_call);
    4. 系统调用管理函数 调用 系统调用服务例程 ( sys_xyz ),真正发轫拍卖该系统调用;

    读者大概会略带难点——输出文本不是用 printf 等函数吗?

    停顿处理是从用户态走入内核态重要的办法

    暂停管理终结前最终一件事是回复现场 RESTORE_ALL

    新葡亰496net 2

      通常API函数库(如glibc)中的函数会调用封装例程,封装例程肩负发起系统调用(通过发软中断或体系调用指令),这么些都运维在用户态。内核开首收取系统调用后,cpu从用户态切换来内核态(cpu处于怎么着情况,程序就叫处于如何境况,所以众多地点也说程序从用户态切换成内核态,实际是cpu运维品级的切换,通常cpu 运营在3级表示用户态,cpu 运转在0级表示内核态),内核调用相关的内核函数来拍卖再逐步再次回到给封装例程,cpu进行一遍内核态到用户态的切换,API函数从包装例程获得结果,再处理完后归来给用户。
     可是API函数不自然供给开始展览系统调用,如有个别数学函数,无需打开系统调用,直接glibc里面就给处理了,整个经过运转在用户态。
      所以作为大家编写linux用户程序的时候,是不能够平昔调用内核里面的函数的,内核里面的函数位于进度设想地址空间里面包车型地铁基业空间,用户空间函数及函数库都处于进度设想地址空间里面的用户空间,用户空间调用内核空间的函数只有八个坦途,那些通道正是系统调用指令,所以平常要调用glibc等库的接口函数,glibc也是用户空间的,但glibc自个儿实现了调用特殊的宏汇编系统调用指令张开cpu运营景况的切换,把进度从用户空间切换成基础空间。

    实施态切换

    应用程序 ( application program )与 库函数 ( libc )之间, 系统调用管理函数 ( system call handler )与 系统调用服务例程 ( system call service routine )之间, 均是普通函数调用,应该简单驾驭。 而 库函数 与 系统调用管理函数 之间,由于涉及用户态与内核态的切换,要复杂一些。

    Linux 通过 软中断 实现从 用户态 到 内核态 的切换。 用户态 与 内核态 是独自的实践流,由此在切换时,必要希图 执行栈 并保存 寄存器 。

    水源达成了大多不及的系统调用(提供分化作用),而 系统调用处理函数 独有一个。 由此,用户进度必须传递三个参数用于区分,那正是 系统调用号 ( system call number )。 在 Linux 中, 系统调用号 一般经过 eax 寄存器 来传递。

    小结起来, 推行态切换 进度如下:

    1. 应用程序 在 用户态 希图好调用参数,推行 int 指令触发 软中断 ,中断号为 0x80 ;
    2. CPU 被软中断打断后,试行相应的 停顿管理函数 ,那时便已步向 内核态 ;
    3. 系统调用管理函数 准备 水源施行栈 ,并保留全体 寄存器 (一般用汇编语言完成);
    4. 系统调用管理函数 根据 系统调用号 调用对应的 C 函数—— 系统调用服务例程 ;
    5. 系统调用管理函数 准备 返回值 并从 内核栈 中恢复 寄存器 ;
    6. 系统调用处理函数 执行 ret 指令切换回 用户态 ;

    真正是。 printf 是越来越高档次的库函数,创建在系统调用之上,完成数量格式化等职能。 由此,本质上恐怕系统调用起决定性成效。

    当从用户态切换成内核态的时候,必须用户态的寄放器上下文物保护存起来,同卓殊候设置内核态的寄放器内容
    停顿/int指令会在饭馆上保留一些存放器的值
          如:用户态栈顶地址、当时的状态字、当时的 cs:eip 的值
    何况设置内核态的栈顶地址、内核态的状态字,中断管理程序的入口地址 cs:eip 的值(对于系统调用来说,它是指向system_call函数)

    停顿管理的欧洲经济共同体进程

    新葡亰496net 3


    用户态函数试行全经过(这里只讲须求举办系统调用的函数)新葡亰496net 4

    编程施行

    下边,通过多少个归纳的程序,看看应用程序怎么样在 用户态 计划参数并因此 int 指令触发 软中断 以陷入 内核态 执行 系统调用 :

    .section .rodata
    
    msg:
        .ascii "Hello, world!n"
    
    .section .text
    
    .global _start
    
    _start:
        # call SYS_WRITE
        movl $4, 
    		

    本文由新葡亰496net发布于电脑系统,转载请注明出处:新葡亰496net:编程中的API函数和系统调用的关系

    关键词: