Linux 线程

1.概念

1.进程是一个执行起来的程序。进程=内核数据结构+代码和数据

2.线程:执行流,执行粒度比进程更细。是进程的一个执行分支。

进程:承担分配 资源的基本实体 。------>申请 内存

线程:线程是OS调度的基本单位 。------>划分内存

如果Linux要支持线程,那么线程就必须被OS管理!

那么就要描述tcb结构------>可不可以使用PCB 代替tcb

2.Linux下线程的概念和实现

结论:

Linux下的线程,是用进程模拟实现的(复用历史代码)

线程的task_struct <= 进程的task_struct ------> Linux执行流,统一称为轻量级进程LWP (Light Weight Process) ------> 线程在进程内部运行,线程在进程的地址空间内运行(共享地址空间

1.3进程 VS 线程

进程,只有一个执行分支,即内部只有一个执行流

初步使用

cpp 复制代码
//线程创建                 输出tid       线程属性集
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
// start_routine:回调函数
// arg:回调函数的参数
bash 复制代码
# 查进程
ps ajx
# 查LWP
ps -aL

查询LWP后,可以观察到:

新线程PID与主线程PID一样,LWP不一样。PID == LWP的是主线程

4.地址空间------资源划分

4.1内存的实现

OS如何管理内存(物理内存)?先描述,在组织

描述:

组织:

cpp 复制代码
struct page * mem_map[N]

这样每一个page就有了下标!

LInux中内存管理的基本单位是4KB

下标 <------> 物理地址 就可以相互转换

物理地址 == 下标*4KB

下标 == 物理地址 / 4KB

一个32位的地址,前10位用来查找一级页表,中间10位用于查找二级页表,最后12为用于确定page内偏移

当我们讨论虚拟地址到物理地址的转换的时候,页表的映射关系已经在加载的时候进行关联了!

虚拟地址地址到物理地址的转换------谁做的?
MMU(Memory Management Unit)------>硬件自动完成的(不能复杂,转换很频繁)

4.2TLB

1.硬件

TLB(Translation Lookaside Buffer)即快表,即页表的缓存。TLB 的作用就是缓存最近使用过的虚拟地址与物理地址的映射关系(从页表中提取)。当 CPU 需要转换虚拟地址时,会先查 TLB:

  • 如果命中(找到对应映射),直接使用 TLB 中的物理地址,无需访问页表,大幅提升速度;
  • 如果未命中,才会去访问页表查询,并将新的映射关系存入 TLB(替换掉不常用的条目)。

TLB 就像页表的 "快捷方式缓存",利用局部性原理 (程序倾向于重复访问近期使用的地址,及其附近的地址),减少内存访问次数。

2.软件

页表中映射内存的权限在加载时就划分好了(代码区、数据区)

cpp 复制代码
char* msg = "hello bit";
*msg = 'h';
//操作系统怎么知道转换失败了呢?
//MMU+TLB => CPU 出错了(权限)------>软中断
  • 缺页中断:TLB没有命中,让OS通过软中断执行新加载逻辑
  • new 和 malloc 是申请虚拟内存(页表添加对应虚拟内存,但设置为命中)(使用时就会发生缺页中断)
  • 写时拷贝:将数据区改成只读,写入时(类似缺氧中断的处理)
  • 未初始化------没有页框指向内存(怎么知道没有人指向我自己:page引用计数)
  • 申请内存,究竟是在干什么?申请虚拟地址空间 && 填充页表

使用虚拟内存,将进程管理与内存管理解耦了。当进程使用申请的内存时,发生缺页中断让操作系统填充页表。

如何区分是缺页了,还是真的越界了??

C/C++越界访问一定会崩溃吗?不一定?

1.页号合法性检查:若合法,但不存在,缺页中断;若非法,越界访问

2.内存映射检查:映射范围不在内存中,缺页中断;不在映射范围内,越界访问。

5.线程控制概念与控制

5.1线程的优点:

  1. 代价小(创建)
  2. 占用资源少
  3. 线程间切换,OS做的工作少很多
  4. 充分利用可并行数量
  5. 等待 I/O操作时,程序可执行其他计算任务
  6. 计算密集型应用,将计算分解多个线程中实现
  7. I/O密集型应用,线程可以同时等待不同的I/O操作

其中4、5、6、7是进程、线程共同的优点

第3点:

① 进程会切换TLB,线程不会

②线程不会切换cache

概率缓存将要使用代码段------>局部性原理------>cache本质是缓存------>预加载

线程不是越多越好,有调度成本

一旦不存在局部性原理,内存预加载就失效了

5.2线程的缺点

  • 性能损失
    • 一个很少被外部事件阻塞的计算密集型线程往往无法与其它线程共享同一个处理器 。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。
  • 健壮性降低
    • 编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。
  • 缺乏访问控制(线程优点带来的缺点)
  • 编程难度高(同步,互斥)

5.3线程异常

  • 单个线程崩溃,进程也会随着崩溃

5.4Linux进程VS线程

  • 进程是资源分配的基本的单位
  • 线程是调度的基本单位
  • 线程共享进程数据,但也拥有自己的一部分数据
    • 线程ID
    • 一组寄存器

5.5Linux线程的控制

Linux内核中,只有LWP的概念,线程是使用LWP模拟的!

Linux操作系统,不会提供线程接口,只会提供轻量级进程接口!

cpp 复制代码
pid_t vfork(void);

int clone(int (*fn)(void *), void *stack, int flags, void *arg, .../* pid_t *parent_tid, void *tls, pid_t *child_tid */ );

实际上,vforkfork都是调用clone封装的

有了线程的概念,在库中实现了用户级线程

计算机中任何问题都可以通过新增一层软件层解决

pthread------用户级别的线程库,Linux系统自带的原生线程库

C++11:thread------C++支持多线程,本质是封装了pthread

POSIX线程库:

cpp 复制代码
// 创建线程
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);

// 1.新线程和main线程谁先运行,不确定
// 2.线程创建出来,要对进程时间片进行瓜分
// 获得自己的id
pthread_t pthread_self(void);

// 3.不加保护情况下,显示器文件就是共享资源
// 4.进程内的函数,线程共享
// 5.全局变量在线程内部是共享的
// 6.线程一旦出现异常,可能会导致其他线程全部崩溃
// 7.线程创建之后,也是要被等待和回收的
// 理由:a.类似僵尸进程的问题	b.为了知道新线程的执行结果

// 阻塞等待                tid			返回值,输出型
int pthread_join(pthread_t thread, void **retval);
// 等待返回值,成功为0;失败返回错误码

// 8.传参问题(以前父子进程:通信、写时拷贝,数据结构)
//	 传递参数,可以是变量、数字、对象
// 9.返回值问题:返回参数,可以是变量、数字、对象
// 9.1理论上,堆空间也是共享的!谁拿着堆空间的入口地址,谁就能访问该堆区
// 10.新线程return表示该线程退出
// exit();//任何地方调用exit,表示进程退出
void pthread_exit(void *retval);// 线程退出
// 取消一个线程
int pthread_cancel(pthread_t thread);
// 不推荐,一般是主线程取消新线程
// 用得少,用kill可以实现同样效果
// 取消线程,一定是目标进程已经启动了
// 1.cancel,必须进行join
// 2.cancel后join后的返回值为-1 //PTHREAD_CANCELED

/*
主线程也要做自己的事情呢?
可以不等待新线程------将目标线程设置为分离状态
线程被等待状态:
1.joined:线程需要被join
2.detach:线程分离(主线程不需要等待新线程,类似分家)(线程退出时,自动释放资源)

在多执行流的情况下,主执行线程是最后、不退出的



*/

6.理解线程调度

6.1线程地址

切入点------线程ID

tid是什么?是地址,用于标识线程栈

Linux只有LWP,用户要用线程?线程接口,如果我想要线程的属性呢?

id,优先级,状态,栈大小

若有100个线程,库内要维护100份线程的属性集合?库要对线程属性进行管理??所以要对LWP进行封装

那么线程的上下文在哪里?地址空间中栈只有一个啊

地址空间,主线程有一个栈,其他线程是被动态申请出来的(在共享区里,固定大小)

_thread修饰内置类型------线程的局部存储(1.每个线程一份变量 2.只能修饰内置类型)

6.2线程栈

对于子线程,stack不再是向下生长的而是事先固定的

centos中size是8M,这种stack不能动态增长,一旦用尽就没有了

相关推荐
maosheng11467 小时前
RHCSA的第一次作业
linux·运维·服务器
wifi chicken8 小时前
Linux 端口扫描及拓展
linux·端口扫描·网络攻击
旺仔.2918 小时前
Linux 信号详解
linux·运维·网络
放飞梦想C8 小时前
CPU Cache
linux·cache
Hoshino.419 小时前
基于Linux中的数据库操作——下载与安装(1)
linux·运维·数据库
播播资源11 小时前
CentOS系统 + 宝塔面板 部署 OpenClaw源码开发版完整教程
linux·运维·centos
源远流长jerry11 小时前
在 Ubuntu 22.04 上配置 Soft-RoCE 并运行 RDMA 测试程序
linux·服务器·网络·tcp/ip·ubuntu·架构·ip
lay_liu11 小时前
Linux安装redis
linux·运维·redis
寂柒13 小时前
序列化与反序列化
linux·网络
lay_liu13 小时前
ubuntu 安装 Redis
linux·redis·ubuntu