编写一个Android Linux内核驱动并用C可执行程序测试
2018-03-11 23:50
519 查看
罗升阳博客里是典型的字符型设备的驱动实现,但是我打算实现一个misc设备驱动。misc设备本质上还是一个字符设备,主设备号为10,但是它实现起来比较简单。
我们驱动要实现的功能是,用户空间调用设备的write函数写入一个int类型的值,然后再调用read函数把它读出来。功能简单,但这一来一往足以让我们了解驱动的写法。
然后在此文件夹里新建hello.c文件,驱动代码就写在这个文件里。
首先定义设备名称常量和用来接收数据的变量
然后进行函数声明和设备操作函数表file_operations的填充
填充miscdevice结构体
给这个设备取名为hello,让Linux为这个设备动态分配次设备号,并且指定它的函数表为刚刚写好的hello_fops。
接下来实现声明的read和write函数
在Linux中内核空间和用户空间的内存是不能随便互相访问的,但是copy_to_user和copy_from_user提供了内核和用户传输数据的通道。
copy_to_user的函数原型如下
unsigned long copy_to_user(void *to, const void *from, unsigned long n);
to:目标地址(用户空间)
from:源地址(内核空间)
n:将要拷贝数据的字节数
copy_from_user的函数原型如下
unsigned long copy_from_user(void *to, const void *from, unsigned long n);
to:目标地址(内核空间)
from:源地址(用户空间)
n:将要拷贝数据的字节数
明白了copy_to_user和copy_from_user函数的功能,以及参数的意义。hello_read和hello_write的实现也就像Hello World一样简单了。
模块初始化和卸载方法
在初始化函数中使用misc_register函数注册misc设备,然后注册了模块初始化和卸载函数。
最后给这个驱动签上自己的名字,就写完了。
新建Makefile文件,写上如下内容。
在drivers目录下的Kconfig文件里添加一行
Makefile文件里添加一行
然后就可以重新编译内核,并用新编译的内核启动模拟器了。
首先应该明确的是,这个测试函数是运行在用户空间的。它先打开了hello设备,并调用read方法,最终调用到内核的hello_read方法,把内核中value的值
拷贝给了用户空间的val,然后将它打印出来。再往下,改变了用户空间val的值,通过write方法并把它拷贝给了内核空间的value,紧接着又读出来。
新建Android.mk文件,写上如下内容。
将这两个文件放在hello文件夹里,然后把hello文件夹放到aosp下的external文件夹下。
在aosp目录下执行
看到如下输出,说明编译成功
最后把编译出来的hello可执行文件push到模拟器上的system/bin路径下执行。
如果一切正确,会看到如下输出
我们驱动要实现的功能是,用户空间调用设备的write函数写入一个int类型的值,然后再调用read函数把它读出来。功能简单,但这一来一往足以让我们了解驱动的写法。
驱动编写
驱动是内核层面的事情,所以我们先进入到内核源码的drivers目录,新建一个hello文件夹用来装驱动的代码。cd /kernel/goldfish/drivers mkdir hello cd hello
然后在此文件夹里新建hello.c文件,驱动代码就写在这个文件里。
首先定义设备名称常量和用来接收数据的变量
// 设备名称 #define DEVICE_NAME "hello" //用来操作的数据 static int value = -1;
然后进行函数声明和设备操作函数表file_operations的填充
/*声明要实现的设备操作函数 */ static int hello_read(struct file* filp, char __user *buf, size_t count, loff_t* f_pos); static int hello_write(struct file* filp, const char __user *buf, size_t count, loff_t* f_pos); /*设备文件操作函数表*/ static struct file_operations hello_fops = { .owner = THIS_MODULE, .read = hello_read, .write = hello_write, };
填充miscdevice结构体
static struct miscdevice misc = { .name = DEVICE_NAME, .minor = MISC_DYNAMIC_MINOR, .fops = &hello_fops };
给这个设备取名为hello,让Linux为这个设备动态分配次设备号,并且指定它的函数表为刚刚写好的hello_fops。
接下来实现声明的read和write函数
// 读取 static ssize_t hello_read(struct file* filp, char __user *buf, size_t count, loff_t* f_pos) { ssize_t err = -1; if(copy_to_user(buf, &value, sizeof(value))) { err = -EFAULT; goto out; } out: return err; } // 写入 static ssize_t hello_write(struct file* filp, const char __user *buf, size_t count, loff_t* f_pos) { ssize_t err = -1; if(copy_from_user(&value, buf, count)) { err = -EFAULT; goto out; } out: return err; }
在Linux中内核空间和用户空间的内存是不能随便互相访问的,但是copy_to_user和copy_from_user提供了内核和用户传输数据的通道。
copy_to_user的函数原型如下
unsigned long copy_to_user(void *to, const void *from, unsigned long n);
to:目标地址(用户空间)
from:源地址(内核空间)
n:将要拷贝数据的字节数
copy_from_user的函数原型如下
unsigned long copy_from_user(void *to, const void *from, unsigned long n);
to:目标地址(内核空间)
from:源地址(用户空间)
n:将要拷贝数据的字节数
明白了copy_to_user和copy_from_user函数的功能,以及参数的意义。hello_read和hello_write的实现也就像Hello World一样简单了。
模块初始化和卸载方法
static int hello_init(void) { int ret; ret = misc_register(&misc); return ret; } static void hello_exit(void) { misc_register(&misc); } //注册驱动初始化函数 module_init(hello_init); //注册驱动卸载函数 module_exit(hello_exit);
在初始化函数中使用misc_register函数注册misc设备,然后注册了模块初始化和卸载函数。
最后给这个驱动签上自己的名字,就写完了。
MODULE_AUTHOR("windcake"); MODULE_DESCRIPTION("windcake driver learn"); MODULE_LICENSE("GPL");
准备编译
在hello文件夹里新建Kconfig文件,写上如下内容config HELLO tristate "Hello Android Driver" default y help This is the hello android driver.
新建Makefile文件,写上如下内容。
obj-$(CONFIG_HELLO) += hello.o
在drivers目录下的Kconfig文件里添加一行
source "drivers/hello/Kconfig"
Makefile文件里添加一行
obj-$(CONFIG_HELLO) += hello/
然后就可以重新编译内核,并用新编译的内核启动模拟器了。
测试
测试程序源码如下#include <stdio.h> #include <stdlib.h> #include <fcntl.h> #define DEVICE_NAME "/dev/hello" int main(int argc, char** argv) { int fd = -1; int val = 0; fd = open(DEVICE_NAME, O_RDWR); read(fd, &val, sizeof(val)); printf("Original value:%d.\n", val); val = 1; printf("Write value %d to %s.\n", val, DEVICE_NAME); write(fd, &val, sizeof(val)); read(fd, &val, sizeof(val)); printf("Read the value again:%d.\n", val); close(fd); return 0; }
首先应该明确的是,这个测试函数是运行在用户空间的。它先打开了hello设备,并调用read方法,最终调用到内核的hello_read方法,把内核中value的值
拷贝给了用户空间的val,然后将它打印出来。再往下,改变了用户空间val的值,通过write方法并把它拷贝给了内核空间的value,紧接着又读出来。
新建Android.mk文件,写上如下内容。
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE_TAGS := optional LOCAL_MODULE := hello LOCAL_SRC_FILES := $(call all-subdir-c-files) include $(BUILD_EXECUTABLE)
将这两个文件放在hello文件夹里,然后把hello文件夹放到aosp下的external文件夹下。
在aosp目录下执行
mmm ./external/hello
看到如下输出,说明编译成功
Install: out/target/product/generic/system/bin/hello make: Leaving directory '/home/windcake/Documents/aosp' #### make completed successfully (3 seconds) ####
最后把编译出来的hello可执行文件push到模拟器上的system/bin路径下执行。
adb remount adb push '/aosp/out/target/product/generic/system/bin/hello' system/bin adb shell cd system/bin ./hello
如果一切正确,会看到如下输出
Original value: -1. Write value 5 to /dev/hello. Read the value again: 5.
相关文章推荐
- Android底层开发(二)之编写驱动测试程序 第五步
- andriod驱动之旅-在Ubuntu上为Android系统内置C可执行程序测试Linux内核驱动程序(4)
- android 编写命令行测试程序
- 在Ubuntu上为Android系统内置C可执行程序测试Linux内核驱动程序
- 在Ubuntu上为Android系统内置C可执行程序测试Linux内核驱动程序
- Android中widget编写注意事项——1(程序成功执行Done却没有widget)
- android 编写命令行测试程序
- 在Ubuntu上为Android系统内置C可执行程序测试Linux内核驱动程序
- 如何在android程序执行初始化的时候弹出一个PopupWindow 触发的事件是在初始化的时候。
- 编写在Android的Linux系统中直接运行的可执行程序 - 检测CPU能力
- 编写一个基本的Android程序
- 从驱动测试程序到apk整个过程的编写
- (二)在Ubuntu上为Android系统内置C可执行程序测试Linux内核驱动程序
- 在Ubuntu上为Android系统内置C可执行程序测试Linux内核驱动程序
- 在Ubuntu上为Android系统内置C可执行程序测试Linux内核驱动程序
- 编写一个基本的Android程序
- 2.编写一个程序,计算0到10的平方和立方,并用制表符(/t)打印值
- 在Ubuntu上为Android系统内置C可执行程序测试Linux内核驱动程序
- 在Ubuntu上为Android系统内置C可执行程序测试Linux内核驱动程序
- 在Ubuntu上为Android系统内置C可执行程序测试Linux内核驱动程序