中断,异常,系统调用

时间:2022-10-31 14:45:44

中断由外部设备产生,异常由CPU内部产生(异常包括错误Fault, 陷阱Trap即故意出错,和终止Abort)。中断要找到中断服务程序所以需要IDT这个大数组来存放中断门(中断门就是一种段描述符,用于找到中断程序入口地址)。CPU为了方便找到IDT加了一个寄存器IDTR。
内核的进入与退出:
系统调用:应用程序主动向操作系统发出服务请求

// System call numbers
#define SYS_fork 1
#define SYS_exit 2
#define SYS_wait 3
#define SYS_pipe 4
#define SYS_write 5
#define SYS_read 6
#define SYS_close 7
#define SYS_kill 8
#define SYS_exec 9
#define SYS_open 10
#define SYS_mknod 11
#define SYS_unlink 12
#define SYS_fstat 13
#define SYS_link 14
#define SYS_mkdir 15
#define SYS_chdir 16
#define SYS_dup 17
#define SYS_getpid 18
#define SYS_sbrk 19
#define SYS_sleep 20
#define SYS_procmem 21
(红色标注文件复制应用的系统调用)

ucore系统调用read(fd, buffer, length)的实现
1. kern/trap/trapentry.S: alltraps()
2. kern/trap/trap.c: trap()
tf->trapno == T_SYSCALL
3. kern/syscall/syscall.c: syscall()
tf->tf_regs.reg_eax ==SYS_read
4. kern/syscall/syscall.c: sys_read()
从 tf->sp 获取 fd, buf, length
5. kern/fs/sysfile.c: sysfile_read()
读取文件
6. kern/trap/trapentry.S: trapret()
Processor ->调用 syscallN() -> 调用int $0x80 –>System_call ->调用实际服务程序
系统调用的执行通过中断或者异常的方式进行的,他将执行相应的机器代码指令,来产生中断或者异常信号,产生中断或者异常的重要效果是系统自动将用户3模式切换为核心模式,来安排异常处理程序的执行。
setup_IDT() 是在页机制刚起作用的时候被调用的,这时候,kernel刚被移到0xC00000000的地方。
异常(exception):非法指令或者其他原因导致当前指令执行失败(如:内存出错)后的处理请求
中断(hardware interrupt):来自硬件设备的处理请求
中断、异常和系统调用比较:
响应方式:中断:异步; 异常:同步;系统调用:同步或异步。
处理机制:中断:持续,对用户应用程序是透明的;异步:杀死或者重新执行意想不到的应用程序指令;系统调用:等待和持续。

中断处理机制:
硬件处理:在CPU初始化时设置中断使能标志(1)依据内部或者外部事件设置中断标志 (2)依据中断向量调用响应中断服务例程。

软件处理:
(1)现场保存(编译器)
(2)中断服务处理(服务例程)
(3)清除中断标记(服务例程)
(4)现场恢复(编译器)
函数调用和系统调用的不同处
系统调用:INT和IRET指令用于系统调用;系统调用时,堆栈切换和 特权级的转换
函数调用:CALL和RET用于常规调用;常规调用时没有堆栈切换

操作系统的内存管理:
抽象:逻辑地址空间
保护:独立地址空间
共享: 访问相同内存
虚拟化: 更大的地址空间

实模式:使用两个16位寄存器来寻址20位,一个16位寄存器用来存放段地址,一个16位寄存器用来存放段偏移。段可以是在1M(1M的地址是00000H~0FFFFFH)内任意以16为倍数的段地址开始,接着最大可达64K的界限。一个段可能开始于实内存中所有1M字节里的任意一个16字节处,所以0001:0019等于0000:0029, 也等于0002:0009
16位实模式:段基址为16的整数倍(对于1M空间的20位寻地址采用两个16位寄存器,将其中一个左偏移4-bit加另一个寄存器构成16位,所以段基址必须是16的整数倍),最大偏移(Limit)为64KB。

32位保护模式:段基址为32位所表示的任何值,Limit可以被设为32-bit所能表示的以2^12=4K为倍数的任何值)在保护模式下各进程对段的描述包括3方面(段基址,段限长,特权级),它们被放在一个64-bit的数据结构中,被称为段描述符,这需要64位段寄存器去访问它,但没有64位段寄存器(为兼容仍然。于是将64-bit放在一个数组中,而将段寄存器的值作为下标来引用,这个数组便是GDT。
在保护模式下除了CS是操作系统自动设置的,其他的段寄存器可以使用(movl $0x10, %eax && mov %ax, %ds)设置,这里的0x10的分析和jmpi指令里的8的含义相同。

寻址:
现代意义上的操作系统都处于32位保护模式下。每个进程一般都能寻址4G的物理空间。但是我们的物理内存一般都是几百M,进程怎么能获得4G的物理空间呢?这就是使用了虚拟地址的好处,通常我们使用一种叫做虚拟内存的技术来实现,因为可以使用硬盘中的一部分来当作内存使用。例外一点现在操作系统都划分为系统空间和用户空间,使用虚拟地址可以很好的保护内核空间被用户空间破坏。
虚拟地址如何转为物理地址,这个转换过程有操作系统和CPU共同完成. 操作系统为CPU设置好页表。CPU通过MMU单元进行地址转换。
内核在进入保护模式前, 还没有启用分页功能, 在这之前内核要先建立一个临时内核页表,因为在进入保护模式后, 内核继续初始化直到建立完整的内存映射机制之前, 仍然需要用到页表来映射相应的内存地址。 临时页表的初始化是在rch/i386/kernel/head.S中进行的:
swapper_pg_dir是临时页全局目录表, 它是在内核编译过程中静态初始化的.
pg0是第一个页表开始的地方, 它也是内核编译过程中静态初始化的.
内核通过以下代码建立临时页表:
ENTRY(startup_32)
…………
/* 得到开始目录项的索引,从这可以看出内核是在swapper_pg_dir的768个表项开始进行建立的, 其对应的线性地址就是0xc0000000以上的地
址, 也就是内核在初始化它自己的页表 */
page_pde_offset = (__PAGE_OFFSET >> 20);
/* pg0地址在内核编译的时候, 已经是加上0xc0000000了, 减去0xc00000000得到对应的物理地址 */。
页式映射分析:页式映射的过程了, Linux系统中的每个进程都有其自身的页面目录PGD, 指向这个目录的指针保存在每个进程的mm_struct数据结构中。 每当调度一个进程进入运行的时候,内核都要为即将运行的进程设置好控制寄存器cr3, 而MMU的硬件则总是从cr3中取得指向当前页面目录的指针。

在响应中断或者处理异常时,80386根据中断向量号转对应的处理程序。但是,在保护模式下,80386不使用实模式下的中断向量表,而是使用中断描述符表IDT。象全局描述符表GDT一样,在整个系统中,中断描述符表IDT只有一个。中断描述符表寄存器IDTR指示IDT在内存中的位置。由于80386只识别256个中断向量号,所以IDT最大长度是2K。

DRUB 通用的bootloader,多系统加载OS
中断,异常,系统调用:异常控制流
中断:使得OS得到控制权,进一步控制。
异常:
系统调用: