操作系统概述(一、并发)

news2024/9/25 3:27:10

系列文章目录


文章目录

  • 系列文章目录
  • 前言
    • 定义
  • 一、操作系统发展史
      • 1940s的程序
      • 1950s的计算机
      • 1960s的计算机
      • 1970s+ 基本和现代一样了
    • others
  • 二、
    • 程序状态模型
      • 从不同视角看程序:
    • 操作系统上的程序
  • 三、线程库
  • 四、程序并发
  • 五、自旋锁与互斥锁的实现
      • 自旋锁的使用场景
  • 六、并发控制:同步 (条件变量、信号量、生产者-消费者
    • 信号量
      • 信号量设计的重点
    • 条件变量
    • 吃饭问题
  • 七、高并发编程
  • 八、并发bug, 如何修bug
    • 并发bug:死锁(deadlock)
  • 总结


前言

操作系统广义上讲可以是…非常广

这里只讨论狭义上的操作系统,如Windows、Linux

定义

操作系统是负责管理软硬件资源,为应用程序和用户提供服务的 系统的 大型 软件。

所以说,操作系统和普通的软件没有本质区别,只不过它会直接操纵硬件资源;当程序员想要申请128byte的内存空间时,只需要调用通过系统提供的API即可,而不是自行编写申请内存空间的程序,所以说操作系统为程序和用户提供服务。


一、操作系统发展史

1940s的程序

此时并不需要操作系统,也不存在这种概念。用户只需要将写好的一沓沓纸带(程序)放入计算器,然后计算器就会自动执行纸带上的指令,输入结果。

就像51单片机,把程序烧录进内存,然后单片机器执行程序中的指令,输出结果,并不需要操作系统。

1950s的计算机

为了管理多个用户的程序,引入操作系统的概念:operator(操作系统)jobs(任务)system系统

此时一个FORTRAN程序就是一沓卡片,卡片上不同的孔位代表不同的指令,多个程序就需要非常多沓的卡片。操作系统要做的就是上一沓卡片执行完后将下一沓卡片“拿”过来执行。为所有程序提供API,如将结果保存到另一沓卡片上。

  • “批处理系统”+库函数API
  • DOS Disk Operating Systems
    • 操作系统中开始出现“设备”、“文件”、“任务”等对象和API

1960s的计算机

集成电路、总线出现

  • 更快的处理器
  • 更快更大的内存;虚拟存储器出现
    • 可以同时载入多个程序而不用“换卡”了
  • 更丰富的IO设备;完善的中断/异常机制

内存变大,可以将多个程序都放入内存中。但是,只有一个CPU。通常程序在执行期间并不全程使用CPU,而是存在不使用CPU的空闲期。

能载入多个程序到内存且灵活调度它们的管理程序,包括程序可以调用的API

  • 有了进程process的概念
  • 进程在执行IO时,可以将CPU让给另一个进程
    • 在多个地址空间隔离的程序之间切换
    • 虚拟存储使一个程序出bug不会crash真个系统

操作系统中自然增加进程管理的API

基于中断(如时钟)机制

  • 时钟中断:使程序在执行时,异步地插入函数调用
  • 由操作系统(调度策略)决定是否要切换到另一个程序执行

1970s+ 基本和现代一样了

others

tldr 是比 man 更易于阅读使用的帮助文档

二、

程序状态模型

数字电路在做的事情:

  1. 设置运行所需初值,初值了来源可以是其它电路(包括自身电路)输出的也可以是什么东西设置的
  2. 运行电路
  3. 为了方便显示,做一些操作可能是printf也可能是其它的

1.2.3步在时钟周期的控制下重复进行,可能是每来一个周期信号就进行一步

从不同视角看程序:

程序是状态机:
每执行一条指令,堆栈的状态就会改变,所以的程序指令在指示堆、栈以及各种指针的状态该如何变化。

程序是二进制的指令
gdb可以从两个视角,C or 汇编 的角度调试程序

程序调用syscall,将程序当前的状态移交给操作系统。

操作系统上的程序

程序 = 状态机 = 计算 --> syscall --> 计算 --> …
用户程序只能做一些计算操作,通过系统调用来完成硬件访问等操作。

操作系统负责管理所有的硬件软件资源

  • 只能用操作系统允许的方式访问操作系统中的对象
  • 这是为了“管理多个状态机”所必须的

gdb
strace

三、线程库

并发的基本单位:线程

  • 执行流拥有独立的堆栈/寄存器
  • 共享全部的内存(指针可以相互引用)

在终端的输出是乱序的可以用 | sort 试试

原子性的丧失

顺序

宽松内存模型

编译器有编译优化,x86处理器自身也有优化

四、程序并发

五、自旋锁与互斥锁的实现

互斥算法:Dekker/Peterson

实现互斥的根本困难:不能同时读和写共享内存

  • load(环顾四周)的时候不能写,只能“看一眼就把眼睛闭上”
    • 看到的东西马上就过时了
  • store(改变物理世界状态)的时候不能读,只能“闭着眼睛动手”
    • 也不知道把什么改成了什么

假设硬件能为我们提供一条“瞬间完成”的读+写指令

#include </stdatomic.h>提供了一些原子操作,可以利用xchg实现自旋锁。
  所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)。
  原子性不可能由软件单独保证–必须需要硬件的支持,因此是和架构相关的。在x86 平台上,CPU提供了在指令执行期间对总线加锁的手段。CPU芯片上有一条引线#HLOCK pin,如果汇编语言的程序中在一条指令前面加上前缀"LOCK",经过汇编以后的机器代码就使CPU在执行这条指令的时候把#HLOCK pin的电位拉低,持续到这条指令结束时放开,从而把总线锁住,这样同一总线上别的CPU就暂时不能通过总线访问内存了,保证了这条指令在多处理器环境中的原子性。
  总结:某条原子指令一旦被执行,必须有且仅有一个线程完整地执行它

int tables = YES;

void lock(){
retry:
  int got = xchg(&table, NOPE);
  if (got == NOPE)
    goto retry;
  assert(got == YES)
}
void unlock(){
  xchg(&table, YES);
}
int locked = 0;
void lock(){ while(xchg(&locked, 1));}
void unlock() { xchg(&locked, 0); }

自旋锁的使用场景

  1. 临界区几乎不“拥堵”
  2. 持有自旋锁时禁止执行流切换

使用场景:操作系统内核的并发数据结构(短临界区)

  • 操作系统可以关闭中断和抢占
    • 保证锁的持有者在很短的时间内可以释放
  • (虚拟机。。。)
    • PAUSE指令会触发VM Exit

六、并发控制:同步 (条件变量、信号量、生产者-消费者

信号量

#include <iostream>
#include <thread>
#include <thread>
#include <semaphore.h>
#include <unistd.h>

sem_t sem_a;
sem_t sem_b;
sem_t sem_c;
int a = 1;
int b = 2;
int c = 3;

class NumCnt
{
private:
  /* data */
public:
  static void fun1()
  {
    goto entry;
    while (1)
    {
      sem_wait(&sem_c);
      entry:
      std::cout << a << "thread id : " << std::this_thread::get_id() << std::endl;
      a += 3;
      sem_post(&sem_a);
      usleep(1000*1000);
    }
  }
  static void fun2()
  {
    while (1)
    {
      sem_wait(&sem_a);
      std::cout << b<< "thread id : " << std::this_thread::get_id() << std::endl;
      b += 3;
      sem_post(&sem_b);
      usleep(1000*1000);
    }
  }
  static void fun3()
  {
    while (1)
    {
      sem_wait(&sem_b);
      std::cout << c<< "thread id : " << std::this_thread::get_id() << std::endl;
      c += 3;
      sem_post(&sem_c);
      usleep(1000*1000);
    }
  }
};
int main()
{
  sem_init(&sem_a, 0, 0);
  sem_init(&sem_b, 0, 0);
  sem_init(&sem_c, 0, 0);
  std::thread th1(NumCnt::fun1);
  std::thread th2(NumCnt::fun2);
  std::thread th3(NumCnt::fun3);
  th1.join();
  th2.join();
  th3.join();
}

打印括号问题,要求打印的括号必须匹配:(正确的)

  void Productor()
  {
    while (1)
    {
      sem_wait(&sem_m);  // 左括号最大允许数量
      std::cout << "(";
      sem_post(&sem_par); // 生产出了多少个
    }
  }
  void Customer()
  {
    while (1)
    {
      sem_wait(&sem_par);  // 好生产出了,开始消费
      std::cout << ")";
      sem_post(&sem_m);   // 消费了一个,最大生产5个,消费了一个那么还有一个坑位让生产者生产
    }
  }

信号量设计的重点

  • 考虑每一单位的资源是什么?谁创造?谁获取?
  • 在“单一位资源”明确的问题上更好用

条件变量

  • 将自旋转变为睡眠,在完成操作时唤醒

条件变量:

  • wait(conditional_variable, mutex)
    • 调用时必须保证已经获得mutex
    • 释放mutex、进入睡眠状态
  • signal/nofity(conditional_variable)
    • 如果有线程在等待cv,则唤醒其中一个线程
  • broadcast/nofityAll(cv)
    • 唤醒全部正在等待cv的线程

下面的代码在多生产者多消费者时会出bug,需要两个条件变量
有BUG的:

int n, cnt;
条件变量cv, 锁lck
void Tproduce()
{
  mutex_lock(&lck);
  if (cnt == n) cond_wait(&cv, &lck);
  printf("(");
  cnt++;
  cond_signal(&cv);
  mutex_unlock(&lck);
}
void Tconsume()
{
  mutex_lock(&lck);
  if (cnt == 0) cond_wait(&cv, &lck);
  printf(")");
  cnt--;
  condi_signal(&cv);
  mutex_unlock(*lck);
}
  1. 两个条件变量: 略
  2. while循环: 如下代码
    mutex_t lk = MUTEX_INIT();
    cond_t cv = COND_INIT();
    
    void Tproduce()
    {
      while(1){
        mutex_lock(&lk);
        while(!(count != n)){
          cond_wait(&cv, &lk);
        }
        // lock is held
        printf("(");
        count++;
        cond_broadcast(&cv);
        mutex_unlock(&lk);
      }
    }
    void Tconsume()
    {
      while(1){
        mutex_lock(&lk);
        while(!(count != 0)){
          cond_wait(&cv, &lk);
        }
        printf(")");
        count--;
        cond_broadcast(&cv);
        mutex_unlock(&lk);
      }
    }
    
  • example:
    struct job{
      void (*run)(void *arg);
      void *arg;
    };
    while(1){
      struct job* job;
      mutex_lock(&lk);
      while(job != get_job()){
        wait(&cv, &lk);
      }
      mutex_unlock(&lk);
      job->run(job->arg); // 不需要持有锁
                          // 可以生成新的job
                          // 注意回收分配的资源
    }
    

吃饭问题

  • 分布式系统中非常常见的解决思路(master-slave)
// slave 线程,可以有多个
void Tphilosopher(int id)
{
  send_request(id, EAT);
  P(allowed[id]);  // 等待是否继续往下执行
  程序处理
  send_request(id, DONE);
}

// master 进程,只有一个
void Twaiter()
{
  while(1){
    id, status = receive_request();
    if (status == EAT) {...}
    if (status ++DONE) {...}
  }
}

设计原则:easy to use, simple to think

  • 不要做任何优化,先写出来
  • 实现同步的方法
    • 条件变量、信号量;生产者-消费之模型
    • job queue可以实现几乎任何并行算法

七、高并发编程

高性能计算的主要挑战:

  • 计算图需要容易并行化
  • 线程间如何通信

数据中心主要挑战:

  • 数据要保持一致(Consistency)
  • 服务器时刻保持可用(Availability)
  • 容忍机器离线(Partition tolerance)

工具:

  • 线程

    • 线程切换简单记为:1.保存当前状态机的所有寄存器 2. 读取下一个线程的状态
  • 协程

    • 多个可以保存/恢复的执行流
    • 比线程更轻量(完全没有系统调用,也就没有操作系统状态)
      #include <stdio.h>
      int count = 1;
      void entry(void *arg){
        for (int i= 0; i < 5; i++){
        printf("%s[&d] ", (const char*)arg, count++);;
        co_yield(); // 切换协程
        }
      }
      int main(){
        struct co *co1 = co_start("co1", entry, "a");
        struct co *co2 = co_start("co2", entry, "b");
        co_wait(co1);
        co_wait(co2);
      }
      

Goroutine:概念上是线程,实际是线程和写成的混合体

  • 每个CPU上有一个Go Worker,自由调度goroutines
  • 执行到blocking API时(如sleep,read)
    • Go Worker偷偷改成non-blocking的版本
      • 成功 -> 立即继续执行
      • 失败 -> 立即yield到另一个需要CPU的goroutine
        • 十分巧妙,CPU和操作系统全部用到100%

八、并发bug, 如何修bug

根本原因:编程语言的缺陷
软件是需求(约规)在计算机数字世界的投影
计算机是负责“翻译”代码,不管和实际需求(约规)是否匹配

防御性编程:
把程序需要满足的条件用 assert 表达出来(打个log)

eg:
在这里插入图片描述

assert  y.left == x; y.right == c; x.left == a; x.right == b;

当写出如下代码:

int* p = (void*)0x23ae3;
*p = 1;

如果是非内核程序,操作系统会帮助用户检查p的值是否合法;如果是操作系统内核,则会默默执行此操作,从而不知在何时引发严重问题。

并发bug:死锁(deadlock)

所有线程都在相互等待。

死锁产生的四个必要条件(Edward G. Coffman, 1971):四个条件缺一不可

  • 互斥:
  • 请求与保持:
  • 不剥夺:
  • 循环等待:

并发控制工具:

  • 互斥锁 - 原子性
  • 条件变量 - 同步


gcc -fsanitize=address

动态程序分析:

  • 在事件发生时记录
  • 解析记录检查问题
  • 付出代价和权衡

动态程序分析工具:

  • AddressSanitizers: 非法内存访问
  • ThreadSanitizer:数据竞争
  • MemorySanitizer:未初始化的读取
  • UBSanitizer:undefined behavior

奇怪的知识

msvc中debug mode 的 guard/fence/canary
- 未初始化栈:0xcccccc
- 未初始化堆:0xcdcdcd
- 对象头尾:-0xfdfdfdfd
- 已回收内存:0xddddd

总结

生产者消费者模型还得是信号量(还是要具体问题具体分析)

自旋锁 -> 互斥锁 -> 条件变量 -> 信号量
自旋锁的实现需要依赖原子操作,而原子操作单靠软件是无法实现的,需要硬件提供支持。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/498164.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

21 brk 的初始化

前言 这个问题是 衍生自 malloc 的问题的调试 malloc 虚拟内存分配的调试(1) malloc 虚拟内存分配的调试(2) 假设我们使用 gdb 或者 gdbserver 启动调试的该测试用例对应的可执行程序 我们可以观察到的现象是 p1, p2, p3 的地址是固定的, 均是类似于 elf 中最大的虚拟地…

基于vue3+pinia2仿ChatGPT聊天实例|vite4.x仿chatgpt界面

使用vue3pinia2开发仿制chatgpt界面聊天实例Vue3-Chatgpt 基于Vue3.xPinia2VueRouterVue3-Markdown等技术构建仿ChatGPT网页端聊天程序。支持经典分栏界面布局、light/dark模式、全屏半屏显示、Markdown语法解析、侧边栏隐藏等功能。 技术框架 编辑工具&#xff1a;Cursor框架…

精炼计算机网络——物理层(二)

文章目录 前言2.4信道复用技术2.4.1 频分复用、时分复用和统计时分复用2.4.2 波分复用2.4.3 码分复用 2.5 数字传输系统2.6 带宽接入技术2.6.1 ADSL技术2.6.2 光纤同轴混合网&#xff08;HFC网&#xff09;2.6.3 FTTx技术 总结 前言 上篇文章&#xff0c;我们初步了解了物理层…

国考省考结构化面试:应急应变事件处理,宏观全面把控,措施有效具体,着眼当前放眼未来,标本兼治

国考省考结构化面试&#xff1a; 2022找工作是学历、能力和运气的超强结合体! 公务员特招重点就是专业技能&#xff0c;附带行测和申论&#xff0c;而常规国考省考最重要的还是申论和行测&#xff0c;所以大家认真准备吧&#xff0c;我讲一起屡屡申论和行测的重要知识点 遇到寒…

idea 创建java项目,引入第三方jar,打包jar包

目录 一、新建并运行项目二、下载第三方Jar三、引入第三方Jar四、将项目打成Jar包 一、新建并运行项目 前提&#xff1a;已安装好JDK&#xff0c;并且配置好了JDK环境变量。 直接点击create创建即可 刚创建完的项目可能是没有out目录的&#xff0c;当我们执行一次main方法&…

Gradio的web界面演示与交互机器学习模型,安装和使用《1》

如何快速地将机器学习模型&#xff0c;给创建和分享出去&#xff0c;让更多的人来体验&#xff1f;Gradio就是一种快速搭建web界面来演示机器学习模型的方式&#xff0c;任何人都可以在任何地方使用它。 官网地址&#xff1a;https://gradio.app/ 1、安装Gradio 前提条件:Gra…

YOLOv5:图解common.py常用模块

YOLOv5&#xff1a;图解common.py常用模块 前言前提条件相关介绍common.py基本模块ConvBottleneckBottleneckCSPC3SPPSPPFFocus未完待续 参考 前言 由于本人水平有限&#xff0c;难免出现错漏&#xff0c;敬请批评改正。更多精彩内容&#xff0c;可点击进入YOLO系列专栏或我的个…

jieba分词(1):入门案例

1 场景介绍 大数据量的查询问题 假设我们要从商品的表里面查询一个商品 我们的数据库里面肯定有个t_goods的表&#xff0c;我们现在利用商品的名称做模糊查询 1.1 对于数据库的查询的 select * from t_goods where goodsName like “%手机%” ; 问题&#xff1a; 这个查询…

CSDN | 好久不见,甚是想念

&#x1f482;作者简介&#xff1a; THUNDER王&#xff0c;一名热爱财税和SAP ABAP编程以及热爱分享的博主。目前于江西师范大学本科在读&#xff0c;同时任汉硕云&#xff08;广东&#xff09;科技有限公司ABAP开发顾问。在学习工作中&#xff0c;我通常使用偏后端的开发语言A…

JavaScript实现在键盘输入按键,浏览器进行显示的代码

以下为实现在键盘输入按键&#xff0c;浏览器进行显示的代码和运行截图 目录 前言 一、在键盘输入按键&#xff0c;浏览器进行显示 1.1 运行流程及思想 1.2 代码段 1.3 JavaScript语句代码 1.4 运行截图 前言 1.若有选择&#xff0c;您可以在目录里进行快速查找&#xf…

《灰盒模型在非侵入式体外估计糖化血红蛋白百分比和数字脉搏波形的推导和验证》阅读笔记

目录 一、论文摘要 二、论文十问 Q1&#xff1a;论文试图解决什么问题&#xff1f; Q2&#xff1a;这是否是一个新的问题&#xff1f; Q3&#xff1a;这篇文章要验证一个什么科学假设&#xff1f; Q4&#xff1a;有哪些相关研究&#xff1f;如何归类&#xff1f;谁是这一课…

84.python input输入函数知识拓展

文章目录 1. input函数知识回顾2. input常犯错误解析3. 用函数转换从终端输入的数据3.1 输入的数为整数&#xff0c;则用int转换为整数3.2 输入的数为浮点数&#xff0c;则用float转换为浮点数3.3 不考虑输入的数据类型&#xff0c;则用eval函数转换 4. 变量的多种赋值方式4.1 …

OpenCV教程——OpenCV环境配置及第一个测试代码

1.OpenCV简介 OpenCV是一个计算机视觉的开源库。英文全称是&#xff1a;Open Source Computer Vision Library。 常用的OpenCV的核心模块&#xff1a; Image ProcessCamera Calibration and 3D ReconstructionVideo AnalysisObject DetectionMachine LearningDeep LearningG…

【Linux】Linux安装Git(图文解说详细版)

文章目录 前言第一步&#xff0c;官网下载安装包第二步&#xff0c;解压安装包第三步&#xff0c;安装编译环境第四步&#xff0c;编译源码第五步&#xff0c;安装git第六步&#xff0c;配置环境变量 前言 服务器版本&#xff1a;CentOS7.8 git官网&#xff1a;https://git-sc…

汽车出租系统【纯控制台】(Java课设)

系统类型 纯控制台类型&#xff08;没有用到数据库&#xff09; 使用范围 适合作为Java课设&#xff01;&#xff01;&#xff01; 部署环境 jdk1.8Idea或eclipse 运行效果 本系统源码地址&#xff1a;https://download.csdn.net/download/qq_50954361/87753364 更多系统…

Qt5.14.2安装教程

之所以选择安装Qt5.14.2&#xff0c;是因为从5.15.0起&#xff0c;对于开源用户&#xff0c;Qt官方不再提供独立安装文件&#xff0c;源码安装听说很繁琐&#xff0c;并且还要激活码。 官网下载链接&#xff1a;https://download.qt.io/archive/qt/5.14/5.14.2/ 1、Windows用户…

智能优化算法:基于驾驶训练的优化算法-附代码

智能优化算法&#xff1a;基于驾驶训练的优化算法 文章目录 智能优化算法&#xff1a;基于驾驶训练的优化算法1. 基于驾驶训练优化算法1.1 初始化1.2 阶段一&#xff1a;驾驶教练培训&#xff08;探索阶段&#xff09;1.3 阶段二&#xff1a;学员学习&#xff08;探索阶段&…

Jupyter notebook 如何设定默认的保存目录?

前言&#xff1a; 做智能车的时候&#xff0c;Jupter Notebook的默认保存在可怜的C盘&#xff0c;本来就很紧张的C肯定受不了&#xff0c;要改到别的地方&#xff0c;网上找了一些参考&#xff0c;说变更一下配置地址就可以了&#xff0c;照着做&#xff0c;99%的博客说&#x…

Linux驱动开发笔记(一):helloworld驱动源码编写、makefile编写以及驱动编译基本流程

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/130534343 红胖子网络科技博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬…

算法和算法竞赛的知识点

2023年5月7日&#xff0c;周日早上&#xff1a; 虽然今天早上我作出了改变学习算法方式的决定&#xff0c;但是知识点有哪些、具体该怎么做还没搞清楚&#xff0c;于是去刷题网站截图了它们的标签。 或许看相关书籍的知识点和题单也不错。 LeetCode的知识点 力扣 蓝桥杯的知识…