news 2026/11/2 1:50:47

【Linux进程(一)】Linux 进程初探:操作系统如何管理进程?fork() 为什么返回两次?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【Linux进程(一)】Linux 进程初探:操作系统如何管理进程?fork() 为什么返回两次?

目录

一、前言

二、冯诺依曼体系结构

1.疑问:冯诺依曼体系结构为什么要有内存?

三、操作系统

1.是什么

2.核心功能

3.如何理解“管理”

核心思想:先描述,再组织

4.系统调用和库函数

1.概念

2.关系

四、进程

1.进程是什么

1.操作系统是如何管理进程的

2.什么是PCB

3.task_struct内部都有哪些进程属性?

4.操作系统是如何组织PCB的?

2.怎么查看进程?

1./proc

2.ps命令家族

3.补充:系统调用接口

1.getpid&&getppid

2.chdir

3.getcwd

4.初识fork

返回值问题

疑问:为什么pid既等于0又大于0?


一、前言

从本篇开始,我们就要进入学习【Linux进程】的相关知识,学习目标包括:

1.认识冯诺依曼系统。

2.操作系统概念与定位,理解"管理"。

3.深⼊理解进程概念,了解PCB。

4.学习进程状态,学会创建进程,掌握僵⼫进程和孤⼉进程,及其形成原因和危害。

5.了解进程调度,Linux进程优先级,理解进程竞争性与独⽴性,理解并⾏与并发。

6.理解进程切换,以及Linux2.6 kernel,O(1)调度算法架构。

7.理解环境变量,熟悉常⻅环境变量及相关指令, getenv/setenv函数。

8.理解C内存空间分配规律,了解进程内存映像和应⽤程序区别, 认识虚拟地址空间。

八个学习目标博主会拆分成四篇博客向大家一一交付,本篇博主会向大家介绍前三个目标——冯诺依曼体系结构,操作系统和进程PCB相关内容。

在这篇博客中,我们会从计算机硬件的底层数据流动讲起,探讨操作系统是如何通过“先描述,再组织”的哲学来管理软硬件资源的。随后,我们将视角拉近,去看看进程的“身份证”——PCB,以及如何在 Linux 中通过/proc目录和ps命令来观察它们。

当然,本篇还会重点带大家初识fork()系统调用。很多初学者在刚接触fork()时,都会被它“调用一次,返回两次”的特性,以及pid变量在父子进程中“既等于 0 又大于 0”的现象绕晕。希望通过本篇的拆解,能帮你理清这背后的执行逻辑,为后续更深入的进程控制与管理打下基础。

二、冯诺依曼体系结构

截⾄⽬前,我们所认识的计算机,都是由⼀个个的硬件组件组成
输⼊单元:包括键盘, ⿏标,扫描仪, 写板等
中央处理器(CPU):含有运算器和控制器等
输出单元:显⽰器,打印机等
关于冯诺依曼,必须强调⼏点:
这⾥的存储器指的是内存
不考虑缓存情况,这⾥的CPU能且只能对内存进⾏读写,不能访问外设(输⼊或输出设备)(数据层⾯)
外设(输⼊或输出设备)要输⼊或者输出数据,也只能写⼊内存或者从内存中读取。
⼀句话,所有设备都只能直接和内存打交道。

1.疑问:冯诺依曼体系结构为什么要有内存?

1.数据在体系结构中的流动:本质就是CPU不直接和I/O设备打交道,交换数据需要内存来当中介。

2.存储分级的问题。

3.因为内存的存在,让计算机在成本不是很高的情况下就拥有了不错的效率。

三、操作系统

1.是什么

广义上,操作系统是控制和管理计算机系统全部软硬件资源、合理组织程序运行、提供用户接口和应用程序运行环境的系统软件集合。

狭义上,操作系统特指内核,即直接运行在硬件之上、常驻内存、负责最核心资源管理和硬件抽象的那部分程序。操作系统内核通常进行进程管理、文件管理,内存管理和驱动与设备管理。下文我们会提到。

总而言之,操作系统是一个进行软件资源管理的软件。

2.核心功能

操作系统通过对软硬件资源进行管理,实现为用户提供良好的运行环境。

对下,与硬件交互,管理所有的软硬件资源。

对上,为用户程序提供一个良好的执行环境。

3.如何理解“管理”

场景:校长对上千名学生进行管理

核心思想:先描述,再组织

  • 先描述(抽象提取):现实世界有各种各样的学生,怎么让计算机认识他们?通过提取关键属性,用代码“描述”出来。

    • 看右上角的代码:定义了struct Stu结构体。把学生的姓名(name)、学号(number)、性别(sex)、成绩(score)、表现(char 表现[])等作为成员变量。

    • 这样,一个复杂的人,在计算机里就变成了一个由各个字段组成的“数据包”。

  • 再组织(建立关联):只有一个个孤立的“学生数据包”是不够的,还需要把他们管理起来。

    • 看左下角的表头和右侧:通过引入struct Stu *next指针,将一个个独立的学生结构体(张三、李四、王五...)像手拉手一样串连起来。

    • 看右中部的图示:画了一个经典的单链表结构(方框代表结构体,箭头代表next指针)。这就把零散的数据“组织”成了一个整体。

计算机管理硬件:先描述再组织。
1.描述起来,⽤struct结构体
2.组织起来,⽤链表或其他⾼效的数据结构

4.系统调用和库函数

1.概念

系统调用是用户程序请求操作系统内核服务的入口;库函数是用户态代码中可调用的函数,可能封装系统调用,也可能完全在用户态完成计算。

系统调用:

系统调用(System Call)是操作系统内核提供给用户程序的编程接口。
以银行系统为例,对外:银行是一个整体,对用户仅提供银行窗口与银行系统进行交互,为了安全考虑不允许用户直接进入银行系统。这个窗口就是“系统调用接口”,用户程序不能直接操作硬件或内核数据结构,必须通过系统调用“陷入”内核态,请求内核完成受保护的操作。

库:

系统调⽤在使⽤上,功能⽐较基础,对⽤⼾的要求相对也⽐较⾼,所以,有⼼的开发者可以对部
分系统调⽤进⾏适度封装,从⽽形成库,有了库,就很有利于更上层⽤⼾或者开发者进⾏⼆次开
发。

2.关系

库函数和系统调用不是互斥关系,常见关系是:

库函数可以封装系统调用。

例如 C 程序中调用printf()

printf 是库函数,它最终可能通过write系统调用把数据写到文件描述符。但printf还做了格式化、缓冲区管理等工作,这些是在用户态完成的。

四、进程

1.进程是什么

进程 = PCB + 代码和数据

1.操作系统是如何管理进程的

核心:先描述,再组织

描述:OS内核里使用结构体(PCB)对进程属性进行描述

组织:PCB里有一定的数据结构,通过某种数据结构把进程管理(组织)起来

操作系统管理进程,就是通过创建 PCB来描述它,通过调度 PCB 所在的链表来运行它,通过系统调用和中断来切换它的状态,最后通过销毁 PCB来终结它。一切管理,最终都落脚在对 PCB 及其所在队列的增删查改上。

2.什么是PCB

PCB(Process Control Block,进程控制块)

简单一句话概括:PCB 就是操作系统用来“描述”和“管理”进程的一个数据结构(结构体),结构体内部包含进程的属性信息和链接字段。

如果把一个运行中的程序(进程)比作一个活生生的人,那么PCB 就是这个进程的“身份证”+“档案袋”。在操作系统眼里,没有 PCB 就没有进程。

Linux操作系统中的PCB叫做:task_struct

磁盘中的程序分为代码段和数据段,程序从磁盘加载到内存,操作系统内核就会新建一个PCB,此时静态的程序就变成了动态的进程。

3.task_struct内部都有哪些进程属性?

标识符(PID):描述本进程的唯⼀标⽰符,⽤来区别其他进程。

状态(STATE):任务状态,退出代码,退出信号等。

优先级(PRI): 相对于其他进程的优先级。

程序计数器: 程序中即将被执⾏的下⼀条指令的地址。

内存指针:包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针

上下⽂数据: 进程执⾏时处理器的寄存器中的数据。

I/O状态信息: 包括显⽰的I/O请求,分配给进程的I/O设备和被进程使⽤的⽂件列表。

记账信息:可能包括处理器时间总和,使⽤的时钟数总和,时间限制,记账号等。

其他信息

4.操作系统是如何组织PCB的?

所有运⾏在系统⾥的进程都以task_struct双链表的形式存在内核⾥。

2.怎么查看进程?

查看进程通常有两种做法:

1./proc

proc(process进程):/proc 目录下以纯数字命名的子目录,代表的是当前系统中“存在”的所有进程。

  • 你执行ls /proc,看到的那些纯数字命名的目录(比如1、1024),每一个数字就是一个进程的 PID。

  • 你执行cat /proc/1234/status,就能看到 PID 为 1234 的进程的状态、内存占用、父子进程 ID。

  • 你执行ls -l /proc/1234/cwd,就能看到这个进程当前的工作目录(这正好对应了你上一个问题里的getcwd系统调用的底层数据来源)。

1.cwd是什么?

cwd(current work directory)指当前进程启动时的工作路径,也就是我们常说的当前路径。

为什么要有?

答:为了支持进程访问文件时,缺省路径的问题。

2.exe是什么?

exe是 Executable 的缩写,代表可执行文件。

这是一个软链接,说明启动这个进程的实际程序是/usr/bin/bash(即 Bash shell)。

2.ps命令家族

ps (Process Status) 是最基础的进程查看工具,它截取的是执行命令那一瞬间的进程状态。

  • ps aux:最常用。以 BSD 风格列出系统中所有进程,并显示详细的资源占用。

  • ps -ef:以 System V 风格列出所有进程,显示PPID(父进程 ID)、C(CPU使用率)、STIME(启动时间)、CMD(完整命令行)。

  • ps -al:只看当前终端下的进程,长格式,侧重调度信息(优先级、等待状态等)。

  • ps axj:看全系统进程的层级关系(PGID进程组、SID会话、TPGID控制终端)。

ps命令通常和管道搭配使用!

3.补充:系统调用接口

1.getpid&&getppid

功能:获取当前进程的id或当前进程的父进程的id

注意:

自定义类型pid_t 本质是一个int类型,不过是在头文件里被重定义了。

使用这两个接口的时候要包含头文件<unistd.h>!

2.chdir

功能:修改当前进程的工作目录

3.getcwd

功能:获取当前进程的当前路径

4.初识fork

功能:创建子进程

翻译:子进程和父进程运行在独立的内存空间中。在fork()的瞬间,两者的内存空间内容相同。其中一个进程进行的内存写入不会影响另一个进程。

返回值问题

fork()是系统编程中最“反直觉”的函数之一,因为它的执行逻辑是:调用一次,返回两次。

返回值发生的进程含义与作用
> 0(大于0)父进程返回的是新创建的子进程的 PID(进程ID)。
父进程需要拿到这个 ID,以便后续对子进程进行管理(比如wait()等待它结束,或者用kill()杀掉它)。
== 0(等于0)子进程返回 0 表示当前代码正在子进程中运行。
为什么是 0?因为子进程不需要知道自己的 PID(它可以自己调用getpid()获取),它只需要知道“我是子进程”。
==-1出错创建子进程失败。常见原因:系统内存不足,或者当前用户的进程数达到了上限(比如ulimit -u限制)。此时没有子进程产生。

疑问:为什么同一个函数会返回两次?

Fork函数核心要做的几件事:

1.创建子进程

2.拷贝PCB

3.修改个性属性数据(例如PID)

4.放入调度队列

—————————此时子进程已经创建好了

5.return(父子进程共享代码块,都要执行return)


场景:

myproc.c文件示例代码:

#include <stdio.h> #include <unistd.h> #include <sys/wait.h> #include <stdlib.h> int main() { pid_t pid; printf("调用 fork() 之前,只有一个进程运行。PID: %d\n", getpid()); printf("----------------------------------------\n"); // 见证奇迹的时刻:调用一次,返回两次 pid = fork(); if (pid < 0) { // 1. 创建失败 perror("fork 失败"); exit(1); } else if (pid == 0) { // 2. 这里是子进程的代码(返回 0) printf("【子进程】我被创建了!\n"); printf("【子进程】fork() 的返回值是: %d\n", pid); printf("【子进程】我的真实 PID 是: %d\n", getpid()); printf("【子进程】我的父进程 PID 是: %d\n", getppid()); exit(0); // 子进程执行完就退出 } else { // 3. 这里是父进程的代码(返回子进程的 PID,大于 0) printf("【父进程】我创建了一个子进程!\n"); printf("【父进程】fork() 的返回值是: %d\n", pid); printf("【父进程】我的真实 PID 是: %d\n", getpid()); wait(NULL); // 父进程等待子进程结束,回收资源 printf("【父进程】子进程执行完毕了。\n"); } return 0; }

运行结果:

奇怪的是:同时进入了两个互斥的条件代码块!

疑问:为什么pid既等于0又大于0?

1. 准备阶段(只有父进程)

pid_t pid; // 在父进程的内存里,开辟了一块4字节的空间,叫 pid pid = fork(); // 准备调用 fork

此时,世界上只有一个进程(父),内存里只有一个pid变量。

2. 发生奇迹(内核的复制魔法)

当我们调用fork()时,我们上一张图里的“拷贝 PCB”和“分配内存”发生了。
操作系统不仅复制了父进程的代码,还把父进程的整个内存空间(包括那个叫做pid的变量)原封不动地复制了一份给子进程。

这时候,内存里有了两个pid变量:

  • 父进程内存里有一个pid(假设地址是 0x1000)

  • 子进程内存里也有一个pid(地址也是 0x1000,但它们是两块独立的物理内存)

记住前面fork手册里的那句红线:两个进程运行在独立的内存空间中。

3. 赋值阶段(分别塞小纸条)

现在,内核开始执行返回操作(也就是图里的return XXX),准备让它们继续往下跑:

  • 针对父进程:内核把父进程内存里的那个pid变量,填上了子进程的 PID(例如 12345,大于 0)。

  • 针对子进程:内核把子进程内存里的那个pid变量,填上了0。

4. 执行阶段(各回各家,各找各妈)

现在两个进程同时从fork()出来,继续往下执行:

if (pid == 0) { // 只有子进程会走进来,因为子进程内存里的 pid 是 0 } else { // 只有父进程会走进来,因为父进程内存里的 pid 是 12345 (>0) }

父进程看自己内存里的pid,是大于 0 的;
子进程看自己内存里的pid,是等于 0 的。

解读(核心原理:写时复制 Copy-On-Write, COW):

1.刚fork()完,系统为了节省资源,并没有真的把父进程的内存全部拷贝一份给子进程,而是让子进程的虚拟内存指向父进程的物理内存(此时“内容相同”)。

2.但是,一旦父进程或子进程尝试修改内存中的变量(比如执行x = 10),系统内核会立刻拦截这个操作,并给修改内存的那个进程分配一块新的物理内存。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/2 1:50:44

基于opencode实现聊天室项目工具的安装(上)

目录 基础工具的安装 框架安装 基础工具的安装 sudo apt-get install gcc g // 编译器安装sudo apt-get install gdb // 调试器安装sudo apt-get install make cmake // 项目构建工具安装sudo apt-get install lrzsz // 传输工具…

作者头像 李华
网站建设 2026/11/2 1:50:37

【FreeRTOS源码学习笔记】链表的实现

【FreeRTOS源码学习笔记】链表的实现前言一、核心数据结构1.1 ListItem_t 链表节点1.2 MiniListItem_t 迷你节点&#xff08;哨兵位&#xff09;1.3 List_t 链表二、链表的基本形态三、核心操作函数3.1 初始化链表节点3.2 链表初始化3.3 插入&#xff1a;vListInsert 与 vListI…

作者头像 李华
网站建设 2026/11/2 1:49:15

微服务跨服务调用异常序列化失败(Dubbo + Hessian)、第三方云平台拦截(Arrearage)、并发限流429Throttling、配置中心读取失败Nacos DataSource

前言我的电商 AI 客服基于 Spring Cloud Alibaba Dubbo Nacos Spring AI 。在接入阿里云百炼&#xff08;DashScope&#xff09;和向量数据库时&#xff0c;从启动到接口调用&#xff0c;出现了很多问题。这里按我的排错顺序&#xff0c;记录一下报错信息、可能的原因、最终…

作者头像 李华
网站建设 2026/11/2 1:48:59

R 字符串处理完全指南:从基础操作到正则实战

一、为什么 R 的字符串处理值得单独讲在数据分析的日常工作中&#xff0c;字符串处理出现的频率远比想象中高&#xff1a;清洗问卷里的自由文本、解析日志文件、从文件名中提取日期、把变量名拼成公式、给图表批量生成标题……这些任务都离不开字符串。R 的字符串能力由两部分组…

作者头像 李华
网站建设 2026/11/2 1:47:56

请详细解释Python中str函数的作用、参数、返回值,并举例说明其在不同类型数据(如整数、浮点数、

关于 str 这个函数, 我们需要搞清楚它的用途到底在哪里, 它是怎么接收参数的, 最后返回什么样的结果给调用者, 并且通过一些具体的案例演示它如何把整数、浮点数以及布尔值这些不同类型的数据变成字符串格式。答案&#xff1a;中的str函数主要用于将一个对象转换成其对应的字符…

作者头像 李华
网站建设 2026/11/2 1:46:48

Proton-GE 安装教程:让 Steam 上的 Windows 游戏真正跑起来

Proton-GE 安装教程&#xff1a;让 Steam 上的 Windows 游戏真正跑起来 【免费下载链接】proton-ge-custom Compatibility tool for Steam Play based on Wine and additional components 项目地址: https://gitcode.com/gh_mirrors/pr/proton-ge-custom 过场动画整片粉…

作者头像 李华