Android的linux内核解耦

5 篇文章 1 订阅
1 篇文章 0 订阅

android设备内核部分,除android定制性修改外还有芯片供应商、ODM/OEM的修改,为了隔离他们的影响,做了内核解耦部分内容。

1、boot内容

查看Boot Image Header,version 2版本包含内容最多,包括了内核、设备树、根目录、recovery设备树,cmdline。boot拆包与内容解析参考1、Android bootimg kernel(boot.img)

2、linux的ramdisk解耦

2.1、ramdisk加载情况

2.1.1、android 13没有recovery分区

vendor_boot的ramdisk,init_boot 的ramdisk,boot镜像的GKI,system的tmpfs共同加载到RAM。根据命令参数选择正常Boot Android还是Boot recovery。

2.1.2、android 13 AB系统有recovery分区情况

Boot Android还是加载vendor_boot的ramdisk,init_boot 的ramdisk,boot镜像的GKI,system的tmpfs;

Boot recovery加载vendor_boot的ramdisk,init_boot 的ramdisk,boot镜像的GKI,system的tmpfs以及recovery ramdisk。

2.1.3、android 13 non-AB系统有recovery情况

Boot Android还是加载vendor_boot的ramdisk,init_boot 的ramdisk,boot镜像的GKI,system的tmpfs;

Boot recovery加载recovery的GKI和ramdisk,boot镜像的GKI与tmpfs;

2.2、ramdisk加载解析

上面三张图来自android开发者社区,显然大家很难看懂,我们抓下面三个关键来看图:

1、init分了两个阶段,每个阶段使用不同的资源文件,Android和recovery模式均是两个阶段。

2、启动必然要kernel,这个kernel在哪个分区?

3、进入recovery的核心二进制文件/system/bin/recovery以及它的资源文件在哪个分区?

相关内容所在分区整理表格如下

android 13

无recovery分区

android13 AB

有recovery分区

android 13 non-AB

有recovery分区

android模式

1nd init

init_boot分区

init_boot

init_boot

1nd resource

vendor_boot&init_boot

init_boot

init_boot

2nd init

system

system

system

2nd resource

system

system

system

kernel

boot kernel

boot kernel

boot kernel

recovery模式

1nd init

init_boot分区

recovery ramdisk

recovery ramdisk

2nd init

init_boot

recovery ramdisk

recovery ramdisk

kernel

boot kernel

boot kernel

recovery kernel

recovery二进制执行文件

vendor_boot

recovery ramdisk

recovery ramdisk

recovery resource

vendor_boot

recovery ramdisk

recovery ramdisk

2.3、ramdisk加载总结

根据上表分析:

1、android模式加载流程基本相同,init第二阶段均需要是用system里面的ramdisk;

2、recovery模式有recovery分区,则1nd init、2nd init、recovery执行文件与资源文件从recovery加载;否则1nd init、2nd init从init_boot加载,recovery执行文件与资源文件从vendor_boot加载;

3、没有recovery分区情况和AB系统情况一直使用同一内核boot GKI,non-AB系统有recovery分区情况在recovery模式下使用recovery的GKI。

3、linux的Kernel解耦

上图就是android的kernel解耦情况。

generic Kernel是Linus Torvalds维护的linux宏内核、GKI Modules是android定制模块,合称GKI;GKI对外接口是KMI;Vendor Modules是动态加载模块,通过KMI与GKI通信。

但是目前动态加载模块加载与执行效率并不理想,andorid还在优化。

这章关键在理解linux的分支管理与动态加载模块,以及KMI接口。内容多引用官网。

3.1 GKI

android底层是linux,这个linux除Linus Torvalds维护的宏内核外还有下面三方的修改:

1、android system针对linux的定制性修改,例如低内存终止守护进程(一个内存管理系统,可更主动地保留内存)、唤醒锁定(一种 PowerManager 系统服务)、Binder IPC 驱动程序、启动验证用的设备映射dm-mapper、OTA升级用的设备映射快照dm-snapshot、文件系统加密用的fscrypt等等;

2、芯片厂商vendor添加的内核方案,包括电源管理/DVFS、系统基础支持、PCIe和USB等高速外围设备等等;

3、ODM/OEM也有自己内核的方案,例如各家的驱动、外设方案。

现在google要脱离芯片厂商与ODM/OEM影响,对linux进行解耦。

3.2 KMI

3.3 GKI Modules与Vendor Modules

其实都是linux动态加载模块(不知道的自己查询),关键点在KMI的规范与稳定。

system的GKI模块,放置到system_dlkm里面,开机挂载分区安装,system_dlkm编译参考https://source.android.google.cn/docs/core/architecture/partitions/gki-partitions

将新功能配置为 GKI 模块

对于新功能,编辑gki_defconfig并将所需内核功能的配置项从n设置为m ( =m )。在arch/arm64/configs/gki_defconfig和arch/x86/configs/gki_defconfig中设置此设置。
将为该功能生成的 KO ( .ko ) 文件添加到common/modules.bzl的COMMON_GKI_MODULES_LIST部分。按排序顺序添加文件。如果您不确定生成的所有文件,构建将失败并列出要添加到列表中的所有必需的 KO 文件。
将步骤 2 中的同一组 KO 文件添加到common/android/gki_protected_modules以将模块指定为受保护的 GKI 模块,在运行时按升序排序以进行二进制搜索。指定为受保护 GKI 模块的模块仍必须经过 Google 批准才能成为官方受保护模块。
将步骤 2 中的同一组 KO 文件添加到common/android/gki_system_dlkm_modules ,以在运行时按升序排序以进行二进制搜索,以确保将文件复制到内核的out/<androidX-YZ>/dist/system_dlkm.img和out/ androidX-YZ /dist/system_dlkm_staging_archive.tar.gz 。 system_dlkm_staging_archive.tar.gz存档中的模块可用作输入以在平台构建中生成system_dlkm.img 。
提交您的更改以供审核。 GKI 模块是 Android 独有的内核功能,因此不需要向上游提交模块转换补丁。但是,您必须遵循其他准则来提交 Android 通用内核 (ACK) 补丁

vendor添加动态加载模块官网地址

https://source.android.google.cn/docs/core/architecture/partitions/vendor-odm-dlkm-partition

添加vendor动态加载模块规则

https://source.android.google.cn/docs/core/architecture/kernel/vendor-module-guidelines

4、dtb与dtbo

android系统对dtb也有依赖,例如system、vendor等的挂载、验证。之前写过dtb与dtbo,并示范多层设备叠加层。dtb和dtbo使用分区没有固定,可以参考https://source.android.google.cn/docs/core/architecture/dto/implement

https://blog.csdn.net/dongyi1988/article/details/103995862#t1

5、BootConfig

BootConfig这个和cmdline一样,将配置详细信息从aboot传递到 Android 12 的机制。

现在与android相关的启动参数androidboot.*从内核 cmdline 移至 bootconfig 文件。

配置参考https://source.android.google.cn/docs/core/architecture/bootloader/implementing-bootconfig#examples-and-source

注意到

  1. kernel header是V4才能用;

  1. 可在/proc/bootconfig与设备树initramfs 节点查看信息;

  1. 在linux的/proc下有文件,显然有bootconfig内核模块。

5.1、bootconfig内存使用图

5.2、代码解析

bootconfig结构如上图,parameters段有直接编译到vendor_boot镜像的固定段、bootloader启动时的动态附加段

  1. mkbootimg编译,将指定文件属性配置编译成bootconfig的固定段

mkbootimg的--vendor_bootconfig参数指定文件附加vendor_boot变成bootconfig里面的固定段,指定属性文件与编译参考地址http://aospxref.com/android-13.0.0_r3/xref/packages/modules/Virtualization/microdroid/(该目录下bootconfig.前缀文件)

  1. vendor_boot加载后,bootloader识别模式往bootconfig附加参数(动态附加段

http://aospxref.com/android-13.0.0_r3/xref/bootable/libbootloader/libxbc/libxbc.c?fi=addBootConfigParameters#addBootConfigParameters

函数addBootConfigParameters

  1. 内核proc模块,按结构图中信息从kernel所占内存获取Bootconfig信息,并变成文件节点/proc/bootconfig(所以这里boot需要V4版本header,才能获取到bootconfig偏移)

proc-$(CONFIG_BOOT_CONFIG) += bootconfig.o

https://cs.android.com/android/kernel/superproject/+/common-android-mainline:common/lib/bootconfig.c;l=3?q=boot&sq=&ss=android%2Fkernel%2Fsuperproject:common%2Flib%2F

  1. init解析/proc/bootconfig成android属性

http://aospxref.com/android-13.0.0_r3/xref/system/core/init/util.cpp#249

6、总结

linux解耦已经初具模型,

Ramdisk二段加载,拆分与android系统无关的启动根目录,变成android依赖根文件系统+其它根文件系统(包括linux系统、vendor、ODM等的文件系统);

KO模块动态加载,将android定制模块从kernel中剥离,变成GKI modules+原生kernel;

dtbo设备树叠加层,开始主要解决设备硬件多样性,当然也可以把android的底层属性与vendor、硬件设备树信息隔开使用。

bootconfig解析为android属性,非andorid属性内容还是用cmdline。

上面提到的ramdisk、KO模块、dtb、BootConfig(cmdline传参方案)等方案,把android系统启动时需要使用的根目录、动态模块、设备树、启动属性等都隔开。

目前还有缺点,动态加载模块性能还不够。后面可能针对这些内容,镜像/分仓上有新改动。

当接近完美时,也许可以换新的闭源系统了。。。

  • 4
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
Linux内核UART驱动通常使用队列来解耦原有的功能函数,以提高代码的可读性和可维护性。这种方法的基本思想是将不同的功能分割成独立的模块,每个模块都有一个输入队列和一个输出队列。 输入队列用于接收来自UART接口的数据,处理数据并将结果放入输出队列中。输出队列中的数据可以是控制命令、状态信息或者其他需要传递给上层应用程序的数据。这种方法的优点是可以使编写驱动程序更加简单,易于维护和扩展。 以下是一个使用队列解耦原有功能函数的UART驱动程序的示例: ```c #include <linux/module.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/slab.h> #include <linux/uaccess.h> #include <linux/serial_core.h> #define BUFFER_SIZE 1024 struct uart_device { struct cdev cdev; struct uart_port port; struct mutex mutex; struct work_struct work; struct tasklet_struct tasklet; spinlock_t lock; wait_queue_head_t read_queue; wait_queue_head_t write_queue; char *buf; int head; int tail; }; static int uart_driver_open(struct inode *inode, struct file *file) { struct uart_device *dev; dev = container_of(inode->i_cdev, struct uart_device, cdev); file->private_data = dev; return 0; } static int uart_driver_release(struct inode *inode, struct file *file) { return 0; } static ssize_t uart_driver_read(struct file *file, char __user *buf, size_t count, loff_t *offset) { struct uart_device *dev = file->private_data; ssize_t ret; if (count == 0) return 0; if (wait_event_interruptible(dev->read_queue, dev->head != dev->tail)) return -ERESTARTSYS; mutex_lock(&dev->mutex); if (dev->head > dev->tail) { ret = min_t(ssize_t, count, dev->head - dev->tail); if (copy_to_user(buf, dev->buf + dev->tail, ret)) { ret = -EFAULT; goto out; } dev->tail += ret; } else { ret = min_t(ssize_t, count, BUFFER_SIZE - dev->tail); if (copy_to_user(buf, dev->buf + dev->tail, ret)) { ret = -EFAULT; goto out; } dev->tail = (dev->tail + ret) % BUFFER_SIZE; } out: mutex_unlock(&dev->mutex); return ret; } static ssize_t uart_driver_write(struct file *file, const char __user *buf, size_t count, loff_t *offset) { struct uart_device *dev = file->private_data; ssize_t ret; if (count == 0) return 0; if (wait_event_interruptible(dev->write_queue, dev->head != ((dev->tail - 1 + BUFFER_SIZE) % BUFFER_SIZE))) return -ERESTARTSYS; mutex_lock(&dev->mutex); if (dev->tail > dev->head) { ret = min_t(ssize_t, count, BUFFER_SIZE - dev->tail); if (copy_from_user(dev->buf + dev->tail, buf, ret)) { ret = -EFAULT; goto out; } dev->tail += ret; } else { ret = min_t(ssize_t, count, dev->head - dev->tail); if (copy_from_user(dev->buf + dev->tail, buf, ret)) { ret = -EFAULT; goto out; } dev->tail = (dev->tail + ret) % BUFFER_SIZE; } out: mutex_unlock(&dev->mutex); return ret; } static void uart_driver_work(struct work_struct *work) { struct uart_device *dev = container_of(work, struct uart_device, work); struct uart_port *port = &dev->port; unsigned char c; int i; mutex_lock(&dev->mutex); while (uart_chars_avail(port)) { c = uart_get_char(port); if (dev->head == ((dev->tail - 1 + BUFFER_SIZE) % BUFFER_SIZE)) { /* Buffer is full, drop the incoming character */ continue; } dev->buf[dev->head] = c; dev->head = (dev->head + 1) % BUFFER_SIZE; } mutex_unlock(&dev->mutex); wake_up_interruptible(&dev->read_queue); } static void uart_driver_tasklet(unsigned long data) { struct uart_device *dev = (struct uart_device *)data; struct uart_port *port = &dev->port; unsigned char c; int i; spin_lock(&dev->lock); while (uart_chars_avail(port)) { c = uart_get_char(port); if (dev->head == ((dev->tail - 1 + BUFFER_SIZE) % BUFFER_SIZE)) { /* Buffer is full, drop the incoming character */ continue; } dev->buf[dev->head] = c; dev->head = (dev->head + 1) % BUFFER_SIZE; } spin_unlock(&dev->lock); wake_up_interruptible(&dev->read_queue); } static void uart_driver_start(struct uart_port *port) { struct uart_device *dev = container_of(port, struct uart_device, port); INIT_WORK(&dev->work, uart_driver_work); tasklet_init(&dev->tasklet, uart_driver_tasklet, (unsigned long)dev); spin_lock_init(&dev->lock); init_waitqueue_head(&dev->read_queue); init_waitqueue_head(&dev->write_queue); mutex_init(&dev->mutex); dev->buf = kzalloc(BUFFER_SIZE, GFP_KERNEL); dev->head = 0; dev->tail = 0; uart_write_wakeup(port); } static void uart_driver_stop(struct uart_port *port) { struct uart_device *dev = container_of(port, struct uart_device, port); cancel_work_sync(&dev->work); tasklet_kill(&dev->tasklet); spin_lock_irq(&dev->lock); dev->head = dev->tail = 0; spin_unlock_irq(&dev->lock); kfree(dev->buf); } static struct uart_ops uart_driver_ops = { .tx_empty = uart_tx_empty, .set_mctrl = uart_set_mctrl, .get_mctrl = uart_get_mctrl, .stop_tx = uart_stop_tx, .start_tx = uart_start_tx, .send_xchar = uart_send_xchar, .stop_rx = uart_stop_rx, .enable_ms = uart_enable_ms, .break_ctl = uart_break_ctl, .startup = uart_driver_start, .shutdown = uart_driver_stop, }; static struct uart_driver uart_driver = { .owner = THIS_MODULE, .driver_name = "uart_driver", .dev_name = "ttyUART", .major = 0, .minor = 0, .nr = 1, .cons = NULL, .ops = &uart_driver_ops, }; static int __init uart_driver_init(void) { dev_t devno; int ret; ret = alloc_chrdev_region(&devno, 0, 1, "uart_driver"); if (ret < 0) return ret; cdev_init(&uart_driver.cdev, &uart_driver_ops); uart_driver.cdev.owner = THIS_MODULE; ret = cdev_add(&uart_driver.cdev, devno, 1); if (ret < 0) { unregister_chrdev_region(devno, 1); return ret; } uart_register_driver(&uart_driver); return 0; } static void __exit uart_driver_exit(void) { uart_unregister_driver(&uart_driver); cdev_del(&uart_driver.cdev); unregister_chrdev_region(uart_driver.cdev.dev, 1); } module_init(uart_driver_init); module_exit(uart_driver_exit); MODULE_AUTHOR("Your Name"); MODULE_LICENSE("GPL"); ``` 在这个驱动程序中,我们使用了两个输入队列和一个输出队列。`read_queue` 用于接收来自 UART 的数据,`write_queue` 用于接收要发送到 UART 的数据,`buf` 用于存储接收到的数据。 `wait_event_interruptible` 函数用于等待数据到达队列。`mutex_lock` 和 `mutex_unlock` 函数用于保护共享数据结构。`wake_up_interruptible` 函数用于唤醒等待在队列上的进程。 `uart_driver_work` 和 `uart_driver_tasklet` 函数用于从 UART 中接收数据,并将接收到的数据放入输入队列中。 `uart_driver_start` 和 `uart_driver_stop` 函数用于初始化和清除输入队列和输出队列中的数据。`uart_driver_ops` 结构体包含了驱动程序中使用的 UART 操作。`uart_driver` 结构体包含了驱动程序的基本信息。 通过使用队列解耦原有的功能函数,我们可以将驱动程序中的不同部分分割成独立的模块,使得代码更加简洁、易于维护和扩展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Kael.dong

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值