android镜像解压资源文件

工具准备

  1. unpackbootimg
  2. resource-tool
  3. dtc
  4. rk 烧录工具

编译 unpackbootimg

源码

https://github.com/huangzhenzeng/android-unpackbootimg

编译

只需要在 linux(需安装 gcc,make,一般是标配)或 windows(需要安装 mingw)的命令行执行 make,即可产生可执行文件 mkbootimg、unpackbootimg

resource-tool 工具

一般编译镜像后,将产生这个工具,目录为:
u-boot/tools/resource_tool/resource_tool

dtc 工具

一般编译镜像后,将产生这个工具,目录为:
kernel/scripts/dtc/dtc

rk 烧录工具

需到如下地址
https://www.t-firefly.com/doc/download/54.html
下载《RKDevTool》

分离镜像

window 下,打开“RKDevTool”,选中“高级”,然后导入固件,选择解压即可,如下图:

最终 output 目录,输出各个子分区,如下图:

解压 boot.img 镜像

boot 格式分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
因为boot.img的格式比较简单,它主要分为三大块(有的可能有四块)
+—————–+
| boot header | 1 page
+—————–+
| kernel | n pages
+—————–+
| ramdisk | m pages
+—————–+
| second stage | o pages
+—————–+
n = (kernel_size + page_size – 1) / page_size
m = (ramdisk_size + page_size – 1) / page_size
o = (second_size + page_size – 1) / page_size
0. all entities are page_size aligned in flash
1. kernel and ramdisk are required (size != 0)
2. second is optional (second_size == 0 -> no second)

linux 下输入命令:
mkdir output
unpackbootimg -I boot.img -O ./output
生成文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
├── boot.img-base (mkbootimg base参数)
├── boot.img-board
├── boot.img-cmdline (mkbootimg cmdline参数)
├── boot.img-dtb
├── boot.img-hash
├── boot.img-kerneloff
├── boot.img-oslevel
├── boot.img-osversion
├── boot.img-pagesize (mkbootimg pagesize参数)
├── boot.img-ramdisk.gz (根文件系统打包文件)
├── boot.img-ramdiskoff
├── boot.img-second (资源文件)
├── boot.img-secondoff
├── boot.img-tagsoff
├── boot.img-zImage (内核文件)

解压 boot.img-second 资源

执行命令:
mkdir out
./resource_tool --verbose --unpack --image=boot.img-second

生成文件如下:

1
2
3
├── logo.bmp
├── logo_kernel.bmp
└── rk-kernel.dtb

dtb 转 dts

执行命令:
./dtc -I dtb -O dts out/rk-kernel.dtb -o tmp.dts
生成 tmp.dts 文件,

最终通过 gedit 或文本编辑查看

rk3399c-ai问题总结

烧录

linux 烧录

注意:系统从7.1升级到10.0需要用mask模式;相同的系统,可以采用loader模式

mask 模式

操作步骤如下:

  1. 设备断开所有电源。
  2. 拔出 SD
  3. 用 Type-C 数据线连接好设备和主卡。机。
  4. 用金属镊子接通 AIO-3399C 上的如下图所示的两个测试点并保持(如下图所示)。
  5. 设备插入电源。
  6. 稍候片刻,之后松开镊子。

ADB 调试

  1. AIO-3399C 用 Type-C 数据线连接设备 Type-C 口和主机
    AIO-3399C(AI) 用双端公头 USB 数据线连接设备蓝色 USB 3.0 口和主机
    如下图:
  2. 根据当前 Android 版本勾选对应路径下的 Connect to PC
1
2
Android7.1、Android8.1 选择 Setting -> USB,然后勾选 Connect to PC
Android10.0 选择 Setting -> Connected devices 然后勾选 Connect to PC

ttysWK1-4 消失

https://dev.t-firefly.com/forum.php?mod=viewthread&tid=100003&extra=&highlight=%B4%AE%BF%DA&page=2

wk2124 驱动文章

https://www.cnblogs.com/chorm590/p/11840606.html
https://blog.51cto.com/u_13640625/5603561?articleABtest=0

wk2124 官网驱动下载

http://www.wkmic.com/News_List.php?tag=Jszc&theId=14

写文件的时候不希望写入一半的时候掉电丢失

Christoph Hellwig 在 2019 Linux Storage, Filesystem, and Memory-Management Summit (LSFMM)会议上,分享了他关于文件系统原子操作的思考。如果 application 在写文件的时候出现了程序崩溃,人们肯定希望文件系统里的数据要么是旧数据,要么是新数据,肯定不希望看到新旧数据混杂在一起。这样就需要对文件有 atomic write 操作的能力。他介绍了在 XFS 里实现的 atomic write,想看看其他文件系统开发者有什么想法。

目前,如果 application 希望对文件做 atomic write,会有两种方法。一种是在 user space 加锁来保护,数据库方案里面通常会这样来做;另一种是重新写一个新文件,然后做“atomic rename”。可惜,应用程序开发者通常都没有正确使用 fsync(),所以最终数据还是会有丢失。

在现代存储系统里面,哪怕存储设备硬件本身,在写操作的时候都不是立刻写入存储单元的。例如闪存设备都有一个 flash translation layer (FTL)抽象层,会把写操作分发到 flash 的各个地方去,避免某些存储单元被写入太多过早损坏(wear leveling),所以它们实际上从来不会在原地址更新数据。对 NVMe 设备来说,每次更新它的一个逻辑块地址(LBA)的数据的时候,都是能确保是 atomic 操作的,不过这个接口地位比较尴尬,估计很少人会用。SCSI 的接口更加好一些,在做 atomic write 的时候能够有错误报告,不过他也没见过哪个厂商真的实现了这个接口。

有的文件系统可以在写操作的时候写在别的地址 ,例如 XFS, Btrfs,等等。这样就能很容易在文件系统层实现 aotmic write。在 5 年前,HP Research 就有一篇有意思的论文,介绍了如何增加一个特殊的 open() flag 来专门指定要 atomic write。这篇论文还只是学术论文,没有真正处理各种 corner case,以及针对现实生活中的限制来实现,不过想法很合理。

在这个系统中,用户写入文件的时候无论写入多少数据都没用,在明确调用 commit 操作之前都不会真正生效。当 commit 操作结束后,所有的改动就真正生效了。这里比较简单的一种实现方式就是在 fsync()里面来恰当地调用 commit 操作,这样就不需要再加一个新的系统调用了。

不久之前,他开始在 XFS 里面用这种方式实现 atomic write。他向社区发布出了一组 patch,不过当时还有不少问题,因此他后来继续修改了这组 patch。目前论文的原作者一直在跟他紧密交流希望能拿到代码,然后就能跟他合作再写一篇论文出来。此外还有一些人也希望使用这个功能。

Chris Mason 问他这里的粒度是多大,是针对每个单独的 write(),还是更多?Hellwig 回答,在 commit 操作之前的所有 write,都会等 commit 的时候一次写入。文件系统会负责对最多能写多少数据来设一个上限。对 XFS 来说,会根据这些 write 所涉及到的不连续区域的数量,来决定这个上限。

不光是传统的 write()系统调用,mmap()映射出来的区域也一样适用。例如,人们现在更改 B-Tree 的多个节点的时候,很难做 atomic update,而这个功能合入后,application 可以简单的在文件 mmap 出来的内存区域做这些更改,然后简单做一次 commit 即可。如果 application 崩溃了,文件系统里存储的数据仍然保证是旧版本或者是新版本,不会混杂起来。

Ted Ts’o 提到他的 Android 领域的朋友也在提需求想要这么一个功能,不过是针对每个文件系统级别的。他们希望每次对 Android 做版本更新的时候,ext4 或者 F2FS 文件系统都可以通过一个 magic option 来加载上来并且关闭所有日志记录真正触发写入操作。等文件更新完毕然后就发一个 ioctl()来开始把所有那些日志都刷到存储设备里。这个方案有点不美观,不过能实现 90%的功能。最后,Ts’o 也认为 ext4 会需要有一个 atomic write 功能,不过每次 commit 之前究竟能更新多少数据,这里可能更加受限制一点。

Hellwig 表示了一些担忧,因为他此前也做过类似的实现方案,都是在内存里面做数据更新,不过最终发现每一批次能缓存的数据非常有限。Ts’o 介绍了 Android 的情况,这里数据块都是会写入存储设备的,而内存中缓存的只是 metadata 相关的更新,一般也就缓存几分钟,这是个非常特殊的应用场景,不具有普适性。不过这个新的实现方法替代了此前的利用 device-mapper 的机制(那个太慢了)。

Chris Mason 提到,只要 interface 设计的好,他很愿意让 Btrfs 支持这个功能。Hellwig 也说对 Btrfs 来说实现这个功能会很直接。对他来说一个比较大的阻碍是怎么支持 O_DIRECT。如果某个 application 先做了 atomic write,然后又把内容读回来,最好是能把刚刚写入的数据读回来。一般的 application 都不会这么做,不过 NFS 确实有这种行为。Linux I/O 的代码里面没有完全支持好这部分功能,所以他还需要做一些修改。

还有一些讨论是关于为什么利用 fsync()的,为什么不用一个专用的系统调用,或者其他什么接口。Hellwig 觉得用 fsync()没有什么不好,毕竟这也是它的本来含义,不应该只做一部分工作,而不做完。Amir Goldstein 问到是否有可能其他进程同时也对这个文件做 fsync()操作,相当于是某种类型的攻击。 Hellwig 说他本来是使用了一个 open()的 flag,不过后来有人提醒没有用过的 flag 可能不会在 open()里面检查,所以利用 flag 来保证数据一致性不是一个很好的主意。 在那种使用模式下,使用 fsync()的接口仅仅会对那些被用这个 flag 打开的 file descriptor 才会做 commit 操作。后来他改成了使用 inode 的 flag,这样更加合理一点,不过目前还没有处理好那些恶意的 fsync()调用的问题。

android 设备写入文件,立即断电重启后,文件丢失,数据没有保存

在 android 开发的过程中碰到写入文件后,立即断电重启,发现写入的文件丢失了

写入时检查了,写入是没有失败的,经过查找资料可能是如下问题引起:

Linux/Unix 系统中,在文件或数据处理过程中一般先放到内存缓冲区中,等到适当的时候再写入磁盘,以提高系统的运行效率。

可能是因为断电时,文件没有写入的物理介质中导致,解决办法如下:

   在write/fwrite写入后,添加fsync(), 这样可以将缓存中的内容强制写入到磁盘中

关于 write/fwrite 和 fsync 的关系如下:
read/write/fsync:

  1. Linux 底层操作;

  2. 内核调用, 涉及到进程上下文的切换,即用户态到核心态的转换,这是个比较消耗性能的操作。

fread/fwrite/fflush:

  1. C 语言标准规定的 io 流操作,建立在 read/write/fsync 之上

  2. 在用户层, 又增加了一层缓冲机制,用于减少内核调用次数,但是增加了一次内存拷贝。

两者之间的关系,见下图:

补充:

  1. 对于输入设备,调用 fsync/fflush 将清空相应的缓冲区,其内数据将被丢弃;
  2. 对于输出设备或磁盘文件,fflush 只能保证数据到达内核缓冲区,并不能保证数据到达物理设备, 因此应该在调用 fflush 后,调用 fsync(fileno(stream)),确保数据存入磁盘。
  3. 在 android java 层中,需要调用 FileDescriptor 的 sync()方法来确保数据存入磁盘。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/* Java 示例代码 */

FileOutputStream fos = null;

byte[] buf = {1,2,3,4};

try {

fos = new FileOutputStream("/sdcard/test.txt");

//将buf中的数据写入fos

fos.write(buf);

//将fos的数据保存到内核缓冲区

//不能确保数据保存到物理存储设备上,如突然断点可能导致文件未保存

fos.flush();

//将数据同步到达物理存储设备

FileDescriptor fd = fos.getFD();

fd.sync();

} catch(Exception e) {

e.printStackTrace();

} finally {

if(fos!=null)

fos.close();

}