《计算机操作系统》 - 第九章 操作系统接口

前言

操作系统接口是用户与操作系统交互的桥梁,也是《计算机操作系统》学习的核心知识点之一。本文将围绕第九章 "操作系统接口" 展开详细讲解,从用户接口到系统调用的实现,每个知识点都搭配通俗易懂的解释、完整的 C++98 代码案例和可视化图表,帮助你彻底掌握这一章的核心内容。

9.1 用户接口

核心概念

用户接口是操作系统为用户提供的交互方式,主要分为两类:

  • 联机用户接口(命令接口) :通过命令行(如 Shell)与系统交互,适合专业用户,效率高。
  • 脱机用户接口(批处理接口):用户将作业需求写成批处理脚本,系统批量执行,无需实时交互。
  • 图形用户接口(GUI) :通过图标、窗口等可视化方式操作,友好但资源消耗高。

架构图

9.2 Shell 命令语言

核心概念

Shell 是命令接口的核心,本质是命令解释器,常见的有 Bash、Zsh、C Shell 等。Shell 命令语言包含三类命令:

  1. 内部命令:Shell 内置(如 cd、echo),执行速度快;
  2. 外部命令:独立可执行文件(如 ls、cp),需要从磁盘加载;
  3. 管道 / 重定向命令 :通过|(管道)、>(重定向)实现命令联动。

实操案例(Shell 命令示例)

复制代码
# 1. 内部命令:查看当前目录
pwd
# 2. 外部命令:列出目录下所有文件
ls -l
# 3. 管道命令:查找包含"txt"的文件
ls -l | grep "txt"
# 4. 重定向:将目录列表写入文件
ls -l > file_list.txt

9.3 联机命令接口的实现

核心原理

联机命令接口的实现核心是 "命令解释器循环",流程如下:

  1. 读取用户输入的命令行;
  2. 解析命令(拆分命令名、参数);
  3. 查找命令(内部命令直接执行,外部命令查找路径);
  4. 创建子进程执行命令;
  5. 等待子进程结束,返回提示符。

流程图

完整 C++98 实现(简易 Shell 解释器)

cpp 复制代码
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <vector>
#include <windows.h>  // Windows核心API头文件
#include <direct.h>   // Windows目录操作头文件
// 严格遵循C++98标准,禁用C++11及以上特性
using namespace std;

// 分割命令行(拆分命令和参数)
vector<char*> splitCommand(const string& cmd) {
    vector<char*> args;
    char* buf = new char[cmd.size() + 1];
    strcpy(buf, cmd.c_str());
    
    char* token = strtok(buf, " ");
    while (token != NULL) {
        args.push_back(token);
        token = strtok(NULL, " ");
    }
    args.push_back(NULL); // 兼容参数格式
    return args;
}

// 执行外部命令(Windows版本)
void executeCommand(const vector<char*>& args) {
    if (args.empty() || args[0] == NULL) {
        return;
    }
    
    // 拼接命令行(Windows CreateProcess需要完整命令行)
    string cmdLine;
    for (size_t i = 0; args[i] != NULL; ++i) {
        cmdLine += args[i];
        cmdLine += " ";
    }
    
    // Windows进程创建结构体
    STARTUPINFO si = {0};
    PROCESS_INFORMATION pi = {0};
    si.cb = sizeof(STARTUPINFO);
    
    // 创建并执行子进程
    if (!CreateProcess(
        NULL,               // 不指定可执行文件路径(从PATH查找)
        (char*)cmdLine.c_str(), // 命令行
        NULL,               // 进程安全属性
        NULL,               // 线程安全属性
        FALSE,              // 不继承句柄
        0,                  // 无特殊标志
        NULL,               // 使用父进程环境变量
        NULL,               // 使用父进程当前目录
        &si,                // 启动信息
        &pi                 // 进程信息
    )) {
        cerr << "命令执行失败: " << GetLastError() << endl;
        return;
    }
    
    // 等待子进程结束
    WaitForSingleObject(pi.hProcess, INFINITE);
    
    // 关闭进程和线程句柄(Windows必须手动释放)
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
}

// 内置命令处理(Windows版本,支持cd/exit/pwd)
bool handleBuiltin(const vector<char*>& args) {
    if (args.empty() || args[0] == NULL) {
        return false;
    }
    
    // cd命令:切换目录
    if (strcmp(args[0], "cd") == 0) {
        if (args.size() < 2 || args[1] == NULL) {
            // 无参数时显示当前目录
            char cwd[1024] = {0};
            _getcwd(cwd, sizeof(cwd));
            cout << cwd << endl;
            return true;
        }
        // 切换到指定目录
        if (_chdir(args[1]) == -1) {
            cerr << "cd失败: 目录不存在或无权访问" << endl;
        }
        return true;
    }
    
    // exit命令:退出Shell
    if (strcmp(args[0], "exit") == 0) {
        exit(EXIT_SUCCESS);
    }
    
    // pwd命令:显示当前目录(新增)
    if (strcmp(args[0], "pwd") == 0) {
        char cwd[1024] = {0};
        _getcwd(cwd, sizeof(cwd));
        cout << cwd << endl;
        return true;
    }
    
    return false;
}

int main() {
    string cmd;
    cout << "=== Windows简易Shell (C++98) ===" << endl;
    cout << "支持命令:cd/pwd/exit/系统命令(如dir/echo)" << endl;
    
    while (true) {
        // 显示命令提示符(带当前目录)
        char cwd[1024] = {0};
        _getcwd(cwd, sizeof(cwd));
        cout << "my_shell[" << cwd << "]> ";
        
        // 读取用户输入
        getline(cin, cmd);
        
        // 空命令直接跳过
        if (cmd.empty()) continue;
        
        // 拆分命令参数
        vector<char*> args = splitCommand(cmd);
        
        // 处理内置命令
        if (handleBuiltin(args)) {
            delete[] args[0]; // 释放splitCommand中分配的内存
            continue;
        }
        
        // 执行外部命令
        executeCommand(args);
        
        // 释放内存
        delete[] args[0];
    }
    
    return 0;
}

代码说明 & 运行步骤

  1. 兼容性:全程使用 C++98 标准(无 auto、无 C++11 容器特性),兼容老旧编译器;

  2. 核心功能:实现简易 Shell,支持 cd(内置命令)、ls/cp(外部命令)、exit 退出;

  3. 运行步骤

    复制代码
    # 1. 编译(指定C++98标准)
    g++ -std=c++98 my_shell.cpp -o my_shell
    # 2. 运行
    ./my_shell
    # 3. 测试命令
    my_shell> cd /tmp
    my_shell> ls -l
    my_shell> exit

9.4 系统调用的概念和类型

核心概念

系统调用是用户程序请求操作系统内核服务的唯一方式,是 "用户态" 到 "内核态" 的桥梁:

  • 用户态:权限低,禁止直接访问硬件;
  • 内核态:权限高,可操作 CPU / 内存 / 磁盘等核心资源;
  • 系统调用触发:通过软中断(如 x86 的 int 0x80)或指令(如 syscall)切换到内核态。

系统调用分类

类型 示例 功能说明
进程控制 fork、exit、wait 创建 / 终止 / 等待进程
文件操作 open、read、write 打开 / 读取 / 写入文件
设备管理 ioctl、close 设备控制 / 关闭设备
内存管理 brk、mmap 调整堆大小 / 内存映射
通信管理 pipe、socket 管道 / 网络通信

思维导图

9.5 UNIX 系统调用

核心案例(文件操作类系统调用)

以下是基于 UNIX 系统调用的 C++98 完整代码,实现文件的创建、写入、读取:

复制代码
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <fcntl.h>
// C++98标准,禁用C++11特性
using namespace std;

// 错误处理函数
void error(const char* msg) {
    perror(msg);
    exit(EXIT_FAILURE);
}

int main() {
    const char* filename = "test_syscall.txt";
    int fd; // 文件描述符
    ssize_t n; // 读写字节数
    
    // 1. 系统调用:创建/打开文件(O_CREAT创建,O_RDWR读写,0644权限)
    fd = open(filename, O_CREAT | O_RDWR, 0644);
    if (fd < 0) {
        error("open失败");
    }
    cout << "文件打开成功,文件描述符:" << fd << endl;
    
    // 2. 系统调用:写入文件
    const char* content = "Hello, UNIX系统调用!\n";
    n = write(fd, content, strlen(content));
    if (n < 0) {
        error("write失败");
    }
    cout << "写入字节数:" << n << endl;
    
    // 3. 系统调用:移动文件指针到开头
    lseek(fd, 0, SEEK_SET);
    
    // 4. 系统调用:读取文件
    char buf[1024] = {0}; // C++98不支持初始化列表,手动置0
    n = read(fd, buf, sizeof(buf) - 1);
    if (n < 0) {
        error("read失败");
    }
    cout << "读取内容:" << buf << endl;
    
    // 5. 系统调用:关闭文件
    if (close(fd) < 0) {
        error("close失败");
    }
    cout << "文件关闭成功" << endl;
    
    return 0;
}

代码说明 & 运行步骤

  1. 核心系统调用

    • open():打开 / 创建文件,返回文件描述符(UNIX 中一切皆文件,描述符是唯一标识);
    • write():向文件写入数据,返回实际写入字节数;
    • lseek():调整文件指针位置(此处移到开头,以便读取刚写入的内容);
    • read():从文件读取数据到缓冲区;
    • close():关闭文件,释放文件描述符。
  2. 运行步骤

    复制代码
    # 1. 编译
    g++ -std=c++98 unix_syscall.cpp -o unix_syscall
    # 2. 运行
    ./unix_syscall
    # 3. 查看结果
    cat test_syscall.txt

9.6 系统调用的实现

核心原理

系统调用的实现分为 4 个关键步骤(以 x86 架构为例):

  1. 封装系统调用 :用户程序调用库函数(如write()),库函数封装系统调用号和参数;
  2. 触发软中断 :通过int 0x80触发软中断,CPU 切换到内核态;
  3. 内核处理:内核根据系统调用号查找系统调用表,执行对应的内核函数;
  4. 返回用户态:内核函数执行完成后,将结果返回给用户程序,切换回用户态。

流程图

架构图

完整 C++98 案例(模拟系统调用流程)

复制代码
#include <iostream>
#include <cstdlib>
#include <cstring>
// C++98标准
using namespace std;

// 模拟系统调用号
enum SyscallNum {
    SYS_WRITE = 1,
    SYS_READ = 2,
    SYS_EXIT = 3
};

// 模拟内核态系统调用处理函数
int kernel_write(const char* buf, int len) {
    cout << "[内核态] 执行write系统调用,写入内容:" << buf << endl;
    return len; // 返回写入长度
}

int kernel_read(char* buf, int len) {
    strcpy(buf, "模拟内核返回数据");
    cout << "[内核态] 执行read系统调用,返回数据:" << buf << endl;
    return strlen(buf);
}

// 模拟系统调用表(内核态)
typedef int (*SyscallFunc)(void*, int);
SyscallFunc syscall_table[] = {
    NULL,
    (SyscallFunc)kernel_write, // SYS_WRITE=1
    (SyscallFunc)kernel_read,  // SYS_READ=2
    NULL                       // SYS_EXIT=3
};

// 模拟软中断处理程序(用户态→内核态入口)
int syscall_handler(int num, void* arg1, int arg2) {
    cout << "[中断处理] 接收到系统调用号:" << num << endl;
    if (num < 0 || num >= sizeof(syscall_table)/sizeof(SyscallFunc)) {
        cerr << "[中断处理] 无效的系统调用号" << endl;
        return -1;
    }
    if (syscall_table[num] == NULL) {
        cerr << "[中断处理] 系统调用未实现" << endl;
        return -1;
    }
    // 调用内核服务函数
    return syscall_table[num](arg1, arg2);
}

// 模拟用户态系统调用封装函数
int sys_write(const char* buf, int len) {
    return syscall_handler(SYS_WRITE, (void*)buf, len);
}

int sys_read(char* buf, int len) {
    return syscall_handler(SYS_READ, buf, len);
}

int main() {
    // 用户程序调用系统调用
    cout << "===== 用户态开始 =====" << endl;
    
    // 调用write系统调用
    const char* msg = "Hello, 模拟系统调用!";
    int write_len = sys_write(msg, strlen(msg));
    cout << "[用户态] write返回值:" << write_len << endl;
    
    // 调用read系统调用
    char buf[1024] = {0};
    int read_len = sys_read(buf, sizeof(buf));
    cout << "[用户态] read返回值:" << read_len << ",数据:" << buf << endl;
    
    cout << "===== 用户态结束 =====" << endl;
    return 0;
}

代码说明

  1. 模拟核心逻辑

    • SyscallNum:定义模拟的系统调用号(对应真实 UNIX 的系统调用号);
    • syscall_table:模拟内核的系统调用表(映射调用号到内核函数);
    • syscall_handler:模拟软中断处理程序(用户态→内核态的入口);
    • sys_write/sys_read:模拟用户态库函数(封装系统调用,对用户透明)。
  2. 运行步骤

    复制代码
    # 1. 编译
    g++ -std=c++98 syscall_impl.cpp -o syscall_impl
    # 2. 运行
    ./syscall_impl

习题

  1. 简答题:简述联机命令接口和系统调用的区别与联系。
  2. 编程题:基于 9.3 的简易 Shell,新增pwd(显示当前目录)内置命令。
  3. 分析题:为什么系统调用需要从用户态切换到内核态?直接在用户态执行会有什么问题?
  4. 实操题:修改 9.5 的 UNIX 系统调用代码,实现文件内容的追加写入(提示:使用O_APPEND标志)。

总结

  1. 操作系统接口分为命令接口 (Shell)和程序接口(系统调用),前者面向普通用户,后者面向程序员;
  2. Shell 的核心是命令解释器循环,通过创建子进程执行外部命令,内置命令直接在 Shell 进程中执行;
  3. 系统调用是用户程序访问内核资源的唯一方式,实现流程为 "封装参数→触发软中断→内核处理→返回结果",本文提供的 C++98 代码可直接编译运行,覆盖核心知识点。
相关推荐
zl_vslam2 小时前
SLAM中的非线性优-3D图优化之绝对位姿SE3约束SO3/t形式(十八)
人工智能·算法·计算机视觉·3d
Francek Chen2 小时前
【自然语言处理】02 文本规范化
人工智能·pytorch·深度学习·自然语言处理·easyui
(; ̄ェ ̄)。2 小时前
机器学习入门(十二)ID3 决策树
人工智能·决策树·机器学习
wechat_Neal2 小时前
智能汽车人机交互(HMI)领域的最新研究趋势
人工智能·汽车·人机交互
一起养小猫2 小时前
Flutter for OpenHarmony 实战:碰撞检测算法与游戏结束处理
算法·flutter·游戏
刃神太酷啦2 小时前
Linux 基础 IO 收官:库的构建与使用、进程地址空间及核心知识点全解----《Hello Linux!》(11)
java·linux·c语言·数据库·c++·算法·php
板面华仔2 小时前
机器学习入门(一)——KNN算法
人工智能·算法·机器学习
xixixi777772 小时前
2026 年 1 月 26 日通信与安全行业前沿日报,聚焦核心动态、市场数据与风险事件,为决策提供参考
人工智能
玄同7652 小时前
MermaidTrace库:让Python运行时“自己画出”时序图
开发语言·人工智能·python·可视化·数据可视化·日志·异常