01 Linux 操作系统分层结构

一、四层分层模型(从上到下)

  1. 应用层:自己写的程序、各类软件(app)
  2. C 库函数层:标准 IO(printf/fopen),封装底层系统调用
  3. 内核层:系统调用(open/read/write)、驱动程序,操作系统核心
  4. 硬件层:磁盘、键盘、显示器、网卡等物理设备

通俗理解

你写printf("hello") → 调用 C 标准库printf → 库内部调用内核系统调用write → 内核驱动控制显卡把文字打印到屏幕。

二、32 位系统 4G 虚拟内存分区(通俗拆解)

32 位程序总共拥有 4GB 虚拟地址空间,分为两大块:

1. 内核空间(3G ~ 4G,高地址)

  • 存放操作系统内核代码、驱动、内核数据
  • 用户程序完全不能直接访问,想要操作硬件必须通过系统调用切换到内核态
  • 所有进程共享同一份内核空间

2. 用户空间(0G ~ 3G,低地址,每个进程独立)

从低地址到高地址依次划分:

  1. 代码段 .text:程序编译后的机器指令(只读)
  2. 已初始化数据段 .data:定义时赋值的全局 / 静态变量 int a=10;
  3. 未初始化数据段 .bss:只声明不赋值的全局 / 静态变量 int b;
  4. 堆 heapmalloc/free动态申请释放内存,地址向上增长
  5. 栈 stack:局部变量、函数参数、函数返回地址,地址向下增长
  6. 空白未使用虚拟内存区间

补充知识点

64 位系统虚拟内存空间极大,分区逻辑一致,只是地址范围更大; 每个进程拥有独立用户空间,进程之间内存隔离,互不干扰。

02 标准 IO 介绍

2.1 IO 基础概念

IO 定义

IO = Input/Output,输入输出;程序和外部设备(文件、键盘、屏幕、网络)之间传输数据。

IO 作用

  • 输入:程序读取外部数据(键盘输入、读取本地文件、接收网络数据)
  • 输出:程序向外输出数据(打印屏幕、写入日志文件、发送网络消息)

2.2 IO 两大分类

分类 1:按实现层级(核心考点)

  1. 系统调用 IO(文件 IO) 底层内核原生接口:open/read/write/close/lseek
  • 运行在内核空间,每次读写都要切换用户态→内核态,开销大
  • 无用户层缓冲区,操作精细,实时性高
  • 平台不兼容:Linux 和 Windows 系统调用完全不一样
  1. 库函数 IO(标准 IO) C 标准库封装:printf/scanf/fopen/fgets/fwrite
  • 运行在用户空间,自带缓冲区,减少系统调用次数,效率更高
  • ANSI C 统一标准,Linux、Windows、macOS 代码通用,移植性强

分类 2:按应用场景

  1. 文件 IO:读写磁盘 txt、日志、配置文件
  2. 设备 IO:读写键盘、显示器、鼠标;Linux「一切皆文件」,硬件全部抽象成文件,读写硬件和读写文件代码写法一致
  3. 网络 IO:浏览器、APP 网络通信,收发网络数据包

2.3 系统调用 vs 标准库函数对比表(原文 + 通俗补充)

表格

对比维度 系统调用 (open/read/write) 标准 IO 库函数 (fopen/printf)
层级 内核底层接口 用户态高层封装,基于系统调用实现
运行空间 内核态,需要 CPU 特权切换 用户态,无需频繁切换内核
性能开销 每次读写都切换内核,频繁操作开销大 自带缓冲区,攒一批数据再调用一次系统调用,开销更低
移植性 依赖操作系统,跨系统无法直接使用 ANSI C 标准,全平台通用
核心特点 无用户缓冲区,实时性强 三层缓冲机制,减少硬件交互,开发简单

2.4 标准 IO 详细介绍

定义

标准 IO 是<stdio.h>提供的一套输入输出函数,由 C 语言国际标准 ANSI C 规定,所有操作系统都实现这套接口。

三大优势

  1. 跨平台:一套代码 Windows/Linux/macOS 直接编译运行
  2. 缓冲提速:用户内存开辟缓冲区,攒满数据再调用系统调用,减少磁盘交互,大幅提升 IO 速度
  3. 开发简单:屏蔽硬件底层细节,不用关心磁盘、显卡驱动,一行函数完成读写

标准 IO 完整执行流程

程序调用printf(用户态库函数)→ 数据存入用户态缓冲区 触发刷新条件(缓冲区满 / 换行 / 程序结束)→ 调用write系统调用(切换内核态) 内核把数据存入内核页缓存 → 内核异步写入硬件屏幕 / 磁盘

03 流 (stream)

3.1 流定义

所有 IO 操作都抽象成字节流动,数据从外部流入程序 = 输入流,程序数据流出到外部 = 输出流,统一称为流。

通俗比喻

流就是一根水管,字节就是水流;读数据 = 水流入程序,写数据 = 水流出程序。

3.2 流两种分类

  1. 文本流 按 ASCII/UTF-8 字符处理,适合 txt、日志、代码文件;换行符\n会根据系统自动转换。
  2. 二进制流 直接读取原始二进制字节,不做任何字符转换;适合图片、视频、可执行程序、压缩包。

重点:复制图片必须用二进制流,用文本流会损坏文件。

3.3 程序默认自动打开 3 个标准流(进程启动自带,无需 fopen)

表格

流名称 指针常量 绑定设备 缓冲特性 作用
标准输入流 stdin 键盘 行缓冲 scanf、getchar 读取用户输入
标准输出流 stdout 终端屏幕 行缓冲 printf 打印普通信息
标准错误流 stderr 终端屏幕 无缓冲 perror、错误打印,立刻输出不缓存

通俗解释缓冲差异

  • stdout 行缓冲:不加\n文字不会立刻打印,等到换行才输出;
  • stderr 无缓冲:报错信息马上显示,不会卡住看不到,专门用来打印错误。

04 FILE 对象

4.1 FILE 对象是什么

FILE 是一个结构体,用来存储当前打开文件 / 流的全部信息:文件描述符、缓冲区起始 / 结束地址、读写指针、文件状态标志、EOF 结束标记、错误标记等。 简化内部结构(glibc 真实源码):

c

运行

struct _IO_FILE {
    int _fileno;         // 底层文件描述符(数字0/1/2对应标准流)
    char* _IO_buf_base;  // 缓冲区开头
    char* _IO_buf_end;   // 缓冲区末尾
    char* _IO_read_ptr;  // 当前读位置
    char* _IO_write_ptr; // 当前写位置
    int _flags;          // 读写、错误、结束标志
};
typedef struct _IO_FILE FILE;

4.2 FILE 指针核心规则

  1. 结构体本身体积大,标准 IO不直接操作结构体变量,全部使用FILE*指针间接访问;
  2. 创建 FILE 指针:FILE *fp = fopen("a.txt", "r");
  3. 销毁、释放资源:fclose(fp);(同时刷新缓冲区)
  4. 所有标准 IO 函数参数都需要 FILE 指针:fread/fwrite/fgets/ftell

4.3 FILE 与底层系统调用的关系

  • FILE 是用户态标准库抽象
  • 内核用数字文件描述符 fd管理文件(0=stdin,1=stdout,2=stderr);
  • FILE 结构体内部保存 fd,标准 IO 函数底层会通过 fd 调用read/write系统调用;
  • 用户缓冲区存在 FILE 结构体内部,是标准 IO 提速的核心。

拓展章节:系统头文件 tags 生成 & vim 代码跳转

作用

系统头文件存放在/usr/include,里面有 printf、FILE、fork 等标准定义;生成 tags 索引后,vim 可以一键跳转到函数 / 结构体源码定义,方便阅读底层库代码。

完整操作步骤(Ubuntu)

1. 安装 ctags 工具

bash

运行

sudo apt install exuberant-ctags
# 验证安装
ctags --version

2. 进入系统头文件目录,生成全局 tags(需要 sudo)

bash

运行

cd /usr/include
sudo ctags -R --c-kinds=+p --fields=+iaS --extra=+q .

参数释义:

  • -R:递归扫描所有子目录头文件
  • --c-kinds=+p:额外索引函数原型声明
  • --fields=+iaS:记录变量作用域、结构体继承信息
  • --extra=+q:增强宏、类型定义检索

3. 配置 vim 永久加载 tags 索引

bash

运行

echo "set tags+=/usr/include/tags" >> ~/.vimrc

4. vim 跳转使用方法

  1. 光标放在FILE/printf上,快捷键 Ctrl+] 跳转到定义
  2. Ctrl+o:退回跳转前代码位置
  3. 命令行直接跳转::tag FILE
  4. 终端直接打开并跳转:vim -t FILE

通俗用处

看代码时不知道 FILE 结构体内部有什么,光标放到 FILE 按Ctrl+],直接跳转到 stdio.h 的源码定义,不用手动翻文件。

全文补充拓展(课本缺失重点)

1. 三种缓冲区完整规则(标准 IO 核心考点)

  1. 全缓冲(磁盘文件默认) 缓冲区填满 /fflush 强制刷新 /fclose 关闭 / 程序正常退出,才会调用 write 刷数据;默认缓冲区 4096/8192 字节。
  2. 行缓冲(stdin/stdout 终端) 遇到换行\n、缓冲区满、程序退出,立刻刷新打印。
  3. 无缓冲(stderr 错误流) 数据写入瞬间直接输出,不缓存,保证报错立刻可见。

2. 标准 IO 与文件 IO 混合使用坑

标准 IO 自带用户缓冲区,文件 IO 无缓冲;混合读写同一个文件会出现数据错乱,禁止混用

3. fflush 强制刷新缓冲区

fflush(stdout); 手动把缓冲区数据立刻刷入内核,不用等换行,解决 printf 不打印问题。

4. 流关闭注意事项

fclose(fp)会自动刷新缓冲区,进程崩溃时缓冲区未刷盘会丢失数据,重要文件建议主动 fflush。

Logo

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

更多推荐