- 2025-01-25
-
发表了主题帖:
《Linux内核深度解析》-内存管理
# 内存管理
### 介绍
本章节主要介绍了这几个内容
1. 虚拟地址的空间布局
2. 内存映射
3. 伙伴分配器
4. 块分配
![1_内存管理架构](/data/attachment/forum/202501/25/010847dxwwwbgs8ebtsbbd.jpg.thumb.jpg?rand=9962.586020944542)
### 一、虚拟地址的空间布局
用户态虚拟地址空间是进程运行的基础,其布局包括代码段、数据段、堆、内存映射区域和栈等部分。不同的架构在地址范围和布局上有所差异,但整体结构相似。如果我们能理解理解虚拟地址空间的布局对于未来的调试和优化程序非常有帮助。
![2_虚拟地址空间划分](/data/attachment/forum/202501/25/010847dxwwwbgs8ebtsbbd.jpg.thumb.jpg?rand=3699.6627689552806)
#### 1.1 用户态虚拟地址空间布局
用户态虚拟地址空间典型的布局:
(1) 代码段(Text Segment):存储进程的可执行代码(即程序的指令)
(2) 数据段(Data Segment):存储全局变量和静态变量
(3) 堆(Heap):动态分配的内存(如 `malloc`、`calloc` 等函数分配的内存)
(4) 内存映射区域(Memory Mapping Segment):文件映射(如 `mmap` 映射的文件)和匿名映射(如共享内存)
(5) 栈(Stack):存储局部变量、函数调用栈帧等
(6) 内核空间:内核代码和数据
![3_用户态虚拟地址空间](/data/attachment/forum/202501/25/010848u84wmjjsj284nknj.jpg.thumb.jpg?rand=372.1256937253137)
#### 1.2 用户态虚拟地址空间的特点
- 独立性:每个进程都有独立的虚拟地址空间,互不干扰。
- 分页机制:虚拟地址通过页表映射到物理内存。
- 动态增长:堆和栈可以根据需要动态增长。
- 内存映射:支持文件映射和匿名映射,灵活管理内存。
### 二、内存映射
内存映射的应用编程接口:
(1) mmap:将文件或设备映射到进程的虚拟地址空间,支持共享内存和匿名映射
(2) mremap:调整已映射内存区域的大小或移动其位置
(3) munmap:解除内存映射,释放由 `mmap` 或 `mremap` 分配的虚拟内存区域
(4) brk:调整堆的结束位置,用于动态分配内存,通常由 `malloc` 内部调用
(5) remap_file_pages:重新映射文件的部分页面到进程的虚拟地址空间,支持非连续映射
(6) mprotect:修改内存区域的访问权限
(7) madvise:向内核提供内存使用建议,如预读、释放等,以优化内存管理
(8) remap_pfn_range:将物理页帧映射到用户空间,通常用于设备驱动
![4_虚拟内存用结构体vm_area_struct描述](/data/attachment/forum/202501/25/010848on66kkwhw70qk6ez.jpg.thumb.jpg?rand=1257.5941859029172)
其中mmap是比较重要的函数,书中列举了mmap最终调用的do_mmap的执行流程
![5_do_mmap执行流程](/data/attachment/forum/202501/25/010849u569j9m9gnsl9zj9.jpg.thumb.jpg?rand=3129.5412483963214)
### 三、伙伴分配器
在内核中,物理内存的管理需要满足以下需求:
- 支持不同大小的内存分配请求。
- 减少内存碎片。
- 快速分配和释放内存。
伙伴分配器通过将内存划分为大小固定的块,并使用“伙伴系统”来合并和拆分内存块,从而满足这些需求。
### 3.1 伙伴分配器的工作流程
1. 初始化:
- 在内核启动时,伙伴分配器会初始化物理内存,将其划分为多个大小固定的块,并将这些块加入到相应的空闲链表中
2. 内存分配:
- 当内核需要分配内存时,伙伴分配器会根据请求的大小找到合适的order
- 如果该阶的空闲链表中有可用块,则直接分配
- 如果没有可用块,则从更高阶的空闲链表中拆分一个块,并将其加入到较低阶的空闲链表中
3. 内存释放:
- 当内核释放内存时,伙伴分配器会将释放的块标记为空闲,并尝试与其伙伴块合并
- 如果伙伴块也是空闲的,则合并成一个更大的块,并继续尝试与更高阶的伙伴块合并
4. 合并与拆分:
- 合并:当两个伙伴块都空闲时,它们会合并成一个更大的块
- 拆分:当需要分配较小块时,较大的块会被拆分为两个较小的伙伴块
### 3.2 分配页接口
- `alloc_pages()`:分配一个或多个物理页
- `free_pages()`:释放通过`__get_free_pages()`分配的物理页
- `__get_free_pages()`:分配连续的物理页,并返回其虚拟地址
- `__free_pages()`:释放通过`alloc_pages()`分配的物理页
### 四、块分配器
在内核中,频繁地分配和释放小对象,如任务结构、文件描述符等,会导致内存碎片和性能下降
传统的伙伴系统虽然适合管理大块内存,但对小对象的管理效率较低。为了解决这个问题,Linux引入了块分配器(Slab Allocator)
Slab Allocator 的核心思想是预分配和缓存。它将内存划分为多个“Slab”,每个Slab由多个固定大小的“对象”组成。Slab Allocator 的主要组件包括:
- Cache:缓存是Slab分配器的核心数据结构,用于管理特定类型的内核对象
- Slab:Slab是内存分配的基本单位,每个Slab包含多个相同大小的对象。
- Object:对象是实际分配的内存单元,大小固定,用于存储内核数据结构。
![6_内存缓存数据结构](/data/attachment/forum/202501/25/010850xp4gh99ghiwulu69.jpg.thumb.jpg?rand=4604.053524983418)
#### 4.1 SLAB分配器
SLAB 是 Linux 内核中最早的块分配器实现,由 Jeff Bonwick 在 Solaris 系统中提出,后被引入 Linux 内核。它的设计目标是减少内存碎片、提高内存分配效率,并优化缓存利用率。
核心特点
1. 缓存机制:
- SLAB 分配器为每种内核对象创建一个缓存
- 每个缓存包含多个 Slab,每个 Slab 由多个固定大小的对象组成
2. Slab 状态:
- Slab 可以处于三种状态:满(所有对象已分配)、部分满(部分对象已分配)、空(所有对象空闲)
3. 对象分配与释放:
- 分配对象时,SLAB 分配器会从部分满或空的 Slab 中分配对象
- 释放对象时,对象会被标记为空闲,并尝试合并到空闲链表中
4. 内存着色:
- SLAB 分配器引入了内存着色机制,通过调整对象在 Slab 中的偏移量,优化 CPU 缓存的利用率
#### 4.2 块分配器种类
Linux内核中的块分配器经历了多次改进,目前主要有以下几种实现:
- SLAB 是最早的块分配器,功能强大但复杂,适用于需要高效管理小对象的场景
- SLUB 是 SLAB 的改进版本,简化了设计并减少了内存开销,成为 Linux 内核的默认分配器
- SLOB 是一种极简化的分配器,适用于资源受限的嵌入式系统
对比总结
| 特性 | SLAB | SLUB | SLOB |
|-----------------|-------------------------------|-------------------------------|-------------------------------|
| 设计目标 | 高效管理小对象,减少碎片 | 简化设计,减少内存开销 | 极简化,低内存开销 |
| 复杂度 | 复杂 | 简单 | 极简单 |
| 内存开销 | 较大 | 较小 | 极小 |
| 性能 | 高 | 更高 | 低 |
| 内存碎片 | 较少 | 较少 | 较多 |
| 适用场景 | 通用场景 | 通用场景(默认) | 嵌入式系统 |
内存分配是linux内核中十分重要的领域,基本每个模块都会涉及到,本章中主要对这些内容进行了学习
# 参考资料
[简说linux-inux内核开发50讲](https://space.bilibili.com/646178510/lists/375089?type=season)
- 2025-01-22
-
回复了主题帖:
武汉芯源CW32L021小评
秦天qintian0303 发表于 2025-1-22 09:20
不错啊,芯片准备做个板子吗?
最近在做别的测评,还没有这个计划,但是会一直关注关注国产芯
-
回复了主题帖:
武汉芯源CW32L021小评
lugl4313820 发表于 2025-1-22 08:29
标题少了一点字,有空补一下谢谢,是武汉芯源!
嗷嗷,是的,已经修复啦
- 2025-01-21
-
发表了主题帖:
武汉芯源CW32L021小评
本帖最后由 rtyu789 于 2025-1-22 15:59 编辑
十分感谢@lugl4313820赠送的芯片,是武汉芯源CW32L021,是武汉芯源半导体有限公司生产的,基于eFlash的单芯片低功耗微控制器,集成了主频高达48MHz的ARM Cortex-M0+ 内核
提供了二路 UART、一路 SPI 、一路 I2C、12 位高速 ADC、四组通用和基本定时器、一组低功耗定时器以及一组高级控制 PWM 定时器。
CW32L010 可以在 -40℃到 85℃的温度范围内工作,供电电压宽达 1.62V ~ 5.5V。支持 Sleep 和 DeepSleep,两种低功耗工作模式。
ARMCortex-M0+的超低功耗芯片,对比STM32的话,就是STM32L0系列
![1_STM32_32位MCU](/data/attachment/forum/202501/21/234453w88s6uw85oxvkt85.png.thumb.jpg?rand=8886.837884106266)
去芯源半导体的官网查看了下相关内容,他们也有自己的烧录器
![2_CW_DPLINK](/data/attachment/forum/202501/21/234453mt9k89sokvzs18pv.png.thumb.jpg?rand=5370.602714702722)
看了下在消费电子领域应用的也比较广泛的
![3_消费电子领域](/data/attachment/forum/202501/21/234455pdqscz4asp8jhaba.png.thumb.jpg?rand=5540.889385829315)
还有一些对开发者的扶持计划
![4_开发者扶持计划](/data/attachment/forum/202501/21/234456ceopcbefx9xhjep0.png.thumb.jpg?rand=1415.329131406211)
国产芯片赶紧还是挺不错的投入,也有挺大的投入力度,扶持国内的生态,希望国产芯加油呀
# 参考资料
[芯源半导体](https://www.whxy.com/)
- 2025-01-19
-
回复了主题帖:
【测评入围名单(最后1批)】年终回炉:FPGA、AI、高性能MCU、书籍等65个测品邀你来~
个人信息确认无误,可以完成计划。
- 2025-01-13
-
回复了主题帖:
《Linux内核深度解析》-进程管理
heleijunjie72 发表于 2025-1-12 17:51
进程管理,是很很硬核的资料,很值得学习,收藏了
加油加油,一起努力
- 2025-01-10
-
回复了主题帖:
投票啦:图像处理、通信"小说"、仓颉编程、Altium书籍,先上哪一本?(人民邮电赞助)
更喜欢一些技术类的硬核书籍,更想要上线图像处理与计算机视觉实践——基于OpenCV和Python,系统了解下opencv的使用
-
发表了主题帖:
《Linux内核深度解析》-进程管理
本帖最后由 rtyu789 于 2025-1-10 00:30 编辑
# 进程管理
### 介绍
本章节主要从三个方面讲述
1. 进程,进程介绍,启动/退出进程
2. 进程调度
3. SMP调度
每个章节作者都是先介绍概念和原理,然后介绍用户空间的函数,从用户空间的用户态调用,深入到内核具体怎么操作的,讲解的十分细致
跟随着作者阅读源码,作者写的内容看起来已经十分丰富了,但是和实际的内核代码相比,内核代码还有更多更多细节。。。。甚至看一个函数,比如fork函数的实现,就需要花去一晚上的时间,真的是一杯茶一包烟一段函数看一天
平时在教科书上学习到的内容就是1,2两点截止了,本书中作者随着源码,阐述了一下SMP调度是怎么实现的,这种调度主要用在现在的多核处理器上,十分有学习的意义。但是由于我了解的不深,只能写一写概念性的东西了。
### 一、进程
进程是操作系统中的一个基本概念,指的是正在运行的程序实例。它不仅是程序的执行过程,还包括程序运行时所需要的内存、寄存器、文件等资源。
每个进程都有独立的地址空间,确保进程之间的隔离性。
书中展示了一些进程描述符 `task_struct` 的主要成员
| 成员名称 | 数据类型 | 描述 |
| ------------- | ---------------------- | ---------------------------------------------------------------- |
| `state` | `volatile long` | 进程的当前状态(如运行、就绪、阻塞等)。 |
| `stack` | `void *` | 指向进程内核栈的指针。 |
| `usage` | `refcount_t` | 进程的引用计数,用于跟踪进程的使用情况。 |
| `pid` | `pid_t` | 进程的唯一标识符(PID)。 |
| `tgid` | `pid_t` | 线程组 ID,表示进程所属的线程组(通常与主线程的 PID 相同)。 |
| `real_parent` | `struct task_struct *` | 指向进程的父进程的指针。 |
| `parent` | `struct task_struct *` | 指向进程的当前父进程的指针(可能因 `ptrace` 而改变)。 |
| `mm` | `struct mm_struct *` | 指向进程内存管理结构的指针,包含进程的地址空间信息。 |
| `active_mm` | `struct mm_struct *` | 指向进程当前活动的内存管理结构的指针。 |
| `comm` | `char[]` | 进程的可执行文件名(通常为 16 字节)。 |
| `prio` | `int` | 进程的动态优先级,用于调度器决策。 |
| `static_prio` | `int` | 进程的静态优先级,由用户或系统设置。 |
| `normal_prio` | `int` | 进程的归一化优先级,基于静态优先级和调度策略计算得出。 |
| `rt_priority` | `unsigned int` | 实时进程的优先级,用于实时调度策略。 |
| `sched_class` | `struct sched_class *` | 指向调度类结构的指针,决定进程的调度策略(如 CFS、实时调度等)。 |
| `exit_state` | `int` | 进程的退出状态,表示进程是否正在退出或已经退出。 |
| `exit_code` | `int` | 进程退出时的返回值,通常用于父进程获取子进程的退出状态。 |
linux4.12的源码,整个结构体的长度有600行左右,有大量的注释和宏定义判断,需慢慢了解各个成员的作用
```C++
struct task_struct {
/*
* For reasons of header soup (see current_thread_info()), this
* must be the first element of task_struct.
*/
struct thread_info thread_info;
/* -1 unrunnable, 0 runnable, >0 stopped: */
volatile long state;
void *stack;
atomic_t usage;
/* Per task flags (PF_*), defined further below: */
unsigned int flags;
unsigned int ptrace;
struct llist_node wake_entry;
int on_cpu;
/* Current CPU: */
unsigned int cpu;
unsigned int wakee_flips;
unsigned long wakee_flip_decay_ts;
struct task_struct *last_wakee;
int wake_cpu;
int on_rq;
int prio;
int static_prio;
int normal_prio;
unsigned int rt_priority;
const struct sched_class *sched_class;
struct sched_entity se;
struct sched_rt_entity rt;
struct task_group *sched_task_group;
struct sched_dl_entity dl;
unsigned int policy;
int nr_cpus_allowed;
cpumask_t cpus_allowed;
unsigned long rcu_tasks_nvcsw;
bool rcu_tasks_holdout;
struct list_head rcu_tasks_holdout_list;
int rcu_tasks_idle_cpu;
struct sched_info sched_info;
struct list_head tasks;
struct mm_struct *mm;
struct mm_struct *active_mm;
/* Per-thread vma caching: */
struct vmacache vmacache;
int exit_state;
int exit_code;
int exit_signal;
/* Bit to tell LSMs we're in execve(): */
unsigned in_execve:1;
unsigned in_iowait:1;
pid_t pid;
pid_t tgid;
/*
* Pointers to the (original) parent process, youngest child, younger sibling,
* older sibling, respectively. (p->father can be replaced with
* p->real_parent->pid)
*/
/* Real parent process: */
struct task_struct __rcu *real_parent;
/* Recipient of SIGCHLD, wait4() reports: */
struct task_struct __rcu *parent;
/*
* Children/sibling form the list of natural children:
*/
struct list_head children;
struct list_head sibling;
struct task_struct *group_leader;
struct sysv_sem sysvsem;
struct sysv_shm sysvshm;
}
```
Linux 内核支持多种类型的命名空间,每种类型对应一种资源的隔离:
| 命名空间类型 | 隔离的资源 | 标志 (`clone` 或 `unshare`) |
| --------------- | ------------------------------ | --------------------------- |
| cgroup 命名空间 | 控制组(cgroup)视图 | `CLONE_NEWCGROUP` |
| IPC 命名空间 | System V IPC 和 POSIX 消息队列 | `CLONE_NEWIPC` |
| 网络命名空间 | 网络设备、IP 地址、路由表等 | `CLONE_NEWNET` |
| 挂载命名空间 | 文件系统挂载点 | `CLONE_NEWNS` |
| PID 命名空间 | 进程 ID(PID) | `CLONE_NEWPID` |
| 用户命名空间 | 用户和用户组 ID | `CLONE_NEWUSER` |
| UTS 命名空间 | 主机名和域名 | `CLONE_NEWUTS` |
![1_进程的命名空间](/data/attachment/forum/202501/10/002417e8gwpwp3mtgwpg08.jpg.thumb.jpg?rand=793.7677865435488)
最开始阅读的时候,感觉书籍中仅仅是简单的罗列,但是实际阅读后发现,每个成员在内核中都有实际的作用,书籍等于是把人引入门的作用
我自己在阅读的过程中,也是慢慢查找资料,了解各个变量的作用,并通过后面的章节,了解到变量会在什么时候会被用到
### 二、启动进程
进程的创建通常通过以下方式:
- fork() 是最常用的进程创建方式,适用于需要独立进程的场景
- vfork() 适用于子进程立即调用 exec() 的场景,性能优于 fork(),但使用限制较多
- clone() 提供了更灵活的控制,适用于创建线程或轻量级进程,是实现多线程编程的基础
![2_进程线程和链表](/data/attachment/forum/202501/10/002418hyauobafpa2fyxr1.jpg.thumb.jpg?rand=4773.287123322459)
```C++
#ifndef CONFIG_HAVE_COPY_THREAD_TLS
/* For compatibility with architectures that call do_fork directly rather than
* using the syscall entry points below. */
long do_fork(unsigned long clone_flags,
unsigned long stack_start,
unsigned long stack_size,
int __user *parent_tidptr,
int __user *child_tidptr)
{
return _do_fork(clone_flags, stack_start, stack_size,
parent_tidptr, child_tidptr, 0);
}
#endif
/*
* Ok, this is the main fork-routine.
*
* It copies the process, and if successful kick-starts
* it and waits for it to finish using the VM if required.
*/
long _do_fork(unsigned long clone_flags,
unsigned long stack_start,
unsigned long stack_size,
int __user *parent_tidptr,
int __user *child_tidptr,
unsigned long tls)
{
struct task_struct *p;
int trace = 0;
long nr;
/*
* Determine whether and which event to report to ptracer. When
* called from kernel_thread or CLONE_UNTRACED is explicitly
* requested, no event is reported; otherwise, report if the event
* for the type of forking is enabled.
*/
if (!(clone_flags & CLONE_UNTRACED)) {
if (clone_flags & CLONE_VFORK)
trace = PTRACE_EVENT_VFORK;
else if ((clone_flags & CSIGNAL) != SIGCHLD)
trace = PTRACE_EVENT_CLONE;
else
trace = PTRACE_EVENT_FORK;
if (likely(!ptrace_event_enabled(current, trace)))
trace = 0;
}
p = copy_process(clone_flags, stack_start, stack_size,
child_tidptr, NULL, trace, tls, NUMA_NO_NODE);
...
wake_up_new_task(p);
...
}
```
![3_copy_process的执行流程](/data/attachment/forum/202501/10/002418pwfltwx1u196tetr.jpg.thumb.jpg?rand=7708.37148283347)
fork是用过系统调用sys_fork实现,sys_fork是通过do_fork实现,do_fork内部最终是由_do_fork实现,其中最核心的函数是copy_process,作者画了详细的调用流程,并对每一步走了解释,内容过多,就需要自己看啦
### 三、进程退出
1. 进程的终止方式
- 正常退出:进程执行完毕,调用 `exit()` 系统调用。
- 异常退出:进程由于错误或异常情况被迫终止,如段错误、除零错误等。
- 强制终止:由其他进程或操作系统强制终止,如通过 `kill` 命令。
![4_进程组退出时的执行流程](/data/attachment/forum/202501/10/002418kukrek6yxk5y9xux.jpg.thumb.jpg?rand=28.88649634224194)
2. 进程终止时的操作
- 释放资源:操作系统回收进程占用的内存、文件描述符等资源。
- 通知父进程:通过信号或进程间通信机制通知父进程子进程的终止状态。
- 更新进程表:将进程从进程表中移除,并将其状态设置为“终止”。
### 四、进程调度
1. 进程的状态
- 就绪状态
- 运行状态
- 轻度睡眠状态
- 中都睡眠状态
- 深度睡眠状态
- 僵尸状态
- 死亡状态
![5_进程的状态变迁](/data/attachment/forum/202501/10/002419fhuwyl6llssuaa6j.jpg.thumb.jpg?rand=6819.272541805505)
2. 调度的目标
- 公平性:确保每个进程都能获得合理的CPU时间。
- 高效性:最大化CPU利用率,减少空闲时间。
- 响应时间:确保交互式进程能够及时响应。
- 吞吐量:在单位时间内完成尽可能多的进程。
3. 进程调度类
进程调度类是 Linux 内核中用于管理和实现不同调度策略的框架。
它是 Linux 调度器(Scheduler)的核心组成部分,定义了如何选择下一个要运行的进程以及如何管理进程的调度行为。
1. stop_sched_class 停机调度类,最高优先级的调度类,用于执行停止 CPU 的任务(如迁移线程)
2. dl_sched_class 期限调度类,用于调度实时任务,基于最早截止时间优先(EDF)算法,确保任务在截止时间内完成
3. rt_sched_class 实时调度类,用于调度优先级固定的实时任务,确保高优先级任务优先执行
4. fair_sched_class 完全公平调度类(CFS),用于普通进程的调度,基于虚拟运行时间实现公平性
5. idle_sched_class 空闲调度类,当系统没有其他任务可运行时,调度空闲任务以降低功耗
![6_进程调度类](/data/attachment/forum/202501/10/002419aezpyzgyt8zpjytt.jpg.thumb.jpg?rand=4220.571229893655)
## 五、SMP调度
在多处理器系统(Symmetric Multiprocessing, SMP)中,多个CPU核心共享同一内存空间,操作系统需要扩展调度机制以充分利用多核资源。SMP调度不仅需要考虑单核调度的问题,还需要处理多核之间的负载均衡、缓存亲和性等问题。
### 1. 进程处理器的亲和性
#### 1.1 亲和性的概念
进程处理器的亲和性(Processor Affinity)是指进程或线程与特定CPU核心之间的绑定关系。通过设置亲和性,可以将进程或线程固定到某个CPU核心上运行,从而利用CPU缓存的局部性,减少缓存失效带来的性能损失。
#### 1.2 亲和性的类型
- 软亲和性:操作系统会尽量将进程调度到上次运行的CPU核心上,但不强制绑定。
- 硬亲和性:进程或线程被明确绑定到特定的CPU核心上,无法迁移到其他核心。
#### 1.3 亲和性的优点
- 减少缓存失效:进程在同一个CPU核心上运行,可以利用缓存中的数据,减少内存访问延迟。
- 提高性能:对于计算密集型任务,绑定到特定核心可以减少上下文切换的开销。
#### 1.4 亲和性的缺点
- 负载不均衡:如果某个核心上的进程负载过高,而其他核心空闲,可能导致系统整体性能下降。
- 灵活性降低:硬亲和性限制了进程的迁移能力,可能影响系统的动态负载均衡。
### 2. 迁移线程
线程迁移是指将正在运行的线程从一个CPU核心迁移到另一个核心的过程。迁移线程的目的是为了实现负载均衡,避免某些核心过载而其他核心空闲
#### 2.1 线程迁移的挑战
- 缓存失效:线程迁移会导致缓存中的数据失效,增加内存访问延迟。
- 上下文切换开销:迁移线程需要保存和恢复线程的上下文,增加了调度器的开销。
- 锁竞争:在多核系统中,线程迁移可能导致锁竞争,影响系统性能。
#### 2.2 线程迁移的策略
- 被动迁移:当某个核心过载时,调度器将部分线程迁移到其他核心上。
- 主动迁移:调度器定期检测系统的负载情况,主动将线程迁移到负载较轻的核心上。
- 亲和性优先:在迁移线程时,优先考虑线程的缓存亲和性,尽量减少缓存失效。
### 总结
进程管理是操作系统的核心功能之一,涉及进程的创建、调度、通信和终止等多个方面。通过合理的进程调度算法和进程间通信机制,操作系统能够高效地管理多个进程,确保系统的稳定性、响应性和公平性。同时,多线程技术的引入进一步提升了系统的并发处理能力,但也带来了同步和资源管理的挑战。理解进程管理的原理和机制,对于深入掌握操作系统的运行机制至关重要。
- 2025-01-07
-
加入了学习《全国大学生电子设计竞赛名师访谈》,观看 西安电子科技大学
-
回复了主题帖:
《Linux内核深度解析》-开箱以及环境准备
Timson 发表于 2025-1-2 17:20
您好!我们已经改好,谢谢您提的意见
哇,感谢,我看的确没有这个问题了,我查看了我历史的帖子,下面多的图片也都没有了,恢复正常了
- 2025-01-02
-
回复了主题帖:
祝福2025!回帖即有奖!选取最有心的送5块国产开发板!
lugl4313820 发表于 2025-1-2 11:06
恭喜大佬入选,烦请私信我收货信息。发的是武汉芯源CW32L021,包邮到家。
哇,感谢感谢,去了解了解这个芯片
- 2025-01-01
-
回复了主题帖:
《Linux内核深度解析》-开箱以及环境准备
nmg 发表于 2024-12-31 17:03
后面几张图片如果没用到,在上传的图片里删除,就不会显示了
我是用markdown编辑的,后面的图片都是正文中使用到的,我也一直想要搞掉后面的图片,但是没有找到方法
我试过先markdown引用图片,然后在上传的图片中删除,发出来帖子中的图片都看不到了
之前问了下管理员,说是没什么关系,那我就这样了
-
回复了主题帖:
祝福2025!回帖即有奖!选取最有心的送5块国产开发板!
好多没有见过的国产芯,2025加油加油
- 2024-12-26
-
发表了主题帖:
《Linux内核深度解析》-开箱以及环境准备
本帖最后由 rtyu789 于 2024-12-26 22:59 编辑
十分感谢EEWorld和人民邮电出版社提供了此次书籍阅读和分享的机会.
这本书是由异步社区出品的,是国内比较比较好的IT图书品牌,出版的IT书籍都比较的高质量。
现在公司主要是做嵌入式产品的,也是在Linux系统上做开发,只是用的内核还是Linux 3.x版本的。
市面上对于较新版本,深入介绍linux的书籍较少,也是有幸阅读这本书,加强对内核的了解。
# 一、书籍概览
这是书籍的大概展示,整体看起来还是不错的。里面的有丰富的流程图、表格,来解释内核的处理处理过程,还是很好的帮助理解的。整本书有将近600页左右,算是比较厚了
![1_书籍封面](/data/attachment/forum/202412/26/225449gf3ty3vh0t3rsm38.jpg.thumb.jpg?rand=1064.3384137323665)
![2_书籍背面](/data/attachment/forum/202412/26/225450rfovwt3zf2fwnvg9.jpg.thumb.jpg?rand=9617.737072495476)
![3_书籍插图1](/data/attachment/forum/202412/26/225450x5ac9ili4e4b2rl3.jpg.thumb.jpg?rand=5209.358224338476)
![4_书籍插图2](/data/attachment/forum/202412/26/225451qbdbbxb2owogrwzd.jpg.thumb.jpg?rand=7873.327410277045)
粗略的翻了翻书籍的目录,内存管理的内容有将近300页,占到书籍的二分之一了,果然内存管理是内核中非常重要的一部分了,阅读计划中的一章阅读完感觉是不太可能了。
从目录看到,书籍整体讲解的还是比较细致,对Linux内核的方方面面都会讲解到。同时由于书籍十分庞大,面面俱到的阅读也是不好的,引用作者在前言中的话:“学习内核关键是要理解数据结构之间的关系和函数调用关系。建议读者在学习时抓住主要线索,弄清楚执行流程,不要过多的关注函数的细节”
# 二、环境准备
1) 源码下载:linux-4.12.1
2) 阅读工具:
1. 作者推荐使用Source Insight,但是由于平时开发看代码都使用VSCode,所以也继续用这个了
2. 试了下,也不怎么卡顿,就是有的时候变量、函数转跳的时候有点吃力
3. 书本中对于代码片段都给出了详细的文件路径,未来阅读代码时候考虑使用手动查找目录的方式,提高对文件结构的熟悉度
4. 缺点,汇编文件不支持转跳,语法支持比较差,没有安装插件直接抓瞎
5. 推荐插件
1. C/C++、C/C++ Extension Pack、C/C++ Themes-C/C++语法支持
2. Makefile Tools-Makefile语法支持,很大部分的kbuild被VSCode自身Makefile语法支持了
3. kconfig-第三方小插件,源码中大量KConfig配置,支持较弱,
4. Arm Assembly-帮助.s文件高亮
5. MASM/TASM-汇编语言支持,
![5_VSCode目录结构](/data/attachment/forum/202412/26/225451kq9quitibapiqqqh.png?rand=6468.077792227984)
3) 作者推荐资料:
1. Cortex-A Series Programmer's Guide for ARMv8-A
1. 官网上只搜索到了一个在线的版本
2. 第三方网站找到了一个离线的版本
3. 链接都放在下方
2. ARM Architecture Reference Manual ARMv8, for ARMv8-A architecture profile
1. 从官网可以下载到
4) 辅助工具:
大模型:现在大模型非常的多,我这边使用的是阿里的通义千问,这是看代码的一个很好的工具,特别对于内核的代码,有许多地方不明白为什么要这么设计的。
可以直接将自己不懂的代码抛给大模型,让他帮忙做出解释
![6_大模型示范](/data/attachment/forum/202412/26/225452ngq5v5rrwqccakbm.png.thumb.jpg?rand=8650.933138222133)
# 参考资料
[The Linux Kernel Archives](https://kernel.org/)
[kernal 4.12](https://mirrors.edge.kernel.org/pub/linux/kernel/v4.x/)
[Cortex-A Series Programmers Guide for ARMv8-A-ARM官网在线版本](https://developer.arm.com/documentation/den0013/d)
[Cortex-A Series Programmers Guide for ARMv8-A-从一个学校的网站上下载的](https://www.macs.hw.ac.uk/~hwloidl/Courses/F28HS/Docu/DEN0013D_cortex_a_series_PG.pdf)
[ARM Architecture Reference Manual ARMv8, for ARMv8-A architecture profile](https://developer.arm.com/documentation/ddi0487/aa/?lang=en)
[The U-Boot Documentation](https://docs.u-boot.org/en/latest/index.html)
[sourceinsight](https://www.sourceinsight.com/)
- 2024-12-11
-
回复了主题帖:
读书入围名单: 《Linux内核深度解析》
个人信息无误,确认可以完成阅读分享计划
- 2024-11-23
-
回复了主题帖:
这5本书,你想让哪一本先上线?快来给它投票啦~
感觉最近测评书目中AI,大模型类型的书籍占比好高呀,最近几期全都是和AI相关的,还是想看一下和嵌入式软硬件相关的内容
投票给了AI编译器开发指南,想要多学习学习编译器相关方面的知识
- 2024-11-20
-
回复了主题帖:
(第10期)——玩具总动员 六爪机器人
这个画图真的太用心了,好可爱的机器人
-
回复了主题帖:
ST NUCLEO-WB09KE-BLE_Peripheral_Lite
Jacktang 发表于 2024-11-6 07:25
advertising的确是广告,官方的意思应该是让板子实现了模拟广告向外界发送信息的过程,,
官方也是用心 ...
的确是的,最开始我一直以为翻译错了,但是的确是这个单词
- 2024-11-06
-
发表了主题帖:
ST NUCLEO-WB09KE-BLE_Peripheral_Lite
# BLE_Peripheral_Lite
使用的例程
![1_BLE_Peripheral_Lite_例程](/data/attachment/forum/202411/05/235828yic2bcazbzcw8i22.png.thumb.jpg?rand=2341.0023126874903)
本例程演示了激活最少的蓝牙BLE外设功能的情况下进行蓝牙通讯
蓝牙Lite服务支持,仅仅激活了所需的最低功能(不包括任务序列器、定时器服务器、低功耗管理器等)
测试需要使用安装了ST BLE Sensor手机app应用
连接后,BLE_Peripheral_Lite可以从客户端接收写入消息并向其发送通知
# 程序功能流程
1. 程序开始运行时,使用本地名称 HELLO!进行数据广告
2. 在广告的过程中,绿色LED灯每0.5秒缓慢闪烁一次
3. 只能手机手机应用程序开始扫描
4. 在智能手机应用程序列表中选择 HELLO!设备并连接,绿色led切换速度更快,0.1秒一次
5. 进入P2P服务器界面,然后单击LED图标以打开/关闭LED1,控制的是Nucleo板0的蓝色LED
6. 服务器Nucleo板,每约1秒向智能手机客户端发送一次通知
7. 当外围设备断开连接时,0.5秒一次的广告将重新启动,并且可以再次连接到它
我一直觉得README中应该写的是0.5s一次的广播,但是原文如下,advertising的确是广告,官方的意思应该是让板子实现了模拟广告向外界发送信息的过程
- When the Peripheral device (Nucleo board) is disconnected, advertising is restarted and it is possible to connect to it again.
# 运行中的图示
![2_Hello界面](/data/attachment/forum/202411/05/235829gan60o0ndoc64pdz.jpg.thumb.jpg?rand=9791.716739154863)
![3_控制LED灯界面](/data/attachment/forum/202411/05/235830v3t2cazs4za14tbs.jpg.thumb.jpg?rand=7980.654300361325)
![4_消息通知界面](/data/attachment/forum/202411/05/235830itdtttd48tizijut.jpg.thumb.jpg?rand=5890.085250969341)
# 代码分析
```C++
int main(void)
{
HAL_Init();
SystemClock_Config();
PeriphCommonClock_Config();
MX_GPIO_Init();
MX_RADIO_Init();
MX_RADIO_TIMER_Init();
MX_PKA_Init();
MX_APPE_Init(NULL);
while (1)
{
MX_APPE_Process();
}
}
#define CFG_LPM_SUPPORTED (0)
void MX_APPE_Process(void)
{
VTimer_Process();
BLEStack_Process();
NVM_Process();
PERIPHERAL_LITE_SERVER_Process();
#if (CFG_LPM_SUPPORTED == 1)
PERIPHERAL_LITE_SERVER_Enter_LowPowerMode();
#endif /* CFG_LPM_SUPPORTED */
}
#define ADV_TIMEOUT_MS (500)
#define CONN_TIMEOUT_MS (100)
#define NOTIFICATION_TIMEOUT_MS (1 * 1000)
void PERIPHERAL_LITE_SERVER_Process(void)
{
if (bleAppContext.restartAdv)
{
bleAppContext.restartAdv = FALSE;
APP_BLE_Procedure_Gap_Peripheral(PROC_GAP_PERIPH_ADVERTISE_START_FAST);
}
/* Notification flow every ~ 1 sec */
if (bleAppContext.is_notification_timer_expired)
{
bleAppContext.is_notification_timer_expired = FALSE;
PERIPHERAL_LITE_SERVER_Switch_c_SendNotification();
/* Restart a timer for sending notification from server to client */
HAL_RADIO_TIMER_StartVirtualTimer(&bleAppContext.Notification_mgr_timer_Id, NOTIFICATION_TIMEOUT_MS);
}
/* Led blinking rate */
if (bleAppContext.is_adv_connection_timer_expired)
{
bleAppContext.is_adv_connection_timer_expired = FALSE;
if (bleAppContext.BleApplicationContext_legacy.connectionHandle == 0xFFFF)
{
/* Start a timer for slow led blinking for advertising */
HAL_RADIO_TIMER_StartVirtualTimer(&bleAppContext.Advertising_mgr_timer_Id, ADV_TIMEOUT_MS);
}
else
{
/* Start a timer for fast led blinking for connection */
HAL_RADIO_TIMER_StartVirtualTimer(&bleAppContext.Advertising_mgr_timer_Id, CONN_TIMEOUT_MS);
}
}
}
```
可以看到主函数书中进入了循环MX_APPE_Process()
在这个函数PERIPHERAL_LITE_SERVER_Process()中不断处理Lite BLE的事件
可以看到,是由bleAppContext.is_adv_connection_timer_expired,这个标志位可以看到是否已经被连接,一定是别的地方使得这个标志位产生了变化
通过宏定义可以看到,在广告状态,就是未连接情况下,闪烁频率是500ms,连接后,闪烁频率变为100ms
```C++
uint32_t MX_APPE_Init(void *p_param)
{
UNUSED(p_param);
APP_DEBUG_SIGNAL_SET(APP_APPE_INIT);
#if (CFG_DEBUG_APP_ADV_TRACE != 0)
UTIL_ADV_TRACE_Init();
UTIL_ADV_TRACE_SetVerboseLevel(VLEVEL_L); /* functional traces*/
UTIL_ADV_TRACE_SetRegion(~0x0);
#endif
#if (CFG_LED_SUPPORTED == 1)
Led_Init();
#endif
#if (CFG_DEBUG_APP_TRACE != 0) && (CFG_DEBUG_APP_ADV_TRACE == 0)
COM_InitTypeDef COM_Init =
{
.BaudRate = 115200,
.WordLength= COM_WORDLENGTH_8B,
.StopBits = COM_STOPBITS_1,
.Parity = COM_PARITY_NONE,
.HwFlowCtl = COM_HWCONTROL_NONE
};
BSP_COM_Init(COM1, &COM_Init);
#endif
if (HW_RNG_Init() != HW_RNG_SUCCESS)
{
Error_Handler();
}
HW_AES_Init();
HW_PKA_Init();
APP_BLE_Init();
APP_DEBUG_SIGNAL_RESET(APP_APPE_INIT);
return STM32_BLE_SUCCESS;
}
void APP_BLE_Init(void)
{
ModulesInit();
BLE_Init();
bleAppContext.Device_Connection_Status = APP_BLE_IDLE;
bleAppContext.BleApplicationContext_legacy.connectionHandle = 0xFFFF;
bleAppContext.Advertising_mgr_timer_Id.callback = Adv_Connection;
bleAppContext.is_adv_connection_timer_expired = FALSE;
bleAppContext.Notification_mgr_timer_Id.callback = Notification_Flow;
bleAppContext.is_notification_timer_expired = FALSE;
bleAppContext.restartAdv = FALSE;
APP_DBG_MSG("\n");
APP_DBG_MSG("Services and Characteristics creation\n");
PERIPHERAL_LITE_SERVER_APP_Init();
APP_DBG_MSG("End of Services and Characteristics creation\n");
APP_DBG_MSG("\n");
APP_BLE_Procedure_Gap_Peripheral(PROC_GAP_PERIPH_ADVERTISE_START_FAST);
HAL_RADIO_TIMER_StartVirtualTimer(&bleAppContext.Advertising_mgr_timer_Id, ADV_TIMEOUT_MS);
bleAppContext.connIntervalFlag = 0;
return;
}
static void Adv_Connection(void *arg)
{
BSP_LED_Toggle(LED_GREEN);
bleAppContext.is_adv_connection_timer_expired = TRUE;
return;
}
```
可以看到,bleAppContext.is_adv_connection_timer_expired 标志位是在Adv_Connection函数中被改变了
Adv_Connection函数在APP_BLE_Init蓝牙的初始化中,被赋值作为了一个回调函数作为bleAppContext蓝牙app处理的上下文
并最终是由MX_APPE_Init函数调用,在主函数中被初始化,由此达到了有蓝牙连接控制LED灯闪烁的目的
# 演示视频
[localvideo]230b74e15d111b1cc1909ea06cb3cf35[/localvideo]
- 2024-11-05
-
发表了主题帖:
《RISC-V开放架构设计之道》-RV32CV+特权架构+可扩展选项
# RV32V 向量
![1_RV32V指令操作码](/data/attachment/forum/202411/05/005002gt10xo2krdcu0psy.jpg.thumb.jpg?rand=5372.011770651946)
向量操作主要用于数据级并行,最著名的数据级并行架构是SIMD,她将64位寄存器划分成了多个8位,16位或32位的片段,然后并行的计算他们,但是RISC-V使用向量架构进行实现
前面章节提到的每一条整数和浮点计算都有对应的向量版本
RV32V 添加了32个名称以V开头的向量寄存器,但是每个向量寄存器的元素不固定
虽然简单的向量处理器一次只能处理一个向量元素,但是根据定义,各个元素的操作相互独立,理论上处理器可以同时计算所有元素
![2_不同向量ISA的DAXPY指令和代码大小](/data/attachment/forum/202411/05/005003vuirr4eem4u8erej.jpg.thumb.jpg?rand=1161.5763078913942)
为了引入SIMD,MIPS-32 MSA和x86-32 AVX2都花费了更多的代码为引入SIMD主循环做准备,所以总指令数上,RV32V更少
# RV64 64位地址指令
RISC-V扩展为63位,只需要加入少量的指令:32位的字,双字和长字版本
![3_RV64位地址指令](/data/attachment/forum/202411/05/005004h9jnb8b4ohe0zzhh.jpg.thumb.jpg?rand=8610.8974008436)
RISC-V受益于同时设计32位和64为架构,但是较老的ISA不得不先后设计他们
放眼未来,最流行的64位指令集可能是ARM-64,RV64和x86-64
# RV32/64 特权架构
主要是为了嵌入式系统设计,目前介绍的指令都是在用户模式,RISV-V提供了两种新的特权模式:机器模式和监管模式,这两种模式的特权均高于用户模式
![4_RISV-V特权架构指令](/data/attachment/forum/202411/05/005004xby9ogvxhov9h8ky.jpg.thumb.jpg?rand=1751.7577720866618)
机器模式, 是RISC-V的硬件线程可以执行的最高特权模式,在这个模式下,硬件线程能完全访问内存,IO和底层系统功能
机器模式最重要的特点是拦截和处理异常,分为同步异常和中断
![5_RISV-V异常和中断的原因](/data/attachment/forum/202411/05/005005ut5tr1epy45ff0e2.jpg.thumb.jpg?rand=3037.143635600643)
监管模式,是可选的特选模式,目的在支持先对的UNIX操作系统,监管模式的特权高于用户模式的,但是低于机器模式
监管模式使用页式虚拟内存,将内存划分为固定大小的页用来进行地址的翻译和内存保护,启用分页时,大多数地址都是虚拟地址
# 未来RISC-V的可扩展选项
1. RV32B,位操作
2. RV32E,嵌入式
3. RV32H,支持虚拟机管理
4. RV32J,动态翻译语言
5. RV32L,十进制浮点
6. RV32N,用户态中断
7. RV32P,紧缩SIMD指令
8. RV32Q,四倍精度浮点