Linux内核笔记(二) 系统调用

Linux内核笔记(二) 系统调用

 

 

一.   系统调用的概念

二.   系统调用的意义

三.   系统调用的实现

四.   C库与系统调用的关系

五.   总结

 

 

 

 

 

一.   系统调用的概念

系统调用:一段运行于内核空间的执行系统功能的子程序

从表面上来看,系统调用与普通库函数没有什么区别,当然,这是从应用程序角度得到的结论,实际上,系统调用与库函数还是有很大区别的,而且系统调用是运行于内核空间,而普通函数则是运行于用户空间的。不仅如此,系统调用的调用方式也不仅仅是普通函数那样简单的调用,而是通过软中断来实现的,这一点在第三节中会详细描述。

 

那么上面提到的内核空间和用户空间是怎么回事呢,这里解释一下。

首先,现代CPU通常实现了不同的工作模式,以ARM为例,实现了7种工作模式:

用户模式(usr)、快速中断(fiq)、外部中断(irq)、管理模式(svc)、数据访问中止(abt)、系统模式(sys)、未定义指令异常(und)

Linux系统利用了CPU的这一特性,使用了其中的两级来分别运行Linux内核与应用程序,这样使操作系统本身得到充分的保护。

其次,在内存中,通常32Linux内核地址空间划分0~3G为用户空间,3~4G为内核空间。注意这里是32位内核地址空间划分,64位内核地址空间划分是不同的。

如下图所示:




当然,这里的地址是指虚拟地址,或者说逻辑地址,只有通过地址映射才能得到真实的物理地址。

 

 

 

 

 

 

 

 

 

 

 

二.   系统调用的意义

系统调用的意义概括来说有两大点:

(1).保证系统的安全稳定。

(2).提供统一的抽象处理,方便应用层应用程序的编写。

 

在系统的安全稳定方面,系统调用使得重要的系统功能函数运行于受到保护的内核空间。

访问这个空间的唯一入口就是系统调用。并且应用程序在调用系统调用时,系统会严格的检查这个调用的参数,保证用户应用程序安全的访问系统内核或者其他应用程序,不会做出危害系统安全稳定的操作或者窃取其他进程的数据。

        

         在提供功能服务方面,系统调用以函数的形式面向编程人员,使得编程人员不需要考虑复杂的内核结构就能够轻松的使用系统的功能。不仅如此,Linux系统一切皆文件的思想在系统调用上也有体现,我们对一切设备的操作全部会被抽象成文件操作。

        

        

 

 

 

 

 

 

 

三.   系统调用的实现

在概念中提到,系统调用的实现基本原理是软中断。

也就是说,调用的时候会产生中断,随后内核代表用户执行系统函数。那么内核是怎么知道用户调用的是哪个系统调用的呢?

与普通函数的函数地址不一样,系统调用是使用中断(软中断)产生的,所以Linux使用X86构架上的EAX寄存器来储存一个数字(其他CPU构架与此类似),这个数字称之为系统调用号,每一个系统调用都与一个系统调用号相对应,并且内核编译完成后就不能更改。这样子一来,在中断触发的时候使用一个寄存器保存一个数字,来实现通知内核应该执行的操作,避免了系统调用的函数名称。

那么系统调用的参数是如何传递的呢。在X86构架上,Linux是通过ebx, ecx,edx, esi和edi五个寄存器来存放函数的前五个参数的。遇到五个以上参数的时候则需要一个单独的寄存器来存放参数栈。

系统调用返回值方面,Linux通过EAX寄存器存放系统调用的返回值。

 

下面放一张图来说明一下系统调用的方式:

 

 

 

 

 

那么Linux内核是如何判断调用是否合法的呢。主要是通过entry.s中定义的系统调用的列表sys_call_table:

 

ENTRY(sys_call_table)

 

.longSYMBOL_NAME(sys_ni_syscall) /* 0-old "setup()" system call*/

 

.longSYMBOL_NAME(sys_exit)

 

.longSYMBOL_NAME(sys_fork)

 

.longSYMBOL_NAME(sys_read)

 

.longSYMBOL_NAME(sys_write)

 

.longSYMBOL_NAME(sys_open) /* 5 */

….

….

….

….

….

….

 

.longSYMBOL_NAME(sys_ni_syscall) /* streams1 */

 

.longSYMBOL_NAME(sys_ni_syscall) /* streams2 */

 

.longSYMBOL_NAME(sys_vfork)

 

 

system_call()函数通过将给定的系统调用号与NR_syscalls做比较来检查其有效性。如果它大于或者等于NRsyscalls,该函数就返回一ENOSYS。否则,就执行相应的系统调用。

      call *sys_ call-table(,%eax, 4)

由于系统调用表中的表项是以32位(4字节)类型存放的,所以内核需要将给定的系统调用号乘以4,然后用所得的结果在该表中查询其位置

 

内核不仅要对中断号进行判断,还要对内存访问进行判断,大致有以下几点:

(1).是否是用户空间的访问,拒绝用户对内核空间的访问

(2).是否访问的是其他进程的空间,拒绝访问其他进程的空间

(3).访问的内存空间权限是否正确

 

 

最后,我们以get_pid的系统调用做个结尾

这个系统调用大概是这个样子的:

<span style="font-size:14px;">asmlinkage long sys_ getpid(void)
{
    returncurrent-> tgid;
}</span>


很简单很简洁,需要注意的是asmlinkage这个标记,所有的系统调用都需要有这个标记,来通知内核。并且这个get_pid的名称在这里改名成sys_getpid,这是系统调用的命名规则,都需要去遵守。

 

 

那么去验证下这个系统函数

printf("syscall(SYS_getpid)= %ld \n" ,syscall(SYS_getpid));


在同一个进程中,这个的结果和下面这句应该是一样的

printf("getpid()= %ld\n", getpid());


 

 

 

四.   C库与系统调用的关系

 

操作系统API通常都以C库的方式提供,Linux也是如此。同时,内核提供的每个系统调用在C库中都具有相应的封装函数。系统调用与其C库封装函数的名称常常相同,比如,read系统调用在C库中的封装函数即为read函数。

但是,系统调用和C库函数之间并不是一一对应的关系。可能几个不同的函数会调用到同一个系统调用,比如说exec族函数。也可能这个库函数什么系统调用都没用到,比如说strcpy。

 

简单来说,C库的一部分是对系统调用的一层封装,为应用程序提供一个接口,另一部分没有使用到系统调用。也就是说系统调用面向需要系统服务的程序,C库面向所有需要使用库函数的程序。

接下来还是放一张图:

 

 

 

五.   总结

1.系统调用是一段运行于内核空间的执行系统功能的子程序。

2.内核空间与用户空间的划分。

3.系统调用可以提高系统运行的稳定与安全,并且位应用程序提供统一的服务接口

4.系统调用是通过软中断实现的

5.系统调用使用一个寄存器存放中断号来确定调用哪个系统调用,调用号存放在一张表内与系统调用一一对应。

6.C库与系统调用不是一个概念,C库对系统调用进行封装,也会调用其他库函数。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值