system_call.s
汇编代码是 Linux 内核的一部分,负责处理系统调用、定时器中断、硬盘中断、软盘中断和并行端口中断。下面是各个部分的详细说明:
系统调用处理 (_system_call)
功能
- 处理系统调用请求。
- 根据传入的系统调用编号 (
%eax
) 调用相应的系统调用处理函数。 - 在系统调用前后执行必要的上下文切换和信号处理。
步骤
- 检查系统调用编号:比较
%eax
的值与nr_system_calls-1
,如果超过最大系统调用编号,则跳转到bad_sys_call
返回-1
。 - 保存寄存器:为了防止被后续指令覆盖,将
%ds, %es, %fs
寄存器的值压入栈。 - 设置数据段寄存器:将
%ds
和%es
设置为内核数据段地址0x10
,将%fs
设置为内核数据段地址0x17
。 - 调用系统调用处理函数:根据
%eax
中的系统调用编号,调用_sys_call_table
中对应的处理函数。 - 检查任务状态:
- 获取当前任务的状态。
- 如果状态不为
0
,则跳转到reschedule
进行任务调度。 - 如果计数器为
0
,则同样跳转到reschedule
。
- 恢复上下文并返回:在返回前,恢复之前保存的寄存器值,并通过
iret
返回到调用者。
定时器中断处理 (_timer_interrupt
)
功能
- 处理定时器中断。
- 更新系统时间 (_jiffies)。
- 执行定时器相关的任务,如任务调度、资源统计等。
步骤
- 保存寄存器:保存
%ds, %es, %fs
寄存器的值。 - 设置数据段寄存器:将
%ds, %es, %fs
设置为内核数据段地址。 - 更新系统时间:增加
_jiffies
的值。 - 发送中断结束信号:向主中断控制器发送结束中断信号。
- 获取调用者权限级别:获取
%eax
中的当前代码段寄存器的低两位作为调用者权限级别 (CPL)
。 - 调用定时器处理函数:调用
_do_timer
函数,该函数根据CPL
值执行不同的操作。 - 返回:跳转到
ret_from_sys_call
函数返回到调用者。
硬盘中断处理 (_hd_interrupt
)
功能
- 处理硬盘中断。
- 发送中断结束信号。
- 调用硬盘中断处理函数。
步骤
- 保存寄存器:保存
%eax, %ecx, %edx, %ds, %es, %fs
寄存器的值。 - 设置数据段寄存器:将
%ds, %es, %fs
设置为内核数据段地址。 - 发送中断结束信号:向主中断控制器发送结束中断信号。
- 调用硬盘中断处理函数:根据
_do_hd
的值决定调用哪个函数处理硬盘中断。 - 恢复寄存器并返回:恢复之前保存的寄存器值,并通过
iret
返回到调用者。
软盘中断处理 (_floppy_interrupt
)
功能
- 处理软盘中断。
- 发送中断结束信号。
- 调用软盘中断处理函数。
步骤
- 保存寄存器:保存
%eax, %ecx, %edx, %ds, %es, %fs
寄存器的值。 - 设置数据段寄存器:将
%ds, %es, %fs
设置为内核数据段地址。 - 发送中断结束信号:向主中断控制器发送结束中断信号。
- 调用软盘中断处理函数:根据
_do_floppy
的值决定调用哪个函数处理软盘中断。 - 恢复寄存器并返回:恢复之前保存的寄存器值,并通过
iret
返回到调用者。
其他中断处理
设备不可用处理 (_device_not_available
)
- 处理设备不可用的情况。
- 设置内核数据段寄存器。
- 调用 _math_emulate 函数处理数学运算异常。
协处理器错误处理 (_coprocessor_error
)
- 处理协处理器错误。
- 设置内核数据段寄存器。
- 调用 _math_error 函数处理数学运算错误。
错误处理
未实现的系统调用处理 (bad_sys_call
)
- 处理未实现的系统调用。
- 将
-1
放入%eax
并通过iret
返回。
结论
这些汇编代码片段是 Linux 内核的核心组成部分,负责处理各种类型的中断和系统调用。它们通过精确控制寄存器和内存访问来确保系统的稳定性和性能。
system_call.s 源码
/*
* 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
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
/*
* 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
bad_sys_call:
movl $-1,%eax
iret
.align 2
reschedule:
pushl $ret_from_sys_call
jmp _schedule
.align 2
_system_call:
cmpl $nr_system_calls-1,%eax
ja bad_sys_call
push %ds
push %es
push %fs
pushl %edx
pushl %ecx # push %ebx,%ecx,%edx as parameters
pushl %ebx # to the system call
movl $0x10,%edx # set up ds,es to kernel space
mov %dx,%ds
mov %dx,%es
movl $0x17,%edx # fs points to local data space
mov %dx,%fs
call _sys_call_table(,%eax,4)
pushl %eax
movl _current,%eax
cmpl $0,state(%eax) # state
jne reschedule
cmpl $0,counter(%eax) # counter
je reschedule
ret_from_sys_call:
movl _current,%eax # task[0] cannot have signals
cmpl _task,%eax
je 3f
cmpw $0x0f,CS(%esp) # was old code segment supervisor ?
jne 3f
cmpw $0x17,OLDSS(%esp) # was stack segment = 0x17 ?
jne 3f
movl signal(%eax),%ebx
movl blocked(%eax),%ecx
notl %ecx
andl %ebx,%ecx
bsfl %ecx,%ecx
je 3f
btrl %ecx,%ebx
movl %ebx,signal(%eax)
incl %ecx
pushl %ecx
call _do_signal
popl %eax
3: popl %eax
popl %ebx
popl %ecx
popl %edx
pop %fs
pop %es
pop %ds
iret
.align 2
_coprocessor_error:
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
jmp _math_error
.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
clts # clear TS so that we can use math
movl %cr0,%eax
testl $0x4,%eax # EM (math emulation bit)
je _math_state_restore
pushl %ebp
pushl %esi
pushl %edi
call _math_emulate
popl %edi
popl %esi
popl %ebp
ret
.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
mov %ax,%ds
mov %ax,%es
movl $0x17,%eax
mov %ax,%fs
incl _jiffies
movb $0x20,%al # EOI to interrupt controller #1
outb %al,$0x20
movl CS(%esp),%eax
andl $3,%eax # %eax is CPL (0 or 3, 0=supervisor)
pushl %eax
call _do_timer # 'do_timer(long CPL)' does everything from
addl $4,%esp # task switching to accounting ...
jmp ret_from_sys_call
.align 2
_sys_execve:
lea EIP(%esp),%eax
pushl %eax
call _do_execve
addl $4,%esp
ret
.align 2
_sys_fork:
call _find_empty_process
testl %eax,%eax
js 1f
push %gs
pushl %esi
pushl %edi
pushl %ebp
pushl %eax
call _copy_process
addl $20,%esp
1: ret
_hd_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,$0xA0 # EOI to interrupt controller #1
jmp 1f # give port chance to breathe
1: jmp 1f
1: xorl %edx,%edx
xchgl _do_hd,%edx
testl %edx,%edx
jne 1f
movl $_unexpected_hd_interrupt,%edx
1: outb %al,$0x20
call *%edx # "interesting" way of handling intr.
pop %fs
pop %es
pop %ds
popl %edx
popl %ecx
popl %eax
iret
_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
_parallel_interrupt:
pushl %eax
movb $0x20,%al
outb %al,$0x20
popl %eax
iret