1.线程诞生史
1.1 线程诞生的原因
早期是没有线程概念的,只有进程的概念,操作系统以进程为调度单位。——可以这么来理解:早期进程相当于现在的单线程的进程(只有一个线程的进程,创建进程时,里面有一个函数入口称之为主线程)。

后来出现了线程,为啥?因为CPU多核心的出现。为了更好的利用起来多核心,榨干cpu性能。
为啥会出现多核心?因为主频提升遇到了瓶颈,主频达到3GHz等已经好多年了,但目前市面上流通的还是2-3GHz为主。你有见过10GHz,100GHz主频的cpu么?估计几百年都难哩。那为啥主频能提高性能呢?因为主频越高,单位时间内执行的指令就越多,程序运行速度就越快,自然性能就高了。
那主频遇到了瓶颈,该咋提高CPU性能呢?那就用数量来凑呗,于是多核心时代来临了。

当然编译器也是一方面,编译器做的越好,同样的代码编译出来的指令就越少,那程序运行自然就更快。——但这得看软件,优秀的程序员了。但是提升主频是硬件厂家能直接干涉的东东,所以这块也是前期cpu发展的一块发力之处,直到遇见瓶颈,才堆积多核心。

1.2 用户态线程先出现
刚开始出现线程,内核开发人员是不支持的——人家懒得支持因为没有说服力,你得证明线程比进程有优点呀,不然人家那么忙凭啥给你支持?于是最开始线程的实现是用户态实现的线程,由用户来管理。内核调度器对用户态线程没有感知,因为和内核调度器打交道的是进程,内核看不到用户态线程——它相当于一个瞎子。这有很大弊端——一旦用户态线程调用阻塞IO接口那么整个进程都被切换出去,这样其他同一个进程内的用户态线程都被阻塞了,这很不符合预想。

1.3 内核线程后出现
随着用户态线程的出现,证明了线程的优点:切换快,占用资源少。——于是各大操作系统的内核开发者就开始支持了内核线程——用户态线程是进程在用户态创建的由用户管理,现在线程的创建由内核来创建和管理了——即为内核线程——内核调度器的调度度单位就换成了线程,也就是说内核不再是个瞎子了,它能看到线程了——内核线程完全由内核管理,掌控其生死。内核管理的任务粒度又更细了。那么这个时候就来到了内核线程时代。这样同一个进程里的内核线程被阻塞了,同进程内的其他内核线程照样可以执行不会受到干扰。这样极大地提高了并发并行能力,而且这也满足了人们最初对线程的幻想——利用多核心实现并行优势、榨干CPU性能。

2.linux内核线程的实现
linux线程的实现很是偷懒,因为linux线程的实现没有额外增加新的结构体,直接拿已经存在的进程的结构体struct task来用——太偷懒了,直接偷人家的结构体,可见程序员的懒惰性。这就有问题了,在linux内核眼中,进程和线程都是task,那怎么区分呢?从结构体上来说,不区分。同一个进程的线程结构体里面的mm等虚拟地址空间等直接都指向同一个地址(同一个指针),用c语言的指针解决,创建线程的时候直接指向主线程指向的地址空间等资源,即对新task相关成员信息直接用指针指过去,这就是为啥linux的线程称之为轻量级进程的原因——只有创建新的进程的时候才会费大力气进行地址空间开辟等操作,而创建线程时直接指针一指完事!——好一个一阳指功法!

3.32位下linux的进程线程在内存中的模样。

虚拟地址是OS对内存的抽象,也就是OS给进程看的假象。为啥叫虚拟地址?其实就是假的地址。
如上图,在32位下,每个进程都觉得自己拥有4G的虚拟地址空间,0-3G的虚拟地址是给进程的用户态使用的,所有进程的3G-4G地址间都是指向同一份的,也即共享的。为啥这么设计?你想如果每个进程的高1G地址空间不共享,单独的,那切换时太复杂了,且浪费空间,很不爽。所有进程共享内核的话,那只需要维护一个内核资源就行了,方便、节省空间。
我好有一个比喻:进程就是房东,进程中的各线程都是寄人篱下的租客。每个租客共享整个房间的公共区域,然后也有各自的私密空间——每个租客都有自己的小秘密!
具体说就是进程中的所有线程共享同一个地址空间、代码段、数据段、bss段、文件映射区、堆区等等(租客共享的公共区域),但每个线程都有独立的线程栈(租客独享的私密空间)。可以看到图中多线程里比单线程不一样的地方是在堆区多了很多普通线程栈——注意了普通线程(调用glic或uclic等运行时库线程接口pthread开辟的线程)的栈是在文件映射区开辟的——每个栈代表一个线程。主线程栈(也称之为进程栈)是fork进程的时候在栈区开辟的。

4.线程栈
线程栈也是栈,只不过是给线程用的。为何有栈呢?因为cpu的寄存器不够用,人他妈的太聪明了,正好用栈记录下,这样入栈出栈,保存和恢复cpu各寄存器的值。每个线程其实就相当于一个函数,只不过一般线程是一个无限循环的函数。因此每个线程都有一个线程栈。创建线程的时候线程栈会初始化为默认的cpu寄存器值。等到线程正常退出时会调用默认的一个函数来做清理工作之类的。
线程的运行动态图之一:线程正常运行时,线程栈就是在周而复始地入栈出栈。——就像一个气球吹大又放气变小,再吹大再放气变小,如此循环往复;也像内燃机或者打针时针管里的活塞运动,周而复始不断地进进出出;也像一个心脏周而复始地在跳动(不断缩紧和膨胀,嘭嘭、嘭嘭响)。
每个线程正常运行时,其栈达到的最大深度是有上限的(这个和程序代码有关,一旦写完就固定了)。栈溢出就是捅破天了。
特别注意:每个线程有各自独立的线程栈——不与其他线程共享。

5.为什么要用虚拟地址?
(1)安全,虚拟地址把用户程序与真实物理地址隔离了——相当于把用户程序关在笼子里、关在监狱里了!如果没有虚拟地址,直接暴露物理地址给用户,用户程序可以把os干死。
(2)解决重定位问题——换入换出导致进程地址不断变化的问题(重定位问题)。虚拟地址的引入解放了编译器、链接器和程序员的工作,增加了os的工作,进一步提高了生产力。编译器、链接器和cpu看到的都是虚拟地址,且都是从0开始,os和MMU看到的是物理地址。那么编译器和程序员等不用考虑换入换出时进程对应的真实物理地址——这部分由os做了,os保证把不同的进程虚拟地址空间映射到不同的物理地址,os负责了换入换出地址映射事宜。
(3)解决内存不够,小内存运行大程序的问题。基于虚拟地址的swapping技术(交换技术)以及程序局部性原理(时间局部性和空间局部性原理,即时间上在不远的将来还会访问该地址,空间上访问了改地址那么下次很有可能访问它相邻地址空间,一个程序运行起来并不需要全部加载只需要一小部分即可运行),使得大程序也能运行在小内存上。

32位linux进程线程在内存中的样子相关推荐

  1. linux 进程映射空间 libc,为什么不能在64位内核的32位Linux进程中映射(MAP_FIXED)最高虚拟页面?...

    尝试测试时是否允许访问跨越x86中零边界的内存?在Linux的用户空间中,我编写了一个32位测试程序,该程序试图映射32位虚拟地址空间的低和高页. 之后echo 0 | sudo tee /proc/ ...

  2. 707-详解32位Linux系统内存地址映射

    详解32位Linux系统内存地址映射 我们先看一段简单的C程序: 我们先来看一张图: 我们平时所说的x86 32位指的是:80386往后到现在的同一个体系的CPU处理芯片,但是x86这个芯片是从808 ...

  3. 32M内存 跑linux内核,32位Linux单进程4G内存限制

    可用下面代码测试系统能给单进程分配多少用户态内存: 1 #include 2 #include 3 4 int main(){ 5   int MB = 0; 6   while(malloc(1 & ...

  4. 32位linux 内存占用,LINUX内存高,触发OOM-KILLER问题解决

    最近遇到两起Linux的内存问题,其一是触发了oom-killer导致系统挂 1. 首先确认该系统的版本是32位 #uname -aLinux alarm 2.6.9-67.ELsmp #1 SMP  ...

  5. 【操作系统实验】Linux进程通信—共享内存通信、管道通信

    Linux进程通信-共享内存通信.管道通信 一.实验目的: 二.实验题目: 1. 试设计程序利用共享内存完成如下进程通信 1.shmget函数 2.shmat函数 3.shmdt函数 4.shmctl ...

  6. Windows Server 2003的32位企业版支持4G以上内存

    很多朋友是为了使用4G以上的内存才安装了WINDOWS2003企业版,可是装好了之后却发现系统所使用的内存只有3G多,是不是WINDOWS2003企业版32位,不支持大于4G以上的内在?其实不是!&q ...

  7. 32位计算机直接访问的内存,32位64位-32位计算机可以访问多少内存?

    32位64位-32位计算机可以访问多少内存? 32位或64位计算机是什么意思? 这是处理器架构-一台32位计算机可以与64位计算机同时读写32位数据-. 32位计算机可以访问的最大内存是多少? 它是2 ...

  8. 详解为什么32位系统只能用4G内存.

              既然是详解, 就从最基础的讲起了. 1. Bit(位)               Bit计算机是计算机最小的存储单位,  大家都知道计算机实质上都是用二进制数0或者1来存储数据的 ...

  9. Linux进程线程学习笔记:运行新程序

    Linux进程线程学习笔记:运行新程序 周银辉 在上一篇中我们说到,当启动一个新进程以后,新进程会复制父进程的大部份上下文并接着运行父进程中的代码,如果我们使新进程不运行原父进程的代码,转而运行另外一 ...

最新文章

  1. Django Cache缓存系统介绍及Memcached使用
  2. 数据库水平切分的实现原理解析——分库,分表,主从,集群,负载均衡器
  3. HDU 3037 Saving Beans (Lucas法则)
  4. java setter_java – 如何获得@getter和@setter?
  5. scala定长数组(接近Java数组)
  6. Debian update apache error AH00111: Config vairable ${APACHE_RUN_DIR} is not defined
  7. 支持向量机SVM的python实现
  8. Uvaoj 11248 Frequency Hopping(Dinic求最小割)
  9. 60-100-240-使用-DataSource-JDBC相关-JDBC读取各种数据源
  10. 孟菲斯风格海报设计素材,艺术一点点
  11. Java Annotation(注解)使用教程
  12. javascript二维数组
  13. Buffer Pool--锁定内存页
  14. java es score_elasticsearch系列(七)java定义score(示例代码)
  15. XMind 2022 使用教程
  16. TortoiseSVN中Branching和Merging实践
  17. Java修改图片尺寸
  18. Linux---带你区分根目录 和 家目录
  19. FFmpeg之视频转码
  20. LINUX系统ubuntu显示隐藏文件夹选项

热门文章

  1. apache配置文件httpd.conf部分参数说明
  2. ASP.Net新手项目经验谈
  3. vs2005 pro 在浏览器查看下的一个问题!
  4. kubernetes 数据_为什么数据科学家喜欢Kubernetes
  5. vis.js入门_使用TensorBoard数据Vis的TensorFlow手术分类器入门
  6. 厦门大学计算机学硕复试,【图片】一战厦大计算机上岸,经验帖。慢更【考研吧】_百度贴吧...
  7. unbuntu管理员的切换
  8. 四.激光SLAM框架学习之A-LOAM框架---项目工程代码介绍---2.scanRegistration.cpp--前端雷达处理和特征提取
  9. 河南理工大学计算机学院课表,河南理工大学实验课课程表.doc
  10. linux基础配置脚本,Linux中selinux基础配置教程详解