背景知识
内存管理
OS进行内存管理不是以字节为单位的,而是以内存块为单位的,默认大小为4kb;系统和磁盘文件进行IO交互的单位是4kb(8个扇区);OS对内存管理实质上是对页框进行管理。
页框(Page Frame):
页框是物理内存中的固定大小的连续块,用于存储进程的页面数据。操作系统将物理内存划分为多个大小相等的页框,作为内存管理的基本单位。例如,在x86架构中,常见页框大小为4KB。页帧(Page Frame):
页帧与页框通常被视为同义词,均指物理内存中的固定块。但在某些上下文中,"页帧"可能更强调其作为地址映射的载体,而"页框"侧重物理划分。实际使用中两者常互换。
对此,操作系统使用struct page结构体来描述和管理页框(struct page memory[1048576])。
struct page {unsigned long flags; // 页状态标志(如脏页、锁定位等)union {struct { // 页缓存和匿名页使用struct list_head lru;struct address_space *mapping;pgoff_t index;unsigned long private;};struct { // slab分配器使用struct kmem_cache *slab_cache;void *freelist;union {void *s_mem;unsigned long counters;};};// 其他使用场景的union分支...};atomic_t _refcount; // 引用计数unsigned long compound_head; // 复合页头指针unsigned char compound_dtor; // 复合页析构函数IDunsigned char compound_order; // 复合页阶数atomic_t compound_mapcount; // 复合页映射计数unsigned int page_type; // 页面类型标识void *virtual; // 内核虚拟地址(若非高端内存)// 其他架构相关成员...
};
页表
虚拟地址32位,系统这样划分:
因此,真实的页表是这样的,页表本质是搜索页框号(二级页表):
页目录4kb,页表一个2kb(每个2字节),一共最大4kb+2MB。根据数据类型就可以拿到完整的数据,每个进程会把自己的页目录起始地址放在CR3寄存器中,CPU使用mmu和该寄存器就可以计算出物理地址。
线程概念
基本概念
线程在进程内部执行,是CPU调度的基本单位。在一个程序里的一个执行路线就叫做线程(thread),更准确的定义是:线程是“一个进程内部的控制序列;每个进程至少一个执行线程。Linux中线程就是创建一批共享地址空间和页表的task_struct,来执行代码中的一部分任务。
进程定义的内核观点:承担分配系统资源的基本实体。
为了方便对线程进行管理,映入struct tcb结构体(Windows),但是在linux不单独设计,复用PCB表示统一执行流,这样不需要设计单独的调度算法:
struct task_struct {volatile long state; // 线程状态(运行、就绪等)void *stack; // 线程栈指针unsigned int flags; // 标志位int prio; // 动态优先级struct list_head tasks; // 线程链表节点struct mm_struct *mm; // 内存管理信息// 更多字段...
};
Linux中的执行流都叫做轻量级进程,CPU看到的执行流<=进程。
线程独立的资源:线程ID,一组寄存器,栈,errno,信号屏蔽字,调度优先级。
线程创建
在Linux中线程叫用户级线程,Windows中叫Windows内核级线程,因为Windows真正实现这一概念。
样例代码:
Linux中没有线程只有进程,因此编译时要引入库-L pthread,系统调研只会给上层用户提供创建轻量级进程的接口,pthread库是Linux自带的原生线程库,对轻量级进程接口进行封装,按照线程接口方式交给用户。
#include<iostream>
using namespace std;
#include<unistd.h>
#include<pthread.h>
void* newthread(void * s)
{while(1){cout << "this is new thread,pid:" <<getpid()<< endl;sleep(2);}
}
int main()
{pthread_t id;pthread_create(&id, nullptr, newthread, (void *)"syx 666");while(1){sleep(1);cout << "this is main thread,pid:" <<getpid()<< endl;}return 0;
}
运行:
查看进程发现:
说明这本就是一个进程,只不过是不同执行流而已,pid一样。
查看线程:
ps -aL
这里LWP(Light Weight Thread)就是线程号。因此,OS调度时看的是LWP,LWP才是执行流的标识,main函数式主线程,其他线程是新线程。
线程优缺点
优点
cpu会把热点数据放在缓存;多线程因为会使用同一个热点数据,减少了进程间的cache切换,相比进程具有更轻量的上下文切换开销,所以调度效率高,进程切换的时候会把上个cache存储的数据删掉,重用率低。
线程上下文切换通常只需保存寄存器状态和栈指针,而进程切换还需刷新TLB、更新内存管理单元等操作。实测数据显示,线程切换开销约为进程切换的1/10到1/30,这种差异在高并发场景下尤为明显。
1.创建一个新线程的代价要比创建一个新进程小得多
2.与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
3.线程占用的资源要比进程少很多 能充分利用多处理器的可并行数量
4.在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
5.计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
6.I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。
线程不是越多越好,合适最好。计算密集型应用时线程数和核数相等最好。IO可以多创建。
缺点
1.性能损失:一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型 线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的 同步和调度开销,而可用的资源不变。
2.健壮性降低:编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了 不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。一个线程出问题,其他线程都会出问题。
3.缺乏访问控制:进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。 编程难度提高 编写与调试一个多线程程序比单线程程序困难得多
4.编写难度高。