【安卓开发系列 -- 系统开发】安卓开发流程梳理 -- 从底层到 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 ] 罗升阳

上一篇:【安卓开发系列 -- 系统开发】基于 android studio 的 android 源码调试环境搭建
下一篇:【安卓开发系列 -- 系统开发】字符设备驱动基础

发表评论

最新留言

哈哈,博客排版真的漂亮呢~
[***.90.31.176]2025年04月16日 03时57分07秒