第10章 系统调用
1 系统调用的流程
- 在内核态保存大多数用户态寄存器的内容
- 调用系统服务例程处理系统调用
- 退出系统调用:把保存在内核栈中的内容加载到寄存器,CPU切回到用户态
2 系统调用的代码示例
// 用户态
xyz()
// 内核态
SYSCALL(id, param) // 进入系统调用,用户态 -> 内核态
system_call:
sys_xyz() // 用于实现系统调用的功能
SYSEXIT // 退出系统调用,内核态 -> 用户态
3 进入和退出系统调用
① Linux老版本:int $0x80汇编进入系统调用,调用system_call()实现;iret汇编指令退出系统调用。
② Linux 2.6以后:如果Intel支持sysenter,就使用sysenter()汇编语言指令,实现快速系统调用;执行sysexit()汇编指令退出系统调用。
4 参数传递
普通C函数的参数传递时通过把参数写入到活动的程序栈中实现;
而对于系统调用,要在发出系统调用前把参数写入到CPU寄存器中,然后调用系统调用服务例程前,把存放在CPU中的参数拷贝到内核堆栈中。
寄存器传递参数的规则:
- 通过eax寄存器传递系统调用号
- 32位寄存器,每个参数的长度不超过32位
- 除了系统调用号,参数的个数不超过6个
access_ok()会对系统调用以参数传递来的线性地址有效性检查,该检查只保证用户态进程不会试图侵扰内核地址空间!
缺页异常处理程序可以检测到用户态进程传递过来的地址在内核态是无效的(没有映射物理页框),进而触发缺页处理。
可以参考:第3章 进程 - RTOS 进程切换
5 访问进程地址空间
5.1 内核态和进程地址空间
系统调用服务例程需要非常频繁地读写进程地址空间的数据 —— 比如写数据到磁盘上某个文件;
系统调用发生在内核态,不属于用户进程地址空间(当然也不属于内核进程),但是需要访问用户进程地址空间。
可以参考:第7章 进程调度 - 内核态用户态的切换与进程调度的关系
5.2 访问进程地址空间的函数和宏
get_user __get_user
put_user __put_user
copy_from_user __copy_from_user
copy_to_user __copy_to_user
...
函数名开头没有下划线的函数或宏,要额外对所请求的线性地址空间进行有效性检查,而有下划线的则会跳过检查;
因此内核重复访问同一块线性区时,可以在开始只对该地址检查一次,以后使用__xxx省去检查提高效率。
(一般而言,内核态访问进程地址空间,指的是用户进程地址空间。)
6 缺页异常
如果系统调用发生时,传过来的线性地址侵扰到内核会被access_ok()拦截;
除此之外,仍有可能触发缺页异常,分为以下五种情况:
| 序号 | 直接原因 | 所属情况 | 解决方法 | 识别方法 |
| 1 | 内核试图访问属于进程地址空间的页,但是对应的页框不存在 | 内核延迟分配页框 | 请求调页:为准备访问的页分配页框(物理内存) | 线性地址属于进程的线性地址空间,对应页框/页表项不存在 |
| 2 | 属于进程地址空间的页,但是内核试图去写一个只读页 | 内核写时复制 | 为准备写的地址分配页框 | 线性地址属于进程的线性地址空间,但是页框为只读属性 |
| 3 | 内核试图访问属于进程地址空间的页,页表项还未初始化 | 内核地址空间的非连续内存区 |
内核在vmalloc()时只会更新主内核页表项; 内核进程首次访问非连续内存区时,肯定会发生缺页异常,进而从主内核页表项(init进程)拷贝对应页表项到当前进程 |
检查相应的主内核页是否包含一个映射该地址的非空页表项 |
| 4 | 内核函数包含编程错误,或者硬件突然错误导致地址异常 | NA | 上报pagefault,并触发panic dump |
内核会把每条访问进程地址空间的指令(并不多)都放到异常表中;发生缺页异常的这两种情况时,检查异常表中是否包含产生异常的指令,以进行区分 |
| 5 | 内核试图读取系统调用传过来的不属于进程地址空间的地址 | NA | 强制返还一个错误码给发起系统调用的用户进程 |
参考:
注意:
内核进程不可以使用库函数,但是可以像用户进程那样调用系统调用。
本文来自博客园,作者:moonのsun,转载请注明原文链接:https://www.cnblogs.com/moon-sun-blog/p/18807168

浙公网安备 33010602011771号