您的位置:首页 > 运维架构 > Linux

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

2016-10-29 11:32 162 查看
一、Linux设备的分类

Linux系统将设备分成三种基本类型,每个模块通常实现为其中某一类:字符模块、块模块或网络模块。

这三种类型有:

字符设备:字符设备是个能够像字节流(类似文件)一样被访问的设备,由字符设备驱动程序来实现这种特性。字符设备可以通过文件系统节点来访问,比如/dev/tty1等。这些设备文件和普通文件之间的唯一差别在于对普通文件的访问可以前后移动访问位置,而大多数字符设备是一个个只能顺序访问的数据通道。

块设备:和字符设备类似,块设备也是通过/dev目录下的文件系统节点来访问。块设备(列如磁盘)上能够容纳文件系统。块设备和字符设备的区别仅仅在于内核内部管理数据的方式,也就是内核及驱动程序之间的软件接口,而这些不同对用户来讲是透明的。

网络设备:任何网络事务都经过一个网络接口形成,即一个能够和其他主机交换数据的设备。通常,接口是个硬件设备,但也可能是个纯软件设备,比如回环(loopback)接口。由于不是面向流的设备,因此将网络接口映射到文件系统中的节点比较困难。

二、字符设备驱动的基本知识
1、主设备号和次设备号

      一个字符设备或块设备都有一个主设备号和一个次设备号。通常而言,主设备号用来标识与设备文件相连的驱动程序,用来反映设备类型。次设备号被驱动程序用来辨别操作的是哪个设备,用来区分同类型的设备。

在内核中,dev_t类型(在<linux/types.h>中定义)用来保存设备编号--------包括这设备号和次设备号。

用MAJOR(dev_t dev);获得主设备号。用MINOR(dev_t dev);获得次设备号。用MKDEV(int
major,int minor)将主设备号和次设备号转换成设备号。

这些宏如下定义:

#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))


2、分配和释放设备号

在建立一个字符设备驱动之前,我们的驱动程序首先要做的事情就是获得一个或多个设备号。

(1)静态分配

int register_chrdev_region(dev_t from, unsigned count, const char *name);
其中first是要分配的设备编号范围的起始地址。count是所请求的连续设备号的个数。name是和该设备号关联的设备名称,它将出现在/proc/devices和sysfs中。成功返回0,在错误情况下返回一个负的错误码。

(2)动态分配

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
在上面的函数中,dev是仅用于输出的参数,在成功完成调用后将保存已分配范围的第一个设备号。firstminor应该是要使用的被请求的第一个次设备号,它通常是0。count和name参数和静态分配函数一样。

(3)释放设备号

在不用时将它们释放。

void unregister_chrdev_region(dev_t from, unsigned count);
通常,我们在模块的清除函数中调用它。

(4)创建设备文件

利用cat /proc/devices查看申请到的设备名,设备号。

1.利用mknod手工创建:mknod [选项]... 名称 类型 [主设备号 次设备号]

2.自动创建:利用udev(mdev)来实现设备文件的自动创建。

[b]三、字符设备驱动中一些重要的数据结构
[/b]

首先,看看Linux软件系统的层次关系:



我们在应用层调用open,read,write这类函数时,系统会调用驱动程序里的open,read,write这类函数。所以在设备驱动中我们需要构建这些函数,事实上,在字符设备驱动中,主要的任务也就是构造这些函数,下面来问几个问题。

1、构建这些open,read这类函数后,内核怎么知道它们呢?

答:

 a、通过file_operations结构。在file_operations结构里填充这些函数;

struct file_operations{
struct module *owner;

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


b、将file_operations结构体初始化到一个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;

};



struct cdev *cdev_alloc(void);//分配一个cdev结构


void cdev_init(struct cdev *cdev, const struct file_operations *fops);//初始化cdev,将file_operations结构加入cdev结构


c、将cde结构体加入到内核当中去,并与一个设备号范围连接。

int cdev_add(struct cdev *p, dev_t dev, unsigned count)//添加cdev到内核,并与一个设备号范围连接


谁来调用cdev_add呢?

答:驱动的入口函数。怎么知道它是入口函数呢?用一个宏module_init修饰。驱动的清除函数也一样。

接下来以一个hello驱动程序为例,方便大家理解:

#include <linux/module.h>

#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
4000

#include <linux/irq.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <linux/poll.h>
#include <linux/cdev.h>

/* 1. 确定主设备号 */
static int major;

static int hello_open(struct inode *inode, struct file *file)
{
printk("hello_open\n");
return 0;
}

/* 2. 构造file_operations */
static struct file_operations hello_fops = {
.owner = THIS_MODULE,
.open = hello_open,
};

#define HELLO_CNT 2

static struct cdev hello_cdev;
static struct class *cls;

static int hello_init(void)
{
dev_t devid;
/* 3. 告诉内核 */
if (major) {
devid = MKDEV(major, 0);
register_chrdev_region(devid, HELLO_CNT, "hello"); /* (major,0~1) 对应 hello_fops, (major, 2~255)都不对应hello_fops */
} else {
alloc_chrdev_region(&devid, 0, HELLO_CNT, "hello"); /* (major,0~1) 对应 hello_fops, (major, 2~255)都不对应hello_fops */
major = MAJOR(devid);

}
cdev_init(&hello_cdev, &hello_fops);

cdev_add(&hello_cdev, devid, HELLO_CNT);

/*利用udev(mdev)自动创建节点*/
cls = class_create(THIS_MODULE, "hello");
class_device_create(cls, NULL, MKDEV(major, 0), NULL, "hello0"); /* /dev/hello0 */
class_device_create(cls, NULL, MKDEV(major, 1), NULL, "hello1"); /* /dev/hello1 */
class_device_create(cls, NULL, MKDEV(major, 2), NULL, "hello2"); /* /dev/hello2 */

return 0;
}

static void hello_exit(void)
{
class_device_destroy(cls, MKDEV(major, 0));
class_device_destroy(cls, MKDEV(major, 1));
class_device_destroy(cls, MKDEV(major, 2));
class_destroy(cls);

cdev_del(&hello_cdev);
unregister_chrdev_region(MKDEV(major, 0), HELLO_CNT);
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");



                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息