Mr Dk.'s BlogMr Dk.'s Blog
  • 🦆 About Me
  • ⛏️ Technology Stack
  • 🔗 Links
  • 🗒️ About Blog
  • Algorithm
  • C++
  • Compiler
  • Cryptography
  • DevOps
  • Docker
  • Git
  • Java
  • Linux
  • MS Office
  • MySQL
  • Network
  • Operating System
  • Performance
  • PostgreSQL
  • Productivity
  • Solidity
  • Vue.js
  • Web
  • Wireless
  • 🐧 How Linux Works (notes)
  • 🐧 Linux Kernel Comments (notes)
  • 🐧 Linux Kernel Development (notes)
  • 🐤 μc/OS-II Source Code (notes)
  • ☕ Understanding the JVM (notes)
  • ⛸️ Redis Implementation (notes)
  • 🗜️ Understanding Nginx (notes)
  • ⚙️ Netty in Action (notes)
  • ☁️ Spring Microservices (notes)
  • ⚒️ The Annotated STL Sources (notes)
  • ☕ Java Development Kit 8
GitHub
  • 🦆 About Me
  • ⛏️ Technology Stack
  • 🔗 Links
  • 🗒️ About Blog
  • Algorithm
  • C++
  • Compiler
  • Cryptography
  • DevOps
  • Docker
  • Git
  • Java
  • Linux
  • MS Office
  • MySQL
  • Network
  • Operating System
  • Performance
  • PostgreSQL
  • Productivity
  • Solidity
  • Vue.js
  • Web
  • Wireless
  • 🐧 How Linux Works (notes)
  • 🐧 Linux Kernel Comments (notes)
  • 🐧 Linux Kernel Development (notes)
  • 🐤 μc/OS-II Source Code (notes)
  • ☕ Understanding the JVM (notes)
  • ⛸️ Redis Implementation (notes)
  • 🗜️ Understanding Nginx (notes)
  • ⚙️ Netty in Action (notes)
  • ☁️ Spring Microservices (notes)
  • ⚒️ The Annotated STL Sources (notes)
  • ☕ Java Development Kit 8
GitHub
  • 🐧 Linux Kernel Comments
    • Chapter 2 - 微型计算机组成结构

      • Chapter 2 - 微型计算机组成结构
    • Chapter 3 - 内核编程语言和环境

      • Chapter 3 - 内核编程语言和环境
    • Chapter 4 - 80X86 保护模式及其编程

      • Chapter 4.1-4.2 - 80X86 系统寄存器和系统指令 & 保护模式内存管理
      • Chapter 4.3 - 分段机制
      • Chapter 4.4 - 分页机制
      • Chapter 4.5 - 保护
      • Chapter 4.6 - 中断和异常处理
      • Chapter 4.7 - 任务管理
      • Chapter 4.8 - 保护模式编程初始化
      • Chapter 4.9 - 一个简单的多任务内核实例
    • Chapter 5 - Linux 内核体系结构

      • Chapter 5.1-5.2 - Linux 内核模式 & 体系结构
      • Chapter 5.3 - Linux 内核对内存的管理和使用
      • Chapter 5.4-5.6 - 中断机制 & 系统调用 & 系统时间和定时
      • Chapter 5.7-5.9 - Linux 进程控制 & 堆栈使用 & 文件系统
    • Chapter 6 - 引导启动程序 (boot)

      • Chapter 6 - 引导启动程序 (boot)
    • Chapter 7 - 初始化程序 (init)

      • Chapter 7 - 初始化程序 (init)
    • Chapter 8 - 内核代码

      • Chapter 8.1 - 内核代码总体功能
      • Chapter 8.2 - asm.s 程序
      • Chapter 8.3 - traps.c 程序
      • Chapter 8.4 - sys_call.s 程序
      • Chapter 8.5 - mktime.c 程序
      • Chapter 8.6 - sched.c 程序
      • Chapter 8.7 - signal.c 程序
      • Chapter 8.8 - exit.c 程序
      • Chapter 8.9 - fork.c 程序
      • Chapter 8.10 - sys.c 程序
    • Chapter 9 - 块设备驱动程序

      • Chapter 9.1 - 块设备驱动程序 总体功能
      • Chapter 9.2 - blk.h 文件
      • Chapter 9.3 - hd.c 程序
      • Chapter 9.4 - ll_rw_blk.c 程序
      • Chapter 9.5 - ramdisk.c 程序
    • Chapter 10 - 字符设备驱动程序

      • Chapter 10.1 - 字符设备驱动程序 总体功能
      • Chapter 10.2 - keyboard.S 程序
      • Chapter 10.3 - console.c 程序
      • Chapter 10.4 - serial.c 程序
      • Chapter 10.5 - rs_io.s 程序
      • Chapter 10.6 - tty_io.c 程序
      • Chapter 10.7 - tty_ioctl.c 程序
    • Chapter 12 - 文件系统

      • Chapter 12.1 - 文件系统 总体功能
      • Chapter 12.2 - buffer.c 程序
      • Chapter 12.3 - bitmap.c 程序
      • Chapter 12.4 - truncate.c 程序
      • Chapter 12.5 - inode.c 程序
      • Chapter 12.6 - super.c 程序
      • Chapter 12.7 - namei.c 程序
      • Chapter 12.9 - block_dev.c 程序
      • Chapter 12.10 - file_dev.c 程序
      • Chapter 12.11 - pipe.c 程序
      • Chapter 12.12 - char_dev.c 程序
      • Chapter 12.13 - read_write.c 程序
      • Chapter 12.14 - open.c 程序
      • Chapter 12.15 - exec.c 程序
      • Chapter 12.16 - stat.c 程序
      • Chapter 12.17 - fcntl.c 程序
      • Chapter 12.18 - ioctl.c 程序
      • Chapter 12.19 - select.c 程序

Chapter 5.4-5.6 - 中断机制 & 系统调用 & 系统时间和定时

Created by : Mr Dk.

2019 / 08 / 03 11:41

Nanjing, Jiangsu, China


5.4 中断机制

5.4.1 中断操作原理

可编程中断控制器 (Programmable Interrupt Controller) 是微机系统中设备中断请求的管理者。

  • 连接到设备的中断请求引脚,接受中断请求信号
  • 在同时收到几个中断的情况下,PIC 会对它们进行优先级比较,选出最高优先级中断进行处理
  • PIC 向 CPU 的 INT 引脚发出中断信号,通过数据总线发送与中断请求对应的中断号
  • CPU 停下当时所做的事情,根绝中断号查询中断表,并开始执行中断服务程序

中断也可由软件产生:使用 INT 指令,并用操作数指明中断号。

5.4.2 80X86 微机的中断子系统

每个 8259A 芯片可以管理 8 个中断源,多片级联,最多可以管理 64 个中断向量系统。在 PC/AT 系列兼容机中,使用了两片 8259A,可管理 15 级中断向量,从片的 INT 引脚级联到了主片的 IRQ2 上,主片的原 IRQ2 被接到了从片的 IRQ9 上。BIOS 中的软件把 IRQ9 的中断 INT 71 定向到 IRQ2 的中断 INT 0x0A 上

为了兼容。。。

在总线控制器控制下,8259A 芯片可以处于编程状态和操作状态:

  • 编程状态:CPU 使用 IN 或 OUT 指令对芯片进行初始化编程的状态
  • 操作状态:芯片随时响应外部请求

5.4.3 中断向量表

80X86 支持 256 个中断。在实地址模式下,每个中断向量由 4B 组成,中断向量指明了对应中断服务程序的段和段内偏移,因此整个中断向量表的长度为 1024B。80X86 微机启动时,ROM BIOS 会在物理内存 0 处初始化中断向量表,各中断默认的中断服务程序则在 BIOS 中给出。在 BIOS 初始化操作中,设置了 8259A 芯片支持的 15 个中断向量,对于实际没有使用的向量,填入临时的哑中断服务程序地址。

对于 Linux,在刚开始加载内核时需要用到 BIOS 提供的显示和磁盘读中断,之后 Linux 会重新初始化 8259A 芯片,并重新设置一张中断向量表。即 Linux 在内核正常运行之后完全抛弃了 BIOS 所提供的中断服务功能。

5.4.4 Linux 内核的中断处理

对于 Linux 内核来说,中断信号分为:

  • 硬件中断
  • 软件中断 (异常)

每个中断由 0-255 之间的数字来标识,前 32 个中断号由 Intel 公司固定设定或保留,属于软件中断;中断 INT32 - INT255 可以由用户自己设定。在 Linux 系统中,将 INT32 - INT47 对应于 8259A 的 IRQ0 - IRQ15,并把用户程序发出的软件中断设置为 INT128 (0x80):系统调用 (System Call),是用户程序使用 OS 资源的唯一界面接口。

系统初始化时,内核使用哑中断向量对 IDT 中的 256 个描述符进行默认设置。哑中断向量指向默认的无中断处理过程。INT0 - INT31 在 traps.c 中进行了重新设置,INT128 则在调度程序初始化函数中进行了重新设置。设置 IDT 时,Linux 内核使用了中断门和陷阱门两种描述符:

  • 中断门描述符执行的中断会复位 IF 标志,避免其它中断干扰当前中断处理过程
  • 陷阱门中执行的中断不会影响 IF 标志

5.4.5 标志寄存器的中断标志

为了避免竞争条件和中断对临界代码区的干扰,Linux 0.12 内核代码中许多地方使用了 CLI 和 STI 指令进行开中断 / 关中断。


5.5 Linux 的系统调用

5.5.1 系统调用接口

系统调用是 Linux 内核与上层应用程序进行交互通信的唯一接口。用户程序通过直接或间接 (库函数) 调用中断 INT 0x80,并在 eax 寄存器中指定系统调用功能号。通常应用程序都是使用具有标准接口定义的 C 库函数间接地使用系统调用,系统调用通过函数的形式调用,因此可以附带参数。执行结果会在返回值中表示:通常负值表示错误,0 表示成功。出错时,错误的类型码会被存放在全局变量 errno 中,通过调用库函数 perror() 可以打印出对应出错的字符串信息。

在 Linux 内核中,每个系统调用都具有唯一的系统调用功能号。0.12 中有 87 个系统调用功能,定义在 include/unistd.h 中,对应于 include/linux/sys.h 定义的系统调用处理程序指针数组表 sys_call_table[] 中的索引。

5.5.2 系统调用处理过程

寄存器 eax 中存放系统调用号,参数可依次放在 ebx、ecx 和 edx 中。因此 Linux 0.12 内核中,用户程序能够向内核最多直接传递三个参数。若需要传递大块数据,可以传递这块数据的指针。内核源码中定义了宏函数 _syscalln(),n 代表携带参数的个数,分别是 0 - 3。

对于 read() 系统调用,定义是:

int read(int fd, char *buf, int n);

对应的宏形式:

_syscall3(int, read, int, fd, char *, buf, int n)
  • 第一个参数对应返回值类型
  • 第二个参数对应系统调用名称
  • 之后是每个参数的参数类型和名称

这个宏会被扩展成包含内嵌汇编的 C 函数。

对于多于三个参数的系统调用,通常使用 _syscall1(),把参数 buffer 的指针传给内核即可。

进入内核的系统调用处理程序 kernel/sys_call.s 后,首先检查 eax 中系统调用功能号是否在有效系统调用号范围内,然后根据 sys_call_table[] 函数指针表执行相应的系统调用处理程序。

5.5.3 Linux 系统调用的参数传递方式

Linux 系统使用的是通用寄存器传递方法。优点:当进入中断服务程序而保存寄存器值时,这些传递参数寄存器也被自动放在了内核堆栈上,不用再专门对传递参数的寄存器进行特殊处理。在每个系统调用处理程序中,应当对传递的参数进行验证,保证所有参数都合法有效。


5.6 系统时间和定时

5.6.1 系统时间

PC/AT 微机系统中提供了用电池供电的实时钟 RT (Real Time) 电路,这部分电路与保存系统信息的 CMOS RAM 集成在一个芯片上:RT/CMOS RAM 电路 - 比如 Motorola 的 MC146818 芯片。内核初始化时,通过 init/main.c 中的 time_init() 函数读取芯片上的当前时间,通过 kernel/mktime.c 中的 kernel_mktime() 函数转换为 Unix 日历时间。

该时间确定了系统开始运行的日历时间,保存在全局变量 startup_time 中供内核所有代码会用,通过系统调用 time() 可以读取这个值,super user 可以通过系统调用 stime() 修改该值。

从系统启动开始计数的系统滴答值 - jiffies:程序可以唯一确定当前时间。每个滴答由系统定时芯片产生 - 10ms。因此内核代码中定义了一个宏,方便代码对当前时间的访问:

#define CURRENT_TIME (startup_time + jiffies/HZ)

HZ = 100,是内核系统时钟的频率。

5.6.2 系统定时

Linux 0.12 内核初始化过程中,PC 中的 Intel 8253/8254 芯片的计数器通道 0 被设置在运行于方式 3 下。每隔 10ms 在通道 0 的输出端 OUT 发出一个方波上升沿,OUT 被连接到 PIC 的 0 级上,因此每 10ms 会产生一个时钟中断 IRQ0。这个时间节拍是 OS 的脉搏:一次中断被称为一个系统滴答或一个系统时钟周期,每经过一个系统时钟周期,系统就会调用一次时钟中断处理程序。时钟中断处理程序通过 jiffies 变量来累积系统启动以来的滴答数:每发生一次时钟中断,jiffies 就加 1,然后调用 do_time() 进行进一步处理。

do_timer() 函数根据特权级对当前进程运行时间作累积

  • CPL == 0,那么进程被中断于内核态,进程的内核态运行时间 + 1
  • 否则把进程的用户态运行时间 + 1

时间片 是一个进程被切换掉之前能持续运行 CPU 的时间,以系统滴答为单位,每发生一次时钟中断,时间片 - 1。若进程时间片递减后依旧 > 0,说明时间片还没有用完 - 退出 do_timer() 继续运行当前进程;如果时间片减至 0,内核根据被中断程序的级别做进一步处理:

  • 如果被中断进程运行在用户态,do_timer() 调用调度程序 schedule() 切换到其它进程
  • 如果被中断进程运行在内核态,do_timer() 立刻退出
  • 决定了进程在内核态运行时是不可抢占的,在用户态运行时是可以被抢占的
Edit this page on GitHub
Prev
Chapter 5.3 - Linux 内核对内存的管理和使用
Next
Chapter 5.7-5.9 - Linux 进程控制 & 堆栈使用 & 文件系统