xym-ee
  • 计算机与嵌入式开发学习
  • 1-1.编程基础
    • C 语言
      • C 中的数据
      • C 语言基础
      • 字符输入输出
      • 函数
      • 数组和指针
      • 字符串处理
      • 存储类别
      • 文件 I/O
      • 复杂数据类型
      • 位操作
      • 预处理和 C 库
    • 数据结构和算法入门
    • leetcode 刷算法题
      • 递归与栈
      • 二叉树与递归
      • 回溯问题
      • 动态规划 1
    • 基本工具和使用
      • shell
      • shell 脚本
      • vim 编辑器
      • 命令行数据整理
      • 命令行环境和配置
  • 1-2.计算机系统基础(CSAPP)
    • 1.计算机基础
    • 2.数据的表示
    • 3.加减运算
    • 4.乘除运算
    • 5.程序的表示转换和链接
    • 6.IA32指令
    • 7.过程调用
    • 10.程序的链接
  • 1-3.数字电路、计算机组成
    • 1.数字电路、virtual circuit board
    • 2.计算机组成/steam:Turing Complete
    • 3.微机原理与接口技术(8086)
  • 1-4.计算机网络
    • 1.从浏览器开始
    • 2.协议栈和网卡
    • 3.网络设备
    • 4.运营商、接入网
    • 5.服务器
    • 6.数据返回浏览器
    • socket编程
  • 1-5.操作系统
    • 0.绪论
      • 1.应用视角的操作系统
      • 2.硬件视角的操作系统
      • 3.数学视角的操作系统
      • 4.状态机模型的应用
    • 1.并发
      • 1.并发 bug 的解决思路
      • 2.互斥
      • 3.同步
      • 4.信号量
      • 5.真实并发
      • 6.调试技巧
      • 7.os kernel 实现
    • 2.虚拟化
      • 1.操作系统上的进程
      • 2.进程的地址空间
      • 3.系统调用和unix shell
      • 4.C 标准库的实现
      • 5.linux 操作系统
      • 6.可执行文件和加载
      • 7.动态链接和加载
      • 8.内核的实现
      • 9.fork 的应用
    • 3.持久化
      • 1.存储设备的原理
      • 2.输入输出设备模型
      • 3.设备驱动程序
      • 4.文件系统 API
      • 5.fat 和 unix 文件系统
      • 6.持久数据的可靠性
    • 总结
  • 2-1.嵌入式裸机开发
    • 嵌入式系统通信接口与协议
    • cortex-m 内核芯片裸机开发
    • MPU
  • 2-2.中等规模系统开发
    • LVGL 图形库
    • 裸机开发的软件框架
    • 基于 rtos 开发
  • 2-3.armv7-m架构与 rtos 原理
    • armv7-m 架构
    • rt-thread 内核实现
    • rt-thread 驱动开发
  • 3-1.linux 应用开发基础
  • 3-2.linux 镜像构建
    • uboot 使用
    • uboot 适配
    • uboot 启动分析
    • uboot 自定义命令
    • linux 内核适配
    • linux 内核启动分析
    • busybox 根文件系统构建
  • 3-3.linux 驱动开发
    • 驱动开发基础
    • sysfs
    • led 驱动
    • 设备树
    • pinctrl 和 gpio 子系统
    • 并发控制
由 GitBook 提供支持
在本页
  • fork 行为的补充解释
  • 写时复制
  • 状态机的复制

这有帮助吗?

  1. 1-5.操作系统
  2. 2.虚拟化

9.fork 的应用

系统调用 👉 libc 👉 shell 👉 应用的“软件栈”

能不能用系统调用实现比普通业务逻辑代码更有趣的东西?

如何把操作系统玩出花来?😂

fork 行为的补充解释

要讲 fork 就要讲操作系统。操作系统是个大的状态机,大的状态机里可以分组,比如说一个程序又是一个状态机,当然操作系统内部也有自己的状态,比如说操作系统的对象,程序可以用文件描述符指向对象。

操作系统自己的代码不执行时(一般情况),会直接把一个程序放到CPU上执行,这个程序如果执行 syscall 时,比如 fork 时,那么操作系统会相应的更新操作系统的状态。即 执行 fork 的程序的状态机完整的复制一份,除了 fork 的返回值不同。父进程返回子进程的 pid,子进程返回 0 。

前面都是讲代码,现在反过来想的时候,还有一些有意思的小东西。比如说如果原进程持有一个操作系统的对象,比如 fd=0(stdin),那么 fork 以后,子进程同样持有指向同一个操作系统对象的文件描述符,这里面做了一个拷贝,像指针的赋值一样。文件描述符就是个整数 int fd; 操作系统的对象只有一个,两个进程指向了同一个。

我们在 execve 的时候,重置了一个状态机。比如执行 execve("echo"); ,这个进程就重置为了 echo 的初始状态。如果在重置前,持有 fd=0 那么时保持的。这个设计,使得在 fork + execve 运行新程序前,可以打开一个管道,这样父子进程都持有指向管道的文件描述符,然后就可以把一部分计算的输出管道给另外一部分计算的输入。

当我们有了文件描述符以后,就有了一点小麻烦。

文件描述符通过这个函数来得到

int open(const char *pathname, int flags);

比如说 open("a.txt"); 这个 a.txt 是操作系统里的一个对象。这时候就会有一个新的文件描述符比如说 fd=3 指向这个 a.txt 对应的对象。

文件描述符是一个指向操作系统内对象的 “指针”,我们只能用操作系统允许的方式来访问

  • 对象只能通过操作系统允许的方式访问

    • 从 0 开始编号 (0, 1, 2 分别是 stdin, stdout, stderr)

    • 可以通过 open 取得;close 释放;dup “复制”

    • 对于数据文件,文件描述符会 “记住” 上次访问文件的位置

      • write(3, "a", 1); write(3, "b", 1);

对于这个特性,

#include <fcntl.h>
#include <unistd.h>

int main()
{
    fd = open("a.txt", O_WRONLY | O_CREAT);
    pid_t pid = fork();
    if (pid == 0) {
        write(fd, "Hello");
    } else {
        write(fd, "World");
    }
}

这段代码,会得到什么呢?父进程写入 world 子进程写入 hello。会覆盖吗?

write 的内在含义就是一直往后写。因此不覆盖才是合理的。从这里,操作系统开始变复杂了。有了偏移量以后操作系统如何管理呢?

复杂性的来源:东西越来越多,设计者还为了保持向前的兼容。好多看起来奇怪的问题都是由历史遗留问题造成的。

前面是对fork的一点补充。

一点点关于 fork 的实现问题。

写时复制

概念上 fork 就是创建了一个进程的副本,但这件事情代价很大,复制一个状态机包括里面的数据。此外类似的情况,操作系统里 malloc(1GB) 这个事情也是允许的。如果进程 fork 了复制了以后又 execve 了,那岂不是前面白复制了,数据也没用上就丢掉了。有没有可能给一个高性能的实现呢?

操作系统如何应对这个问题?

copy-on-write

进程是有地址空间的。pmap看到的就是,操作系统实现虚拟地址的时候用了分页,用了 MMU,实际上进程的每个页面,

进程只是个映射表,内存页面是操作系统的,概念上的状态机,实现上,所有进程的页面属于操作系统,操作系统做了偷梁换柱,有个CR3寄存器,做了个地址翻译,使得进程可以引用操作系统为他分配的页面。

既然所有页面都是操作系统持有的,那么就可以耍一些小花招,在实现 fork 时除了按照里要所有内容都复制一份。还可以让新进程和老进程共享页面,即只复制映射表。新老进程指向同一个可写页面。对于可读可写段,临时禁止写这个行为,这样要写时,触发缺页中断,如果是非法访问,那么是报段错误,如果发现是临时禁止的,那么就拷贝一份再写。

除此之外,操作系统还要维护一个引用计数,计数为1,就可以把rw权限重新还回去。如果是fork后execve基础不会去写内存,这样开销就很小。

这就是 copy-on-write 。好处,一些.so动态库,一些都会引用的东西,整个物理内存里有一份副本就行。

如何写代码证明操作系统有这个机制?

并发,查看内存占用,试探操作系统的行为。

这时候想查看一个进程占用了多少内存是个伪命题。mmap查看的,可以分配特别大的内存,但是不使用。

状态机的复制

上一页8.内核的实现下一页3.持久化

最后更新于9个月前

这有帮助吗?