【笔记】Linux驱动学习第二章

作者:Exculivor
日期:2015年06月26日

本次学习内容:

  • 设备驱动程序简介和分类
  • 设备驱动程序的开发步骤
  • 示例驱动程序和相应讲解
  • 示例应用程序和相应讲解

设备驱动程序简介和分类

设备驱动程序简介

我们先来看《Linux设备驱动程序》一书给出的描述:

设备驱动程序在Linux内核中扮演着特殊的角色。它们是一个个独立的“黑盒子”,使某个特定硬件响应一个定义良好的内部编程接口,这些接口完全隐藏了设备的工作细节。用户的操作通过一组标准化的调用执行,而这些调用独立于特定的驱动程序。将这些调用映射到作用于设施及硬件的设备特有操作上,则是设备驱动程序的任务。

简单来说设备驱动程序就是将硬件功能映射成几个接口。应用程序通过对这些接口的操作就可以实现硬件设备的某些功能。这样应用程序的编写者就可以从无数繁杂的硬件操作规则中解脱出来从而可以专注于应用程序的优化上来。

编写设备驱动程序的注意事项

《Linux设备驱动程序》一书中给出了以下建议:

  • 提供给用户尽量多的选择,不强加给用户多余的特定策略。
  • 把握编写驱动程序要占用的时间
  • 尽量保持程序简单而不至于错误丛生

设备驱动程序分类

Linux将设备分成三种基本类型:

  • 字符模块
  • 块模块
  • 网络模块

通常来说,每个模块为上述其中一种。

字符设备

字符(char)设备是个能够像字节流(类似文件)一样被访问的设备,由字符设备驱动程序来实现这种特性。字符设备驱动程序通常至少要实现open、close、read和write系统调用。

典型字符设备如:字符终端(/dev/console)、串口(/dev/ttys0)等。他们可以通过文件系统节点来访问,比如/dev/tty1和/dev/lp0等。他们和普通文件的唯一区别在于,普通文件的访问指针可以前后移动,而大多数字符设备文件是一个只能顺序访问的数据通道。

块设备

块设备和字符设备类似,也是通过/dev目录下的文件系统节点来访问。块设备(例如磁盘)上能够容纳文件系统。

在大多数Unix系统中,进行I/O操作时块设备每次只能传输一个或者多个完整的块,而每块包含512个字节(或2的更高次幂字节的数据)。Linux可以让应用程序像字符设备一样地读写块设备,允许一次传递任意多字节的数据。因而块设备和字符设备的区别仅仅在于内核内部管理数据的方式,也就是内核及驱动程序之间的接口,而这些不同对于用户来讲是透明的。在内核中,和字符驱动程序相比,块驱动具有完全不同的接口。

网络接口

任何网络事务都经过一个网络接口形成,即一个能够和其他主机交换数据的设备。其由内核中的网络子系统驱动,负责发送和接收数据包,但它不需要了解每项事务如何映射到实际传送的数据包。Unix访问网络接口的方法仍然是给它们分配一个唯一的名字(比如eth0),但这个名字在文件系统中不存在对应的节点。

内核和网络设备驱动程序间的通信,完全不同于内核和字符设备以及块设备驱动程序之间的通信,内核调用一套和数据包传输相关的函数,而不是read、write等。

设备驱动程序的开发步骤

大多数情况下编写驱动程序的难点并不是硬件的具体操作,而是弄清楚现有驱动程序的框架,在这个框架中加入这个硬件。
编写驱动还需要注意许多地方,比如:驱动程序可能同时被多个进程使用,这需要考虑并发的问题;尽可能发挥硬件的作用以提高效率;处理硬件的各种异常情况(即使效率低),否则出错时可能导致整个系统崩溃。

  • 查看原理图、数据手册,了解设备的操作方法。
  • 在内核中找到相近的驱动程序,以它为模板进行开发,有时候需要从零开始。
  • 实现驱动程序的初始化:比如向内核注册这个驱动程序,这样应用程序传入文件名时,内核才能找到相应的驱动程序。
  • 设计所要实现的操作,比如open、close、read、write等函数。
  • 实现中断服务(中断并不是每个设备驱动所必须的)。
  • 编译该驱动程序到内核中,或者用insmod命令加载。
  • 测试驱动程序。

示例驱动程序和相应讲解

示例代码

#include <linux/mm.h>
#include <linux/miscdevice.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/mman.h>
#include <linux/random.h>
#include <linux/init.h>
#include <linux/raw.h>
#include <linux/tty.h>
#include <linux/capability.h>
#include <linux/ptrace.h>
#include <linux/device.h>
#include <linux/highmem.h>
#include <linux/crash_dump.h>
#include <linux/backing-dev.h>
#include <linux/bootmem.h>
#include <linux/splice.h>
#include <linux/pfn.h>
#include <linux/export.h>
#include <linux/io.h>
#include <linux/aio.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <asm/uaccess.h>

#define DEVICE_NAME     "Exculivor" 
#define Exculivor_MAJOR     0

static struct class *Exculivor_drv_class;

static int Exculivor_drv_open(struct inode *inode, struct file *file)
{
    printk("%s:Hello Exculivor\n", __FUNCTION__);
    return 0;
}

static ssize_t Exculivor_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
    printk("%s:Hello Exculivor\n", __FUNCTION__);
    return 0;
}

static ssize_t Exculivor_drv_write(struct file *file, const char __user *buf, size_t size, loff_t *ppos)
{
    printk("%s:Hello Exculivor\n", __FUNCTION__);
    return 0;
}

static struct file_operations Exculivor_drv_fops = {
    .owner  = THIS_MODULE,
    .open   = Exculivor_drv_open,     
    .read   = Exculivor_drv_read,      
    .write  = Exculivor_drv_write,     
};

int major;

static int __init Exculivor_drv_init(void)
{
    major = register_chrdev(Exculivor_MAJOR, DEVICE_NAME, &Exculivor_drv_fops);
    if (major < 0)
    {
        printk(DEVICE_NAME " can't register major number\n");
        return major;
    }

    Exculivor_drv_class = class_create(THIS_MODULE, "Exculivor");
    device_create(Exculivor_drv_class, NULL, MKDEV(major, 0), NULL, "Exculivor");

    printk("%s:Hello Exculivor\n", __FUNCTION__);
    return 0;
}

static void __exit Exculivor_drv_exit(void)
{
    unregister_chrdev(major, "Exculivor");  
    device_destroy(Exculivor_drv_class, MKDEV(major, 0));
    class_destroy(Exculivor_drv_class);
    printk("%s:Hello Exculivor\n", __FUNCTION__);
}


module_init(Exculivor_drv_init);
module_exit(Exculivor_drv_exit);

MODULE_AUTHOR("http://blog.csdn.net/chuangjianndsc");
MODULE_VERSION("0.1.0");
MODULE_DESCRIPTION("RT5350 FIRST Driver");
MODULE_LICENSE("GPL");

分析

通常我们不需要记住这么多头文件,直接都包含进来也无所谓。
开头的宏定义:

#define DEVICE_NAME     "Exculivor" 
#define Exculivor_MAJOR     0

分别定义设备名称和主设备号。
其中设备名称可以在加载驱动后在/dev目录下看到。
主设备号被系统用来确定驱动程序(设备类型:如USB设备,硬盘设备),次设备号被驱动程序用来确定具体的设备。
在没有使用devfs(linux下专门用来管理设备的文件系统,同类的还有sysfs。2.6内核以前一直使用的是devfs)的时候,向系统增加一个驱动程序意味着要赋值它一个主设备号。这一赋值过程应该在驱动程序(模块)的初始化过程中完成。即调用如下函数:

int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);

返回值提示成功或者失败。返回一个负值,表示出错;返回零或正值,表示成功。
参数major是所请求的主设备号,name是你的设备的名字,它将在/proc/devices中出现,fops是一个指向函数队列的指针,利用它完成对设备函数的调用。
一旦设备驱动程序注册到内核表中,它的操作都与分配的主设备号匹配,何时在字符设备文件上操作都与它的主设备号相关联,内核都会通过file_operations结构体查找并调用相应的驱动程序中的函数。为了这个原因,传递给register_chrdev的指针应该是指向驱动程序中的全局结构体,而不是一个局部的一个模块初始化函数。

static struct class *Exculivor_drv_class;

struct class即设备类,这里需要讲到Linux设备模型。简单来说是Linux用来管理日趋复杂的设备类型的一种方式。相关知识以后讲解,此处先略过。
我们此时只需记住,需要在驱动初始化的代码里调用class_create为该设备创建一个class,再为每个设备调用device_create创建对应的设备节点。

static int Exculivor_drv_open(struct inode *inode, struct file *file)
{
    printk("%s:Hello Exculivor\n", __FUNCTION__);
    return 0;
}

static ssize_t Exculivor_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
    printk("%s:Hello Exculivor\n", __FUNCTION__);
    return 0;
}

static ssize_t Exculivor_drv_write(struct file *file, const char __user *buf, size_t size, loff_t *ppos)
{
    printk("%s:Hello Exculivor\n", __FUNCTION__);
    return 0;
}

这里的三个函数分别处理驱动的open、read、write操作。
其中使用到的数据结构:

int (*open)(struct inode *, struct file *);
ssize_t (*read)(struct file *, char __user *, size_t, loff_t *);
ssize_t (*write)(struct file *, char __user *, size_t, loff_t *);

是file_operations结构里面的字段,具体可以阅读《Linux设备驱动程序》第53页内容。
而inode结构,则是内核内部表示文件的方式。file表示打开的文件的描述符。对于单个文件,可能会有许多个表示打开的文件描述符file结构,但它们同时指向单个inode结构。这符合驱动文件可以被多个应用程序调用的特点。

static struct file_operations Exculivor_drv_fops = {
    .owner  = THIS_MODULE,
    .open   = Exculivor_drv_open,     
    .read   = Exculivor_drv_read,      
    .write  = Exculivor_drv_write,     
};

这便是前面提到的file_operations结构。我们需要将驱动的所有操作对应的函数指定到相应的字段中。
《Linux设备驱动程序》推荐一下这种简单好记的声明方式:

struct file_operations scull_fops = {
    .owner    = THIS_MODULE,
    .llseek   = scddp_llseek,
    .read     = scddp_read,
    .write    = scddp_write,
    .ioctl    = scddp_ioctl,
    .open     = scddp_open,
    .release  = scddp_release,
};

下面是驱动的初始化函数:

int major;

static int __init Exculivor_drv_init(void)
{
    major = register_chrdev(Exculivor_MAJOR, DEVICE_NAME, &Exculivor_drv_fops);
    if (major < 0)
    {
        printk(DEVICE_NAME " can't register major number\n");
        return major;
    }

    Exculivor_drv_class = class_create(THIS_MODULE, "Exculivor");
    device_create(Exculivor_drv_class, NULL, MKDEV(major, 0), NULL, "Exculivor");

    printk("%s:Hello Exculivor\n", __FUNCTION__);
    return 0;
}

其中定义major来存放注册主设备号的结果。

major = register_chrdev(Exculivor_MAJOR, DEVICE_NAME, &Exculivor_drv_fops);

即上面说过的注册设备的函数,将刚才设置好的Exculivor_drv_fops和主设备号Exculivor_MAJOR还有DEVICE_NAME传递给函数,进行设备注册。
失败则major<0执行:

if (major < 0)
    {
        printk(DEVICE_NAME " can't register major number\n");
        return major;
    }

成功则继续运行:

Exculivor_drv_class = class_create(THIS_MODULE, "Exculivor");

创建class。
之后便开始创建设备节点:

 device_create(Exculivor_drv_class, NULL, MKDEV(major, 0), NULL, "Exculivor");

下面一个函数则是相应的驱动退出函数:

static void __exit Exculivor_drv_exit(void)
{
    unregister_chrdev(major, "Exculivor");  
    device_destroy(Exculivor_drv_class, MKDEV(major, 0));
    class_destroy(Exculivor_drv_class);
    printk("%s:Hello Exculivor\n", __FUNCTION__);
}

负责执行设备反注册等工作。
执行shell命令make完了之后直接使用insmod命令加载模块。我们可以通过sudo dmesg命令看到驱动输出的信息:

[33061.187858] Exculivor_drv_init:Hello Exculivor

接下来我们写一个简单的应用程序来实现驱动提供的open、read、write等几个功能。

示例应用程序和相应讲解

#include <stdio.h>
#include <stdlib.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main( void )
{
    int fd;
    int Read_buffer[512];
    int temp;

    if( ( fd=open("/dev/Exculivor", O_RDWR, 0755) )<0 )
    {
        perror("open file failure!\n");
        exit( 1 );
    }
    else
    {
        printf("open device %d success!\n", fd);
    }

    if((temp = read(fd, Read_buffer, 512))<0)
    {
        perror("Read device error!\n");
        exit( 1 );
    }
    else
        printf("Read %d Bytes!\n", temp);
    if((temp = write(fd, Read_buffer, 512))<0)
    {
        perror("Write device error!\n");
        exit( 1 );
    }
    else
        printf("Write %d Bytes!\n", temp);
    close(fd);
    exit( 0 );
}

这里使用了几个简单的系统调用直接以访问文件的方式访问设备。
make并执行:

open device 3 success!
Read 0 Bytes!
Write 0 Bytes!

之后,我们就可以使用dmesg命令看到输出信息了:

[33279.790347] Exculivor_drv_open:Hello Exculivor
[33279.790702] Exculivor_drv_read:Hello Exculivor
[33279.790780] Exculivor_drv_write:Hello Exculivor

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值