操作系统(二) -- 操作系统的接口与实现

2023-05-16

  • 前言
  • 操作系统的接口
    • 什么是操作系统的接口
    • POSIX标准
  • 系统调用的实现
    • 1,用户程序能不能直接调用系统内核
    • 2,如果不能直接调用,为什么?如何实现的
    • 3,用户程序如何才能调用系统内核
    • 系统调用的核心:
  • 具体实现:以printf为例
    • 总结一下系统调用的实现:
  • 参考资料

前言

前面说了操作系统启动时发生的事情,最后一个文件main.c中有这样一行代码:

if(!fork()){init();}

这行代码就是启动第一个进程,对于windows来说就是启动桌面,对于linux来说就是打开shell。这一篇文章说说操作系统的接口以及实现,即上层应用是如何穿过接口进入操作系统的。

操作系统的接口

什么是操作系统的接口

接口其实是一种抽象,比如插排,它将内部的电路全部封装起来,只提供两个插口,用电设备插上就能用;不用管插座内部是如何实现的。操作系统的接口也是如此,操作系统的接口其实就是一个个函数,知道它的功能然后直接调用就行,而不用管它内核里面是怎么实现的,因为这个函数是系统调用的,所以也称为系统调用。比如:write()、read()等等

POSIX标准

POSIX(Portable Operating System Interface of Unix),POSIX标准定义了操作系统应该为应用程序提供的接口标准,目的是为了增强程序的可移植性。

系统调用的实现

前面说的是操作系统的接口,说白了就是一个个函数,调用它们就可以使用相应的功能。那这些系统调用到底是如何实现的呢?下面就来解解密。解决三个问题:

  • 1,用户程序能不能直接调用系统内核
  • 2,如果不能直接调用,为什么?如何实现的
  • 3,用户程序如何才能调用系统内核

1,用户程序能不能直接调用系统内核

不能

2,如果不能直接调用,为什么?如何实现的

如果能的话,那么你从网上下载一段程序就可能进入系统内核获取你的root密码,那么还有什么安全感呢?

但是操作系统和用户程序都是在内存里面,在内存里面是可以交换数据的呀?那为什么就不能直接使用jmp、mov或者函数调用直接进入操作系统内核呢?怎么实现的呢?

实现方法:利用硬件设计将内核程序与用户程序进行隔离,内核程序的所在的那段内存程称为核心态,用户程序所在的那段内存叫用户态。用户态的程序不能直接访问核心态的数据。

实现手段:利用CS的低两位CPL和DS的低两位DPL来实现隔离。首先在head.s里面建立gdt表的时候就将内核段DPL置为0,而CPL是当前指令的特权级,如果是在用户态,那么CPL就为3(如果是核心态就是0);在访问某个地址的时候,要看有没有权限访问,0的特权级是高于3,如果CPL的特权级小于等于DPL的特权级,那么就不能访问;注意:如果CPL=DPL是可以访问的;比如CPL=0(说明是内核态),DPL=3(说明是用户态),CPL的特权级大于DPL的特权级,所以能访问。也就是说内核态能访问内存的任意区域。这个隔离对于跳转指令(jmp、mov)同样有效。

3,用户程序如何才能调用系统内核

用户态不能直接访问内核态,那么有什么方法可以访问呢?方法肯定是有的,不然系统调用就实现不了了啊;用户态访问内核态只能通过一种途径,那就是中断,int指令将使CS中的CPL从3变为0,这样就可以访问了(即进入内核),这是用户程序发起的调用内核代码的唯一方式。并且这个中断号只能是0x80.

系统调用的核心:

1,用户程序中包含一段包含int指令的代码
2,操作系统中有中断函数表,从中可以获取中断服务函数入口地址
3,操作系统执行中断服务函数

具体实现:以printf为例

首先c代码里面的printf是这样的,printf(“%d”,a);在printf()内部其实是调用了系统函数write,而write函数的函数头其实是这样的:

ssize_t write(int fd, const void *buf, size_t count);

fd:要进行写操作的文件描述词。
buf:需要输出的缓冲区
count:最大输出字节计数

可以看到,printf()函数的形参和write()的形参是不一样的,因此如果printf(“%d”,a)能调用write函数的话,肯定要对printf的形参进行处理,使其符合write函数的格式,或者说换一种方式调用。在printf()函数里面调用write()如下所示:

# include <unisted.h>
_syscall3(int, write, int, fd, const char* buf, off_t, count)

可以看到其实利用的是_syscall3这个宏,这个宏的定义如下:


#define _syscall3(type,name,atype,a,btype,b,ctype,c)\
type name(atype a, btype b, ctype c) \
{ long __res;\
__asm__ volatile(“int 0x80”:”=a”(__res):””(__NR_##name),
”b”((long)(a)),”c”((long)(b)),“d”((long)(c)))); if(__res>=0) return
(type)__res; errno=-__res; return -1;}

_syscall3这个宏调用之后就是展开成上面的一段汇编代码,比如write调用:

_syscall3(int, write, int, fd, const char* buf, off_t, count)

就是将宏展开的代码中的

type=int,name=write,atype=int,a=fd,btype=const char * ,b=buf,ctype=off_t,c=count;

用这些来替换;因此

type name(atype a, btype b, ctype c)

就变成了

int write(int fd,const char * buf, off_t count)

这样,展开的汇编代码一样跟着变。这里需要注意的是int0x80这个中断;前面已经说过在head.s里面会重新建立idt表,之后中断就是表示根据中断号查那个表,然后获取中断服务函数的入口地址,int0x80这个中断就是进入操作系统内核,这是上层应用进入操作系统的唯一手段,int 0x80相当于是操作系统的一个门户,接着看_syscall3宏定义下面的代码:

long __res;\
__asm__ volatile(“int 0x80”:”=a”(__res):””(__NR_##name),
”b”((long)(a)),”c”((long)(b)),“d”((long)(c)))); if(__res>=0) return
(type)__res; errno=-__res; return -1;

这是一段内嵌汇编,冒号左边为输入,右边为输入,,上面代码最右边一个冒号右边是:”“表示与前面的a一样,即eax这个寄存器,所以”“(_NR##name)的意思就是将__NR_write赋值给eax这个寄存器,__NR_write称为系统调用号,后面有大用。

在linux/inlcude/unistd.h中
# define __NR_write 4   

什么是系统调用号呢?所有的系统调用都是通过int 0x80这个中断来
调用的,那么如何区分是write调用还是read调用或者是其他调用呢?就是根据这个系统调用号来区分的,__NR_write表示write调用,会接着执行write对应的内核代码,__NR_read表示read调用,同理,其他的系统调用号也是如此。后面的

”b”((long)(a)),”c”((long)(b)),“d”((long)(c))

就是把形参的a、b、c依次赋值给ebx、ecx、edx三个寄存器;输入完成之后就通过int 0x80这个中断号进入操作系统,int 0x80这条指令执行完之后,eax中就会存放int 0x80的返回值,然后将这个返回值赋值给__res,__res就是int write()这个系统调用的返回值。write这个系统调用也就结束了。

总结一下_syscall3这个宏的用法:
调用这个宏可以理解为调用一个函数,宏的定义:

#define _syscall3(type,name,atype,a,btype,b,ctype,c)

type 表示函数返回值,name表示函数名,后面分别是三个形参的类型和行参名。
name不同,系统调用号不同,所以调用_syscall3之后执行的代码不同,在宏里面通过
int 0x80进入系统内核并将指条指令的结果存在eax寄存器中,然后返回到宏的调用处。

具体再扒一下:

前面说的int 0x80都是用“这条指令“来表示了,那么int 0x80到底
是什么呢?int 0x80是进入中断服务函数的一条指令。
int 指令首先要查idt表转去哪里执行。

void sched_init(void)
{ set_system_gate(0x80,&system_call); }

int 0x80对应的中断处理程序就是system_call,从这个init就知道这是一个初始化,0x80这个中断就是用后面这个system_call来处理,那么系统是怎么设置的呢?通过set_system_gate这个宏。

在linux/include/asm/system.h中
#define set_system_gate(n, addr) \
_set_gate(&idt[n],15,3,addr); //idt是中断向量表基址

set_system_gate这个宏又调用了_set_gate这个宏,

在linux/include/asm/system.h中
#define _set_gate(gate_addr, type, dpl, addr)\
__asm__(“movw %%dx,%%ax\n\t” “movw %0,%%dx\n\t”\
“movl %%eax,%1\n\t” “movl %%edx,%2:\
:”i”((short)(0x8000+(dpl<<13)+type<<8))),“o”(*(( \
char*)(gate_addr))),”o”(*(4+(char*)(gate_addr))),\
“d”((char*)(addr),”a”(0x00080000))

这里我也看不懂,但是我知道_set_gate这个宏的作用就是建立一个类似这样的下图表,处理函数入口点偏移=system_call,DPL就是3,段选择符就是0x0008,即CS是8。
这里写图片描述
用户态的程序如果要进入内核,必须使用0x80号中断,那么就必须先要进入idt表。用户态的CPL=3,且idt表的DPL故意设置成3,因此能够跳到idt表,跳到idt表中之后就能找到之后程序跳转的地方,也就是中断服务函数的起始地址,CS就是段选择符(8),ip就是”处理函数入口点偏移“。记不记得setup.s里面有一行

jmpi 0,8

这条指令表示根据gdt表跳转到内核代码的地址0处。CS=8,ip=system_call就是跳到内核的system_call这个函数;另外如果CS=8,那么CPL=0,因为CPL是CS最低两位。也就是说当前程序的特权级变了,变成内核态的了。完整流程:初始化的时候0x80号中断的DPL设成3,让用户态的代码能跳进来,跳进来之后根据CS=8将CPL设为0,到了内核态,到了内核态就什么都能干了,将来int 0x80返回的之后,CS最后两位肯定变成3,变成用户态。

中断处理函数system_call到底做了什么呢?


在linux/kernel/system_call.s中
nr_system_calls=72
.globl _system_call
_system_call: cmpl $nr_system_calls-1,%eax
ja bad_sys_call
push %ds push %es push %fs
pushl %edx pushl %ecx pushl %ebx //调用的参数
movl $0x10,%edx mov %dx,%ds mov %dx,%es //内核数据
movl $0x17,%edx mov %dx,%fs //fs可以找到用户数据
call _sys_call_table(,%eax,4) //a(,%eax,4)=a+4*eax
pushl %eax //返回值压栈,留着ret_from_sys_call时用
... //其他代码
ret_from_sys_call: popl %eax, 其他pop, iret

前面都是压栈和赋值,接着调用了_sys_call_table(,%eax,4)。
a(,%eax,4)=a+4*eax_sys_call_table(,%eax,4)=_sys_call_table+4*%eax;这是一种寻址方式。eax是系统调用号,那_sys_call_table是什么?

在include/linux/sys.h中
fn_ptr sys_call_table[]=
{sys_setup, sys_exit, sys_fork, sys_read, sys_write,
...};

在include/linux/sched.h中
typedef int (fn_ptr*)();

sys_call_table是一个fn_ptr类型的全局函数表,fn_ptr是一个函数指针,4个字节,这就是_sys_call_table+4*%eax;这里为什么要*4的原因,sys_call_table的每一项都是4个字节,然后就可以根据eax来知道要调用的真正中断服务函数的入口地址了,对于write系统函数来说,就是sys_write。

总结一下系统调用的实现:

printf ->_syscall3 ->write -> int 0x80 -> system_call -> sys_call_table -> sys_write
printf通用_syscall3这个宏调用write函数,在write函数里面用system_call来处理int 0x80,在system_call中会调用system_call_table这个表,根据eax中存储的系统调用号就可以找到真正的sys_write了。

参考资料

哈工大李志军操作系统

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

操作系统(二) -- 操作系统的接口与实现 的相关文章

  • 终端连接控制(stty的编写)

    终端连接控制 stty的编写 一 背景 文件与目录在之前已经学习过了 文件中包含着数据 这些数据可以被读出 写入 也可以用以操作 但文件不仅仅是计算机唯一的数据来源 计算机的数据还可以来自于许多的外部设备 比如扫描仪 照相机 鼠标等输入设备
  • Tomcat7安装及配置教程

    Apache Tomcat7 0安装及配置教程 Apache Tomcat7 0官方网站链接 http tomcat apache org apache tomcat 7 0 73 windows x64 先解压下载的压缩包 然后在bin目
  • java调优总结

    JVM调优总结 序 几年前写过一篇关于JVM调优的文章 前段时间拿出来看了看 又添加了一些东西 突然发现 基础真的很重要 学习的过程是一个由表及里 再由里及表的过程 呵呵 所谓的 温故而知新 而真正能走完这个轮回的人 也就能称为大牛或专家了
  • Linux网络安全-Zabbix入门(一)

    一 基本概念 1 监控目的 运行情况 提前发现问题 2 监控资源类别 公开 tcp udp 端口 私有 cpu 磁盘 监控一切需要监控的东西 只要能够想到 能够用命令实现的都能用来监控 如果想远程管理服务器就有远程管理卡 比如Dell id
  • JSON.stringify()

    JSON stringify 将一个JavaScript值 对象或者数组 转换为一个 JSON字符串 如果指定了replacer是一个函数 则可以选择性的替换值 或者如果指定了replacer是一个数组 可选择性的仅包含数组指定的属性 语法
  • Linux 磁盘与文件系统管理(鸟哥私房菜)

    本文来自 http vbird dic ksu edu tw linux basic 0230filesystem php 第八章 Linux 磁盘与文件系统管理 系统管理员很重要的任务之一就是管理好自己的磁盘文件系统 每个分割槽不可太大也
  • office2013 excel 打开时提示excel词典xllex.dll文件丢失或损坏

    今天打开Excel时 发现报错 xllex dll文件丢失或损坏 我用的是office2013 网上找了好多都是2007的dll文件 导入不了 于是乎重装office 问题解决 但还是把xllex dll烤出来做个备份吧 参考下面步骤即可
  • Linux使用nvida-smi查看GPU类型

    nvida smi提供一个查看GPU信息的方法 然而这种方式不能查看GPU型号 型号被省略成了GeForce RTX 208 如果我们需要查看GPU的型号 只需要运行nvidia smi L即可 mrfive ubuntu nvidia s
  • Windows 添加永久静态路由

    route add p 10 10 0 0 mask 255 255 0 0 10 10 6 1 p 参数 p 即 persistent 的意思 p 表示将路由表项永久加入系统注册表
  • Java堆的自动垂直缩放

    多年以来 java一直是贪婪的应用程序的同义词 这种类型的应用程序在晚上打开冰箱并吞噬所有可用资源 直到崩溃 该行为的主要原因是缺乏一种有效的方式来将操作系统在Java堆中分配且不再使用的内存交还给操作系统 However with the
  • Linux系统编程:多线程交替打印ABC

    引言 分享关于线程的一道测试题 因为网上基本都是Java的解决方法 决定自己写一篇来记录一下线程的学习 问题描述 编写一个至少具有三个线程的程序 称之为线程 A B 和 C 其中线程 A 输出字符 A 线程 B 输出字符 B 线程 C 输出
  • InfoQ视频直播分享报名:前贝尔实验室、Oracle架构师为你在线揭秘分布式平台内核...

    报名方式 关注InfoQ微信公众号 ID infoqchina 回复 InfoQ 即可观看在线直播技术分享 分享地点 u0026amp 时间 InfoQ直播微课堂将在熊猫 TV 网站播出 看腻了卖肉的女主播 来看看QCon 的明星讲师如何
  • Linux,Network manager 导致节点异常重启

    推断是Network manager 导致的 原因待查今天在VmWare的虚拟机上装了个测试RAC 又遇到了一个摸不到头绪的问题CRS装好后 一旦登陆图形界面 节点就重启 事情就有这么巧不登陆图形界面 观察了1个小时没问题 一旦登陆后 立刻
  • 03LinuxC线程学习之线程共享和非共享

    1 线程共享和非共享 1 1 线程共享资源 1 文件描述符表 由于线程间共享进程间的内容 而文件描述符表在主线程的PCB当中 各个线程可以直接去请求访问 所以线程间通信就不需要像进程那样通过管道这些方式通信 2 每种信号的处理方式 即当某个
  • Windows运行常用命令(win+R)

    1 calc 启动计算器 2 notepad 打开记事本 3 write 写字板 4 mspaint 画图板 5 snippingtool 截图工具 支持无规则截图 6 mplayer2 简易widnows media player 7 S
  • Ubuntu9.04太多乱码(中文不能正常显示)

    最近在使用Ubuntu9 04的过程中 发现有好多地方都出现乱码 其实是中文不能正常显示 现在把我所遇到的所有乱码问题集中一下 方便以后查阅参考 一 Flash乱码 在终端输入 sudo gedit etc fonts conf d 49
  • OS——文件管理系统磁盘的结构之搞清盘面和柱面

    如上图 每个柱面有三个盘面 即就是3个磁道 柱面可以抽象的理解成是一个套一个的立体的同心圆柱体 例 2019年408真题 磁盘有300个柱面 每个柱面有10个磁道 每个磁道有200个扇区 扇区大小为512B 则磁盘容量 分析 每个柱面有10
  • 如何快速构建CMBD系统-glpi

    脚本后续更新及迭代将由kkitDeploy项目代替 https github com luckman666 kkitdeploy server 请大家持续关注kkitDeploy 一 CMBD系统构建步骤 起初 开发这套CMBD系统是为了帮
  • C#实现FTP文件夹下载功能【转载】

    网上有很多FTP单个文件下载的方法 前段时间需要用到一个FTP文件夹下载的功能 于是找了下网上的相关资料结合MSDN实现了一段FTP文件夹下载的代码 实现的思路主要是通过遍历获得文件夹下的所有文件 当然 文件夹下可能仍然存在文件夹 这样就需
  • 【操作系统xv6】学习记录4-一级页表与二级页表

    占位

随机推荐

  • npm超时

    有时使用npm下载很慢出现超时 xff0c 可以尝试使用cnpm xff0c 国内镜像 安装 npm install cnpm g registry 61 https registry npm taobao org 测试是否成功 cnpm
  • 机试第一节

    问题 xff1a 1 6中的n的阶乘太大时发生溢出怎么处理 分割线 1 双精度浮点类型的定义 xff1a double 输出 lf 单精度浮点类型的定义 xff1a float 输出 xff1a f 2鸡兔同笼问题 xff0c 判断所给样例
  • Python小技巧之——巧用with语句实现异常处理

    Python的异常处理语句try except大家都很熟悉了 xff0c 例如 xff1a try 1 0 except Exception as ex print ex integer division or modulo by zero
  • 将lwip1.4.1工程移植至lwip2.1.2记录

    将lwip1 4 1工程移植至lwip2 1 2记录 1 ip addr结构体2 etharp h3 cc h与arch h4 tcp impl h 关于二者文件 功能等差异网上已有很多文章介绍 xff0c 类似这个文档有简单说了下这两个版
  • 在Windows和Ubuntu上安装VNC连接远程服务器

    如果你是公用的服务器的管理员需要添加一些用户 xff0c 并配置VNC服务以便远程访问 xff0c 简要介绍一下配置方法 具体的命令可以参照命令手册去查看 man span class hljs command span class hlj
  • 变频器的工作原理及其电路分析

    变频器简单的说就是结合了变频技术和微电子技术研制出来的可以改变输入电源的频率得到另外一种频率电源输出的设备 其输入的电源就是我们工业上面使用的电源 xff0c 一般都是电压和频率都固定不变的交流电 240v或者380v交流电 通过内置的一些
  • 欠拟合、过拟合及其解决方法

    在我们机器学习或者训练深度神经网络的时候经常会出现欠拟合和过拟合这两个问题 xff0c 但是 xff0c 一开始我们的模型往往是欠拟合的 xff0c 也正是因为如此才有了优化的空间 xff0c 我们需要不断的调整算法来使得模型的表达能拿更强
  • ubuntu18.04安装ROS Melodic的详细过程以及填坑经历

    一 版本说明 ROS官方将在2021年不再维护Kinetic xff0c 后续使用Ubuntu18 04 43 Melodic组合 xff0c Melodic支持时间到2023年5月 二 安装前Ubuntu18 04设置 打开Ubuntu1
  • win10和ubuntu20双系统设置默认启动系统为win10

    在win10下安装了Ubuntu20 04系统 xff0c 默认情况下 xff0c 启动的是Ubuntu系统 要将默认启动系统设置成win10 xff0c 方法如下 xff1a 1 进入ubuntu系统 xff0c 按住Ctrl 43 Al
  • Keil添加芯片支持包(Pack)

    1 前言 一直用STM32的芯片 xff0c 现在想看看工程是否可以在其他厂家的芯片上跑 xff0c 可是keil的Device中只有ST厂家的 因此 xff0c 尝试在keil中添加其他厂家的芯片支持包 2 keil软件内安装 点击工具栏
  • Qt 设置窗体大小和背景颜色

    1 一种方法是设置它的最大窗口值和最小窗口值 xff0c 并且使最大值和最小值相等 简单的示例 xff1a setMinimumSize 370 150 setMaximumSize 370 150 此时窗口大小便被固定为 xff08 37
  • Shell 脚本详解

    简介 shell xff1a 蛋 壳 shell脚本是在操作系统外 xff0c 可以直接调用系统内核命令的一个脚本语言 shell脚本可以分为两大类组成 xff1a 1 命令行 xff08 系统命令行 xff09 2 脚本语法 xff08
  • Windows——电脑不能连接手机热点(WLAN显示已经禁用)的解决办法

    笔记本电脑提示 xff1a 已关闭无线功能 基于这篇博客之上 xff0c 在第二步中 xff0c 关闭WLAN AutoConfig 服务 xff0c 之后重新打开WLAN AutoConfig 服务 xff0c 即可
  • Ubuntu——系统语言由英文切换到中文的方法

    一 方法一 ubuntu设置系统语言为中文 二 方法二 若方法一中不能拖动中文输入法到第一行 xff0c 则可以直接采取卸载英文输入法 xff0c 这样就中文输入法到第一行了 xff0c 切换成中文了 英文输入法可以根据需要考虑是否安装 一
  • RealSense D435——基本介绍

    一 结构介绍 采用的是结构光Tof成像方案 正面的四个摄像头从左至右 xff0c 依次是左红外相机 红外点阵投影仪 右红外相机 RGB相机 xff08 前三个负责形成深度图 xff0c 最后一个就形成RGB图 xff09 二 小贴士 RGB
  • RealSense D435——相机内参获取

    RealSense D435 相机内参获取 一 参考博客二 小贴士2 1 遇到的问题及解决方案问题一描述问题一解决方法问题二描述问题二解决方法 一 参考博客 RealSense D435内参获取环境配置 xff1a Realsense D4
  • Vscode——报错解决:Unable to start debugging.Unexpected GDB output from command. 或 程序点击运行一直无结果

    一 报错截图 1 Unable to start debugging Unexpected GDB output from command 2 程序点击运行一直无结果 二 原因 路径中含有中文 三 解决办法 将文件放入不包含中文的路径下
  • Github——合并分支

    一 当两个分支不一样时 xff0c 会出现下面的标志 xff08 前提是设定了分支保护 xff09 xff0c 点击Compare amp pull request 二 选择双方分支 三 处理请求 四 确认请求
  • 基于四旋翼飞行器的陀螺仪、加速度计、磁力计传感器说明

    一 什么是磁力计 加速度计和陀螺仪以及他们之间的区别 1 什么是陀螺仪 加速度计和磁力计 xff1f xff08 1 xff09 陀螺仪 xff08 Gyroscope GYRO Sensor xff09 也叫地感器 xff0c 三轴陀螺仪
  • 操作系统(二) -- 操作系统的接口与实现

    前言操作系统的接口 什么是操作系统的接口POSIX标准 系统调用的实现 1 xff0c 用户程序能不能直接调用系统内核2 xff0c 如果不能直接调用 xff0c 为什么 xff1f 如何实现的3 xff0c 用户程序如何才能调用系统内核系