云计算百科
云计算领域专业知识百科平台

第2章 从基础到进阶:现代计算机系统架构与工作原理解析

第2章 从基础到进阶:现代计算机系统架构与工作原理解析

计算机系统是现代科技的核心基础,理解其工作原理对于软件开发和系统设计至关重要。本章将深入探讨计算机系统的基本结构和工作机制,帮助读者建立全面的系统观念。

2.1 系统基本组成结构

现代计算机系统主要由以下几个基本部分组成:处理器(CPU)、存储器(内存)、输入/输出设备以及它们之间的连接总线。这些组件协同工作,共同完成数据处理任务。

处理器作为系统的"大脑",负责执行指令和数据处理;存储器用于保存程序和数据;输入/输出设备实现计算机与外部世界的交互;总线则是连接各组件的"高速公路",保障数据传输。

c

// 简单示例:系统各组件之间的交互
void system_interaction_example() {
// CPU执行指令
int calculation_result = perform_calculation();

// 存储结果到内存
store_to_memory(calculation_result);

// 通过I/O设备输出结果
output_to_device(calculation_result);
}

2.2 处理器与缓存结构

2.2.1 面向用户的缓存系统

处理器中的缓存(Cache)是一种高速小容量的存储器,位于CPU和主存之间,用于加速数据访问。由于处理器速度远快于内存访问速度,缓存的引入有效缓解了这一"速度鸿沟"。

用户可见缓存主要体现在程序执行性能上,虽然程序员通常不直接操作缓存,但了解其工作原理有助于编写高效代码。

c

// 利用缓存局部性原理的代码示例
void cache_friendly_code() {
int matrix[1000][1000];

// 按行访问(缓存友好)
for (int i = 0; i < 1000; i++) {
for (int j = 0; j < 1000; j++) {
matrix[i][j] = i + j;
}
}

// 而非按列访问(缓存不友好)
// for (int j = 0; j < 1000; j++) {
// for (int i = 0; i < 1000; i++) {
// matrix[i][j] = i + j;
// }
// }
}

2.2.2 控制与状态缓存机制

处理器内部还包含多种控制和状态寄存器,用于存储CPU当前工作状态、控制信息和特殊功能标志。这些寄存器对于操作系统和底层程序设计至关重要。

常见的控制和状态寄存器包括:

  • 程序计数器(PC):指向下一条将要执行的指令
  • 指令寄存器(IR):保存当前正在执行的指令
  • 状态寄存器:包含标志位如零标志、进位标志、溢出标志等
  • 控制寄存器:控制CPU操作模式、中断使能等

c

// 模拟处理器状态寄存器的操作
typedef struct {
unsigned int zero_flag : 1; // 零标志位
unsigned int carry_flag : 1; // 进位标志位
unsigned int overflow_flag : 1; // 溢出标志位
unsigned int sign_flag : 1; // 符号标志位
// 其他标志位…
} StatusRegister;

void update_status_register(StatusRegister* sr, int result) {
sr->zero_flag = (result == 0) ? 1 : 0;
sr->sign_flag = (result < 0) ? 1 : 0;
// 更新其他标志位…
}

2.3 指令执行过程分析

2.3.1 指令获取与执行流程

指令执行是计算机系统的核心工作。一条指令的执行通常分为以下几个阶段:

  • 取指令(Fetch):从内存中读取指令
  • 解码(Decode):解析指令内容,确定操作类型和操作数
  • 执行(Execute):执行指令指定的操作
  • 访存(Memory Access):若需要,访问内存
  • 写回(Write Back):将结果写回寄存器
  • 这个过程被称为指令周期,现代处理器通过流水线技术并行处理多条指令的不同阶段,大大提高了执行效率。

    c

    // 模拟简单的指令执行过程
    typedef enum {FETCH, DECODE, EXECUTE, MEMORY, WRITEBACK} InstructionStage;

    void instruction_cycle_simulation() {
    InstructionStage current_stage = FETCH;
    while (1) { // 持续执行指令
    switch (current_stage) {
    case FETCH:
    // 从内存获取指令
    fetch_instruction_from_memory();
    current_stage = DECODE;
    break;
    case DECODE:
    // 解析指令
    decode_instruction();
    current_stage = EXECUTE;
    break;
    case EXECUTE:
    // 执行操作
    execute_operation();
    current_stage = MEMORY;
    break;
    case MEMORY:
    // 内存访问(如果需要)
    if (instruction_needs_memory_access()) {
    perform_memory_access();
    }
    current_stage = WRITEBACK;
    break;
    case WRITEBACK:
    // 写回结果
    write_back_result();
    current_stage = FETCH; // 准备执行下一条指令
    break;
    }
    }
    }

    2.3.2 输入/输出函数机制

    输入/输出(I/O)操作是计算机与外部设备交互的关键。I/O函数提供了程序与外部设备通信的接口,实现数据的输入和输出。

    I/O操作有三种主要实现方式:

    • 程序控制I/O:CPU直接控制I/O操作,轮询设备状态
    • 中断驱动I/O:设备准备好后通过中断通知CPU
    • 直接内存访问(DMA):允许设备直接与内存交换数据,减轻CPU负担

    c

    // 简单的程序控制I/O示例
    #define DEVICE_STATUS_REG 0x1000 // 设备状态寄存器地址
    #define DEVICE_DATA_REG 0x1004 // 设备数据寄存器地址
    #define STATUS_READY 0x01 // 设备就绪状态位

    int programmed_io_read() {
    volatile int* status_reg = (volatile int*)DEVICE_STATUS_REG;
    volatile int* data_reg = (volatile int*)DEVICE_DATA_REG;

    // 轮询等待设备就绪
    while ((*status_reg & STATUS_READY) == 0) {
    // 等待…
    }

    // 设备就绪,读取数据
    return *data_reg;
    }

    2.4 中断系统设计

    中断是计算机系统实现多任务处理的关键机制,允许CPU暂停当前任务,处理更紧急的事件,然后返回继续原任务。

    2.4.1 中断与指令周期关系

    中断检测通常发生在指令执行周期的结束阶段。当一条指令执行完成后,处理器会检查是否有挂起的中断请求。如果有,则会:

  • 保存当前程序状态(程序计数器、标志寄存器等)
  • 跳转到相应的中断处理程序
  • 处理完成后恢复之前的程序状态,继续执行
  • 中断与指令周期紧密结合,确保了系统既能响应外部事件,又能保持程序执行的连续性。

    c

    // 模拟带中断检测的指令周期
    void instruction_cycle_with_interrupts() {
    while (1) {
    // 1. 取指令
    fetch_instruction();

    // 2. 执行指令
    execute_instruction();

    // 3. 中断检测
    if (interrupt_pending()) {
    save_current_state(); // 保存当前状态
    handle_interrupt(); // 处理中断
    restore_previous_state(); // 恢复之前状态
    }
    }
    }

    2.4.2 中断处理机制实现

    中断处理涉及硬件和软件的协同工作。硬件负责检测中断信号、保存部分处理器状态,而软件(中断处理程序)则负责处理具体的中断事件。

    中断处理的典型步骤包括:

  • 保存上下文:保存寄存器值等程序状态
  • 确定中断源:识别引发中断的设备或事件
  • 处理中断:执行相应的处理操作
  • 恢复上下文:恢复之前保存的程序状态
  • 中断返回:返回到被中断的程序继续执行
  • c

    // 简化的中断处理程序结构
    void interrupt_handler() {
    // 1. 保存上下文(在实际系统中,部分由硬件自动完成)
    save_context();

    // 2. 确定中断源
    int interrupt_source = identify_interrupt_source();

    // 3. 根据中断源调用相应的处理函数
    switch (interrupt_source) {
    case TIMER_INTERRUPT:
    handle_timer_interrupt();
    break;
    case KEYBOARD_INTERRUPT:
    handle_keyboard_interrupt();
    break;
    case DISK_INTERRUPT:
    handle_disk_interrupt();
    break;
    // 处理其他中断…
    }

    // 4. 恢复上下文
    restore_context();

    // 5. 中断返回(通常通过特殊指令实现)
    return_from_interrupt();
    }

    2.4.3 多重中断处理技术

    在复杂的系统中,中断可能随时发生,甚至在处理一个中断的过程中可能又产生新的中断。多重中断处理技术通过中断嵌套和优先级机制解决这一问题。

    中断优先级系统允许高优先级中断打断低优先级中断的处理,确保紧急事件得到及时响应。同时,对于相同优先级的中断,系统通常会暂时屏蔽,直到当前中断处理完成。

    c

    // 多重中断处理示例
    void nested_interrupt_handler() {
    // 保存当前中断级别
    int previous_level = current_interrupt_level;

    // 获取新中断的级别
    int new_interrupt_level = get_interrupt_level();

    // 如果新中断优先级更高,则允许处理
    if (new_interrupt_level > previous_level) {
    // 更新当前中断级别
    current_interrupt_level = new_interrupt_level;

    // 允许更高优先级中断(选择性开中断)
    enable_higher_priority_interrupts(new_interrupt_level);

    // 处理当前中断
    process_interrupt();

    // 恢复之前的中断级别
    current_interrupt_level = previous_level;
    }
    else {
    // 低优先级中断,暂时保留,稍后处理
    queue_interrupt_for_later();
    }
    }

    2.4.4 多任务程序设计基础

    中断机制为多任务程序设计提供了基础。操作系统利用时钟中断实现进程调度,允许多个程序看似同时运行,实际上是快速切换执行。

    多任务程序设计的核心概念包括:

    • 任务切换:在不同任务之间切换执行
    • 上下文保存和恢复:保存和恢复任务状态
    • 调度算法:决定下一个执行的任务
    • 同步和互斥:协调任务之间的关系

    c

    // 简化的任务切换示例
    typedef struct {
    void* stack_pointer; // 任务堆栈指针
    int task_id; // 任务ID
    int priority; // 任务优先级
    TaskState state; // 任务状态(就绪、运行、阻塞等)
    // 其他任务信息…
    } Task;

    // 当前运行的任务
    Task* current_task;

    // 任务切换函数
    void switch_to_task(Task* next_task) {
    // 1. 保存当前任务上下文
    save_context(current_task);

    // 2. 更新当前任务指针
    Task* prev_task = current_task;
    current_task = next_task;

    // 3. 恢复新任务的上下文
    restore_context(current_task);

    // 注:实际的上下文切换通常需要汇编实现
    }

    2.5 存储器的层级组织

    计算机系统中的存储设备按速度、容量和成本形成了层次结构,从高速缓存到主存再到辅助存储。这种分层设计平衡了性能和成本,同时利用程序的局部性原理提高整体效率。

    存储层次结构典型安排:

  • 寄存器:处理器内部,访问最快,容量最小
  • 高速缓存:L1/L2/L3缓存,速度快,容量相对较小
  • 主存(RAM):中等速度和容量
  • 固态硬盘(SSD):较快的辅助存储
  • 硬盘驱动器(HDD):大容量,低速辅助存储
  • 网络存储、磁带等:超大容量,最低速度
  • 随着距离处理器越远,存储设备的访问速度降低,但容量增大,单位成本降低。

    c

    // 演示内存层次对性能的影响
    #include <time.h>
    #include <stdio.h>

    void memory_hierarchy_demo() {
    const int SIZE = 100000000; // 1亿个整数
    int* large_array = malloc(SIZE * sizeof(int));
    clock_t start, end;
    double cpu_time_used;

    // 初始化数组
    for (int i = 0; i < SIZE; i++) {
    large_array[i] = i;
    }

    // 测试1: 顺序访问(利用缓存)
    start = clock();
    long sum1 = 0;
    for (int i = 0; i < SIZE; i++) {
    sum1 += large_array[i];
    }
    end = clock();
    cpu_time_used = ((double) (end – start)) / CLOCKS_PER_SEC;
    printf("顺序访问时间: %f秒\\n", cpu_time_used);

    // 测试2: 跳跃访问(缓存命中率低)
    start = clock();
    long sum2 = 0;
    for (int i = 0; i < SIZE; i += 16) { // 步长为16
    sum2 += large_array[i];
    }
    end = clock();
    cpu_time_used = ((double) (end – start)) / CLOCKS_PER_SEC;
    printf("跳跃访问时间: %f秒\\n", cpu_time_used);

    free(large_array);
    }

    2.6 高性能缓存技术

    2.6.1 缓存设计动机

    高速缓存的主要动机是解决处理器与主存之间的"速度鸿沟"。随着处理器速度的不断提升,主存访问速度成为系统性能的主要瓶颈。缓存通过存储最近访问或频繁使用的数据,减少对主存的访问,从而提高系统整体性能。

    处理器与主存速度差距持续扩大:

    • 现代CPU执行速度可达GHz级别(每秒数十亿次操作)
    • 主存访问延迟仍在数十到数百个CPU周期
    • 无缓存系统中,CPU大部分时间都在等待数据

    c

    // 缓存重要性演示
    void cache_importance() {
    // 假设主存访问需要100个CPU周期
    // 缓存访问需要1个CPU周期
    // 缓存命中率为90%

    int memory_access_time = 100; // 周期
    int cache_access_time = 1; // 周期
    float hit_rate = 0.9; // 90%命中率

    // 不使用缓存的平均访问时间
    int no_cache_time = memory_access_time; // 100周期

    // 使用缓存的平均访问时间
    float with_cache_time = hit_rate * cache_access_time +
    (1 – hit_rate) * (cache_access_time + memory_access_time);
    // = 0.9*1 + 0.1*101 = 0.9 + 10.1 = 11周期

    printf("无缓存访问时间: %d周期\\n", no_cache_time);
    printf("有缓存访问时间: %.1f周期\\n", with_cache_time);
    printf("性能提升: %.1f倍\\n", (float)no_cache_time / with_cache_time);
    }

    2.6.2 高速缓存工作原理解析

    缓存的工作建立在程序局部性原理的基础上:

    • 时间局部性:最近访问过的数据很可能再次被访问
    • 空间局部性:访问某个数据后,其附近的数据很可能也会被访问

    基于这些原理,缓存系统不仅存储单个数据,而是以"缓存行"(Cache Line)为单位从主存读取数据,通常为64字节。这种设计利用了空间局部性,提高了缓存效率。

    c

    // 模拟缓存系统的基本工作流程
    typedef struct {
    bool valid; // 有效位
    unsigned int tag; // 标记位
    int data[16]; // 缓存数据(一个缓存行)
    } CacheLine;

    typedef struct {
    CacheLine lines[128]; // 128个缓存行
    } Cache;

    // 从缓存/内存读取数据
    int read_memory(Cache* cache, unsigned int address) {
    // 计算地址对应的缓存行索引和标记
    unsigned int index = (address / sizeof(int)) % 128;
    unsigned int tag = (address / sizeof(int)) / 128;
    unsigned int offset = (address / sizeof(int)) % 16;

    // 检查缓存是否命中
    if (cache->lines[index].valid && cache->lines[index].tag == tag) {
    // 缓存命中
    return cache->lines[index].data[offset];
    } else {
    // 缓存未命中,从"主存"读取整个缓存行
    for (int i = 0; i < 16; i++) {
    // 简化模拟,实际应从主存读取
    cache->lines[index].data[i] = simulate_memory_read(address – offset*sizeof(int) + i*sizeof(int));
    }
    cache->lines[index].valid = true;
    cache->lines[index].tag = tag;

    // 返回请求的数据
    return cache->lines[index].data[offset];
    }
    }

    2.6.3 现代高速缓存设计技术

    现代处理器中的缓存设计涉及多个关键决策:

  • 缓存映射策略

    • 直接映射:每个主存地址只能映射到特定缓存行
    • 全相联:任何缓存行可以存储任何主存数据
    • 组相联:折中方案,提供有限的灵活性
  • 替换策略

    • LRU (最近最少使用):替换最长时间未使用的数据
    • FIFO (先进先出):替换最早加入的数据
    • 随机替换:随机选择要替换的缓存行
  • 写入策略

    • 写直达:数据同时写入缓存和主存
    • 写回:数据只写入缓存,标记为脏,仅在替换时写回主存
  • c

    // 组相联缓存模拟(两路组相联)
    typedef struct {
    bool valid;
    bool dirty; // 脏位,用于写回策略
    unsigned int tag;
    int data[16];
    unsigned int last_used; // 用于LRU算法
    } CacheLine;

    typedef struct {
    CacheLine ways[2][64]; // 2路组相联,64组
    } SetAssociativeCache;

    // LRU替换策略
    int find_lru_way(SetAssociativeCache* cache, unsigned int set) {
    if (cache->ways[0][set].last_used < cache->ways[1][set].last_used) {
    return 0;
    } else {
    return 1;
    }
    }

    // 写回缓存模拟
    void write_to_cache(SetAssociativeCache* cache, unsigned int address, int value) {
    unsigned int set = (address / sizeof(int) / 16) % 64;
    unsigned int tag = (address / sizeof(int)) / (16 * 64);
    unsigned int offset = (address / sizeof(int)) % 16;
    static unsigned int time_counter = 0;
    time_counter++;

    // 检查是否命中
    for (int way = 0; way < 2; way++) {
    if (cache->ways[way][set].valid && cache->ways[way][set].tag == tag) {
    // 缓存命中,写入数据
    cache->ways[way][set].data[offset] = value;
    cache->ways[way][set].dirty = true; // 标记为脏
    cache->ways[way][set].last_used = time_counter;
    return;
    }
    }

    // 缓存未命中,需要找一个位置放入新数据
    // 先检查是否有空闲位置
    for (int way = 0; way < 2; way++) {
    if (!cache->ways[way][set].valid) {
    // 找到空闲位置
    load_cache_line(cache, way, set, address); // 加载缓存行
    cache->ways[way][set].data[offset] = value;
    cache->ways[way][set].dirty = true;
    cache->ways[way][set].last_used = time_counter;
    return;
    }
    }

    // 没有空闲位置,需要替换,使用LRU策略
    int way_to_replace = find_lru_way(cache, set);

    // 如果被替换的行是脏的,需要写回主存
    if (cache->ways[way_to_replace][set].valid &&
    cache->ways[way_to_replace][set].dirty) {
    write_back_to_memory(cache, way_to_replace, set);
    }

    // 加载新的缓存行
    load_cache_line(cache, way_to_replace, set, address);
    cache->ways[way_to_replace][set].data[offset] = value;
    cache->ways[way_to_replace][set].dirty = true;
    cache->ways[way_to_replace][set].last_used = time_counter;
    }

    2.7 输入/输出通信系统

    2.7.1 可编程输入/输出技术

    可编程I/O是最基本的输入/输出控制方式,CPU通过特定指令直接控制I/O设备的操作。这种方式下,CPU不断轮询设备状态,等待设备准备好后再进行数据传输。

    优点:

    • 实现简单,硬件要求低
    • 适用于低速设备

    缺点:

    • CPU利用率低,大量时间用于等待
    • 不适合高速设备或多设备系统

    c

    // 可编程I/O示例:向串口发送一个字符
    #define SERIAL_STATUS_PORT 0x3F8 + 5 // 串口状态寄存器
    #define SERIAL_DATA_PORT 0x3F8 // 串口数据寄存器
    #define SERIAL_READY_TO_SEND 0x20 // 发送就绪位

    void programmed_io_send_char(char c) {
    // 轮询等待串口准备好发送
    while ((inp(SERIAL_STATUS_PORT) & SERIAL_READY_TO_SEND) == 0) {
    // 空循环等待
    }

    // 串口准备好,发送数据
    outp(SERIAL_DATA_PORT, c);
    }

    // 发送字符串
    void programmed_io_send_string(const char* str) {
    while (*str) {
    programmed_io_send_char(*str);
    str++;
    }
    }

    2.7.2 中断驱动型输入/输出实现

    中断驱动I/O解决了可编程I/O的效率问题。CPU发出I/O请求后可以继续执行其他任务,当设备完成操作后通过中断机制通知CPU,CPU再执行相应的数据传输。

    优点:

    • CPU利用率高,可以并行处理其他任务
    • 适合处理偶发性I/O操作

    缺点:

    • 每次数据传输仍需CPU参与
    • 频繁中断会导致上下文切换开销增大

    c

    // 中断驱动I/O示例
    #include <signal.h>

    // 全局变量记录I/O状态
    volatile bool io_operation_complete = false;
    volatile char received_data;

    // 中断处理函数
    void io_interrupt_handler(int signum) {
    // 读取设备数据(简化示例)
    received_data = read_device_data();
    io_operation_complete = true;
    }

    // 初始化中断驱动I/O
    void init_interrupt_driven_io() {
    // 注册中断处理函数
    signal(SIGUSR1, io_interrupt_handler);

    // 配置设备使用中断
    configure_device_for_interrupts();
    }

    // 使用中断驱动I/O读取数据
    char interrupt_driven_read() {
    // 重置完成标志
    io_operation_complete = false;

    // 启动I/O操作
    start_device_read_operation();

    // 继续执行其他任务直到I/O完成
    while (!io_operation_complete) {
    // 执行其他有用的工作
    do_other_processing();

    // 或短暂休眠避免忙等待
    usleep(1000);
    }

    // I/O完成,返回数据
    return received_data;
    }

    2.7.3 直接内存存取技术详解

    直接内存访问(DMA)进一步减少了CPU在I/O操作中的参与。在DMA模式下,CPU只需设置好DMA控制器的参数(源地址、目标地址、传输长度等),然后启动传输,DMA控制器直接控制内存和设备之间的数据传输,完成后才通知CPU。

    优点:

    • CPU几乎不参与数据传输过程,效率最高
    • 适合大量数据传输和高速设备

    缺点:

    • 硬件复杂度增加
    • 可能与CPU争用内存总线

    c

    // DMA传输模拟示例
    typedef struct {
    void* source; // 源地址
    void* destination; // 目标地址
    size_t length; // 传输长度
    bool direction; // 传输方向(0:内存到设备, 1:设备到内存)
    volatile bool busy; // DMA控制器忙状态
    } DMAController;

    // 全局DMA控制器
    DMAController dma_controller;

    // 初始化DMA传输
    void setup_dma_transfer(void* source, void* destination, size_t length, bool direction) {
    // 等待DMA控制器空闲
    while (dma_controller.busy) {
    // 等待…
    }

    // 设置DMA参数
    dma_controller.source = source;
    dma_controller.destination = destination;
    dma_controller.length = length;
    dma_controller.direction = direction;
    }

    // 启动DMA传输
    void start_dma_transfer() {
    // 标记DMA控制器为忙
    dma_controller.busy = true;

    // 在实际硬件中,这里会触发DMA控制器开始传输
    // 在模拟中,我们启动一个线程执行传输
    pthread_t dma_thread;
    pthread_create(&dma_thread, NULL, dma_transfer_thread, NULL);
    pthread_detach(dma_thread);
    }

    // DMA传输线程(模拟DMA控制器的工作)
    void* dma_transfer_thread(void* arg) {
    // 执行数据传输
    if (dma_controller.direction == 0) {
    // 内存到设备
    memcpy(dma_controller.destination, dma_controller.source, dma_controller.length);
    } else {
    // 设备到内存
    memcpy(dma_controller.destination, dma_controller.source, dma_controller.length);
    }

    // 传输完成,标记DMA控制器为空闲
    dma_controller.busy = false;

    // 生成中断通知CPU传输完成
    generate_dma_complete_interrupt();

    return NULL;
    }

    // 使用DMA读取磁盘数据的示例
    void read_disk_block_dma(int block_number, void* buffer) {
    // 计算磁盘块地址
    void* disk_address = calculate_disk_address(block_number);

    // 设置DMA传输
    setup_dma_transfer(disk_address, buffer, BLOCK_SIZE, 1);

    // 启动DMA传输
    start_dma_transfer();

    // CPU可以继续执行其他任务
    // …

    // 如果需要等待传输完成
    wait_for_dma_completion();
    }

    2.8 推荐读物与在线资源

    为了深入理解计算机系统架构,以下是一些推荐的书籍和在线资源:

  • 经典书籍:

    • "计算机组成与设计:硬件/软件接口"(Patterson & Hennessy)
    • "计算机体系结构:量化研究方法"(Hennessy & Patterson)
    • "深入理解计算机系统"(Bryant & O'Hallaron)
  • 在线课程:

    • MIT的"计算机系统架构"课程
    • Stanford的"计算机组成与系统"课程
    • Coursera上的相关专业课程
  • 网站和论坛:

    • Stack Overflow的计算机架构板块
    • Computer Architecture Today博客
    • ACM SIGARCH网站
  • 实践资源:

    • 开源CPU设计项目(如RISC-V)
    • 计算机模拟器和虚拟机
    • 硬件描述语言教程(如Verilog, VHDL)
  • 2.9 关键术语、习题与实践练习

    关键术语
    • 计算机系统架构:计算机系统的组织结构和工作方式
    • 冯·诺依曼架构:存储程序式计算机的基本架构
    • 缓存(Cache):高速小容量存储器,用于加速数据访问
    • 指令周期:取指、译码、执行、访存、写回的循环过程
    • 中断:暂停当前程序执行,处理紧急事件的机制
    • DMA:直接内存访问,无需CPU参与的数据传输方式
    • 流水线:将指令执行过程分解为多个阶段并行执行
    • 存储层次结构:从高速缓存到主存到辅存的存储设备组织
    复习题
  • 描述计算机系统的基本组成部分及其功能。
  • 解释缓存在计算机系统中的作用,以及缓存命中和缓存未命中的概念。
  • 描述指令执行的基本周期,并解释现代处理器如何通过流水线提高效率。
  • 比较程序控制I/O、中断驱动I/O和DMA三种I/O方式的优缺点。
  • 解释中断如何影响指令执行过程,以及处理器如何处理中断。
  • 实践练习

    练习1:测量缓存性能
    编写程序,通过比较顺序访问和随机访问大数组的性能差异,观察缓存对性能的影响。

    c

    #include <stdio.h>
    #include <stdlib.h>
    #include <time.h>

    #define ARRAY_SIZE 100000000 // 1亿个整数

    int main() {
    int* array = (int*)malloc(ARRAY_SIZE * sizeof(int));
    if (!array) {
    printf("内存分配失败\\n");
    return 1;
    }

    // 初始化数组
    for (int i = 0; i < ARRAY_SIZE; i++) {
    array[i] = i;
    }

    // 测试1: 顺序访问
    clock_t start = clock();
    long sum1 = 0;
    for (int i = 0; i < ARRAY_SIZE; i++) {
    sum1 += array[i];
    }
    clock_t end = clock();
    double time1 = ((double)(end – start)) / CLOCKS_PER_SEC;

    // 测试2: 随机访问
    int* indices = (int*)malloc(ARRAY_SIZE / 100 * sizeof(int));
    for (int i = 0; i < ARRAY_SIZE / 100; i++) {
    indices[i] = rand() % ARRAY_SIZE;
    }

    start = clock();
    long sum2 = 0;
    for (int i = 0; i < ARRAY_SIZE / 100; i++) {
    sum2 += array[indices[i]];
    }
    end = clock();
    double time2 = ((double)(end – start)) / CLOCKS_PER_SEC;

    printf("顺序访问时间: %.6f秒\\n", time1);
    printf("随机访问时间(总数1/100): %.6f秒\\n", time2);
    printf("随机访问时间(归一化): %.6f秒\\n", time2 * 100);
    printf("随机/顺序时间比: %.2f\\n", (time2 * 100) / time1);

    free(array);
    free(indices);
    return 0;
    }

    练习2:模拟指令周期
    实现一个简单的处理器模拟器,执行基本指令并处理中断。

    c

    #include <stdio.h>
    #include <stdbool.h>
    #include <stdlib.h>

    // 指令类型
    typedef enum {
    INST_ADD, // 加法
    INST_SUB, // 减法
    INST_LOAD, // 加载数据
    INST_STORE, // 存储数据
    INST_JUMP, // 跳转
    INST_HALT // 停止
    } InstructionType;

    // 指令结构
    typedef struct {
    InstructionType type;
    int operand1;
    int operand2;
    int operand3;
    } Instruction;

    // CPU状态
    typedef struct {
    int registers[16]; // 16个通用寄存器
    int program_counter; // 程序计数器
    bool halt; // 停止标志
    bool interrupt_pending; // 中断挂起标志
    int interrupt_vector; // 中断向量
    } CPUState;

    // 内存模拟
    #define MEMORY_SIZE 1024
    int memory[MEMORY_SIZE];

    // 指令内存
    Instruction program[] = {
    {INST_LOAD, 0, 100, 0}, // 将内存地址100的值加载到寄存器0
    {INST_LOAD, 1, 101, 0}, // 将内存地址101的值加载到寄存器1
    {INST_ADD, 2, 0, 1}, // 寄存器0和寄存器1相加,结果存入寄存器2
    {INST_STORE, 2, 102, 0}, // 将寄存器2的值存入内存地址102
    {INST_HALT, 0, 0, 0} // 停止执行
    };

    // 初始化CPU和内存
    void initialize_system(CPUState* cpu) {
    // 初始化CPU状态
    for (int i = 0; i < 16; i++) {
    cpu->registers[i] = 0;
    }
    cpu->program_counter = 0;
    cpu->halt = false;
    cpu->interrupt_pending = false;

    // 初始化内存
    for (int i = 0; i < MEMORY_SIZE; i++) {
    memory[i] = 0;
    }

    // 设置测试数据
    memory[100] = 25; // 第一个操作数
    memory[101] = 17; // 第二个操作数
    }

    // 执行单条指令
    void execute_instruction(CPUState* cpu, Instruction inst) {
    switch (inst.type) {
    case INST_ADD:
    cpu->registers[inst.operand1] = cpu->registers[inst.operand2] +
    cpu->registers[inst.operand3];
    break;
    case INST_SUB:
    cpu->registers[inst.operand1] = cpu->registers[inst.operand2] –
    cpu->registers[inst.operand3];
    break;
    case INST_LOAD:
    cpu->registers[inst.operand1] = memory[inst.operand2];
    break;
    case INST_STORE:
    memory[inst.operand2] = cpu->registers[inst.operand1];
    break;
    case INST_JUMP:
    cpu->program_counter = inst.operand1 – 1; // -1因为后面会+1
    break;
    case INST_HALT:
    cpu->halt = true;
    break;
    }
    }

    // 中断处理
    void handle_interrupt(CPUState* cpu) {
    printf("处理中断: 向量 %d\\n", cpu->interrupt_vector);

    // 简单的中断处理:将中断向量存入内存特定位置
    memory[500] = cpu->interrupt_vector;

    cpu->interrupt_pending = false;
    }

    // 模拟指令周期
    void instruction_cycle(CPUState* cpu) {
    while (!cpu->halt) {
    // 1. 检查中断
    if (cpu->interrupt_pending) {
    handle_interrupt(cpu);
    }

    // 2. 取指令
    Instruction current_instruction = program[cpu->program_counter];

    // 3. 执行指令
    execute_instruction(cpu, current_instruction);

    // 4. 更新程序计数器
    cpu->program_counter++;

    // 打印CPU状态
    printf("PC: %d, R0: %d, R1: %d, R2: %d\\n",
    cpu->program_counter, cpu->registers[0],
    cpu->registers[1], cpu->registers[2]);
    }

    printf("程序执行完毕\\n");
    }

    // 生成模拟中断
    void generate_interrupt(CPUState* cpu, int vector) {
    cpu->interrupt_pending = true;
    cpu->interrupt_vector = vector;
    }

    int main() {
    CPUState cpu;
    initialize_system(&cpu);

    // 在第2条指令执行后生成中断
    int interrupt_after = 2;

    // 修改主循环以在特定指令后生成中断
    while (!cpu.halt) {
    // 检查中断
    if (cpu.interrupt_pending) {
    handle_interrupt(&cpu);
    }

    // 取指令
    Instruction current_instruction = program[cpu.program_counter];

    // 执行指令
    execute_instruction(&cpu, current_instruction);

    // 更新程序计数器
    cpu.program_counter++;

    // 打印CPU状态
    printf("PC: %d, R0: %d, R1: %d, R2: %d\\n",
    cpu.program_counter, cpu.registers[0],
    cpu.registers[1], cpu.registers[2]);

    // 在指定指令后生成中断
    if (cpu.program_counter == interrupt_after) {
    printf("生成中断!\\n");
    generate_interrupt(&cpu, 42);
    }
    }

    printf("程序执行完毕\\n");
    printf("计算结果(内存地址102): %d\\n", memory[102]);

    return 0;
    }

    通过本章的学习,读者应该能够理解计算机系统的基本组成和工作原理,为后续深入学习操作系统、编译原理和计算机网络等内容奠定基础。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 第2章 从基础到进阶:现代计算机系统架构与工作原理解析
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!