申小林

  • 2025-01-17
  • 回复了主题帖: 《奔跑吧Linux内核1:基础架构》-01-总体介绍

    freebsder 发表于 2025-1-17 16:29 新活动?好像24年Linux的读书活动不少呢。 回炉的,就申请了一下。

  • 回复了主题帖: 《Linux内核深度解析》-05-Linux内核互斥技术说明

    秦天qintian0303 发表于 2025-1-17 13:53 采用互斥技术是不是就可以确定内容使用的唯一性?  我的理解是操作的唯一性,内容应该也是唯一的,只是有可能内存部分没更新导致不同步。

  • 发表了主题帖: 《奔跑吧Linux内核1:基础架构》-01-总体介绍

    在操作系统领域,Linux 内核以其开源、高效、稳定等特性占据着重要地位。本书作为深入了解 Linux 内核基础架构的佳作,为读者打开了一扇通往内核世界的大门。对于渴望深入探究操作系统底层原理、提升技术能力的技术爱好者和开发者而言,阅读此书是一次宝贵的学习之旅。本书开篇介绍了 Linux 内核的发展历程,从最初的版本不断演进,逐步成长为如今功能强大、应用广泛的内核。接着深入讲解了内核的基本组成部分,包括进程管理、内存管理、文件系统等。通过生动的比喻和详细的图示,让读者对内核的整体架构有了清晰的认识。例如,将进程管理类比为工厂中的生产流程,每个进程如同一条生产线,形象地展现了进程的创建、调度和终止等过程。          内核数据结构与算法,在数据结构与算法部分,书中详细阐述了内核中常用的数据结构,如链表、树等。这些数据结构在实现内核的各种功能中发挥着关键作用。以链表为例,在进程管理中,链表用于组织进程控制块,方便对进程进行遍历和操作。同时,介绍了相关的算法,如内存分配算法,详细解释了如何高效地管理内存资源,确保系统的稳定运行。 深入探讨了内核的各种机制,如中断处理机制。当外部设备产生中断信号时,内核如何快速响应并进行处理,书中给出了详细的流程和代码实现分析。在系统调用方面,解释了用户空间如何通过系统调用进入内核空间,获取内核提供的服务,这是应用程序与内核交互的重要方式。   整个目录看起来是基于ARM64 的方面比较多,同是还介绍了ARMV8架构进行了对比说明,     通过阅读本书,我对 Linux 内核的理解从表面深入到了底层。深入掌握了进程管理的原理,明白了进程调度算法如何根据不同的系统需求合理分配 CPU 资源,这对于优化系统性能至关重要。在内存管理方面,学会了如何高效地管理内存,避免内存泄漏和碎片问题。这些知识不仅丰富了我的技术储备,也为今后从事系统开发和优化工作打下了坚实的基础。阅读过程中,我深刻体会到了 Linux 内核开发者严谨的思维方式。内核的设计和实现需要考虑各种复杂的情况,确保系统的稳定性和高效性。这让我在面对自己的编程任务时,也开始更加注重代码的健壮性和可扩展性。学会了从系统的角度去思考问题,而不仅仅局限于功能的实现,这种思维方式的转变将对我今后的技术工作产生深远的影响。         《奔跑吧 Linux 内核 1:基础架构》是一本极具价值的技术书籍。通过阅读本书,我在 Linux 内核的知识领域取得了显著的进步,不仅掌握了丰富的技术知识,还实现了思维方式的转变,深刻感悟到了开源精神。同时,本书的知识对我的实际工作和学习有着重要的启发和指导作用。未来,我将继续深入学习 Linux 内核的相关知识,阅读后续的系列书籍,进一步探索内核的奥秘。同时,希望能够将所学的知识应用到实际项目中,为开源社区贡献自己的力量。我相信,在 Linux 内核的学习道路上不断前行,将会为我的技术生涯带来更多的机遇和挑战,让我在技术领域不断成长和进步。      

  • 发表了主题帖: 《Linux内核深度解析》-05-Linux内核互斥技术说明

    在 Linux 内核的复杂环境中,多进程并发执行是常态 。当多个进程同时访问共享资源时,就如同多个线程同时对一个共有的变量进行加 1 操作,由于并行运行,可能导致本该被加两次的变量只被加了一次。这就是所谓的竞态条件,会造成数据的不一致和错误 。 为了避免这种情况,就需要引入互斥技术,保证在同一时刻,只有一个进程能够访问共享资源,从而确保数据的完整性和一致性。互斥技术就像是一把锁,当一个进程获取到这把锁时,其他进程必须等待,直到锁被释放,才能有机会访问共享资源。 (一)并发与竞态        在 Linux 内核的运行环境中,并发是指多个执行单元(如进程、线程或中断处理程序)在同一时间段内同时执行 。想象一下,在一个繁忙的火车站,多个乘客同时在售票窗口买票,这就类似于并发的场景。而竞态则是当这些并发执行的单元同时访问和修改共享资源时,由于它们的执行顺序不确定,导致最终结果出现不可预测的情况。例如,多个进程同时对一个共享的计数器进行加 1 操作,如果没有适当的同步机制,可能会出现计数器的值增加的次数少于预期的情况。 (二)临界区       临界区是指访问共享资源的代码段,这段代码在同一时间只能被一个执行单元执行,以避免竞态条件的发生。继续以上述火车站为例,售票窗口的工作人员处理每个乘客的购票请求时,这个处理过程就相当于临界区,同一时间只能为一位乘客服务,否则就会出现混乱。临界区的存在是为了确保共享资源的一致性,它需要被严格保护,防止多个执行单元同时进入。 (一)中断屏蔽       中断屏蔽的原理是在进入临界区之前,通过特定的指令将 CPU 的中断响应功能暂时关闭 ,使得在临界区代码执行期间,不会被外部中断所打断。在 Linux 内核中,提供了一系列用于中断屏蔽的函数,如local_irq_disable()用于禁止本地中断,local_irq_enable()用于使能本地中断 。以一个简单的示例来说明,如果有一个共享资源是一个全局变量shared_variable,当一个进程要对其进行修改时,为了避免在修改过程中被中断干扰,可以这样使用中断屏蔽:   local_irq_disable(); // 访问和修改共享资源 shared_variable = shared_variable + 1; local_irq_enable(); 中断屏蔽的优点在于它能够非常有效地保证在临界区内的代码执行不会被中断,从而避免了由于中断导致的竞态条件。由于中断与进程调度紧密相关,屏蔽中断也限制了系统进程的并发,进一步降低了竞态发生的可能性 。然而,它的缺点也很明显。Linux 内核中许多重要的操作,如异步 I/O 等都依赖于中断,如果长时间屏蔽中断,会导致这些操作无法正常进行,可能会对整个系统的性能和稳定性造成严重影响。中断屏蔽只能解决单 CPU 内部的竞态问题,对于多 CPU 系统(SMP),由于其他 CPU 的中断仍然可能发生,所以无法解决 SMP 多 CPU 引发的竞态 。因此,中断屏蔽通常适用于临界区代码非常简短的场景,并且在单 CPU 环境下使用更为合适。   (二)原子操作       原子操作指的是在执行过程中不会被中断的操作,要么全部执行成功,要么全部不执行,具有不可分割性 。在单处理器系统中,由于不存在多个处理器同时访问共享资源的情况,原子操作相对容易实现。一些简单的指令,如对单个变量的赋值操作,本身就是原子的,因为在单处理器上,指令是顺序执行的,不会被其他处理器干扰 。而在多处理器系统中,情况就变得复杂起来。不同的处理器体系结构采用了不同的方法来实现原子操作。在 x86 架构中,通过在指令前加上 “LOCK” 前缀来实现原子的读 - 修改 - 写操作。当一个处理器执行带有 “LOCK” 前缀的指令时,它会锁住总线,使得其他处理器在该指令执行期间无法访问内存,从而保证了操作的原子性 。在 ARM 架构中,使用 “load exclusive” 和 “store exclusive” 指令对来实现原子操作。“load exclusive” 指令会加载内存值,并标记该内存位置为独占访问,“store exclusive” 指令会尝试存储值,并检查该内存位置是否仍处于独占状态,如果是则存储成功,否则失败。这种机制确保了在多处理器环境下,对共享资源的操作能够以原子的方式进行。 (三)自旋锁       自旋锁是一种用于实现多处理器环境下互斥访问的机制 。其工作原理是,当一个进程试图获取自旋锁时,如果锁当前处于可用状态(即未被其他进程持有),则该进程立即获得锁并继续执行;如果锁已被其他进程持有,那么该进程会在原地不断循环检查锁的状态,直到锁被释放 。自旋锁的结构体在 Linux 内核中定义为spinlock_t,相关的操作函数有spin_lock()用于获取自旋锁,spin_unlock()用于释放自旋锁 。例如:   spinlock_t lock; spin_lock_init(&lock); // 尝试获取自旋锁 spin_lock(&lock); // 访问临界区 // 释放自旋锁 spin_unlock(&lock); 自旋锁适用于临界区执行时间较短的场景,因为在等待锁的过程中,进程不会睡眠,而是一直占用 CPU 资源进行自旋,所以如果临界区执行时间过长,会浪费大量的 CPU 资源 。当一个处理器长时间持有自旋锁,而其他多个处理器在不断自旋等待时,会导致系统的整体性能下降。 (四)信号量       信号量是一个整型变量,它通过一个计数器来控制对共享资源的访问 。信号量分为二值信号量和计数信号量,二值信号量相当于一把锁,只有 0 和 1 两种状态,用于实现互斥访问;计数信号量则可以有多个值,用于控制对多个相同资源的访问 。当一个进程试图获取信号量时,如果信号量的值大于 0,则将其值减 1 并成功获取;如果信号量的值为 0,则该进程会被阻塞,放入等待队列中,直到有其他进程释放信号量 。在 Linux 内核中,信号量的结构体为struct semaphore,相关操作函数有down()用于获取信号量,up()用于释放信号量 。比如:   struct semaphore sem; sema_init(&sem, 1); // 获取信号量 down(&sem); // 访问临界区 // 释放信号量 up(&sem);       信号量与自旋锁的主要区别在于,当无法获取信号量时,进程会被阻塞并进入睡眠状态,让出 CPU 资源;而自旋锁在无法获取锁时,进程会自旋等待,一直占用 CPU 。因此,信号量适用于临界区执行时间较长的场景,这样可以避免自旋等待浪费 CPU 资源。 互斥锁是一种用于实现进程或线程间互斥访问的同步机制,它的核心作用是确保在同一时刻,只有一个进程或线程能够进入临界区 。与信号量相比,互斥锁更为纯粹地实现了互斥功能,信号量可以通过设置初始值来控制多个资源的访问,而互斥锁就像一把严格的 “独占锁”,只允许一个进程进入临界区 。例如,在一个多线程的数据库访问程序中,多个线程可能同时请求对数据库进行写操作,如果不加以控制,就会导致数据的混乱。使用互斥锁,当一个线程获取到锁后,其他线程必须等待,直到该线程完成数据库写操作并释放锁,这样就能保证数据库数据的一致性。       在 Linux 内核中,互斥锁的主要操作函数包括用于获取互斥锁的mutex_lock()和用于释放互斥锁的mutex_unlock() 。mutex_lock()函数用于获取互斥锁,如果锁当前未被持有,调用该函数的进程将立即获得锁并继续执行;如果锁已被其他进程持有,那么调用该函数的进程会进入睡眠状态,直到锁被释放 。mutex_unlock()函数则用于释放互斥锁,当一个进程完成对临界区的访问后,必须调用该函数来释放锁,以便其他进程有机会获取锁进入临界区 。在使用互斥锁的操作函数时,需要注意以下几点:一定要确保在获取锁后,在合适的时机释放锁,否则会导致死锁。获取锁的时间应尽可能短,以提高系统的并发性能。例如,在对一个共享的链表进行操作时,在获取互斥锁后,应尽快完成链表的插入、删除等操作,然后释放锁,避免其他进程长时间等待。          Linux 内核的互斥技术是确保系统在多进程并发环境下稳定运行的关键 。从基本的中断屏蔽到复杂的自旋锁、信号量以及互斥锁,每一种技术都有其独特的应用场景和优缺点 。中断屏蔽简单直接,但长时间使用会影响系统性能;原子操作为基本数据操作提供了原子性保障;自旋锁适用于短时间临界区,而信号量和互斥锁则在更广泛的场景中发挥作用 。 随着技术的不断发展,Linux 内核互斥技术也在持续演进 。未来,我们可以期待更高效、更智能的互斥机制出现,以满足不断增长的系统性能需求 。希望本文能为你深入理解 Linux 内核互斥技术提供有益的帮助,也鼓励你在实际的编程实践中不断探索和应用这些技术,提升自己的技术能力 。          

  • 2025-01-13
  • 发表了主题帖: 《Linux内核深度解析》-04-中断、异常、系统调用

    说实话,自己对于LInux的内核的运行机制了解的还是太少,对于这个中断。异常、系统调用的还是了解的不清楚,即使读了书中的介绍说明,但是理解还是不是很多, 这里借用一下网图来和大家说一下自己的见解吧,大家不要喷我,仅代表个人意见。 中断        在 Linux 系统的复杂架构中,中断、异常和系统调用犹如精密齿轮组中的关键部件,各自发挥着独特且不可或缺的作用,共同确保系统的高效稳定运行。 中断作为连接硬件与操作系统的桥梁,使得硬件设备能够及时 “告知” CPU 需要处理的事件 ,像键盘的敲击、硬盘数据传输完成等,都能通过中断迅速传递给 CPU,让系统迅速响应。        异常则是系统在执行指令过程中遇到特殊情况时的 “预警信号”,比如除零操作、内存访问越界等错误,它促使系统进行相应的错误处理,避免程序崩溃。        系统调用为用户空间的程序提供了访问内核功能的 “合法通道”,程序借此实现文件读写、进程创建等关键操作,同时也保障了内核的安全性和稳定性,防止用户程序随意访问内核资源。 深入理解这三者的原理与机制,对于 Linux 系统的开发、调试与优化至关重要。接下来,让我们逐步揭开它们的神秘面纱。        中断,简单来说,是指计算机在执行程序过程中,当遇到急需处理的事件时,暂停当前正在运行的程序,转去执行有关服务程序,处理完后自动返回原程序的过程。其概念的提出,源于计算机系统需要高效处理硬件设备与 CPU 之间的异步事件 。在早期计算机中,CPU 与外部设备的交互效率低下,CPU 需要不断查询设备状态,浪费大量时间。中断机制的出现,使得硬件设备能主动 “通知” CPU,大大提高了系统效率。        在 Linux 系统中,中断主要分为硬件中断和软件中断。硬件中断由硬件设备产生,比如键盘按键、硬盘读写完成等操作都会触发硬件中断 。以键盘为例,当用户按下一个按键时,键盘控制器会向 CPU 发送一个中断请求信号,通知 CPU 有按键事件需要处理。硬件中断具有异步性,它的发生不受 CPU 控制,随时可能出现。        软件中断则是通过软件指令触发的中断,常用于系统调用和异常处理。例如,在用户程序需要请求内核服务时,会通过执行特定的软中断指令来实现。软中断通常与特定的功能号相关联,CPU 根据功能号来调用相应的内核函数。       中断处理流程严谨而有序。当硬件设备有事件发生时,会首先向中断控制器发送中断请求信号 。中断控制器负责收集和管理这些请求,并根据优先级决定将哪个请求发送给 CPU。 CPU 接收到中断请求后,会暂停当前正在执行的程序,将程序的上下文(如寄存器的值、程序计数器等)保存到栈中,以便后续恢复。接着,CPU 根据中断向量表找到对应的中断处理程序入口地址。中断向量表是一个存储中断类型与处理程序对应关系的表格,通过它,CPU 能快速定位到处理特定中断的程序。随后,CPU 跳转到中断处理程序开始执行。在处理过程中,会根据中断的具体类型进行相应操作,比如读取硬件设备的数据、更新设备状态等。处理完成后,CPU 会从栈中恢复之前保存的程序上下文,继续执行被中断的程序。 中断在 Linux 系统中扮演着举足轻重的角色。它使得系统能够实时响应硬件设备的各种事件,保障了硬件与系统的高效协同工作 。例如,在网络通信中,网卡收到数据包时会触发中断,Linux 系统能够及时响应并处理数据包,确保网络通信的顺畅。        同时,中断也是实现多任务并发处理的关键。通过中断机制,CPU 可以在不同任务之间快速切换,看似同时处理多个任务,极大提高了系统的资源利用率和整体性能 。没有中断机制,Linux 系统将难以实现高效的硬件管理和多任务处理,无法满足现代计算机系统的复杂需求。   异常      异常,是指在程序执行过程中,由于出现了一些特殊情况或错误,导致程序的正常执行流程被打断的事件。它就像是程序运行过程中的 “小插曲”,但如果处理不当,可能会引发严重问题。在 Linux 系统中,常见的异常类型多种多样。例如,除零异常,当程序尝试进行除以零的运算时,就会触发该异常 ,因为数学规则中,除数不能为零,这是一种明显的错误操作。再如,内存访问越界异常,当程序试图访问超出其分配内存范围的地址时,便会产生此异常。这就好比你在自己房间里活动是正常的,但如果未经允许闯入别人的房间,就会引发问题。还有非法指令异常,当 CPU 遇到无法识别或执行的指令时,会触发该异常,这通常意味着程序代码存在错误。 Linux 系统具备一套完善的异常处理机制。当异常发生时,CPU 会首先检测到这一特殊情况 。它会暂停当前正在执行的指令,将程序的相关上下文信息,如寄存器的值、程序计数器等,保存到特定的内存区域,以便后续恢复程序执行。       接着,系统会根据异常的类型,查找对应的异常处理程序。与中断向量表类似,Linux 系统中有一张异常向量表,存储着各种异常类型与处理程序的对应关系。通过这张表,系统能够迅速定位到合适的处理程序。异常处理程序会对异常进行相应的处理。对于一些可恢复的异常,如缺页异常(当程序访问的页面不在内存中时引发),处理程序会尝试从磁盘中读取所需页面到内存,然后让程序继续执行。而对于一些不可恢复的严重异常,如除零异常,处理程序可能会终止相关程序的执行,并向用户返回错误信息,防止错误进一步扩大影响系统稳定性。 异常处理机制对系统稳定性起着至关重要的作用。它能够及时捕获并处理程序中的错误,避免因一个小错误导致整个系统崩溃 ,确保系统在面对各种异常情况时,仍能保持相对稳定的运行状态。       异常与中断在多个方面存在明显区别。从产生源来看,中断主要由硬件设备产生,是外部设备向 CPU 发出的请求信号;而异常则是由 CPU 在执行指令过程中,因内部出现特殊情况或错误而产生的 。例如,键盘敲击产生中断,而程序中的除零操作引发异常。      在处理流程上,虽然两者都需要保存程序上下文、跳转至相应处理程序,但中断处理更侧重于对硬件设备事件的响应,处理完后通常会快速返回被中断的程序继续执行;而异常处理则更关注对程序错误或特殊情况的处理,可能会根据异常类型进行不同的操作,如恢复程序执行、终止程序等。 尽管存在差异,它们之间也有着紧密联系。异常和中断都属于系统对外部或内部特殊事件的响应机制,都需要暂停当前程序的执行,进行相应处理后再恢复或结束程序 。并且,在某些情况下,异常也可能会引发中断,比如当发生硬件故障导致的异常时,可能会触发相应的中断通知系统进行处理   系统调用 系统调用,简单来说,是用户程序与内核之间进行交互的桥梁。它为用户空间的程序提供了一种安全、受控的方式来访问内核所提供的各种服务 。内核作为操作系统的核心,掌控着硬件资源的管理与分配等关键任务,但出于系统安全性和稳定性的考虑,用户程序不能随意直接访问内核资源。系统调用就解决了这一问题,它允许用户程序通过特定的接口,向内核发起请求,让内核代为执行一些需要特权级别的操作。 其作用体现在多个方面。在硬件操作抽象方面,它为用户程序屏蔽了底层硬件的复杂性 。比如,当用户程序需要从硬盘读取数据时,无需了解硬盘的具体物理结构和读写原理,只需通过系统调用(如 read 系统调用),内核就能完成从硬盘读取数据的复杂操作,并将数据返回给用户程序。 在资源管理领域,系统调用发挥着关键作用。它使得用户程序能够方便地请求和释放系统资源 。例如,通过 open 系统调用打开文件,内核会为该文件分配相应的资源,并返回一个文件描述符供用户程序后续操作使用;而 close 系统调用则用于释放这些资源。在进程管理中,fork 系统调用可创建新进程,exit 系统调用用于终止当前进程,这些都依赖系统调用对资源进行合理的分配与回收。 从进程间通信的角度看,系统调用提供了支持 。像管道、共享内存等进程间通信机制,都通过系统调用实现。不同进程可以借助这些系统调用,安全有效地交换数据,实现协同工作。 安全性也是系统调用的重要考量。通过系统调用,内核能够严格控制用户程序对系统资源的访问 ,防止用户程序因错误操作或恶意行为而破坏系统的稳定性和安全性。例如,用户程序不能直接访问内核的内存空间,但可以通过系统调用请求内核分配和管理内存,从而保障系统的安全运行。         在 Linux 内核中,系统调用的实现机制涉及多个关键步骤。当用户程序需要执行系统调用时,首先会执行一条特殊的指令,这条指令被称为 “陷入指令”(例如在 x86 架构中,使用 int 0x80 或 syscall 指令) 。这条指令的执行会引发一个异常,使得 CPU 从用户态切换到内核态,这是进入内核执行系统调用服务程序的关键一步。 在切换到内核态后,系统会根据系统调用号来确定具体要执行的内核函数。每个系统调用都被分配了一个唯一的系统调用号,就像每个人都有一个独特的身份证号码一样。系统调用号会被存储在特定的寄存器(如 eax 寄存器)中传递给内核。内核中有一个系统调用表,该表存储了系统调用号与对应的内核函数之间的映射关系 。内核通过查找系统调用表,就能迅速找到与该系统调用号对应的内核函数,从而进入相应的处理流程。       关于参数传递,由于系统调用是从用户态到内核态的特殊函数调用,不能像普通函数调用那样直接通过栈来传递参数。在 Linux 中,系统调用主要通过 CPU 寄存器来传递参数 。一般来说,前几个参数会依次存储在 ebx、ecx、edx、esi、edi 等寄存器中。如果参数较多,超过了寄存器的承载能力,会采用其他方式,比如将参数存储在用户空间的一块内存区域,然后将该内存区域的地址通过寄存器传递给内核。内核在执行完系统调用对应的服务程序后,会将结果返回给用户程序 。这个过程通常是将返回值存储在特定的寄存器(如 eax 寄存器)中,然后 CPU 从内核态切换回用户态,用户程序继续执行后续指令。 总结:     中断、异常和系统调用对 Linux 系统的性能、稳定性和安全性有着深远影响。高效的中断处理机制确保了系统能够快速响应硬件设备的请求,减少了 CPU 的等待时间,提高了系统的整体性能。例如,在高速网络通信场景下,快速的中断响应能够及时处理大量的网络数据包,保障网络通信的流畅性。异常处理机制则是系统稳定性的重要保障。它能够及时捕获并处理程序中的错误,防止错误扩散导致系统崩溃。在大型服务器应用中,稳定的异常处理机制可以确保长时间运行的服务不会因程序内部的小错误而中断,提高了系统的可用性 。系统调用的安全受控特性,保障了内核资源不被用户程序随意访问,维护了系统的安全性。在多用户、多任务的 Linux 环境中,系统调用的严格控制能够防止恶意程序对系统资源的非法获取和破坏,保护了系统和用户数据的安全 。       在异常处理方面,随着软件复杂度的增加,需要更智能、更全面的异常检测和处理机制。例如,通过人工智能技术辅助异常诊断和修复,提高系统应对复杂异常情况的能力 。 对于系统调用,随着云计算、大数据等新兴技术的发展,对系统调用的功能和性能也会有新的需求。未来可能会出现更多针对特定应用场景的系统调用,以满足不同领域对系统资源管理和利用的需求。同时,在保障安全性的前提下,进一步优化系统调用的性能,减少用户态与内核态切换的开销,也是重要的发展方向 。总之,中断、异常和系统调用将持续在 Linux 系统的发展中扮演核心角色,不断演进以适应技术的进步和应用的需求。                      

  • 2025-01-08
  • 回复了主题帖: 【测评入围名单(第一批)】年终回炉:FPGA、AI、高性能MCU、书籍等65个测品邀你来~

    个人信息无误,确认可以完成测评计划

  • 2025-01-05
  • 回复了主题帖: 【 AI挑战营(进阶)】2.踩坑记录1 模型验证

    iexplore123 发表于 2025-1-5 14:46 嗷,我忘了贴仓库地址了Danbinabo/insighrface,这个仓库适合学习整个人脸项目的流程,不过不太适合这次 ... 感谢大佬!

  • 回复了主题帖: 【 AI挑战营(进阶)】2.踩坑记录1 模型验证

    牛逼牛逼,出个更详细的教程让我复刻一下嘛

  • 2025-01-04
  • 发表了主题帖: 【嵌入式AI挑战营 】-05-摄像头测试

    今天先给大家测试一下摄像头的东西,前段时间一直不知道如何入门,我觉得首先应该动起来,还没动就说放弃了那才是真的死的太憋屈了,按照网上的流程,我们首先将摄像头,网线,调试线接好,由于自己是使用的自己做的底板,所以相对来说连接比较方便一点, 首先下载镜像,完成系统的更新,我这里直接使用的是出厂自带的镜像, 然后中端查看IP,通常情况下只需要在同一网段基本没有问题。 查看到了开发板的IP,需要记住当前的IP, 然后下载VLC Media Player,网上都有破解版的,开源的,大家不要被广告骗了, 然后我们在VLC做设置   说明一下,这里的IP是你的开发板的IP,需要根据实际情况做修改。 点击播放就可以看到摄像头里面的东西 了   好了,今天的测试先到这里了,后面有了新的测试再做分享。      

  • 发表了主题帖: 【嵌入式AI挑战营 】-04-RKNN推理基础知识介绍说明

    前段时间一直没有推进,主要是和自己的知识面有关系,因为自己对于AI这一块的东西了解的太少了,然后后面发现对于RK1106的wiki页面有不少的知识,这里我就搬过来和大家一起来分享一下,也算是对于自己学习的一个交代。 具体的教程连接如下:   LuckFox 教程连接: 连接 1. RKNPU 简介​ NPU(Nerual Processing Unit)是一种专门用于加速神经网络计算的处理器。为了满足人工智能的需求,瑞芯微逐渐将NPU集成到其处理器中,这种内置于瑞芯微处理器的NPU被称为RKNPU。LuckFox Pico 系列开发板了搭载瑞芯微 RV1103/RV1106 芯片,内置瑞芯微自研第4代NPU。该NPU具有高运算精度,支持int4、int8、int16混合量化。其中,int8算力为0.5TOPs,int4算力可达1.0TOPs。RKNPU4.0被划分为RKNPU2,因此要使用RKNPU2的SDK和工具套件。 2. rknn-Toolkit2 简介​ RKNN-Toolkit2 工具在 PC 平台上提供 C 或 Python 接口,简化模型的部署和运行。用户可以通过该工具轻松完成以下功能:模型转换、量化、推理、性能和内存评估、量化精度分析以及模型加密。RKNN 软件栈可以帮助用户快速的将 AI 模型部署到 Rockchip 芯片。整体的框架如下: 为了使用 RKNPU,用户需要首先在计算机上运行 RKNN-Toolkit2 工具,将训练好的模型转换为 RKNN 格式模型,之后使用 RKNN C API 或 Python API 在开发板上进行部署。本节介绍用户如何快速在Luckfox Pico系列板子上使用 RKNN Toolkit2 和 RKNPU2 工具转换 yolo5s.onnx 模型为 yolov5s.rknn 模型并进行板端推理。 3 rknn-Toolkit2 安装(PC ubuntu22.04) sudo apt-get update sudo apt-get install python3 python3-dev python3-pip sudo apt-get install libxslt1-dev zlib1g zlib1g-dev libglib2.0-0 libsm6 libgl1-mesa-glx libprotobuf-dev gcc 4 安装RKNN-ToolKit2依赖包 pip3 install -r rknn-toolkit2/packages/requirements_cpxx-1.6.0.txt # such as: pip3 install -r rknn-toolkit2/packages/requirements_cp310-1.6.0.txt 5.安装RKNN-ToolKit2 pip3 install rknn-toolkit2/packages/rknn_toolkit2-x.x.x+xxxxxxxx-cpxx-cpxx-linux_x86_64.whl # such as: pip3 install rknn-toolkit2/packages/rknn_toolkit2-1.6.0+81f21f4d-cp310-cp310-linux_x86_64.whl 包名格式为:rknn_toolkit2-{版本号}+{commit 号}-cp{Python 版本}-cp{Python 版本}-linux_x86_64.whl,根据不同的Python版本,选择安装对应的安装包:     具体的详细的安装步骤,可以直接按照原厂的连接去搭建,这里就只做介绍,毕竟也是原厂搬过来的。      

  • 回复了主题帖: 【嵌入式AI挑战营 】-03-RV1106自制底板的使用

    freebsder 发表于 2024-12-24 15:02 谢谢分享,评测活动自己打板的还是不太多呢。 就是自己弄个底板,调试的时候懒得接线,方便一些。

  • 2025-01-03
  • 发表了主题帖: 《Linux内核深度解析》-03-内存管理

          在LInux系统中,内存管理是一个非常重要的环节,烧友不慎就还导致系统崩溃,所以内存管理是系统稳定运行的基础,也是非常重要的一环。在 Linux 系统的庞大架构里,内存管理无疑是一块关键基石。它肩负着保障系统稳定运行、实现资源高效利用以及提升应用程序性能等多重重任,犹如一位幕后英雄,默默支撑着整个系统的运转。当我们同时开启多个应用程序,比如一边听音乐、一边浏览网页,还后台运行着文件下载任务,此时内存管理就要像一位精明的管家,合理分配内存资源,让每个程序都能顺畅运行,互不干扰。这背后靠的就是内存管理对进程内存空间的精细划分与调度,确保每个进程都有专属的 “内存领地”,避免数据混乱与冲突。再者,对于资源有限的嵌入式设备,如智能手环、智能家居控制器等,高效的内存管理更是决定设备性能优劣的关键。通过优化内存使用,系统能够快速响应操作指令,避免卡顿,为用户带来流畅体验。毫不夸张地说,深入探究 Linux 内核内存管理机制,是解锁系统潜能、优化应用性能的必经之路   1 物理地址         在 Linux 内核的底层世界里,物理内存有着一套严谨且精妙的组织架构。内核通常以页框(Page Frame)作为管理物理内存的基本单位,这就好比将一片广阔的土地划分成规整的小块,每一块都能独立管理与分配。一个页框通常对应着固定大小的内存空间,常见的页框大小为 4KB,当然,在不同架构和配置下,也可能出现 8KB、16KB 甚至更大的页框规格。 为了进一步适配不同硬件特性与系统需求,内核又将物理内存划分成多个区域,也就是常说的区(Zone)。其中,ZONE_DMA 区专门用于满足那些需要直接内存访问(DMA)的设备,这类设备往往对内存访问有特殊要求,只能在特定低地址、物理连续的内存范围内操作,一般涵盖内存起始的 16MB 空间。ZONE_NORMAL 区则是内核与普通进程频繁交互的 “主战场”,涵盖 16MB 到 896MB 的内存段,这片区域的内存能够被内核直接线性映射,访问起来高效便捷。而对于 32 位系统中物理内存超过 896MB 的部分,会被纳入 ZONE_HIGHMEM 区,由于这部分内存无法直接被内核线性映射,需要借助一些特殊的映射机制来访问,像是动态映射技术,不过这也使得访问速度相对较慢。再往宏观层面看,在 NUMA(非一致内存访问)架构系统里,内存还会依据节点(Node)来划分。每个节点关联着一定数量的 CPU 核心以及对应的本地内存,这种架构设计充分考虑到了多处理器系统中,不同 CPU 访问不同内存区域的速度差异,旨在优化整体内存访问性能,让数据获取与处理更加高效。        当系统需要分配物理内存时,针对不同规模的内存需求,内核有着不同的应对策略。对于大块内存的申请,伙伴系统(Buddy System)就会挺身而出。伙伴系统的核心原理犹如一场精妙的拼图游戏,它按照 2 的幂次大小对内存块进行分块管理,从最小的 1 个页框(4KB)开始,逐步扩展到 2 个、4 个、8 个…… 直至 2 的若干次幂个页框的连续内存块。这些大小各异的内存块如同不同规格的拼图碎片,被有序组织在各个链表之中。当有内存分配请求到来,系统就会在相应链表中寻找合适大小的 “拼图碎片”。要是找不到恰好匹配的,它还会尝试将更大的内存块进行拆分,直到满足需求;而当内存被释放回系统时,伙伴系统又会施展 “合并魔法”,将相邻且大小相同的空闲内存块重新组合,回归到更大规格的内存块链表中,时刻保持内存的规整与高效利用,避免碎片化问题的加剧。 与之相对,对于那些频繁出现的小内存分配需求,slub 分配器则扮演着关键角色。它像是一位精明的 “内存管家”,在系统初始化阶段,就预先从伙伴系统中获取一批内存页框,然后将这些页框精细划分成一个个小的内存对象,再把这些对象串联成缓存链表。当进程或内核模块需要小内存时,无需再大费周章地向伙伴系统申请,直接从 slub 分配器的缓存链表中快速获取即可,用完之后归还到链表,以供后续复用。这一过程极大地减少了小内存分配时的碎片化风险,同时借助缓存机制,显著提升了内存分配与回收的速度,确保系统在面对海量小内存需求时,依然能够保持流畅运行。   虚拟地址空间   一)用户态虚拟地址空间构成 在 Linux 系统的进程运行天地里,用户态虚拟地址空间犹如一片专属 “自留地”,为进程的数据与代码存储提供了多样的 “分区”。这片空间起始于地址 0,大小依据处理器架构而定,在 32 位系统中,通常是 3GB 的范围。 代码段(Text Segment),作为这片空间的 “智慧中枢”,承载着程序执行的关键 ——CPU 执行的机器指令。它如同一位严谨的指挥官,只读不写,确保指令的稳定性与安全性,防止程序运行时误操作对代码的篡改,让程序沿着既定的逻辑轨道顺畅前行。 紧挨着代码段的是数据段(Data Segment),这里存放着程序中已初始化且初值不为 0 的全局变量和静态变量,犹如一个装满宝藏的仓库,为程序运行提供各类初始数据,随时待命供程序取用。与之相邻的 BSS 段(Block Started by Symbol),则专门收纳那些未初始化以及初始值为 0 的全局变量和静态变量。在程序加载之际,操作系统会贴心地将这片区域初始化为零或空值,既节省了目标文件的存储空间,又确保变量有初始的 “栖息之所”。 再往高地址方向探索,便是充满活力的堆(Heap)区域,它像是一片可拓展的 “建筑工地”,用于存放进程运行时动态分配的内存。当程序调用 malloc ()、new () 等函数时,如同建筑工人在此添砖加瓦,新分配的内存单元不断在堆上 “拔地而起”;而 free ()、delete () 函数的调用,则如同拆除废弃建筑,释放的内存回归堆中,等待下次被重新利用。堆向高地址扩展,不过由于其内存分配与回收的随机性,就像建筑工地的布局常变,容易产生内存碎片问题。 与堆相反,栈(Stack)区域则是从高地址向低地址 “生长”,它如同一个高效的 “数据管家”,负责存储函数内部声明的非静态局部变量、函数参数以及函数返回地址等关键信息。函数调用时,栈帧如同一个个收纳盒,层层堆叠,保存着当前函数的上下文;函数返回时,栈帧又依次弹出,恢复之前的状态。栈内存由编译器自动分配释放,其操作迅速高效,确保函数调用的流畅进行,不过一旦使用不当,比如递归过深或局部变量占用空间过大,就可能引发栈溢出的风险,如同管家的收纳盒堆满溢出现象。 最后,还有内存映射区(mmap),这是一片神奇的 “连接地带”,通过 mmap () 系统调用,它能将磁盘文件的内容直接映射到内存之中,让程序可以像操作内存一样便捷地读写文件内容,极大提升文件操作效率;同时,动态链接库在加载时也会入驻这片区域,为程序运行提供丰富的功能拓展,如同为程序开启一扇通往外部资源的大门。 (二)内核态虚拟地址空间剖析 相较于用户态虚拟地址空间,内核态虚拟地址空间更像是一片 “管控中枢”,掌控着系统的核心资源与关键操作。在 32 位系统中,它占据着虚拟地址空间的 3GB 到 4GB 这 1GB 范围,虽然看似不大,却有着极高的 “权限” 与精妙的布局。 其中,直接映射区是内核态空间的一大 “基石”,占据了前 896MB 的区域。它如同一条坚实的 “纽带”,将内核空间与物理内存中的 ZONE_DMA 和 ZONE_NORMAL 区域紧密相连,建立起一种直接、固定的映射关系。这种映射方式简单而高效,内核只需通过简单的数学运算,将虚拟地址减去特定偏移量,就能精准定位到对应的物理地址,如同在地图上凭借坐标快速找到目的地,使得内核可以迅速访问关键的物理内存资源,满足日常运行需求。 而对于高端内存的管理,内核态引入了动态映射区。由于内核需要掌控所有物理内存,但受限于 1GB 的虚拟地址空间,当物理内存超过 896MB 时,直接映射已无法满足全部需求。此时,动态映射区就像一位 “灵活的调度员” 挺身而出,它位于内核态虚拟地址空间的高端部分,通过一系列复杂而巧妙的页表操作,能够按需将虚拟地址映射到物理内存中的高端内存区域(ZONE_HIGHMEM)。无论是临时的数据缓存,还是内核模块加载到高端内存,动态映射区都能灵活调配,确保内核在面对多样的内存需求时,始终游刃有余,维持系统的稳定高效运行。 内存映射:虚拟与物理的 “桥梁”    (一)分页机制:精准映射的实现 在 Linux 内核的内存管理 “蓝图” 里,分页机制宛如一位神奇的 “空间架构师”,将虚拟内存和物理内存这两大 “空间领域”,精心划分成一个个固定大小的 “单元积木”—— 也就是页。常见的页大小为 4KB,这一规格如同标准的积木尺寸,在不同硬件架构与系统配置下,虽可能有所调整,如变为 8KB、16KB 等,但都遵循着统一、规整的划分原则。 如此一来,虚拟内存中的每一页,都能依据一套精密的 “映射蓝图”—— 页表,精准找到与之对应的物理内存页。页表,就像是一座连接虚拟与物理世界的 “桥梁”,它的每一项记录(页表项)都承载着关键信息,如同桥梁上的 “指示牌”,清晰指引着虚拟页通往物理页的路径。以 32 位系统为例,虚拟地址通常被划分为两部分:高 20 位作为虚拟页面号(VPN),宛如街区编号,用于在页表中精准定位到对应的页表项;低 12 位则是页内偏移量,如同房间号,能在找到的物理页帧内迅速定位到具体的数据存储位置。 这种分页模式带来的优势显著非凡。一方面,它极大地削减了内存管理的复杂度,内核无需再对每一个字节的内存 “斤斤计较”,只需聚焦于这些固定大小的页,如同城市规划师只需着眼于一个个规整的街区,管理负担大幅减轻。另一方面,内存的分配与回收效率得到飞跃提升。当进程申请内存时,内核能迅速从空闲页链表中挑选出若干连续或离散的页进行分配;进程结束后,回收这些页也如同清理闲置街区般轻松,有效规避了内存碎片的滋生,确保内存空间始终井然有序。 更为关键的是,分页机制为内存的保护与共享筑牢了坚实根基。每个进程配备的独立页表,恰似一把把专属 “钥匙”,赋予进程访问自身内存页的权限,严禁越界访问,有力保障了系统的安全性,防止进程间的内存数据 “相互串门”、引发混乱。同时,借助巧妙的页表设置,多个进程能够共享同一段物理内存,就像不同的租客可以共享公寓的公共区域,实现了内存资源的高效复用,极大提升了系统的整体性能,让有限的物理内存发挥出更大的价值。 (二)MMU 与 TLB:加速访问的 “利器” 在内存管理的 “高速通道” 上,内存管理单元(MMU)无疑是一位核心 “调度员”,肩负着将虚拟地址迅速转换为物理地址的重任,保障 CPU 能精准、高效地访问内存数据。当 CPU 发出内存访问指令,携带的虚拟地址信息就如同一份 “快递收件地址”,MMU 会依据进程专属的页表,快速解读这份地址,将其转换为对应的物理地址,如同快递员依据地址簿找到收件人的实际住址,整个过程精准且迅速,确保数据能及时送达。 为了进一步加快地址转换的速度,提升系统运行效率,MMU 内部还配备了一个强大的 “缓存助手”—— 转换后备缓冲器(TLB),也就是常说的快表。TLB 宛如一个 “高速快递分拣站”,专门缓存近期频繁使用的页表项,这些页表项如同提前分拣好、等待派送的快递包裹。当 CPU 发起内存访问时,MMU 首先会在 TLB 中 “查找包裹”,若幸运命中,就能直接获取物理地址,省去了访问内存中庞大页表的时间,如同快递员直接从分拣站取走包裹派送,效率极高;若不幸 TLB 未命中,才会 “前往” 内存中的页表进行完整的地址转换查找,不过此时找到的页表项也会被 “顺手” 存入 TLB,为后续的访问加速。 在实际运行场景中,TLB 的命中率对于系统性能起着关键作用。由于程序运行通常具有良好的局部性原理,即在一段时间内,CPU 往往频繁访问相邻或相关的内存区域,这使得 TLB 有很大几率缓存到这些热点区域的页表项,命中率常常能达到 90% 以上。像日常办公软件,在文档编辑过程中,CPU 频繁读写文档内容、样式数据所在的内存页,这些页表项大概率会驻留在 TLB 中,使得操作响应迅速;又如图形渲染场景,渲染数据、纹理内存页在一段时间内被密集访问,TLB 的高效缓存也能显著减少地址转换延迟,让渲染流程更加流畅,为用户带来顺滑的使用体验。 内存分配与回收   (一)伙伴系统算法:大块内存的高效分配 伙伴系统算法作为 Linux 内核管理大块内存的得力 “干将”,其核心原理基于一种巧妙的分组策略。它把物理内存中的空闲页框,按照 2 的幂次大小进行分组,如同将积木按照不同规格整齐归类。从最小的 1 个页框(4KB)开始,逐步形成包含 2 个、4 个、8 个…… 直至 2 的若干次幂个连续页框的组,每组构成一个链表,这些链表就像是不同规格积木的收纳盒,有序存放着相应大小的内存块资源。 当系统收到内存分配请求时,比如某个进程需要 128KB(即 32 个 4KB 页框)的连续内存空间,伙伴系统首先会在对应 128KB 大小的链表中寻找空闲块。若该链表中有可用资源,直接取出交付给进程使用;要是此链表为空,它便会向更大一级的链表(如 256KB 链表)“求助”。一旦找到合适的大内存块,就将其一分为二,一半分配给进程,另一半则插入到 128KB 链表中备用,确保后续同规格内存需求能快速满足。 而在内存回收阶段,伙伴系统的 “合并魔法” 就开始施展。当进程释放一块内存时,系统会检查其 “伙伴” 内存块状态,这里的 “伙伴” 需满足三个严苛条件:一是大小相同;二是物理地址连续;三是起始地址符合特定对齐规则,即起始物理地址是对应内存块大小的整数倍。只有同时满足这些条件,两块内存才能合并成更大的内存块,回归到上一级链表之中,就像两个相同规格的积木重新拼接成更大组件,放回对应的收纳盒,时刻保持内存的规整性,有效避免外部碎片的产生。 举个例子,假设系统初始有一块 1MB(256 个 4KB 页框)的连续空闲内存,以 2 的幂次划分为多个子块并链接管理。当依次有进程 A、B、C 分别申请 64KB、128KB、32KB 内存时,系统会精准分配,并按需拆分更大内存块,剩余部分妥善归位到相应链表。待进程结束释放内存,伙伴系统又会依据 “伙伴” 规则迅速合并可用内存,让内存布局始终处于高效有序状态。不过,这种以 2 的幂次分配内存的方式,偶尔也会产生一些内部碎片。例如进程申请 70KB 内存,系统只能分配 128KB 内存块,就会有 58KB 空间暂时闲置,造成一定的内存浪费,这也是在享受伙伴系统高效管理带来便利时,需要权衡考量的小 “代价”。 (二)Slab 分配器:小对象的贴心管家 在 Linux 内核的日常运作里,频繁会有对小内存块的分配需求,像进程描述符(task_struct)、文件描述符等这些内核常用的数据结构,往往只需占用几十字节到几百字节的空间。此时,slab 分配器就宛如一位贴心管家登场,专为管理此类小对象而生。 slab 分配器的精妙之处在于,它为不同类型的内核对象量身定制了专属的内存缓存。内核初始化阶段,就依据各类对象的常见使用频率与大小,提前从伙伴系统 “批发” 来大块内存,再将这些大块内存精细切分成一个个与对象大小适配的小块,同时配备相应的管理数据结构,共同构成一个个高速缓存组。这些缓存组如同一个个分类明晰的 “小宝箱”,针对不同内核对象,有着不同规格的 “内存格子”,随时准备为内核分配小内存块。 以常见的内核对象为例,对于文件描述符,slab 分配器会创建专门的高速缓存。这个缓存中,每个 slab 通常由一个或多个连续物理页组成,再进一步将其划分成多个文件描述符大小的对象。内核需要新的文件描述符时,无需大费周章向伙伴系统申请大块内存,直接从对应的文件描述符高速缓存中快速取出一个空闲对象即可,整个过程如同从装满文件描述符 “模板” 的小宝箱里顺手拿出一个,迅速且便捷;用完之后,归还到缓存,等待下次复用,避免频繁的内存申请与释放操作带来的性能开销。 从操作细节看,创建 slab 缓存时,内核通过 kmem_cache_create () 函数,依据对象大小、对齐要求等参数,构建起 kmem_cache 结构体,它如同宝箱的 “总管家”,掌控着缓存内对象的分配、回收等诸多事宜。分配对象时,首选 kmem_cache_cpu 结构体管理的快速通道,这里缓存着当前 CPU 本地的空闲对象,通过 freelist 指针迅速定位获取;若本地无空闲,则向 kmem_cache_node 结构体管理的共享缓存 “求助”;实在不够,才向伙伴系统申请新内存扩充缓存。释放对象时,对象会依据其状态,在 slabs_full、slabs_partial、slabs_empty 这三个链表间灵活转移,确保内存高效利用。    对比 kmalloc () 和 vmalloc () 等内存分配函数,slab 分配器的优势尽显。kmalloc () 虽能分配连续物理内存,但对于频繁小对象分配,易产生内存碎片;vmalloc () 虽可分配大块虚拟内存,但物理内存可能不连续,访问效率相对较低。而 slab 分配器基于对象类型精准管理内存,不仅减少碎片,还利用缓存机制加速分配,成为内核小对象内存管理的不二之选,保障内核在复杂任务处理中始终高效运行。         至此,我们一同深入探究了 Linux 内核内存管理的诸多关键层面,从物理内存的底层架构搭建,到虚拟地址空间的精巧划分;从内存映射的精准桥梁构建,再到内存分配与回收的动态智慧施展,乃至嵌入式驱动开发场景中的实战应用,每一处细节都彰显着 Linux 内核内存管理的精妙与强大。 它不仅是系统稳定运行的根基,更是开发者挖掘系统潜能、优化应用性能的核心工具。深入理解这些机制,无论是对于系统编程高手雕琢大型软件架构,还是嵌入式开发者优化硬件资源利用,都有着不可估量的指引作用,能助力大家在面对复杂系统挑战时,游刃有余地设计、调试与优化。        Linux 内核内存管理领域仍在持续演进,新技术、新优化不断涌现。希望各位读者以此为起点,保持探索热情,深入研读内核源码,紧跟内核开发社区动态,不断实践积累,去解锁更多前沿技术奥秘,为 Linux 系统性能提升、创新应用落地添砖加瓦,在开源世界里留下属于自己的智慧印记。  

  • 2025-01-02
  • 发表了主题帖: 《Linux内核深度解析》-02-关于设备的初始化,进程管理

    首先还是图片镇楼       在嵌入式系统开发领域,Linux 内核无疑是一颗璀璨的明珠,其重要性不言而喻。对于众多开发者而言,深入了解 Linux 内核的引导初始化、进程管理以及内存管理等核心环节,犹如掌握了开启高效、稳定嵌入式系统大门的钥匙。当嵌入式设备上电启动,引导初始化过程率先登场,它精心为系统的运行搭建基础环境,确保内核能够顺利加载并执行。进程管理则如同交通枢纽,精准调度系统中的众多进程,让它们有条不紊地运行,充分利用系统资源,提升整体性能。而内存管理恰似一位智慧的管家,精心统筹着有限的内存资源,合理分配、高效回收,保证系统流畅运行,避免资源浪费。本次测评,我将以一个长期专注于嵌入式开发的从业者视角,深入探究 Linux 内核在这三个关键领域的卓越表现,结合实际开发案例与经验,为大家呈现其精妙之处,希望能给同行们带来一些有益的参考与启发。 我们首先来说一下,关于内核的启动以及引导 Linux 内核引导初始化剖析 (一)引导程序概览 在嵌入式设备加电的瞬间,引导程序就如同一位尽职的领航员,率先开启系统启动之旅。以广泛应用的 U-Boot(Universal Bootloader)为例,它承担着初始化硬件环境的重任,从最基础的 CPU 时钟设置,确保系统运行节奏稳定,到内存初始化,为后续内核运行开辟空间,再到存储设备的准备,让内核镜像能够顺利读取,每一步都至关重要。当硬件环境就绪,U-Boot 精准地将 Linux 内核加载到内存指定位置,为内核的启动搭建好舞台,随后把控制权优雅地移交,让内核开启后续精彩表演。 在开源的嵌入式世界里,还有不少类似的引导程序明星。像 Redboot,它凭借出色的跨平台特性,适配多种硬件架构,为开发者提供了极大的便利,无论是 ARM 还是 x86 架构的设备,都能轻松驾驭;而 Barebox 则以高度可定制化著称,开发者可以根据项目需求,灵活调整引导流程、添加专属功能,宛如为项目量身定制的启动神器。这些引导程序各自闪耀,凭借独特优势,助力嵌入式开发者在不同硬件平台上乘风破浪,开启系统开发之旅。 (二)内核初始化流程 当内核接过引导程序的接力棒,首先映入眼帘的是汇编语言编写的启动代码,这堪称内核启动的基石。它小心翼翼地设置内核运行的初始状态,从关键寄存器的初始化,精准设定系统运行的初始参数,到中断向量表的精心构建,为系统应对各类中断事件筑牢根基,每一个细节都关乎后续的稳定运行。紧接着,内核无缝切换到 C 语言编写的初始化函数,开启一场盛大的系统初始化狂欢。内存管理子系统率先登场,它精心规划内存布局,将物理内存巧妙划分为内核区、用户区等不同区域,宛如城市规划师精心布局城市功能区;同时构建页表,实现虚拟内存与物理内存的高效映射,让进程访问内存如同在地图上精准导航,快速且准确。进程调度子系统不甘示弱,初始化进程调度器,为后续多进程的和谐共处制定规则,确保每个进程都能在合适的时机获得 CPU 资源,公平竞争,高效运行。设备驱动子系统也忙碌起来,逐一识别并初始化各类硬件设备,从常见的网卡、硬盘,到特定的传感器,让内核能够与硬件设备顺畅沟通,协同工作。当这一系列初始化工作圆满完成,内核会启动 init 进程,这是系统迈向用户空间的关键一步,它如同开启一扇通往丰富多彩应用世界的大门,后续的用户进程都将在它的引领下有序诞生与运行,为整个系统注入无限生机与活力。 Linux 内核进程管理探究   (一)进程的诞生与家族树 在 Linux 内核的进程世界里,进程的创建方式多种多样,其中 fork、vfork 和 clone 系统调用堪称三把 “利器”。fork 系统调用如同复印机,它会全面复制父进程的资源,为子进程打造一个全新且独立的运行环境。从代码段、数据段到堆栈,无一遗漏,子进程初始时几乎与父进程 “一模一样”,宛如克隆体,随后二者便可各自独立发展,互不干扰。vfork 系统调用则像是一位 “急性子”,它追求创建的高效性,直接让子进程共享父进程的虚拟地址空间,连页面的复制步骤都省略了,父子进程仿佛共用一个 “空间”,不过这也要求子进程尽快执行 exec 系列函数,开启新的程序之旅,以免影响父进程后续运行。clone 系统调用宛如一位 “定制大师”,它最为灵活,通过不同的参数组合,开发者能精准定制子进程对父进程资源的共享程度,既可以共享文件系统、信号处理表,也能单独设定线程运行的起点,满足各种复杂的进程创建需求。 以一个简单的嵌入式 Web 服务器项目为例,主进程负责监听网络端口,一旦有新的 HTTP 请求到来,它便调用 fork 系统调用创建子进程。子进程继承了主进程的网络相关资源配置,随后专注于处理该请求,读取网页文件、生成响应数据,而主进程则继续监听端口,随时迎接新请求,二者并行不悖,高效协同,确保服务器能够流畅地服务众多客户端。 在内核中,进程之间还存在着类似家族树的层级关系,每个进程都有自己的父进程,除了 0 号进程(init 进程)作为系统所有进程的 “始祖”。当一个进程创建子进程时,便形成了一条父子相连的分支,众多分支交织成庞大复杂的进程家族树,内核通过这棵树状结构巧妙管理进程的生命周期、资源分配以及调度顺序,确保整个系统有条不紊地运行。 此外,Linux 内核还支持内核线程与用户线程两种线程模型。内核线程如同系统的 “幕后英雄”,由内核直接创建和管理,独立运行在内核空间,专注于执行内核函数,如内存同步、延时管理等关键系统任务,为系统的稳定运行默默付出。而用户线程则是在用户空间由线程库创建并调度,它们依托于进程,共享进程的用户地址空间,对于普通应用开发者更为熟悉,开发者可以利用线程库灵活地在应用程序中创建多个用户线程,实现并发执行,提升程序性能,比如在图形渲染程序中,多个用户线程分别负责图像加载、绘制、特效处理等任务,让画面呈现更加流畅迅速。 (二)调度的艺术:确保公平与高效 Linux 内核中的完全公平调度算法(CFS)犹如一位公正的裁判,致力于让每个进程都能在 CPU 资源分配上得到公平对待。它为每个进程引入了虚拟运行时间(vruntime)的概念,巧妙地结合进程权重,实现了精细的资源分配。想象一下,在一个多任务的嵌入式系统中,既有实时性要求较高的传感器数据采集进程,需要频繁地与硬件交互、快速处理数据,又有后台数据同步进程,对时间敏感性稍低,但数据量较大。CFS 算法会根据它们的权重,精准分配 CPU 时间片。对于传感器数据采集进程,赋予较高权重,使其在单位调度周期内获得相对更多的运行时间,确保数据的及时性;而后台数据同步进程权重稍低,在系统较为空闲时充分利用 CPU 资源进行数据传输,避免过度占用关键进程的时间。 在实际运行过程中,进程切换时机的把握至关重要。内核通过时钟中断机制,周期性地检查当前运行进程的时间片使用情况。当进程的时间片耗尽,或者有更高优先级的进程进入可运行状态时,内核便迅速启动进程切换流程。以下是一段简化的进程切换关键代码片段:   // 进程调度函数 void schedule(void) {     struct task_struct *prev, *next;     // 获取当前运行进程     prev = current;     // 根据调度算法选择下一个运行进程     next = pick_next_task();     if (likely(prev!= next)) {         // 上下文切换准备工作         context_switch(prev, next);     } } // 上下文切换函数 void context_switch(struct task_struct *prev, struct task_struct *next) {     // 切换内存管理相关上下文     switch_mm(prev->active_mm, next->active_mm, next);     // 切换寄存器状态,完成进程切换     switch_to(prev, next, prev); } 这段代码清晰地展示了从调度决策到实际上下文切换的关键步骤。首先,schedule 函数确定当前运行进程 prev 和下一个要运行的进程 next,若二者不同,则调用 context_switch 函数。在 context_switch 中,先处理内存管理相关的切换,确保进程能访问正确的内存空间,再通过 switch_to 宏完成寄存器状态的切换,这一过程如同瞬间切换舞台上的演员,新进程无缝衔接,继续执行,保证系统高效运行。不仅如此,内核还会根据进程的行为动态调整优先级。当一个进程长时间处于等待 I/O 完成的阻塞状态,内核会适当提升其优先级,待其重新就绪后,能更快地获得 CPU 时间,补偿其等待的损失;而对于占用 CPU 时间过长的进程,内核则会降低其优先级,避免它 “霸占” 资源,让其他进程也有机会一展身手,如此这般,整个系统的响应速度与资源利用率都得到了显著优化。    

  • 2024-12-31
  • 发表了主题帖: 【RainbowLink USB 协议转换器】-04-RS232测试

    在RS232测试的时候需要做一下说明,由于232 信号的电平比较特殊,所以在这里应该很难测试出极限参数,这里估计应该就是某一个232的极限,不一定是这个转关起的极限值, 硬件端的连接就是两个串口的连接   25600正常 921600 正常通信 1M的情况下,正常通信 在2M的情况下,出现乱码,这个可能是自己的调试器的极限,并不是RainbowLink 的极限,目前可以了解到的是RainbowLink  应该是支持2M的波特率的,而且还能支持更高。      

  • 发表了主题帖: 【RainbowLink USB 协议转换器】-03-RS485接口的极限参数测试

    RainbowLink USB的RS485接口是非常实用的,现场很多都是485和232 接口的,如果232 接口可以直接扩展DB口或者接线接口的话我们就可以非常容易做测试了,下面就开始测试极限参数, 我们还是从常用的最高点开始,115200开始测试,然后继续看看测试的极限参数 首先准备一个常用的,稳定的调试器,链接如图。 基础的25600bps 是没有问题的。 然后就是921600 也是没有问题的,接下来就是高速飞跃的时候了 3M的情况下是稳定传输的,3M的情况实际应用已经很少了,通常最高就是115200. 在4M的时候出现了问题,可以看见我们两个通信是出现问题了的,为了进一步的了解,我们吧我们的调试器换成逻辑分析仪,硬件连接如下   我们还是从3M开始 3M的波形还是不粗的,我们使用了逻辑分析仪的协议分析功能 在4M的情况下,我们的逻辑分析仪是可以正常抓包和分析的 在我们的芯片推荐的6M的情况下,我们的RS485接口还是可以正常工作。 由于我们的逻辑分析仪的限制,所以我们的串口能够测试到的极限是6M的波特率,但是我相信实际的应该还是可以更高。   好了这个485接口的测试就到这里,整体性能还是相对于其他厂商已经是性能非常卓越了。                    

  • 发表了主题帖: 【RainbowLink USB 协议转换器】-02-关于TTL电平的极限参数测试

    通过上面的蚕食我们可以知道,我们的主控使用的是CH344Q,按照手册上来说可以到6M的速度,那实际上极限是多少呢? 我今天闲来没事,做个极限测试 简单的就来做个115200的,后面的就按照整数来测试吧。 首先我们从硬件上来说明连接关系,两个TTL串口进行交叉连接,使得两个串口能够轻松的互传数据。     115200BPS 小菜一叠 按照芯片手册上测试,我们可以直接测试6M的波特率,还是稳稳当当的。 9M的波特率,互传是没有问题的。 最后测试一下,12M的波特率,12M的是没有问题的, 如果使用12M以上的话,就回出现连接不上串口的情况,应该是在驱动里面做了限制吧。 好吧,目前能测试到的极限的,可以正常传输的速率就是到12M,还是相当不错 的了            

  • 2024-12-30
  • 发表了主题帖: 【RainbowLink USB 协议转换器】-01- 开箱测试

    本帖最后由 申小林 于 2024-12-30 20:30 编辑 收到这个板子还是非常欣喜的, 我首先还是直接来图片上来说说 外包装有点凹陷, 外面贴上了保护膜 撕掉之后好看多了 上电之后又电源 整个板子以CH344G为主 CH344G 的芯片资料 一共4个串口,可以根据实际使用情况使用,非常实用   整个板子做工还是非常精细的,接线采用压接端子,接线非常方便。而且还是四个独立的串口    

  • 2024-12-24
  • 回复了主题帖: 【测评入围名单】RainbowLink USB 协议转换器(RS485 / RS232 / TTL)

    个人信息无误,确认可以完成测评分享计划

  • 2024-12-20
  • 发表了主题帖: 【嵌入式AI挑战营 】-03-RV1106自制底板的使用

    前段时间在做这个底板,所以花了点时间,今天给大家分享一下,主要是做了接口的扩展,包括IO口、SPI-Flash,Led、RS485接口、IIC的温度传感器、调试串口,还有外部供电接口供电电压从6V到36V均可以正常供电,还带有反接保护,保证稳定运行,调试串口是Typec 接口,用起来很方便。好了直接上图不废话。 原理图上图,需要的大家自己抄一下。   PCB的电路图   测试一下,串口正常工作 整体的看看。   这个是按键,带了自首发的RS485的接口   独立供电还没有焊接接口,供电电压是6-36V,温度传感器还没有焊接,主要是看手册上说不要做IIC的存储的接口。   好了,分享到这里,后期继续跟大家分享。      

  • 发表了主题帖: 《Linux内核深度解析》-概读和总览

          其实再周二的时候就收到了书籍,今天周五了,才有时间来给大家分享一下对于整本书籍的大致了解,由于自己还没有认证的去看每一章的具体的内容,目前只是对书籍讲述内容的大致情况做了总的预览,按照常规的说法就是,先总览,在细节,总览有助于了解作者再整个过程中的一个思路,以及整个书籍呈现出的脉络。所以我每次读书的时候都是先总览一边,然后再去跟着作者的思想进行下一步的学习,这样的话才有助于读者更加体会编者的心情与思路。理解也会更加的深刻。 首先还是给大家来展示一下吧。 《Linux 内核深度解析:基于 arm64 架构的 Linux4.x 内核》是一本由余华兵著,人民邮电出版社和中国工信出版集团联合出版的专业书籍。       整个本书籍,于我们常见的书籍是有些不同的,可能做技术的人员感触会比较深刻,就是整个数据很少有台华丽的词藻去修饰一些东西,上来就单枪直入,说明系统的整个其实入点。start.s文件的作用以及启动前需要做的事情。 接下来就是最基础的函数入口,内存管理,中断向量额说明,进程调度、任务调度的基本原则以及调度的顺序说明。内存分配的机制以及常见内存错误的处理方式。 前言是必读的内容,前言里面说明了作者的编写的思想,以及在写完书的一些感受,也希望通过书籍的理解。           对于 Linux 内核开发人员,这是一本不可多得的参考书籍。它可以帮助他们深入理解内核的运行机制,在进行内核调试、性能优化、新功能开发等工作时提供理论支持。例如,在开发基于 arm64 架构的嵌入式 Linux 系统时,书中关于内核引导和初始化的内容可以帮助开发人员确保系统的正确启动。对于已经有一定 Linux 基础的读者,这本书可以作为他们进阶学习的重要资料。通过深入学习内核机制,可以提升他们在 Linux 系统管理、运维、开发等方面的专业水平。例如,系统管理员在了解了进程管理和调度机制后,可以更好地对服务器的资源进行管理和优化。由于内容专业性极强,对于没有一定 Linux 基础和相关编程经验的读者来说,理解起来可能会非常困难。书中涉及到大量的技术术语、底层机制和代码逻辑,可能会让初学者望而却步。   对于整本书籍,我做了如下的总结 专业性强          该书聚焦于 Linux 内核的深度解析,以基于 arm64 架构的 Linux 4.x 内核为具体研究对象。对于从事 Linux 系统开发、嵌入式系统开发等相关领域的专业人士来说,内容极具针对性。它深入到内核的引导和初始化过程,包括从哪里读取引导程序、引导程序的具体流程、内核初始化的各个步骤等内容,如详细介绍了汇编语言部分和 C 语言部分在内核初始化中的作用。 结构清晰         从目录来看,书籍的结构条理分明。它依次从内核引导和初始化开始,逐步深入到进程管理、内存管理等核心内容。例如,在进程管理部分,涵盖了进程的创建、退出、状态、调度等 多个方面,对每个环节都有详细的阐述;内存管理部分则从虚拟地址空间布局、物理地址空间布局到内存映射、内存分配器等都有深入的讲解。这种结构有助于读者循序渐进地学习和理解 Linux 内核的复杂机制。 理论与实践结合       虽然书中主要以理论知识为主,但这些理论内容对于实践操作有着重要的指导意义。例如,书中对 SMP 调度、内存管理等机制的深入剖析,对于从事多核处理器环境下的 Linux 系统优化、内存优化等实践工作的人员来说,能够提供坚实的理论基础,帮助他们更好地解决实际工作中遇到的问题。          总体而言,《Linux 内核深度解析:基于 arm64 架构的 Linux4.x 内核》是一本非常专业且有深度的书籍,适合有一定基础的 Linux 从业者和相关专业人员深入学习,但对于初学者来说可能有一定的难度。    

统计信息

已有231人来访过

  • 芯积分:409
  • 好友:2
  • 主题:45
  • 回复:158

留言

你需要登录后才可以留言 登录 | 注册


现在还没有留言