【Linux线程】Linux系统多线程(一):线程概念

张开发
2026/4/10 20:48:52 15 分钟阅读

分享文章

【Linux线程】Linux系统多线程(一):线程概念
个人主页艾莉丝努力练剑❄专栏传送门《C语言》《数据结构与算法》《C/C干货分享学习过程记录》《Linux操作系统编程详解》《笔试/面试常见算法从基础到进阶》《Python干货分享》⭐️为天地立心为生民立命为往圣继绝学为万世开太平 艾莉丝的简介文章目录1 ~ Linux线程概念1.1 直入主题1.2 Linux线程的一种实现方式轻量级进程“轻一点”1.2.1 什么是轻量级进程1.2.2 重新定义进程1.2.3 CPU如何看待1.2.4 创建进程和创建线程本质上是不一样的。1.2.5 整理1.3 执行流资源划分的本质内存管理1.3.1 这个结构体并不大1.3.2 三个主要结论1.3.3 文件到物理页框的映射1.3.4 执行流资源划分本质的思维导图1.4 理解页表和虚拟地址到物理地址映射1.4.1 页表1.4.2 二级页表1.4.3 实验创建线程1.4.4 交付概念1.5 Linux线程的验证结尾1 ~ Linux线程概念1.1 直入主题必须先谈一下进程是什么进程 内核数据结构 自己的代码和数据具体实现角度去谈的抽象 - 具体今天给进程再下一个定义从内核视角出发的进程是承担分配系统资源的基本单位。我们再给线程下个定义线程是进程内部的一个执行分支概念线程的粒度比进程要细一些、小一些或者更轻一些1.2 Linux线程的一种实现方式轻量级进程“轻一点”1.2.1 什么是轻量级进程这里的task_struct叫做轻量级进程。系统当中线程个数比进程更多。线程是在进程的地址空间内部线程也有自己的唯一性标识——线程也需要被操作系统管理起来那必然要有一个结构体struct TCB {}。线程在OS内如果存在那么就一定会存很多OS也就必然要对线程进行管理——要管理那必然是“先描述再组织”。TCBThread Ctrl Block线程内部是不是有自己的结构呢也有状态也有优先级。线程要作为进程的一部分PCB 里面是不是还会维护一个线程链表来维护呢这个系统一定会设置的非常复杂Windows 就是这样干的所以有时候会有莫名其妙的错误。Linux 这里就比较聪明了没有给创建内核级的 TCB并且要搞的话是不是还得给你整调度算法啊所以复用进程的 PCB实现线程的效果——轻量级进程。1.2.2 重新定义进程我们需要重新定义一下进程了进程内部的执行流 地址空间 代码和数据合起来叫做进程。线程轻量级进程和进程的关系。一个进程有多个执行流进程。今天谈的和之前的概念完全适配今天的进程叫做多执行流进程内部可能有一个或多个PCB。我们以前学的是特殊情况今天是普遍情况。创建线程只需要创建PCB然后分配就行啦所以说线程比进程更小、更细、更轻。故事全世界的社会资源分配的单位如果是家庭假设每个家庭有一个自己的房子今天内部通常会存在很多的人每一个人都在做各自的事情今天内部会有各种各样的成员各自做着各自的事情——其实都在共同实现同一个目标我们都在完成把家里的日子过好这样一个目标——社会交给家庭的任务。今天的线程其实是轻量级进程的多执行流进程以前的“进程”只是单执行流的特殊情况相当于一个家里只有一个家庭成员而线程就是有各种各样的家人为了一个同样的目标——“把家里面的日子过好”——把家庭看成进程把家庭内部的一个一个成员叫做线程。概念回归进程是承担分配系统资源的基本单位。每一个进程内部创建线程在进程内部再分配资源。创建进程和创建线程本质上是不一样的。我们以前学习到的进程算一个家庭里面只有一个人了这就是线程一个执行流可能是曾经的一部分。什么叫是进程内部的执行分支具体来说就是在进程的 PCB 中再单独创建一块地址空间把代码给他一部分就是线程。这个线程是在进程的地址空间内部每创建一个线程都是复用然后给一块代码和数据。这样我直接复用进程的就能实现线程的效果。1.2.3 CPU如何看待CPU 调度进程的时候会看到很多很多task_struct有可能执行的是某一个单独的进程也可能是某一个进程中的一部分某一个执行分支线程。Linux 中我们也可以把task_struct叫做轻量级进程不喜欢叫做线程——之前的调度算法状态切换依旧是适用的。进程状态的调度、切换、优先级依旧适用。1.2.4 创建进程和创建线程本质上是不一样的。要创建执行流创建地址空间创建等等。进程承担系统分配资源这都是为了未来给线程分配。以前创建进程就是我生了个儿子但是他独立出去了有了自己的家庭。我们以后不把PCB叫做进程了而是把那一整块叫做进程一个或者多个PCB地址空间页表等。我们说把进程的资源分配一份给线程执行流咋分配的呀怎么分配啊怎么让不同的执行流执行进程中的不同代码呢1.2.5 整理1.3 执行流资源划分的本质内存管理重谈虚拟地址空间谈页表——具体查表的过程。我们从磁盘中读写以 4KB 为单位这个之前说过好多次了。关键是读写磁盘时读到哪里从哪里来写到磁盘是和物理内存在进行 IO数据从内存来。IO 基本单位 4KB。物理内存逻辑上也是以 4KB 为单位管理的为了方便计算物理内存是被划分成了一个个块的。比如我们内存 4GB4GB/4KB 我们把一个 4KB 的这块叫做页框或者页帧一页数据。我们在内存管理视角更喜欢叫页框。写时拷贝也不是变量而是以4KB为单位的一块进行写时拷贝的。那我们难道物理内存是像巧克力板子一样一块一块这样的嘛当然不可能。硬件上其实是没有这样 4KB4KB 的东西的我们可以想成一个空房子里面放啥是我们决定的软件决定的。有的内存块是数据可读可写有的是代码可读可执行有的是其它的属性数据有的是保存的 OS 的代码有的是被划分到内核级缓冲区里面。有的内存块是缓冲文件的代码和数据处于被占用状态。有的是脏的需要被刷新等等。所以不同的内存块都会有自己的状态。OS 需要对所有的物理内存块进行管理OS 必须对多个数据页框进行管理——如何进行管理——“先描述再组织”。“先描述再组织”。必然有内核数据结构// 不会被设置的太大structPage{intflags;// 标志位最重要的}必然有内核数据结构——1.3.1 这个结构体并不大这个结构体本身不大再组织怎么组织不同的系统差别比较大。在 Linux 中在内核里往往会存在一个 --采用的是数组的方式会在 OS 定义一个全局的数组有多少个页框就是多大。看这棵树树根包含一个节点类似于一个数组slots 指向一个个 struct page。只要我们知道一个 page的地址想知道下标的话转换一下。不一定长这样但是是个数组每个 page 是不是都会有下标0 对应第一个 4KB 依次下去。我必须知道物理地址才能访问它。下标可以转换为物理地址嘛Index 12 || index * 4KB我们就直接把下标转成物理地址。地址 4KB 对齐那么低 12 位是全 0。我知道一个页的地址能不能直接转成下标物理地址 12理解物理内存的管理知道了任意一个页的地址能不能转成下标Index 12就行了低12位清零。任意一个地址高20位页框地址低12位无非就是任意地址在这个页框中的偏移量——任意一个地址呢1、对应那一个页框任意地址 0xFFFFF0002、对应的 struct page 怎么找本质是如何对应数组下标12 位4KB 2^121.3.2 三个主要结论结论一OS 管理物理内存是以 4KB 为基本单位的。写时拷贝也不是变量而是一块进行写时拷贝的结论二页地址任意地址数组下标可以互相转换结论三无论是进程还是文件还是其他模块要向物理内存申请内存的过程本质就是申请 struct page 结构体和下标的过程。1.3.3 文件到物理页框的映射我们来看看页表的标志位使用不同的比特位表示不同的状态所谓的文件缓冲区就是一个或多个 Page。我们可以看到 Page 里面也是引用计数的。Struct Page// 不会被设置的太大{intflags---标志位Page里面最重要的}地址4KB对齐地址低12位是全0。所谓的文件缓冲区就是一个或者多个Page。Page里面也是引用计数的。只要是联合体——只会用其中一个字段——不会太大。Page就64字节不大在Linux中归根结底在内核里会存在一个struct page。操作系统内部定义一个全局的数组大数组1.6%内存空间用来保存这个大数组。对内存块的管理转换成对这个大数组的管理。每个Page是不是都会有一个下标0对应第一个4KB依次下去。光知道下标有什么用呢必须知道物理地址才能访问它啊下标可以转换成物理地址嘛可以。把下标*4KBIndex * 4KB或者左移12位Index 12就可以转换成物理地址了。右移是位操作对CPU来说是非常快的本质是右移12位12低12位清零高20位保留——右移12位把任意地址转换成数组下标。页框地址、任意地址数组下标可以互相转换。页框的地址找到了到时候就告诉操作系统物理内存申请好了这个内核结构也是一个树形结构字典树的一种没听过字典树我知道多叉树树根包含一个节点信息包含一个slots。slots内部是一个个节点。这样Struct page就知道自己在哪个页框里了。1.3.4 执行流资源划分本质的思维导图1.4 理解页表和虚拟地址到物理地址映射虚拟、物理内存都有了因为页表是虚拟到物理的映射页表左边是虚拟地址右边是物理地址页表还有标志位假设这样一行页表按10字节算做虚拟地址到物理地址的映射10字节2^32这样算下来就是4GB1040GB40GB的1%也有400MB光一个进程的一张页表我的物理内存都装不下页表在逻辑上是key-value虚拟到物理的映射这样的结构但是物理上一定不是。理解了页表才能够理解进程是怎么做资源划分的呢饶了这么大一圈就是为了理解什么是线程1.4.1 页表单纯使用页表访问到某个列。这段代码是 Linux 内核中关于ARM 架构特别是 v6/v7 时代页表项PTE定义的核心片段。简单来说它定义了 CPU 如何理解一段内存的“性格”能不能读、能不能写、是不是缓存。1~12个标志位对应12个比特位和page的很多标志位是对应的。对于一个进程来说进程拥有多少资源本质是取决于这个进程拥有多少个有效虚拟地址本质就是虚拟地址经过页表映射的页表条目越多虚拟地址空间本质是一个窗口——进程拥有的资源有多少就是虚拟地址空间的这个窗口开得多大有效的虚拟地址越多窗口越大——本质就是虚拟地址经过页表映射的页表条目越多拥有的资源就越多。所以把一个进程的资源进行划分本质是把这个进程相关有效虚拟地址进行划分就可以了再本质一点就是大家在划分页表所映射的页框1.4.2 二级页表下面是质变内容非常非常重要怎么把一个资源绑定给一个执行流。数组下标可以和物理地址进行映射可以进行转换。32个比特位前10个比特位是一块中间10个是一块后面12个是一块每个虚拟地址被划分成了101012三个部分一共32个比特位。虚拟地址不是铁板一块而是被MMU把虚拟地址看成了三块分别是10/10/12102410244096【刚好是页框大小】。页目录4KB1024项的每一项4字节的下标就是虚拟地址的前10位——未来可以用前10位来索引。页目录保存的也是页框的地址。页表中也存特定页表的地址。这就叫做【两级页表】机制64位平台是四级页表机制不考虑。特定页表的地址我可以随便填写。拿着虚拟地址的前10位就可以索引页目录在页目录里就可以索引页目录数组页目录、页表都是做数组下标的数组内容是特定页框的地址换言之拿着虚拟地址前20位就可以索引到具体哪一个页框了数组内容是页表里面特定页框的地址可以随便填写我拿着对应物理页框的起始地址 虚拟地址低12位页内偏移量就可以索引物理内存中的地址了。结论页表映射只会先映射到对应的页框 对应物理页框的起始地址 虚拟地址低12位作为页内偏移就可以拿到具体提供字节的物理地址了。多叉树树形结构CPU内部有硬件转换单元MMU、寄存器。虚拟地址是在编译代码的时候就映射好了的。这样设定的话页表的大小就能够符合要求了吗一个页表4KB对应1024个页框4MB打满的话两级页表1024*1024——整个页表最大就是4MB。不存在大页表页表拆成了两份。是在进程加载的时候创建页表只要使用了内存这个特定页框的地址也填写进去了是操作系统OS帮我填充。编译代码的时候很多的连续代码和数据都会聚集在同一个页内因为属于低12位虚拟地址在页框也就直接能够找连续编址是4KB4KB的进行编址的。并没有到字节级的映射而是页框级别的映射虚拟地址本身不是铁板一块被MMU把虚拟地址看作成了三块1011121024,1024,4096刚好是页框地址大小。搞个页目录4KB1024项每一系项4字节拿虚拟地址前10位来索引页目录确定是那一个页表再拿中间10位去索引页表页表里面保存的是页框的地址。其实我们把页目录 页表叫做两级页表的结构64位是四级先不考虑。所以前20位就可以索引到具体那个页框了数组内容是页表里面页框的地址可以随便填。拿着对应物理页框的起始地址 虚拟地址的低12位页内偏移页表映射只会先映射到对应的页框拿着特定页框的地址 虚拟地址低12位页内偏移就可以拿到具体提供字节的地址。是在进程加载的时候创建页表加载代码和数据的时候虚拟地址啥的有了之后页表创建好了只要使用了内存的话这个特定页框的地址也就填写进去了谁填的其实是OS来操作的。编译代码的时候连续的代码和数据都会聚集在同一个页内部因为他属于低12位。虚拟地址可以映射到物理内存的任意一个页框。页表条目4字节。32个比特位。其实页表当中只能前20个比特位就可以那还有12个呢可以作为权限标志位不能和我们描述一个Page的标志位矛盾必须相辅相成的。对于一个进程来讲进程拥有多少资源本质是看这个进程拥有多少个有效虚拟地址虚拟地址经过页表映射的条目越多。虚拟地址空间本质是一个窗口有效虚拟地址越多窗子越大拥有的资源就越多。所以把一个进程的资源进行划分本质是把这个进程的相关的有效虚拟地址进行划分就可以了——划分页表所映射的页框。把代码资源划分给指定线程本质是只要让不同的线程执行不同的函数即可因为函数在编址的时候已经做了地址空间的划分。1.4.3 实验创建线程在Linux当中创建线程需要调用属性设为nullptr我不管它。下面要看到快速创建一个线程的结果。参数为void*这个参数就是第四个参数即void *restrict arg作为回调函数的参数返回值也是void*的入口函数总结一下新线程执行上面的逻辑主线程执行下面的逻辑代码如下两个死循环不可能在一个单进程里面同时跑。Ubuntu是可以编译通过的支持第三方库-l选项我们学的线程其实是库级别的为什么之后说。依赖哪些库我们是看不出来的同时打印可能存在单执行流里两个死循环一起跑不可能这里肯定是多执行流。主线程和新线程都在同一个进程里面我们查也是查同一个进程所以我两个执行流执行的是同一个进程的同一份。运行验证一下是不是同一个pid运行一下确实是同一个进程但是还是看不出来-a系统中的所有的执行流-L查看轻量级进程Linux中多线程是用轻量级进程来模拟的。在操作系统和CPU的视角操作系统和CPU调度的基本单位是线程在Linux当中就相当于“轻量级进程”。进程承担分配系统资源的基本实体PID是进程级别的看ID我主要看的是LWP。以前看PID还是LWP无所谓两者相等是特殊情况因为以前只有一个执行流所以PID等于LWP。修改一下代码看看是不是把新线程的内容打过去了#includeiostream#includeunistd.h#includepthread.h// 线程// 两个死循环一个执行流不可能同时跑两个死循环验证确实是多个执行流void*hello(void*args){constchar*name(constchar*)args;while(true){std::cout我是新线程...,pid : getpid()std::endl;sleep(1);}}intmain(){// 创建线程pthread_t tid;pthread_create(tid,nullptr,hello,(void*)new-thread);// 调用hellowhile(true){std::cout我是主线程...,pid : getpid()std::endl;sleep(1);}return0;}运行一下打通一下应用层。反汇编查看一下也就是说我把代码资源划分给指定的线程本质是只要让不同的线程执行不同的函数即可因为函数在编址的时候已经做了地址空间的划分。不同的虚拟地址做物理地址映射的时候映射到不同的页框不就是划分了进程的资源执行了不同的函数这不就是我们说的执行了一部分代码嘛1.4.4 交付概念共同完成一个工作拆成多个线程各自完成各自的函数。什么叫做进程资源的合理分配一人一个函数每个线程可以main函数为入口以hello函数为入口……。1.5 Linux线程的验证上面我学习的都是Linux下的线程在Linux系统内部有没有存在真正意义上的struct TCB这样的结构没有PCB模拟线程~~轻量级线程Linux内部所有的PCB执行流都叫做轻量级进程本质上是用轻量级进程平替了线程的概念。Linux内部只有轻量级进程的概念没有线程的系统调用只有提供创建轻量级进程的创建接口这个接口在Linux当中叫做创建执行流有两种方式一种是fork底层封装了这个clone——只不过是标志位让我复制拷贝了页表等结构本质上是创建了一个新的进程创建全新进程就是没有直接创建线程的接口轻量级进程和线程在模式上是一样的但是我现在不要把两个混为一谈了在Linux内部就是没有线程只有轻量级进程意味着对上层也只会提供了创建轻量级进程的接口。并不存在TCB这样的接口。不是世界上所有的程序员都是Linux的工程师所以也不是所有人都懂Linux。怎么线程到你Linux这里成轻量级进程了呢内核层面上也没有线程啊操作系统层面上就不存在线程了嘛这不是没有线程感了吗这不就被其它系统比下去了吗在应用层Linux对上会封装一层软件层表现出线程的概念所以我就看到了LWP这个封装的软件层就叫做pthread库原生线程库这样用户在这个库之上就可以进行线程操作了Linux线程属于用户级线程内核有的是轻量级线程系统调用之上用户级线程。C也提供了线程也可以创建线程#includeiostream#includeunistd.h#includepthread.h// 线程voidhello(){constchar*name(constchar*)args;while(true){std::cout我是新线程...,pid : getpid()std::endl;sleep(1);}}intmain(){std::threadt(hello);while(true){std::cout我是主线程...,pid : getpid()std::endl;sleep(1);}t.join();return0;}单进程代码不可能让两个死循环跑所以一定存在多执行流C里面的多线程和我这个Linux中的pthread库有什么关系C中的线程库也是对pthread库C语言无法做到面向对象做了进一步地封装面向对象的封装就可以面向对象了。结尾uu们本文的内容到这里就全部结束了艾莉丝在这里再次感谢您的阅读艾莉丝努力练剑C/C Linux 底层探索者 | 一个正在努力练剑的技术博主【关注】跟随我一起深耕技术领域见证每一次成长。❤️【点赞】让优质内容被更多人看见让知识传递更有力量。⭐【收藏】把核心知识点存好在需要时随时查、随时用。【评论】分享你的经验或疑问评论区一起交流避坑不要忘记给博主“一键四连”哦“今日练剑达成”“技术之路难免有困惑但同行的人会让前进更有方向。”结语希望对学习Linux相关内容的uu有所帮助不要忘记给博主“一键四连”哦往期回顾【Linux信号】Linux进程信号下可重入函数、Volatile关键字、SIGCHLD信号博主在这里放了一只小狗大家看完了摸摸小狗放松一下吧૮₍ ˶ ˊ ᴥ ˋ˶₎ა

更多文章