操作系统是如何运行的?
硬件中断
在我们使用键盘的时候,操作系统要怎么知道键盘上有数据了呢?硬件中断!
硬件中断过程如图所示:
按照图中所示,外设直接与CPU进行交互,但是之前对于冯诺依曼体系架构的学习可知,外设要和CPU交互必须要通过内存,那么怎么做到的呢?
有两个颜色的信号交互线路,分别是控制信号和数据信号。之前的描述中主要是对于数据的拷贝线路进行的学习,实际上还有一个控制信号的线路,只要进行传输控制信号即可,无需拷贝数据,所以外设可以和CPU进行交互。主要是传输信息!
外设和中央处理器之间如何连接通信呢?
上图为CPU。会有很多针脚,针脚与主板相连,外设也会和主板连接。CPU的针脚有相当一部分是用来与外设交互的。
再了解一下寄存器的概念:
当我们向磁盘发送数据,想在磁盘进行存储,假如指令in 100(扇区编号) XXXX(数据)
。
磁盘要怎么往扇区存储呢?
实际上磁盘有磁盘控制器,里面会有各个不同功能的寄存器!
拓展一下,外设都有自己的控制器!
继续理解硬件中断的过程图示:
- **外设就绪:**硬件交互的信号也就是高低电平,外设准备就绪。
- **发起中断:**通过电平变化,在中断控制器产生中断,并且产生中断号(外设特有的编号)
- **通知CPU:**结合上述关于控制信号发送和寄存器的概念,通知CPU有中断产生
- **CPU得知中断,获取中断号:**CPU收到通知,但是要处理中断需要得到中断号。CPU在在中断控制器得到中断号,现在CPU就知道哪一个中断号准备好了。
- **根据中断号,执行中断处理方法:**现在已经得到哪一个设备中断了,需要具体的处理方法。操作系统提供了中断向量表,就是一个函数指针数组,中断号对应的就是不同下标,每个中断号有着不同的函数方法。根据中断号进行查找方法,然后执行。(中断向量表是操作系统的一部分,操作系统启动后记忆加载到内存中了)
- 中断完毕,继续之前的工作。
这一套过程可以得到:
- 操作系统不会关注外设是否准备好,而是外设准备好之后通知操作系统。
- 过程很熟悉,与信号的过程很像,发信号,信号编号,保存信号,处理信号
内核代码:
// Linux内核0.11源码void trap_init(void)
{int i;set_trap_gate(0, &_error); // 设置除操作出错的中断向量值。以下雷同。set_trap_gate(1, &debug);set_trap_gate(2, &nmi);set_system_gate(3, &int3); /* int3-5 can be called from all */set_system_gate(4, &overflow);set_system_gate(5, &bounds);set_trap_gate(6, &invalid_op);set_trap_gate(7, &device_not_available);set_trap_gate(8, &double_fault);set_trap_gate(9, &coprocessor_segment_overrun);set_trap_gate(10, &invalid_TSS);set_trap_gate(11, &segment_not_present);set_trap_gate(12, &stack_segment);set_trap_gate(13, &general_protection);set_trap_gate(14, &page_fault);set_trap_gate(15, &reserved);set_trap_gate(16, &coprocessor_error);// 下面将 int 17-47 的陷阱门先均设置为 reserved,以后每个硬件初始化时会重新设置自己的陷阱门。for (i = 17; i < 48; i++)set_trap_gate(i, &reserved);set_trap_gate(45, &irq13); // 设置协处理器的陷阱门。outb_p(inb_p(0x21) & 0xfb, 0x21); // 允许主 8259A 芯片的 IRQ2 中断请求。outb(inb_p(0xA1) & 0xdf, 0xA1); // 允许从 8259A 芯片的 IRQ13 中断请求。set_trap_gate(39, ¶llel_interrupt); // 设置并行口的陷阱门。
}void rs_init(void)
{set_intr_gate(0x24, &rs1_interrupt); // 设置串口 1 的中断门向量 (硬件 IRQ4 信号)。set_intr_gate(0x23, &rs2_interrupt); // 设置串口 2 的中断门向量 (硬件 IRQ3 信号)。init(tty_table[1].read_q.data); // 初始化串口 1 (.data 是端口号)。init(tty_table[2].read_q.data); // 初始化串口 2。outb(inb_p(0x21) & 0xE7, 0x21); // 允许主 8259A 芯片的 IRQ3,IRQ4 中断信号请求。
}
时钟中断
- 在没有中断的时候,操作系统在做什么?
- 进程可以在操作系统的指挥下,被调度,被执行,那么操作系统自己被谁指挥,被谁推动执行呢?
- 外部设备可以触发硬件中断,但是这个是需要用户或者设备自己触发,有没有自己可以定期触发的设备?
在没有中断的时候,操作系统在做什么?
时钟中断怎么进行
- 运行 Idle 进程
- 当所有可运行的用户/内核进程都已运行完时间片或被阻塞,没有其他事情可做时,CPU 会进入所谓的 “idle”(空闲)进程。
- 在 x86 上,idle 里常见写法大致是:
for (;;) {asm("hlt"); // 让 CPU 进入低功耗等待状态,直到下一个中断到来
}
或者:
for (;;) {pause(); // 类似 hlt,也会在硬件中断到来前挂起
}
- **目的**<font style="color:rgb(31,35,41);">:减少功耗,不浪费 CPU 周期在“忙等”上。</font>
void main(void) /* 这里确实是 void,并没有错。 */
{/* 在 startup 程序(head.s)中就是这样假设的。 */// 进入无限循环,等待调度器决定是否需要执行其他任务for (;;)pause(); // 'pause()' 等待一个信号的到来(例如,任务切换或者硬件中断)/* 注意!! 对于任何其它的任务,'pause()' 将意味着我们必须等待收到一个信号才会返回就绪运行态。然而,任务0(task0)是唯一的例外情况(参见 'schedule()')。因为任务0在任何空闲时间都会被激活(当没有其它任务在运行时)。对于任务0,'pause()' 仅意味着我们返回来查看是否有其它任务可以运行,如果没有的话,我们就回到这里,继续循环执行 'pause()'。*/
} // end main
- 后台守护与内核线程
- 一些内核线程(如 kswapd、ksmd、kworker 等)会被唤醒去做后台清理、内存回收、延迟任务执行等。
- 但如果它们也都阻塞起来,则真正就只剩下 idle 持续运行。
当没有任何外部中断(时钟、I/O、系统调用陷阱等)时,CPU 就一直跑到图最右侧的 <font style="color:rgb(31,35,41);">for(;;) pause()</font>
,等待下一个“蓝色箭头”打来的时钟中断。
- 时钟源
- 外部设备一般包括时钟源,时钟源会以特定的频率,向CPU发送特定的中断。
- 一旦定时器中断到来,CPU 自动:
- 保存现场(用寄存器保存当前进程状态)
- 切换到内核栈
- 根据中断号查 IDT(Interrupt Descriptor Table)
- 跳转到对应的中断服务例程
- IDT就是中断向量表,存放不同中断信号所对应的服务例程方法
- 当中断后就会跳转到中断服务例程中的进程调度,专门用来为时钟中断服务,保证操作系统的持续运行,持续进行进程调度等一系列操作系统的基础操作。也就是在空闲的时候去以特定的频率在全局数据结构中扫一遍,查看有没有需要处理的事情
- 也可以理解,操作系统就是:基于中断进行工作的软件。
时钟源以恒定频率发中断(比如 1 ns 一次),这个就是CPU的主频。每个进程被分配一个固定的“滴答数”作时间片(比如 10 个滴答 = 10 ns),每次中断既减少当前进程的剩余时间片,也把全局计数器
total
(jiffies)加一,这样既能实现进程的公平调度,又能即使离线也可以精确地统计系统运行时间。
时钟中断和进程调度核心流程概述
- 硬件定时器中断的注册:
- 内核在启动时设置好与时钟中断相关的处理程序。通过
set_intr_gate
将定时器中断(IRQ0)与处理函数(timer_interrupt
)关联。这相当于告诉内核,当硬件定时器发出中断信号时,应该跳转到哪个函数进行处理。
- 内核在启动时设置好与时钟中断相关的处理程序。通过
- 中断处理入口:
- 每次硬件定时器触发时,CPU 会进入中断处理程序。中断向量表将控制权传递给
timer_interrupt
入口,CPU 会保存现场,允许处理函数执行。此时的中断处理并不直接切换到其他任务,而是先通过汇编指令跳转到 C 语言的do_timer
函数。
- 每次硬件定时器触发时,CPU 会进入中断处理程序。中断向量表将控制权传递给
- 时间片管理:
void do_timer(void) {/* 更新全局时钟节拍 */total++; // jiffies++,记录自开机以来的中断总次数// 让“脱机”(OS 未运行)时的时间也能被累计/* 对当前进程的时间片计数 */if (--current->counter > 0)return; // 进程的时间片还没用完,直接退出中断/* 时间片耗尽,进行进程调度 */current->counter = DEFAULT_TIMESLICE; // 重置下次时间片(图中用 struct task_struct.count)schedule(); // 进行真正的上下文切换
}
- 在 `do_timer` 函数中,内核会检查当前进程的剩余时间片(`current->counter`)。如果该进程的时间片还没用完,就直接返回继续执行当前进程。
- 如果时间片已经耗尽,内核会为进程重置时间片,并调用 `schedule()` 来触发进程调度。通过调度器选择下一个准备好的进程执行。
- 调度与上下文切换:
<font style="color:rgb(31,35,41);">schedule()</font>
会遍历就绪队列,挑选优先级最高或公平调度的下一个进程。<font style="color:rgb(31,35,41);">switch_to()</font>
保存当前进程的寄存器/栈指针,恢复下一个进程的上下文。- 上下文切换完成后,执行
<font style="color:rgb(31,35,41);">iret</font>
,回到新进程的用户态或内核态继续运行。
- 循环与响应:
- 该过程会不断循环进行,确保系统在执行多个进程时能合理地分配 CPU 时间。如果没有其他进程需要执行,CPU 将进入 idle 进程,并继续等待下一个时钟中断。
操作系统自己被谁指挥、被谁推动执行?
- “事件驱动”模型
- Linux 内核并不是一个自扫描的“大循环”,而是被各种“事件”唤醒执行。这些事件主要分三类:
- 硬件中断(timer、网卡、键盘、磁盘等)
- 异常/陷阱(系统调用
<font style="color:rgb(31,35,41);">int 0x80</font>
/<font style="color:rgb(31,35,41);">syscall</font>
、页错误、非法指令等) - 软中断/底半部(
<font style="color:rgb(31,35,41);">raise_softirq()</font>
、<font style="color:rgb(31,35,41);">tasklet</font>
、<font style="color:rgb(31,35,41);">workqueue</font>
等)
- Linux 内核并不是一个自扫描的“大循环”,而是被各种“事件”唤醒执行。这些事件主要分三类:
- 具体流程
- 进程态 → 内核态:当用户进程执行到
<font style="color:rgb(31,35,41);">syscall</font>
指令,CPU 会做一次“软中断”,转到内核的系统调用入口。 - 中断处理:当硬件发中断时,CPU 保存现场后,跳到对应的中断服务例程(IDT 中的相应中断门)。
- 中断/系统调用处理完毕,通过
<font style="color:rgb(31,35,41);">iret</font>
或<font style="color:rgb(31,35,41);">sysret</font>
返回到原来被抢占或陷入的进程。
- 进程态 → 内核态:当用户进程执行到
- 总结:
操作系统“自己”并没有一个独立的“调度者”,而是被外部与内部的“事件”驱动——每来了一个中断或陷阱,就把控制权交给内核。
有没有自己可以定期触发的设备?
- 定时器芯片(PIT、HPET、APIC timer)
- Linux 在启动时会编程硬件定时器,让它们以固定频率(HZ,通常 100、250 或 1000Hz)自动产生中断。
- 例如:
- PIT(Programmable Interval Timer)
- IO-APIC/LAPIC Timer(本地/APIC 定时器)
- HPET(High Precision Event Timer)
- 无需人为或 I/O 触发,它们自己“滴答”——并把 IRQ0(或 APIC 定时中断)发给中断控制器。
- RTC(Real-Time Clock)周期中断
- 另一种定期中断来源是实时时钟芯片(CMOS RTC),可以配置为每秒或每几分之一秒产生一次 IRQ8。
- Linux 也可以利用 RTC 来做秒级或更低频率的周期唤醒
这样操作系统就可以在硬件时钟的推动下进行自动调度了~
软中断
想象一下,你正在编写一个简单的程序,比如读取一个文件并打印其内容。这个看似简单的操作,背后却隐藏着操作系统内核的复杂工作。你的程序运行在用户空间 (User Space),一个相对受限的环境;而文件系统、硬件设备等核心资源则由内核空间 (Kernel Space) 掌控,拥有最高权限。那么,用户程序如何安全、可控地请求内核来完成这些特权操作呢?答案就是通过系统调用 (System Call),而系统调用的实现,很大程度上就依赖于我们今天要讲的软中断。
中断:CPU的“请注意”信号
在深入软中断之前,我们先快速回顾一下什么是“中断”。你可以把中断想象成一种信号,它会打断 CPU 当前正在执行的任务,要求 CPU 立即关注并处理一个更紧急或特殊的事件。
中断主要分为两类:
- 硬件中断 (Hardware Interrupt): 由外部硬件设备(如键盘敲击、鼠标移动、网卡收到数据包、硬盘完成读写)产生。这些事件是异步的,发生时间不可预测。
- 软中断 (Software Interrupt): 由 CPU 内部执行的软件指令触发。它们是同步的,发生时间点就在指令执行的那一刻。
上文已经讲解了关于硬件中断的相关知识点,我们今天要聚焦的就是第二种——软中断。
初步了解软中断
CPU 设计了对应的汇编指令 (
int
或者syscall
), 可以让 CPU 内部触发中断逻辑。
没错,软中断的核心就是 CPU 提供了一些特殊的指令,允许正在运行的程序主动“中断”自己,将控制权交给预先定义好的处理程序(通常是操作系统内核的一部分)。
- 在经典的 x86 架构上,
INT n
指令就是用来触发软中断的,其中n
是一个中断号(0-255)。Linux 早期广泛使用INT 0x80
作为系统调用的入口。 - 随着 CPU 发展,为了提高效率,引入了更快的专用指令,如
SYSENTER
(配合SYSEXIT
) 和SYSCALL
(配合SYSRET
)。
无论使用哪种指令,效果都是类似的:暂停当前的用户程序,切换到更高权限的内核模式,并跳转到指定的中断处理程序。
系统调用
软中断最广为人知的应用场景就是实现系统调用。让我们跟随一次典型的系统调用(比如 read
文件),来一场“从用户空间到内核再返回”的深度游:
第一站:用户空间 - 请求发起
- 应用程序员视角: 你在代码里调用了一个库函数,比如 C 语言的
read(fd, buffer, count);
。 - C库 (glibc) 视角: 你调用的
read()
函数并非直接操作硬件。它是一个封装层。它的主要工作是:- 确定
read
操作对应的系统调用号(这是一个事先约定好的数字,例如,在 x86-64 Linux 中,read
的号是 0)。 - 下图为调用系统调用是系统调用号对应的函数指针表:
- 确定
/* 系统调用函数指针表,用于系统调用中断处理程序 (int 0x80) 作为跳转表 */
static fn_ptr sys_call_table[] = {/* 0 - 9 */sys_setup, /* 0 */sys_exit, /* 1 */sys_fork, /* 2 */sys_read, /* 3 */sys_write, /* 4 */sys_open, /* 5 */sys_close, /* 6 */sys_waitpid, /* 7 */sys_creat, /* 8 */sys_link, /* 9 *//* 10 - 19 */sys_unlink, /* 10 */sys_execve, /* 11 */sys_chdir, /* 12 */sys_time, /* 13 */sys_mknod, /* 14 */sys_chmod, /* 15 */sys_chown, /* 16 */sys_break, /* 17 */sys_stat, /* 18 */sys_lseek, /* 19 *//* 20 - 29 */sys_getpid, /* 20 */sys_mount, /* 21 */sys_umount, /* 22 */sys_setuid, /* 23 */sys_getuid, /* 24 */sys_stime, /* 25 */sys_ptrace, /* 26 */sys_alarm, /* 27 */sys_fstat, /* 28 */sys_pause, /* 29 *//* 30 - 39 */sys_utime, /* 30 */sys_stty, /* 31 */sys_gtty, /* 32 */sys_access, /* 33 */sys_nice, /* 34 */sys_ftime, /* 35 */sys_sync, /* 36 */sys_kill, /* 37 */sys_rename, /* 38 */sys_mkdir, /* 39 *//* 40 - 49 */sys_rmdir, /* 40 */sys_dup, /* 41 */sys_pipe, /* 42 */sys_times, /* 43 */sys_prof, /* 44 */sys_brk, /* 45 */sys_setgid, /* 46 */sys_getgid, /* 47 */sys_signal, /* 48 */sys_geteuid, /* 49 *//* 50 - 59 */sys_getegid, /* 50 */sys_acct, /* 51 */sys_phys, /* 52 */sys_lock, /* 53 */sys_ioctl, /* 54 */sys_fcntl, /* 55 */sys_mpx, /* 56 */sys_setpgid, /* 57 */sys_ulimit, /* 58 */sys_uname, /* 59 *//* 60 - 69 */sys_umask, /* 60 */sys_chroot, /* 61 */sys_ustat, /* 62 */sys_dup2, /* 63 */sys_getppid, /* 64 */sys_getpgrp, /* 65 */sys_setsid, /* 66 */sys_sigaction,/* 67 */sys_sgetmask, /* 68 */sys_ssetmask, /* 69 *//* 70 - 77 */sys_setreuid, /* 70 */sys_setregid /* 71 *//* 如果有更多 syscall,请在此继续添加并更新区间注释 */
};
- 将这个系统调用号放入指定的寄存器(通常是 `EAX` 或 `RAX`)。
- 将函数的参数(文件描述符 `fd`、缓冲区 `buffer` 的地址、要读取的字节数 `count`)按照 **ABI (Application Binary Interface)** 的约定,放入其他指定的寄存器(如 `RDI`, `RSI`, `RDX` 等)或压入堆栈。
- 执行触发软中断的指令,比如 `syscall`。
第二站:模式切换 - “陷阱”之门
系统调用的过程,其实就是先使用
int 0x80
、syscall
陷入 (Trap) 内核,本质就是触发软中断…
- 当 CPU 执行到
syscall
(或INT 0x80
) 指令时,奇妙的事情发生了:- 权限提升: CPU 的运行模式从用户模式 (Ring 3) 切换到内核模式 (Ring 0)(后续总结讲解用户态和内核态)。
- 状态保存: CPU 自动保存当前用户程序的一些关键状态,至少包括:下一条指令的地址 (Instruction Pointer, 如 RIP/EIP)、代码段寄存器 (CS)、标志寄存器 (RFLAGS/EFLAGS)。用户堆栈指针 (RSP/ESP) 和段寄存器 (SS) 通常也会被保存(或切换到内核堆栈时隐式保存)。
- 寻找处理程序: CPU 使用
syscall
指令(或INT 0x80
的中断号 0x80)作为索引,去查询一个特殊的数据结构——中断描述符表 (IDT - Interrupt Descriptor Table)。IDT 中存储了每个中断号对应的处理程序的入口地址和所需权限等信息。 - 跳转执行: CPU 加载 IDT 中找到的内核态代码段和指令指针,跳转到内核的系统调用入口处理程序 (System Call Entry Handler)。
(你可以想象图示中,有一条从用户态执行 int 0x80/syscall
指令,穿过用户态/内核态边界,指向 IDT,再由 IDT 指向内核中特定处理代码的路径。)
第三站:内核空间 - 请求处理
- 系统调用分发器 (Dispatcher): 内核的入口处理程序(如汇编代码
_system_call
)接管控制权。它的任务是:- 保存更完整的用户上下文:将用户态的通用寄存器(如 RBX, RCX, RDX, RDI, RSI, RBP 等)压入内核堆栈。
- 从
RAX
(或EAX
) 寄存器中读取之前 C 库放入的系统调用号。 - 查表: 使用这个系统调用号作为下标,在内核维护的系统调用表 (
sys_call_table
) 中查找。操作系统不会提供任何系统调用接口,只提供系统调用号。
• 系统调用号的本质:数组下标!
•fn_ptr sys_call_table[] = { sys_setup, sys_exit, sys_fork, sys_read, ... };
•call [_sys_call_table + eax * 4]
(或call sys_call_table[,%rax,8]
在 64 位)- 上文已经提到过。
这个表里存放的是指向具体内核实现函数(如 sys_read
, sys_write
, sys_open
等)的指针。
- 执行内核函数: 分发器调用
sys_call_table
中找到的函数指针,也就是执行sys_read
。 - 过程也就是:当使用系统调用函数后,函数内会将该系统调用的系统调用号mov到eax寄存器,然后使用
int 0x80
或者syscall
陷入内核,在系统调用表找到对应的系统调用函数执行。 - 真正的内核工作:
sys_read
函数会执行真正的文件读取逻辑,这可能涉及到:- 检查文件描述符的有效性和权限。
- 与虚拟文件系统 (VFS) 层交互。
- 通过文件系统层找到数据在磁盘的位置。
- 与块设备层和磁盘驱动程序交互,发起 I/O 请求。
- 等待 I/O 完成(期间当前进程可能会被挂起,让 CPU 去做其他事)。
- 将读取到的数据从内核缓冲区拷贝到用户传入的
buffer
地址。 - 准备返回值(实际读取的字节数,或出错时的错误码)。
第四站:返回用户空间 - 功成身退
- 内核函数返回:
sys_read
执行完毕,将返回值(比如成功读取的字节数)放入RAX
(或EAX
) 寄存器。 - 恢复上下文: 系统调用分发器从内核堆栈中恢复之前保存的用户通用寄存器。
- 执行返回指令: 执行特殊的返回指令,如
sysret
(对应syscall
) 或iret
(对应INT
)。 - CPU 的返程操作:
- 权限降低: CPU 运行模式从内核模式切换回用户模式。
- 状态恢复: CPU 自动恢复之前保存的用户态指令指针、代码段、标志寄存器、堆栈指针和段寄存器。
- 回到 C 库: 控制权回到用户空间的 C 库函数 (
read
) 中,就在syscall
指令之后。 - 返回应用程序: C 库函数从
RAX
取出内核返回的结果,并将其作为read()
函数的返回值,返回给你的应用程序。
至此,一次完整的系统调用结束。整个过程虽然复杂,但通过软中断机制,实现了用户空间到内核空间的安全、受控的“穿越”。
为何我们感受不到 INT 0x80
/syscall
?
因为 Linux 的 GNU C 标准库,给我们把⼏乎所有的系统调⽤全部封装了。
正如课件所说,我们平时编程依赖的标准库(如 Linux 下的 glibc,Windows 下的 ntdll.dll 或 kernel32.dll)为我们隐藏了这些底层细节。库函数就像是“系统调用代理人”,负责处理参数传递、触发软中断、获取结果等所有繁琐步骤。这使得应用程序员可以专注于业务逻辑,而无需关心底层的中断和模式切换。
不仅仅是系统调用:CPU 的“异常”信号
软中断的范畴并不局限于程序员主动发起的系统调用。CPU 在执行指令时,如果遇到无法处理的错误或需要特殊处理的情况,也会触发内部中断。这些通常被称为异常 (Exceptions)。
缺页中断?内存碎片处理?除零野指针错误?这些问题,全部都会被转换成为CPU内部的软中断… CPU内部的软中断,比如除零/野指针等,我们叫做 异常 (Exception)… CPU内部的软中断,比如
int 0x80
或者syscall
,我们叫做 陷阱 (Trap)。
这个区分很有用。我们可以进一步细化一下:
- 陷阱 (Trap): 通常是有意触发的中断,用于调用某种服务或功能。
INT 0x80
/syscall
就是典型的陷阱。调试断点指令 (INT 3
) 也是陷阱。执行完陷阱处理程序后,通常会返回到陷阱指令的下一条指令继续执行。 - 故障 (Fault): 通常由错误条件引起,但可能是可恢复的。最典型的例子就是缺页故障 (Page Fault)。当程序访问一个有效但当前不在物理内存中的页面时,会触发 Page Fault。操作系统会介入,将页面从磁盘加载到内存,然后重新执行导致故障的那条指令。除零错误、无效操作码、段错误(访问非法内存地址)、保护错误(权限不足)等也常被归为故障。如果故障无法恢复,操作系统可能会终止进程。
- 中止 (Abort): 表示发生了严重的、通常不可恢复的错误,比如硬件错误或系统表不一致。程序无法继续执行,通常会被强制终止。
无论是陷阱、故障还是中止,它们都使用与系统调用类似的机制:CPU 检测到事件 -> 保存状态 -> 查 IDT -> 跳转到内核处理程序。操作系统在初始化时(如课件中的 trap_init
函数)会为这些预定义的异常编号设置好相应的处理函数入口。
// 示例:设置异常处理入口 (概念性)
set_trap_gate(0, ÷_error_handler); // 除零错误
set_trap_gate(3, &breakpoint_handler); // 断点陷阱 (INT 3)
set_trap_gate(6, &invalid_opcode_handler); // 无效指令
set_trap_gate(13, &general_protection_fault_handler); // 通用保护错误
set_trap_gate(14, &page_fault_handler); // 缺页故障
中断是操作系统的脉搏
操作系统就是躺在中断处理例程上的代码块!
这句话精辟地指出了中断(包括硬件中断和软中断)对于操作系统的核心意义。操作系统的大部分代码,无论是设备驱动、文件系统、内存管理还是进程调度,很多时候都是在响应某个中断事件。软中断提供了:
- 用户与内核的桥梁: 安全、受控地访问内核服务。
- 错误处理机制: 统一处理 CPU 内部产生的各种异常。
- 系统运行的基础: 驱动了虚拟内存、调试器、进程终止等关键功能的实现。
一点补充: 在 Linux 内核中,还有一个叫做 “softirq” 的机制,它与我们这里讨论的 CPU 级软中断(INT
/syscall
/异常)是不同的概念。Linux 的 softirq 主要用于将硬件中断处理中耗时较长的部分“延迟”到底半部(bottom half)异步执行,以尽快释放硬件中断上下文。这是一个内核内部的优化技术,不要与 CPU 指令触发的软中断混淆。
小结
软中断就像是操作系统这座大厦中隐藏的楼梯和电梯,连接着用户空间和内核空间,也连接着正常的程序执行与异常处理。理解了软中断,你就能更深刻地把握程序是如何与操作系统交互,以及操作系统是如何应对各种内部事件的。希望这次的深入探讨,能让你对这个“看不见”却至关重要的机制有更清晰的认识!
相关文章:
操作系统是如何运行的?
硬件中断 在我们使用键盘的时候,操作系统要怎么知道键盘上有数据了呢?硬件中断! 硬件中断过程如图所示: 按照图中所示,外设直接与CPU进行交互,但是之前对于冯诺依曼体系架构的学习可知,外设要…...
【智驾中的大模型 -3】VLA 在自动驾驶中的应用
1.前言 在上一篇文章中,我们深入探讨了 VLM 模型在自动驾驶中的应用。VLA(Very Large Architecture,大型架构)和 VLM(Very Large Model,非常大模型)在 AI 领域皆指向超大规模的神经网络模型&am…...
Go语言中的sync.Map与并发安全数据结构完全指南
1. 引言 在Go语言的世界里,并发不是一个附加功能,而是语言的核心设计理念。那句广为人知的"Do not communicate by sharing memory; instead, share memory by communicating"(不要通过共享内存来通信,而应该通过通信来…...
ADVB协议
ADVB:航空数字视频总线 ADVB协议是基于FC光纤通道协议和FC-AV光纤音频视频协议标准来制定 的一种新型的数字视频接口和协议。 FC协议,FC-AV协议,FC-ADVB协议。 协议层次结构,协议拓扑结构。 ADVB总线协议container容器是作为基本传输单元…...
Vue3中provide和inject数据修改规则
在 Vue3 中,通过 inject 接收到的数据是否可以直接修改,取决于 provide 提供的值的类型和响应式处理方式: 1. 若提供的是普通值(非响应式数据) javascript 复制 // 父组件 provide(staticValue, 123); 子组件修改行…...
VuePress 使用教程:从入门到精通
VuePress 使用教程:从入门到精通 VuePress 是一个以 Vue 驱动的静态网站生成器,它为技术文档和技术博客的编写提供了优雅而高效的解决方案。无论你是个人开发者、团队负责人还是开源项目维护者,VuePress 都能帮助你轻松地创建和管理你的文档…...
Linux操作系统简介:从开源内核到技术生态
一、Linux的起源与核心架构 1. 历史背景与发展 1991年,芬兰赫尔辛基大学学生林纳斯托瓦兹(Linus Torvalds)开发了首个Linux内核。这一开源项目与GNU工具链结合,形成完整的GNU/Linux操作系统。截至2023年,Linux内核贡…...
iOS 应用性能测试工具对比:Xcode Instruments、克魔助手与性能狗
iOS 应用性能测试工具对比:Xcode Instruments、克魔助手与性能狗 在移动应用开发领域,性能优化是确保用户体验流畅、留存率高的关键因素。对于 iOS 开发者而言,选择合适的性能测试工具能够帮助快速定位和解决应用中的性能瓶颈。本文将深入分…...
CentOS 10 /root 目录重新挂载到新分区槽
1 观察 ##观察目录/root 所占的磁盘空间大小 rootbogon:~# du -smh /root/ 1.6G /root/ rootbogon:~# du -smh /* |grep root du: 无法访问 /proc/19146/task/19146/fd/3: 没有那个文件或目录 du: 无法访问 /proc/19146/task/19146/fdinfo/3: 没有那个文件或目录 du: 无法访问…...
【读书笔记·VLSI电路设计方法解密】问题64:什么是芯片的功耗分析
低功耗设计是一种针对VLSI芯片功耗持续攀升问题的设计策略。随着工艺尺寸微缩,单颗芯片可集成更多元件,导致功耗相应增长。更严峻的是,现代芯片工作频率较二十年前大幅提升,而功耗与频率呈正比关系。因此,芯片功耗突破…...
python爬虫复习
requests模块 爬虫的分类 通用爬虫:将一整张页面进行数据采集聚焦爬虫:可以将页面中局部或指定的数据进行采集 聚焦爬虫是需要建立在通用的基础上来实现 功能爬虫:基于selenium实现的浏览器自动化的操作分布式爬虫:使用分布式机群…...
深入解析主流数据库体系架构:从关系型到云原生
数据库是现代信息系统的核心组件,其体系架构设计直接影响性能、扩展性和可靠性。本文将从传统关系型数据库到新兴云原生数据库,系统解析主流数据库的架构特点及适用场景。 目录 一、关系型数据库(RDBMS)架构 典型代表&…...
2026《数据结构》考研复习笔记四(第一章)
绪论 前言时间复杂度分析 前言 由于先前笔者花费约一周时间将王道《数据结构》知识点大致过了一遍,圈画下来疑难知识点,有了大致的知识框架,现在的任务就是将知识点逐个理解透彻,并将leetcode刷题与课后刷题相结合。因此此后的过…...
Mysql insert一条数据的详细过程
以下是MySQL在接收到INSERT语句后存储数据的详细过程解析,结合存储引擎(以InnoDB为例)和物理存储机制分步说明。 一、SQL解析与事务启动 1.语法解析 MySQL首先解析INSERT语句,验证字段是否存在、数据类型是否匹配、约束…...
流水灯右移程序(STC89C52单片机)
#include <reg52.h> sbit ADDR0 P1^0; sbit ADDR1 P1^1; sbit ADDR2 P1^2; sbit ADDR3 P1^3; sbit ENLED P1^4; void main() { unsigned int i 0; //定义循环变量i,用于软件延时 unsigned char cnt 0; //定义计数变量cnt,用…...
AI-Sphere-Butler之如何使用Llama factory LoRA微调Qwen2-1.5B/3B专属管家大模型
环境: AI-Sphere-Butler WSL2 英伟达4070ti 12G Win10 Ubuntu22.04 Qwen2.-1.5B/3B Llama factory llama.cpp 问题描述: AI-Sphere-Butler之如何使用Llama factory LoRA微调Qwen2-1.5B/3B管家大模型 解决方案: 一、准备数据集我这…...
智能体团队 (Agent Team)
概述 智能体团队是一种多智能体协作模式,它将多个智能体组织成一个团队,共同解决复杂任务。与智能体监督模式不同,智能体团队中的成员通常具有平等的地位,通过相互交流和协作来达成目标。这种模式特别适合需要多种观点或多领域专…...
AI日报 - 2025年04月19日
🌟 今日概览(60秒速览) ▎🤖 AGI突破 | OpenAI与Google模型在复杂推理上展现潜力,但距AGI仍有距离;因果AI被视为关键路径。 模型如o3解决复杂迷宫,o4-mini通过棋盘测试,但专家预测AGI仍需30年。 ▎…...
【实战中提升自己】内网安全部署之dot1x部署 本地与集成AD域的主流方式(附带MAC认证)
1 dot1x部署【用户名密码认证,也可以解决私接无线AP等功能】 说明:如果一个网络需要通过用户名认证才能访问内网,而认证失败只能访问外网与服务器,可以部署dot1x功能。它能实现的效果是,当内部用户输入正常的…...
算法—合并排序—js(场景:大数据且需稳定性)
合并排序基本思想(稳定且高效) 将数组递归拆分为最小单元,合并两个有序数组。 特点: 时间复杂度:O(n log n) 空间复杂度:O(n) 稳定排序 // 合并排序-分解 function mergeSort(arr) {if (arr.length < …...
绝对路径与相对路径
绝对路径和相对路径是在计算机系统中用于定位文件或目录的两种方式,以下是具体介绍: 绝对路径 • 定义:是从文件系统的根目录开始到目标文件或目录的完整路径,它包含了从根目录到目标位置的所有目录和子目录信息,具有…...
RabbitMQ,添加用户时,出现Erlang cookie不一致,导致添加用户失败的问题解决
1. 问题现象 RabbitMQ 添加用户,出现以下报错 ./rabbitmgctl add user admin admin666*2. 问题原因和解决方法 安装的 RabbitMQ 里的 Erlang cookie,和 Erlang 环境的 cookie 不一致导致的 解决方法:将 Erlang 环境的 cookie ,…...
阿拉丁神灯-第16届蓝桥第4次STEMA测评Scratch真题第2题
[导读]:超平老师的《Scratch蓝桥杯真题解析100讲》已经全部完成,后续会不定期解读蓝桥真题,这是Scratch蓝桥真题解析第219讲。 第16届蓝桥第4次STEMA测评已于2025年1月12日落下帷幕,编程题一共有5题(初级组只有前4道编…...
常用的验证验证 onnxruntime-gpu安装的命令
#工作记录 我们经常会遇到明明安装了onnxruntime-gpu或onnxruntime后,无法正常使用的情况。 一、强制重新安装 onnxruntime-gpu 及其依赖 # 强制重新安装 onnxruntime-gpu 及其依赖 pip install --force-reinstall --no-cache-dir onnxruntime-gpu1.18.0 --extra…...
docker配置skywalking 监控springcloud应用
在使用 Docker 配置 SkyWalking 监控 Spring Cloud 应用时,主要分为以下几个步骤: 1. 准备工作 确保你的开发环境已经安装了 Docker 和 Docker Compose。准备好 Spring Cloud 应用代码,并确保它支持 SkyWalking 的探针(Agent&…...
HBase安装与基本操作指南
## 1. 安装准备 首先确保您的系统已经安装了以下组件: - Java JDK 8或更高版本 - Hadoop(HBase可以运行在独立模式下,但建议配合Hadoop使用) ## 2. 下载与安装HBase ```bash # 下载HBase(以2.4.12版本为例) wget https://downloads.apache.org/hbase/2.4.12/hbase-2…...
【Linux】Rhcsa复习5
一、Linux文件系统权限 1、文件的一般权限 文件权限针对三类对象进行定义: owner 属主,缩写u group 属组, 缩写g other 其他,缩写o 每个文件针对每类访问者定义了三种主要权限: r:read 读 w&…...
C++11特性补充
目录 lambda表达式 定义 捕捉的方式 可变模板参数 递归函数方式展开参数包 数组展开参数包 移动构造和移动赋值 包装器 绑定bind 智能指针 RAII auto_ptr unique_ptr shared_ptr 循环引用 weak_ptr 补充 总结 特殊类的设计 不能被拷贝的类 只能在堆上创建…...
缓存 --- Redis性能瓶颈和大Key问题
缓存 --- Redis性能瓶颈和大Key问题 内存瓶颈网络瓶颈CPU 瓶颈持久化瓶颈大key问题优化方案 Redis 是一个高性能的内存数据库,但在实际使用中,可能会在内存、网络、CPU、持久化、大键值对等方面遇到性能瓶颈。下面从这些方面详细分析 Redis 的性能瓶颈&a…...
css3新特性第三章(文本属性)
一、文本属性 文本阴影文本换行文本溢出文本修饰文本描边 1.1 文本阴影 在 CSS3 中,我们可以使用 text-shadow 属性给文本添加阴影。 语法: text-shadow: h-shadow v-shadow blur color; 值描述h-shadow必需写,水平阴影的位置。允许负值。…...
Redis 缓存—处理高并发问题
Redis的布隆过滤器、单线程架构、双写一致性、比较穿透、击穿及雪崩、缓存更新方案及分布式锁。 1 布隆过滤器 是一种高效的概率型数据结构,用于判断元素是否存在。主要用于防止缓存穿透,通过拦截不存在的数据查询,避免击穿数据库。 原理&…...
嵌入式芯片中的 SRAM 内容细讲
什么是 RAM? RAM 指的是“随机存取”,意思是存储单元都可以在相同的时间内被读写,和“顺序访问”(如磁带)相对。 RAM 不等于 DRAM,而是一类统称,包括 SRAM 和 DRAM 两种主要类型。 静态随机存…...
实操基于MCP驱动的 Agentic RAG:智能调度向量召回或者网络检索
我们展示了一个由 MCP 驱动的 Agentic RAG,它会搜索向量数据库,当然如果有需要他会自行进行网络搜索。 为了构建这个系统,我们将使用以下工具: 博查搜索 用于大规模抓取网络数据。作为Faiss向量数据库。Cursor 作为 MCP 客户端。…...
位运算---总结
位运算 基础 1. & 运算符 : 有 0 就是 0 2. | 运算符 : 有 1 就是 1 3. ^ 运算符 : 相同为0 相异为1 and 无进位相加位运算的优选级 不用在意优先级,能加括号就加括号给一个数 n ,确定它的二进制位中第 x 位是 0 还是 1? 规定: 题中所说的第x位指:int 在32位机器下4个…...
从0开始搭建一套工具函数库,发布npm,支持commonjs模块es模块和script引入使用
文章目录 文章目标技术选型工程搭建1. 初始化项目2. 安装开发依赖3. 项目结构4. 配置文件tsconfig.json.eslintrc.jseslint.config.prettierrc.jsrollup.config.cjs创建 .gitignore文件 设置 Git 钩子创建示例工具函数8. 版本管理和发布9 工具函数测试方案1. 安装测试依赖2. 配…...
精通 Spring Cache + Redis:避坑指南与最佳实践
Spring Cache 以其优雅的注解方式,极大地简化了 Java 应用中缓存逻辑的实现。结合高性能的内存数据库 Redis,我们可以轻松构建出响应迅速、扩展性强的应用程序。然而,在享受便捷的同时,一些常见的“坑”和被忽视的最佳实践可能会悄…...
DSP28335入门学习——第一节:工程项目创建
写这个文章是用来学习的,记录一下我的学习过程。希望我能一直坚持下去,我只是一个小白,只是想好好学习,我知道这会很难,但我还是想去做! 本文写于:2025.04.20 DSP28335开发板学习——第一节:工程项目创建 前言开发板说明引用解答…...
Docker Registry(镜像仓库)
官方架构 Docker 使用客户端 - 服务器 (C/S) 架构模式,使用远程 API 来管理和创建 Docker 容器。Docker 容器通过 Docker 镜像来创建。 Docker 仓库(Registry):Docker 仓库用来保存镜像,可以理解为代码控制中的代码仓库。Docker Hu…...
通过Dify快速搭建本地AI智能体开发平台
1. 安装Docker Desktop 访问 Docker官网 点击Download Docker Desktop,直接按照官方要求来就可以。 # 这串命令就像魔法咒语,在黑色窗口(命令提示符)里输入就能检查安装是否成功 docker --version2.安装dify 3.运行 Ollama 大…...
计算机视觉与深度学习 | Transformer原理,公式,代码,应用
Transformer 详解 Transformer 是 Google 在 2017 年提出的基于自注意力机制的深度学习模型,彻底改变了序列建模的范式,解决了 RNN 和 LSTM 在长距离依赖和并行计算上的局限性。以下是其原理、公式、代码和应用的详细解析。 一、原理 核心架构 Transformer 由 编码器(Encod…...
skywalking agent 关联docker镜像
Apache SkyWalking 提供了多种方式来部署和使用 SkyWalking Agent,包括在 Docker 容器中运行的应用。虽然 SkyWalking Agent 本身不是一个独立的 Docker 镜像,但你可以通过几种方式将 SkyWalking Agent 集成到你的 Docker 应用中。 方式一:手…...
【中间件】nginx将请求负载均衡转发给网关,网关再将请求转发给对应服务
一、场景 前端将请求发送给nginx,nginx将请求再转发给网关,网关再将请求转发至对应服务。由于网关会部署在多台服务器上,因此nginx需要负载均衡给网关发请求。nginx所有配置均参照官方文档nginx开发文档,可参考负载均衡板块内容 二…...
Milvus(1):什么是 Milvus
Milvus 由 Zilliz 开发,并很快捐赠给了 Linux 基金会下的 LF AI & Data 基金会,现已成为世界领先的开源向量数据库项目之一。它采用 Apache 2.0 许可发布,大多数贡献者都是高性能计算(HPC)领域的专家,擅…...
第十六节:高频开放题-React与Vue设计哲学差异
响应式原理(Proxy vs 虚拟DOM) 组合式API vs Hooks React 与 Vue 设计哲学差异深度解析 一、响应式原理的底层实现差异 1. Vue 的响应式模型(Proxy/数据劫持) Vue 的响应式系统通过 数据劫持 实现自动依赖追踪: • …...
【Hot100】 240. 搜索二维矩阵 II
目录 引言搜索二维矩阵 II我的解题贪心求解解题思路详解搜索策略(以从右上角开始为例)为什么这种方法有效? 完整代码实现复杂度分析示例演示 🙋♂️ 作者:海码007📜 专栏:算法专栏Ὂ…...
每日面试实录·携程·社招·JAVA
📍面试公司:携程 👜面试岗位:后端开发工程师(社招) 🕐面试时长:约 50 分钟 🔄面试轮次:第 1 轮技术面 ✨面试整体节奏: 这场携程的社招 Java 一面…...
Oracle--用户管理
前言:本博客仅作记录学习使用,部分图片出自网络,如有侵犯您的权益,请联系删除 用户管理在 Oracle 数据库中至关重要。一个服务器通常只运行一个 Oracle 实例,而一个 Oracle 用户代表一个用户群,他们通过该用…...
20.3 使用技巧5
版权声明:本文为博主原创文章,转载请在显著位置标明本文出处以及作者网名,未经作者允许不得用于商业目的 20.3.8 CellContentClick事件 当增加新按钮列或者超链接列后,按钮或者超链接,会发现,按钮或者超链…...
Kubernetes相关的名词解释Metrics Server组件(7)
什么是Metrics Server? Metrics Server 是 Kubernetes 集群中的一个关键组件,主要用于资源监控和自动扩缩容。 kubernetes 从1.8版本开始不再集成cadvisor,也废弃了heapster,使用metrics server来提供metrics。那么...... 什么…...
17.【.NET 8 实战--孢子记账--从单体到微服务--转向微服务】--单体转微服务--SonarQube部署与配置
在将孢子记账系统从单体架构转向微服务架构的过程中,代码质量的管理变得尤为重要。随着项目规模的扩大和团队协作的深入,我们需要一个强大的工具来帮助我们持续监控和改进代码质量。我们首选SonarQube,它能够帮助我们识别代码中的潜在问题、技…...