logo
导航

03. 运行环境

操作系统的运行环境

计算机系统的层次结构

现代计算机系统采用分层架构,从底层到顶层依次为:

┌─────────────────┐
│   应用程序层    │  ← 用户程序
├─────────────────┤
│   操作系统层    │  ← 系统软件
├─────────────────┤
│   硬件抽象层    │  ← 驱动程序
├─────────────────┤
│   硬件层        │  ← 物理设备
└─────────────────┘

各层功能

  • 硬件层:CPU、内存、外设等物理设备
  • 硬件抽象层:设备驱动程序,隐藏硬件细节
  • 操作系统层:内核,管理硬件资源
  • 应用程序层:用户程序,提供具体功能

内核态与用户态

特权级的概念

现代处理器通常支持多个特权级,用于区分不同程序的权限:

1. 特权级分类

Intel x86 架构

  • Ring 0:内核态,最高权限
  • Ring 1-2:系统服务层(较少使用)
  • Ring 3:用户态,最低权限

ARM 架构

  • EL0:用户态
  • EL1:内核态
  • EL2:虚拟化层
  • EL3:安全监控层

2. 内核态(Kernel Mode)

特点

  • 最高权限级别
  • 可以执行所有指令
  • 直接访问硬件
  • 可以修改系统状态

权限

  • 访问所有内存地址
  • 执行特权指令
  • 控制中断和异常
  • 管理硬件设备

典型操作

  • 内存管理
  • 进程调度
  • 设备驱动
  • 系统调用处理

3. 用户态(User Mode)

特点

  • 较低权限级别
  • 只能执行非特权指令
  • 不能直接访问硬件
  • 受系统保护

限制

  • 只能访问用户空间内存
  • 不能执行特权指令
  • 不能直接操作硬件
  • 受操作系统监控

典型操作

  • 应用程序执行
  • 用户数据处理
  • 调用系统服务
  • 文件操作

状态切换机制

1. 用户态到内核态的切换

触发条件

  • 系统调用
  • 中断
  • 异常
  • 特权指令

切换过程

用户态 -> 保存用户态上下文 -> 切换到内核态 -> 执行内核代码

具体步骤

  1. 保存用户态寄存器状态
  2. 切换到内核栈
  3. 设置内核态标志
  4. 跳转到内核代码

2. 内核态到用户态的切换

触发条件

  • 系统调用返回
  • 中断处理完成
  • 异常处理完成

切换过程

内核态 -> 恢复用户态上下文 -> 切换到用户态 -> 继续用户程序

具体步骤

  1. 恢复用户态寄存器状态
  2. 切换到用户栈
  3. 清除内核态标志
  4. 跳转到用户代码

内存保护机制

1. 地址空间隔离

用户空间

  • 应用程序可访问的内存区域
  • 受操作系统保护
  • 每个进程独立

内核空间

  • 操作系统内核使用的内存区域
  • 所有进程共享
  • 受硬件保护

2. 内存管理单元(MMU)

功能

  • 虚拟地址到物理地址的转换
  • 内存访问权限控制
  • 内存保护

工作原理

虚拟地址 -> MMU -> 物理地址
         ↓
    权限检查 -> 允许/拒绝访问

中断与异常

中断(Interrupt)

1. 中断的概念

定义:由硬件设备或外部事件引起的程序执行流程的临时中断。

特点

  • 异步发生
  • 可屏蔽
  • 有优先级
  • 可嵌套

2. 中断的分类

按来源分类

  • 硬件中断:由硬件设备产生
  • 软件中断:由程序指令产生

按可屏蔽性分类

  • 可屏蔽中断:可以被 CPU 忽略
  • 不可屏蔽中断:CPU 必须立即处理

按优先级分类

  • 高优先级中断:系统关键事件
  • 低优先级中断:一般设备事件

3. 常见的中断类型

时钟中断

  • 由系统时钟产生
  • 用于时间片轮转
  • 定期发生

I/O 中断

  • 由 I/O 设备产生
  • 表示 I/O 操作完成
  • 异步通知

硬件故障中断

  • 由硬件错误产生
  • 需要立即处理
  • 系统保护机制

4. 中断处理过程

中断发生 -> 保存现场 -> 切换到内核态 -> 执行中断服务程序 -> 恢复现场 -> 返回

详细步骤

  1. 中断检测:CPU 检测到中断信号
  2. 现场保存:保存当前程序状态
  3. 模式切换:切换到内核态
  4. 中断处理:执行相应的中断服务程序
  5. 现场恢复:恢复被中断程序的状态
  6. 程序返回:继续执行被中断的程序

异常(Exception)

1. 异常的概念

定义:由程序执行过程中的错误或特殊情况引起的程序执行流程的中断。

特点

  • 同步发生
  • 不可屏蔽
  • 与程序执行相关
  • 需要立即处理

2. 异常的分类

按严重程度分类

  • 故障(Fault):可恢复的错误
  • 陷阱(Trap):有意的异常
  • 终止(Abort):不可恢复的错误

按来源分类

  • 程序异常:由程序错误引起
  • 系统异常:由系统状态引起

3. 常见的异常类型

页错误(Page Fault)

  • 访问不存在的内存页
  • 访问权限不足的内存页
  • 触发虚拟内存管理

除零错误(Divide by Zero)

  • 除数为零的除法运算
  • 程序错误
  • 需要异常处理

非法指令(Illegal Instruction)

  • 执行不存在的指令
  • 执行特权指令
  • 程序错误

栈溢出(Stack Overflow)

  • 栈空间不足
  • 递归过深
  • 缓冲区溢出

4. 异常处理过程

异常发生 -> 保存现场 -> 切换到内核态 -> 执行异常处理程序 -> 决定后续操作

处理结果

  • 恢复执行:修复错误后继续执行
  • 终止程序:无法恢复,终止程序
  • 系统重启:严重错误,重启系统

中断与异常的区别

特征中断异常
发生时机异步同步
可屏蔽性可屏蔽不可屏蔽
来源硬件设备程序执行
处理方式中断服务程序异常处理程序
优先级有优先级无优先级

系统调用(System Call)

系统调用的概念

1. 定义

系统调用是操作系统提供给应用程序的接口,允许用户程序请求操作系统内核的服务。

2. 作用

  • 资源管理:申请和释放系统资源
  • 进程控制:创建、终止、等待进程
  • 文件操作:文件的创建、读写、删除
  • 设备控制:设备的打开、关闭、控制
  • 通信服务:进程间通信、网络通信

3. 系统调用的特点

  • 特权操作:需要切换到内核态
  • 标准化接口:提供统一的调用方式
  • 安全性:受操作系统保护
  • 可移植性:不同平台接口一致

系统调用的实现

1. 调用过程

用户程序 -> 系统调用接口 -> 内核态切换 -> 内核服务 -> 返回用户态 -> 用户程序

详细步骤

  1. 参数准备:用户程序准备系统调用参数
  2. 调用接口:通过系统调用接口进入内核
  3. 模式切换:从用户态切换到内核态
  4. 服务执行:内核执行相应的服务
  5. 结果返回:将结果返回给用户程序
  6. 模式恢复:从内核态切换回用户态

2. 系统调用号

定义:每个系统调用都有一个唯一的编号,用于标识不同的系统调用。

示例(Linux x86-64):

#define SYS_read    0
#define SYS_write   1
#define SYS_open    2
#define SYS_close   3
#define SYS_fork    57
#define SYS_execve  59

3. 参数传递

寄存器传递

  • 系统调用号放在特定寄存器中
  • 参数依次放在其他寄存器中
  • 返回值放在指定寄存器中

栈传递

  • 参数压入栈中
  • 系统调用号放在栈顶
  • 返回值放在栈中

常见的系统调用分类

1. 进程管理

进程创建

pid_t fork(void);           // 创建子进程
int execve(const char *pathname, char *const argv[], char *const envp[]); // 执行程序

进程控制

pid_t wait(int *status);    // 等待子进程
int exit(int status);       // 终止进程

进程信息

pid_t getpid(void);         // 获取进程ID
pid_t getppid(void);        // 获取父进程ID

2. 文件操作

文件控制

int open(const char *pathname, int flags); // 打开文件
int close(int fd);          // 关闭文件

文件读写

ssize_t read(int fd, void *buf, size_t count);  // 读取文件
ssize_t write(int fd, const void *buf, size_t count); // 写入文件

文件定位

off_t lseek(int fd, off_t offset, int whence); // 文件定位

3. 内存管理

内存分配

void *brk(const void *addr); // 调整数据段大小
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); // 内存映射

内存保护

int mprotect(void *addr, size_t len, int prot); // 设置内存保护

4. 进程间通信

管道

int pipe(int pipefd[2]);    // 创建管道

信号

int kill(pid_t pid, int sig); // 发送信号
sighandler_t signal(int signum, sighandler_t handler); // 设置信号处理

共享内存

int shmget(key_t key, size_t size, int shmflg); // 创建共享内存
void *shmat(int shmid, const void *shmaddr, int shmflg); // 连接共享内存

系统调用的安全性

1. 参数验证

边界检查

  • 检查参数的有效性
  • 验证内存地址的合法性
  • 检查权限是否足够

示例

// 检查文件描述符是否有效
if (fd < 0 || fd >= MAX_FILES) {
    return -EBADF;
}

// 检查内存地址是否在用户空间
if (!access_ok(VERIFY_READ, buf, count)) {
    return -EFAULT;
}

2. 权限检查

用户权限

  • 检查用户是否有相应权限
  • 验证文件访问权限
  • 检查系统资源限制

示例

// 检查文件访问权限
if (!may_access(path, mode)) {
    return -EACCES;
}

3. 资源限制

系统资源

  • 检查系统资源是否足够
  • 防止资源耗尽
  • 实施资源配额

示例

// 检查内存限制
if (current->mm->total_vm > rlimit(RLIMIT_AS)) {
    return -ENOMEM;
}

总结

操作系统的运行环境是理解操作系统工作原理的基础。内核态与用户态的分离提供了安全性和稳定性,中断与异常机制处理了各种突发事件,系统调用则为用户程序提供了访问操作系统服务的标准接口。

这些机制相互配合,共同构成了现代操作系统的运行环境,确保了系统的安全、稳定和高效运行。理解这些概念对于深入学习操作系统的其他内容具有重要意义。