zoukankan      html  css  js  c++  java
  • 【转】linux设备驱动程序之简单字符设备驱动

    原文网址:http://www.cnblogs.com/geneil/archive/2011/12/03/2272869.html

    一、linux系统将设备分为3类:字符设备、块设备、网络设备。使用驱动程序:

    1、字符设备:是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后数据。字符设备是面向流的设备,常见的字符设备有鼠标、键盘、串口、控制台和LED设备等。
    2、块设备:是指可以从设备的任意位置读取一定长度数据的设备。块设备包括硬盘、磁盘、U盘和SD卡等。

      每一个字符设备或块设备都在/dev目录下对应一个设备文件。linux用户程序通过设备文件(或称设备节点)来使用驱动程序操作字符设备和块设备。

    二、字符设备驱动程序基础:
    1、主设备号和次设备号(二者一起为设备号):
      一个字符设备或块设备都有一个主设备号和一个次设备号。主设备号用来标识与设备文件相连的驱动程序,用来反映设备类型。次设备号被驱动程序用来辨别操作的是哪个设备,用来区分同类型的设备。
      linux内核中,设备号用dev_t来描述,2.6.28中定义如下:
      typedef u_long dev_t;
      在32位机中是4个字节,高12位表示主设备号,低12位表示次设备号。

    可以使用下列宏从dev_t中获得主次设备号:                   也可以使用下列宏通过主次设备号生成dev_t:
    MAJOR(dev_t dev);                              MKDEV(int major,int minor);
    MINOR(dev_t dev);

    View Code

    2、分配设备号(两种方法):

    (1)静态申请:
    int register_chrdev_region(dev_t from, unsigned count, const char *name);

    View Code

    (2)动态分配:

    int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);

    View Code

    注销设备号

    void unregister_chrdev_region(dev_t from, unsigned count);


    创建设备文件
    利用cat /proc/devices查看申请到的设备名,设备号。
    (1)使用mknod手工创建:mknod filename type major minor
    (2)自动创建;

      利用udev(mdev)来实现设备文件的自动创建,首先应保证支持udev(mdev),由busybox配置。在驱动初始化代码里调用class_create为该设备创建一个class,再为每个设备调用device_create创建对应的设备。

    3、字符设备驱动程序重要的数据结构
    (1)struct file:代表一个打开的文件描述符,系统中每一个打开的文件在内核中都有一个关联的struct file。它由内核在open时创建,并传递给在文件上操作的任何函数,直到最后关闭。当文件的所有实例都关闭之后,内核释放这个数据结构。

    View Code

    (2)struct inode:用来记录文件的物理信息。它和代表打开的file结构是不同的。一个文件可以对应多个file结构,但只有一个inode结构。inode一般作为file_operations结构中函数的参数传递过来。
      inode译成中文就是索引节点。每个存储设备或存储设备的分区(存储设备是硬盘、软盘、U盘 ... ... )被格式化为文件系统后,应该有两部份,一部份是inode,另一部份是Block,Block是用来存储数据用的。而inode呢,就是用来存储这些数据的信息,这些信息包括文件大小、属主、归属的用户组、读写权限等。inode为每个文件进行信息索引,所以就有了inode的数值。操作系统根据指令,能通过inode值最快的找到相对应的文件。

    View Code

    (3)struct file_operations

    本部分来源于:http://blog.chinaunix.net/space.php?uid=20729583&do=blog&id=1884550,感谢chinahhucai的分享。

    View Code

    三、字符设备驱动程序设计

    1.设备注册
    在linux2.6内核中,字符设备使用struct cdev来描述;

    复制代码
    struct cdev
    {
    struct kobject kobj;//内嵌的kobject对象
    struct module *owner;//所属模块
    struct file_operations *ops;//文件操作结构体
    struct list_head list;
    dev_t dev;//设备号,长度为32位,其中高12为主设备号,低20位为此设备号
    unsigned int count;
    };
    复制代码

    字符设备的注册分为三个步骤:

    (1)分配cdev: struct cdev *cdev_alloc(void);
    (2)初始化cdev: void cdev_init(struct cdev *cdev, const struct file_operations *fops);
    (3)添加cdev: int cdev_add(struct cdev *p, dev_t dev, unsigned count)

    View Code

    2.设备操作的实现:file_operations函数集的实现(要明确某个函数什么时候被调用?调用来做什么操作?)
    特别注意:驱动程序应用程序的数据交换:
      驱动程序和应用程序的数据交换是非常重要的。file_operations中的read()和write()函数,就是用来在驱动程序和应用程序间交换数据的。通过数据交换,驱动程序和应用程序可以彼此了解对方的情况。但是驱动程序和应用程序属于不同的地址空间。驱动程序不能直接访问应用程序的地址空间;同样应用程序也不能直接访问驱动程序的地址空间,否则会破坏彼此空间中的数据,从而造成系统崩溃,或者数据损坏。安全的方法是使用内核提供的专用函数,完成数据在应用程序空间和驱动程序空间的交换。这些函数对用户程序传过来的指针进行了严格的检查和必要的转换,从而保证用户程序与驱动程序交换数据的安全性。这些函数有:

    unsigned long copy_to_user(void __user *to, const void *from, unsigned long n); 
    unsigned long copy_from_user(void *to, const void __user *from, unsigned long n);
    put_user(local,user);
    get_user(local,user);

    3.设备注销:void cdev_del(struct cdev *p);

    四、字符设备驱动小结:

      字符设备是3大类设备(字符设备、块设备、网络设备)中较简单的一类设备,其驱动程序中完成的主要工作是初始化、添加和删除cdev结构体,申请和释放设备号,以及填充file_operation结构体中操作函数,并实现file_operations结构体中的read()、write()、ioctl()等重要函数。如图所示为cdev结构体、file_operations和用户空间调用驱动的关系。

    五:字符设备驱动程序分析:

    (1)memdev.h

    View Code

    (2)memdev.c

    复制代码
    static mem_major = MEMDEV_MAJOR;

    module_param(mem_major, int, S_IRUGO);

    struct mem_dev *mem_devp; /*设备结构体指针*/

    struct cdev cdev;

    /*文件打开函数*/
    int mem_open(struct inode *inode, struct file *filp)
    {
    struct mem_dev *dev;

    /*获取次设备号*/
    int num = MINOR(inode->i_rdev);

    if (num >= MEMDEV_NR_DEVS)
    return -ENODEV;
    dev = &mem_devp[num];

    /*将设备描述结构指针赋值给文件私有数据指针*/
    filp->private_data = dev;

    return 0;
    }

    /*文件释放函数*/
    int mem_release(struct inode *inode, struct file *filp)
    {
    return 0;
    }

    /*读函数*/
    static ssize_t mem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
    {
    unsigned long p = *ppos; /*记录文件指针偏移位置*/
    unsigned int count = size; /*记录需要读取的字节数*/
    int ret = 0; /*返回值*/
    struct mem_dev *dev = filp->private_data; /*获得设备结构体指针*/

    /*判断读位置是否有效*/
    if (p >= MEMDEV_SIZE) /*要读取的偏移大于设备的内存空间*/
    return 0;
    if (count > MEMDEV_SIZE - p) /*要读取的字节大于设备的内存空间*/
    count = MEMDEV_SIZE - p;

    /*读数据到用户空间:内核空间->用户空间交换数据*/
    if (copy_to_user(buf, (void*)(dev->data + p), count))
    {
    ret = - EFAULT;
    }
    else
    {
    *ppos += count;
    ret = count;

    printk(KERN_INFO "read %d bytes(s) from %d ", count, p);
    }

    return ret;
    }

    /*写函数*/
    static ssize_t mem_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos)
    {
    unsigned long p = *ppos;
    unsigned int count = size;
    int ret = 0;
    struct mem_dev *dev = filp->private_data; /*获得设备结构体指针*/

    /*分析和获取有效的写长度*/
    if (p >= MEMDEV_SIZE)
    return 0;
    if (count > MEMDEV_SIZE - p) /*要写入的字节大于设备的内存空间*/
    count = MEMDEV_SIZE - p;

    /*从用户空间写入数据*/
    if (copy_from_user(dev->data + p, buf, count))
    ret = - EFAULT;
    else
    {
    *ppos += count; /*增加偏移位置*/
    ret = count; /*返回实际的写入字节数*/

    printk(KERN_INFO "written %d bytes(s) from %d ", count, p);
    }

    return ret;
    }

    /* seek文件定位函数 */
    static loff_t mem_llseek(struct file *filp, loff_t offset, int whence)
    {
    loff_t newpos;

    switch(whence) {
    case 0: /* SEEK_SET */ /*相对文件开始位置偏移*/
    newpos = offset; /*更新文件指针位置*/
    break;

    case 1: /* SEEK_CUR */
    newpos = filp->f_pos + offset;
    break;

    case 2: /* SEEK_END */
    newpos = MEMDEV_SIZE -1 + offset;
    break;

    default: /* can't happen */
    return -EINVAL;
    }
    if ((newpos<0) || (newpos>MEMDEV_SIZE))
    return -EINVAL;

    filp->f_pos = newpos;
    return newpos;

    }

    /*文件操作结构体*/
    static const struct file_operations mem_fops =
    {
    .owner = THIS_MODULE,
    .llseek = mem_llseek,
    .read = mem_read,
    .write = mem_write,
    .open = mem_open,
    .release = mem_release,
    };

    /*设备驱动模块加载函数*/
    static int memdev_init(void)
    {
    int result;
    int i;

    dev_t devno = MKDEV(mem_major, 0);

    /* 申请设备号,当xxx_major不为0时,表示静态指定;当为0时,表示动态申请*/
    /* 静态申请设备号*/
    if (mem_major)
    result = register_chrdev_region(devno, 2, "memdev");
    else /* 动态分配设备号 */
    {
    result = alloc_chrdev_region(&devno, 0, 2, "memdev");
    mem_major = MAJOR(devno); /*获得申请的主设备号*/
    }

    if (result < 0)
    return result;

    /*初始化cdev结构,并传递file_operations结构指针*/
    cdev_init(&cdev, &mem_fops);
    cdev.owner = THIS_MODULE; /*指定所属模块*/
    cdev.ops = &mem_fops;

    /* 注册字符设备 */
    cdev_add(&cdev, MKDEV(mem_major, 0), MEMDEV_NR_DEVS);

    /* 为设备描述结构分配内存*/
    mem_devp = kmalloc(MEMDEV_NR_DEVS * sizeof(struct mem_dev), GFP_KERNEL);
    if (!mem_devp) /*申请失败*/
    {
    result = - ENOMEM;
    goto fail_malloc;
    }
    memset(mem_devp, 0, sizeof(struct mem_dev));

    /*为设备分配内存*/
    for (i=0; i < MEMDEV_NR_DEVS; i++)
    {
    mem_devp[i].size = MEMDEV_SIZE;
    mem_devp[i].data = kmalloc(MEMDEV_SIZE, GFP_KERNEL);
    memset(mem_devp[i].data, 0, MEMDEV_SIZE);
    }

    return 0;

    fail_malloc:
    unregister_chrdev_region(devno, 1);

    return result;
    }

    /*模块卸载函数*/
    static void memdev_exit(void)
    {
    cdev_del(&cdev); /*注销设备*/
    kfree(mem_devp); /*释放设备结构体内存*/
    unregister_chrdev_region(MKDEV(mem_major, 0), 2); /*释放设备号*/
    }

    MODULE_AUTHOR("David Xie");
    MODULE_LICENSE("GPL");

    module_init(memdev_init);
    module_exit(memdev_exit);
    复制代码



    (3)应用程序(测试文件):app-mem.c

    复制代码
    #include <stdio.h>

    int main()
    {
    FILE *fp0 = NULL;
    char Buf[4096];

    /*初始化Buf*/
    strcpy(Buf,"Mem is char dev!");
    printf("BUF: %s ",Buf);

    /*打开设备文件*/
    fp0 = fopen("/dev/memdev0","r+");
    if (fp0 == NULL)
    {
    printf("Open Memdev0 Error! ");
    return -1;
    }

    /*写入设备*/
    fwrite(Buf, sizeof(Buf), 1, fp0);

    /*重新定位文件位置(思考没有该指令,会有何后果)*/
    fseek(fp0,0,SEEK_SET);

    /*清除Buf*/
    strcpy(Buf,"Buf is NULL!");
    printf("BUF: %s ",Buf);


    /*读出设备*/
    fread(Buf, sizeof(Buf), 1, fp0);

    /*检测结果*/
    printf("BUF: %s ",Buf);

    return 0;

    }
    复制代码

    测试步骤:

    1)cat /proc/devices看看有哪些编号已经被使用,我们选一个没有使用的XXX。
    2)insmod memdev.ko
    3)通过"mknod /dev/memdev0 c XXX 0"命令创建"/dev/memdev0"设备节点。
    4)交叉编译app-mem.c文件,下载并执行:
    #./app-mem,显示:
    Mem is char dev!
  • 相关阅读:
    HBase 高性能加入数据
    Please do not register multiple Pages in undefined.js 小程序报错的几种解决方案
    小程序跳转时传多个参数及获取
    vue项目 调用百度地图 BMap is not defined
    vue生命周期小笔记
    解决小程序背景图片在真机上不能查看的问题
    vue项目 菜单侧边栏随着右侧内容盒子的高度实时变化
    vue项目 一行js代码搞定点击图片放大缩小
    微信小程序进行地图导航使用地图功能
    小程序报错Do not have xx handler in current page的解决方法
  • 原文地址:https://www.cnblogs.com/wi100sh/p/4242119.html
Copyright © 2011-2022 走看看