第一部分:进程与线程管理

1. 进程状态转换
  • 文字解释:进程在其生命周期中会经历多个状态:新建、就绪、运行、阻塞、终止。

    • 就绪 → 运行:被调度器选中,获得CPU。

    • 运行 → 就绪:时间片用完或被更高优先级进程抢占。

    • 运行 → 阻塞:等待某种事件(如I/O完成、信号量)。

    • 阻塞 → 就绪:等待的事件已发生。

  • 代码描述:在Linux中,可以通过ps命令查看进程状态(R=运行/就绪,S=可中断睡眠,D=不可中断睡眠,Z=僵尸)。

bash

# 查看进程状态
ps aux | grep "process_name"
# 状态列显示: R, S, D, Z, T 等
2. 进程调度算法
  • 文字解释

    • FCFS(先来先服务):简单但可能造成“护航效应”。

    • SJF(短作业优先):最优平均等待时间,但难以预知作业长度。

    • 时间片轮转:公平,每个进程获得固定时间片,适合分时系统。

    • 多级反馈队列:综合优先级和时间片,是实际系统常用的算法。

  • 代码描述:模拟简单的时间片轮转。

c

// 伪代码: 时间片轮转调度
#include <stdio.h>
#include <unistd.h>

int main() {
    int time_quantum = 10;  // 时间片10ms
    while (1) {
        // 从就绪队列取出下一个进程
        Process* p = ready_queue.dequeue();
        if (p == NULL) break;
        
        // 运行该进程,最多运行 time_quantum
        int used = p->run(time_quantum);
        if (p->remaining_time > 0) {
            ready_queue.enqueue(p);  // 仍未完成,放回队列末尾
        } else {
            p->terminate();
        }
    }
    return 0;
}
3. 进程间通信(IPC)
  • 文字解释

    • 管道(Pipe):父子进程间单向通信,字节流。

    • 消息队列:数据块传输,有边界,支持多对多。

    • 共享内存:最快的IPC,但需要同步(如信号量)。

    • 信号:异步通知机制,如SIGKILLSIGSTOP

  • 代码描述:使用共享内存和信号量实现生产者-消费者(简化版)。

c

// 生产者-消费者 (使用POSIX信号量和共享内存)
#include <semaphore.h>
#include <sys/mman.h>
#include <fcntl.h>

struct shared {
    int buffer[10];
    int in, out;
    sem_t empty, full, mutex;
};

int main() {
    // 创建共享内存
    int shm_fd = shm_open("/myshm", O_CREAT | O_RDWR, 0666);
    ftruncate(shm_fd, sizeof(struct shared));
    struct shared* sp = mmap(NULL, sizeof(*sp), PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
    
    // 初始化信号量
    sem_init(&sp->empty, 1, 10);  // 空槽位=10
    sem_init(&sp->full, 1, 0);    // 满槽位=0
    sem_init(&sp->mutex, 1, 1);
    
    // 生产者生产一个物品
    sem_wait(&sp->empty);
    sem_wait(&sp->mutex);
    // 放入buffer[in]
    sp->buffer[sp->in] = produce_item();
    sp->in = (sp->in + 1) % 10;
    sem_post(&sp->mutex);
    sem_post(&sp->full);
}

第二部分:内存管理

1. 分页与分段
  • 文字解释

    • 分页:将物理内存划分为固定大小的帧,逻辑内存划分为同样大小的页。通过页表映射逻辑地址到物理地址。优点是无外部碎片,但可能有内部碎片。

    • 分段:按照程序逻辑结构(代码段、数据段、栈段)划分,每个段有独立的基址和限长。优点是有利于保护和共享,但会产生外部碎片。

  • 代码描述:模拟简单的地址转换(页号 + 偏移 → 物理地址)。

c

// 模拟页式地址转换
#define PAGE_SIZE 4096
int page_table[64];  // 页号 -> 帧号

int translate_address(int logical_address) {
    int page_num = logical_address / PAGE_SIZE;
    int offset = logical_address % PAGE_SIZE;
    if (page_num >= 64 || page_table[page_num] == -1) {
        return -1;  // 缺页错误
    }
    return page_table[page_num] * PAGE_SIZE + offset;
}
2. 页面置换算法
  • 文字解释:当物理内存不足时,需将某些页换出到磁盘。

    • FIFO(先进先出):简单但会出现Belady异常(增加帧数反而缺页增多)。

    • LRU(最近最少使用):性能好,但需要硬件支持。

    • Clock(二次机会):近似LRU,使用引用位,操作系统实际常用。

  • 代码描述:模拟FIFO页面置换,计算缺页次数。

python

def fifo_page_replacement(pages, frame_count):
    frames = []
    page_faults = 0
    for page in pages:
        if page not in frames:
            if len(frames) < frame_count:
                frames.append(page)
            else:
                frames.pop(0)   # 移除最早进来的
                frames.append(page)
            page_faults += 1
    return page_faults

# 测试
pages = [7, 0, 1, 2, 0, 3, 0, 4, 2, 3]
print(fifo_page_replacement(pages, 3))  # 输出 9

第三部分:文件系统

1. 文件分配方式
  • 文字解释

    • 连续分配:文件块在磁盘上连续存储,读写快,但会产生外部碎片,且文件增长困难。

    • 链接分配:每个块包含指向下一个块的指针,无外部碎片,但随机访问慢,且指针占用空间。

    • 索引分配:每个文件有一个索引块,记录所有数据块地址。支持随机访问,但索引块大小需权衡。

  • 代码描述:模拟简单的索引分配,读取文件的第N个块。

c

// 模拟索引分配的文件读取
#define MAX_BLOCKS 100
int index_block[MAX_BLOCKS];  // 存储数据块号,-1表示未分配

int read_block(int file_index, int block_num) {
    if (block_num >= MAX_BLOCKS) return -1;
    int data_block = index_block[block_num];
    if (data_block == -1) return -1;
    // 从磁盘读取 data_block 块
    return disk_read(data_block);
}
2. 目录实现
  • 文字解释

    • 线性列表:简单,但查找速度O(n)。

    • 哈希表:查找速度快,但管理复杂(冲突处理)。

    • 树形结构(如Linux的ext4使用B树),支持快速查找和扩展。

  • 代码描述:模拟简单的线性目录查找(简化版)。

c

struct dir_entry {
    char name[256];
    int inode_number;
};

struct dir_entry directory[100];
int dir_size = 0;

int lookup(char* filename) {
    for (int i = 0; i < dir_size; i++) {
        if (strcmp(directory[i].name, filename) == 0) {
            return directory[i].inode_number;
        }
    }
    return -1;  // 文件不存在
}

第四部分:并发与同步

1. 临界区问题与互斥
  • 文字解释:多个进程/线程访问共享资源时,需保证同一时刻只有一个执行单元进入临界区。满足条件:互斥、前进、有限等待。

  • 代码描述:使用pthread_mutex_t实现互斥锁(Linux C)。

c

#include <pthread.h>
#include <stdio.h>

pthread_mutex_t lock;
int shared_counter = 0;

void* increment(void* arg) {
    for (int i = 0; i < 100000; i++) {
        pthread_mutex_lock(&lock);
        shared_counter++;   // 临界区
        pthread_mutex_unlock(&lock);
    }
    return NULL;
}

int main() {
    pthread_t t1, t2;
    pthread_mutex_init(&lock, NULL);
    pthread_create(&t1, NULL, increment, NULL);
    pthread_create(&t2, NULL, increment, NULL);
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    printf("Final counter: %d\n", shared_counter);  // 应为200000
    pthread_mutex_destroy(&lock);
    return 0;
}
2. 死锁的四个必要条件与预防
  • 文字解释

    1. 互斥:资源不能被多个进程同时使用。

    2. 占有并等待:进程持有资源并等待其他资源。

    3. 非抢占:资源只能由持有者主动释放。

    4. 循环等待:存在进程等待环路。

  • 预防策略

    • 破坏“占有并等待”:要求进程一次性申请所有资源。

    • 破坏“循环等待”:对资源进行排序,强制按顺序申请。

  • 代码描述:银行家算法(避免死锁)的简化判断——检查资源分配是否安全。

c

// 银行家算法安全性检查(伪代码)
bool is_safe(int available[], int max[][], int allocation[][]) {
    int finish[processes] = {0};
    int work[] = available;
    while (1) {
        bool found = false;
        for each process i {
            if (!finish[i] && max[i] - allocation[i] <= work) {
                work += allocation[i];
                finish[i] = 1;
                found = true;
            }
        }
        if (!found) break;
    }
    return all(finish == 1);
}

第五部分:一个综合实验:实现一个迷你Shell

这个例子结合了进程管理(fork/exec)、信号处理文件描述符

c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>

#define MAX_CMD 256

int main() {
    char cmd[MAX_CMD];
    while (1) {
        printf("mysh> ");
        fgets(cmd, sizeof(cmd), stdin);
        cmd[strcspn(cmd, "\n")] = '\0';  // 去掉换行
        
        if (strcmp(cmd, "exit") == 0) break;
        
        pid_t pid = fork();
        if (pid == 0) {
            // 子进程: 解析命令并执行
            char* args[10];
            char* token = strtok(cmd, " ");
            int i = 0;
            while (token != NULL && i < 9) {
                args[i++] = token;
                token = strtok(NULL, " ");
            }
            args[i] = NULL;
            execvp(args[0], args);
            perror("execvp failed");
            exit(1);
        } else if (pid > 0) {
            wait(NULL);  // 等待子进程结束
        } else {
            perror("fork failed");
        }
    }
    return 0;
}
Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐