申小林 发表于 2025-1-2 11:05

《Linux内核深度解析》-02-关于设备的初始化,进程管理

<div class='showpostmsg'><div style="text-align: center;"></div>

<div style="text-align: center;">首先还是图片镇楼</div>

<div style="text-align: center;">&nbsp;</div>

<div style="text-align: center;">
<p style="text-align: left;">&nbsp; &nbsp; 在嵌入式系统开发领域,Linux 内核无疑是一颗璀璨的明珠,其重要性不言而喻。对于众多开发者而言,深入了解 Linux 内核的引导初始化、进程管理以及内存管理等核心环节,犹如掌握了开启高效、稳定嵌入式系统大门的钥匙。当嵌入式设备上电启动,引导初始化过程率先登场,它精心为系统的运行搭建基础环境,确保内核能够顺利加载并执行。进程管理则如同交通枢纽,精准调度系统中的众多进程,让它们有条不紊地运行,充分利用系统资源,提升整体性能。而内存管理恰似一位智慧的管家,精心统筹着有限的内存资源,合理分配、高效回收,保证系统流畅运行,避免资源浪费。本次测评,我将以一个长期专注于嵌入式开发的从业者视角,深入探究 Linux 内核在这三个关键领域的卓越表现,结合实际开发案例与经验,为大家呈现其精妙之处,希望能给同行们带来一些有益的参考与启发。</p>

<p style="text-align: left;">我们首先来说一下,关于内核的启动以及引导</p>

<h2 style="text-align: left;">Linux 内核引导初始化剖析</h2>

<h3 style="text-align: left;">(一)引导程序概览</h3>

<p style="text-align: left;">在嵌入式设备加电的瞬间,引导程序就如同一位尽职的领航员,率先开启系统启动之旅。以广泛应用的 U-Boot(Universal Bootloader)为例,它承担着初始化硬件环境的重任,从最基础的 CPU 时钟设置,确保系统运行节奏稳定,到内存初始化,为后续内核运行开辟空间,再到存储设备的准备,让内核镜像能够顺利读取,每一步都至关重要。当硬件环境就绪,U-Boot 精准地将 Linux 内核加载到内存指定位置,为内核的启动搭建好舞台,随后把控制权优雅地移交,让内核开启后续精彩表演。</p>

<p style="text-align: left;">在开源的嵌入式世界里,还有不少类似的引导程序明星。像 Redboot,它凭借出色的跨平台特性,适配多种硬件架构,为开发者提供了极大的便利,无论是 ARM 还是 x86 架构的设备,都能轻松驾驭;而 Barebox 则以高度可定制化著称,开发者可以根据项目需求,灵活调整引导流程、添加专属功能,宛如为项目量身定制的启动神器。这些引导程序各自闪耀,凭借独特优势,助力嵌入式开发者在不同硬件平台上乘风破浪,开启系统开发之旅。</p>

<h3 style="text-align: left;">(二)内核初始化流程</h3>

<p style="text-align: left;">当内核接过引导程序的接力棒,首先映入眼帘的是汇编语言编写的启动代码,这堪称内核启动的基石。它小心翼翼地设置内核运行的初始状态,从关键寄存器的初始化,精准设定系统运行的初始参数,到中断向量表的精心构建,为系统应对各类中断事件筑牢根基,每一个细节都关乎后续的稳定运行。紧接着,内核无缝切换到 C 语言编写的初始化函数,开启一场盛大的系统初始化狂欢。内存管理子系统率先登场,它精心规划内存布局,将物理内存巧妙划分为内核区、用户区等不同区域,宛如城市规划师精心布局城市功能区;同时构建页表,实现虚拟内存与物理内存的高效映射,让进程访问内存如同在地图上精准导航,快速且准确。进程调度子系统不甘示弱,初始化进程调度器,为后续多进程的和谐共处制定规则,确保每个进程都能在合适的时机获得 CPU 资源,公平竞争,高效运行。设备驱动子系统也忙碌起来,逐一识别并初始化各类硬件设备,从常见的网卡、硬盘,到特定的传感器,让内核能够与硬件设备顺畅沟通,协同工作。当这一系列初始化工作圆满完成,内核会启动 init 进程,这是系统迈向用户空间的关键一步,它如同开启一扇通往丰富多彩应用世界的大门,后续的用户进程都将在它的引领下有序诞生与运行,为整个系统注入无限生机与活力。</p>

<div style="text-align: center;"></div>

<h2 style="text-align: left;">Linux 内核进程管理探究</h2>

<p>&nbsp;</p>

<h3 style="text-align: left;">(一)进程的诞生与家族树</h3>

<p style="text-align: left;">在 Linux 内核的进程世界里,进程的创建方式多种多样,其中 fork、vfork 和 clone 系统调用堪称三把 &ldquo;利器&rdquo;。fork 系统调用如同复印机,它会全面复制父进程的资源,为子进程打造一个全新且独立的运行环境。从代码段、数据段到堆栈,无一遗漏,子进程初始时几乎与父进程 &ldquo;一模一样&rdquo;,宛如克隆体,随后二者便可各自独立发展,互不干扰。vfork 系统调用则像是一位 &ldquo;急性子&rdquo;,它追求创建的高效性,直接让子进程共享父进程的虚拟地址空间,连页面的复制步骤都省略了,父子进程仿佛共用一个 &ldquo;空间&rdquo;,不过这也要求子进程尽快执行 exec 系列函数,开启新的程序之旅,以免影响父进程后续运行。clone 系统调用宛如一位 &ldquo;定制大师&rdquo;,它最为灵活,通过不同的参数组合,开发者能精准定制子进程对父进程资源的共享程度,既可以共享文件系统、信号处理表,也能单独设定线程运行的起点,满足各种复杂的进程创建需求。</p>

<p style="text-align: left;">以一个简单的嵌入式 Web 服务器项目为例,主进程负责监听网络端口,一旦有新的 HTTP 请求到来,它便调用 fork 系统调用创建子进程。子进程继承了主进程的网络相关资源配置,随后专注于处理该请求,读取网页文件、生成响应数据,而主进程则继续监听端口,随时迎接新请求,二者并行不悖,高效协同,确保服务器能够流畅地服务众多客户端。</p>

<p style="text-align: left;">在内核中,进程之间还存在着类似家族树的层级关系,每个进程都有自己的父进程,除了 0 号进程(init 进程)作为系统所有进程的 &ldquo;始祖&rdquo;。当一个进程创建子进程时,便形成了一条父子相连的分支,众多分支交织成庞大复杂的进程家族树,内核通过这棵树状结构巧妙管理进程的生命周期、资源分配以及调度顺序,确保整个系统有条不紊地运行。</p>

<p style="text-align: left;">此外,Linux 内核还支持内核线程与用户线程两种线程模型。内核线程如同系统的 &ldquo;幕后英雄&rdquo;,由内核直接创建和管理,独立运行在内核空间,专注于执行内核函数,如内存同步、延时管理等关键系统任务,为系统的稳定运行默默付出。而用户线程则是在用户空间由线程库创建并调度,它们依托于进程,共享进程的用户地址空间,对于普通应用开发者更为熟悉,开发者可以利用线程库灵活地在应用程序中创建多个用户线程,实现并发执行,提升程序性能,比如在图形渲染程序中,多个用户线程分别负责图像加载、绘制、特效处理等任务,让画面呈现更加流畅迅速。</p>

<h3 style="text-align: left;">(二)调度的艺术:确保公平与高效</h3>

<p style="text-align: left;">Linux 内核中的完全公平调度算法(CFS)犹如一位公正的裁判,致力于让每个进程都能在 CPU 资源分配上得到公平对待。它为每个进程引入了虚拟运行时间(vruntime)的概念,巧妙地结合进程权重,实现了精细的资源分配。想象一下,在一个多任务的嵌入式系统中,既有实时性要求较高的传感器数据采集进程,需要频繁地与硬件交互、快速处理数据,又有后台数据同步进程,对时间敏感性稍低,但数据量较大。CFS 算法会根据它们的权重,精准分配 CPU 时间片。对于传感器数据采集进程,赋予较高权重,使其在单位调度周期内获得相对更多的运行时间,确保数据的及时性;而后台数据同步进程权重稍低,在系统较为空闲时充分利用 CPU 资源进行数据传输,避免过度占用关键进程的时间。</p>

<p style="text-align: left;">在实际运行过程中,进程切换时机的把握至关重要。内核通过时钟中断机制,周期性地检查当前运行进程的时间片使用情况。当进程的时间片耗尽,或者有更高优先级的进程进入可运行状态时,内核便迅速启动进程切换流程。以下是一段简化的进程切换关键代码片段:</p>

<p>&nbsp;</p>

<table style=" border-collapse:collapse; border:none">
        <tbody>
                <tr>
                        <td style="border-bottom:1px solid black; background-color:#f5f6f7; border-top:1px solid black; border-right:1px solid black; border-left:1px solid black" valign="top">
                        <p>// 进程调度函数</p>

                        <p>void schedule(void)</p>

                        <p>{</p>

                        <p>&nbsp;&nbsp;&nbsp; struct task_struct *prev, *next;</p>

                        <p>&nbsp;&nbsp;&nbsp; // 获取当前运行进程</p>

                        <p>&nbsp;&nbsp;&nbsp; prev = current;</p>

                        <p>&nbsp;&nbsp;&nbsp; // 根据调度算法选择下一个运行进程</p>

                        <p>&nbsp;&nbsp;&nbsp; next = pick_next_task();</p>

                        <p>&nbsp;&nbsp;&nbsp; if (likely(prev!= next)) {</p>

                        <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 上下文切换准备工作</p>

                        <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; context_switch(prev, next);</p>

                        <p>&nbsp;&nbsp;&nbsp; }</p>

                        <p>}</p>

                        <p>// 上下文切换函数</p>

                        <p>void context_switch(struct task_struct *prev, struct task_struct *next)</p>

                        <p>{</p>

                        <p>&nbsp;&nbsp;&nbsp; // 切换内存管理相关上下文</p>

                        <p>&nbsp;&nbsp;&nbsp; switch_mm(prev-&gt;active_mm, next-&gt;active_mm, next);</p>

                        <p>&nbsp;&nbsp;&nbsp; // 切换寄存器状态,完成进程切换</p>

                        <p>&nbsp;&nbsp;&nbsp; switch_to(prev, next, prev);</p>

                        <p>}</p>
                        </td>
                </tr>
        </tbody>
</table>

<p style="text-align: left;">这段代码清晰地展示了从调度决策到实际上下文切换的关键步骤。首先,schedule 函数确定当前运行进程 prev 和下一个要运行的进程 next,若二者不同,则调用 context_switch 函数。在 context_switch 中,先处理内存管理相关的切换,确保进程能访问正确的内存空间,再通过 switch_to 宏完成寄存器状态的切换,这一过程如同瞬间切换舞台上的演员,新进程无缝衔接,继续执行,保证系统高效运行。不仅如此,内核还会根据进程的行为动态调整优先级。当一个进程长时间处于等待 I/O 完成的阻塞状态,内核会适当提升其优先级,待其重新就绪后,能更快地获得 CPU 时间,补偿其等待的损失;而对于占用 CPU 时间过长的进程,内核则会降低其优先级,避免它 &ldquo;霸占&rdquo; 资源,让其他进程也有机会一展身手,如此这般,整个系统的响应速度与资源利用率都得到了显著优化。</p>

<p style="text-align: left;">&nbsp;</p>
</div>

<p>&nbsp;</p>
</div><script>                                        var loginstr = '<div class="locked">查看本帖全部内容,请<a href="javascript:;"   style="color:#e60000" class="loginf">登录</a>或者<a href="https://bbs.eeworld.com.cn/member.php?mod=register_eeworld.php&action=wechat" style="color:#e60000" target="_blank">注册</a></div>';
                                       
                                        if(parseInt(discuz_uid)==0){
                                                                                                (function($){
                                                        var postHeight = getTextHeight(400);
                                                        $(".showpostmsg").html($(".showpostmsg").html());
                                                        $(".showpostmsg").after(loginstr);
                                                        $(".showpostmsg").css({height:postHeight,overflow:"hidden"});
                                                })(jQuery);
                                        }                </script><script type="text/javascript">(function(d,c){var a=d.createElement("script"),m=d.getElementsByTagName("script"),eewurl="//counter.eeworld.com.cn/pv/count/";a.src=eewurl+c;m.parentNode.insertBefore(a,m)})(document,523)</script>
页: [1]
查看完整版本: 《Linux内核深度解析》-02-关于设备的初始化,进程管理