您的位置:首页 > 运维架构 > Linux

编写一个Android Linux内核驱动并用C可执行程序测试

2018-03-11 23:50 519 查看
罗升阳博客里是典型的字符型设备的驱动实现,但是我打算实现一个misc设备驱动。misc设备本质上还是一个字符设备,主设备号为10,但是它实现起来比较简单。

我们驱动要实现的功能是,用户空间调用设备的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.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: