linux 0.11 内核学习 -- system_call.s,系统调用仅是如此。

来源:互联网 发布:itunes windows xp版 编辑:程序博客网 时间:2024/06/06 02:50

/*

 * 本程序主要是实现系统调用中断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

参考《linux内核完全注释》和网上相关文章

原创粉丝点击