结构体与联合体在操作系统中的应用

说实话,很多学C语言的朋友都觉得结构体和联合体就是个“高级点的数组”。但等你真正去啃操作系统源码时,你会发现——整个操作系统的核心数据结构,几乎都是用结构体和联合体搭起来的

今天我就带你看看,这三个经典场景:进程控制块(PCB)、文件描述符表、系统调用参数。它们是怎么用结构体和联合体实现的,以及我踩过的那些坑。

一、进程控制块(PCB)——操作系统的“身份证”

每个进程在操作系统里都有一个“身份证”,这就是PCB。我当年第一次看Linux源码时,差点被task_struct吓到——好家伙,几百行!但说白了,它就是个巨大的结构体。

核心要点:PCB是结构体的典型应用,它把进程的所有属性打包在一起。

// 简化的进程控制块
struct task_struct {
    // 进程状态
    volatile long state;    // -1 不可运行, 0 可运行, >0 已停止
    
    // 进程ID
    pid_t pid;
    
    // 进程调度信息
    unsigned int policy;    // 调度策略
    int prio;               // 动态优先级
    int static_prio;        // 静态优先级
    
    // 内存管理
    struct mm_struct *mm;   // 进程地址空间
    
    // 文件系统信息
    struct fs_struct *fs;   // 文件系统信息
    
    // 打开的文件
    struct files_struct *files;  // 文件描述符表
    
    // 信号处理
    struct signal_struct *signal;
    
    // 时间统计
    cputime_t utime, stime;  // 用户态、内核态时间
};

你看,这个结构体里什么都有:状态、ID、调度信息、内存、文件、信号……一个进程需要的所有东西,全在这里了

我个人习惯在写嵌入式RTOS时,把PCB设计得更精简一些。比如这样:

// 嵌入式RTOS中的精简PCB
typedef struct {
    uint32_t *sp;           // 栈指针
    uint32_t stack_size;    // 栈大小
    uint8_t priority;       // 优先级
    uint8_t state;          // 状态:就绪、运行、阻塞
    char name[16];          // 进程名
    void (*entry)(void*);   // 入口函数
    void *args;             // 参数
} os_pcb_t;

我的经验:在资源受限的MCU上,PCB别搞太复杂。我曾经在一个项目里把PCB设计成Linux那样,结果RAM直接爆了。后来精简到10个字段,跑得稳稳的。

二、文件描述符表——结构体数组的妙用

文件描述符表,说白了就是一个结构体数组。每个进程打开一个文件,就在这个数组里占一个位置。

为什么会用结构体数组?因为每个文件描述符需要记录的信息不止一个:文件指针、访问模式、状态标志……这些用结构体来组织再合适不过了。

// 文件描述符表的核心结构
struct file {
    mode_t f_mode;          // 文件访问模式
    loff_t f_pos;           // 文件读写位置
    unsigned int f_flags;   // 文件状态标志
    struct file_operations *f_op;  // 文件操作函数表
    void *private_data;     // 私有数据
    atomic_t f_count;       // 引用计数
};

// 每个进程的文件描述符表
struct files_struct {
    atomic_t count;         // 引用计数
    struct file **fd_array; // 文件描述符数组
    int max_fds;            // 最大文件描述符数
};

嗯,这里要注意一个细节:fd_array是个二级指针。为什么?因为文件描述符表是动态增长的。一开始只分配几个,不够了再扩展。我见过不少新手直接写死数组大小,结果文件一多就崩了。

避坑指南:我曾经在一个网络服务器项目里,把fd_array写成固定大小256。结果客户一上线就开了300个连接,程序直接段错误。后来改成动态分配,问题解决。

三、系统调用参数——联合体的经典用法

系统调用是用户程序进入内核的“大门”。但问题来了:不同的系统调用,需要的参数类型和数量都不一样。怎么办?

答案就是:联合体

// 系统调用参数联合体
union syscall_args {
    // 文件操作
    struct {
        const char *pathname;
        int flags;
        mode_t mode;
    } open;
    
    // 内存操作
    struct {
        void *addr;
        size_t length;
        int prot;
        int flags;
    } mmap;
    
    // 进程控制
    struct {
        pid_t pid;
        int sig;
    } kill;
    
    // 网络操作
    struct {
        int sockfd;
        struct sockaddr *addr;
        socklen_t addrlen;
    } connect;
};

// 系统调用入口
long syscall_handler(int nr, union syscall_args *args) {
    switch (nr) {
        case SYS_OPEN:
            return sys_open(args->open.pathname, 
                           args->open.flags, 
                           args->open.mode);
        case SYS_MMAP:
            return sys_mmap(args->mmap.addr,
                           args->mmap.length,
                           args->mmap.prot,
                           args->mmap.flags);
        // ...
    }
}

你看,联合体在这里的作用就是节省空间。所有系统调用的参数共用一块内存,一次只用一个。这比用结构体省多了——如果用结构体,你得把每种系统调用的参数都列出来,那得多大?

核心思想:联合体适合“多选一”的场景。系统调用参数、协议解析、寄存器模拟……这些地方用联合体,既清晰又高效。

四、知识体系总览

下面这张图,帮你理清结构体和联合体在操作系统中的角色:

结构体与联合体在操作系统中的应用 进程控制块 (PCB) • 进程状态 • 进程ID • 调度信息 • 内存管理 • 文件系统 • 信号处理 • 时间统计 → 结构体打包所有属性 文件描述符表 • 文件访问模式 • 文件读写位置 • 文件状态标志 • 操作函数表 • 私有数据 • 引用计数 → 结构体数组管理 系统调用参数 • 文件操作参数 • 内存操作参数 • 进程控制参数 • 网络操作参数 • 设备控制参数 → 联合体节省空间 结构体:打包管理 | 联合体:多选一节省空间 | 两者结合:高效灵活

五、实际项目中的组合用法

在实际的操作系统代码里,结构体和联合体经常嵌套使用。比如,PCB里可能包含一个联合体,用来表示进程的不同状态:

// 进程状态联合体
union process_state {
    struct {
        uint8_t running : 1;
        uint8_t ready   : 1;
        uint8_t blocked : 1;
        uint8_t zombie  : 1;
    } bits;
    uint8_t value;
};

// PCB中使用联合体
typedef struct {
    char name[32];
    pid_t pid;
    union process_state state;  // 状态用联合体表示
    uint32_t *stack_ptr;
    // ...
} pcb_t;

这样设计的好处是:既可以用位域单独操作每个状态位,也可以整体读写状态值。我在调试时经常直接打印state.value,一眼就能看出进程处于什么状态。

小技巧:用联合体+位域的组合,可以做到“既能原子操作,又能位操作”。这在操作系统的同步机制里特别有用。

六、总结

好了,今天的内容就到这里。结构体和联合体在操作系统里的应用,说白了就是:

  • PCB:用结构体把进程的所有属性打包在一起,方便管理
  • 文件描述符表:用结构体数组管理多个打开的文件
  • 系统调用参数:用联合体让不同系统调用共用参数空间

这些设计思想,你在写嵌入式系统、驱动开发、甚至大型应用时都能用上。下次写代码时,不妨想想:这里用结构体还是联合体?或者两者结合?


公众号:蓝海资料掘金营,微信deep3321