专栏首页CSDN技术头条用 C 语言实现操作系统

用 C 语言实现操作系统

本文选自 | 《操作系统基础:C 语言实现用户线程》

Chat 作者 | Allen()

整理 | linse

码农都懂的一句话:C 语言无所不能,什么都能搞。把 C 学好,上手其他语言也会事半功倍。因为热爱 C 语言,所以才有了以下这篇文——

一直以来,我们学习线程切换与调度,都是通过阅读操作系统书籍或 Linux 源码甚至反汇编 Window 内核代码。无论怎样,这些东西都很抽象,学习起来十分困难。

另外,随着现代化编程语言逐渐流行,C++20 的正式发布,都离不开用户态线程这些基础知识。再比如 Golang 的 Goroutine,腾讯公司的开源的 libco,百度的 BRPC 中的 bthread,如果想深刻理解它们,都需要扎实的基本功

本文会带你循序渐近的掌握以下知识点:

  • 控制流切换原理
  • 上下文切换
  • 线程设计
  • 调度函数的封装与代码模块化
  • 线程的主动切换
  • 时间片轮转调度

本文实验环境:

  • ubuntu 16.04 32 位操作系统(读者请务必提前安装好);
  • 挑选一个你自己觉得好用的虚拟机软件,比如 VMWare;
  • 请把你的虚拟机环境配置成单核 CPU。

学习时间:大于 5 小时

本场 Chat 作者——Allen()

百度高级研发工程师,CSDN 博客专家。目前从事智能交互式对话系统云端开发。负责过大流量、高并发等业务场景。

时间紧张,心急看全文的朋友来这里

扫码查看完整 Chat 原文

还能进入读者圈与作者深度交流

回归正文,我为什么要使用 32 位系统?因为方便初学者学习,也能更快速的掌握原理。

需要注意的是,本文的代码,并没有使用操作系统为我们提供的 pthread 系列函数,thread_createthread_join 函数都是自己纯手工实现的。唯一使用操作系统的函数就是设置时钟,因此会有时钟信号产生,这一步是为了模拟时间片轮转算法而做的。

01

控制流切换原理

控制流,指的是一系列按顺序执行的指令。比如你编写的多线程程序,每个线程就可以看成是一个控制流,多个线程允许多个控制流一起执行。

在我们学习编程的时候,如果不借助操作系统提供的线程框架,几乎无法完成多控制流的运行的。

接下来剖析一下,我们的指令如何”莫名奇妙“的就切换到其它线程的。

1.1 指令执行

不管你用的是什么语言编程,最后都要落实到 CPU 上,而 CPU 只认识它自己的语言,机器语言。机器语言可以抽象出对应 CPU 架构的汇编指令。

对于单核 CPU 来说,永远只有一条控制流,也就是只有一条指令序列。所以,宏观上模拟的多线程程序,本质上还只是单控制流,所谓的多线程,只不过是一种被制造出来的假像!

1.2 控制流切换(x86 CPU 架构)

汇编指令在执行的时候,最重要地方在于它需要依赖 CPU 环境:

  • 一套通用寄存器 (eax、edx、ecx、ebx、esp、ebp、esi、edi);
  • 标志寄存器 eflags;
  • 指令寄存器 eip (eip 用来保存下一条要被指令的地址)。

如果你不理解 CPU 寄存器是什么意思,把它想象成它是 CPU 中预先定义好的变量。

1.3 再论控制流切换

我们换个思路,不修改栈的内容,而是直接换一个栈,本质上也就是换 esp 的值,能不能达到相同的效果?

话说回来,这个真的是控制流“切换”吗,只是看起来像而已,本质上它只是个跳转。

02

上下文切换

切换出去,是完成线程调度,最关键的一点在于还得切换回来。

2.1 上下文切换原理

上下文切换不同于第 1 节所述的暴力切换,因为在那个实验里,我们永远无法重新返回到 main 函数中。如果你想从那个 fun 函数再跳回目的地,我们需要在切换控制流前保存当前寄存器环境,以及当前的栈顶位置。

2.2 保存寄存器环境

有很多种手段保存寄存器环境,最简单的一种就是保存到定义好结构体去。

03

用户态线程设计

相信上下文切换的实验你已经写出来了,不过你肯定还很多不满意的地方:

  • 线程的切换太暴力
  • 代码看起来一箩筐没有组织
  • 看起来根本没有线程的样子

在我们需要对程序进行改进时,主要有以下 2 点:

  • 封装线程创建函数
  • 完成一个简单的调度算法

在完整原文里,我带领大家完成了线程切换与线程调度的框架,在此基础上,还可以探索更多有关线程的东西,比如线程的同步与互斥,还有更多的线程状态。理解了本文之后,更深入的学习对你来说已经不是难事了!

最终的结果和下图的效果一样

图1 用户态线程运行示例

本场 Chat 关键点总结:

  • 理解“栈切换”的本质
  • 掌握指令执行与栈的关系
  • 掌握上下文是如何切换的
  • 理解主动切换,知道主动切换可能会发生在哪些地方
  • 理解什么是时间片
  • 对于不主动让出 cpu 的线程,是如何切换到其它进程的

本文的代码托管在 Github,在文件夹 v5 里,有需要的朋友可以点击阅读原文,订阅这场 Chat 获取代码!

本文分享自微信公众号 - GitChat精品课(CSDN_Tech),作者:Allen()

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-04-25

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Java 并发编程之美-线程相关的基础知识

    借用 Java 并发编程实践中的话:编写正确的程序并不容易,而编写正常的并发程序就更难了;相比于顺序执行的情况,多线程的线程安全问题是微妙而且出乎意料的,因为在...

    CSDN技术头条
  • 【问底】夏俊:深入网站服务端技术(一)——网站并发的问题

    本文来自拥有十年IT从业经验、擅长网站架构设计、Web前端技术以及Java企业级开发的夏俊,此文也是《关于大型网站技术演进的思考》系列文章的最新出炉内容,首发于...

    CSDN技术头条
  • 深入理解 Java 多线程核心知识:跳槽面试必备

    多线程相对于其他 Java 知识点来讲,有一定的学习门槛,并且了解起来比较费劲。在平时工作中如若使用不当会出现数据错乱、执行效率低(还不如单线程去运行)或者死锁...

    CSDN技术头条
  • java线程(一)—— 新建线程

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 ...

    逝兮诚
  • 并发编程之volatile关键字

    一、Java内存模型 想要理解volatile为什么能确保可见性,就要先理解Java中的内存模型是什么样的。 Java内存模型规定了所有的变量都存储在主内存中...

    lyb-geek
  • 你真的了解 volatile 关键字吗?

    作者:Ruheng, www.jianshu.com/p/7798161d7472 一、Java内存模型 想要理解volatile为什么能确保可见性,就要先理...

    顶级程序员
  • netty 线程模型(未完成)

    虽然Netty的线程模型基于主从Reactor多线程,借用了MainReactor和SubReactor的结构。但是实际实现上SubReactor和Worker...

    平凡的学生族
  • Java 内存模型详解

    Java的内存模型(Java Memory Model )简称JMM。首先应该明白,Java内存模型是一个规范,主要规定了以下两点:

    乱敲代码
  • Java中的锁原理、锁优化、CAS、AQS

    结论:如果volatile变量修饰符使用恰当的话,它比synchronized的使用和执行成本更低,因为它不会引起线程上下文的切换和调度。

    Java团长
  • Java 中的锁原理、锁优化、CAS、AQS 详解!

    结论:如果volatile变量修饰符使用恰当的话,它比synchronized的使用和执行成本更低,因为它不会引起线程上下文的切换和调度。

    芋道源码

扫码关注云+社区

领取腾讯云代金券