操作系统六
1. 进程同步和P、V操作
若有三个并发进程互斥,如果利用信号灯方式控制其同步关系,那么三个进程的同步过程如下图:
2. Linux系统功能调用的实现机制
2.1 Linux系统调用的进入
Linux系统的软中断指令是汇编语言指令int 0x80,执行该指令会发生中断,处理机的状态由用户态自陷到内核态(在更新的芯片中添加了sysenter汇编语言指令等)。int 0x80指令使用的异常向量号是128(即16进制的80),该异常向量包含了内核系统调用程序的入口地址。在内核初始化时,已将系统调用处理程序的入口地址送入向量128的中断描述符表表项中,设置段地址为内核开始的地方,段内偏移则指向系统调用处理程序system_call()。当应用程序请求操作系统服务,发出int 0x80指令时,就会从用户态自陷到内核态,并从system_call()开始执行系统调用处理程序。当系统调用处理完毕后,通过iret汇编语言指令返回到用户态。
2.2 系统调用号和系统调用表
2.2.1 系统调用号
在Linux中,每个系统调用被赋予一个唯一的系统调用号。用户空间的进程通过系统调用号指明要执行的具体系统调用
系统调用号定义在include/asm-i386/unistd.h头文件中,这个头文件定义了该系统所有的系统调用号。格式如下:
#define _NR_restart_syscall 0
#define _NR_exit 1
#define _NR_fork 2
#define _NR_read 3
#define _NR_write 4
#define _NR_open 5
......
......
#define _NR_mq_getsetattr 282
该文件中的每一行表示为#define_NR_name NNN,其中,”NR“是一种约定,name为系统调用的名称,而NNN则是该系统调用对应的号码。该文件的最后,还定义了几个与系统调用相关的关键的宏。
2.2.2 系统调用表
系统调用表记录了内核中所有已注册的系统调用,它是系统调用的跳转表,实际上是一个函数指针数组,表中依次保存所有系统调用的函数指针,以方便总的系统调用处理函数system_call进行索引。Linux系统调用表保存在arch/i386/kernel/下的entry.S中,其形式如下:
ENTRY(sys_call_table)
.long sys_restart_syscall /* 0 */
.long sys_exit /* 1 */
.long sys_fork /* 2 */
.long sys_read /* 3 */
.long sys_write /* 4 */
.long sys_open /* 5 */
......
......
.long sys_mq_getsetattr /* 6 */
文件中有许多.long SYMBOL_NAME(sys_ni_syscall)的结构,”.”代表当前地址,”sys_call_table”代表数组首地址
2.3 系统调用处理程序
系统调用处理程序是system_call(),该函数的主要工作如下。
- 通过宏SAVE_ALL保护异常处理程序中要用到的所有寄存器到寄存器到内核堆栈中,其中,指令地址和处理机状态已在中断进入过程中保护(eflags、cs、eip、ss、esp寄存器除外)
- 进行系统调用正确性检查,如对用户态进程传递来的系统调用号进行有效性检查,若该号大于或等于系统调用表的表项数,系统调用处理程序就终止
- 根据eax中所包含的系统调用号,调用其对应的服务例程
- 系统服务例程结束时,通过宏RESTORE_ALL恢复寄存器,最后通过iret指令返回
3. 增加一个新的系统调用方法
如果需要扩充Linux系统的功能,增加一个新的系统调用需要做的工作包括以下几个方面:
- 描述新增加功能的服务例程
- 增加一个新的系统调用号
- 在系统调用表中登记新的系统调用号以及对应的服务例程
- 新增加的服务例程要被Linux系统接受,必须重新编译内核,生成新的包含新增服务例程的内核
当要增加一个新的系统调用时,首先要确定新增的服务例程的名字,因为确定了这个名字后,在系统调用中的几个相关的名字也就确定了。如:
- 新增加的系统调用名为mysyscall
- 系统调用的编号名字为_NR_mysyscall
- 内核中系统调用服务例程的名字为sys_mysyscall
3.1 添加新的服务例程
编写新增的服务例程加到内核中去,即在/usr/src/linux/kernel/sys.c文件中增加一个新的函数,该函数的名字是sys_mysyscall。函数体内是新增加的功能描述,在Linux系统增加一个新的系统调用时,首先要保证整个控制过程正确,所以在开始调试时,新增功能尽量简单,如下例,其功能是返回一个整形值。
asmlinkage int sys_mycall(int number) {
return number;
}
3.2 增加新的系统调用号
定义系统调用号,在文件include/asm-i386/unistd.h中添加一项#define _NR_mysyscall XX
XX 为增加的系统调用号,此数字选一未用值,一般在已定义的系统调用号的最后增加一项,如下所示:
#define _NR_restart_syscall 0
#define _NR_exit 1
#define _NR_fork 2
#define _NR_read 3
#define _NR_write 4
#define _NR_open 5
......
......
#define _NR_mq_getsetattr 282
#define _NR_mysyscall 283
3.3 修改系统调用表
在文件/arch/i386/kernel/entry.S中的系统调用表 sys_call_table中添加新增的系统调用,sys_call_table数组包含指向内核中每个系统调用的指针,这样就在数组中增加了新的内核函数的指针。在清单最后添加一行。如下所示:
ENTRY(sys_call_table)
.long sys_restart_syscall /* 0 */
.long sys_exit /* 1 */
.long sys_fork /* 2 */
.long sys_read /* 3 */
.long sys_write /* 4 */
.long sys_open /* 5 */
......
......
.long sys_mq_getsetattr /* 6 */
.long sys_mysyscall /* 7 */
3.4 重新编译内核并启动新内核
为使新的系统调用生效,需要重建Linux的内核。
对于不同的计算机配置、Linux不同的版本等,重新编译内核命令可能会有所不同。