一个字符型设备的简单例子

一个字符型设备的简单例子

一、概述

作为一名Android应用开发工程师,略懂些linux设备驱动的知识是有必要的,因为我们常见的Log框架和Binder框架都离不开底层驱动的支持。而考虑到Log和Binder又都是字符型设备,所以能够对字符型设备的知识有些了解,会对我们理解Android的框架有一定帮助。

   
 

从截图中可以看出:它们都是miscdevice,也是字符型设备。

本文通过一个非常简单的例子,希望能够帮助上层开发的同学对字符型设备的驱动有些许了解,这样以后我们研究Binder也会扫除一些障碍。这个例子包括两部分:一是在dev目录下创建一个叫hello_mycdev的字符型设备,这个设备有一个int的值;二是在system/bin目录下写一个可执行程序,类似grep这样的,可以去修改hello_mycdev这个设备内的value值。效果如下图所示:

                                                                                                     

在开始讲解例子之前,先说明两点:一、这会是个结构不完整、漏洞百出、有一堆bug的例子;二、请做驱动的同学就不要看了。

 

二、写驱动文件hello_cdev.c

#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/uaccess.h>
//这里是设备名称,最终能够看到/dev/ hello_mycdev这样一个字符型设备文件
#define HELLO_CDEV_NAME "hello_mycdev"
//自定义设备的结构体,它就代表了我们的设备,其中misc表明我们是一个miscdevice
struct my_dev
{
	struct miscdevice misc;
	int value;
};
//自定义此设备的读函数和写函数,这里只是声明,定义在下面
static ssize_t my_cdev_read(struct file* filp, char __user *buf, size_t count, loff_t* f_pos);
ssize_t my_cdev_write(struct file* filp, const char __user *buf, size_t count, loff_t* f_pos);
//任何一个设备都应该定义自己的file_operations,这里就相当于告诉内核
//我们这个设备的读函数和写函数分别是my_cdev_read和my_cdev_write,
//当对此设备调用read和write时,就会自动调用这两个方法。
static struct file_operations my_cdev_fops = 
{
	.owner = THIS_MODULE,
	.read = my_cdev_read,
	.write = my_cdev_write,
};
//声明一个我们这个设备的变量,这里对它初始化,包括前面定义的my_cdev_fops和设备名称HELLO_CDEV_NAME
struct my_dev my_devp = 
{
	.misc = 
	{
		.minor = MISC_DYNAMIC_MINOR,
		.name = HELLO_CDEV_NAME,
		.fops = &my_cdev_fops,
		.parent = NULL,
	},
	.value = 0,
};
//设备读函数的实现,非常简单,只是把value的值读出而已
static ssize_t my_cdev_read(struct file* filp, char __user *buf, size_t count, loff_t* f_pos)
{
	copy_to_user(buf, &(my_devp.value), sizeof(my_devp.value));
}
//设备写函数的实现,也非常简单,只是写入value的值而已
ssize_t my_cdev_write(struct file* filp, const char __user *buf, size_t count, loff_t* f_pos)
{
	copy_from_user(&(my_devp.value), buf, count);
}
//初始化我们的设备,这里只做了两件事,一是调用系统方法注册misc设备,而是为value赋初值
static int __init my_cdev_init(void)
{
	int ret;
	ret = misc_register(&(my_devp.misc));
	my_devp.value = 6;
	//我们会在后面看到这条log的
	printk(KERN_ERR "yutao--init ok!");
	return 0;
}
//为我们的模块指明初始化方法
module_init(my_cdev_init);
 

三、编译驱动文件

首先,我们将前面的文件命名为hello_cdev.c,然后我们再写一个Makefile文件,文件内容如下(只有一句):obj-m := hello_cdev.o。

然后,我们需要把这两个文件放到一个目录下,命名该目录为hello_cdev。将这个目录放到代码中:alps/kernel/drivers/(我的代码是MTK平台)。

之后单编此模块,编译命令:./mk r kkernel/drivers/hello_cdev。

最后,我们能够在alps/kernel/out/drivers/hello_cdev目录下生成文件hello_cdev.ko,把它push到手机的/system/lib/modules目录下,shell进入到此目录,执行insmodhello_cdev.ko就可以把我们这个模块安装到手机中了。你要验证我们的设备是否ok,可以看看手机的/dev目录下是否已经有了hello_mycdev这个文件,当然你也可以查看代码里那条log是否输出:

                                                                                      

这就能确定我们的模块已经成功安装了(insmod—猜测是install module的缩写,安装模块;rmmod—猜测是remove module,作用是卸载模块;lsmod—猜测是ls module,可以查看当前安装了哪些模块)。

 

四、写一个可执行程序,来读取和写入设备的value值(hello.c)

这个程序非常简单,就是我们常见的操作文件的方法:先open、然后read或write,然后close,代码都没有注释的必要:

         为了单编模块,我们还需要一个Android.mk文件,这个文件要注意的是最后一句,因为我们需要编译生成的是一个可执行文件。

 

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#define DEVICE_NAME "/dev/hello_mycdev"
int main(int argc, char** argv)
{
	int fd = -1;
	int val = 0;
	fd = open(DEVICE_NAME, O_RDWR);
	
	printf("Read original value: ");
	read(fd, &val, sizeof(val));
	printf("%d.\n", val);

	printf("Please input the value you want to set: ");
	scanf("%d",&val);
	printf("\n");

    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;
}



单编模块:./mk mm external/hello/就会在out相关目录下的system/bin下生成了一个hello文件,把它push到手机的/system/bin目录下,然后shell进入手机,直接输入hello命令,就可以像我之前描述的那样查看和修改我们那个设备的value值了。

 

五、小结

Linux驱动对于我来说是非常庞大和复杂的内容,好在我并不需要了解它的全貌,学习它只是为了帮助我在学习Android框架时少一些障碍。希望这对和我一样的同学有些许帮助。也希望驱动的同事能够写一些通俗易懂的技术分享,非常感谢!

你可能感兴趣的:(一个字符型设备的简单例子)