第三章:字符设备驱动程序

原创 2012年03月29日 11:24:41

主设备号和次设备号

对字符设备的访问是通过文件系统内的设备名称进行的,他们通常位于/dev目录,在此目录下ls –l 命令可以查看所有设备,每一行第一列”c”表示字符设备,”b”表示块设备。

主设备号标识设备对应的驱动程序,虽然现代linux运行多个驱动程序共享主设备号,但是大多数设备依然按照“一个主设备号对应一个驱动程序”的原则组织。

次设备号有内核使用,可以获得指向内核设备的一个指针。

设备编号的内部表达

在内核中,dev_t类型(<linux/types.h>中定义)用来保存设备编号(主设备号以及次设备号)。

我们应该始终使用<linux/kdev_t.h>中的宏来获得主设备号以及次设备号:

MAJOR(dev_t dev);

MINOR(dev_t dev);

同样,可以将主设备号以及次设备号转换为对应的dev_t类型:

MKDEV(int major, int minor);

分配和释放设备编号

在建立一个字符设备之前,我们的驱动程序首先要获得一个或者多个设备编号。可以使用定义在<linux/fs.h>中的函数完成该工作。

int register_chrdev_region(dev_t first, unsigned int count, char *name);

在我们提前明确知道所需要的设备编号时,此函数会工作的很好。first是所要分配的设备编号范围的起始值,通常被设置为0count是所请求的连续设备的编号的个数。name是和该编号范围关联的设备名称,会出现在/proc/devices/sysfs中。

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

动态分配设备编号。dev是输出参数,在成功完成调用后保存已分配范围的第一个编号。firstminor应该是要使用的被请求的第一个此设备编号。

释放设备编号

void unregister_chrdev_region(dev_t first, unsigned int count);

通常,我们在模块的清楚函数中调用释放设备编号函数。

动态分配主设备号

作为驱动程序开发者,有两种选择:1.简单选定一个未被使用的编号;2.由系统动态分配一个设备编号。强烈建议使用第二种方法。

动态分配编号的缺点是:由于无法预知主设备号,所以无法预先创建设备节点。但一旦分配了设备号,就可以从/proc/devices中读取到。因此,对insmod的调用可替换为一个简单的脚本,该脚本在调用insmod之后,读取/proc/devices以获得新分配的设备编号,然后创建对应的设备文件。

一些重要的数据结构

文件操作

定义在<linux/fs.h>中的file_operations结构用来将任何驱动程序操作连接到已经申请的设备编号。

驱动程序通常的file_operations结构被初始化如下:

struct file_operations scull_fops = {
.owner  =  THIS_MODULE,
.llseek   =  scull_llseek,
.read    =  scull_read,
.write   =   scull_write,
.ioctl    =  scull_ioctl,
.open   =  scull_open,
.release  =  scull_release
}

此声明采用标准C的标记化结构初始化语法。它使驱动程序在结构发生变化时更具有可移植性。

file结构

定义在<linux/fs.h>中的struct file与用户空间的FILE没有任何关联。系统中每个打开的文件在内核空间都有一个对应的file结构。

inode结构

内核用inode结构在内部表示文件。file结构表示打开的文件描述符。对单个文件,可能会有许多个表示打开的文件描述符的file结构,但它们都指向单个inode结构。

字符设备的注册

内核内部使用包含在<linux/cdev.h>中的struct cdev结构来表示字符设备。

注册字符设备时可能用到的两个函数:

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

初始化以分配的结构。

int cdev_add(struct cdev *cdev, dev_t num, unsigned int count);

num是该设备对应的第一个设备编号,count是应该和该设备关联的设备编号的数量。此函数可能会返回失败,一般情况下会返回成功,返回成功以后我们的设备会被内核调用,因此一定要保证驱动程序完全准备好处理设备上的操作时才调用该函数。

从系统中移除一个字符设备调用如下函数:

void cdev_del(struct cdev *cdev);

早期的方法

注册一个字符设备驱动程序的经典方式是:
int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);

和它对应的从系统中移除设备的函数是:

int unregister_chrdev(unsigned int major, const char *name);

open方法

open方法提供给驱动程序以初始化的能力,一般情况下,open应该完成以下工作:

l 检查设备特定的错误(如设备未就绪或类似的硬件问题)

l 如果设备是首次打开,则对其进行初始化。

l 如有必要,更新f_op指针。

l 分配并填写置于filp->private_data里的数据结构。

open的原型为:int (*open)(struct inode *inode, struct file *filp);

<linux/kernel.h>中的container_of宏:container_of(pointer, container_type, container_field);可以通过一个结构体中某一个成员变量的指针得到指向这个结构体的指针。方法:通过指针偏移。

release方法

release方法主要完成以下工作:

l 释放由open分配,保存的filp->private_data中所有的内容

l 在最后一次关闭操作时关闭设备

内核在进程退出的时候,通过在内部使用close系统调用自动关闭所有相关的文件。

scull的内存使用

linux中用于内存管理的两个核心函数定义在<linux/slab.h>中,它们是

void *kmalloc(size_t size, int flags);

void kfree(void *ptr);

我们不可以将非kmalloc返回的指针传递给kfree,但是可以将NULL传递给kfree

readwrite

readwrite完成的任务很相似,他们的函数原型是:

ssize_t read(struct file *filp, char __user *buff, size_t count, loff_t *offp);

ssize_t write(struct file *filp, const char __user *buff, size_t count, loff_t *offp);

filp是文件指针,count是请求传输的长度,buff是指向用户空间的缓冲区的一个指针,offp是一个指向“长偏移量类型(long offset type)”对象的指针,这个对象指明用户在文件中进行存取操作的位置。

buff是指向用户空间的指针,内核不能直接引用其内容,所以大多数readwrite方法实现的核心部分是:

unsigned long copy_to_user(void __user *to, const void *form, unsigned long count);

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

这两个函数在函数内部进行了指针有效性的检查,如果不需要检查指针有效性的情况下,可以使用__copy_to_user, __copy_from_user函数,如果我们没有真正的检查指针就调用此函数,可能会导致内核崩溃或者建立安全漏洞。

访问用户空间的任何函数必须是可重入的,并且必须能够和其他驱动程序函数并发执行。

readvwritev

这两个函数用来一次read或者write若干个数据块。

查漏补缺

file_operations结构保存了字符驱动程序的方法,struct file表示一个打开的文件,struct inod 表示一个磁盘上的文件。

#include <asm/uaccess.h>该文件声明了在内核代码和用户空间之间移动数据的函数。

LINUX字符设备驱动程序原理总结

LINUX字符设备驱动程序原理总结  一)设备的输入/输出原理通常,任何数据都必须通过内核空间才能到达应用程序的缓冲上。例如:对一个设备的读操作会引起数据被至少复制两遍,一遍是将内容复制到内核缓冲中,...
  • jackred
  • jackred
  • 2010年07月08日 10:12
  • 574

第三章 字符设备驱动程序

《1》在内核中, dev_t 类型( 中定义)用来保存设备编号-----包括主设备号和次设备号。在内核2.6.0版本中,dev_t是一个32位的数,其中的12位用来表示主设备号,而其余20位用来表示次...
  • seleveny
  • seleveny
  • 2012年11月30日 20:01
  • 188

字符设备驱动程序(第三章 )

(1)dev_t是表示设备编号的数据类型:      #include      dev_t (2)从设备好重取出主/次设备编号:      int MAJOR(dev_t dev);  ...
  • cnxxrj
  • cnxxrj
  • 2013年11月19日 11:26
  • 742

第三章--字符设备驱动程序

本文作为第三章--字符设备驱动程序,主要讲述: 1、设备号。 2、驱动和应用层拷贝数据。...
  • apple_guet
  • apple_guet
  • 2014年03月11日 11:49
  • 661

第三章 字符设备驱动程序(笔记)

scull, 即“Simple Character Utility for Loading Localities,区域装载的简单字符工具“的缩写。 1. scull的设计     编写驱动程序的第...
  • eyardchen
  • eyardchen
  • 2012年05月25日 22:19
  • 210

字符设备驱动之Buttons-循环缓冲队列

buttons.c #include #include #include #include #include #include #include #i...
  • yicao821
  • yicao821
  • 2011年09月14日 17:09
  • 529

第三章 字符设备驱动程序——note

1.在内核中, dev_t 类型(在 中定义)用来持有设备编号 -- 主次部分都包括. 对于 2.6.0 内核, dev_t 是 32 位的量, 12 位用作主编号, 20 位用作次编号...
  • oLinXi1234567
  • oLinXi1234567
  • 2014年11月09日 22:38
  • 202

第三章字符设备驱动程序

linux2.6.10内核似乎和2.6.31有些不一样。make过程中出现很多问题,现在这里总结下:1、main.c# include → #include 2、access.ccurrent...
  • TsuiLei
  • TsuiLei
  • 2009年12月25日 09:23
  • 425

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

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

Linux高级字符设备驱动程序

设备ioctl控制 大部分驱动除了需要具备读写设备的能力外,还需要具备对硬件控制的能力。例如,要求硬件设备报告错误信息,改变波特率,这些操作常常通过ioctl方法来实现。 用户使用方法:在用户空间...
  • qq_27522735
  • qq_27522735
  • 2016年12月26日 16:38
  • 399
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:第三章:字符设备驱动程序
举报原因:
原因补充:

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