ie传递给系统调用的数据区域太小_【Linux系列】系统调用

在现代OS中,内核提供了用户进程与内核进行交互的一组接口。这些接口让应用程序受限地访问硬件设备,提供了创建新进程并与已有进程进行通信的机制,也提供了申请OS其他资源的能力。

系统调用在用户空间进程和硬件设备之间添加了一个中间层。该层主要作用有三个:

  • 为用户空间提供了一种硬件的抽象接口

  • 保证了系统的稳定和安全

  • 每个进程都运行在虚拟系统中,而在用户空间和系统的其余部分提供这样一层公共接

⚠️Linux—OS系统调用用户空间访问内核的唯一手段;除异常和陷入外,是内核唯一的合法入口。实际上其他的像设备文件和/proc之类的方式,最终还是通过系统调用进行访问的。

1.API: 一般情况下,应用程序通过在用户空间实现的应用编程接口API而不是直接通过系统调用来编程。因为application使用的API实际上并不需要和内核提供的系统调用对应。一个API定义了一组应用程序使用的编程接口

20584ee06371a26bd38fd6d96c8e3fff.png

⚠️在Unix中最流行的应用编程接口是基于POSIX标准的。

⚠️️POSIX是由IEEE的一组标准组成,其目标是提供一套大体上基于Unix的可移植OS标准。在大多数Unix系统上,根据POSIX定义的API函数和系统调用之间有着直接关系;非Unix系统如Windows也提供了与POSIX兼容的库。

Linux的系统调用作为C库的一部分提供。C库实现了Unix系统的主要API,同时提供了POSIX的绝大部分API。Unix的系统调用抽象出了用于完成某种确定的目的函数,不需要内核关心函数的使用。

系统调用syscall通过C中定义的函数调用来进行,会通过一个long类型的返回值来表示成功or错误。在出现错误时C库会把错误码写入errno全局变量。通过调用perror()库函数可以把该变量翻译成用户可以理解的错误字符串。

~如何定义系统调用?

1.注意函数声明中的asmlinkage限定词,这是一个编译指令,通知编译器仅从栈中提取该函数的参数。所有的syscall都需要这个asmlinkage限定词

2.函数返回long类型。为了保证32 bit和64 bit系统兼容,syscall在用户空间(返回int)和内核空间(返回long)由不同的返回值类型。

3.注意系统调用get_pid()在内核中被定义成sys_getpid()——命名规则

【系统调用号】

在Linux中,每个系统调用syscall被赋予一个唯一系统调用号

当用户空间的进程执行一个系统调用时,这个系统调用号就用来指明到底是要执行哪个系统调用;进程不会提及系统调用的名称。
⚠️️Linux有一个“未实现”系统调用sys_ni_syscall(),只返回无效的系统调用错误号-ENOSYS

⚠️内核sys_call_table记录了系统调用表中的所有已注册过的syscall列表,该表为每一个有效的系统调用指定了唯一的系统调用号。

【syscall性能】

Linux系统调用比其他许多OS执行都要快。

  • 很短的上下文切换时间

  • 系统调用处理程序和每个系统调用本身都很简洁

【系统调用处理程序】

用户空间的程序无法直接执行内核代码,因为内核驻留在受保护的地址空间上。那么需要通知内核要执行一个系统调用,其机制是靠软中断实现的

软中断~通过引发一个异常来促使系统切换到内核态去执行异常处理程序,此时的异常处理程序即为系统调用处理程序

⚠️X86系统上预定义的软中断是128,通过int$0x80指令触发该中断。这条指令会触发一个异常导致系统切换到内核态并执行第128号异常处理程序/系统调用处理程序system_call()

⚠️X86增加了sysenter指令提供了更快、更专业的陷入内核执行系统调用的方式。

A.指定恰当的syscall

因所有的系统调用陷入内核方式一样,必须把系统调用号一并传给内核。在X86上,系统调用号是通过eax寄存器传递给内核的。

system_call()函数通过将给定的系统调用号与NR_syscalls做比较来检查其有效性。若 >= NR_syscalls,该函数就返回-ENOSYS。否则就执行相应的syscall:

call *sys_call_table(, %rax, 8)

B.参数传递

除了系统调用号以外,大部分系统调用都还需要一些外部的参数输入。可以像传递系统调用号把这些参数从用户空间传给内核。

在X86-32系统上,ebx、ecx、edx、esi和edi按照顺序存放前5个参数。

【系统调用实现】

1.系统调用实现

每个系统调用都应该有一个明确的用途。在Linux中不提倡采用多用途的syscall(一个系统调用通过传递不同的参数值来选择完成不同的工作)。Unix主张提供机制而不是策略

  • 系统调用的接口应该力求简洁,参数尽可能少。

  • 系统调用的语义和行为非常关键,因为应用程序依赖于它们。

  • 尽量为将来多考虑,考虑其扩展性。

2.参数验证

系统调用必须仔细检查它们所有的参数是否合法有效。syscall内核空间执行,如果任由用户将不合法的输入传递给内核,那么系统的安全和稳定将受到影响。

⚠️最重要的一种检查就是检查用户提供的指针是否有效。在接收一个用户空间的指针之前,内核必须保证:

  • 指针指向的内存区域属于用户空间。~进程绝不能欺骗内核去读内核空间的data

  • 指针指向的内存区域在进程的地址空间里。进程绝不能欺骗内核去读其他进程的data

  • 如果是读,该内存应被标记为可读;

    如果是写,该内存应被标记为可写;

    如果是可行,该内存应被标记为可执行

    进程绝不能绕过内存访问限制

内核提供了2方法来完成必须的检查和内核空间<=>用户空间之间data的来回拷贝

⚠️内核无论何时都不能轻率地接受来自用户空间的指针!⚠️

1.copy_to_user():向用户空间写入data

该函数有3个参数,分别为进程空间中的目的内存地址、内核空间内的源地址、需要拷贝的data长度。

2.copy_from_user():从用户空间读取data

该函数有3个参数与1相似,把第二个参数指定位置上的data拷贝到第一个参数指定的位置上,拷贝的数据长度由第三个参数决定。

【返回值】 若失败,返回的是没能完成拷贝的数据字节数;若成功则为0。

新版本的Linux允许检查针对特定资源的特殊权限。调用者可以使用capable()函数来检查是否有权对指定的资源进程操作(0无非0)。默认情况下,属于超级用户的进程拥有所有权利而非超级用户没有任何权利。Linux/capability.h包含一份所有这些权能和其对应的权限列表

【系统调用上下文】

内核在执行syscall时处于进程上下文。在进程上下文中,内核可以休眠(如在系统哦给你调用阻塞or显式调用schedule()时)并且可以被抢占。

  • 休眠表明系统调用可以使用内核提供的绝大部分功能。

  • 可抢占表明当前的进程可被其他进程抢占。

⚠️⚠️⚠️因为新的进程可能使用相同的系统调用,所以必须保证系统调用是可重入的

当系统调用返回时,控制权仍然在system_call()中,它最终负责切换到用户空间并让用户进程继续执行下去。

1.绑定一个系统调用的最后步骤

当编写完一个系统调用后,要把它注册成一个正式的系统调用。如下步骤:

  • 在系统调用表的最后加入一个表项。⚠️index从0开始

  • 对于所支持的各种体系结构,系统调用号都必须定于

  • 系统调用必须被编译进内核映像(而不能编译成模块)。需要把它放入kernel/下的一个相关文件即可。

2.从用户空间访问系统调用

通常系统调用靠C库支持。用户程序通过包含标准文件并和C库链接,就可使用syscall。

⚠️Linux本身提供了一组宏,用于直接对系统调用进行访问。它会设置好寄存器并调用陷入指令。这些宏是_syscalln(),其中n的范围[0, 6],代表需要传递给系统调用的参数个数。对于每个宏,都有2 + 2*n个参数。

~例如:

open()系统调用定义如下:

long  open(const char *filename,int flags, int mode)

而不靠库支持,直接调用此系统调用的宏的形式如下:

#define  NR_open  5_syscall3(long,  open, const char *filename,int flags, int mode)

其中long为返回值类型,open为系统调用名称,后面为参数信息。

该宏会被扩展成为内嵌汇编的C函数;由汇编语言执行。将系统调用号和参数压入寄存器并触发软中断来陷入内核。

3.why不通过系统调用的方式实现???

~建立一个new系统调用的好处:

  • 系统调用创建容易且使用方便

  • Linux系统调用的高性能显而易见

引发的问题:

  1. 需要一个内核在处于开发版本时由官方分配的系统调用号

  2. 系统调用被加入稳定内核后就被固化了,为了避免应用程序崩溃,它的接口不允许改动

  3. 需要将系统调用分别注册到每个需要支持的体系结构中去

  4. 在脚本中不容易调用系统调用,也不能从文件系统直接访问系用调用

  5. 由于需要系统调用号,因此在住内核树外是很难维护和使用系统调用的

  6. 如果仅仅进行简单的信息狡猾,系统调用就大材小用了

⚠️不要以为你以为的就是你以为的~这很哲学⚠️

~替代方法:

实现一个设备节点,并对此实现read()和write()。使用ioctl()对特定的设置进行操作or对特定的信息进行检索。

  • 像信号量接口可以用文件描述符来表示。因此也就可以按上述方式对其操作

  • 把增加的信息作为一个文件放在sysfs的合适位置

Linux系统尽量避免每出现一种新抽象就简单的加入一个new系统调用。新系统调用增添频率很低也反映出了Linux是一个相对稳定且功能较为完善的OS

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值