linux系统线程实现原理浅析

背景

对进程和线程的理解,之前一直都是凭一些零碎不完整的信息在理解;

linux的进程和线程基本上一样,线程是轻量级进程,彼此有关联又独立。

得亏内核支持的好,写用户态程序可以不依赖于实现的理解,只需要知道从哪个函数开始就是线程函数了,哪里的变量是全局的,哪里的变量是线程私有的。

这样写出来的程序也不至于有问题。

一直觉得没有把这个东西搞清楚,终于受不了这种煎熬决定看下底层是怎么实现的。

线程/进程历史

linux系统是先有进程的概念,后有线程的概念;

linux内核里面现有的线程实现,其实是redhat这个发行版厂商的实现,合入到linux主线了,redhat的实现就是现在的glibc里面的nptl(Native POSIX Thread Library)加上内核fork共同协作实现的

除了NPTL,还有IBM的开源实现NGPT,后面没有被纳入内核,停止维护;

Linux内核也实现了Linux Threads,对比NPTL优势不明显吧

Linux线程实现概述

用户态(glibc)+内核态组合共同实现

用户态做的事情

线程attribute赋值(如果需要)

线程运行时用户态运行的栈空间分配(包括栈空间的缓存和分配,大小、栈地址)

glibc的线程对象创建以及初始化

内核态做的事情

clone系统调用在调用do_fork时,主要flag有:CLONE_VM | CLONE_FS | CLONE_FILES

CLONE_VM表示和创建线程的进程共享地址空间

CLONE_FS表示和创建线程的进程共享文件系统(根目录、工作目录、文件创建掩码等)

CLONE_FILES表示和创建线程的进程共享打开的文件

创建线程用的clone和创建真进程fork的区别是什么

fork在内核也是调用do_fork函数,fork系统调用在调用do_fork创建新的进程时,只携带了SIGCHLD这一个flag,其实就是不共享地址空间、打开的文件、使用的文件系统等,其它的和clone基本没有区别。

说线程是轻量级进程就是这个原因,和创建它的进程共享了一部分主要资源。

后文把线程、轻量级进程、进程统一称为进程,linux系统里面进程是内核支持的,线程是轻量级进程的别名(我认为这样好理解,没有官方说法支持)

用户态和内核态如何配合达到效果

进程是对CPU的虚拟化,CPU是用来执行二进制程序的,二进制可以是用户态也可以是内核态;也就是进程既可以执行用户态代码,也可以执行内核态代码,进程是一个逻辑概念,内核态/用户态代码是表示运行指令时CPU处在不同的保护模式(ring0还是ring3)

进程是由内核态创建,进程主要作用是做为调度一员,获取CPU时间片、地址空间、文件系统、打开文件、进程权限、用户权限、cpu亲和性、调度算法、进程组、运行时间、命名空间等信息(其它信息可以看task_struct的定义)

进程如何创建和运行起来

long do_fork(...)
{struct task_struct *p;p = copy_process(clone_flags, stack_start, stack_size,child_tidptr, NULL, trace, NUMA_NO_NODE);wake_up_new_task(p);}

依据clone_flags(用户态或者系统调用时添加的flags)的不同使用copy_process创建一个新的进程p;

新创建的进程p执行了wake_up_new_task后就会被调度进程调度运行,进程被调度就会分配CPU运行。

如何执行用户态线程函数

用户态线程函数如何触发执行

glibc里面pthread_create在X86_64的实现里面会调用__clone函数(clone.S,clone的实现之一)

1、用户线程函数地址调用pthread_create后如何保存

用户调用glibc函数创建线程时传递下来的fn地址是放在rdi这个寄存器(clone.S注释有说明,应该也是遵循ABI函数调用堆栈参数保存约定的)。

注意这里的fn其实是glibc的start_thread函数,这个函数再调用户的线程函数,指针保存在start_routine成员里面,具体代码如下

if (pd->c11){/* The function pointer of the c11 thread start is cast to an incorrecttype on __pthread_create_2_1 call, however it is casted back to correctone so the call behavior is well-defined (it is assumed that pointersto void are able to represent all values of int.  */int (*start)(void*) = (int (*) (void*)) pd->start_routine;ret = (void*) (uintptr_t) start (pd->arg);}
elseret = pd->start_routine (pd->arg);

2、fn如何被调用

fn是用户态地址,确实没有必要传递到内核函数里面在内核态调用,这样就牵扯到地址转换了

那如何调用?在clone系统调用之前,把fn地址压到子进程的栈里面,系统调用返回后,再弹出地址,通过call执行函数,相关代码为:

ENTRY (__clone)......movq	%rdi,0(%rsi)............movl	$SYS_ify(clone),%eax....../* End FDE now, because in the child the unwind info will bewrong.  */cfi_endproc;syscalltestq	%rax,%raxjl	SYSCALL_ERROR_LABELjz	L(thread_start)retL(thread_start):popq	%rax		/* Function to call.  */popq	%rdi		/* Argument.  */call	*%rax

内核态如何找到用户态的栈

glibc创建栈空间后,栈起始地址以及栈大小会通过系统调用的参数传递到内核

(如上面copy_process函数的第二个和第三个参数)

接下来内核调用copy_thread(clone_flags, stack_start, stack_size, p)(不同的架构对应不同的实现)

*childregs = *current_pt_regs();childregs->ax = 0;
if (sp)childregs->sp = sp;

这里对应的是X86_64的实现,可以看到把用户态传下来的栈指针设置到栈寄存器里面

这里有个点,这个sp和上面保存fn的栈是一个吗?是用户态分配的栈吗?

我认为是的,这个是已经push过参数和fn之后的栈。

其它

为什么线程的栈要用户分配在用户态?

A:栈要么分在用户态,要么分在内核态

如果分在用户态,线程主循环函数运行在用户态,都是在用户态,就不需要CPU模式切换,运行速度更快,都是用户态代码也不存在对内核的安全问题

如果分在内核态,那用户态函数执行,函数执行要不停操作栈,那至少就会有内核地址到用户态地址的转换,要不要牵扯模式切换就另说了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/81641.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL连接报错处理:1130-host ... is not allowed to connect to this MySql server

在MySQL安装完成后,很多开发者会遇到这样一个问题: 错误代码 1130:host xxx.xxx.xxx.xxx is not allowed to connect to this MySql server 这个错误通常出现在你尝试通过远程工具(如 Navicat、DBeaver 等)连接 MySQL …

Linux系统之----进程控制

1.进程创建 进程创建部分由于就是fork函数,还有写时拷贝,在上一篇已经讲述过了,这里不在进行赘述,有疑问的读者可以前往上一篇博文《Linux系统--程序地址空间》中阅读! 这里在多说一嘴写时拷贝吧 我们可以对比一下写…

Spring框架的设计目标,设计理念,和核心是什么 ?

Spring框架是一个为简化企业级应用开发而设计的开源框架,它提供了全面的基础设施支持,使得Java应用开发更加简单、快速和可维护。下面我将详细解释Spring框架的设计目标、设计理念以及核心组件。 设计目标 简化Java企业级应用开发:通过提供…

Red Hat6.4环境下搭建DNS服务器

DNS服务器(Domain Name System Server)是互联网中用于将域名(如 www.example.com)解析为IP地址(如 192.0.2.1)的服务器。它是互联网基础设施的重要组成部分,帮助用户通过易于记忆的域名访问网站…

Nginx核心功能 02

目录 Nginx代理技术核心概念 (一)正向代理(Forward Proxy) 1. 基本定义 2. 技术原理 3. 应用场景 (二)反向代理(Reverse Proxy) 1. 基本定义 2. 技术原理 3. 应用场景 一、…

关于Python:3. Python标准库和常用模块

1. os 和 sys(系统编程基础) 这两个模块是进行系统层面操作(如文件管理、路径处理、环境变量访问等)必不可少的工具。 os 模块 os 主要是用于与操作系统交互的,比如: 文件和目录操作 获取系统信息 运行…

Java基于SaaS模式多租户ERP系统源码

目录 一、系统概述 二、开发环境 三、系统功能介绍 一、系统概述 ERP,全称 Enterprise Resource Planning 即企业资源计划。是一种集成化的管理软件系统,它通过信息技术手段,将企业的各个业务流程和资源管理进行整合,以提高企业…

个人健康中枢的多元化AI网络革新与精准健康路径探析

引言 随着数字化转型的深入推进,个人健康中枢作为集成化健康管理系统,正在从传统的单一功能向多元化的AI驱动方向快速发展。在这一背景下,新兴网络硬件技术,特别是DPU(数据处理单元)和全光网络的出现,为个人健康中枢的革新提供了前所未有的机遇。本研究将深入探讨这些技…

AI跑得快,MCP来加速——模型计算平台在训练与推理中的硬核作用

AI跑得快,MCP来加速——模型计算平台在训练与推理中的硬核作用 一、引言:AI是“铁人三项”,但训练+推理常常“掉链子” 如今的人工智能系统越来越强,像ChatGPT、Stable Diffusion、Segment Anything等模型不断刷新技术天花板。但你是否也注意到: 明明模型设计得挺好,训练…

《MATLAB实战训练营:从入门到工业级应用》工程实用篇-自动驾驶初体验:车道线检测算法实战(MATLAB2016b版)

《MATLAB实战训练营:从入门到工业级应用》工程实用篇-🚗 自动驾驶初体验:车道线检测算法实战(MATLAB2016b版) 大家好!今天我要带大家一起探索自动驾驶中一个非常基础但又至关重要的技术——车道线检测。我…

模型部署——cuda编程入门

CUDA中的线程与线程束 kernel是在device上线程中并行执行的函数&#xff0c;核函数用__global__符号声明&#xff0c;在调用时需要用<<<grid_size, block_size>>>来指定kernel要执行的线程数量。在CUDA中&#xff0c;每一个线程都要执行核函数&#xff0c;并…

WordPress不支持中文TAG标签出现404的解决方法

我们在后台编辑文章时输入中文标签会发现出现404的情况&#xff0c;其实中文TAG标签链接无法打开的原因是WordPress不支持中文的编码。那么解决的方法也很容易&#xff0c;只要改代码让WordPress能支持中文的编码形式&#xff0c;也就是UTF-8和GBK编码即可&#xff0c;无需用到…

金融信贷公司所需的技术和风控体系及其带来的价值

金融信贷公司的技术架构通过集成传统大型机系统与现代数据平台&#xff0c;能够有效支持金融信贷业务的运作&#xff0c;同时通过大数据、ETL、报表开发、数据仓库等技术为公司带来更高效的数据驱动决策、精准的风控分析和更灵活的业务支持。 一、公司技术架构 数据仓库架构&…

《AI大模型应知应会100篇》第43篇:大模型幻觉问题的识别与缓解方法

第43篇&#xff1a;大模型幻觉问题的识别与缓解方法 摘要 当AI系统自信满满地编造"量子计算机使用香蕉皮作为能源"这类荒谬结论时&#xff0c;我们不得不正视大模型的幻觉问题。本文通过15个真实案例解析、6种检测算法实现和3套工业级解决方案&#xff0c;带您掌握…

计算方法实验五 插值多项式的求法

【实验性质】 综合性验 【实验目的】 掌握Lagrange插值算法、Newton插值算法&#xff1b;理解Newton插值算法相对于Lagrange插值算法的优点。 【实验内容】 先用C语言自带的系统函数sin x求出 的值&#xff0c;然后分别用Lagrange、Newton方法求出的值&#xff0c;并与用…

文献总结:TPAMI端到端自动驾驶综述——End-to-End Autonomous Driving: Challenges and Frontiers

端到端自动驾驶综述 1. 文章基本信息2. 背景介绍3. 端到端自动驾驶主要使用方法3. 1 模仿学习3.2 强化学习 4. 测试基准4.1 真实世界评估4.2 在线/闭环仿真测试4.3 离线/开环测试评价 5. 端到端自动驾驶面临的挑战5.1 多模态输入5.2 对视觉表征的依赖5.3 基于模型的强化学习的世…

PostgreSQL:pgAdmin 4 使用教程

pgAdmin 4 是一个用于管理和维护 PostgreSQL 数据库的强大工具。它提供了一个图形化界面&#xff0c;使用户能够轻松地连接到数据库、创建表、运行 SQL 语句以及执行其他数据库管理任务。 安装和使用 安装 pgAdmin 4 安装 pgAdmin 4 非常简单。下载并运行安装程序&#xff0…

Java学习手册:关系型数据库基础

一、关系型数据库概述 关系型数据库是一种基于关系模型的数据库&#xff0c;它将数据组织成一个或多个表&#xff08;或称为关系&#xff09;&#xff0c;每个表由行和列组成。每一列都有一个唯一的名字&#xff0c;称为属性&#xff0c;表中的每一行是一个元组&#xff0c;代…

wpf CommandParameter 传递MouseWheelEventArgs参数

在 WPF 中通过 CommandParameter 传递 MouseWheelEventArgs 参数时&#xff0c;需结合 ‌事件到命令的转换机制‌ 和 ‌参数转换器‌ 来实现。以下是具体实现方案及注意事项&#xff1a; 一、核心实现方法 1. ‌使用 EventToCommand 传递原始事件参数‌ 通过 Interaction.Tr…

八大排序之选择排序

本篇文章将带你详细了解八大基本排序中的选择排序 目录 &#xff08;一&#xff09;选择排序的时间复杂度和空间复杂度及稳定性分析 &#xff08;二&#xff09;代码实现 (三)输出结果 选择排序的基本原理是&#xff1a;每次从待排序的数组中找出最大值和最小值。具体流程是…