一、再次认识进程地址空间
我们知道,进程地址空间有几个分区:
每一个进程在启动的时候,OS都会给其分配一个地址空间,这就是进程地址空间
- 以先描述再组织的思想,进程地址空间其实是操作系统内核的一个数据结构
struct mm_struct
- 进程具有独立性,在多个进程运行的时候,需要独享各种资源。而进程地址空间的作用,就是让进程以为自己是独占操作系统中的所有资源!
这样的操作,其实就是操作系统给该进进程画了一个(大饼)假的内存(虚拟地址),当进程需要内存的时候,操作系统就会在页表里面编地址给他,再将该地址通过页表映射到物理内存上面
当进程需要申请内存的时候,本质就是操作系统在mm_strcut
中修改不同区域的end罢了。
现在我们详细将一下页表:
我们都知道,页表是用来虚拟地址映射物理地址的。虚拟地址可以任意改变(编译程序的时候,认为程序是按照00...00~FF...FF
进行编址的)。,这样进程就能认为是独享整个操作系统的资源了。
OS进行内存管理,不是以字节为单位的,而是以内存块为单位的,默认大小4KB(32位系统)。
系统和磁盘进行IO的基本单位也是4KB——8个扇区。
操作系统对内存的管理工作,基本单位是4KB
这些看似巧合,其实都是经过精心设计的。
实现了软件与硬件的互通。
我们都知道地址打印出来有32位(32位系统下)。这也是虚拟地址。页表对虚拟地址进行转换为物理地址的时候,会将这32位地址分成3部分,分别是前10位,中10位和尾12位。
OS启动进程的时候,会将程序加载到物理内存。他会按照4KB为单位(IO的基本单位)进行加载,这样每4KB一组加载进物理内存,叫做页框。
这个页框中是存储着物理地址的。而每个页框都进行了各自的编址,他用的就是虚拟内存的尾12位。2^12是4096,正好4KB这样正好对应0—4095.可以存储4KB个字节。我们寻找这4KB的地址。只需要通过首地址进行偏移就行了。
我们知道了物理地址具体存在哪里之后,就需要知道页框的管理方式。虚拟地址的中10位就是管理页框的。2^10是1024。这里可以产生1024个页框。可以存储1024*4KB的内容了。中间这10位我们叫做页号。页号里存着指向页框的起始地址。
虚拟地址的前10位是用来管理页号的。2^10=1024。因此有1024个页号。管理页号的这10位叫页目录。页目录存着执行页号的起始地址。所以,32位下进程虚拟地址可以加载的内容是1024*1024*4KB=4096MB=4GB。
就这样进程地址空间的4GB就被管理起来了。而这个进程最大的虚拟地址也就是4KB+2MB。
其中页目录4KB,页号中存储页框的是12个二进制位。2个字节也就搞定了。一个页号2KB也就搞定。1024个页号。2MB就足够。最多也就2MB+4KB就管理了4GB的空间。页目录也叫一级页表,页号也叫二级页表。
操作系统对页框的管理还通过先描述再组织设计了一个结构体。类似于一下的struct page:
里面存储着对页框中内存的信息,包括处理权限,是否脏页等其他信息。
这些page结构体用数组管理起来:
我们在进行程序编译的时候。每个函数代码都进行了编址。
而且同一个函数我们认为地址是连续的。
函数我们认为是由一个连续的代码地址构成的代码块。一个函数,对应了一批连续的虚拟地址。
虚拟地址本质是一种资源。
进程=内核+进程的代码和数据。
同时进程:承担分配系统资源的基本实体。
二、线程概念
1 .什么是线程
在一个程序里的一个执行路线就叫做线程( thread )。更准确的定义是:线程是 “ 一个进程内部的控制序列 ”一切进程至少都有一个执行线程( 线程是cpu调度的基本单位 )线程在进程内部运行,本质是在进程地址空间内运行在 Linux 系统中,在 CPU 眼中,没有线程的概念,只有轻量级进程,复用了进程PCB的结构和调度算法。看到的 PCB 都要比传统的进程更加轻量化(进程与线程大部分内容相似,重新设计线程不划算且增加了维护的难度)透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流
2 .线程的创建与映射
创建多个 task_struct,并让这多个 task_struct 共享进程地址的空间和页表,这多个 task_struct 就是进程内部的多个执行流,即为 “线程”。
相对于进程,线程的创建成本明显低了很多,只要创建出 task_struct 然后复用进程的地址空间和页表完成。
线程在进程内部运行,本质就是线程在进程地址空间内运行,即进程曾经申请的所有资源,几乎都是被所有线程共享的。
3 . 线程的优点
1. 创建一个线程的代价比创建一个进程的代价小很多。
2. 和进程间的切换相比,线程之间的切换需要 OS 做的工作会少很多。
3. 线程占用的资源比进程少很多。
4. 线程可以充分利用多处理器的可并行数量,一般创建的线程的数量 = CPU 核一般 CPU 是几核的就创建几个线程。
5. 在等待慢速 IO 操作的过程中,程序还可以执行其他的计算任务。
6. 计算密集型应用,为了能在多处理器系统上运行,会将计算分解到多个线程中实现。
7. IO 密集型应用,为了提高性能,将 IO 操作重叠,线程能同时等待不同的 IO 操作。
4 . 线程的缺点
一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。
编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。
进程是访问控制的基本粒度,在一个线程中调用某些 OS 函数会对整个进程造成影响。
编写与调试一个多线程程序比单线程程序困难得多
5 .线程异常
单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止,该进程内的所有线程也就随即退出
6 .线程用途
合理的使用多线程,能提高 CPU 密集型程序的执行效率合理的使用多线程,能提高 IO 密集型程序的用户体验(如生活中我们一边写代码一边下载开发工具,就是多线程运行的一种表现)
三、进程和线程
1 .进程和线程的区别
- 进程是资源分配的基本单位;线程是CPU调度的基本单位。
- 线程共享进程的数据,但也拥有自己的一部分数据:线程 ID、一组寄存器、独立栈结构、errno、信号屏蔽字、调度优先级。
2 .进程的多线程共享
- 由于使用的是同一个地址空间,因此所谓的代码段、数据段都是共享的:
- 如果定义一个函数,在各线程中都可以调用。
- 如果定义一个全局变量,在各线程中都可以访问到。
线程共享的进程资源和环境
- 文件描述符表,进程打开一个文件后,其他线程也能够看到。
- 每种信号的处理方式,SIG_IGN、SIG_DFL或者自定义的信号处理函数。
- 当前工作目录。
- 用户 ID 和组 ID。
3 . 进程和线程的关系
- 在接触多线程之前,之前所接触的都属于单线程进程。
FL或者自定义的信号处理函数。
- 当前工作目录。
- 用户 ID 和组 ID。
4 . 线程私有的资源
- 线程的硬件上下文,CPU 寄存器的值 (强调线程的调度)
- 线程的独立栈结构
- 线程 ID
- 一组寄存器
- 独立栈结构
- errno
- 信号屏蔽字
- 调度优先级。