linux系统调用

1、为什么要分成内核空间和用户空间

为了安全。在内核空间,可以对cpu进行任意访问,也就是拥有最高的权限。如果用户程序也拥有了这个权限,如果应用程序不安全(携带病毒),就会影响内核的安全。因此,分成内核空间和用户空间(用户空间的权限受限)。
注:这里涉及到保护模式的概念。总得来说,就是:在保护模式下,程序不能再随意的访问物理内存了,有些内存地址CPU做了明确的保护限制。保护机制能有效的实现不同任务之间和同一任务内的保护。
详见博客:
https://blog.csdn.net/gatieme/article/details/50646461

2、应用程序如何访问内核资源

应用程序要想访问内核资源,有三种方法:系统调用、异常(软/硬中断),和陷入。
这里重点分析系统调用。
eg:
应用程序工作在用户空间,驱动运行在内核空间。当用户想要实现对内核的操作,比如,使用open()函数来打开/dev/led这个驱动,因为用户空间不能直接对内核进行访问,因此,必须使用一个叫“系统调用”的方法来实现从用户空间“陷入(其实就是软中断)”到内核空间,这样才能实现对底层驱动的操作。
系统调用其实也是一堆的函数。每一个系统调用都有一个系统调用号(eg:如下图所示:read()的系统调用号是3;write()的系统调用号是4)。从用户空间“陷入”到内核以后,会提供一个系统调用号(eg: 我的内核是4.1.15,系统调用号在arch\arm64\include\asm\ unistd_32.h文件存放)。内核知道这是一个系统调用后,根据系统调用号调用内核里面对用的函数。
注:
系统调用号相当关键,一旦分配就不能再有任何变更,否则编译好的应用程序就会崩溃。
在这里插入图片描述

3、系统调用概述

linux内核中设置了一组用于实现系统功能的子程序,称为系统调用。系统调用和普通库函数调用非常相似,只是系统调用由操作系统核心提供,运行于内核态,而普通的函数调用由函数库或用户自己提供,运行于用户态。
应用程序运行在用户空间,而 Linux 驱动属于内核的一部分,因此驱动运行于内核空间。
当我们在用户空间想要实现对内核的操作,比如使用 open 函数打开/dev/led 这个驱动,因为用户空间不能直接对内核进行操作,因此必须使用一个叫做“系统调用”的方法来实现从用户空间“陷入”到内核空间,这样才能实现对底层驱动的操作。在linux中系统调用是用户空间访问内核的唯一手段,除异常和陷入外,他们是内核唯一的合法入口。
一般情况下应用程序通过应用编程接口API,比如: C库(C库包括C语言库和系统调用。通过该接口,应用程序可以访问硬件设备和其他操作系统资源。这组接口在应用程序和内核之间扮演了使者的角色,应用程序发送各种请求,而内核负责满足这些请求(或者让应用程序暂时搁置)。实际上提供这组接口主要是为了保证系统稳定可靠,避免应用程序肆意妄行,惹出大麻烦),而不是直接通过系统调用来编程。
在Unix世界,最流行的API是基于POSIX标准的。
注:
1)、一个API可能会需要一个或多个系统调用来完成特定功能。通俗点说就是并不是所有的API函数都一一对应一个系统调用,有时,一个API函数会需要几个系统调用来共同完成函数的功能,甚至还有一些API函数不需要调用相应的系统调用(因此它所完成的不是内核提供的服务)
2)、API是一个提供给应用程序的接口,一组函数,是与程序员进行直接交互的。
3)、系统调用则不与程序员进行交互的,它根据API函数,通过一个软中断机制向内核提交请求,以获取内核服务的接口。

4、系统调用的实现

系统调用要从用户空间进入内核空间,通过一些处理器支持的特殊机制----软中断。arm上,就是汇编指令SWI。

4.1什么是软中断

1). 软中断的处理非常像硬中断。然而,它们仅仅是由当前正在运行的进程所产生的。
2). 通常,软中断是一些对I/O的请求。这些请求会调用内核中可以调度I/O发生的程序。对于某些设备,I/O请求需要被立即处理,而磁盘I/O请求通常可以排队并且可以稍后处理。根据I/O模型的不同,进程或许会被挂起直到I/O完成,此时内核调度器就会选择另一个进程去运行。I/O可以在进程之间产生并且调度过程通常和磁盘I/O的方式是相同。
3). 软中断仅与内核相联系。而内核主要负责对需要运行的任何其他的进程进行调度。一些内核允许设备驱动的一些部分存在于用户空间,并且当需要的时候内核也会调度这个进程去运行。
4). 软中断并不会直接中断CPU。也只有当前正在运行的代码(或进程)才会产生软中断。这种中断是一种需要内核为正在运行的进程去做一些事情(通常为I/O)的请求。有一个特殊的软中断是Yield调用,它的作用是请求内核调度器去查看是否有一些其他的进程可以运行。
转自: http://www.linuxidc.com/Linux/2014-03/98013.htm

4.2 硬中断和软中断的区别

1)、软中断是执行中断指令产生的,而硬中断是由外设引发的。
2)、硬中断的中断号是由中断控制器提供的,软中断的中断号由指令直接指出,无需使用中断控制器。
3)、硬中断是可屏蔽的,软中断不可屏蔽。
4)、硬中断处理程序要确保它能快速地完成任务,这样程序执行时才不会等待较长时间,称为上半部。软中断处理硬中断未完成的工作,是一种推后执行的机制,属于下半部。
扩展:
为了满足实时系统的要求,中断处理应该是越快越好。linux为了实现这个特点,当中断发生的时候,硬中断处理那些短时间就可以完成的工作,而将那些处理事件比较长的工作,放到中断之后来完成,也就是软中断来完成。

4.3

软中断是通过一条具体指令SWI,引发中断操作,也就是说用户程序里可以通过写入SWI指令来切换到特权模式,当CPU执行到SWI指令时会从用户模式切换到管理模式下,执行软件中断处理(eg:当执行应用程序open()函数的时候,就已经进入了内核态 )。由于SWI指令由操作系统提供的API封装起来,并且软件中断处理程序也是操作系统编写者提前写好的,因此用户程序调用API时就是将操作权限交给了操作系统,所以用户程序还是不能随意访问硬件。

4.4系统调用的实现原理

4.4.1从用户空间通过C库访问系统调用
在Linux中每个系统调用都有相应的系统调用号作为唯一的标识,内核维护一张系统调用表,sys_call_table,表中的元素是系统调用函数的起始地址,而系统调用号就是系统调用在调用表的偏移量(对于系统调用表中的表项是以32位(4字节)类型存放的,所以内核需要将给定的系统调用号乘以4,然后用所得的结果在该表中查询其位置)。通过这种方式,找到对应的系统调用响应函数的入口地址。
因为所有的系统调用陷入内核的方式都一样,所以仅仅是陷入内核空间是不够的。因此必须把系统调用号一并传给内核。对于ARM来说,以r7作为传递系统调用号的寄存器。
系统调用的陷入:
在这里插入图片描述

4.4.2从用户空间通过C库访问系统调用实例

操作系统使用系统调用表将系统调用编号翻译为特定的系统调用。系统调用表包含有实现每个系统调用的函数的地址。例如,read() 系统调用函数名为sys_read。read()系统调用编号是 3,所以sys_read() 位于系统调用表的第四个条目中(因为系统调用起始编号为0)。从地址 sys_call_table + (3 * word_size) 读取数据,得到sys_read()的地址。
找到正确的系统调用地址后,它将控制权转交给那个系统调用。我们来看定义sys_read()的位置,即fs/read_write.c文件。这个函数会找到关联到 fd 编号(传递给 read() 函数的)的文件结构体。那个结构体包含指向用来读取特定类型文件数据的函数的指针。进行一些检查后,它调用与文件相关的 read() 函数,来真正从文件中读取数据并返回。与文件相关的函数是在其他地方定义的 —— 比如套接字代码、文件系统代码,或者设备驱动程序代码。这是特定内核子系统最终与内核其他部分协作的一个方面。
读取函数结束后,从sys_read()返回,它将控制权切换给 ret_from_sys。它会去检查那些在切换回用户空间之前需要完成的任务。如果没有需要做的事情,那么就恢复用户进程的状态,并将控制权交还给用户程序。
4.4.3从用户空间直接访问系统调用实例
通常,系统调用靠C库支持。用户程序通过包含标准头文件并和C库链接,就可以使用系统调用(或者调用库函数,再由库函数实际调用)。但如果你仅仅写出系统调用,glibc库恐怕并不提供支持。值得庆幸的是,Linux本身提供了一组宏,用于直接对系统调用进行访问。它会设置好寄存器并调用陷人指令。这些宏是_syscalln(),其中n的范围从0到6。代表需要传递给系统调用的参数个数,这是由于该宏必须了解到底有多少参数按照什么次序压入寄存器。举个例子,open()系统调用的定义是:
long open(const char filename, int flags, int mode)
而不靠库支持,直接调用此系统调用的宏的形式为:
#define NR_ open 5
syscall3(long, open, const char
,filename, int, flags, int, mode)
这样,应用程序就可以直接使用open()。

5、源码分析

在Linux下系统调用是用软中断实现的,下面以一个简单的open例子简要分析一下应用层的open是如何调用到内核中的sys_open的。在glibc库中,通过封装例程(Wrapper Routine)将API和系统调用关联起来。API是头文件中所定义的函数接口,而位于glibc中的封装例程则是对该API对应功能的具体实现。事实上,我们知道接口open()所要完成的功能是通过系统调用open()完成的,因此封装例程要做的工作就是先将接口open()中的参数复制到相应的寄存器中,然后引发一个异常,从而系统进入内核区执行sys_open(),最后当系统调用执行完毕后,封装例程还要将错误码返回到应用程序中。
1)、先来看一下下面这段程序:

int main(){
        int fd;
        fd=open(".",O_RDWR);
}

2)、然后对该程序进行静态编译,并且反汇编:

000082f0 <main>:
    82f0:	e92d4800 	push	{fp, lr}
    82f4:	e28db004 	add	fp, sp, #4	; 0x4
    82f8:	e24dd010 	sub	sp, sp, #16	; 0x10
    82fc:	ebffffd5 	bl	8258 <test>
    8300:	e1a03000 	mov	r3, r0
    8304:	e50b300c 	str	r3, [fp, #-12]
    8308:	e59f002c 	ldr	r0, [pc, #44]	; 833c <main+0x4c>
    830c:	e3a01002 	mov	r1, #2	; 0x2
    8310:	eb002e82 	bl	13d20 <__libc_open>
    8314:	e1a03000 	mov	r3, r0
    8318:	e50b3008 	str	r3, [fp, #-8]
    831c:	e59f301c 	ldr	r3, [pc, #28]	; 8340 <main+0x50>

可以看到在main 函数中,调用了__libc_open函数,再看一下__libc_open:

00013d20 <__libc_open>:
   13d20:	e51fc028 	ldr	ip, [pc, #-40]	; 13d00 <___fxstat64+0x50>
   13d24:	e79fc00c 	ldr	ip, [pc, ip]
   13d28:	e33c0000 	teq	ip, #0	; 0x0
   13d2c:	1a000006 	bne	13d4c <__libc_open+0x2c>
   13d30:	e1a0c007 	mov	ip, r7
   13d34:	e3a07005 	mov	r7, #5	; 0x5    //open的系统调用号5
   13d38:	ef000000 	svc	0x00000000   //产生软中断
   13d3c:	e1a0700c 	mov	r7, ip
   13d40:	e3700a01 	cmn	r0, #4096	; 0x1000
   13d44:	312fff1e 	bxcc	lr
   13d48:	ea0008b0 	b	16010 <__syscall_error>

__libc_open的实现应该在glib封装函数中,以r7作为传递系统调用号的寄存器,并且使用svc 0x00000000命令陷入内核,这种处理方式说明使用的是比较新的EABI方式的系统调用。
注:好多资料上说陷入软中断是使用SWI指令,ARM官网解释:
作为 ARM 汇编语言开发成果的一部分,SWI 指令已重命名为 SVC。 在此版本的 RVCT 中,SWI 指令反汇编为 SVC,并提供注释以指明这是以前的 SWI。
此 ARM 指令可用于所有版本的 ARM 体系结构。

参考博客:
【1】
https://blog.csdn.net/gatieme/article/details/50779184?ops_request_misc=%25257B%252522request%25255Fid%252522%25253A%252522160921617116780299028620%252522%25252C%252522scm%252522%25253A%25252220140713.130102334.pc%25255Fall.%252522%25257D&request_id=160921617116780299028620&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_v2~rank_v29-26-50779184.nonecase&utm_term=linux%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F%E8%B0%83%E7%94%A8%E7%B3%BB%E7%BB%9F%E8%B0%83%E7%94%A8%E7%9A%84%E8%BF%87%E7%A8%8B
【2】
https://blog.csdn.net/oqqYuJi12345678/article/details/100746436?ops_request_misc=%25257B%252522request%25255Fid%252522%25253A%252522160923189116780271118297%252522%25252C%252522scm%252522%25253A%25252220140713.130102334.pc%25255Fall.%252522%25257D&request_id=160923189116780271118297&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_v2~rank_v29-6-100746436.nonecase&utm_term=swi%E7%B3%BB%E7%BB%9F%E8%B0%83%E7%94%A8%E5%8F%B7%E6%98%AF%E8%87%AA%E5%B7%B1%E5%AE%9A%E4%B9%89%E7%9A%84%E5%90%97

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

One Piece&

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值