您的位置:首页 > 运维架构 > Linux

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

2010-01-28 21:53 806 查看
/*

* 本程序主要是实现系统调用中断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内核完全注释》和网上相关文章
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: