Android标准架构实例分析之编写最简单的hello驱动

来源:互联网 发布:linux alias 多条命令 编辑:程序博客网 时间:2024/06/05 17:35

Android标准架构实例分析之编写最简单的hello驱动

摘要:

本文主要实现了一个虚拟的字符设备驱动–hello_device 。这个设备驱动会创建相关的cdev数据结构和file_operations,并通过class_createdevice_create在sys文件系统上创建相关的目录和文件,为udev创建相关的设备文件提供资源。最终会在/dev/下面创建/dev/hello这个文件节点。并提供文件节点测试程序和测试方法。

  1. udev工作原理
  2. 创建并初始化cdev加入到链表中
  3. 测试代码分析和编译
  4. 详细代码列表

udev工作原理

udev实现了用户空间动态的方法管理/dev目录。/dev目录是设备目录,里面的文件就是设备文件。udev文件系统在用户空间工作,它可以根据sysfs文件系统导出的信息(设备号(dev)等),动态建立和删除设备文件。而不再需要使用mknod来手动建立设备文件,也不必为查找设备号(尤其是驱动中动态申请产生的设备号)而头疼。

为了能让udev能够正常工作,一个设备驱动程序要做的事情是:通过sysfs将驱动程序所控制设备的主设备号和此设备号导出到用户空间。对于那些使用子系统分配主设备号和次设备号的驱动程序,该工作已经有子系统完成,驱动程序不做任何事情。这样的子系统有:tty,misc,usb,input,scsi,block,i2c,network,framebuffer子系统。如果驱动程序通过调用cdev_init函数,自己处理获得主设备号和次设备号,那么为了能正确的使用udev,需要对驱动程序进行修改。Udev在sysfs中的/class/目录树下搜索名为dev的文件,这样内核通过/sbin/hotplug接口调用它的时候,就能获得分配给特定设备的主设备号和次设备号。

*注:
/dev,设备文件存储目录,应用程序通过对这些文件的读写和控制,可以访问实际的设备;
/sys/devices目录,按照设备挂接的总线类型,组织成层次结构,保存了系统所有的设备;是文件系统管理设备的最重要的目录结构;
/sys/dev下有两个子目录,block和char,存放的是块设备和字符设备的主次号码,形式为(major:minor),它指向/sys/devices目录下的设备。*

字符设备驱动的分析和编译

①主设备编号和次设备编号的申请:

传统上, 主编号标识设备相连的驱动,大部分设备仍然按照一个主编号一个驱动的原则来组织.
次编号被内核用来决定引用哪个设备.
在内核中, dev_t 类型,用来持有设备编号 – 主次部分都包括.

下面的例子是一个动态申请设备编号的过程代码:

/* the major device number */static int hello_major = 0;//主设备编号static int hello_minor = 0;//次设备编号dev_t devno = 0;//devno = MKDEV(int major, int minor); /*动态分配主设备和从设备号*/ret = alloc_chrdev_region(&devno, hello_minor, DEVICE_SUM, "hello");hello_major = MAJOR(devno);hello_minor = MINOR(devno);

通过 /proc/devices 或者 ls -l /dev/ 可以查看设备号和设备信息。

②创建并初始化cdev加入到链表中

struct cdev 是内核的内部结构, 代表字符设备。

下面是一个字符设备最简单的初始化和添加到链表的方式:

/* init the file_operations structure */struct file_operations hello_fops ={    .owner = THIS_MODULE,    .open = hello_open,    .release = hello_release,    .read = hello_read,    .write = hello_write,};struct cdev *cdev;cdev = cdev_alloc();cdev->owner = THIS_MODULE;cdev->ops = &hello_fops;

但是, 偶尔你会想将 cdev 结构嵌入一个你自己的设备特定的结构; scull 这样做了. 在这种情况下, 你应当初始化你已经分配的结构, 使用:

void cdev_init(struct cdev *cdev, struct file_operations *fops);

下面是将初始化好的cdev添加到字符设备到链表:

 if ((ret = cdev_add(cdev, devno, 1))){    printk(KERN_NOTICE "hello:Error %d adding hello.\n", ret);    return 0;}

这里, dev 是 cdev 结构, num 是这个设备响应的第一个设备号, count 是应当关联到设备的设备号的数目. 常常 count 是 1。

③在sysfs中创建一些必要的数据结构

前面已经分析了udev的工作原理,只是①②两个操作是不足以完成设备节点在/dev下面生成,必须要操作sysfs进行一些必要的设备目录和文件的创建,下面是具体的代码:

struct device* temp = NULL;static struct class* hello_class = NULL;hello_class = class_create(THIS_MODULE, "hello"); /*在/dev/目录和/sys/class/hello目录下分别创建设备文件hello*/temp = device_create(hello_class, NULL, devno, "%s", "hello");

如果/sys/class/hello/hello创建成功,udev会导入这些信息并创建/dev/hello设备节点。

测试结果如下图:

这里写图片描述

④ copy_to_user & copy_from_user

#include <asm/uaccess.h>

这个包含文件声明内核代码使用的函数来移动数据到和从用户空间.

unsigned long copy_from_user (void *to, const void *from, unsigned long count);unsigned long copy_to_user (void *to, const void *from, unsigned long count);

在用户空间和内核空间拷贝数据.

示例代码:

static int global_var = 0; /* global var */if(copy_to_user(buf, &global_var, sizeof(int))){return -EFAULT;}if(copy_from_user(&global_var, buf, sizeof(int))){    return -EFAULT;}

⑤编译

创建同级目录下的Makefile文件,文件内容为:

obj-y += hello.o

在上一级Makefile中添加:

obj-y += hello/

测试代码分析和编译

测试代码主要是对文件节点/dev/hello进行open,read,write操作,主要注意文件的权限问题,使用

adb shell chmod 777 /dev/hello //修改权限

文件的操作详细参考:百度或者谷歌,搜索:Linux 文件操作 系统调用;

编译过程:

将测试代码放在Android /external/hello/文件夹下,编写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)

执行:mmm external/hello/ 会在system/bin/下面生成测试程序可执行文件:hello

执行: make snod打包system.img 下载后,执行测试程序结果:

这里写图片描述

详细代码列表

驱动文件hello.c

/* * hello.c -- A simple virtual char device driver */#include <linux/module.h>  #include <linux/types.h>  #include <linux/fs.h>  #include <linux/errno.h>  #include <linux/mm.h>  #include <linux/sched.h>  #include <linux/init.h>  #include <linux/cdev.h>  #include <linux/slab.h>#include <asm/io.h>  #include <linux/device.h>#include <asm/uaccess.h>  MODULE_LICENSE("GPL");MODULE_AUTHOR("eliot shao");#define DEVICE_SUM 1static int hello_open(struct inode *inode, struct file *filp);static int hello_release(struct inode *, struct file *filp);static ssize_t hello_read(struct file*, char*, size_t, loff_t*);static ssize_t hello_write(struct file*, const char*, size_t, loff_t*);/* the major device number */static int hello_major = 0;static int hello_minor = 0;static struct class* hello_class = NULL;/* init the file_operations structure */struct file_operations hello_fops ={    .owner = THIS_MODULE,    .open = hello_open,    .release = hello_release,    .read = hello_read,    .write = hello_write,};/* define a cdev device */struct cdev *cdev;static int global_var = 0; /* global var *//* module init */static int __init hello_init(void){        int ret = 0;        struct device* temp = NULL;        dev_t devno = 0;        printk("hello:hello_init .\n");        /*动态分配主设备和从设备号*/        ret = alloc_chrdev_region(&devno, hello_minor, DEVICE_SUM, "hello");        if(ret < 0) {            printk(KERN_ALERT"hello:Failed to alloc char dev region.\n");            goto fail;        }        hello_major = MAJOR(devno);        hello_minor = MINOR(devno);        cdev = cdev_alloc();        cdev->owner = THIS_MODULE;        cdev->ops = &hello_fops;        if ((ret = cdev_add(cdev, devno, 1)))        {            printk(KERN_NOTICE "hello:Error %d adding hello.\n", ret);            return 0;        }        else            printk("hello:hello register success.\n");            /*在/sys/class/目录下创建设备类别目录hello*/        hello_class = class_create(THIS_MODULE, "hello");        if(IS_ERR(hello_class)) {            ret = PTR_ERR(hello_class);            printk(KERN_ALERT"Failed to create hello class.\n");            goto destroy_cdev;        }                /*在/dev/目录和/sys/class/hello目录下分别创建设备文件hello*/        temp = device_create(hello_class, NULL, devno, "%s", "hello");        if(IS_ERR(temp)) {            ret = PTR_ERR(temp);            printk(KERN_ALERT"Failed to create hello device.");            goto destroy_class;        }          return ret;destroy_class:    class_destroy(hello_class);destroy_cdev:    cdev_del(cdev);fail:    return ret;}/* module exit */static void __exit hello_exit(void){    dev_t devno = MKDEV(hello_major, 0);    /* remove cdev from kernel */    cdev_del(cdev);    /* unregister the device driver */    unregister_chrdev_region(devno, 1);    /* free the dev structure */    if(cdev)        kfree(cdev);    cdev = NULL;}/* open device */static int hello_open(struct inode *inode, struct file *filp){    int ret = 0;    printk("KERNEL:open success.\n");    return ret;}/* release device */static int hello_release(struct inode *inode, struct file *filp){    printk("KERNEL:release success.\n");    return 0;}/* read device */static ssize_t hello_read(struct file *filp, char *buf, size_t len, loff_t *off){    printk("KERNEL:reading...\n");    if(copy_to_user(buf, &global_var, sizeof(int)))    {        return -EFAULT;    }    return sizeof(int);}/* write device */static ssize_t hello_write(struct file *filp, const char *buf, size_t len, loff_t *off){    printk("KERNEL:writing...\n");    if(copy_from_user(&global_var, buf, sizeof(int)))    {        return -EFAULT;    }    return sizeof(int);}/* module register */module_init(hello_init);module_exit(hello_exit);

测试程序hello.c

#include <stdio.h>#include <stdlib.h>#include <fcntl.h>#include <unistd.h>#define DEVICE_NAME "/dev/hello"int main(int argc, char** argv){    int fd = -1;    int val = 0;    fd = open(DEVICE_NAME, O_RDWR);    if(fd == -1) {        printf("Failed to open device %s\n", DEVICE_NAME);        return -1;    }    printf("Read original value:\n");    read(fd, &val, sizeof(val));    printf("%d\n", val);    val = 5;    printf("Write value %d to %s.\n\n", val, DEVICE_NAME);        write(fd, &val, sizeof(val));    printf("Read the value again:\n");        read(fd, &val, sizeof(val));        printf("%d\n", val);    close(fd);    return 0;}
1 0
原创粉丝点击