4系统调用的工作机制

安大大 + 原创作品转载请注明出处 + 《Linux操作系统分析》MOOC课程

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

程序员通过库函数的方式和系统调用打交道,库函数把系统调用给封装起来了。
一般现代CPU都有几种不同的指令执行级别
♦ 在高执行级别下,代码可以执行特权指令,访问任意的物理地址,这种CPU执行级别就对应着内核态
♦ 而在相应的低级别执行状态下,代码的掌控范围会受到限制。只能在对应级别允许的范围内活动
♦ 举例:intel x86 CPU有四种不同的执行级别0-3,Linux只使用了其中的0级和3级分别来表示内核态和用户态


为什么有权限级别的划分

防止程序员非法访问系统或者是其它资源而使得系统崩溃

Linux中怎么区分用户态和内核态:

♦ cs寄存器的最低两位表明了当前代码的特权级
♦ CPU每条指令的读取都是通过cs:eip这两个寄存器:
其中cs是代码段选择寄存器,eip是偏移量寄存器。
♦ 上述判断由硬件完成
♦ 一般来说在Linux中,地址空间是一个显著的标志:
0xc0000000以上的地址空间只能在内核态下访问,0x00000000-0xbfffffff的地址空间在两种状态下都可以访问
注意:这里所说的地址空间是逻辑地址而不是物理地址

在内核态时,cs和eip可以是任意的地址


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

用户态进入内核态一般来说都是用中断来触发的,可能是硬件中断。也可能是用户态程序运行当中调用了系统调用进入了内核态(trap)。系统调用是一种特殊的中断。

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

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

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

中断处理结束前最后一件事是恢复现场 RESTORE_ALL
中断处理的完整过程

系统调用概述

以系统调用为例,看看中断服务具体是怎么执行的:

系统调用的意义

  • 操作系统为用户态进程与硬件设备进行交互提供了一组接口——系统调用
  • 把用户从底层的硬件编程中解放出来
  • 极大的提高了系统的安全性
  • 使用户程序具有可移植性
操作系统提供的API和系统调用的关系

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

  • API只是一个函数定义
  • 系统调用通过软中断(trap)向内核发出一个明确的请求
    Libc库定义的一些API引用了封装例程(wrapper routine,唯一目的就是发布系统调用)
  • 一般每个系统调用对应一个封装例程
  • 库再用这些封装例程定义出给用户的API
    不是每个API都对应一个特定的系统调用。
  • API可能直接提供用户态的服务,如一些数学函数
  • 一个单独的API可能调用几个系统调用
  • 不同的API可能调用了同一个系统调用
    返回值
  • 大部分封装例程返回一个整数,其值的含义依赖于相应的系统调用
  • -1在多数情况下表示内核不能满足进程的请求
  • Libc中定义的errno变量(error number)包含特定的出错码
    应用程序、封装例程、系统调用处理程序及系统调用服务例程之间的关系

左边是用户态User Mode,右边是内核态Kernel Mode,最左边api:xyz()封装了一个系统调用,这个系统调用会触发一个0x80的中断。0x80这个中断向量就对应着system_call这个内核代码的入口起点。这个内核代码里可能有SAVE_ALL,sys_xyz()中断服务程序,在中断服务程序执行完后,可能ret_from_sys_call,在return的过程中可能发生进程调度,这是一个进程调度的时机。如果没有发生系统调度,就会iret,再返回到用户态接着执行。
系统调用的三层皮:xyz(api)、system_call(中断向量)和sys_xyz


当用户态进程调用一个系统调用时,CPU切换到内核态并开始执行一个内核函数。
  • 在Linux中是通过执行int $0x80来执行系统调用的,这条汇编指令产生向量为128的编程异常
    系统调用号讲xyz和sys_xyz关联起来了

传参:

内核实现了很多不同的系统调用,进程必须指明需要哪个系统调用,这需要传递一个名为系统调用号的参数
-使用eax寄存器

系统调用的参数传递方法

系统调用也需要输入输出参数,例如:
  • 实际的值
  • 用户态进程地址空间的变量的地址
  • 甚至是包含指向用户态函数的指针的数据结构的地址

如果是函数调用的时候,它可以把函数压栈的方式来传递。而用户态到内核态的函数传递的方法:

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)
•超过6个怎么办? 如果超过六个,某一个寄存器作为一个指针指向一块内存,进入到内核态以后,它可以访问所有的内存空间,可以通过这块内存来传递数据。


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

简单的系统调用time,来获取当前系统的时间:

#include <stdio.h>
#include <time.h>

int main()
{
    time_t tt;// tt只是int型的数值 
    struct tm *t;// tm为了输出的时候变成可读的 
    tt = time(NULL);//time系统调用,返回值赋给tt//使用了time这个库函数,api 
    t = localtime(&tt);//把tt改成t这种格式的,即tm格式
    printf("time:%d:%d:%d:%d:%d:%d\n",t->tm_year+1900, t->tm_mon, t->tm_mday,t->tm_hour,t->tm_min,t->tm_sec); 
    return 0;   
} 
Paste_Image.png


使用库函数的方式比较简单,下边使用汇编的方式:

用汇编方式触发系统调用获取系统当前时间

#include <stdio.h>
#include <time.h>

int main()
{
    time_t tt;
    struct tm *t;
    asm volatile(
        "mov $0,%%ebx\n\t"//把ebx清零 
        "mov $0xd,%%eax\n\t"//把0xd(13)放到eax里,eax是用来传递系统调用号的 
        "int $0x80\n\t"
        "mov %%eax,%0\n\t"//通过eax返回值 
        :"=m"(tt) 
    ); 
    t = localtime(&tt);
    printf("time:%d:%d:%d:%d:%d:%d\n",t->tm_year+1900, t->tm_mon, t->tm_mday,t->tm_hour,t->tm_min,t->tm_sec); 
    return 0;   
} 


可以看到执行的效果是完全一样的。用户态进程向内核态传递了一个系统调用号。在那段汇编代码里,先是给ebx传参数,然后给eax传系统调用号,int指令,系统调用执行完后返回结果eax,这就完成了系统调用。

系统调用号的定义在 /usr/include/asm/unistd.h 文件中

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • 所有的程序员在写程序的时候都离不开通过库函数的方式和系统调用打交道 什么是用户态和内核态?(从CPU指令级别的角度...
    那只大象阅读 3,676评论 1 5
  • 一、温故而知新 1. 内存不够怎么办 内存简单分配策略的问题地址空间不隔离内存使用效率低程序运行的地址不确定 关于...
    SeanCST阅读 7,774评论 0 27
  • 1 临界区 1.1简介 在早期计算机系统中,只有一个任务进程在执行,并不存在资源的共享与竞争。随着技术和需求的飞速...
    Fly晴天里Fly阅读 9,006评论 2 13
  • 每个人都有自己专属的感官,对于自己而言,别人给予的外在条件,一种是为满足对方的自恋纠正,另一种是对于自身的关怀与激...
    杨平的阅读 166评论 0 0
  • 八月即将结束了,恍惚间,这个夏天也快要接近尾声吧!慢慢地褪去了炙热,初秋的清风徐徐吹拂,心情也开始趋于平静...
    杂草莹阅读 308评论 3 1