初入android驱动开发之字符设备(一)

2023-10-31

大学毕业,初入公司,招进去的是android驱动开发工程师的岗位,那时候刚进去,首先学到的就是如何搭建kernel、android的编译环境,然后就是了解如何刷设备以及一些最基本的工具。如adb、fastboot、grep、minicom、kermit、svn、git、eclispe、ndk等相关的知识,记得那时候很挫,过去很多东西都不懂。到了那,一周,都是熟悉使用ubuntu,然后了解刷机的流程,了解uboot、kernel、ramdisk、recovery、system的作用以及相关的框架,印象最深的是,就搞定刷机这个问题,都折腾了很久,原因之前的文章也说了,usb id 没有配好,因为android设备在开机状态和fastboot的模式下,usb id是不一样的。在开机状态下,可以通过adb shell 进入android系统,但一切换fastboot模式,就发现无法找到设备。

当初,学习驱动开发的第一步,就是点亮一个LED灯,当然是基于android系统的,不是裸版上操作。正所谓初生牛犊不怕虎,先把百度,网上多的是例子,很高兴,马上copy一份代码,修改修改,试一试,编译通过,然后按着说明步骤,一步一步操作,发现insmod led.ko的时候,加载不成功,没办法,继续百度,搞了半天,没找到问题所在,然后尝试静态的编进去,别说,成功加载了,在/dev下找到自己的驱动,灯也亮了。当时,觉得完成任务,也没有多考虑什么,就向师父说,搞定了。就这样,一步一步的学下去,平台设备驱动模型,帧缓冲设备,输入子系统,中断,并态竞争,并开始慢慢解bug,调模块,UHF,nfc,rfid,电池,3G,音频,扫描头,wifi等一些,也许由于时间较紧,或者更可能也是因为得过且过,觉得在这家公司也能生存下去,对一些细节、原理性的东西并未深究。比如,内核层的数组越界表现为设备整个重启,JNI层数组越界,可能从andorid重启,app出现问题,表现应用挂掉。现在,事情不太多,想着把以前学的东西,从新梳理一下,并且深入的跟一下,毕竟不能浮于表面,应该多学习学习。但因中途电脑出现故障,所存资料全报废了,只能挑一些当时印象比较深刻的问题,重新学习一下。这次主要讲一下字符设备。

1. 什么是字符设备?

字符设备是 3 大类设备(字符设备、块设备和网络设备)中较简单的一类设备,提供连续的数据流,应用程序可以顺序读取,通常不支持随机存取。相反,此类设备支持按字节/字符来读写数据。

其驱动程序中完成的主要工作是初始化、添加和删除 cdev 结构体,申请和释放设备号,以及填充file_operations 结构体中的操作函数,实现file_operations 结构体中的read()、write()和ioctl()等函数是驱动设计的主体工作。


2. 字符设备的框架模型:

备注:此图片来源于:http://my.oschina.net/u/1169027/blog/191538)


3. 字符设备的重要的数据结构

3.1 一个简单的字符设备的例子:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/input.h>
#include <linux/platform_device.h>
#include <linux/miscdevice.h>



static int first_drv_open(struct inode *inode, struct file *file)
{
	printk("first_drv_open\n");
	return 0;
}

static int first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
	printk("first_drv_write\n");
	return 0;
}
/*3. 这个结构是字符设备驱动程序的核心
 * 当应用程序操作设备文件时所调用的open、read、write等函数,
 * 最终会调用这个结构中指定的对应函数
static struct file_operations first_drv_fops = {
    .owner  =   THIS_MODULE,   
    .open   =   first_drv_open,     
    .write	=	first_drv_write,	   
};
static struct class *firstdrv_class;
static struct device *firstdrv_class_dev;
int major;
// 执行insmod命令时就会调用这个函数 
static int first_drv_init(void)
{
	major = register_chrdev(0, "first_drv", &first_drv_fops); 
	firstdrv_class = class_create(THIS_MODULE, "firstdrv");
	firstdrv_class_dev = device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "myhello"); 
	printk("add ko,/dev/myhello \n");
	return 0;
}
/*
 * 执行rmmod命令时就会调用这个函数 ,注销函数,主要是释放你在注册是申请的资源,与你注册顺序相反,先注册的后释放。
 */
static void first_drv_exit(void)
{
	unregister_chrdev(major, "first_drv"); 
	device_unregister(firstdrv_class_dev);
	class_destroy(firstdrv_class);
	printk("del ko, \n");
}
//1. 我们一般从入口函数看起,先找到该字符设备的入口函数,
module_init(first_drv_init);
module_exit(first_drv_exit);
MODULE_LICENSE("GPL");

 3.2. 主要看入口函数:

在入口函数中,有3个主要的函数:几个重要的结构体:firstdrv_class,firstdrv_class_dev,cdev


先看一下 字符设备注册函数:
static inline int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)        
  参数说明:

   major:cdev的主设备号,此为0;  

  name:cdev的名称,此为 first_drv;

                  file_operation: cdev的文件操作接口,非常主要,一般为open、close、read、write、ioctl等,此只有open、write。


看下此函数如何调下去的:
__register_chrdev(major, 0, 256, name, fops);    这里可以看出,它把major的主设备号下的256个次设备号都归为此字符设备
   下面三个函数,是注册字符设备的3步:
   cd = __register_chrdev_region(major, baseminor, count, name);    
   cdev = cdev_alloc();    
   err = cdev_add(cdev, MKDEV(cd->major, baseminor), count);

 此中重要的结构体:

struct cdev *cdev;    
struct cdev {
struct kobject kobj;//内嵌的kobject对象    
struct module *owner;所属模块,通常为THIS_MODULE
const struct file_operations *ops;//文件操作结构体 
struct list_head list;
dev_t dev; //设备号    
unsigned int count;
};

然后看device_create,这是创建一个类,然后在类下创建一个设备,这个其实就是帮你在proc/ 和 dev/下创建设备节点,赋予相应的属性,

我们跟下代码,看看是如何调用的:


__class_create(struct module *owner, const char *name,struct lock_class_key *key)        
    __class_register(cls, key);    
    error = kset_register(&cp->subsys);    
    kobject_uevent(&k->kobj, KOBJ_ADD);    
    kobject_uevent_env(kobj, action, NULL);    
    /* environment buffer *//* 分配保存环境变量的内存 */
        env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL);
        /* complete object path */
        devpath = kobject_get_path(kobj, GFP_KERNEL);
/* default keys */  /*设置环境变量 */        
            retval = add_uevent_var(env, "ACTION=%s", action_string);
        retval = add_uevent_var(env, "DEVPATH=%s", devpath);
        retval = add_uevent_var(env, "SUBSYSTEM=%s", subsystem);
/* 调用应用程序: 比如mdev */
        /* 启动脚本 echo /sbin/mdev > /proc/sys/kernel/
        * 设置了uevent_helper为“/sbin/mdev“
        */    
        argv [0] = uevent_helper;
         argv [1] = (char *)subsystem;
        argv [2] = NULL;
        retval = add_uevent_var(env, "HOME=/");
       retva=add_uevent_var(env,"PATH=/sbin:/bin:/usr/sbin:/usr/bin");
        retval = call_usermodehelper(argv[0], argv,env->envp, UMH_WAIT_EXEC);    

再看一下设备创建:     
device_create()
    device_destroy(struct class *cls, dev_t devt);    
    dev = class_find_device(class, NULL, &devt, __match_devt);  


编译的Makefile 文件:

# 下面这个很重要,指向你的内核路径,在编译完成后,会出现Module.symvers
#在内核源码树根目录中,其中的Module.symvers文件就包含了内核所有的导出符号以及所有编译后模块的导出符号。
#在编译内核时,根目录下会生成Module.symvers文件,它包含了内核以及编译后的模块导出的所有符号。对于每一个符号,相应的CRC校验值也被保存,
#当内核编译选项CONFIG_MODVERSIONS关闭时,所有的CRC值都为0x00000000。
#Module.symvers文件主要有以下用途:
#1.列出vmlinux和所有模块的导出函数
#2.列出所有符号的CRC校验值
#若不指向,则insmod 模块时,会不成功。

KERN_DIR = /home/yl/workplace/svn4.0/urovo-sq39/samsung_android_kernel_3.0/  
all:
	make -C $(KERN_DIR) M=`pwd` modules 
clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order
obj-m	+= first_drv.o
编译:

只需要配置好内核的交叉编译环境即可,

编译: make

清除: make clean

4、 字符设备驱动的加载、卸载、测试

编译: 

yl@yl-Lenovo:~/workplace/svn4.0/urovo-sq39/study$ make -j4
make -C /home/yl/workplace/svn4.0/urovo-sq39/samsung_android_kernel_3.0/ M=`pwd` modules 
make[1]: Entering directory `/home/yl/workplace/svn4.0/urovo-sq39/samsung_android_kernel_3.0'
make[1]: warning: jobserver unavailable: using -j1.  Add `+' to parent make rule.
  CC [M]  /home/yl/workplace/svn4.0/urovo-sq39/study/first_drv.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /home/yl/workplace/svn4.0/urovo-sq39/study/first_drv.mod.o
  LD [M]  /home/yl/workplace/svn4.0/urovo-sq39/study/first_drv.ko
make[1]: Leaving directory `/home/yl/workplace/svn4.0/urovo-sq39/samsung_android_kernel_3.0'


推送到设备的目录下,并加载设备:

获得root权限

yl@yl-Lenovo:~/workplace/svn4.0/urovo-sq39/study$ adb root
adbd is already running as root

加载驱动,并查看:

yl@yl-Lenovo:~/workplace/svn4.0/urovo-sq39/study$ adb push first_drv.ko system/
1288 KB/s (59300 bytes in 0.044s)
yl@yl-Lenovo:~/workplace/svn4.0/urovo-sq39/study$ adb shell
root@android:/ # insmod system/first_drv.ko                                    
root@android:/ # ls dev/myhello -l                                             
crw------- root     root     248,   0 2015-08-04 15:31 myhello 

查看kernel日志,adb shell cat proc/kmsg ,会发现加载驱动时,打印的log。

这一部分,主要涉及到一些基本的命令,如adb push ,adb pull,adb root,

或 lsmod 查看系统加载的动态模块

    insmod 加载模块

    rmmod  删除模块

至于如何编译android文件系统下的字符设备测试程序,字符设备的高阶写法,如具体去操作某一个设备(led、按键),加入中断、并发、定时器等,留在下一编讲解,不过,字符设备的框架基本这样:

1. 分配
2. 设置
3. 注册
4. 硬件相关的代码






本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

初入android驱动开发之字符设备(一) 的相关文章

  • 00 SD卡知识简介

    具体可见如下文章 源地址 SD卡介绍
  • ARM体系结构简介 —— 迅为

    目录 单片机和ARM处理器 内存管理单元 MMU 高速缓冲存储器 CACHE 指令集 ARM的指令系统 ARM处理器工作模式 ARM处理器的内部寄存器 ARM处理器的异常 ARM中断向量 ARM架构的发展 单片机和ARM处理器 内存管理单元
  • mesa调试技巧

    技术关键字 mesa log系统 环境变量 目录 前言 一 gdb或vscode的断点调试 二 mesa log 系统的使用 总结 前言 软件调试技术是要求软件开发人员必备的一项技能 不同的问题具有不同的调试手段和方法 本文从mesa库的实
  • 驱动开发 作业 day7 9/18

    基于GPIO子系统实现led灯点亮 head h ifndef HEAD H define HEAD H 构建LED开关的功能码 不添加ioctl第三个参数 define LED ON IO l 1 define LED OFF IO l
  • RTC实时时钟实验(低功耗、纽扣电池供电)

    目录 I MX6U RTC 简介 硬件原理分析 实验程序编写 修改文件MCIMX6Y2 h 编写实验程序 编译下载验证 编写Makefile 和链接脚本 编译下载 实时时钟是很常用的一个外设 通过实时时钟我们就可以知道年 月 日和时间等信息
  • Linux SPI 驱动实验

    目录 Linux 下SPI 驱动框架简介 SPI 主机驱动 SPI 设备驱动 SPI 设备和驱动匹配过程 I MX6U SPI 主机驱动分析 SPI 设备驱动编写流程 SPI 设备信息描述 SPI 设备数据收发处理流程 硬件原理图分析 试验
  • Linux 块设备驱动实验

    一 块设备驱动要远比字符设备驱动复杂得多 不同类型的存储设备又对应不同的驱动子系统 本章我们重点学习一下块设备相关驱动概念 不涉及到具体的存储设备 1 什么是块设备 块设备是针对存储设备的 比如 SD 卡 EMMC NAND Flash N
  • Linux驱动_多点电容触摸

    一丶Linux下多点电容触摸驱动框架 电容触摸屏IC是FT5426 为IIC协议芯片 因此需要编写IIC驱动 触摸IC会发出中断信号 并在中断服务函数中上报信息 因此需要编写中断框架 触摸屏向Linux内核上报的信息都属于Input子系统
  • 正点原子STM32 H743完成RT Thread下的LAN8720 网卡驱动 LWIP跑起来

    目前RT官网对H743的支持力度还不理想 本想按照F407的搞定网卡的套路来搞定H743的网卡 因为phy也是LAN 8720 以为会很轻松 没想到却是一条遍布荆棘的路 好在已经有不少大佬做了不少工作 终于在巨人肩膀人完成了网卡的驱动 能p
  • STM32寄存器

    问题 什么是寄存器 什么是存储器映射 什么是寄存器映射 STM32架构 程序存放在FLASH中 const的常量存放在FLASH中 变量 全局 静态变量 存放在SRAM中 System总线主要读取寄存器 AHB 高速 总线上挂着SDIO 复
  • 基于STM32MP157调试MIPI-DSI屏幕

    平台 STM32MP157 屏幕 mipi dsi接口 1024x600 内核版本 linux5 4 本人是第一次调试mipi屏 在157这个平台上遇到的问题有一点多 接下来简单的描述下我的调试经验 一 先配置一下设备树DTB ltdc p
  • Linux INPUT 子系统实验

    目录 input 子系统 input 子系统简 input 驱动编写流程 input event 结构体 硬件原理图分析 实验程序编写 修改设备树文件 按键input 驱动程序编写 编写测试APP 运行测试 编译驱动程序和测试APP 运行测
  • 一个主设备号是如何支持多个次设备?

    1 主次设备号 参考博客 字符设备驱动详解 主次设备号 注册 卸载字符设备驱动 创建设备节点 地址映射 2 次设备号介绍 1 在老的驱动程序里是不需要次设备号的 在老版内核中注册驱动用register chrdev 函数 只需要传入主设备号
  • 采用通信方式控制台达B2伺服驱动器运行在速度模式

    目录 前言 一 伺服驱动器恢复出厂设置 二 伺服驱动器设置为速度模式 三 关闭告警信息 四 通讯功能设置 五 采用通信功能控制伺服驱动器按速度模式运行 总结 前言 最近 使用台达B2伺服驱动器做项目 项目中用伺服电机的速度模式驱动一个螺杆按
  • 5V转±12V无变压器双boost电路

    最近有个新项目 需要 10V范围的模拟量输出 非隔离 对于5V以下供电的控制板而言单端输出绝对没问题 可现在需要有正负输出 是少不了正负电源的 因此准备设计一个5V转 12V的电源 然后选择一个双向供电的运放 来实现单端模拟量信号向双向模拟
  • LCD笔记(4)分析内核自带的LCD驱动程序

    1 驱动程序框架 Linux驱动程序 驱动程序框架 硬件编程 在前面已经基于QEMU编写了LCD驱动程序 对LCD驱动程序的框架已经分析清楚 核心就是 分配fb info 设置fb info 注册fb info 硬件相关的设置 1 1 入口
  • LCD背光调节实验

    目录 LCD 背光调节简介 硬件原理分析 实验程序编写 编译下载验证 编写Makefile 和链接脚本 编译下载 不管是使用显示器还是手机 其屏幕背光都是可以调节的 通过调节背光就可以控制屏幕的亮度 在户外阳光强烈的时候可以通过调高背光来看
  • <Linux开发>驱动开发 -之- Linux LCD 驱动

    Linux开发 驱动开发 之 Linux LCD 驱动 交叉编译环境搭建 Linux开发 linux开发工具 之 交叉编译环境搭建 uboot移植可参考以下 Linux开发 之 系统移植 uboot移植过程详细记录 第一部分 Linux开发
  • uboot下UCLASS框架详解---结合项目工作中spi master和flash驱动开发

    文章目录 一 综述 二 UCLASS架构解析 2 1 uclass 2 2 udevice 2 3 uclass driver 2 4 driver 2 4 1 spi master driver 三 uboot代码解析 3 1 DM的初始
  • Windows驱动开发(一)第一个驱动程序

    首先我们需要了解 在操作系统中 是分两种权限的 一种是内核态 我们也称为0环 一种是用户态 称之为3环 而在我们的电脑中 驱动程序是运行在内核态的 这意味着和操作系统内核是在同一权限的 而普通的应用程序的权限是最低的 高权限谁不想拥有呢 因

随机推荐

  • 【Unity-Cinemachine相机】相机跟随之Transposer属性

    相机跟随和瞄准行为 Transposer 虚拟相机将在某个固定的偏移或距离上跟随目标移动 上面的偏移量就是Follow Offset Binding Mode决定Follow Offset是目标本地坐标系下的身后十米还是世界坐标系下的身后十
  • apache druid学习之Processes and servers

    Processes and servers Apache Druid Process types Druid has several process types Coordinator Overlord Broker Historical
  • 获取代理服务器ip列表的方法

    开源项目 https github com SpiderClub haipproxy 看爬代理的网址列表应该是最多的 CRAWLER TASKS name mogumiao com resource http www mogumiao co
  • MongoDB下载安装教程

    MongoDB下载安装教程 1 下载 2 安装 3 启动mongoDB 3 1 在windows的任务管理器中启动mongoDB 3 2 双击mongo exe 启动mongodb 3 3 在此处输入命令 操作数据库 4 操作mongoDB
  • linux经典面试问题

    你在准备 Linux 面试吗 我们准备了一些常见的 Linux 面试问题及其答案 如果您是初学者 具有一定的 Linux 知识或获得认证 或具有专业的 Linux 管理经验 那么下面的问答有助于您准备面试 1 什么是Linux及基本组件 L
  • java.net.UnknownServiceException: CLEARTEXT communication to www.httpbin.org not permitted by networ

    博主前些天发现了一个巨牛的人工智能学习网站 通俗易懂 风趣幽默 忍不住也分享一下给大家 点击跳转到网站 在Android9 0系统的手机上访问http的api 出现以下异常 java net UnknownServiceException
  • 理解RoIAlign实际操作

    我们的模型取一个大小为 512x512x3 宽度x高度x RGB 的图像输入 VGG16将其映射为一个 16x16x512的feature map 比例因子是 32 接下来 我们将使用其中一个proposed RoIs 145x200box
  • C++构造函数/析构函数 设置成private的原因

    将构造函数 析构函数声明为私有和保护的 那么对象如何创建 已经不能从外部调用构造函数了 但是对象必须被构造 应该如何解决 麻烦大家帮忙说明 关于构造 析构函数声明为私有和保护时的用法 提出这个问题 说明你已经对c 有所思考了 从语法上来讲
  • C++多线程之std::thread

    C 11 包含头文件 thread h 并使用命名空间std thread类提供的方法 方法 描述 thread 构造函数 在这里传入线程执行函数 和函数参数 get id 返回std thread id 这是一个类 可以间接得到unsig
  • 在centos7下docker 制作 java8镜像,上传到阿里云镜像仓库

    一 本地制作镜像 1 拉取centos4基础镜像 docker pull centos 7 创建目录 后面都在这个目录下操作 mkdir usr local docker app java projects java8 cd usr loc
  • vue 前端数据进行 RSA 加密、解密、签名、验签

    未整理完 关于加密 base64 加密是属于双向加密 就是加密后可以解密回来 MD5 是单向加密 就是加密后无法解密 MD5 加盐加密就是将 用户名 密码 进行MD5加密 类似都叫加盐 这里用户名就是盐值 支付宝的公钥私钥加密 公钥解密私钥
  • Intel Corporation SSD 750 Series 性能测试

    作者 QQ群 852283276 微信 arm80x86 微信公众号 青儿创客基地 B站 主页 https space bilibili com 208826118 机器 j2 j2 pc uname a Linux j2 pc 4 13
  • 【“码”上有你】智能合约库有奖征码第3期来袭

    智能合约库有奖征码 活动开展以来 伙伴们群策群力踊跃贡献 帮助夯实了合约库的基础功能 涌现了诸如共享经济 商品溯源等更多面向实际业务场景的合约样板 使得合约库更加满足开发者和行业多样化的诉求 为了让更多开发者参与到智能合约库组件优化中 拓展
  • 解析xml第二子节点

    import org w3c dom Document import org w3c dom Element import org w3c dom Node import org w3c dom NodeList import org xm
  • Linux下addr2line命令用法

    Linux下addr2line命令用于将程序指令地址转换为所对应的函数名 以及函数所在的源文件名和行号 当含有调试信息 g 的执行程序出现crash时 core dumped 可使用addr2line命令快速定位出错的位置 如果无法确定文件
  • 【1day】复现时空智友企业流程化管控系统文件上传漏洞

    注 该文章来自作者日常学习笔记 请勿利用文章内的相关技术从事非法测试 如因此产生的一切不良后果与作者无关 目录 一 漏洞描述 二 影响版本 三 资产测绘 四 漏洞复现 一 漏洞描
  • 构建面向未来的前端架构

    To build a house you need to put one brick on top of another 不积跬步无以至千里 大家好 我是柒八九 今天 我们来讲讲在 前端架构 要想在大项目中做到构建性能良好并且在架构方面具有
  • Hello Spring Cloud Alibaba(八)之使用spring security oAuth2

    Hello Spring Cloud Alibaba 八 之使用spring security oAuth2 oAuth2介绍 什么是 oAuth 什么是 Spring Security 认证服务器 导入包 配置文件 配置类 资源服务器 导
  • SQL关系代数——除法

    如何理解关系中的除法 定义 设关系 R除以关系S的结果为关系T 则T包含所有在R中但不在S中的属性及其值 且T的元组与S的元组的所有组合都在R 中 设有关系R S以及RS 如图所示 求RS S的结果 很容易求得结果为 张三 所以你很容易看出
  • 初入android驱动开发之字符设备(一)

    大学毕业 初入公司 招进去的是android驱动开发工程师的岗位 那时候刚进去 首先学到的就是如何搭建kernel android的编译环境 然后就是了解如何刷设备以及一些最基本的工具 如adb fastboot grep minicom