链接装载与库(一)编译和链接
原图
被隐藏了的过程
1234567#include <stdio.h>int main(){ printf("Hello World\n"); return 0;}
在 Linux 下,当我们使用 GCC 来编译 Hello World 程序时,只须使用最简单的命令(假设源代码文件名为 hello.c):
123gcc hello.c./a.outHello world
事实上,上述过程可以分解为 4 个步骤,分别是预处理(Prepressing)、编译(Compilation)、汇编(Assembly)和链接(Linking)。
预编译
第一步预编译的过程相当于如下命令(-E 表示只进行预编译):
1gcc -E hello.c -o hello.i
或者:
1cpp hello.c hello.i
预编译过程主要处理那些源代码文件中的以“#”开始的预编译指令。比如“#include”、“#define”等,预编译成一个。i 文件,主要处理规则如下:
将所有的“#define”删除,并且展开所有的宏定义
处理所有条件预编译指令,比如“#if”、“#i ...
基于反作用轮的 3D 倒立摆的非线性分析与控制
摘要
本文介绍了基于反作用轮的 3D 倒立摆 Cubli 的非线性分析和控制设计。 使用广义动量的概念,将基于反作用轮的 3D 倒立摆的关键特性与 1D 情况的特性进行比较,以提出相对简单和直观的非线性控制器。 最后,在 Cubli 上实现了所提出的控制器,并给出了实验结果
INTRODUCTION
本文介绍了图 1 所示 Cubli 的非线性分析和控制。Cubli 是一个边长为 150 mm 的立方体,三个反作用轮相互垂直安装。 与其他 3D 倒立摆试验台 [1] 和 [2] 相比,Cubli 具有两个独特的功能。 一是其占地面积相对较小(因此得名 Cubli,源自瑞士德语中“立方体”的缩略词)。 另一个特点是它能够在没有任何外部支撑的情况下从静止位置跳起来,通过突然制动以高角速度旋转的反作用轮。 在 [3] 中介绍了跳跃的概念,在 [4] 中介绍了线性控制器,本文重点分析非线性模型和非线性控制策略。
与 [5] 和 [6] 中提出的工作相反,本文从纯机械的角度来处理控制器设计。 Cubli 的基本机械特性被利用来提出具有物理洞察力的简单直观的推导([5]:1D,反作用轮,反馈线性化 ...
Linux 文件系统(三)VFS
原图
通用文件系统接口
VFS 使得用户可以直接使用 open()、read() 和 write() 这样的系统调用而无须考虑具体文件系统和实际物理介质,使得这些通用的系统调用可以跨越各种文件系统和不同介质执行。
Unix 使用了四种和文件系统相关的传统抽象概念:文件、目录项、索引节点和安装点 (mount point)。
VFS 对象及其数据结构
VFS 其实采用的是面向对象的设计思路,使用一组数据结构来代表通用文件对象,有四个主要的对象类型,它们分别是:
超级块对象,它代表一个具体的已安装文件系统
索引节点对象,它代表一个具体文件
目录项对象,它代表一个目录项,是路径的一个组成部分
文件对象,它代表由进程打开的文件
注意,因为 VFS 将目录作为一个文件来处理,所以不存在目录对象。
每个主要对象中都包含一个操作对象,这些操作对象描述了内核针对主要对象可以使用的方法:
super_operations 对象,其中包括内核针对特定文件系统所能调用的方法,比如 write_inode() 和 sync_fs() 等方法
inode_operations 对象,其中包括内核针 ...
Linux 文件系统(一)基本概念
原图
声明:本文转载自 文件系统的原理 和 Linux 中的 VFS 实现 [一]
没有文件系统,访问磁盘上的数据就需要直接读写磁盘的 sector(繁琐),而文件系统存在的意义,就是能更有效的组织、管理和使用磁盘上的 raw data。
文件系统的组成
因为磁盘上的数据要和内存交互,而内存通常是以 4KB 为单位管理的,所以把磁盘按照 4KB 划分比较方便(称为一个 block)。现在假设由一个文件系统管理 64 个 blocks 的一个磁盘区域:
文件
文件系统的基础要素自然是文件,而文件作为一个数据容器的逻辑概念,本质上是字节构成的集合,这些字节就是文件的 user data(对应下图的"D")。
除了文件本身包含的数据,还有文件的访问权限、大小和创建时间等控制信息,这些信息被称为文件的 meta data。这些 meta data 的数据结构就是 inode(对应下图的"I",有些文件系统称之为 dnode 或 fnode)。
假设一个 inode 占据 256 字节,那么一个 4KB 的 block 可以存放 16 个 inodes,使用 5 个 blocks 可 ...
Linux 文件系统(二)块 I/O
原图
系统中能够随机访问固定大小数据片的硬件设备称作块设备,这些固定大小的数据片就称作块。另一种基本的设备类型是字符设备,字符设备按照子节流的方式被有序访问,像串口和键盘就属于字符设备。对于这两种类型的设备,它们的区别在于是否可以随机访问数据。
剖析一个块设备
块设备中最小的可寻址单元是扇区(sector)。扇区大小一般是 2 的整数倍,而最常见的是 512 字节。扇区的大小是设备的物理属性,扇区是所有块设备的基本单元—-块设备无法对比它还小的单元进行寻址和操作。
因为各种软件的用途不同,所以它们都会用到自己的最小逻辑可寻址单元--块(block)。块是文件系统的一种抽象,只能基于块来访问文件系统。虽然物理磁盘寻址是按照扇区级进行的,但是内核执行的所有磁盘操作都是按照块进行的。由于扇区是设备的最小可寻址单元,所以块不能比扇区还小,只能数倍于扇区大小。另外,内核(对有扇区的硬件设备)还要求块大小是 2 的整数倍,而且不能超过一个页(page)的长度。所以,对块大小的最终要求是,必须是扇区大小的 2 的整数倍,并且要小于页面大小。所以通常块大小是 512 字节、1KB 或 4KB。
...
Linux 内核调试(二)内存检测
原图
一般的内存访问错误如下。
越界访问(out-of-bounds)
访问已经被释放的内存(use after free)
重复释放(double free)
内存泄漏(memory leak)
栈溢出(stack overflow)
本节主要介绍 Linux 内核中常用的内存检测的工具和方法。
slub_debug
在 Linux 内核中,对于小块内存分配,大量使用 slab/slub 分配器。slab/slub 分配器提供了一个内存检测功能,很方便在产品开发阶段进行内存检查。内存访问中比较容易出现错误的地方如下。
访问己经被释放的内存
越界访问
释放己经释放过的内存
配置和编译内核
首先,需要重新配置内核选项,打开 CONFIG_SLUB、CONFIG_SLUB_DEBUG_ON 以及 CONFIG_SLUB_STATS 选项。
1234# CONFIG_SLAB is not setCONFIG_SLUB=yCONFIG_SLUB_DEBUG_ON=yCONFIG_SLUB_STATS=y
修改了上述配置文件之后,需要重新编译内核并更新根文件系统。
添加 slub_ ...
Linux 内核调试(一)ftrace
原图
位置无关码
加载地址:存储代码的物理地址。如 ARM64 处理器上电复位后是从 0x0 地址开始第一条指令的,所以通常这个地方存放代码最开始的部分,如异常向量表的处理地址
运行地址:指程序运行时的地址
链接地址:在编译链接时指定的地址,编程人员设想将来程序要运行的地址。程序所有标号的地址在链接后便确定了,不管程序在哪里运行都不会改变。aarch64-linux-gnu-obidump (objdump) 工具进行反汇编查看的就接地链接地址
链接地址和运行地址可以相同,也可以不同。那什么时候运行地址和链接地址不相同,什么时候相同呢?我们以一块 ARM64 开发板为例,芯片内部有 SRAM,起始地址为 0x0,DDR 内存的起始地址为 0x4000 0000。
通常代码存储在 Nor Flash 存储器或者 Nand Flash 存储器中,芯片内部的 BOOT ROM 会把开始的小部分代码装载到 SRAM 中运行。芯片上电复位之后,从 SRAM 中取指令。由于 Uboot 的镜像太大了,SRAM 放不下,因此必须要放在 DDR 内存中。通常 Uboot 编译时链接地址都设置到 ...
Linux 并发与同步(四)RCU
原图
RCU 概述
RCU 的全称 Read-Copy-Update,它是 Linux 内核中一种重要的同步机制。Linux 内核中已有了原子操作、自旋锁、读写自旋锁、读写信号量、互斥锁等锁机制,为什么要单独设计一个比它们复杂得多的新机制呢?回忆自旋锁、读写信号量和互斥锁的实现,它们都使用了原子操作指令,即原子地访问内存,多 CPU 争用共享的变量会让高速缓存一致性变得很糟,使得性能下降。
以读写信号量为例,除了上述缺点外,读写信号量还有一个致命弱点,它允许多个读者同时存在,但是读者和写者不能同时存在。因此 RCU 机制要实现的目标是,读者线程没有同步开销,或者说同步开销变得很小,甚至可以忽略不计,不需要额外的锁,不需要使用原子操作指令和内存屏障指令,即可畅通无阻地访问;而把需要同步的任务交给写者线程,写者线程等待所有读者线程完成后才会把旧数据销毁。
在 RCU 中,如果有多个写者同时存在,那么需要额外的保护机制。RCU 机制的原理可以概括为 RCU 记录了所有指向共享数据的指针的使用者,当要修改共享数据时,首先创建一个副本,在副本中修改。所有读者线程离开读者临界区之后,指针指向修 ...
Linux 并发与同步(三)互斥锁
原图
概述
互斥锁类似于 count 值等于 1 的信号量,为什么内核社区要重新开发互斥锁,mutex 有如下优点:
mutex 数据结构的定义比信号量小
互斥锁相对于信号量要简单轻便一些,在锁争用激烈的测试场景下,互斥锁比信号量执行速度更快
可扩展性更好
mutex 数据结构
下面来看 mutex 数据结构的定义。
123456789101112131415<include/linux/mutex.h>struct mutex { atomic_long_t owner; raw_spinlock_t wait_lock;#ifdef CONFIG_MUTEX_SPIN_ON_OWNER struct optimistic_spin_queue osq; /* Spinner MCS lock */#endif struct list_head wait_list;#ifdef CONFIG_DEBUG_MUTEXES void *magic;#endif#ifdef CONFIG_DEBUG_LOCK_ALLOC struct lockdep_map d ...
Linux 并发与同步(二)信号量
原图
信号量
信号量( semaphore)是操作系统中最常用的同步原语之一。自旋锁是一种实现忙等待的锁,而信号量则允许进程进入睡眠状态。简单来说,信号量是一个计数器,它支持两个操作原语,即 P 和 V 操作。
信号量实现
semaphore 数据结构的定义如下。
123456<include/linux/semaphore.h>struct semaphore { raw_spinlock_t lock; unsigned int count; struct list_head wait_list;};
lock 是自旋锁变量,用于保护 semaphore 数据结构里的 count 和 wait_list 成员
count 用于表示允许进入临界区的内核执行路径个数
wait_list 链表用于管理所有在该信号量上睡眠的进程,没有成功获取锁的进程会在这个链表上睡眠
通常通过 sema_init() 函数进行信号量的初始化,其中 __SEMAPHORE_INITIALIZER() 完成对 semaphore 数椐结构的填充,val 值通常设为 1。
1234567 ...