ZeroOne AI
← 返回文章列表

Linux系统篇(十一)——进程(一):从零入门操作系统:冯诺依曼体系到进程的完整理解

👁 4
分类:工业互联网

从冯诺依曼体系讲起,理解操作系统内核与系统调用,再到进程与 PCB,用 fork 与写时拷贝实战入门进程编程

项目背景

计算机是现代生活与编程学习的基础,而操作系统是连接硬件与软件的核心桥梁。想真正理解程序如何运行、系统如何工作,必须从底层原理开始:程序为什么必须先加载到内存?CPU 为什么只能和内存打交道?进程和程序到底有什么区别?

本文将循序渐进地梳理操作系统相关知识:从冯·诺依曼体系结构出发,理解操作系统的定位,再经由系统调用进入进程的世界,最后用 fork 实战创建进程,帮助读者建立完整的底层认知框架。

技术方案

1. 冯·诺依曼体系结构:计算机的骨架

冯·诺依曼体系是现代计算机的底层逻辑,定义了计算机的五大核心部件与数据流动规则。

核心组成:

关键规则:

性能视角:

设备的"带宽"决定了系统瓶颈,速度排序为:

寄存器 > 高速缓存 > 内存 > 磁盘/外设

速度从上到下递减,成本也随之降低。操作系统的核心工作之一,就是通过调度和缓存让数据尽量停留在高速设备中,减少低速 IO 等待。

2. 操作系统:软硬件之间的"大管家"

操作系统是一组管理软硬件资源的程序集合,既是硬件的管理者,也是上层软件的服务提供者。

两种口径:

设计目的:

校园类比:

可以把内核、系统服务与进程的关系想象成校园管理:校长(内核)在办公楼制定资源规则与调度秩序,握着分配教室、图书馆、网络的最终决定权;辅导员(系统服务/中间层)守在教学楼接收学生申请;学生(应用进程)需要教室(CPU 时间)、座位(内存)、黑板(显示设备)、网络接口(外设),但谁也不能直接闯进校长办公室,只能先向辅导员提交申请。

内核只需握住第一个学生节点的地址,就能通过单向链表遍历管理全校学生:新生入学就创建节点并链入链表,毕业就摘除节点,状态变化就更新节点信息——内核无需直接接触每个学生,就能高效完成全校资源与秩序的管控。

计算机硬件管理哲学总结:先描述(用 struct),再组织(用链表等高效数据结构)。

3. 系统调用与库函数

操作系统为了安全,将运行空间划分为用户态内核态。用户程序无法直接操作硬件,必须通过系统调用向内核发起请求。

银行柜台模型:

系统调用与库函数的关系:

库函数(如 C 标准库)是对系统调用的封装,提供更易用的接口。例如 printf() 最终会调用 write() 系统调用把数据输出到标准输出设备。库函数运行在用户态,而系统调用会触发用户态到内核态的切换。

系统架构

进程:程序运行的动态实体

程序是存储在磁盘上的静态文件,进程是程序运行起来后在内存中的动态实体。

PCB 的主要内容:

类别说明
标识符描述本进程的唯一标识,用于区分其他进程
状态任务状态、退出代码、退出信号等
优先级相对其他进程的优先级
程序计数器下一条即将执行的指令地址
内存指针程序代码、进程数据及共享内存块的指针
上下文数据进程执行时处理器寄存器中的数据
I/O 状态信息I/O 请求、分配的设备、使用的文件列表
记账信息处理器时间总和、时钟数、时间限制、记账号等

进程的组织: Linux 内核中,所有进程以 task_struct 双向链表的形式存在内核中。

进程关键属性:

task_struct 核心字段示意:

struct task_struct {
    // 1. 进程标识
    pid_t pid;                        // 进程ID
    pid_t tgid;                       // 线程组ID(主线程PID = 进程ID)
    struct task_struct *parent;       // 父进程指针
    struct list_head children;        // 子进程链表头
    uid_t uid;                        // 真实用户ID
    gid_t gid;                        // 真实组ID

    // 2. 状态与调度
    volatile long state;              // 进程状态(核心字段)
    int prio, static_prio, normal_prio; // 优先级
    unsigned int rt_priority;         // 实时进程优先级
    struct sched_entity se;           // 调度实体

    // 3. 内存管理
    struct mm_struct *mm;             // 用户态地址空间
    struct mm_struct *active_mm;      // 内核态地址空间

    // 4. 文件与IO
    struct files_struct *files;       // 文件描述符表
    struct fs_struct *fs;             // 文件系统信息

    // 5. 信号处理
    struct signal_struct *signal;     // 信号相关全局信息
    struct sighand_struct *sighand;   // 信号处理函数集合
    sigset_t blocked;                 // 阻塞的信号集

    // 6. 上下文与栈
    void *stack;                      // 进程内核栈指针
    struct thread_struct thread;      // CPU寄存器上下文

    // 7. 时间统计
    cputime_t utime, stime;           // 用户态/内核态耗时
};

实施过程

1. 查看进程

Linux 中可用以下方式查看进程:

ls /proc    # 查看内核暴露的进程信息目录
top         # 动态监控进程资源占用
ps          # 静态查看进程快照

2. 通过系统调用获取进程标识符

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main()
{
    printf("我的进程:%d\n", getpid());   // 获取当前进程 PID
    printf("我的父进程:%d\n", getppid()); // 获取父进程 PID
    return 0;
}

3. 通过 fork 创建进程

fork() 会创建一个新进程,返回值有三种情况:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main()
{
    int ret = fork();
    printf("hello proc:%d, ret:%d\n", getpid(), ret);
    sleep(1);
    return 0;
}

为什么会有两行输出?因为 fork() 创建了一个与父进程几乎一模一样的子进程,此后两个进程各自独立运行,都从 fork() 之后的代码继续执行。

4. 区分父子进程

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main()
{
    int ret = fork();
    if (ret < 0)
    {
        perror("fork fail!\n");
        return 1;
    }
    else if (ret == 0)
    {
        printf("我是一个子进程\n");
    }
    else
    {
        printf("我是一个父进程\n");
    }
    return 0;
}

注意:一个变量,两个返回值——因为 fork 之后存在两个进程,各自持有自己的变量副本。

关键特性:

5. 进程独立性:写时拷贝机制

父子进程的数据看似共享,实际通过**写时拷贝(Copy-On-Write, COW)**实现真正独立:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main()
{
    int val = 100;
    pid_t pid = fork();

    if (pid == 0)
    {
        // 子进程修改 val
        val = 200;
        printf("子进程:val = %d,地址:%p\n", val, &val);
    }
    else
    {
        sleep(1);   // 等待子进程先执行
        printf("父进程:val = %d,地址:%p\n", val, &val);
    }
    return 0;
}

运行结果中父子进程的 val 值互不影响,印证了写时拷贝带来的数据独立性。

应用价值

SEO关键词

Linux, 操作系统, 冯诺依曼, 进程, PCB, 系统调用, fork, 写时拷贝

评论(0