os lock
占位
占位
1 | Alice 正在她的电脑上输入一个字符串。但是她打字技术比较笨拙,她 可能 在一个按键上按太久,导致一个字符被输入 多次 。 |
把一个复杂问题拆成多个子问题,先解决子问题,保存结果,再从小到大解决整个问题。
当你的问题需要处理一段区间,比如字符串的一部分,数组的一段,那么通常使用区间动态规划。Interval DP。
属于DP模型之一。
| 类型 | 特点 |
|---|---|
| 0-1 背包 | 每个物品只能选一次 |
| 完全背包 | 每个物品可以选无数次 |
| 多重背包 | 每个物品最多只能选 cnt 次 |
1 | s[i] = f[0] + f[1] + ... + f[i]; |
如果你想枚举从上一个阶段状态转移过来,比如,当前字符段长度为cnt,当前字符串要变成长度j,你只能从长度在 [j-cnt,j-1] 的状态转移过来。
传统做法是
1 | for (int t = 1; t <= cnt; ++t) { |
优化做法是
1 | f_new[j] = g[j - 1] - g[j - cnt - 1]; // 差分区间快速求和 |
在linux内核中,密码相关的头文件在 include crypto下,相关概念大致有加密 块加密 异步块加密 哈希 分组加密模式等等。
算法:
AEAD算法:一种带有认证功能的加密方式。拆分为认证和加密两部分。常见的有GCM和CCM,
对称加密算法: 使用同一个密钥进行加密和解密。这意味着加密方和解密方必须事先共享同一个密钥,并且保证这个密钥的安全。
AES:AES-128 AES-256等,后个字段对应密钥长度。密钥越长,安全性能越高,加密时间越长。
DES,3DES等。
非对称加密算法: 使用一对密钥,一个公开密钥(公钥)用于加密,一个私有密钥(私钥)用于解密。公钥可以公开分享,而私钥必须保持私密。
模式:
实现不同的算法有几种模式:
主要有ECB CBC CFB OFB 和 CTR等这几种。
不同模式的区分如下:
ECB模式: ECB是最简单的块密码加密模式,加密前根据加密块大小(如AES为128位)分成若干块,之后将每块使用相同的密钥单独加密,解密同理。
CBC模式: CBC模式对于每个待加密的密码块在加密前会先与前一个密码块的密文异或然后再用加密器加密。第一个明文块与一个叫初始化向量的数据块异或。
CFB模式: 与ECB和CBC模式只能够加密块数据不同,CFB能够将块密文(Block Cipher)转换为流密文(Stream Cipher)。
OFB模式: OFB是先用块加密器生成密钥流(Keystream),然后再将密钥流与明文流异或得到密文流,解密是先用块加密器生成密钥流,再将密钥流与密文流异或得到明文,由于异或操作的对称性所以加密和解密的流程是完全一样的。
CTR模式: CTR模式是一种通过将逐次累加的计数器进行加密生成密钥流的流密码。
以aes加密算法为例。

所有的加密算法名都是xxx_alg的方式。关键成员有算法名 驱动名 算法类型 同步,异步,分组大小,上下文。加解密函数等等。
在alg结构中,块加密和普通分组加密的区别就是.cra的设置。普通分组加密指定的是cipher.同步块加密指定的是blkcipher. 异步块加密指定的是ablkcipher。
ctx:上下文。指的是算法执行过程中所要贯穿始终的数据结构。由每个算法自己定义。set_key encrypt decrypt这几个函数都可以从参数获得算法上下文的指针。算法上下文所占的内存空间由密码管理器来分配。注册alg的时候指定ctx大小和对其即可。ctx对齐对于一些硬件加密等。ctx的首地址可能需要在内存中4字节或者16字节对齐。
device mapper是linux 2.6内核中支持的逻辑卷管理的通用设备映射机制。为实现用于才能出资源管理的块设备驱动提供了一个高度模块化的内核架构。

在内核中它通过一个一个模块化的target driver实现对IO请求的过滤或者重定向。包括软raid,软加密,多路径,镜像,快照等等。device mapper用户空间相关部分主要负责配置具体的策略和控制逻辑。比如逻辑设备和哪些物理设备映射。怎么建立这些映射关系等等。而具体的过滤和重定向IO请求的工作由内核相关代码完成。整个device mapper机制由两部分组成 内核空间的device mapper驱动,用户空间的device mapper库以及他提供的dmsetup工具。
device mapper的内核相关代码在driver/md目录中。device mapper在内核中作为一个块设备驱动被注册的,包含三个重要的对象概念,mapped device,映射表,target device。
mapped device是一个逻辑抽象,是内核向外提供的逻辑设备。通过映射表描述的映射关系和target device建立映射。从mapped device 到一个 target device的映射表由一个多元组表示,该多元组由表示mappdevice逻辑的起始地址,范围,和表示在target device所在的物理设备的地址偏移量以及target类型等变量组成。以磁盘扇区为单位,512字节大小。
target device表示的是mapped device所映射的物理空间段。device mapper中这三个对象和target driver一起构成了可迭代的设备树 。
Device mapper在用户空间包括device mapper库和dmsetup工具,device mapper库就是对ioctl 用户空间创建删除devicemapper逻辑设备所需必要操作的封装。dmsetup工具是一个应用层直接操作device mapper设备的命令行工具。大致包含,发现每个mapper device相关的target device,根据配置信息创建映射表,将用户空间构建好的映射表传入内核,让内核构建mapper device对应的dm table结构。
LUKS (linux unified key setup),linux统一密钥设置。是一种高性能安全的磁盘加密方法。基于cryptsetup。使用dm-crypt作为磁盘加密后端。
定义了如何安全存储加密密钥和加密元数据。支持多重密码。最多8个key slot。不需要重加密就能够更换密码。配合dm-crypt使用。实际加密使用的是内核支持的加密算法,LUKS只负责配置,封装和密钥管理。

任何文件系统都可以加密,包括交换分区,加密卷的开头有一个未加密的头部。允许存储多达8个lusk1或者32个lusk2加密密钥,以及诸如密码类型和密钥大小之类的加密参数。
dm crypt,内核提供的磁盘加密功能。即device mapper crypto。lucks通过dmcrypt模块使用内核设备映射器子系统。负责处理设备的加密和解密。
命令行的前端,通过它来操作 dm crypt。创建和访问加密设备。
分成User Space layer 和 kernel Space layer。
在kernel space密码学算法上。主要分成软件以及硬件运算。
软件运算。主要由CPU进行密码学算法运算,不需要额外硬件,但很费CPU性能。linux kernel 原始码位于crypto subsystem下。
硬件加速。由硬件辅助进行密码学运算,不需要耗费cpu性能,但需要额外硬件。
SoC Component–许多ARM SoC厂商都会将硬件加解密元件放入SoC中,Linux Kernel原始码多位于drivers/crypto底下.且设计必须遵照Linux crypto framework,不能私下修改。
主要的功能是提供界面。让user space可存取kernel space.目前主流为cryptodev以及af_alg
不在linux kernel中自带。开源模块。需要单独移植,并挂载kernel module。
openssl支持cryptodev。通过操作cryptdev节点来操作加密
openssl从1.1开始支持af_alg。
常见的有openssl,wolfssl。
openssl提供af alg以及cryptdev的engine,可以透过engine来存取crypto api。
介绍由应用层所发出的crypto(cryptography)request,透过system call将request传送到Linux kernel端,并经由crypto subsystem将request转发给硬件算法引擎(hardware crypto engine)的流程。
Crypto subsystem是Linux系统中负责处理crypto request的子系统,除了包含流程控制机制之外,另一个重要特色就是提供算法实作的抽象层,让各家厂商能够依据需求去客制化实作方式。
其中一个常见例子就是厂商在硬件构架中加入用以加速特定算法运算效率的硬件算法引擎,并且透过crypto subsystem将驱动硬件算法引擎的流程整合进Linux系统中,供其他kernel module或是应用层使用。

在Linux系统中,想要实现应用层与硬件装置的沟通,第一个想到的就是透过character/block device driver,让应用程序开启表示此硬件装置的抽象层,并且藉由读写行为与硬件装置进行互动。
而Cryptodev-linux就是负责此角色,它提供中间层的服务,接收由应用层传送过来的crypto request,再呼叫Linux kernel crypto Subsystem的crypto API将request转发给特定的硬件算法引擎。
Cryptodev-linux为misc device类型的kernel module,预设路径是/dev/crypto,使用ioctl file operation cryptodev_ioctl来接受应用端所传递过来的数据。

应用端则是使用cryptdev.h定义好的struct crypt_op或是struct crypt_auth_op来组成指定crypto request,并呼叫ioctl system call将request送给Cryptodev-linux。
simple of cryptdev linux ioctl

另外,Cryptodev-linux也提供session机制,每个crypto request对应到一个session,而session管理当前crypto request的状态。
例如,目前session在initialized的状态,则表示此crypto request可执行encrypt,透过此方式来确保crypto request会在正确的流程下运作。

首先 crypto subsystem有两个重要元素,transformation object(tfm),被称作cipher handle,transformation implementation,是transformation object底层的实作内容,又被称为crypto algo。就是crypto engine算法实作。
之所以要区分成object和implementation,最主要的原因是有可能多个object会使用同一个implementation。
举例来说,A和B使用者都要使用hmac-sha256算法,因此会新建立A和B两个transformation object并包含A和B各自拥有的key值,但这两个object有可能会使用同一个transformation implementation来呼叫同一个crypto engine进行算法运算。

当有crypto request进来,会先根据request中指定的算法名称,从已注册的crypto algorithm list中取出适合的crypto algorithm,并新建立transformation object。
之后,transformation object会再被组成crypto subsystem所用的cipher request。cipher request有可能共享同一个transformation object,举例来说,hmac-sha256的transformation object包含了transformation implementation和一个key值,而这个transformation object可以使用在多个cipher request的messsage上进行hash算法。当cipher request完成相关设值之后,接着实际调用transformation object的transformation implementation执行算法运算。
此时会出现一个问题,就是当短时间有多个request进来时,我们该如何依序地处理request?
这点crypto subsystem也设计了方便的struct crypto_engine,crypto engine提供了queue管理机制,让多个request能够顺序地转发给对应的crypto engine。
要新增transformation implementation到crypto subsystem,最重要的就是注册transformation implementation到crypto algorithm list中。

使用crypto_register_skciphers即可完成注册。
另外,cra_priority代表各算法的优先程度,优先级高的会先被采用。
在crypto subsystem中,crypto API分成asynchronous(异步)和synchronous(同步)两种机制。
最早版本的crypto API其实只有synchronous crypto API,但随着要处理的数据量增加,运算和数据传输时间也可能大幅拉长,此时synchronous crypto API有可能让处理流程陷入较长时间的等待,因此后来引入了asynchronous crypto API,供使用者依据自己的使用场景来选择适合的机制。
而asynchronous与synchronous crypto API在命名设计上有所区别,asynchronous会在前缀多加一个a字,反之synchronous则是s字,以hash为例:

synchronous api

只要顺序的呼叫对应流程的API,并且针对返回的结果进行error handling即可。
在API的呼叫流程中,crypto_shash_update可以被多次呼叫,让使用者放入多组需要进行加密的message。当完成一组message后,可能有些中间状态是需要被保存起来的。这些状态就会存在state handler中。在使用者呼叫api前,会需要自己分配一块足够大小的内存,让crypto engine能够存放这些状态。在transformation implementation中会设定好crypto engine需所需的状态存储空间大小,使用者只需要呼叫特定API即可。

除了命名之外,由于两种机制的处理流程不同,因此所需的参数也会有所不同。
async request

包含一个callback function crypto completion,当运算完成后,会透过此callback来通知使用者继续处理完成的流程。由于asynchronous非同步机制,因此crypto engine在处理request时,行为和流程也和synchronous同步机制有蛮大的差异,其中常见的实作方式加入request queue来管理多个request,当使用者呼叫update API发送request时,则会将request加入到queue中,并直接回传处理中(-EINPROGRESS)的状态信息。
如果使用者使用asynchronous hash API,但是实际上对应的transformation implementation却是synchronous型态,crypto subsystem会主动进行相关的数据转换,因此也是可以正常运作的。
一般常见的方式是 crypto queue搭配worker,额外开一个kernel thread来与crypto engine进行沟通,让crypto request按照FIFO的顺序处理。

建立一个全局的crypto request list,将进来的request依序排到list中,建立一个worker(kernel thread)和对应的work queue来与hardware crypto engine进行沟通,worker的任务除了从crypto request list中取出request处理h之后,也可能会包含crypto engine的初始化和资源释放等工作。注册interrupt handler,当status interrupt举起时,呼叫user自定义的completion callback function来完成最后的流程。
crypto/engine.h中有提供接口可以直接调用。

1 | SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol) |
1 | 同步阻塞的开销主要有: |
1 | epoll_create |
1 | 本质上是 极大程序的减少了无用的进程上下文切换,让进程更专注处理网络请求。 |
1 | epoll也是阻塞的。没有请求处理的时候,也会让出CPU。阻塞不会导致低性能。过多的阻塞才会。W |
阻塞指的是进程因为等待某个事件而主动让出CPU挂起的操作。在网络IO中。当进程等待socket上的数据时,如果数据还没有到来,那就把当前状态从TASK_RUNNING修改为TASK_INTERRUPTIBLE。然后主动让出CPU,由调度器来调度下一个就绪状态的进程来执行。
所以,以后分析是否阻塞就看是否让出CPU。
用户进程在用户态通过send系统调用接口进入内核态,send通过内存拷贝skb,进程协议处理进入驱动ring buffer,通过pci总线发送后,产生中断通知发送完成,并清理ring buffer。
1 | send |
数据发送完毕后,释放缓存 队列等内存。在网卡发送完毕后,给CPU发送一个硬中断来通知cpu。实际是触发了 NET_RX_SOFTIRQ 。所以服务器中proc softirqs里面NET_TX要比RX大很多。
网卡启动最重要的任务之一就是分配和初始化RingBuffer。在对应的驱动程序的open函数中,对于ringbuffer进行分配。
1 | igb_setup_all_tx_resources |
为什么要用环形队列?好处是什么
| 特性/对比点 | 环形队列(Ring Buffer) | 普通 FIFO 队列(如链表) |
|---|---|---|
| 内存分配方式 | 预分配,连续内存块 | 动态分配,每个节点独立分配 |
| 缓存命中率 | 高(连续内存 + 局部性好) | 低(指针跳转 + 内存分散) |
| 指针操作复杂度 | 简单,仅需要 head 和 tail | 操作复杂,涉及节点申请/释放 |
| 支持无锁操作 | 易于 lockless 实现,尤其单生产者/单消费者模型 | 难,容易涉及竞态和锁 |
| 空间使用效率 | 高,数组固定大小,空间紧凑 | 较低,节点指针额外开销 |
| 硬件 DMA 支持 | 很多硬件直接支持环形 DMA 描述符结构 | 不适用于 DMA 映射 |
| 数据结构大小固定性 | 是,数组固定大小,易于调优和估算 | 否,链表大小动态变化,管理麻烦 |
| 实现难度 | 结构简单,逻辑清晰 | 相对复杂,需要考虑链表指针等各种异常处理 |

sendto中 构造 找到sock,构造msg后 通过 __sock_sendmsg 发送,到sock_sendmsg_nosec中,通过 inet6_sendmsg 调用进入协议栈。
在进入协议栈后,会找到具体的发送函数。对tcp来说是 tcp_sendmsg –> tcp_sendmsg_locked。 tcp_write_queue_tail获取发送队列的最后一个skb。把用户内存里的数据拷贝到内核态,涉及到1次/几次内存拷贝的开销。
1 | if (forced_push(tp)) { |
调用tcp_push_one / __tcp_push_pending_frames 将数据包发送出去。
最终会调用到。
1 | /* Send _single_ skb sitting at the send head. This function requires |
tcp_write_xmit内部处理了传输层的拥塞控制,滑动窗口相关的工作,满足窗口要求的时候,设置TCP头然后将skb传到更低的网络层进行处理。
tcp_transmit_skb开启真正的发送函数。clone新的tcp出来,封装tcp的头.
1 | /* Build TCP header and checksum it. */ |
为什么要进行clone? tcp支持超时重传, 在收到对方的ACK之前,这个skb’不能被删除.等收到ACK后再次删除.
tcp_options_write中对TCP头进行设置, skb中包含了网络协议中的所有头,在设置TCP的头的时候,只需要把指针指向skb的合适位置,后面设置IP头的时候,指针在挪一挪即可.避免频繁的内存申请拷贝.
1 | queue_xmit 在 ipv4中 实际值得是. |
位于网络层和数据链路层中间的一个系统, 为网络层提供一个下层的封装, 让网络层不必关心下层的地址信息, 让下层决定发送到哪个MAC地址. 主要查找/创建邻居项,在创建邻居项的时候,可能会发出实际的ARP请求,然后封装MAC头,将发送过程传递到更下层的网络设备子系统.
调用 neigh_resolve_output 发出,有可能触发arp请求.
最后调用 dev_queue_xmit 将skb传递给Linux网络设备子系统
__dev_queue_xmit()
└── __dev_queue_xmit_xmit()
└── dev_hard_start_xmit()
└── xmit_one()
└── dev->netdev_ops->ndo_start_xmit()
1 | // 1.分配 sk_buff,复制用户数据 |
1 | 1. 软中断 |
参考资料
https://blog.csdn.net/weixin_44260459/article/details/121480230
hwmon,hardware monitor是linux内核中的一个子系统,用于提供对硬件设备,如温度传感器,电压监控器,风扇速度等的监控支持。该子系统允许用户通过标准的接口读取和控制硬件状态。尤其是温度,电压,风扇转速等。提供了统一的方式访问硬件监测信息。
在hwmon子系统中,主要即借助设备驱动模型中的设备的注册以及设备属性的创建,即可针对于一个硬件芯片创建多个属性文件。

hwmon子系统主要提供了接口hwmon_attr_show、hwmon_attr_store,同时抽象了针对温度芯片、风扇芯片、电源芯片等硬件监控芯片相关参数的支持,即温度芯片、风扇芯片、电源芯片等硬件监控芯片相关参数的访问接口,均会在接口hwmon_attr_show、hwmon_attr_store中被统一调用,而温度芯片、风扇芯片、电源芯片等硬件监控芯片只需要实现hwmon_ops类型函数指针即可。调用关系如下所示,其中,HWMON子系统层则为hwmon子层抽象的部分,而最下层则由具体的hwmon 设备驱动实现即可。

1 | devm_hwmon_device_register_with_groups(dev, "my_hwmon", NULL, my_temp_attr); |
1 | sensor_device_attribute |
sys/class/hwmon下暴露。
1 | cat /sys/class/hwmon/hwmon0/temp1_input # 获取温度 |
1 | void hwmon_device_unregister(struct device *dev); |
1 | git commit --amend |
1 | git reset --soft HEAD~1 |
1 | git reset --mixed HEAD~1 |
1 | git reset --hard HEAD~1 |
将两个分支合并到一起,并生成一个新的commit记录,新生成的commit节点会有两个父节点。
在 master 分支上,使用 git merge bugFix 将 bugFix 分支合并到 master 分支。
merge 解决冲突时内容是基于两个分支的所有的 commit.
rebase也可以合并分支,会取出一系列的commit记录,然后在目标分支逐个放下去,rebase可以保持线性的提交历史,使历史更加清晰。
在bugfix上,使用git rebase master分支,会将bugfix分支上的commit记录复制到master分支,并保证提交历史是线性的。
rebase除了在合并时使用,还可以用来整理commit记录。
正如它的中文名字“变基操作”一样,会将所在分支新添加的内容,增加到目的分支,并保证了 commit 提交记录的串行性。简单来说就是,会以目的分支(一般是 master)为基础,逐一的将当前分支的 commit 记录应用。需要注意的是,在应用时并不是直接应用在 master 分支,而是将 master 分支整体拷贝,然后将当前 commit 应用在拷贝后的分支上。
cherry pick 是将一些commit复制到当前分支的HEAD上,和rebase相比,更加灵活,可以随意的选择commit进行复制。
通过 git cherry-pick c3 c4 c7 将其他分支上的 3 个 commit 复制到当前的 master.
rebase 操作正好相反,会以当前的分支为基础,然后将 commit 一个个的拿过来应用。形成的 commit 记录也是串行的。
本文章介绍linux下应用层访问phy寄存器的几种方式 便于开发者开发
一些厂家会直接提供类似/dev/mdio类似的节点,可以find -name 搜索一下看看。通过操作节点可以直接操作phy寄存器
uboot下可以通过mii cmd来实现读写phy寄存器
1 | /* 成功时返回文件描述符,失败时返回-1 |

常用:
SOCK_STREAM:
流,TCP。面向连接。
SOCK_DGRAM
面向消息的,不可靠的。
1 | LONG skfd = -1; |
1 | // 内核通用框架 |
当内存发生panic的时候,需要把panic的内容以日志的方式记录下来。目前有几种方式,kdump,mtdoops,crashlog(openwrt特有),以及pstore。
kdump主要用在x86系统上,因为它使用大量的内存和硬盘信息。
mtdoops和crashlog主要用于嵌入式系统。记录文本日志。
mtdoop功能在发生oops时,把msg区写入特定的mt分区。写入过程,不支持文件系统。直接二进制文本写。 它需要由mtd驱动的支持,就是mtd驱动支持mtd_panic_write。也就是原子式写入。不能被中断。一般flash不支持。 在mtdoops.c文件,并在标准内核的drivers/mtd/mtdcore中。
1 | mtd_panic_write |
在初始化过程中,需要指定写入哪个分区,对应的分区名/号,可以写入的size是多少。使用kmsg_dump_register注册一个cxt->dump.dump绑定的回调函数 (eg:mmcoops_do_dump),此函数主要是读取kmsg区的内容,直接调用mtd->write的驱动进行写操作。
在linux内核启动的时候,保留一块64K的内存,用于记录panic日志,crashlog发生在oop时候,把msg写入之前分配好的MEM区域,并加上magic,再重启后,check magic ok,则把上次日志放入到/sys/kernel/debug/crashlog。
pstore最初是用于系统发生oops或panic时,自动保存内核log buffer中的日志。不过在当前内核版本中,其已经支持了更多的功能,如保存console日志、ftrace消息和用户空间日志。同时,它还支持将这些消息保存在不同的存储设备中,如内存、块设备或mtd设备。 为了提高灵活性和可扩展性,pstore将以上功能分别抽象为前端和后端,其中像dmesg、console等为pstore提供数据的模块称为前端,而内存设备、块设备等用于存储数据的模块称为后端,pstore core则分别为它们提供相关的注册接口。
通过模块化的设计,实现了前端和后端的解耦,因此若某些模块需要利用pstore保存信息,就可以方便地向pstore添加新的前端。而若需要将pstore数据保存到新的存储设备上,也可以通过向其添加后端设备的方式完成。

除此之外,pstore还设计了一套pstore文件系统,用于查询和操作上一次重启时已经保存的pstore数据。当该文件系统被挂载时,保存在backend中的数据将被读取到pstore fs中,并以文件的形式显示。
源码在/fs/pstore/ram_core.c
1 | fs/pstore/ |
oops/panic日志位于 pstore 目录下的dmesg-ramoops-x文件中,根据缓冲区大小可以有多个文件,x从0开始。
函数调用序列日志位于 pstore 目录下的ftrace-ramoops文件中。
1 | CONFIG_PSTORE=y |
1 | ramoops_mem: ramoops_mem { |
1 | 方案1: |
1 | mount -t pstore pstore /sys/fs/pstore |
以上几种方案都使用到了kmsg_dump的注册机制。 注册很简单,就是把一个全局变量结构挂到一个全局list中。 kmsg_dump是oops时进入kmsg_dump的入口。由panic,die,oops_exit等函数调用。它会一一调用回调函数。 每一个回调函数都会用到kmsg_dump_get_buffer。它先是计算dump还有多少空间,然后把kmsg中最后的一部分写进去。
mstar平台为例子。
ssr931g在插入usb过程中会报错。
1 | cma:cma_calloc:memory range at ptrval is busy, retrying。 |
CMA –> 连续内存分配器。是一种用于申请大量的,并且物理上连续的内存块的方法。在设备驱动USB,HOST,DMA,ETH PHY中起关键作用。

sstar的内存分配图。如上。LX_MEM有可能有好几块,例如某些SOC上会有双通道DDR,每个DDR上面会各自分配一块LX_MEM。还有某些特别的情况,一颗DDR上面可能会分配多个LX_MEM。多个LX_MEM的命名规则为LX_MEM1、LX_MEM2,以此类推。
注意:MMA Heap以及HW IP Layout分配出来的内存在物理上是连续的,但是LX_MEM分配给linux kernel的不一定是在物理上连续的。