Android 尝试写一份Linux 字符设备驱动
从事android工作几年时间,功底不是很深,一直围绕这android系统定制移植开发,慢慢的从应用层接触到framework层,在接触到hal,目前从事的工作wifi驱动相关工作。但是没有系统的学习过驱动知识,现在跟着阳光玻璃杯学习一份驱动的简单实现。
字符设备是指在I/O传输过程中以字符为单位进行传输的设备,如键盘,打印机
字符设备驱动主要做如下3件事:
1,定义一个结构体static struct file_operations变量,在里面定义一些设备的打开、关闭、读、写、控制函数
2,在结构体外分别实现结构体中定义的这些函数
3,向内核中注册或删除驱动模块
static struct file_operations myDriver_fops = {
.owner=THIS_MODULE,
.write=myDriver_write,
.read=myDriver_read,
.ioctl=myDriver_ioctl,
.open=myDriver_open,
.release=myDriver_release,
};
此结构体中规定了驱动程序想应用程序提供的操作接口。
实现write, 从应用程序接收数据送到硬件
static ssize_t myDriver_write(struct file *filp, const char *buf, size_t count, loff_t *f_pos){
.....
copy_from_user(&myDriver_Buffer[*f_pos], buf, fill_size); //用于把用户态的数据拷到内核态,实现数据的传送
....
}
实现read, 从硬件读取数据并交给应用程序
static ssize_t myDriver_read(struct file *filp, char *buf, size_t count, loff_t *f_pos){
....
copy_to_user(buf, &myDriver_Buffer[*f_pos], read_size); //用于实现把内核态的数据拷到用户态下
....
}
实现ioctl, 就是为应用程序提供对硬件行为的控制
static int myDriver_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg){
switch(cmd){
case MYDRV_IOCTL0:
break;
case MYDRV_IOCTL1:
break;
}
return 0;
}
实现open, 应用程序打开设备时对设备进行初始化
static int myDriver_open(struct inode *inode, struct file *filp){
return 0;
}
实现release, 当应用程序关闭设备时处理设备的关闭操作
static int myDriver_release(struct inode * inode, struct file *filp){
return 0;
}
实现一个简单的字符设备驱动,功能写一个字符串进去,然后再把他读出来。驱动创建/dev/hello节点,/sys/class/hello/hello/val 设备节点, /proc/hello/hello设备节点。/sys/class/hello/hello/val 主要用于快速测试,/dev/hello才是供我们使用的节点。
一,驱动源码
此份依据阳光玻璃杯的代码修改过,已经在电视机顶盒android 7.1.2版本上测试过。这里也是直接贴出代码,方便日后查看学习
一共含有4个文件,hello.c hello.h Kconfig Makefile,此份驱动没有内置到系统源码下面,是新建了一个文件夹,采用编译链的方式
结构如下:
appkernel
├── hello.c
├── hello.h
├── Kconfig
└── Makefile
hello.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<linux/sched.h>
#include<linux/errno.h>
#include<linux/fcntl.h>
#include<linux/poll.h>
#include<linux/seq_file.h>
#include<linux/mutex.h>
#include<linux/workqueue.h>
#include<asm/uaccess.h>
#include<linux/slab.h>
#include<linux/kernel.h>#include "hello.h"//主设备和从设备号变量
static int hello_major = 0;
static int hello_minor = 0;//设备类别和设备变量
static struct class* hello_class = NULL;
static struct hello_test_dev* hello_dev = NULL;//传统的设备文件操作方法
static int hello_open(struct inode* inode, struct file* filp);
static int hello_release(struct inode* inode, struct file* filp);
static ssize_t hello_read(struct file* filp, char __user* buf, size_t count, loff_t* f_pos);
static ssize_t hello_write(struct file* filp, const char __user* buf, size_t count, loff_t* f_pos);
static int hello_create_proc(void);
static void hello_remove_proc(void);
static ssize_t hello_proc_read(struct file *filp, char __user *buf, size_t len, loff_t *ppos);
static ssize_t hello_proc_write(struct file *filp, const char __user *buf, size_t len, loff_t *ppos);
static int proc_seq_open(struct inode *inode, struct file *file);
/*** 设备文件操作方法表*/
static struct file_operations hello_fops = {.owner = THIS_MODULE,.open = hello_open,.release = hello_release,.read = hello_read,.write = hello_write,
};static struct file_operations hello_proc_ops={.owner = THIS_MODULE,.read = seq_read,.write = hello_proc_write,.llseek=seq_lseek,.release = seq_release,.open=proc_seq_open,
};//访问设置属性方法
static ssize_t hello_val_show(struct device * dev, struct device_attribute* attr, char *buf);
static ssize_t hello_val_store(struct device* dev, struct device_attribute* attr, const char *buf, size_t count);//定义设备属性
static DEVICE_ATTR(val, S_IRUGO | S_IWUSR, hello_val_show, hello_val_store);/*打开设备方法*/
static int hello_open(struct inode* inode, struct file* filp){struct hello_test_dev* dev;/*将自定义设备结构体保存在文件指针的私有数据域中,以便访问设备时拿来用*/dev = container_of(inode->i_cdev, struct hello_test_dev, dev);filp->private_data = dev;return 0;
}//设备文件释放时调用
static int hello_release(struct inode* inode, struct file* filp){return 0;
}//读设备的寄存器val的值
static ssize_t hello_read(struct file* filp, char __user *buf, size_t count, loff_t* f_pos){ssize_t err = 0;struct hello_test_dev* dev = filp->private_data;//同步访问if(down_interruptible(&(dev->sem))){return -ERESTARTSYS;}if(count < sizeof(dev->val)){goto out;}/*将寄存器val的值拷贝到用户提供的缓冲区*/if(copy_to_user(buf, dev->val, sizeof(dev->val))){err = -EFAULT;goto out;}err = sizeof(dev->val);
out:up(&(dev->sem));return err;
}//写设备的寄存器值val
static ssize_t hello_write(struct file* filp, const char __user *buf, size_t count, loff_t* f_pos){struct hello_test_dev* dev = filp->private_data;ssize_t err = 0;//同步访问if(down_interruptible(&(dev->sem))){return -ERESTARTSYS;}/*将用户提供的缓冲区的值写到设备寄存器去*/if(copy_from_user(dev->val, buf, count)){err = -EFAULT;goto out;}err = sizeof(dev->val);
out:up(&(dev->sem));return err;
}/*** 通过devfs文件系统访问方法,把val看成设备的一个属性,通过读写这个属性来对设备进行访问,* 主要是实现hello_val_show和hello_val_store两个方法,同时定义了两个内部使用的访问val值的方法__hello_get_val和__hello_set_val:*/
//读取寄存器val的值到缓冲区buf中,内部使用
static ssize_t __hello_get_val(struct hello_test_dev* dev, char * buf){if(down_interruptible(&(dev->sem))){return -ERESTARTSYS;}up(&(dev->sem));return snprintf(buf, PAGE_SIZE, "%s\n",dev->val);
}//写字符串到内存
static ssize_t __hello_set_val(struct hello_test_dev* dev, const char* buf, size_t count){if (down_interruptible(&(dev->sem))){return -ERESTARTSYS;}printk(KERN_ALERT"_hello_set_val.buf: %s count:%zu\n", buf, count);printk(KERN_ALERT"_hello_set_val.dev->val:%s count:%zu\n",dev->val, count);strncpy(dev->val, buf, count);printk(KERN_ALERT"_hello_set_val.dev->val:%s count:%zu\n" ,dev->val, count);up(&(dev->sem));return count;
}//读取设备属性val
static ssize_t hello_val_show(struct device* dev, struct device_attribute* attr, char * buf){struct hello_test_dev * hdev = (struct hello_test_dev*) dev_get_drvdata(dev);printk(KERN_ALERT"hello_val_show.\n");printk(KERN_ALERT"%s \n", hdev->val);return __hello_get_val(hdev,buf);
}//写设备属性val
static ssize_t hello_val_store(struct device* dev, struct device_attribute* attr, const char* buf, size_t count){struct hello_test_dev* hdev = (struct hello_test_dev*) dev_get_drvdata(dev);printk(KERN_ALERT"hello_val_store.buf:%s count:%zu\n", buf, count);return __hello_set_val(hdev, buf, count);
}/*** 定义通过proc文件系统访问方法,主要实现了hello_proc_read和hello_proc_write两个方法,* 同时定义了在proc文件系统创建和删除文件的方法hello_create_proc和hello_remove_proc*/
static char *str = NULL;
static void *my_seq_start(struct seq_file *m, loff_t *pos)
{if(0== *pos){++*pos;return (void *)1;}return NULL;
}static void *my_seq_next(struct seq_file *m, void *v, loff_t *pos)
{return NULL;
}static void my_seq_stop(struct seq_file *m, void *v)
{}static int my_seq_show(struct seq_file *m, void *v)
{seq_printf(m, "current kernel time is %llu\n", (unsigned long long )get_jiffies_64());seq_printf(m, "str is %s\n", str);return 0;
}static struct seq_operations my_seq_fops =
{.start = my_seq_start,.next = my_seq_next,.stop = my_seq_stop,.show = my_seq_show,
};static int proc_seq_open(struct inode *inode, struct file *file)
{return seq_open(file, &my_seq_fops);
}//读取设备寄存器val的值,保存在page缓冲区中
static ssize_t hello_proc_read(struct file *filp, char __user *buf, size_t len, loff_t *ppos){char * ptr = PDE_DATA(file_inode(filp));printk(KERN_ALERT"ptr=%s\n",ptr);printk("proc read\n");return 0;
}//把缓冲区的值buff保存到设备寄存器val中去
static ssize_t hello_proc_write(struct file *filp, const char __user *buf, size_t len, loff_t *ppos){char *temp = kzalloc((len+1), GFP_KERNEL);if(!temp) return -ENOMEM;printk(KERN_ALERT"proc write\n");if(copy_from_user(temp,buf, len)){kfree(temp);return -EFAULT;}kfree(str);str = temp;return len;
}struct proc_dir_entry *entry = NULL;
struct proc_dir_entry *proc_root = NULL;//创建 /proc/hello文件
static int hello_create_proc(void){char temp[] = "hello test\0";proc_root = proc_mkdir("hello", NULL);if(proc_root == NULL){printk(KERN_ERR"Can't create /proc/hello directory\n");return -EFAULT;}entry = proc_create_data(HELLO_DEVICE_PROC_NAME, S_IRUGO | S_IWUSR | S_IWGRP, proc_root, &hello_proc_ops, &temp);if(entry == NULL){printk(KERN_ERR"Can't create /proc/hello_dir/hello file\n");return -EFAULT;}return 0;
}//删除 /proc/hello文件
static void hello_remove_proc(void){remove_proc_entry(HELLO_DEVICE_PROC_NAME, proc_root);
}//初始化设备
static int __hello_setup_dev(struct hello_test_dev* dev){int err;dev_t devno = MKDEV(hello_major, hello_minor);memset(dev, 0, sizeof(struct hello_test_dev));cdev_init(&(dev->dev), &hello_fops);dev->dev.owner = THIS_MODULE;dev->dev.ops = &hello_fops;err = cdev_add(&(dev->dev), devno, 1);if (err){return err;}sema_init(&(dev->sem),10);dev->val = kmalloc(20, GFP_KERNEL);strncpy(dev->val, "hello", sizeof("hello"));return 0;
}//模块加载方法
static int __init hello_init(void){int err = -1;dev_t dev = 0;struct device* temp = NULL;printk(KERN_ALERT"hello_init.\n");//动态分配主设备和从设备号err = alloc_chrdev_region(&dev, 0, 1, HELLO_DEVICE_NODE_NAME);if(err < 0){printk(KERN_ALERT"Failed to alloc char dev region.\n");goto fail;}hello_major = MAJOR(dev);hello_minor = MINOR(dev);//分配hello设备结构体变量hello_dev = kmalloc(sizeof(struct hello_test_dev), GFP_KERNEL);if(!hello_dev){err = -ENOMEM;printk(KERN_ALERT"Failed to alloc hello_dev.\n");goto unregister;}//初始化设备err = __hello_setup_dev(hello_dev);if(err){printk(KERN_ALERT"Failed to setup dev:%d\n",err);goto cleanup;}//在/sys/class/目录下创建设备类别目录hellohello_class = class_create(THIS_MODULE, HELLO_DEVICE_CLASS_NAME);if(IS_ERR(hello_class)){err = PTR_ERR(hello_class);printk(KERN_ALERT"Failed to create hello class.\n");goto destroy_cdev;}//在/dev 目录和 /sys/cleass/hello 目录下分别创建设备文件hellotemp = device_create(hello_class, NULL, dev, "%s", HELLO_DEVICE_FILE_NAME);if(IS_ERR(temp)){err = PTR_ERR(temp);printk(KERN_ALERT"Failed to create hello device.\n");goto destroy_class;}//在 /sys/class/hello/hello 目录下创建属性文件valerr = device_create_file(temp, &dev_attr_val);if(err < 0){printk(KERN_ALERT"Failed to create attribute val.\n");goto destroy_device;}dev_set_drvdata(temp, hello_dev);//创建/proc/hello文件hello_create_proc();printk(KERN_ALERT"Succedded to initialize hello device.\n");return 0;destroy_device:device_destroy(hello_class, dev);
destroy_class:class_destroy(hello_class);
destroy_cdev:cdev_del(&(hello_dev->dev));
cleanup:kfree(hello_dev);
unregister:unregister_chrdev_region(MKDEV(hello_major, hello_minor), 1);
fail:return err;
}//模块卸载方法
static void __exit hello_exit(void) {dev_t devno = MKDEV(hello_major, hello_minor);printk(KERN_ALERT"hello_exit.\n");//删除/proc/hello文件hello_remove_proc();//销毁设备类别和设备if(hello_class){device_destroy(hello_class, MKDEV(hello_major, hello_minor));class_destroy(hello_class);}//删除字符设备和释放设备内存if(hello_dev){cdev_del(&(hello_dev->dev));kfree(hello_dev);}if(hello_dev->val != NULL){kfree(hello_dev->val);}//释放设备号unregister_chrdev_region(devno, 1);
}MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Test Driver");module_init(hello_init);
module_exit(hello_exit);
hello.h
#ifndef _HELLO_TEST_H_
#define _HELLO_ANDROID_H_#include <linux/cdev.h>
#include <linux/semaphore.h>#define HELLO_DEVICE_NODE_NAME "hello"
#define HELLO_DEVICE_FILE_NAME "hello"
#define HELLO_DEVICE_CLASS_NAME "hello"
#define HELLO_DEVICE_PROC_NAME "hello"
/*
*定义字符设备结构体
*/
struct hello_test_dev {char * val;struct semaphore sem; //信号量struct cdev dev; //内嵌的字符设备
};#endif
Kconfig
config HELLOtristate "Test Driver"default nhelpThis is the test driver.
Makefile
EXTRA_CFLAGS += -Werror -Wno-unused
EXTRA_CFLAGS += -O1
ARCH:=arm64
CROSS_COMPILE:=aarch64-linux-gnu-
KSRC:=/home/fht/work/chengshi/n-amlogic/out/target/product/p281/obj/KERNEL_OBJobj-$(CONFIG_HELLO)+= hello.o
export CONFIG_HELLO = mall:modulesmodules:$(MAKE) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) -C $(KSRC) M=$(shell pwd) modules.PHONY: modules cleanclean:$(MAKE) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) -C $(KSRC) M=$(shell pwd) clean
其中 ARCH,CROSS_COMPILE, KSRC 需要根据系统的编译链修改,否则编译出来的ko文件可能导致insmod失败
CROSS_COMPILE 可以使用AOSP/prebuilts/gcc/linux-x86下gcc, KSRC需要先把AOSP整编,然后才能使用对应.config文件
如果有已知kernel的.config文件,可以修改为已知文件的绝对路径
二,编译
以上文件写好后,我们就可以在当前目录下执行make, 编译驱动文件了
hello.c hello.ko hello.mod.c .hello.mod.o.cmd .hello.o.cmd Makefile Module.symvers
hello.h .hello.ko.cmd hello.mod.o hello.o Kconfig modules.order .tmp_versions
hello.ko文件就是我们需要的驱动文件了
三,应用/测试
使用adb push 将hello.ko文件放置到system/lib/modules下,最好是能有已经root过的设备上测试。
adb push hello.ko system/lib/modules
加载刚刚放置进来的hello.ko
adb shell; cd system/lib/modules;
insmod hello.ko 如果没有报错说明加载没问题,我们可以使用lsmod查看已经加载的驱动列表,Module列是否有名称为hello,有,即说明加载成功
可以看到/dev/hello /sys/class/hello/hello/val /proc/hello/hello 文件已经生成
我们进入/sys/class/hello/hello目录下 输入:echo hah > val 看串口打印是否有输出
[230352.249030@2] hello_val_store.buf:haha
[230352.249030@2] count:5
[230352.249803@2] _hello_set_val.buf: haha
[230352.249803@2] count:5
[230352.256572@2] _hello_set_val.dev->val:123
[230352.256572@2] oworld count:5
[230352.263440@2] _hello_set_val.dev->val:haha
[230352.263440@2] world count:5
以上输出说明已经写入成功,使用命令 cat val 读取刚刚写入的字符串
[230510.445927@0] hello_val_show.
[230510.446117@0] haha
[230510.446117@0] world
看看haha打印出来说明写入是成功的
四,使用/dev/hello节点写简单程序调用测试
在驱动的同级目录下新建hellotest目录,在目录下新建jni目录,在jni目录下新建Android.mk Application.mk hellotest.c 三个文件
hellotest.c 这个文件是用来打开访问/dev/hello文件节点,读写字符串操作
/*#include<stdio.h>int main(){return 0;
}*/#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>#define HELLO_DEVICE "/dev/hello"int main(){int fd = -1;char * str = malloc(20);fd = open(HELLO_DEVICE, O_RDWR);if (fd == -1){printf("Failed to open device %s.\n", HELLO_DEVICE);return -1;}printf("Read original value:\n");read(fd, str, 20);printf("read data: %s\n", str);strncpy(str, "nihao", sizeof("nihao"));printf("write %s\n", str);write(fd, str, sizeof(str));printf("Read the value again:\n");read(fd, str, 20);printf("read data: %s\n", str);close(fd);return 0;
}
Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_CFLAGS := -Wall -Wextra -Werror -Wunused
LOCAL_SRC_FILES := \hellotest.c
LOCAL_MODULE := hellotest
include $(BUILD_EXECUTABLE)
Application.mk
APP_ABI := armeabi
以上三个文件创建成功,在该路径下执行ndk-build -B ,编译可执行测试程序
[armeabi] Compile thumb : hellotest <= hellotest.c
[armeabi] Executable : hellotest
[armeabi] Install : hellotest => libs/armeabi/hellotest
将编译出来的libs/armeabi/hellotest可执行程序push到开发板内,
adb push hellotest /data
修改权限 adb shell ; chmod 777 /data/hellotest
执行./hellotest
输出
Read original value:
read data: hello
write nihao
Read the value again:
read data: nihao
开始读出hello字串,后面写入nihao ,读出nihao,可见我们测试驱动成功
感谢 阳光玻璃杯 https://blog.csdn.net/u011913612/article/details/52516303
Android 尝试写一份Linux 字符设备驱动相关推荐
- linux字符设备驱动在哪里设置,从点一个灯开始学写Linux字符设备驱动!
原标题:从点一个灯开始学写Linux字符设备驱动! [导读] 前一篇文章,介绍了如何将一个hello word模块编译进内核或者编译为动态加载内核模块,本篇来介绍一下如何利用Linux驱动模型来完成一 ...
- linux设备模型 字符设备,Linux 字符设备驱动模型之框架解说
一.软件操作硬件设备模型 在进行嵌入式开发的过程中,在常做的事情就是驱动配置硬件设 备,然后根据功能需求使用硬件设备,实现功能的逻辑.如下图为其 相互之间的关系. 如上图所示: 驱动程序:主要作为操作 ...
- Linux 字符设备驱动结构(四)—— file_operations 结构体知识解析
前面在 Linux 字符设备驱动开发基础 (三)-- 字符设备驱动结构(中) ,我们已经介绍了两种重要的数据结构 struct inode{...}与 struct file{...} ,下面来介绍另 ...
- Linux字符设备驱动
/*Linux字符设备驱动源代码scdd.c*/ #include <linux/init.h> /*模块头文件*/ #include <linux/module.h> # ...
- Linux 字符设备驱动的编写
Linux 字符设备驱动的编写 作者:解琛 时间:2020 年 8 月 17 日 Linux 字符设备驱动的编写 一.Linux 设备分类 二.open() 三.数据结构 3.1 struct fil ...
- Linux 字符设备驱动结构(三)—— file、inode结构体及chardevs数组等相关知识解析
前面我们学习了字符设备结构体cdev Linux 字符设备驱动开发 (一)-- 字符设备驱动结构(上) 下面继续学习字符设备另外几个重要的数据结构. 先看下面这张图,这是Linux 中虚拟文件系统. ...
- ()shi linux字符设备,Linux字符设备驱动基础(三)
Linux字符设备驱动基础(三) 6 创建设备节点 6.1 手动创建设备节点 查看申请的设备名及主设备号: cat /proc/devices # cat /proc/devices Characte ...
- linux字符设备文件的打开操作,Linux字符设备驱动模型之字符设备初始化
因为Linux字符设备驱动主要依赖于struct cdev结构,原型为: 所以我们需要对所使用到的结构成员进行配置,驱动开发所使用到的结构成员分别为:[unsigned int count;].[de ...
- linux生成驱动编译的头文件,嵌入式Linux字符设备驱动——5生成字符设备节点
嵌入式Linux字符设备驱动开发流程--以LED为例 前言 留空 头文件 #include 查看系统设备类 ls /sys/class 设备类结构体 文件(路径):include/linux/devi ...
最新文章
- 这次终于不再为 iptables 犯迷糊了!
- ubuntu18 安装 chrome
- JavaScript校验身份证,包含省份、长度、出生年月日、校验位的检测、性别、年龄...
- 斐波那契数列的3种求法及几种素数筛法
- 对于Eclipse的正确用法
- 【DFS】HDU 1364 POJ 1071 Illusive Chase
- JavaScript-严格检查模式
- python中easygui有几种_一、Python 模块EasyGui详细介绍
- 子类重写方法aop切不到_Spring-aop 全面解析(从应用到原理)
- java 僵尸进程_有关僵尸进程和孤儿进程的解释
- 查看tomcat版本信息
- 分析Redis集群原理
- Android识别图片中的颜色
- java 调用 pb dll_[转载]一个java调用delphi写的dll问题,郁闷了一天一晚解决
- 花生壳 内网穿透配置
- 360度全景视频后视镜
- css 侧栏跟随_JS+CSS实现侧边栏跟随浏览器滚动效果
- 超好看的樱花飘落网络科技官网源码
- SWOOLE进阶-06网络IO模型-阻塞模型
- iStat Menus for mac中文