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

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 1

static 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;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值