【安卓开发系列 -- 系统开发】安卓开发流程梳理 -- 从底层到 APP (Linux 驱动开发)
【0】开发环境简介
1. 主机,Win10;虚拟机,Ubuntu 16.04
2. 开发板,AIO-3399C 六核 AI 开发板
3. android 源码,android 7.1
【1】Linux 驱动开发
Linux 驱动开发 Makefile 文件
ifneq ($(KERNELRELEASE),)
obj-m += freg.o
else
# 指定内核代码树
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 clean
modules:
# 编译内核驱动模块
$(MAKE) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) -C $(KERNELDIR) M=$(PWD) modules
clean:
# 清除中间文件
@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_PATH
LOCAL_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-userdebug
AI 版:lunch rk3399_firefly_aioc_ai-userdebug
mmm ./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 /data
Windows 控制台中,adb push <Linux 驱动模块> /data;上传 Linux 驱动模块到安卓系统中;
问题 4. No command 'mmm' found
解决方案 :
使用m、mm、mmm命令之前要在android源码目录下执行命令
. build/envsetup.sh
参考致谢
本博客为博主学习笔记,同时参考了网上众博主的博文以及相关专业书籍,在此表示感谢,本文若存在不足之处,请批评指正。
【1】Android 系统源代码情景分析(第三版) [ M ] 罗升阳
【3】驱动模块编译错误提示--implicit declaration of function ‘kmalloc’
【5】在Ubuntu上为Android系统编写Linux内核驱动程序 + 编写加载动态模块ko
【11】linux驱动开发--copy_to_user 、copy_from_user函数实现内核空间数据与用户空间数据的相互访问