Linux驱动程序开发之字符设备驱动——基础篇(二)

转载 2012年09月14日 23:07:20

Linux驱动程序开发之字符设备驱动——基础篇(二)

转自:http://www.cnblogs.com/LakeFollow/archive/2012/07/30/2614475.html

Linux下的大部分驱动程序都是字符设备驱动程序,通过下面的学习我们将 会了解到字符设备是如何注册到系统中的,应用程序是如何访问驱动程序的数据的,及字符驱动程序是如何工作的。


设备号
通过前面的 学习我们知道应用程序是通过设备节点来访问驱动程序及设备的,其根本是通过设备节点的设备号(主设备号及从设备号)来关联驱动程序及设备的,字符设备也不 例外(其实字符设备只能这样访问)。这里我们详细讨论Linux内部如何管 理设备号的。

  • 设备号类型

Linux内核里用dev_t来表示设备 号,它是一个32位的无符号 数,其高12位用来表示主 设备号,低20位用来表示从 设备号。它被定义在<linux/types.h>头文件里。 内核里提供了操作“dev_t”的函数,驱动 程序中通过这些函数(其实是宏,定义在<linux/kdev_t.h>文件中)来 操作设备号。

#define MINORBITS    20
#define MINORMASK    ((1U << MINORBITS) - 1)
#define MAJOR(dev)    ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev)    ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi)    (((ma) << MINORBITS) | (mi))


MAJORdev)用于获取主设备号,MINORdev)用于获取从设备号,而MKDEVmami)用于通过主设备号和从设备号构造"dev_t"数据。
另一点需要 说明的是,dev_t数据类型支持2^12个主设备号,每个主设备号(通常是一个设备驱动)可以支持2^20个设备,目前来说这已经足够大了,但谁又能说将来还能满足要求 呢?一个良好的编程习惯是不要依赖dev_t这个数据类 型,切记必须使用内核提供的操作设备号的函数。

  • 字符设备号注册

内核提供了字符设备号管理的函数接口,作为一个良好的编程习惯,字符设备驱动程 序应该通过这些函数向系统注册或注销字符设备号。

int register_chrdev_region(dev_t from, unsigned count, const char *name)
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
            const char *name)
void unregister_chrdev_region(dev_t from, unsigned count)


register_chrdev_region用于向内核 注册已知可用的设备号(次设备号通常是0)范围。由 于历史的原因一些设备的设备号是固定的,你可以在内核源代码树的Documentation/devices.txt文件中找到 这些静态分配的设备号。

alloc_chrdev_region用于动态分 配的设备号并注册到内核中,分配的设备号通过dev参数返回。 作为一个良好的内核开发习惯,我们推荐你使用动态分配的方式来生成设备号。
unregister_chrdev_region用于注销一 个不用的设备号区域,通常这个函数在驱动程序卸载时被调用。


字符设备
Linux2.6内核使用“struct cdev”来记录字符设 备的信息,内核也提供了相关的函数来操作“struct cdev”对象,他们 定义在<linux/cdev.h>头文件中。 可见字符设备及其操作函数接口定义的很简单。

struct cdev {
    struct kobject kobj;
    struct module *owner;
    const struct file_operations *ops;
    struct list_head list;
    dev_t dev;
    unsigned int count;
};

void cdev_init(struct cdev *, const struct file_operations *);
struct cdev *cdev_alloc(void);
void cdev_put(struct cdev *p);
int cdev_add(struct cdev *, dev_t, unsigned);
void cdev_del(struct cdev *);


对于Linux 2.6内核来说,struct cdev是内核字符设备的基础结构,用来表示一个字符设备, 包含了字符设备需要的全部信息。

  • kobjstruct kobject对象数据,用 来描述设备的引用计数,是Linux设备模型的 基础结构。我们在后面的“Linux设备模型在做详细的介绍。
  • ownerstruct module对象数据,描 述了模块的属主,指向拥有这个结构的模块的指针,显然它只有对编译为模块方式的驱动才由意义。一般赋值位“THIS_MODULE”
  • opsstruct file_operations对象数据,描 述了字符设备的操作函数指针。对于设备驱动来说,这是一个很重要的数据成员,几乎所有的驱动都要用到这个对象,我们会在下面做详细介绍。
  • devdev_t对象数据,描述了字符设备的设备号。

内核提供了操作字符设备对象“struct cdev”的函数,我们 只能通过这些函数来操作字符设备,例如:初始化、注册、添加、移除字符设备。

  • cdev_alloc:用于动态 分配一个新的字符设备 cdev 对象,并对其 进行初始化。采用cdev_alloc分配的cdev对象需要显示的初始化ownerops对象。

// 参考drivers/scsi/st.c:st_probe 函数
struct cdev *cdev = NULL;
cdev = cdev_alloc();
// Error Processing
cdev->owner = THIS_MODULE;
cdev->ops = &st_fops;

 

  • cdev_init:用于初始 化一个静态分配的cdev对象,一般这 个对象会嵌入到其他的对象中。cdev_init会自动初始 化ops数据,因此应 用程序只需要显示的给owner对象赋值。cdev_init的功能与cdev_alloc基本相同,唯 一的区别是cdev_init初始化一个 已经存在的cdev对象,并且这 个初始化会影响到字符设备删除函数(cdev_del)的行为, 请参考cdev_del函数。
  • cdev_add:向内核系 统中添加一个新的字符设备cdev,并且使它立 即可用。
  • cdev_del:从内核系 统中移除cdev字符设备。如 果字符设备是由cdev_alloc动态分配的, 则会释放分配的内存。
  • cdev_put:减少模块 的引用计数,一般很少会有驱动程序直接调用这个函数。

文件操作对象
Linux中的所有设备都是文件,内核中用“struct file”结构来表示一 个文件。尽管我们的驱动不会直接使用这个结构中的大部分对象,其中的一些数据成员还是很重要的,我们有必要在这里做一些介绍,具体的内容请参考内核源代码 树<linux/fs.h>头文件。

// struct file 中的一些重要数据成员
const struct file_operations    *f_op;
unsigned int         f_flags;
mode_t            f_mode;
loff_t            f_pos;
struct address_space    *f_mapping;


这里我们不对struct file做过多的介绍,另一篇struct file将做详细介绍。这个结构中的f_ops成员是我们的驱动所关心的,它是一个struct file_operations结构。Linux里的struct file_operations结构描述了一 个文件操作需要的所有函数,它定义在<linux/fs.h>头文件中。

struct file_operations {
    struct module *owner;
    loff_t (*llseek) (struct file *, loff_t, int);
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
    ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
    ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
    int (*readdir) (struct file *, void *, filldir_t);
    unsigned int (*poll) (struct file *, struct poll_table_struct *);
    int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
    long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
    int (*mmap) (struct file *, struct vm_area_struct *);
    int (*open) (struct inode *, struct file *);
    int (*flush) (struct file *, fl_owner_t id);
    int (*release) (struct inode *, struct file *);
    int (*fsync) (struct file *, struct dentry *, int datasync);
    int (*aio_fsync) (struct kiocb *, int datasync);
    int (*fasync) (int, struct file *, int);
    int (*lock) (struct file *, int, struct file_lock *);
    ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
    unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
    int (*check_flags)(int);
    int (*dir_notify)(struct file *filp, unsigned long arg);
    int (*flock) (struct file *, int, struct file_lock *);
    ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
    ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
    int (*setlease)(struct file *, long, struct file_lock **);
};


这是一个很大的结构,包含了所有的设备操作函数指针。当然,对于 一个驱动,不是所有的接口都需要来实现的。对于一个字符设备来说,一般实现openreleasereadwritemmapioctl这几个函数就足够了。
这里需要指 出的是,openrelease函数的第一个参数是一个struct inode对象。这是一个内核文件系统索引节点对象,它包含 了内核在操作文件或目录是需要的全部信息。对于字符设备驱动来说,我们关心的是从struct inode对象中获取设备号(inodei_rdev成员)内核提供了两个函数来做这件事。

static inline unsigned iminor(const struct inode *inode)
{
    return MINOR(inode->i_rdev);
}
static inline unsigned imajor(const struct inode *inode)
{
    return MAJOR(inode->i_rdev);
}


尽管我们可 以直接从inode->i_rdev获取设备 号,但是尽量不要这样做。我们推荐你调用内核提供的函数来获取设备号,这样即使将来inode->i_rdev有所变化, 我们的程序也会工作的很好。

 

字符设备驱 动可以参考Linux 设备驱动程序 第三版和linux设备驱动开发 详解,其中linux设备驱动程序 第三版中讲的:

主次编号

一些重要数据结构

字符设备注册

Openrelease

读和写

一些头文件和结构体;

都非常经典, 都理解字符驱动设备很重要,很值得参考!


linux设备驱动第三篇:写一个简单的字符设备驱动

在linux设备驱动第一篇:设备驱动程序简介中简单介绍了字符驱动,本篇简单介绍如何写一个简单的字符设备驱动。本篇借鉴LDD中的源码,实现一个与硬件设备无关的字符设备驱动,仅仅操作从内核中分配的一些内存...
  • HAOMCU
  • HAOMCU
  • 2015年03月28日 19:05
  • 23852

简单字符设备驱动流程

1.linux系统将设备分为3类:字符设备、块设备、网络设备。使用驱动程序: 其中,字符设备是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后数据。字符设...
  • zq979999
  • zq979999
  • 2015年09月10日 18:36
  • 1361

LINUX设备驱动程序(第3版)[高清PDF]

下载地址:网盘下载 内容简介 编辑 《LINUX设备驱动程序(第3版)》已针对Linux内核的2610版本彻底更新过了。内核的这个版本针对常见任务完成了合理化设计及相应的...
  • cf406061841
  • cf406061841
  • 2017年05月29日 10:07
  • 1557

字符型设备驱动程序--gpio 驱动实例

概述: 字符设备驱动程序: 是按照字符设备要求完成的由操作系统调用的代码。 重点理解以下内容:  1. 驱动是写给操作系统的代码,它不是直接给用户层程序调用的,而是给系统调用的  2. 所以驱动要向系...
  • hejinjing_tom_com
  • hejinjing_tom_com
  • 2014年05月09日 18:03
  • 4819

字符设备驱动开发流程详解

字符驱动相关概念解析 一、驱动初始化 1.1分配设备描述结构 1.2初始化设备描述结构 1.3.注册设备描述结构 1.4.硬件初始化 二、实现设备操作 2.1...
  • liu0808
  • liu0808
  • 2016年11月15日 09:37
  • 1082

Linux设备驱动之字符设备驱动

一、linux系统将设备分为3类:字符设备、块设备、网络设备。 应用程序调用的流程框图: 三种设备的定义分别如下, 字符设备:只能一个字节一个字节的读写的设备,不能随机读取设备内...
  • andylauren
  • andylauren
  • 2016年07月01日 19:36
  • 4699

字符设备驱动编写流程以及大概框架

本文主要是针对Linux的字符设备驱动,Linux设备驱动中字符设备驱动的注册类型与注册方式 字符设备是Linux驱动中最简单的驱动,也是最常的驱动! 字符设备驱动除了注册为字符设备类还可以注册为混杂...
  • softwoker
  • softwoker
  • 2015年04月18日 14:40
  • 2639

Linux应用程序访问字符设备驱动详细过程解析

在linux下对上面的文件进行静态编译(考虑到前面开发板上移植的某些库还没有添加进去)生成read-mem目标文件,然后进行反汇编并将反汇编生成的文件导入到当前目录下的dump上去。 这里红箭头指向的...
  • coding__madman
  • coding__madman
  • 2016年05月08日 18:52
  • 4851

嵌入式Linux字符设备驱动LED驱动编写

嵌入式Linux字符设备驱动LED驱动编写 标签: linux内核 2015-04-30 14:41 105人阅读 评论(0) 收藏 举报  分类:   Linux开...
  • chinazhangzhong123
  • chinazhangzhong123
  • 2016年06月01日 21:50
  • 899

Linux设备驱动程序学习笔记02:编写编译并运行驱动程序

学习一个新东西最好的方式就是去实践它。在实践的过程中会不断遇到问题、产生疑问。解决这些问题的过程就是我们进步成长的过程。 一、如何写驱动程序 在学习C语言的时候有一个著名的hello world程...
  • CheerMoon2009
  • CheerMoon2009
  • 2014年07月19日 22:10
  • 1370
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Linux驱动程序开发之字符设备驱动——基础篇(二)
举报原因:
原因补充:

(最多只允许输入30个字)