
【安卓开发系列 -- 系统开发】安卓开发流程梳理 -- 从底层到 APP (Linux 驱动开发)
发布日期:2021-05-07 20:51:04
浏览次数:24
分类:原创文章
本文共 11490 字,大约阅读时间需要 38 分钟。
【安卓开发系列 -- 系统开发】安卓开发流程梳理 -- 从底层到 APP (Linux 驱动开发)
【0】开发环境简介
1. 主机,Win10;虚拟机,Ubuntu 16.042. 开发板,AIO-3399C 六核 AI 开发板3. android 源码,android 7.1
【1】Linux 驱动开发
Linux 驱动开发 Makefile 文件
ifneq ($(KERNELRELEASE),) obj-m += freg.oelse # 指定内核代码树 KERNELDIR := /home/shallysun/proj/firefly-rk3399-Industry/kernel # 获取代码所在的当前目录 PWD := $(shell pwd) # 指定交叉编译链 ARCH = arm64 CROSS_COMPILE=/home/shallysun/proj/firefly-rk3399-Industry/prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.9/bin/aarch64-linux-android- .PHONY: modules cleanmodules: # 编译内核驱动模块 $(MAKE) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) -C $(KERNELDIR) M=$(PWD) modulesclean: # 清除中间文件 @rm -rf *.o *.order *.symvers *.mod.* .*.o.cmd .*.mod.o.cmd .*.ko.cmd .tmp_versions *.ko endif
Linux 驱动开发 驱动示例源码 (Android 系统源代码情景分析(第三版) 第二章 源码)
驱动示例代码 H 头文件
#ifndef _FAKE_REG_H_#define _FAKE_REG_H_#include <linux/cdev.h>#include <linux/semaphore.h>#define FREG_DEVICE_NODE_NAME "freg"#define FREG_DEVICE_FILE_NAME "freg"#define FREG_DEVICE_CLASS_NAME "freg"struct fake_reg_dev { int val; struct semaphore sem; struct cdev dev;};#endif
驱动示例代码 C 文件
#include <linux/init.h>#include <linux/module.h>#include <linux/types.h>#include <linux/fs.h>#include <linux/proc_fs.h>#include <linux/device.h>#include <asm/uaccess.h>#include <linux/slab.h>#include "freg.h"// 指定主设备号与从设备号static int freg_major = 0;static int freg_minor = 0;// 设备类别和设备变量static struct class* freg_class = NULL;static struct fake_reg_dev* freg_dev = NULL;/****************************************************************************************************** * 设备文件的操作方法 ******************************************************************************************************/// 打开设备文件函数static int freg_open(struct inode* inode, struct file* filp);// 释放设备文件函数static int freg_release(struct inode* inode, struct file* filp);// 读设备文件函数static ssize_t freg_read(struct file* filp, char __user *buf, size_t count, loff_t* f_pos);// 写设备文件函数static ssize_t freg_write(struct file* filp, const char __user *buf, size_t count, loff_t* f_pos);// 传统设备文件操作方法表,定义了一个 file_operations 结构体变量,并制定了其中的打开、释放、读、写操作函数static struct file_operations freg_fops = { .owner = THIS_MODULE, .open = freg_open, .release = freg_release, .read = freg_read, .write = freg_write,};/****************************************************************************************************** * devfs 文件系统的设备属性操作方法 ******************************************************************************************************/// 显示寄存器的值static ssize_t freg_val_show(struct device* dev, struct device_attribute* attr, char* buf);// 写入寄存器static ssize_t freg_val_store(struct device* dev, struct device_attribute* attr, const char* buf, size_t count);// devfs 文件系统的设备属性static DEVICE_ATTR(val, S_IRUGO | S_IWUSR, freg_val_show, freg_val_store);/****************************************************************************************************** * 设备文件的操作方法 ******************************************************************************************************/static int freg_open(struct inode* inode, struct file* filp) { struct fake_reg_dev* dev; /** * container_of(ptr, type, member) 函数的实现包括两部分 : * 1. 判断 ptr 与 member 是否为同意类型 * 2. 计算 size 大小,结构体的起始地址 = (type *)((char *)ptr - size) (注:强转为该结构体指针) * * 作用 : 通过一个结构变量中一个成员的地址找到这个结构体变量的首地址 */ dev = container_of(inode->i_cdev, struct fake_reg_dev, dev); filp->private_data = dev; return 0;}static int freg_release(struct inode* inode, struct file* filp) { return 0;}static ssize_t freg_read(struct file* filp, char __user *buf, size_t count, loff_t* f_pos) { ssize_t err = 0; struct fake_reg_dev* dev = filp->private_data; /** * int down_interruptible(struct semaphore *sem) * 这个函数的功能就是获得信号量,如果得不到信号量就睡眠,此时没有信号打断,那么进入睡眠; * 但是在睡眠过程中可能被信号打断,打断之后返回-EINTR,主要用来进程间的互斥同步; */ if(down_interruptible(&(dev->sem))) { return -ERESTARTSYS; } if(count < sizeof(dev->val)) { goto out; } /** * 原型 : unsigned long copy_to_user(void *to, const void *from, unsigned long n) * to : 目标地址(用户空间) * from : 源地址(内核空间) * n : 将要拷贝数据的字节数 * 返回 : 成功返回0,失败返回没有拷贝成功的数据字节数 */ if(copy_to_user(buf, &(dev->val), sizeof(dev->val))) { err = -EFAULT; goto out; } err = sizeof(dev->val);out: // 唤醒进程 up(&(dev->sem)); return err;}static ssize_t freg_write(struct file* filp, const char __user *buf, size_t count, loff_t* f_pos) { struct fake_reg_dev* dev = filp->private_data; ssize_t err = 0; if(down_interruptible(&(dev->sem))) { return -ERESTARTSYS; } if(count != sizeof(dev->val)) { goto out; } /** * 原型 : unsigned long copy_from_user(void *to, const void *from, unsigned long n); * to : 目标地址(内核空间) * from : 源地址(用户空间) * n : 将要拷贝数据的字节数 * 返回 :成功返回0,失败返回没有拷贝成功的数据字节数 */ if(copy_from_user(&(dev->val), buf, count)) { err = -EFAULT; goto out; } err = sizeof(dev->val);out: // 唤醒进程 up(&(dev->sem)); return err;}/****************************************************************************************************** * devfs 文件系统的设备属性操作方法 ******************************************************************************************************/static ssize_t __freg_get_val(struct fake_reg_dev* dev, char* buf) { int val = 0; if(down_interruptible(&(dev->sem))) { return -ERESTARTSYS; } val = dev->val; up(&(dev->sem)); return snprintf(buf, PAGE_SIZE, "%d\n", val);}static ssize_t __freg_set_val(struct fake_reg_dev* dev, const char* buf, size_t count) { int val = 0; // 将一个字符串转换成 unsigend long long 型数据 val = simple_strtol(buf, NULL, 10); if(down_interruptible(&(dev->sem))) { return -ERESTARTSYS; } dev->val = val; up(&(dev->sem)); return count;}static ssize_t freg_val_show(struct device* dev, struct device_attribute* attr, char* buf) { struct fake_reg_dev* hdev = (struct fake_reg_dev*)dev_get_drvdata(dev); return __freg_get_val(hdev, buf);}static ssize_t freg_val_store(struct device* dev, struct device_attribute* attr, const char* buf, size_t count) { struct fake_reg_dev* hdev = (struct fake_reg_dev*)dev_get_drvdata(dev); return __freg_set_val(hdev, buf, count);}// 初始化设备static int __freg_setup_dev(struct fake_reg_dev* dev) { int err; /** * MKDEV,将主设备号与次设备号转换为 dev_t 类型的数据 */ dev_t devno = MKDEV(freg_major, freg_minor); memset(dev, 0, sizeof(struct fake_reg_dev)); // 静态的方式初始化字符设备 cdev_init(&(dev->dev), &freg_fops); dev->dev.owner = THIS_MODULE; dev->dev.ops = &freg_fops; // 调用 cdev_add() 函数将初始化之后的 cdev 添加到系统中去; err = cdev_add(&(dev->dev),devno, 1); if(err) { return err; } // 初始化信号量 sema_init(&(dev->sem), 1); dev->val = 0; return 0;}// 驱动初始化函数static int __init freg_init(void) { int err = -1; dev_t dev = 0; struct device* temp = NULL; printk(KERN_ALERT"Initializing freg device.\n"); // alloc_chrdev_region 是让内核分配一个尚未使用的主设备号 err = alloc_chrdev_region(&dev, 0, 1, FREG_DEVICE_NODE_NAME); if(err < 0) { printk(KERN_ALERT"Failed to alloc char dev region.\n"); goto fail; } // 获取主设备号与从设备号 freg_major = MAJOR(dev); freg_minor = MINOR(dev); /** * 原型 : void *kmalloc(size_t size, int flags); * size 要分配内存的大小,以字节为单位. * flags 要分配内存的类型 * #define GFP_KERNEL(__GFP_WAIT | __GFP_IO | __GFP_FS) * __GFP_WAIT : 缺内存页的时候可以睡眠; * __GFP_IO : 允许启动磁盘IO; * __GFP_FS : 允许启动文件系统IO; */ freg_dev = kmalloc(sizeof(struct fake_reg_dev), GFP_KERNEL); if(!freg_dev) { err = -ENOMEM; printk(KERN_ALERT"Failed to alloc freg device.\n"); goto unregister; } // 初始化设备 err = __freg_setup_dev(freg_dev); if(err) { printk(KERN_ALERT"Failed to setup freg device: %d.\n", err); goto cleanup; } // 在 /sys/class/ 目录下创建设备类别目录 freg_class = class_create(THIS_MODULE, FREG_DEVICE_CLASS_NAME); if(IS_ERR(freg_class)) { err = PTR_ERR(freg_class); printk(KERN_ALERT"Failed to create freg device class.\n"); goto destroy_cdev; } // 在 /dev/ 目录和 /sys/class/freg 目录下分别创建设备文件 freg temp = device_create(freg_class, NULL, dev, "%s", FREG_DEVICE_FILE_NAME); if(IS_ERR(temp)) { err = PTR_ERR(temp); printk(KERN_ALERT"Failed to create freg device.\n"); goto destroy_class; } /** * dev_attr_val 结合宏 DEVICE_ATTR 理解 */ err = device_create_file(temp, &dev_attr_val); if(err < 0) { printk(KERN_ALERT"Failed to create attribute val of freg device.\n"); goto destroy_device; } dev_set_drvdata(temp, freg_dev); printk(KERN_ALERT"Succedded to initialize freg device.\n"); return 0;// 错误处理destroy_device: device_destroy(freg_class, dev);destroy_class: class_destroy(freg_class);destroy_cdev: cdev_del(&(freg_dev->dev)); cleanup: kfree(freg_dev);unregister: unregister_chrdev_region(MKDEV(freg_major, freg_minor), 1); fail: return err;}// 驱动退出函数static void __exit freg_exit(void) { dev_t devno = MKDEV(freg_major, freg_minor); printk(KERN_ALERT"Destroy freg device.\n"); // 销毁设备类别和设备 if(freg_class) { device_destroy(freg_class, MKDEV(freg_major, freg_minor)); class_destroy(freg_class); } // 删除字符设备和释放设备内存 if(freg_dev) { cdev_del(&(freg_dev->dev)); kfree(freg_dev); } // 释放设备号资源 unregister_chrdev_region(devno, 1);}// 驱动模块信息MODULE_LICENSE("GPL");MODULE_DESCRIPTION("Fake Register Driver");// 注册驱动的初始化与卸载函数module_init(freg_init);module_exit(freg_exit);
安卓安装 Linux 驱动模块
adb push <linux 内核驱动模块> /data (安卓根文件系统中的路径)insmod <linux 内核驱动模块名称>.ko 安装驱动模块rmmod <linux 内核驱动模块名称> 卸载驱动模块lsmod 查看安装的模块dmesg 查看模块装、卸日志信息
测试 Linux 驱动模块
方法一,测试 /sys/class
方法二,编写 C 代码测试 /dev/freg
测试代码示例
#include <stdio.h>#include <stdlib.h>#include <fcntl.h>#define FREG_DEVICE_NAME "/dev/freg"int main(int argc, char** argv){ int fd = -1; int val = 0; fd = open(FREG_DEVICE_NAME, O_RDWR); if(fd == -1) { printf("Failed to open device %s.\n", FREG_DEVICE_NAME); return -1; } printf("Read original value:\n"); read(fd, &val, sizeof(val)); printf("%d.\n\n", val); val = 5; printf("Write value %d to %s.\n\n", val, FREG_DEVICE_NAME); write(fd, &val, sizeof(val)); printf("Read the value again:\n"); read(fd, &val, sizeof(val)); printf("%d.\n\n", val); close(fd); return 0;}
LOCAL_PATH := $(call my-dir)# 每个 Android.mk 文件必须以定义 LOCAL_PATH 为开始,它用于在开发 tree 中查找源文件;# 宏 my-dir 则由 Build System 提供,返回包含 Android.mk 的目录路径;include $(CLEAR_VARS)# CLEAR_VARS 变量由 Build System 提供,并指向一个指定的 GNU Makefile;# 由它负责清理很多 LOCAL_xxx,但不清理LOCAL_PATHLOCAL_MODULE_TAGS := optional# user : 指该模块只在user版本下才编译 # eng : 指该模块只在eng版本下才编译 # tests : 指该模块只在tests版本下才编译# optional : 指该模块在所有版本下都编译LOCAL_MODULE := freg# LOCAL_MODULE 模块必须定义,以表示 Android.mk 中的每一个模块,名字必须唯一且不包含空格;# Build System 会自动添加适当的前缀和后缀;LOCAL_SRC_FILES := $(call all-subdir-c-files)# LOCAL_SRC_FILES 变量必须包含将要打包如模块的 C/C++ 源码# 不必列出头文件,build System 会自动帮我们找出依赖文件include $(BUILD_EXECUTABLE)# BUILD_STATIC_LIBRARY : 编译为静态库# BUILD_SHARED_LIBRARY : 编译为动态库# BUILD_EXECUTABLE : 编译为 Native C 可执行程序
基于 AIO-3399C 六核 AI 开发板编译驱动测试文件
source build/envsetup.sh标准版:lunch rk3399_firefly_aioc-userdebugAI 版:lunch rk3399_firefly_aioc_ai-userdebugmmm ./external/freg/编译成功后,结果将保存在 ./out/target/product/rk3399_firefly_aioc_ai/system/bin 之中将编译好的可执行文件上传到 android 系统,chmod 777 freg,改变文件性质./freg 执行文件进行测试注意 : 将驱动测试 C 源文件放置在 external 目录下执行编译
测试结果
常见问题总结
问题 1. implicit declaration of function ‘kmalloc’解决方案 : 添加 #include <linux/slab.h>问题 2. init_MUTEX 的调用编译出错解决方案 :init_MUTEX 已经被禁用,改为 sema_init(&(dev->sem), 1);问题 3. Android adb shell data 目录,Permission denied解决方案 :安卓系统的控制台中 su root 获取 root 权限修改 /data 目录权限,chmod 777 /dataWindows 控制台中,adb push <Linux 驱动模块> /data;上传 Linux 驱动模块到安卓系统中;问题 4. No command 'mmm' found解决方案 :使用m、mm、mmm命令之前要在android源码目录下执行命令. build/envsetup.sh
参考致谢
本博客为博主学习笔记,同时参考了网上众博主的博文以及相关专业书籍,在此表示感谢,本文若存在不足之处,请批评指正。
【1】Android 系统源代码情景分析(第三版) [ M ] 罗升阳
发表评论
最新留言
哈哈,博客排版真的漂亮呢~
[***.90.31.176]2025年04月16日 03时57分07秒
关于作者

喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!
推荐文章
Hibernate(十四)抓取策略
2021-05-09
[菜鸟的设计模式之旅]观察者模式
2021-05-09
Spring-继承JdbcDaoSupport类后简化配置文件内容
2021-05-09
Java基础IO流(一)
2021-05-09
Hibernate入门(四)---------一级缓存
2021-05-09
MySQL事务(学习笔记)
2021-05-09
一个web前端开发者的日常唠叨
2021-05-09
内存分配-slab分配器
2021-05-09
技术写作技巧分享:我是如何从写作小白成长为多平台优秀作者的?
2021-05-09
Jupyter Notebook 暗色自定义主题
2021-05-09
[Python学习笔记]组织文件
2021-05-09
基于Redo Log和Undo Log的MySQL崩溃恢复流程
2021-05-09
从RocketMQ的Broker源码层面验证一下这两个点
2021-05-09
如何正确的在项目中接入微信JS-SDK
2021-05-09
纵览全局的框框——智慧搜索
2021-05-09
快服务流量之争:如何在快服务中占领一席之地
2021-05-09
【活动】直播揭秘<如何从0开发HarmonyOS硬件>
2021-05-09
Unity平台 | 快速集成华为性能管理服务
2021-05-09
对模拟器虚假设备识别能力提升15%!每日清理大师App集成系统完整性检测
2021-05-09
使用Power BI构建数据仓库与BI方案
2021-05-09