在现代操作系统架构中,内核空间和用户空间之间增加了一个中间层,这就是系统调用层。
系统调用层主要有如下作用。
为用户空间程序提供一层硬件抽象接口。这能够让应用程序编程者从学习硬件设备底层编程中解放出来。例如,当需要读写一个文件时,应用程序编写者不用去关心磁盘类型和介质,以及文件存储在磁盘哪个扇区等底层硬件信息。
保证系统稳定和安全。应用程序要访问内核必须通过系统调用层,那么内核可以在系统调用层对应用程序的访问权限、用户类型和其他一些规则进行过滤,这样可以避免应用程序不正确地访问内核。
可移植性。可以让应用程序在不修改源代码的情况下,在不同的操作系统或者不同的硬件体系结构的系统中重新编译并且运行。
1 系统调用和POSIX标准
有的读者可能对应用编程接口(API)和系统调用之间的关系有点糊涂了。
一般来说,应用程序调用用户空间实现的应用编程接口来编程,而不是直接调用系统调用。
一个 API接口函数可以由一个系统调用实现,也可以由多个系统调用来实现,甚至完全不使用任何系统调用。
因此,一个API接口没有必要对应一个特定的系统调用。
在UNIX系统设计的早期就出现了操作系统的API接口层。
在UNIX的世界里,最通用的系统调用层接口是POSIX(Portable Operating System Interface of UNIX)标准。POSIX的诞生和UNIX的发展密不可分。
UNIX系统诞生于20世纪70年代的贝尔实验室,很多商业厂商基于UNIX发展自己的UNIX系统,但是标准不统一。后来IEEE制定了POSIX标准,但是需要注意的是,POSIX标准针对的是API而不是系统调用。
判断一个系统是否与POSIX兼容时,要看它是否提供一组合适的应用编程接口,而不是看它的系统调用是如何定义和实现的。
Linux操作系统的API接口通常是以C标准库的方式提供的,比如Linux中的libc库。
C库提供了POSIX的绝大部分的API的实现,同时也为内核提供的每个系统调用封装了相应的函数,并且系统调用和 C 库封装的函数名称通常是相同的。
例如,open 系统调用在 C库的函数也是open函数。
另外几个API函数可能调用封装了不同功能的同一个系统调用,例如,libc库函数中实现的malloc()、calloc()和free()等函数,这几个函数用来分配和释放虚拟内存(堆上的虚拟内存),它们都是利用brk系统调用来实现的。
大家都知道malloc是c中常用的内存操作函数,malloc动态的申请一块指定大小的内存,方便存放数据。而brk/sbrk则是实现malloc的底层函数,其中brk是系统调用。brk和sbrk主要的工作是实现虚拟内存到内存的映射。
每个进程可访问的虚拟内存空间为3G,但在程序编译时,不可能也没必要为程序分配这么大的空间,只分配并不大的数据段空间,程序中动态分配的空间就是从这一块分配的。如果这块空间不够,malloc函数族(realloc,calloc等)就调用sbrk函数将数据段的下界移动,sbrk函数在内核的管理下将虚拟地址空间映射到内存,供malloc函数使用。
sbrk不是系统调用,是C库函数。系统调用通常提供一种小功能,而库函数通常提供比较复杂的功能。sbrk/brk是从堆中分配空间,本质是移动一个位置,向后移就是分配空间,向前移就是释放空间,sbrk用相对的整数值确定位置,如果这个整数是正数,会从当前位置向后移若干字节,如果为负数就向前若干字节。在任何情况下,返回值永远是移动之前的位置。
从操作系统角度来看,进程分配内存有两种方式,分别由两个系统调用完成:brk和mmap(不考虑共享内存)。
brk是将数据段(.data)的最高地址指针_edata往高地址推;
mmap是在进程的虚拟地址空间中(堆和栈中间,称为文件映射区域的地方)找一块空闲的虚拟内存。
这两种方式分配的都是虚拟内存,没有分配物理内存。在第一次访问已分配的虚拟地址空间的时候,发生缺页中断,操作系统负责分配物理内存,然后建立虚拟内存和物理内存之间的映射关系。
在标准C库中,提供了malloc/free函数分配释放内存,这两个函数底层就是由brk,mmap,munmap这些系统调用实现的。
2 系统调用表
Linux系统为每一个系统调用赋予一个系统调用号。
当应用程序执行一个系统调用时,应用程序就可以知道执行和调用到哪个系统调用了,从而不会造成混乱。
系统调用号一旦分配之后就不会有任何变更,否则已经编译好的应用程序就不能运行了。
对于ARM32系统来说,其系统调用号定义在arch/arm/include/uapi/asm/unistd.h头文件中。
<arch/arm/include/uapi/asm/unistd.h> /** This file contains the system call numbers.*/ #define __NR_restart_syscall (__NR_SYSCALL_BASE+ 0) #define __NR_exit (__NR_SYSCALL_BASE+ 1) #define __NR_fork (__NR_SYSCALL_BASE+ 2) #define __NR_read (__NR_SYSCALL_BASE+ 3) #define __NR_write (__NR_SYSCALL_BASE+ 4) #define __NR_open (__NR_SYSCALL_BASE+ 5) #define __NR_close (__NR_SYSCALL_BASE+ 6) /* 7 was sys_waitpid */ #define __NR_creat (__NR_SYSCALL_BASE+ 8) #define __NR_link (__NR_SYSCALL_BASE+ 9) #define __NR_unlink (__NR_SYSCALL_BASE+ 10) #define __NR_execve (__NR_SYSCALL_BASE+ 11) #define __NR_chdir (__NR_SYSCALL_BASE+ 12) #define __NR_time (__NR_SYSCALL_BASE+ 13) #define __NR_mknod (__NR_SYSCALL_BASE+ 14)