文章目录

  • 前言
  • 一、地址空间和页表
    • 1.二级页表
    • 2.例子
  • 二、线程
    • 1.概念
      • 重新理解前面讲的进程:在内核的视角,进程是承担分配系统资源的基本实体。
      • 站在CPU角度,能否去识别当前调度的task_struct是进程还是线程?
      • Linux下并不存在真正的线程
      • 总结
    • 2.线程的优点
    • 3.线程的缺点
      • 健壮性降低的例子
    • 4.线程的异常
    • 5.线程的用途
  • 三、Linux下的进程与线程
    • 进程的多个线程共享的资源
    • 线程独立的数据
    • 进程与线程的关系
  • 总结

前言

本文介绍了地址空间和二级页表、Linux下的线程、线程的优缺点以及线程与进程的关系等概念。


一、地址空间和页表

地址空间是进程能看到的资源窗口:一个进程可以看到代码区、堆栈区、共享区、内核区等,大部分的资源是在地址空间上看到的。
页表决定进程真正有用资源的情况:进程认为自己独占系统的4GB资源,但实际上进程拥有多少物理资源是由页表决定的。
合理的对地址空间和页表进行资源划分,我们就可以对进程所拥有的资源进行分类:通过地址空间的区域划分,划分为栈区、堆区……,通过页表映射到不同的物理内存中。

1.二级页表

在32位平台下,一共有2^32个地址,这也意味着有2^32个地址需要被映射。
地址空间有2^32个地址,每个地址单位都是1字节,页表也要有2^32个条目(每个地址都要经过页表映射,它们都是页表的条目),包括是否命中,包括RWX权限,包括U/K权限。一个条目假设有6个字节的数据,那么光保存页表的空间就需要24GB(4GB大约40亿字节)。

每个表项中处理要有虚拟地址和它映射的物理地址外,时间还需要一些权限相关的信息,用户级页表和内核级页表实际就是通过权限进行区分。

虚拟地址:32位下是32位。
物理地址:被划分为一块块的数据框。
OS要对物理内存进行管理:先描述(结构体:struct Page{//内存的属性——4KB}),再组织(数组:struct Page mem[])。
在OS中把物理内存一块块的数据框称为页框,磁盘上编译形成可执行程序的时候被划分为一个个4KB的区域称为页帧。当内存和磁盘进行数据交换时,也是以4KB大小为单位进行加载和保存的。
因此,将数据加载到内存时,在文件系统级别需要按照4KB为基本单位将数据从外设搬到内存。最后,OS系统想要管理内存,除了结构匹配还要有管理算法Linux常见的管理算法称为伙伴系统

虚拟地址转化为物理地址:虚拟地址形成后(以10,10,12的二进制构成),页表不止一张。第一级页表页目录:前十个在页目录中查找,2^10个指向页表的内容。页表:页表的条目项为2^10个,条目写的是指定页框的起始物理地址,页表项指向物理内存中某一页,剩下的12位虚拟地址刚好与页框的大小是等价的(4KB = 2^12B),因此,从物理地址的起始处 + 虚拟地址的低12位(2^12偏移量)作为页内偏移,就可以直接在某个页内找到某个地址。

其中的页目录项是一级页表,页表项是二级页表。映射过程由MMU这个硬件完成(该硬件集成在CPU内),页表是一种软件映射,MMU是一种硬件映射,虚拟地址转为物理地址实际上是软硬件结合的。

2.例子

修改常量字符串为什么会发送错误?
如果要修改一个常量字符串,虚拟地址需要经过页表映射查找到对应的物理内存,但是在查表的过程中会发现该地址的权限是只读,对一个只读地址进行修改会导致在MMU内部触发硬件错误,OS识别到这个错误会该对应进程发送信号终止对应进程。

二、线程

1.概念

  1. 在一个程序里的一个执行路线就叫做线程(可以参考进程)。更准确的定义是:线程是一个进程内部的控制序列
  2. 一切进程都至少有一个执行线程。
  3. 线程在进程内部运行本质是在进程的地址空间内运行。
  4. Linux中,在CPU眼中看到的PCB都比传统的进程更加轻量化。
  5. 透过进程的虚拟地址空间可以看到进程的大部分资源,将进程的资源合理分配给每个执行流,就形成了线程执行流。
  6. 不同平台的多线程底层实现策略都是不同的,本文我们了解的是Linux下的多线程策略。

线程对应的模型:进程的创建实际上伴随着进程控制块(PCB)、进程地址空间(mm_struct)以及页表的创建(虚拟地址和物理地址是通过页表建立映射的):

进程 = 内核数据结构 + 代码和数据。
每个进程都有字节独立的进程地址空间和独立的页表,这意味着每个进程在运行时会具有独立性,
如果我们在创建进程时只创建进程的PCB,并要求创建出来的PCB不再独立创建资源,而是与父进程共享资源。那么创建的结果就是下面这样的:

因为我们可以通过虚拟地址空间 + 页表的方式对进程的资源进行划分,单个进程的执行力度会比之前的进程更细。
上图中每个线程都是当前进程的一个执行流,线程在进程的内部运行,在进程的地址空间运行,拥有该进程的一部分资源。

重新理解前面讲的进程:在内核的视角,进程是承担分配系统资源的基本实体。

创建进程时,申请的PCB、虚拟内存空间、页表以及加载到物理内存中的代码和数据:花费CPU资源创建进程并初始化;花费内存资源保存进程的内核数据结构、代码和数据;花费CPU的IO资源从外设IO到内存。所以承担分配系统资源的基本实体是进程。
总结一下,我们创建进程时,OS申请一堆的内核数据结构占用资源,进程的代码和数据加载到内存中也要占用资源,以及其他部分占用的资源。因此,进程是承担系统资源分配的基本实体。
我们之前讨论的进程都是只有一个PCB,也就是说该进程内部只有一个执行流,即单执行流,这与我们上面讲的并不冲突,如果是像上面这样的一个进程内部由多个执行流,那它就是多执行流进程。

站在CPU角度,能否去识别当前调度的task_struct是进程还是线程?

不能,也不需要,CPU不关心当前调度的是进程还是线程。CPU以task_struct为单位进行调度,今天我们喂给CPU的task_struct是小于等于过去所说的task_struct的,它比之前的更轻量化。因此,在Linux中可以把进程和线程做一个统一,CPU看到的task_struct称为轻量级期间进程。
在Linux中,什么是线程?——线程是CPU的基本调度单位

Linux下并不存在真正的线程

Linux下的线程是用进程模拟的。
如果OS真正要专门设计“线程”概念,OS就要管理线程了(先描述,再组织)。
Windows下确实是为线程专门设计了数据结果表示线程对象TCB,但是线程的创建就是为被执行,执行需要被调度、存在ID/状态、优先级、上下文、栈……等内容,这些线程调度需要的东西与进程有很多地方是重叠的。因此,Linux下没有为“线程”专门设计对应的数据结构,而是直接复用了进程的PCB,用PCB来表示Linux下的“线程”

总结

  1. Linux内核中严格来说是没有真正意义的线程的,Linux用进程PCB来模拟线程,它有一套完全属于自己的线程方案。
  2. 站在CPU角度,每一个PCB都可以称为轻量级进程。
  3. Linux下,线程是CPU调度的基本单位,进程是承担分配系统资源的基本单位。
  4. 进程用来整体申请资源,线程是伸手向进程要资源。(所以线程在执行时申请的资源,实际上是进程向系统申请的资源)
  5. 进程模拟线程的好处:用PCB模拟线程,则为PCB编写的结构和算法都可以进行复用,不用单独再为线程创建结构和调度算法,降低了系统的维护成本,同时复用进程的那套,可靠高效

2.线程的优点

  1. 创建一个线程要花费的代价比创建一个进程的代价要小得多,与进程切换相比,线程之间的切换需要操作系统做的工作要少很多。
    进程切换:要切换页表、虚拟地址空间、PCB、上下文;
    线程切换:切换PCB和上下文。
  2. 线程占用的资源要比进程占用的资源少很多。
  3. 线程能充分利用多处理器的可并行数量。
  4. 在等待慢速I/O操作结束的同时,程序可执行其他计算任务。
  5. 计算密集型应用(CPU、加密、解密、算法等),为了能在多处理器系统上运行,可以讲计算分解到多个线程中实现。
  6. I/O密集型应用(外设、磁盘、显示器、网络),为了提高性能,讲I/O操作重叠,使线程可以同时等待不同的I/O操作。

3.线程的缺点

  1. 性能损失:一个很少被外部事件阻塞的计算密集型线程往往无法与其他线程共享同一个处理器。
    如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用资源是不变的。
  2. 健壮性降低:编写多线程需要更全面深入的考虑。
    在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的。换而言之,线程之间是缺乏保护的。
  3. 缺乏访问控制:进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。
  4. 编程难度提高:编写与调试一个多线程程序比单线程程序困难的多。

健壮性降低的例子

一个线程如果出现了异常会影响其他线程(健壮性、鲁棒性较差)

  1 #include<iostream>2 #include<string>3 #include<unistd.h>4 #include<pthread.h>5 using namespace std;6 void* start_routine(void* args)7 {8         string name = static_cast<const char*>(args);//安全的进行强制类型转换9         while(1)10         {11                 cout<<"new thread create success, name:"<<name<<endl;12                 sleep(1);13                 int* p = nullptr;14                 *p = 0;15         }16 }17 int main()18 {19         pthread_t id;20         pthread_create(&id, nullptr, start_routine, (void*)"thread new");21         while(1)22         {23                 cout<<"new thread create success, name: main thread"<<endl;24                 sleep(1);25         }26         return 0;27 }

运行:

线程出现异常会影响其他线程,这是因为信号是由OS发送给整个进程的,当前线程出现异常,那么OS识别到当前硬件报错、地址转化出现失败、没有权限的空间进行写入、MMU+页表执行异常等问题,OS会立即识别是哪个线程/进程出错,而所有的线程的PID是相同的,因此OS会直接给所有该PID的线程的PCB写入11号段错误信号,这就终止了当前的进程执行流,当前进程就退了,而线程所拥有的资源是进程给的,进程没了,线程也就得退出了。

4.线程的异常

当线程如果出现除零、野指针问题,会导致当前线程崩溃,进程也会随之崩溃。线程是进程的执行分支,线程出现异常,就等同于进程出现异常,进而触发信号机制,终止进程。进程终止了,进程内运行的所有线程也就终止了。

5.线程的用途

  1. 合理使用多线程,可用提高CPU密集型程序的执行效率;
  2. 合理使用多线程,可用提高IO密集型程序的用户体验(例如,我们一边写代码,一边下载开发工具,就是多线程运行的一种表现)

三、Linux下的进程与线程

进程是承担分配系统资源的基本实体,线程是系统调度的基本单位。

进程的多个线程共享的资源

  1. 因为这些线程在同一个地址空间,所以代码段(Text Segment)、数据段(Data Segment)都是共享的。
  2. 如果是函数,那么在各个线程内都是可用调用的;如果是变量,那么在各个线程中都可以访问到。
  3. 线程还贡献一下进程资源和环境:
    文件描述符表、每种信号的处理方式(SIG_IGN、SIG_DFL或者自定义的信号处理函数)、当前的工作目录、用户id和组id。

线程独立的数据

进程内的线程共享进程的数据,但是也拥有自己独立的一部分数据。
线程ID、一组寄存器:存储线程的上下文信息、栈:线程的临时数据、errno、信号屏蔽字、调度优先级。

进程与线程的关系


我们之前接触到的只有一个线程执行流的进程,就是单线程进程。


总结

以上就是今天要讲的内容,本文介绍了本文介绍了地址空间和二级页表、Linux下的线程、线程的优缺点以及线程与进程的关系等概念。本文作者目前也是正在学习Linux相关的知识,如果文章中的内容有错误或者不严谨的部分,欢迎大家在评论区指出,也欢迎大家在评论区提问、交流。
最后,如果本篇文章对你有所启发的话,希望可以多多支持作者,谢谢大家!

Linux之多线程(上)——Linux下的线程概念相关推荐

  1. linux线程堆分配,如何在Linux中的相同进程下为线程分配堆栈或内存

    Linux中当前的"线程"概念是 NPTL. NPTL使用 clone(),包装 sys_clone().为新的'线程'分配堆栈在用户空间(即libc)中处理,而不是在内核(即Li ...

  2. 达梦DM8单进程多线程架构模式下各线程详解

    达梦数据库进程管理方式类似于Mysql,属于单进程多线程模式.数据库服务进程包含:DmServer(主服务进程)和DmAPService(备份服务进程).线程主要包括:监听线程.IO线程.工作线程.调 ...

  3. linux c++ 线程支持 多核应用,linux C++多线程服务端开发

    linux C++多线程服务端开发 UNIX 线程安全的对象生命期管理 当析构函数遇到多线程 构造不要在构造函数中注册任何回调 不要在构造函数中把this传给跨线程的对象 即便在构造函数的最后一行也不 ...

  4. Linux查看进程下的线程

    在Linux中,查看进程下的线程有很多种方式,常见的有:top.ps.htop(需额外安装),具体的参考:Link,下面主要介绍使用ps命令的方式. 在ps命令中,"-T" 选项可 ...

  5. 操作系统学习笔记-2.1.5线程概念和多线程模型

    操作系统学习笔记-2019 王道考研 操作系统-2.1.5线程概念和多线程模型 文章目录 5线程概念和多线程模型 5.1知识概览 5.2 什么是线程?为什么要引入线程? 5.3引入线程及之后,有什么变 ...

  6. Windows 和 Linux 应用程序从上到下调用层次比较

    刚毕业的时候,做了将近一年的Window下的程序开发,主要用MFC,那是也不明白程序在操作系统角度从上到下的整个调用层次.遇到调用库函数,不明白,就查MSDN,每个月1500行代码左右,那时以为这就是 ...

  7. Linux 多线程(一)线程概念:线程概念、线程与进程、线程间的独有与共享、多线程与多进程、线程控制

    线程概念 线程与进程 线程间的独有与共享 多线程与多进程 线程控制 线程概念 什么是线程 线程是进程中的一条执行流,执行程序中的某部分代码.linux下没有具体实现的线程,只有库函数用pcb来实现的线 ...

  8. linux下java多线程_Linux系统下Java问题排查——cpu使用率过高或多线程锁问题

    原标题:Linux系统下Java问题排查--cpu使用率过高或多线程锁问题 一个系统.特别是多线程并发的后台系统,在某些特定场景下,可能触发系统中的bug:导致cpu一直居高不下.进程hang了或处理 ...

  9. linux 多线程 semaphore ,Linux下多线程编程-Pthread和Semaphore使用.doc

    比锄戴垒丛共麦溺庄哆氏葫季袒飞闲棉铆稼椰悲倘寓矩案铺汞嫡懂伸腑箩五穗颗撩护尚巷苯宅瑚铱焕涅职枝怎摔什街杠写冻泡峡蠢舀以咽铝皇篮糠村墟凤帜攒摧定畜遁陛葛杯复妄婚赣续踌肖祷就抖帘荒徘魂圭焙酸劈待钞林讯啊铂 ...

最新文章

  1. 沈阳招聘.NET(C#)高级软件工程师
  2. bzoj#2125. 最短路
  3. 【JAVA基础篇】Socket编程
  4. 同事删库跑路后,我连表名都不能修改了?
  5. 【SPOJ】2319 BIGSEQ - Sequence
  6. eclipse maven配置_Gradle学习记录015 声明仓库,检查依赖,管理依赖的配置
  7. python 广告分析算法_[Python]研究广告渠道的特征数据与结果数据的相关性, 并对渠道作出评分模型...
  8. 只能发邮件不能接受_《GTA5》R星发邮件问候玩家会发生什么?以下操作一个也别碰...
  9. Hibernate 中 set 里的属性及定义
  10. 华为mate8对flex的支持
  11. .net 怎么在控制器action中返回一个试图_ASP.NET Core MVC/WebAPI中另辟蹊径的全局统一异常处理方式...
  12. 2020-11-11 对象与类
  13. 如何实现电子签章效果
  14. 北风网-Spark视频从入门到精通(高级特性、案例实战、内核源码、性能调优)-中华石杉老师
  15. ocpc php,oCPC匹配词很乱怎么办?| SEM问答
  16. 树状数组、线段树、分块 在同一题目中的应用(Acwing 243)
  17. 显示无法定位程序输入点_CxxFrameHandler4于动态链接库,该怎么解决?
  18. android微信分享长图功能,安卓分享9宫格图片到微信
  19. Ubuntu16.04系统nvidia显卡上图形界面及OpenGL环境搭建心得
  20. 程序员必备技能之英语学习(一)

热门文章

  1. 高数:常用等价无穷小
  2. 怎么让vscode也可以预览html文件:安装扩展view-in-browser
  3. 我的shell脚本实战-编写一个系统发布脚本
  4. Zeo正式对外承接8项Flash业务
  5. SQL语法:浅析select之七大子句
  6. 想要爬虫获取百度指数,浏览器的cookie怎么获得?
  7. 十大经典排序算法最强总结(含Java代码实现),从零开始学springboot百度网盘
  8. Python 企业微信群推送消息
  9. OpenCV开运算(Opening)闭运算(Closing)形态梯度(Gradient)TopHat、BlackHat
  10. django+拉勾网招聘数据可视化