linux内核——系统调用

来源:互联网 发布:hdfs如何保证数据安全 编辑:程序博客网 时间:2024/06/10 22:33

系统调用

系统调用是内核提供给用户进程与内核进行交互的一组接口,这些接口让应用程序受限的访问硬件设备,提供了创建进程并与已有进程进行通信

的机制,也提供了申请操作系统其他资源的能力。用户进程通过系统调用发出各种请求,而内核负责满足这些请求或者无法满足时返回一个错误,

因此,实际上系统调用主要是为了保证系统稳定可靠,避免用户进程恣意妄为。


与内核通信

在Linux中,除了异常和陷入外,系统调用是用户空间访问内核的唯一合法入口。

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

  • 为用户空间提供了一种硬件的抽象接口(如读取存放在不同文件系统中的不同类型的文件的时候open,read,close.....);
  • 保证系统的稳定和安全;
  • 每个进程运行在虚拟系统中,而在用户空间和系统的其余部分提供这样一层公共接口,可以提供更好是稳定性和安全性;


API、POSIX和C库

一般情况下,应用程序通过用户空间实现的应用编程接口(API)而不是直接通过系统调用来编程。这样的好处:
  •  API并不需要和内核提供的系统调用对应起来;
  • API定义了一组应用程序使用的编程接口,它们的实现可以是通过一个系统调用,也可以通过多个系统调用,不使用系统调用来实现也是可以的;
  • API可以在不同的系统上给应用程序提供完全相同的接口,但它们本身在这些系统上的实现可能各不相同;
通常,系统调用靠C库的支持。用户程序通过包含标准头文件并和C库连接,就可以使用系统调用(调用库函数,再由库函数实际调用)。
但是如果自己加的系统调用,C库可能就不支持了,但是linux提供了一组宏,用于直接对系统调用进行访问——_syscalln() .
如下,应用程序调用printf在屏幕输出时,首先调用C库的printf,而C库中printf又是通过wirte实现的,C库的write接着去调用内核中的write系统调用,
从而实现用户空间通过printf调用操作硬件(屏幕)的功能:

{(应用程序)调用printf()}--->{(C库)printf()--->(C库)write()}--->{(内核)write()系统调用} 

POSIX是Unix世界中最流行的应用程序编程接口标准,其有IEEE的一组标准组成,目标是提供一套基于Unix的可移植操作系统标准。在大多数的

Unix系统上,POSIX定义的API和系统调用之间有着直接的关系,因为POSIX是仿照早期Unix系统的接口建立的。Linux的系统调用作为C库的一部

分提供,C库实现了Unix系统的主要API,包括标准C库和系统函数,此外C库提供了POSIX的绝大部分API。

从程序员的角度看,系统调用无关紧要,只需要跟C库和POSIX提供的API打交道就可以了;相反,内核只需要和系统调用打交道,无需关系应用

程序怎么使用系统调用,内核只需时刻牢记系统调用所有的潜在用途,并保证他们有良好的通用性和灵活性。这也体现了linux中提供机制而不是

策略的规则。


系统调用号

linux中,每个系统调用被赋予一个独一无二的系统调用号;系统调用号一经分配就不能在有任何变更,即使该系统调用被删除,它所占据的系统调
用号也不允许被回收再利用。用户空间的进程执行一个系统调用的时候,是通过独一无二的系统调用号来指定调用那个系统调用的。

系统调用的性能

Linux系统调用比其他操作系统执行的快,是因为Linux的上下文切换时间很短,进出内核被优化的很简洁高效,另外系统调用本身和其处理程序也被
优化的很简洁。

系统调用处理程序

应用程序无法直接执行内核代码,只能以某种方式通知系统,告诉内核自己需要执行一个系统调用,希望切换到内核态,这样内核就可以代表应用程
序在内核空间执行系统调用。
通知内核的机制是靠软件中断来实现的:通过触发一个异常来促使系统切换到内核态去执行异常处理程序,此时的异常处理程序实际上就是系统调用处
理程序。软中断进入内核后,执行的系统调用处理程序是system_call(),然后system_call()函数根据传递的系统调用号,从而执行相应的系统调用。

系统调用的实现

Linux中添加一个新的系统调用的工作相对容易,因为Linux的系统调用的实现并不需要关心它和系统调用处理程序之间的关系,它并没有规定处理程序
如何实现,只要能提供希望的功能就可以了。但怎样去设计一个系统调用却是个难题:
  • 决定新系统调用的功能;
  • 设计接口的时候要尽量为将来多做考虑;
  • 可移植性和健壮性;
系统的调用必须严格的检查所有参数的合法性,因为其运行在内核空间中,如果任何用户将不合法的输入参数传递给内核,对系统的稳定性将产生极大
的影响。最重要的检查就是检查用户提供的指针是否有效,在接受一个用户空间的指针前,内核必须保证:
  • 指针指向的内存区域属于用户空间;
  • 指针指向的地址空间在进程的地址空间里面;
  • 对内存做读写执行前先检查相应的权限;
编写好系统调用后将其注册为一个系统调用:
  • 首先,在系统调用表的最后面加入一个表项,代表要增加的系统调用;
  • 其次,对于所支持的各种体系结构,系统调用号都必须定义于<asm/unistd.h>中;
  • 最后,系统调用必须被编译进内核映像,而不能编译成模块。

系统调用的上下文

内核在执行系统调用的时候处于进程上下文。在进程上下文中内核可以休眠(比如在系统调用阻塞或者显式调用schedule()的时候)并且可以被抢占;
可以被抢占表明,调用系统调用处理的用户空间进程,和正常用户空间进程一样,可以被其他进程抢占,同样新的进程也可以使用相同的系统调用;
系统调用返回的时候,控制权任然在system_call()中,它最终会负责切换到用户空间,并让用户进程继续执行下去。


0 0