系统调用
内核提供了用户进程和内核交互的接口,使得应用程序可以受限制的访问硬件设备。
提供这些接口主要是为了保证系统稳定可靠,避免应用程序恣意妄行。
一、内核通信
系统调用在用户空间进程和硬件设备之间添加中间才能。作用有三:
- 为用户空间提供一种硬件的抽象接口。无需理会物理结构是怎么样的。
- 系统调用保证了系统的稳定和安全。内核可以有选择的对其访问进行控制。
- 每个进程都运行在虚拟系统中,用户空间和系统的其余部分提供这样一层公共接口。
二、API、POSIX和C库
API:应用程序不需要访问内核,编程接口可以组合成API
POSIX:依据大多数Unix系统接口建立的
C库:提供了Unix主要API,所有C程序都可以使用C库
三、系统调用
例如,getpid中的系统调用:
SYSCALL_DEFINE0(getpid) { return task_tgid_vnr(current); //returns current->tgid }
SYSCALL_DEFINE0只是一个宏,它定义一个无参数的系统调用(因此数字0),展开后代码如下:
asmlinkage long sys_getpid(void)
asmlinkage是一个限定词,是一个编译指令,通知编译器仅从栈中提取该函数的参数。
3.1 系统调用号
在Linux中,每个系统调用被附于一个系统调用号。
系统调用号,一旦分配就不能在更改,否则编译好的应用程序会崩溃。
Linux中有一个"未实现"系统调用sys_ni_syscall(),会返回-ENOSYS。
内核记录了系统调用表中所有已注册过的系统调用的列表,存储在sys_call_table中。定义于arch/i386/kernel/syscall_64.c
3.2 系统调用的性能
Linux系统调用比其他操作系统执行的要快,上下文切换时间是一个重要的原因,内核都被优化的简洁高效。
四、系统调用处理程序
通知内核的机制是靠软中断实现的:通过引发一个异常来促使系统切换到内核态去执行异常处理程序。
此时的异常处理程序就是系统调用处理程序。程序名字叫system_call(),并且与硬件系统密切相关。
system_call()通过将给定的系统调用号与NR_syscalls作比较来检查有效性。如果它大于或者等于NR_syscalls,该函数就返回-ENOSYS。否则执行系统调用
call *sys_call_table(, %rax, 8)
4.2 参数传递
参数依次用ebx、ecx、edx、esi和edi的顺序存放前5个参数。
需要六个或六个以上参数的情况不多见,此时,应该用一个单独的寄存器存放指向所有这些参数在用户空间地址的指针。
五、系统调用的实现
怎样设计和实现一个系统调用是难题所在,而把它加到内核里却无须太多周折。
5.1 实现系统调用
Linux中不提倡采用多用途的系统调用(一个系统调用通过传递不同的参数值来选择完成不同的工作)。
- 系统调用的接口应该力求简介,参数尽可能少。并且力求稳定,不做改动。
- 设计接口的时候要尽量为将来多做考虑,系统调用设计的越通用越好。
- 要时刻注意可移植性和健壮性。
5.2 参数验证
如果用户将不合法的输入传递给内核,那么系统的安全和稳定将面临极大的考验。
最重要的检查就是检查用户提供的指针是否有效,在接收用户空间的指针之前,内核必须保证:
- 指针指向的内存区是用户空间,进程决不能哄骗内核去读其他进程的数据。
- 指针知悉将发的内存区域在进程的地址空间里,进程决不能哄骗内核去读其他进程的数据。
- 如果是读,该内存应被标记为可读;如果是写,该内存应被标记为可写;如果可执行,该内存应被标记为可执行。进程不能绕过内存访问限制。
向用户空间写入数据,内核提供了copy_to_user(),第一个参数是进程空间中的目的内存地址。第二个参数是内核空间的源地址,最后一个是数据长度。
从用户空间读取数据,内核提供了copy_from_user(),把第二个参数拷贝到第一个参数中,第三个参数是数据长度
SYSCALL_DEFINE4(reboot, int, magic1, int, magic2, unsigned int, cmd, void __user *, arg) { char buffer[256]; /* 我们只信任启动系统的系统管理员 */ if(!capable(CAP_SYS_BOOT)) return -EPERM; /* 为了安全起见,我们需要"magic"参数 */ if(magic1 != LINUX_REBOOT_MAGIC1 || (magic2 != LINUX_REBOOT_MAGIC2 && magic2 != LINUX_REBOOT_MAGIC2A && magic2 != LINUX_REBOOT_MAGIC2B && magic2 != LINUX_REBOOT_MAGIC2C)) return -EINVAL; /* 当未设置pm_power_off时,请不要试图让power_off的代码看起来像是可以停机 * 而应该采用更简单的方式 */ if((cmd == LINUX_REBOOT_CMD_POWER_OFF) && !pm_power_off) cmd = LINUX_REBOOT_CMD_HALT; lock_kernel(); switch(cmd) { case LINUX_REBOOT_CMD_RESTART: kernel_restart(NULL); break; case LINUX_REBOOT_CMD_CAD_ON: C_A_D = 1; break; case LINUX_REBOOT_CMD_CAD_OFF: C_A_D = 0; break; case LINUX_REBOOT_CMD_HALT: kernel_halt(); unlock_kernel(); do_exit(0); break; case LINUX_REBOOT_CMD_POWER_OFF: kernel_power_off(); unlock_kernel(); do_exit(0); break; case LINUX_REBOOT_CMD_RESTART2: if(strncpy_from_user(&buffer[0], arg, sizeof(buffer) - 1) < 0) { unlock_kernel(); return -EFAULT; } buffer[sizeof(buffer) - 1] = '