- 2025-03-03
-
回复了主题帖:
《趣味微项目轻松学Python》-7-13章
Jacktang 发表于 2025-3-2 09:38
对99 Bottles Of Beer歌曲编写代码实现,并做测试这个很神奇
是的,后面作者还说了,编写代码实现由很多的方式,最终都可以通过做测试的方式来验证是否正确
- 2025-02-28
-
发表了主题帖:
《趣味微项目轻松学Python》-7-13章
7-13章主要介绍了这几个内容
1. 字典推导式
2. 字符串替换
3. 随机函数
4. 编写和测试函数
5. 一些小技巧
# 一、字典推导式
第七章介绍的是字典推导式
对比上一个帖子的列表推导式,字典也有推导式
```python
# for循环填充字典
lookup = {}
for line in args.file:
lookup[line[0].upper()] = line.rstrip()
# 字典推导式填充字典,类似列表推导式
lookup = {line[0].upper(): line.rstrip() for line in args.file}
```

# 二、字符串替换
第八章主要介绍了字符串的替换
之前还不知道有这么多种字符串替换的方法,书中列举的就比较详细了,我举例了一些在下面
```python
# 方法一:str.replace()
for v in 'aeiou':
text = text.replace(v, vowel).replace(v.upper(), vowel.upper())
print(text)
# 方法二:str.maketrans()
trans = str.maketrans('aeiouAEIOU', vowel 5 + vowel.upper() 5)
text = text.translate(trans)
print(text)
# 方法三:列表推导式
text = [
vowel if c in 'aeiou' else vowel.upper() if c in 'AEIOU' else c
for c in args.text
]
print(''.join(text))
# 方法四:带函数的列表推导式
def new_char(c):
return vowel if c in 'aeiou' else vowel.upper() if c in 'AEIOU' else c
print(''.join([new_char(c) for c in args.text]))
# 方法五:使用map函数和lambda表达式
text = map(
lambda c: vowel if c in 'aeiou' else vowel.upper()
if c in 'AEIOU' else c, args.text)
print(''.join(text))
# 方法六:正则表达式
text = re.sub('[aeiou]', vowel, text)
text = re.sub('[AEIOU]', vowel.upper(), text)
print(text)
```

# 三、随机函数

每一章的开头都会介绍本章的的目标文件需要什么样的输入输出,十分清晰明了
第九章和第十章都是类似的内容,主要介绍了随机数种子的使用
```python
random.seed(args.seed) # 设置随机数种子
adjectives = """
bankrupt base caterwauling corrupt cullionly detestable dishonest false
""".strip().split()
nouns = """
Judas Satan ape ass barbermonger beggar block boy braggart butt
""".strip().split()
for _ in range(args.number):
# random.sample() 函数用于从列表中随机选择不重复的元素
adjs = ', '.join(random.sample(adjectives, k=args.adjectives))
print(f'You {adjs} {random.choice(nouns)}!')
```
# 四、编写和测试函数
第十一章介绍了如何编写测试函数以及测试的意义
pytest 是一个功能强大且易于使用的 Python 测试框架,广泛用于编写和运行单元测试、集成测试和功能测试
pytest 的测试用例通常以 test_ 开头,pytest 会自动发现这些测试用例并执行它们
```python
def verse(bottle):
"""Sing a verse"""
next_bottle = bottle - 1
s1 = '' if bottle == 1 else 's'
s2 = '' if next_bottle == 1 else 's'
num_next = 'No more' if next_bottle == 0 else next_bottle
return '\n'.join([
f'{bottle} bottle{s1} of beer on the wall,',
f'{bottle} bottle{s1} of beer,',
f'Take one down, pass it around,',
f'{num_next} bottle{s2} of beer on the wall!',
])
# --------------------------------------------------
def test_verse():
"""Test verse"""
last_verse = verse(1)
assert last_verse == '\n'.join([
'1 bottle of beer on the wall,', '1 bottle of beer,',
'Take one down, pass it around,',
'No more bottles of beer on the wall!'
])
two_bottles = verse(2)
assert two_bottles == '\n'.join([
'2 bottles of beer on the wall,', '2 bottles of beer,',
'Take one down, pass it around,', '1 bottle of beer on the wall!'
])
```
对99 Bottles Of Beer歌曲编写代码实现,并做测试

作者介绍了驱动测试开发的概念,通过测试也可以较好的指导软件开发:
1. 为实现的功能单元添加一个测试
2. 运行所有测试,会看到测试失败
3. 编写新功能代码
4. 运行所有测试,直到所有测试成功
5. 重构
6. 从头开始(重复)
# 五、一些小技巧
第十二十三章介绍了一些小技巧
主要是对前面例子的一些完善
```python
# append 另一种写法
ransom = []
ransom.append(choose(char))
ransom += choose(char)
# map可以替换列表表达式
ransom = [choose(char) for char in args.text]
ransom = map(choose, args.text)
# 可以换行的输出方式
print('\n\n'.join(verses), file=args.outfile)
```

- 2025-02-21
-
回复了主题帖:
读书入围名单: 《具身智能机器人系统》
个人信息无误,确认可以完成阅读分享计划。
- 2025-02-17
-
回复了主题帖:
《趣味微项目轻松学Python》-开箱介绍以及基础知识y
okhxyyo 发表于 2025-2-11 09:41
这个小标题起的有意思。就是为什么说是瞭望哨呢?
瞭望哨就是图3中的图片,一个水手站在桅杆上瞭望风
书中可能意思是输出字符串就像水手看到了危险的东西一样,需要呼喊出来
-
回复了主题帖:
《趣味微项目轻松学Python》-开箱介绍以及基础知识y
秦天qintian0303 发表于 2025-2-11 09:09
这个系列的书籍好像还不少,python给我最深的印象就是他的格式
是的,通过缩进来判断语法是否正确
- 2025-02-11
-
发表了主题帖:
《趣味微项目轻松学Python》-开箱介绍以及基础知识y
十分感谢EEWorld和清华大学出版社提供了此次书籍阅读和分享的机会
# 一、书籍概览
这是书籍的大概展示,比较完整,没有破损,里面的字迹和代码都可以看的十分的清晰
书籍的封面是一个长得很奇怪的人,序言中介绍是穿过街道的土耳其女人,以前的人们由于地域上的不同,很容易通过衣着辨认出他们住在哪里,但是现在由于工业化的进程,地区的差异逐渐变少了。
在一个很难区分不同计算机书籍的时代,用两个多世纪前多样的书籍封面表述计算机行业的创新和创想




# 二、如何编写和测试Python程序
主要讲解了
1. print输出
2. main函数使用
3. argparse解析参数
4. #!/usr/bin/env 环境变量,指定运行环境
下面是书中的一个小demo
```python
#!/usr/bin/env python3
# Purpose: Say hello
import argparse
def main():
parser = argparse.ArgumentParser(description='Say hello')
parser.add_argument('-n', '--name', metavar='name',
default='World', help='Name to greet')
args = parser.parse_args()
print('Hello, ' + args.name + '!')
if __name__ == '__main__':
main()
```
# 三、字符串、列表、字典
主要介绍python中几种常见的数据结构以及如何使用
### 3.1 瞭望哨:使用字符串
主要介绍了字符串的常用操作
```python
# 将字符串所有字母转换为大写形式
word.upper()
# 检查字符串是否全部由大写字母组成
word.isupper()
# 首先将字符串中的所有字母转换为大写,然后检查转换后的字符串是否全为大写字母
word.upper().isupeer()
# 返回字符串的长度
len(word)
# 比较字符串是否等于"a"
word == "a"
# 使用format()方法格式化字符串,结果为"hello world"
"hello {}".format("world")
```

### 3.2 去野餐:使用列表
介绍了列表的常见操作,介绍了列表切片,很大的提高了操作效率
```python
# 列表常见操作
# 创建一个空列表,使用list()构造函数
items = list()
# 创建一个空列表,使用[]直接量方式
items = []
# 输出items变量的数据类型
type(items)
# 返回列表 items 中元素的数量
len(items)
# 显示列表对象的帮助文档
help(items)
# 向列表中添加一个字符串"test1"
items.append("test1")
# 移除并返回列表中的最后一个元素
items.pop()
# 返回"test1"字符串在列表中的索引位置
items.index("test1")
# 对列表中的元素进行排序
items.sort()
# 将列表中的元素顺序反转
items.reverse()
# 使用 ',' 作为分隔符,将列表中的元素连接成一个新的字符串
', '.join(items)
# 列表切片
# 访问列表中的第一个元素
items[0]
# 访问列表中的最后一个元素
items[-1]
# 获取列表中从索引0到索引2的元素切片
items[0:2]
# 等价于items[0:2],获取前两个元素的切片
items[:2]
# 获取从索引10开始到列表末尾的所有元素
items[10:]
```
### 3.3 跳过5:使用字典
介绍了字典的常见操作,迭代遍历的方法
```python
# 创建一个空字典
ans = dict()
ans = []
# 尝试向ans添加键值对"test1":"a1"
ans["test1"] = "a1"
# 检查ans的类型
type(ans)
# 定义ans为一个包含两个键值对的字典
ans = dict(test1="a1", test2="a2")
# 定义字典
ans = {"test1" : "a1", "test2" : "a2"}
# 检查"test1"是否是字典ans中的一个键
"test1" in ans # 访问字典
# 获取字典ans中键"test1"对应的值
ans.get("test1")
# 返回字典ans中所有的键,结果是一个可迭代对象
ans.keys()
# 返回字典ans中所有的值,结果是一个可迭代对象
ans.values()
# 返回字典ans中所有的键值对,每个键值对以元组形式存在,结果是一个可迭代对象
ans.items()
# 遍历字典ans的所有键值对,并打印出每一对键值
for key, val in ans.items()
print(f"{key} {val}")
```

# 四、读写文件 STDIN、STDOUT
介绍了读写文件的方法,format格式化字符串
```python
# 输入输出文件
import os
# 检查名为"test"的文件是否存在
os.path.isfile("test")
# 返回路径"/var/lib"的目录名部分
os.path.dirname("/var/lib")
# 返回路径"/var/lib"的文件名或最后一级目录名
os.path.basename("/var/lib")
# 尝试以只读模式打开"/var/lib"目录
fh = open("/var/lib/file")
# 试图读取文件对象 fh 的内容
fh.read()
# 打开"/var/lib/file"并读取其内容。
text = open("/var/lib/file").read()
# 试图向文件对象fh写入字符串"hello world"
fh.write("hello world")
# 关闭文件对象fh
fh.close()
# format 格式化文本
import math
# 使用 .format() 方法格式化字符串
"pi is {}".format(math.pi)
# 使用 .format() 方法并指定格式,保留两位小数的浮点数。
"pi is {:0.02f}".format(math.pi)
# 使用f-string(格式化字符串字面量)和格式说明符
f"pi is {math.pi:0.02f}"
# 使用 .format() 方法格式化字符串,使"hello"占据至少8个字符宽度的空间,右对齐。
"{:8}".format("hello")
# 对整数进行格式化,使123占据至少8个字符宽度的空间,右对齐。
"{:8}".format(123)
```

参考代码
# 参考资料
[tiny_python_projects](https://github.com/kyclark/tiny_python_projects)
[tinypythonprojects官网](https://tinypythonprojects.com/)
- 2025-02-10
-
回复了主题帖:
《Linux内核深度解析》-文件系统
oxlm_1 发表于 2025-2-10 09:19
确切的说,应该是看代码的同时翻纸质书,事半功倍
对照的代码的确更好啦,代码也是文档,里面的注释也对学习源码提供了很大帮助
-
回复了主题帖:
《Linux内核深度解析》-文件系统
wulishu 发表于 2025-2-10 14:36
非常好的资料,用来学习内核的非常方便,非常感谢分享
《Linux内核深度解析》-文件系 ...
感谢感谢
-
回复了主题帖:
《Linux内核深度解析》-文件系统
Jacktang 发表于 2025-2-10 07:33
linux内核学习阅读纸质书籍还是比较有用的
是的,纸质书的话可以来回的翻看,对于有问题内容可以反复对照
特别对于像是文件状态标志这种表格列举的情况,一般书籍里面列举的都比较详细
-
回复了主题帖:
《Linux内核深度解析》-中断异常和系统调用
Jacktang 发表于 2025-2-9 09:29 同步异常和异步异常相比,同步还是比多些
但是在有的情况下还是需要用到异步异常哒
-
发表了主题帖:
《Linux内核深度解析》-文件系统
# 文件系统
1. 虚拟文件系统的数据结构
2. 常见操作
1. 挂载
2. 打开
3. 关闭
4. 创建
5. 删除
6. 权限
7. 读文件
8. 写文件
9. 文件回写
# 一、虚拟文件系统的数据结构
在 Linux 系统中,一切皆文件,除了通常所说的文件以外,目录、设备、套接字和管道等都是文件

在对文件系统进行操作的时候,需要和外部存储设备进行交互,外部存储设备主要分为以下几类:
| 外部存储设备分类 | 子类别 |
| ---------------- | ------------ |
| 块设备 | 机械硬盘 |
| | 闪存类块设备 |
| 闪存 | NAND 闪存 |
| | NOR 闪存 |
| NVDIMM 设备 | - |
机械硬盘HDD和NAND闪存SSD是两种不同的数据存储技术,它们在结构、性能以及应用场景上有显著的区别
- 机械硬盘:主要由一个或多个旋转的磁盘和移动的读写头组成,其访问速度受到物理限制
- NAND闪存:是一种非易失性存储技术,不包含任何可移动部件,有更快的访问速度
- SSD比HDD具有更快的数据读写速度,尤其是随机读写性能
- 由于没有机械部件,SSD更耐震动和冲击,因此在便携式设备中更为理想
虽然不同文件系统类型的物理结构不同,但是虚拟文件系统定义了一套统一的数据结构,主要的数据结构有以下:
1. 超级块:用来描述文件系统的总体信息,结构体super_block
2. 索引节点:每个文件对应一个索引节点,每个索引节点有一个唯一的编号,结构体inode
3. 目录项:文件系统把目录看作文件的一种类型,结构体dentry

# 二、常见操作
### 1. 挂载 mount
内存中目录被组织为一棵树,只有挂载到内存中目录树的一个目录下,进程才能访问这个文件系统
glibc封装的挂载文件系统的函数mount
```c
int mount(const char *dev_name, const char *dir_name,
const char *type, unsigned long flags,
const void *data);
```


共享子树(Shared Subtrees)是Linux文件系统命名空间特性的一部分,它允许对挂载点进行更复杂的管理与控制
1. 共享挂载(shared mount):当一个挂载点被设置为共享挂载时,任何在该挂载点上执行的挂载或卸载操作都会传播到所有与之共享状态的其他挂载点
2. 从属挂载(slave mount):从属挂载是一种单向的共享关系,允许创建一种父子关系,在这种关系中,父挂载点的变化会影响到子挂载点,但反之则不然
3. 私有挂载(private mount):一个私有挂载点不会接收也不会传播任何挂载或卸载事件
4. 不可绑定挂载(unbindable mount):无法重新挂载它

### 2. 打开 open
进程读写文件之前需要先打开文件,得到文件描述符fd,通过fd读写文件
内核的两个打开文件的系统调用:
```c
int open(const char *pathname, int flags, mode_t mode);
int openat(int dirfd, const char *pathname, int flags, mode_t mode);
```
pathname是文件路径
flags是文件访问模式:O_RDONLY(只读)、O_WRONLY(只写)或O_RDWR(读写)
mode指定创建新文件时的文件模式
##### 文件状态标志
| 标志 | 描述 |
| ---------- | -------------------------------------------------------- |
| O_APPEND | 使用追加模式打开文件 |
| O_ASYNC | 启用信号驱动的输入/输出 |
| O_DIRECT | 直接读写存储设备 |
| O_DSYNC | 调read,把数据写回到存储设备 |
| O_NOATIME | 不要更新文件的访问时间 |
| O_NONBLOCK | 使用非阻塞模式打开文件 |
| O_PATH | 不会真正打开文件,不能执行读操作和写操作,获得文件描述符 |
| O_SYNC | 调write,把数据写回到存储设备 |
##### 标准的文件模式位
| 模式 | 权限 | 描述 |
| -------------- | ---- | ---------------------------------------------- |
| S_IRWXU(00700) | 用户 | 用户有读、写和执行权限 |
| S_IRUSR(00400) | 用户 | 用户有读权限 |
| S_IWUSR(00200) | 用户 | 用户有写权限 |
| S_IXUSR(00100) | 用户 | 用户有执行权限 |
| S_IRWXG(00070) | 组 | 文件拥有者所在组的其他用户有读、写和执行权限 |
| S_IRGRP(00040) | 组 | 文件拥有者所在组的其他用户有读权限 |
| S_IWGRP(00020) | 组 | 文件拥有者所在组的其他用户有写权限 |
| S_IXGRP(00010) | 组 | 文件拥有者所在组的其他用户有执行权限 |
| S_IRWXO(00007) | 其他 | 其他组的用户有读、写和执行权限 |
| S_IROTH(00004) | 其他 | 其他组的用户有读权限 |
| S_IWOTH(00002) | 其他 | 其他组的用户有写权限 |
| S_IXOTH(00001) | 其他 | 其他组的用户有执行权限 |
##### Linux 私有的文件模式位
| 模式 | 描述 |
| ---------------- | ------------------ |
| S_ISUID(0004000) | set-user-ID位 |
| S_ISGID(0002000) | set-group-ID位 |
| S_ISVTX(0001000) | 粘滞位 |
### 3. 关闭 close
进程可以使用系统调用close关闭文件,进程退出时,内核将会把进程打开的所有文件关闭
```c
int close(int fd);
```

由于篇幅限制,对接下的每个文件操作api就不一一介绍了
# 结束语
两个多月的书籍阅读,收获颇丰,对于linux内核学习到了很多的知识,这本书也用图示的方式讲的很详细
对于linux内核的相关内容,还将不断地学习,希望未来论坛内还有更多linux内核相关的好书分享
- 2025-02-09
-
发表了主题帖:
《Linux内核深度解析》-内核互斥技术
本帖最后由 rtyu789 于 2025-2-9 23:44 编辑
# 内核互斥技术
本章节主要介绍了这几个内容
1. 内核互斥技术
2. 内存屏障
3. RCU
4. 死锁检测工具lockdep
这章图片比较少,主要通过文字性内容来说明
# 一、内核互斥技术
1. 临界区执行时间较长
1. 信号量
2. 读写信号量
3. 互斥锁
4. 实时互斥锁
2. 临界区执行时间短
1. 原子变量
2. 自旋锁
3. 读写自旋锁
4. 顺序锁
### 1. 信号量
```C
struct semaphore {
raw_spinlock_t lock;
unsigned int count;
struct list_head wait_list;
};
```
lock,是一个自旋锁,用于确保在多处理器系统中对信号量的操作是原子性的,从而避免竞态条件,保护信号量的其他成员
count,该成员表示信号量的计数值,表示多少个进程可以进入临界区
wait_list,双向链表的头节点,表示等待进入临界区的进程列表
### 2. 读写信号量
读写信号量是对互斥信号量的改进,旨在允许多个线程同时读取共享资源,但在写入时只允许一个线程进行操作
这种机制特别适用于读操作远多于写操作的场景,因为它可以显著提高并发性能
```C
struct rw_semaphore {
atomic_long_t count;
struct list_head wait_list;
raw_spinlock_t wait_lock;
struct task_struct *owner;
struct lockdep_map dep_map;
};
```
### 3. 互斥锁
互斥锁确保在任何时刻只有一个线程可以访问该资源,保护比较长的临界区
```C
struct mutex {
atomic_long_t owner;
spinlock_t wait_lock;
#ifdef CONFIG_MUTEX_SPIN_ON_OWNER
struct optimistic_spin_queue osq; /* Spinner MCS lock */
#endif
struct list_head wait_list;
};
```
### 4. 实时互斥锁
实时互斥锁是专门为实时系统设计的一种特殊类型的互斥锁,是对互斥锁的改进,旨在满足实时系统的严格时间约束和优先级继承的需求,解决了优先级反转问题
```C
struct rt_mutex {
raw_spinlock_t wait_lock;
struct rb_root waiters;
struct rb_node *waiters_leftmost;
struct task_struct *owner;
};
```
# 二、内存屏障
内存屏障(memory barrier)是一种保证内存访问顺序的方法,用来解决下面这些内存访问乱序问题:
1. 编译器编译代码时可能重新排列汇编指令
2. 现代的处理器采用超标量体系结构和乱序执行技术
3. 在多处理器系统中,处理器之间的内存访问乱序问题
内核支持3种内存屏障:
1. 编译器屏障
2. 处理器内存屏障
3. 内存映射I/O写屏障

# 三、RCU
RCU(Read-Copy Update)的意思是读-复制更新,主要用于在高读低写场景下提供高效的并发控制
它允许读操作在不加锁的情况下进行,极大地提高了读性能其基本思想是,在修改数据时,并不是直接对原有数据进行修改,而是创建一份原有数据的副本,在副本上进行修改修改完成后,将指向原数据的指针重新指向新的副本,同时保留旧数据直到所有正在进行的读操作完成
RCU 根据数据结构可以分为以下两种
1. 树型 RCU(tree RCU):也称为基于树的分层RCU,为拥有几百个或几千个处理器的大型系统设计
2. 微型 RCU(tiny RCU):为不需要实时响应的单处理器系统设计
为了解决性能问题,基于树的分层RCU采用了类似淘汰赛的原理,主要为四个阶段:
1. 处理器分组
2. 经历静止状态
3. 自旋锁竞争
4. 递归向上
这种机制通过减少直接锁竞争和利用局部性原理,使得即使在大规模并行环境中也能高效地管理和协调读-复制更新操作这样不仅提高了系统的整体性能,也增强了扩展能力

# 四、死锁检测工具lockdep
lockdep通过追踪锁之间的依赖关系来检测可能发生的死锁它记录所有锁的获取顺序,并检查是否存在循环依赖,即一种可能导致死锁的情形lockdep在内核运行时进行这些检测,无需修改代码逻辑,使得它可以用来分析现有的复杂系统而不需要额外的测试用例设计
死锁检测工具lockdep的配置宏主要如下:
| 配置宏 | 描述 |
|-------------------------------|------------------------------------------------------------|
| CONFIG_LOCKDEP | 死锁检测工具的核心配置在配置菜单中不可见,但会在特定条件下自动启用 |
| CONFIG_PROVE_LOCKING | 启用内核报告死锁问题的功能允许内核识别并报告潜在的死锁情况 |
| CONFIG_DEBUG_LOCK_ALLOC | 检查内核是否错误释放已被持有的锁有助于发现不正确的锁操作 |
| CONFIG_DEBUG_LOCKING_API_SELFTESTS | 在内核初始化过程中运行自我测试程序,以检查调试机制能否发现常见的锁缺陷 |

- 2025-02-08
-
回复了主题帖:
《Linux内核深度解析》-内存管理
oxlm_1 发表于 2025-2-6 17:02
Slab分配器的基础是啥?自己单独管理内存,还是基于伙伴分配器给的内存
slab分配器的基础是基于伙伴系统管理的内存哦,伙伴分配器提供了大块的内存,slab分成更小的对象进行管理
-
发表了主题帖:
《Linux内核深度解析》-中断异常和系统调用
# 中断异常和系统调用
本章节主要介绍了这几个内容
1. ARM64异常处理
2. 中断控制器
3. 软终端
4. 系统调用
# 一、ARM64异常处理
ARM64架构定义了四个异常处理级别EL,用于管理不同权限级别的代码执行和异常处理这些级别从EL0到EL3,数字越大,权限越高
1. EL0,用户模式(User Mode):EL0是应用程序运行的环境,通常用于运行用户空间的代码
2. EL1,操作系统内核模式(Kernel Mode):EL1是操作系统内核运行的环境,负责管理系统的核心资源
3. EL2,虚拟机监控模式(Hypervisor Mode):EL2用于运行虚拟机监控程序,负责管理虚拟机的创建、调度和资源分配
4. EL3,安全监控模式(Secure Monitor Mode):EL3是ARM64架构中权限最高的级别,通常用于安全监控和可信执行环境(TEE)

在ARM64架构中,异常分为同步异常和异步异常两大类
1)常见的同步异常类型:
- 系统调用异常:当执行SVC、HVC或SMC指令时触发,用于从低权限级别(如EL0)切换到高权限级别(如EL1、EL2或EL3)
- 数据终止
- 指令终止
- 内存访问异常
- 地址对齐错误
- 权限错误
- 页错误
- 未定义指令异常
- 调试异常
2)常见的异步异常类型:
- 中断 IRQ:通常用于处理外设的输入/输出请求
- 快速中断 FIQ:用于处理高优先级的中断请求,具有更低的延迟
- SError(系统错误):由系统总线或内存子系统中的错误触发,例如内存访问超时或数据校验错误
# 二、中断控制器
在ARM64架构中,中断分为四种主要类型:软件生成中断(SGI)、私有外设中断(PPI)、共享外设中断(SPI)和局部特定外设中断(LPI)
这些中断类型由ARM的通用中断控制器(GIC)管理,用于处理不同来源的中断请求
1. 软件生成中断:SGI是由软件显式触发的中断,通常用于多核处理器中核间通信(Inter-Processor Communication, IPC)
2. 私有外设中断:PPI是每个CPU核心私有的中断,通常与特定核心的外设相关
3. 共享外设中断:SPI是由系统中的外设触发的中断,可以由任意CPU核心处理
4. 局部特定外设中断:LPI是一种基于消息的中断,通常用于支持高级外设(如PCIe设备)的中断请求
### 中断类型对比
| 类型 | 中断号范围 | 特点 | 常见用途 |
| ---- | ---------- | ---------------------------------------------- | --------------------- |
| SGI | 0-15 | 软件生成,用于核间通信 | 核间唤醒、任务调度 |
| PPI | 16-31 | 每个CPU核心私有,与核心紧密相关 | 本地定时器、性能监控 |
| SPI | 32-1019 | 共享外设中断,可由任意CPU核心处理 | 网络接口、存储控制器 |
| LPI | 8192及以上 | 基于消息的中断,支持大量中断号,适合高性能外设 | PCIe设备、GPU、加速器 |

# 三、软中断
软中断是中断处理程序在开启中断情况下执行的部分,可以被硬终端抢占
在Linux内核中,小任务(tasklet)是一种基于软中断实现的机制,用于在中断上下文中延迟执行某些任务他是基于软中断实现,Tasklet的执行时机由内核调度决定,通常会在中断处理完成后尽快执行
常见的Tasklet编程接口
### 1. 定义Tasklet
用DECLARE_TASKLET宏可以静态定义一个Tasklet
```c
DECLARE_TASKLET(tasklet_name, tasklet_function, tasklet_data);
```
### 2. 动态初始化Tasklet
如果需要在运行时动态初始化Tasklet,可以使用tasklet_init函数
```c
void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data);
```
### 3. 调度Tasklet
使用tasklet_schedule函数将Tasklet调度到软中断队列中,等待执行
```c
void tasklet_schedule(struct tasklet_struct *t);
```
### 4. 禁用和启用Tasklet
禁用Tasklet,使其不会被调度执行
```c
void tasklet_disable(struct tasklet_struct *t);
```
启用Tasklet,允许其被调度执行
```c
void tasklet_enable(struct tasklet_struct *t);
```
### 5. 终止Tasklet
使用tasklet_kill函数杀死一个Tasklet,确保其不会再次被调度
```c
void tasklet_kill(struct tasklet_struct *t);
```

# 四、系统调用
系统调用是内核给用户程序提供的编程接口,通常使用glibc库针对单个系统调用封装成函数
在Linux内核中,系统调用的定义和实现通常使用SYSCALL_DEFINE宏来简化代码编写
例如:
- open():封装sys_open
- read():封装sys_read
- write():封装sys_write
#### SYSCALL_DEFINE宏的格式
在内核中很常见这样的定义,需要熟悉一下
```c
SYSCALL_DEFINE(name, arg1_type, arg1_name, arg2_type, arg2_name, ...)
```
- N:系统调用的参数个数
- name:系统调用的名称
- argX_type:第X个参数的类型
- argX_name:第X个参数的名称
- 2025-01-25
-
发表了主题帖:
《Linux内核深度解析》-内存管理
# 内存管理
### 介绍
本章节主要介绍了这几个内容
1. 虚拟地址的空间布局
2. 内存映射
3. 伙伴分配器
4. 块分配

### 一、虚拟地址的空间布局
用户态虚拟地址空间是进程运行的基础,其布局包括代码段、数据段、堆、内存映射区域和栈等部分。不同的架构在地址范围和布局上有所差异,但整体结构相似。如果我们能理解理解虚拟地址空间的布局对于未来的调试和优化程序非常有帮助。

#### 1.1 用户态虚拟地址空间布局
用户态虚拟地址空间典型的布局:
(1) 代码段(Text Segment):存储进程的可执行代码(即程序的指令)
(2) 数据段(Data Segment):存储全局变量和静态变量
(3) 堆(Heap):动态分配的内存(如 `malloc`、`calloc` 等函数分配的内存)
(4) 内存映射区域(Memory Mapping Segment):文件映射(如 `mmap` 映射的文件)和匿名映射(如共享内存)
(5) 栈(Stack):存储局部变量、函数调用栈帧等
(6) 内核空间:内核代码和数据

#### 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:将物理页帧映射到用户空间,通常用于设备驱动

其中mmap是比较重要的函数,书中列举了mmap最终调用的do_mmap的执行流程

### 三、伙伴分配器
在内核中,物理内存的管理需要满足以下需求:
- 支持不同大小的内存分配请求。
- 减少内存碎片。
- 快速分配和释放内存。
伙伴分配器通过将内存划分为大小固定的块,并使用“伙伴系统”来合并和拆分内存块,从而满足这些需求。
### 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:对象是实际分配的内存单元,大小固定,用于存储内核数据结构。

#### 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系列

去芯源半导体的官网查看了下相关内容,他们也有自己的烧录器

看了下在消费电子领域应用的也比较广泛的

还有一些对开发者的扶持计划

国产芯片赶紧还是挺不错的投入,也有挺大的投入力度,扶持国内的生态,希望国产芯加油呀
# 参考资料
[芯源半导体](https://www.whxy.com/)
- 2025-01-19
-
回复了主题帖:
【测评入围名单(最后1批)】年终回炉:FPGA、AI、高性能MCU、书籍等65个测品邀你来~
个人信息确认无误,可以完成计划。
- 2025-01-13
-
回复了主题帖:
《Linux内核深度解析》-进程管理
heleijunjie72 发表于 2025-1-12 17:51
进程管理,是很很硬核的资料,很值得学习,收藏了
加油加油,一起努力