Linux 系统调用浅析

版权声明:原创文章 && 转载请著名出处 https://blog.csdn.net/zhoutaopower/article/details/86474392

系统调用原理


引子 —— CPU 在硬件上支持用户模式和 CPU 特权模式,在 CPU 特权模式下可以访问 CPU 的所有资源,而在用户模式,是无法访问一些特权模式下才能访问的资源。这样就有效的将用户和特权分开,限制用户态的操作不去破坏操作系统内部逻辑。

系统调用,顾名思义,说的是操作系统提供给用户程序调用的一组“特殊”接口。用户程序可以通过这组“特殊”接口来获得操作系统内核提供的服务,比如用户可以通过文件系统相关的调用请求系统打开文件、关闭文件或读写文件,可以通过时钟相关的系统调用获得系统时间或设置定时器等。

系统服务之所以需要通过系统调用来提供给用户空间的根本原因是为了对系统进行“保护”,因为我们知道Linux的运行空间分为内核空间与用户空间,它们各自运行在不同的级别中,逻辑上相互隔离。所以用户进程在通常情况下不允许访问内核数据,也无法使用内核函数,它们只能在用户空间操作用户数据,调用用户空间函数。比如我们熟悉的“hello world”程序(执行时)就是标准的用户空间进程,它使用的打印函数printf就属于用户空间函数,打印的字符“hello word”字符串也属于用户空间数据。

但是很多情况下,用户进程需要获得系统服务(调用系统程序),这时就必须利用系统提供给用户的“特殊接口”——系统调用了,它的特殊性主要在于规定了用户进程进入内核的具体位置;换句话说,用户访问内核的路径是事先规定好的,只能从规定位置进入内核,而不准许肆意跳入内核。有了这样的陷入内核的统一访问路径限制才能保证内核安全无虞。

系统调用实现


在用户空间,通过调用C库,到内核。当然也可以自定义一个系统调用(绝对不建议这么做)。

ARM Linux系统利用 SWI 指令来从用户空间进入内核空间,还是先让我们了解下这个SWI指令吧。SWI指令用于产生软件中断,从而实现从用户模式到管理模式的变换,CPSR保存到管理模式的SPSR,执行转移到SWI向量。在其他模式下也可使用SWI指令,处理器同样地切换到管理模式。

每个系统调用都被赋予一个系统调用号,当用户空间执行一个系统调用后,这个系统调用号就用来指定到底是要执行哪一个系统调用。也就是,一个 ID 绑定一个系统调用。内核记录了所有的注册了的系统调用的列表,在 sys_call_table 中,每一个执行的函数对应了唯一的系统调用号。

在发生 SWI 软中断的时候,通过获取系统调用号,得到了对应执行的函数,同时将系统调用的参数传递到内核。最后跳转到指定的函数位置,去执行系统调用即可。

在 Linux Kernel 侧,存在很多类似 SYSCALL_DEFINEx 的宏定义。x 的范围从 0~6,系统调用也就是支持最大 6 个入参

在 kernel/sys.c 文件中定义了很多处理函数,比如 getpid:

/**
 * sys_getpid - return the thread group id of the current process
 *
 * Note, despite the name, this returns the tgid not the pid.  The tgid and
 * the pid are identical unless CLONE_THREAD was specified on clone() in
 * which case the tgid is the same in all threads of the same group.
 *
 * This is SMP safe as current->tgid does not change.
 */
SYSCALL_DEFINE0(getpid)
{
	return task_tgid_vnr(current);
}

它没有入参,所以使用的是 SYSCALL_DEFINE0

该函数其实在 include/linux/syscalls.h 中定义:

asmlinkage long sys_getpid(void);

也就是说这个宏,其实做的是名字拼接,生成了一个处理系统调用的函数名而已。

当然,也可以自己实现一个系统调用,需要做的是,在递增的系统调用编号上进行新增一个编号,用户新增的系统调用,然后在 sys_call_table 中放入对应的处理函数的指针。最后在类似于 sys.c 的文件中去实现这个系统调用。

由于新增的系统调用,在 C 库中肯定没有对应的函数,所以在用户空间中,使用自定义的系统调用,就无法走 C 库这一条路。但是 Linux 本身提供了一组宏,用于直接对系统进行访问,它会设置好寄存器,并调用陷入指令(SWI),这些宏是 _syscalln(),其中的 n 的范围是 0~6,代表传递的参数个数。对于每一个这样的宏来说,都有 2 + 2 * n 个参数,比如:open() 系统调用为:

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

如果不使用 C 库进行调用的话,使用的调用形式为:

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

其实就是把类型和参数展开了。

猜你喜欢

转载自blog.csdn.net/zhoutaopower/article/details/86474392