/*
* 本程序主要是实现系统调用中断int 0x80的入口处理过程机信号检测过程,
* 同时给出了两个系统调用功能的底层接口sys_execve和sys_fork。还列出了
* 处理过程类似的协处理器出错int 16,设备不存在int 7,硬盘中断int 46,
* 软盘中断int 38的中断处理程序。
*
*/
/*
* linux/kernel/system_call.s
*
* (C) 1991 Linus Torvalds
*/
/*
* system_call.s contains the system-call low-level handling routines.
* This also contains the timer-interrupt handler, as some of the code is
* the same. The hd- and flopppy-interrupts are also here.
*
* NOTE: This code handles signal-recognition, which happens every time
* after a timer-interrupt and after each system call. Ordinary interrupts
* don't handle signal-recognition, as that would clutter them up totally
* unnecessarily.
*
* Stack layout in 'ret_from_system_call':
*
* 0(%esp) - %eax
* 4(%esp) - %ebx
* 8(%esp) - %ecx
* C(%esp) - %edx
* 10(%esp) - %fs
* 14(%esp) - %es
* 18(%esp) - %ds
* 1C(%esp) - %eip
* 20(%esp) - %cs
* 24(%esp) - %eflags
* 28(%esp) - %oldesp
* 2C(%esp) - %oldss
*/
SIG_CHLD = 17 # 信号,子进程结束或者是终止。
##############################################
# 堆栈中寄存器的偏移量
EAX = 0x00
EBX = 0x04
ECX = 0x08
EDX = 0x0C
FS = 0x10
ES = 0x14
DS = 0x18
EIP = 0x1C
CS = 0x20
EFLAGS = 0x24
OLDESP = 0x28 # 当特权级变化时
OLDSS = 0x2C
#############################################
##############################################
# 以下是定义任务结构task_struct中偏移量。
state = 0 # these are offsets into the task-struct.
# 进程状态码
counter = 4 # 任务运行时间片数
priority = 8
signal = 12 # 信号位图
sigaction = 16 # MUST be 16 (=len of sigaction)
blocked = (33*16)
# offsets within sigaction
sa_handler = 0 # 信号处理过程句柄
sa_mask = 4 # 信号屏蔽码
sa_flags = 8 # 信号集
sa_restorer = 12
nr_system_calls = 72 # linux 0.11中系统调用总数
/*
* Ok, I get parallel printer interrupts while using the floppy for some
* strange reason. Urgel. Now I just ignore them.
*/
.globl _system_call,_sys_fork,_timer_interrupt,_sys_execve
.globl _hd_interrupt,_floppy_interrupt,_parallel_interrupt
.globl _device_not_available, _coprocessor_error
# 错误的系统调用
.align 2 # 内存4字节对齐
bad_sys_call:
movl $-1,%eax
iret
# 重新执行调度程序入口
.align 2
reschedule:
pushl $ret_from_sys_call # $ret_from_sys_call地址入栈
jmp _schedule
# int 0x80 -- linux系统调用入口点,eax为中断号
.align 2
_system_call:
# 调用号如果超出范围,退出
cmpl $nr_system_calls-1,%eax
ja bad_sys_call
# 如果调用号没有超出范围,继续执行。
# 保护原来寄存器
push %ds
push %es
push %fs
# 下面的代码是将系统调用c函数的参数入栈,此过程是在执行
# 系统函数调用时实现的
pushl %edx
pushl %ecx # push %ebx,%ecx,%edx as parameters
pushl %ebx # to the system call
# ds,es指向内核数据段
# 参见文档 <linux0_11系统调用的执行过程是怎样的.doc>
movl $0x10,%edx # set up ds,es to kernel space
mov %dx,%ds
mov %dx,%es
# fs指向局部数据段
movl $0x17,%edx # fs points to local data space
mov %dx,%fs
# _sys_call_table(,%eax,4)使用的at&t格式寻址。其实就是调用eax
# 对应的系统调用函数
call _sys_call_table(,%eax,4)
pushl %eax # 系统调用号入栈
movl _current,%eax # 将当前进程数据结构地址保存在eax
cmpl $0,state(%eax) # state,如果当前进程不就绪
jne reschedule # 执行调度程序reschedule
cmpl $0,counter(%eax) # counter,如果当前的进程就绪
# 但是时间片用完,调度去了。
je reschedule
# ret_from_sys_call是在中断处理程序完成之后,对信号量进行识别处理
ret_from_sys_call:
# 得到当前进程数据结构地址到eax
movl _current,%eax # task[0] cannot have signals
cmpl _task,%eax # 当前的任务是task[0]?task在c语言定义。
je 3f # 如果是直接返回
##########################################
# 通过对调用程序代码的选择符的检查来判断调用程序是否
# 是超级用户。如果是超级用户直接退出,无须信号处理,
# 否则需要进行信号处理。这里比较选择符是否为普通用户
# 代码选择符0x000f(RPL = 3,局部表,第一个段(代码段))。
# 暂时认为的是linux内核在实现用户权限时的设置时这样的:
# 首先是普通用户的应用程序是在用户的空间实现的,但是root
# 用户的程序是在内核空间运行的。
cmpw $0x0f,CS(%esp) # was old code segment supervisor ?
jne 3f
###########################################
# 如果员堆栈的段选择符不是0x17,即是原来的堆栈
# 不在用户的的数据段,则也退出。
cmpw $0x17,OLDSS(%esp) # was stack segment = 0x17 ?
jne 3f
##########################################
# 下面程序开始执行,首先查看是否有信号量到来。
#
# 下面的代码首先是取得当前的任务结构中的信号位图
# (32位,每一位代表一种信号),然后用任务结构中的
# 信号屏蔽码,阻塞不允许的信号位,取得数值最小的信号值
# 在把原信号位图中对该信号对应位置0,最后将该信号的参数
# 值作为参数调用函数do_signal
# do_signal函数包含13个参数。
movl signal(%eax),%ebx # 取得信号位图 -- ebx
movl blocked(%eax),%ecx # 取得阻塞信号位图 -- ecx
notl %ecx # 每位取反
andl %ebx,%ecx # 获得许可信号位图
bsfl %ecx,%ecx # 从低位开始扫描,看是否存在1
# 如果有,则eax保留该位的偏移量
je 3f # 如果没有的信号向前退出
btrl %ecx,%ebx # 复位该信号 ebx含有原signal位图
movl %ebx,signal(%eax) # 重新保存signal -- current->signal
incl %ecx # 将信号调整为从1开始的数
# 下面的代码是调用函数_do_signal
pushl %ecx # 参数入栈
call _do_signal # 函数调用
popl %eax # 弹出信号值
#下面的代码是恢复在ret_from_sys_call中保存的值
3: popl %eax
popl %ebx
popl %ecx
popl %edx
pop %fs
pop %es
pop %ds
iret
# 下面的这段代码是处理协处理器发出的出错信号。跳转发哦c函数math_error
# 去执行,返回之后调用ret_from_sys_call处继续执行。
.align 2
_coprocessor_error:
push %ds
push %es
push %fs
pushl %edx
pushl %ecx
pushl %ebx
pushl %eax
movl $0x10,%eax # ds,es指向的是内核数据段
mov %ax,%ds
mov %ax,%es
movl $0x17,%eax # fs指向的是出错程序的数据段
mov %ax,%fs
pushl $ret_from_sys_call # 函数的返回地址入栈
jmp _math_error # 执行c函数math_error
# int7 -- 设备部存在或者是协处理器不存在
# 控制寄存器中的cr0中的em标志位置位,则当cpu在执行esc转义指令时,
# 就会引发该中断,这样就能够让这个中断处理程序模拟esc转义指令。
# esc转义指令的纤细解释见文档 <<esc转义指令说明.doc>>
# cr0的ts标志是在cpu执行任务转换时设置的。ts可以确定什么时候
# 协处理器的内容(上下文)与cpu正常执行的人物不匹配。当cpu在运行
# 一个转义指令时发现ts指令置位,就引发该中断。
# 该中断最后将转移到标号$ret_from_sys_call处继续执行。
.align 2
_device_not_available:
push %ds
push %es
push %fs
pushl %edx
pushl %ecx
pushl %ebx
pushl %eax
movl $0x10,%eax
mov %ax,%ds
mov %ax,%es
movl $0x17,%eax
mov %ax,%fs
pushl $ret_from_sys_call# 将$ret_from_sys_call地址入栈
clts # clear TS so that we can use math
movl %cr0,%eax
testl $0x4,%eax # EM (math emulation bit)
# 如果不是em引起的,则恢复新任务协处理器
# 状态。
je _math_state_restore # 执行c函数math_state_restore
pushl %ebp
pushl %esi
pushl %edi
call _math_emulate # 调用函数math_emulate
popl %edi
popl %esi
popl %ebp
ret # 跳转到$ret_from_sys_call执行
# int32 -- 时钟中断程序。
# 定时芯片8254/8253是在sched.c中完成初始化的。下面的
# 这段代码首先将jiffies的值增加1,发送中断指令给8259
# 控制器,然后用当前特权级作为参数调用c函数do_timer
# (long CPL)。当调用返回时转出检测并处理信号。
.align 2
_timer_interrupt:
# 保护现场
push %ds # save ds,es and put kernel data space
push %es # into them. %fs is used by _system_call
push %fs
pushl %edx # we save %eax,%ecx,%edx as gcc doesn't
pushl %ecx # save those across function calls. %ebx
pushl %ebx # is saved as we use that in ret_sys_call
pushl %eax
movl $0x10,%eax # ds,es指向的是内核的数据段
mov %ax,%ds
mov %ax,%es
movl $0x17,%eax # fs指向的是局部的数据段,出错程序的
# 的数据段
mov %ax,%fs
incl _jiffies # 增加jiffies的值
# 由于初始化中断控制芯片时没有采用自动eoi,所以这里发送
# 指令结束硬件的中断。
movb $0x20,%al # EOI to interrupt controller #1
outb %al,$0x20 # 操作命令字ocw2送到0x20端口
# 下面的3条语句从选择符中取出当前特权级0或者是3,并压入、
# 栈中,作为do_timer的参数
movl CS(%esp),%eax
andl $3,%eax # %eax is CPL (0 or 3, 0=supervisor)
pushl %eax
# do_timer执行任务的切换,计时等工作
call _do_timer # 'do_timer(long CPL)' does everything from
addl $4,%esp # task switching to accounting ...
jmp ret_from_sys_call
# 下面是系统调用sys_execve()函数ude调用过程。首先出去中断
# 调用程序代码指针作为参数传递给c函数do_execve,然后调用
# 函数do_execve
.align 2
_sys_execve:
lea EIP(%esp),%eax
pushl %eax
call _do_execve
addl $4,%esp
ret
# sys_fork系统调用,其主要的作用是创建子进程。
# 下面的代码首先调用c函数find_empty_process,
# 取得进程号pid,若果返回的是负值的话,说明
# 当前任务数组已满。然后调用copy_process复制
# 进程
.align 2
_sys_fork:
call _find_empty_process # 调用函数find_empty_process
testl %eax,%eax #测试函数的返回值否为负值?
js 1f # 为负值,跳转到ret指令
# 否则继续执行
push %gs
pushl %esi
pushl %edi
pushl %ebp
pushl %eax
call _copy_process # 复制进程
addl $20,%esp
1: ret
# int46 -- 硬盘中断处理程序。
# 首先向8259a中断控制从芯片发送结束硬件中断指令eoi,
# 然后取出变量do_hd中函数指针放入edx中,并置do_hd
# 的值为空,接着判断的是edx函数指针是否为空。如果
# 为空的话,则将edx指向unexcept_hd_interrupt,用于
# 显示错误信息。然后向8259a主芯片发送eoi指令,并
# 调用edx函数指向的函数:read_intr(),write_intr()
# unexcept_hd_interrup
_hd_interrupt:
# 保护寄存器
pushl %eax
pushl %ecx
pushl %edx
push %ds
push %es
push %fs
movl $0x10,%eax # ds,es指向的是内核的数据段
mov %ax,%ds
mov %ax,%es
movl $0x17,%eax # fs指向的调用程序的局部数据段
mov %ax,%fs
# 想8259a从设备发送结束硬件中断指令
movb $0x20,%al
outb %al,$0xA0 # EOI to interrupt controller #1
# 只为延时
jmp 1f # give port chance to breathe
1: jmp 1f
1: xorl %edx,%edx # edx为0
xchgl _do_hd,%edx# edx指向的是do_hd的指针,do_hd指向的是
# 原来edx的值,即是null
testl %edx,%edx # edx是否为空?
jne 1f
movl $_unexpected_hd_interrupt,%edx # 如果edx指向的值为空,则将edx指向该函数
# 向8259a主芯片发送“结束硬件中断”指令
1: outb %al,$0x20
# 调用函数,或者是read_intr(),write_intr()或是unexcept_hd_interrup
call *%edx # "interesting" way of handling intr.
pop %fs
pop %es
pop %ds
popl %edx
popl %ecx
popl %eax
iret
# int38 -- 软盘驱动中断处理程序
# 软盘中断处理程序和硬盘中断处理程序大致相同。
_floppy_interrupt:
pushl %eax
pushl %ecx
pushl %edx
push %ds
push %es
push %fs
movl $0x10,%eax
mov %ax,%ds
mov %ax,%es
movl $0x17,%eax
mov %ax,%fs
movb $0x20,%al
outb %al,$0x20 # EOI to interrupt controller #1
xorl %eax,%eax
xchgl _do_floppy,%eax
testl %eax,%eax
jne 1f
movl $_unexpected_floppy_interrupt,%eax
1: call *%eax # "interesting" way of handling intr.
pop %fs
pop %es
pop %ds
popl %edx
popl %ecx
popl %eax
iret
# int39 -- 并口中断处理程序
# 本程序还未实现,这里只是发送eoi指令
_parallel_interrupt:
pushl %eax
movb $0x20,%al
outb %al,$0x20
popl %eax
iret